diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json index ecc980e131..0de3fb82e6 100644 --- a/frontend/src/i18n/locales/en.json +++ b/frontend/src/i18n/locales/en.json @@ -1711,6 +1711,7 @@ "waiting": "Waiting for {{name}} to check in… this turns green the moment the command connects.", "verified": "{{name}} is listening — mentions will wake it.", "stillWaiting": "Still no check-in from {{name}}. Run the command above in Terminal on your computer, and this page will confirm it.", + "npmPermission": "If npm stops with EACCES (permission denied), don’t add sudo. Reinstall Node with a version manager such as nvm, then run the four lines again.", "ctaWarning": "{{name}} isn’t listening yet: it won’t answer mentions until the command above is running.", "needs": "Needs Node 20+ with npm and {{tool}} signed in. Run all four lines in one window: the exports last only there.", "osLabel": "Setting up on:", diff --git a/frontend/src/i18n/locales/zh-CN.json b/frontend/src/i18n/locales/zh-CN.json index ec401c9906..8de5961df7 100644 --- a/frontend/src/i18n/locales/zh-CN.json +++ b/frontend/src/i18n/locales/zh-CN.json @@ -1771,6 +1771,7 @@ "waiting": "正在等待 {{name}} 接入…命令一接通,这里就会变绿。", "verified": "{{name}} 已在监听 —— @提及 会唤醒它。", "stillWaiting": "{{name}} 还没接入。请在你电脑的终端里运行上面的命令,这里会自动确认。", + "npmPermission": "如果 npm 报 EACCES(权限被拒绝),不要加 sudo。请用版本管理工具(如 nvm)重新安装 Node,然后重新运行这四行。", "ctaWarning": "{{name}} 尚未监听:上面的命令运行起来之前,它不会回复 @提及。", "needs": "需要 Node 20+(含 npm)和已登录的 {{tool}}。四行都要在同一个窗口里运行,换了窗口 export 就失效。", "osLabel": "你的系统:", diff --git a/frontend/src/v2/__tests__/V2AgentBYOListenSnippet.test.tsx b/frontend/src/v2/__tests__/V2AgentBYOListenSnippet.test.tsx index dd3b1e5481..f3d4a89f00 100644 --- a/frontend/src/v2/__tests__/V2AgentBYOListenSnippet.test.tsx +++ b/frontend/src/v2/__tests__/V2AgentBYOListenSnippet.test.tsx @@ -159,6 +159,15 @@ describe('BYO listener snippet', () => { expect(zhNeeds).toMatch(new RegExp(`需要 Node ${CLI_NODE_FLOOR}\\+`)); }); + test('an npm EACCES names the fix and says not to use sudo, right after the command', async () => { + await issueToken(); + const hint = screen.getByTestId('byo-listen-npm-hint'); + expect(hint).toHaveTextContent(/npm stops with EACCES/); + expect(hint).toHaveTextContent(/don’t add sudo/); + // eslint-disable-next-line no-bitwise + expect(listenCommand().compareDocumentPosition(hint) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy(); + }); + test('"I use Codex instead" gives a Codex command and says Codex is what it needs', async () => { await issueToken(); fireEvent.click(screen.getByTestId('byo-listen-adapter-switch')); @@ -254,6 +263,7 @@ describe('BYO listener snippet', () => { expect(notice).toHaveTextContent(/Claude Code can’t answer @mentions from Windows yet/); expect(notice).toHaveTextContent(/with the command below, or choose “Run it here”/); expect(notice).not.toHaveTextContent(/WSL/); + expect(screen.queryByTestId('byo-listen-npm-hint')).toBeNull(); expect(mcpBox().split('\n')).toEqual([ 'npm i -g @commonlyai/mcp', expect.stringMatching(/^claude mcp add commonly -e COMMONLY_API_URL=\S+ -e COMMONLY_AGENT_TOKEN=cm_agent_test_token -- cmd \/c commonly-mcp$/), @@ -271,11 +281,13 @@ describe('BYO listener snippet', () => { expect(listener()).toBeUndefined(); expect(screen.queryByTestId('byo-listen-reach')).toBeNull(); expect(screen.getByTestId('byo-listen-unavailable')).toHaveTextContent(/from Linux yet/); + expect(screen.queryByTestId('byo-listen-npm-hint')).toBeNull(); expect(mcpBox()).toMatch(/-- npx -y @commonlyai\/mcp$/); expect(screen.getByTestId('byo-mcp-claude')).not.toHaveTextContent(/command above|Optional/); fireEvent.click(screen.getByTestId('byo-listen-adapter-switch')); expect(listener()).toBeUndefined(); expect(screen.getByTestId('byo-listen-unavailable')).toHaveTextContent(/The Codex CLI can’t answer @mentions from Linux yet\. .*Linux is not tested yet\./); + expect(screen.queryByTestId('byo-listen-npm-hint')).toBeNull(); }); test('Codex on Windows says so, and offers hosted or another machine', async () => { @@ -285,6 +297,7 @@ describe('BYO listener snippet', () => { expect(listener()).toBeUndefined(); expect(screen.getByTestId('byo-listen-unavailable')) .toHaveTextContent(/The Codex CLI can’t answer @mentions from Windows yet\. Choose “Run it here”/); + expect(screen.queryByTestId('byo-listen-npm-hint')).toBeNull(); expect(screen.queryByTestId('byo-mcp-claude')).toBeNull(); }); diff --git a/frontend/src/v2/components/V2AgentBYO.tsx b/frontend/src/v2/components/V2AgentBYO.tsx index d7571f97ab..072eeff44f 100644 --- a/frontend/src/v2/components/V2AgentBYO.tsx +++ b/frontend/src/v2/components/V2AgentBYO.tsx @@ -1563,6 +1563,11 @@ const V2AgentBYO: React.FC = () => { )} + {listenerAvailable && ( +
+ {t('agentByo.listen.npmPermission')} +
+ )} {!listenerAvailable ? null : listenState === 'listening' ? ({t('agentByo.listen.verified', { name: issued.agentName })}