Fix Feishu MCP npx resolution
This commit is contained in:
+18
-2
@@ -2283,15 +2283,27 @@ def _feishu_paths(state: AgentState, account_id: str | None) -> dict[str, Path]:
|
|||||||
|
|
||||||
def _feishu_command() -> list[str]:
|
def _feishu_command() -> list[str]:
|
||||||
return [
|
return [
|
||||||
'npx',
|
_feishu_npx_executable(),
|
||||||
'-y',
|
'-y',
|
||||||
f'--registry={FEISHU_NPM_REGISTRY}',
|
f'--registry={FEISHU_NPM_REGISTRY}',
|
||||||
FEISHU_MCP_PACKAGE,
|
FEISHU_MCP_PACKAGE,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _feishu_npx_executable() -> str:
|
||||||
|
explicit = os.environ.get('CLAW_NPX_BIN')
|
||||||
|
if explicit and Path(explicit).expanduser().exists():
|
||||||
|
return str(Path(explicit).expanduser())
|
||||||
|
node_bin_dir = os.environ.get('CLAW_NODE_BIN_DIR')
|
||||||
|
if node_bin_dir:
|
||||||
|
candidate = Path(node_bin_dir).expanduser() / 'npx'
|
||||||
|
if candidate.exists():
|
||||||
|
return str(candidate)
|
||||||
|
return shutil.which('npx') or 'npx'
|
||||||
|
|
||||||
|
|
||||||
def _feishu_env(paths: dict[str, Path]) -> dict[str, str]:
|
def _feishu_env(paths: dict[str, Path]) -> dict[str, str]:
|
||||||
return {
|
env = {
|
||||||
'HOME': str(paths['home']),
|
'HOME': str(paths['home']),
|
||||||
'XDG_CONFIG_HOME': str(paths['config']),
|
'XDG_CONFIG_HOME': str(paths['config']),
|
||||||
'npm_config_cache': str(paths['npm_cache']),
|
'npm_config_cache': str(paths['npm_cache']),
|
||||||
@@ -2299,6 +2311,10 @@ def _feishu_env(paths: dict[str, Path]) -> dict[str, str]:
|
|||||||
'NO_UPDATE_NOTIFIER': 'true',
|
'NO_UPDATE_NOTIFIER': 'true',
|
||||||
'FEISHU_LOGIN_MODE': 'devicecode',
|
'FEISHU_LOGIN_MODE': 'devicecode',
|
||||||
}
|
}
|
||||||
|
node_bin_dir = os.environ.get('CLAW_NODE_BIN_DIR')
|
||||||
|
if node_bin_dir:
|
||||||
|
env['PATH'] = f'{node_bin_dir}{os.pathsep}{os.environ.get("PATH", "")}'
|
||||||
|
return env
|
||||||
|
|
||||||
|
|
||||||
def _run_feishu_cli(
|
def _run_feishu_cli(
|
||||||
|
|||||||
@@ -20,6 +20,15 @@ export OPENAI_API_KEY
|
|||||||
export OPENAI_BASE_URL="${OPENAI_BASE_URL:-http://model.mify.ai.srv/v1}"
|
export OPENAI_BASE_URL="${OPENAI_BASE_URL:-http://model.mify.ai.srv/v1}"
|
||||||
export OPENAI_MODEL="${OPENAI_MODEL:-xiaomi/mimo-v2-flash}"
|
export OPENAI_MODEL="${OPENAI_MODEL:-xiaomi/mimo-v2-flash}"
|
||||||
|
|
||||||
|
# 后端也会调用 Node 生态工具,例如 feishu-mcp-pro。systemd 不读取交互式
|
||||||
|
# shell 的 nvm 配置,所以需要把部署时记录的 Node 路径显式传给 Python 进程。
|
||||||
|
if [[ -n "${CLAW_NODE_BIN_DIR:-}" ]]; then
|
||||||
|
export PATH="${CLAW_NODE_BIN_DIR}:${PATH}"
|
||||||
|
fi
|
||||||
|
export CLAW_NPM_BIN="${CLAW_NPM_BIN:-}"
|
||||||
|
export CLAW_NODE_BIN="${CLAW_NODE_BIN:-}"
|
||||||
|
export CLAW_NODE_BIN_DIR="${CLAW_NODE_BIN_DIR:-}"
|
||||||
|
|
||||||
BACKEND_HOST="${CLAW_BACKEND_HOST:-127.0.0.1}"
|
BACKEND_HOST="${CLAW_BACKEND_HOST:-127.0.0.1}"
|
||||||
BACKEND_PORT="${CLAW_BACKEND_PORT:-8765}"
|
BACKEND_PORT="${CLAW_BACKEND_PORT:-8765}"
|
||||||
PYTHON_BIN="${CLAW_PYTHON_BIN:-${ROOT_DIR}/.venv/bin/python}"
|
PYTHON_BIN="${CLAW_PYTHON_BIN:-${ROOT_DIR}/.venv/bin/python}"
|
||||||
|
|||||||
@@ -114,6 +114,28 @@ class FeishuIntegrationTests(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(url, 'https://mi.feishu.cn/docx/CZIldpM3QofbbXxxAq1cEininTd')
|
self.assertEqual(url, 'https://mi.feishu.cn/docx/CZIldpM3QofbbXxxAq1cEininTd')
|
||||||
|
|
||||||
|
def test_feishu_command_uses_deployed_node_bin_dir(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as tmp_dir, patch.dict(
|
||||||
|
gui_server.os.environ,
|
||||||
|
{'CLAW_NODE_BIN_DIR': tmp_dir, 'PATH': ''},
|
||||||
|
clear=False,
|
||||||
|
):
|
||||||
|
npx_path = Path(tmp_dir) / 'npx'
|
||||||
|
npx_path.write_text('#!/usr/bin/env node\n', encoding='utf-8')
|
||||||
|
npx_path.chmod(0o755)
|
||||||
|
|
||||||
|
command = gui_server._feishu_command()
|
||||||
|
env = gui_server._feishu_env(
|
||||||
|
{
|
||||||
|
'home': Path(tmp_dir) / 'home',
|
||||||
|
'config': Path(tmp_dir) / 'config',
|
||||||
|
'npm_cache': Path(tmp_dir) / 'npm-cache',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(command[0], str(npx_path))
|
||||||
|
self.assertTrue(env['PATH'].startswith(tmp_dir))
|
||||||
|
|
||||||
def test_create_online_doc_rejects_jsonl_for_now(self) -> None:
|
def test_create_online_doc_rejects_jsonl_for_now(self) -> None:
|
||||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||||
client, state = _build_client(Path(tmp_dir))
|
client, state = _build_client(Path(tmp_dir))
|
||||||
|
|||||||
Reference in New Issue
Block a user