From 1ee78d22ba1a62e0d7df619aa0b47cd28570744b Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sat, 31 Oct 2026 17:43:24 -0700 Subject: [PATCH 1/2] feat(byo): the page names the Terminal command when only the MCP snippet was copied (TASK-721) After three minutes waiting on a check-in, if the person copied the MCP snippet but not the listener command, the waiting line says so and points at the Terminal command above. Tracks copies per kind for the current issue, via the copy button or a manual select-and-copy. Co-Authored-By: Claude Sonnet 5.5 Co-authored-by: Keel Agent-Model: claude/claude-opus-5-5/high --- frontend/src/i18n/locales/en.json | 1 + frontend/src/i18n/locales/zh-CN.json | 1 + .../__tests__/V2AgentBYOListenVerify.test.tsx | 37 +++++++++++++++++ frontend/src/v2/components/V2AgentBYO.tsx | 40 ++++++++++++++++--- 4 files changed, 73 insertions(+), 6 deletions(-) diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json index 9f4722335c..67d52c5cd9 100644 --- a/frontend/src/i18n/locales/en.json +++ b/frontend/src/i18n/locales/en.json @@ -1737,6 +1737,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.", + "missedCommand": "You copied the MCP snippet, but not the Terminal command that starts {{name}}. The snippet lets a tool call Commonly; only the Terminal command lets {{name}} answer mentions. Copy it above and run it in Terminal on your computer.", "stopsOnClose": "This stops when this Terminal window closes. For an agent that keeps running, add this computer under Other ways to connect, then choose On my computer.", "stopsOnCloseWithComputer": "This stops when this Terminal window closes. For an agent that keeps running, start another agent and choose On my computer.", "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.", diff --git a/frontend/src/i18n/locales/zh-CN.json b/frontend/src/i18n/locales/zh-CN.json index 86754ad1e5..055303810c 100644 --- a/frontend/src/i18n/locales/zh-CN.json +++ b/frontend/src/i18n/locales/zh-CN.json @@ -1796,6 +1796,7 @@ "waiting": "正在等待 {{name}} 接入…命令一接通,这里就会变绿。", "verified": "{{name}} 已在监听 —— @提及 会唤醒它。", "stillWaiting": "{{name}} 还没接入。请在你电脑的终端里运行上面的命令,这里会自动确认。", + "missedCommand": "你复制了 MCP 片段,但没有复制启动 {{name}} 的终端命令。MCP 片段只能让工具调用 Commonly;只有终端命令才能让 {{name}} 回复 @提及。请复制上面的命令,在你电脑的终端里运行。", "stopsOnClose": "关闭这个终端窗口后就会停止。如果希望智能体持续运行,请在「其他接入方式」里添加这台电脑,然后选择「在我的电脑上」。", "stopsOnCloseWithComputer": "关闭这个终端窗口后就会停止。如果希望智能体持续运行,请再添加一个智能体,然后选择「在我的电脑上」。", "npmPermission": "如果 npm 报 EACCES(权限被拒绝),不要加 sudo。请用版本管理工具(如 nvm)重新安装 Node,然后重新运行这四行。", diff --git a/frontend/src/v2/__tests__/V2AgentBYOListenVerify.test.tsx b/frontend/src/v2/__tests__/V2AgentBYOListenVerify.test.tsx index b27a50cab7..c98d2d23fb 100644 --- a/frontend/src/v2/__tests__/V2AgentBYOListenVerify.test.tsx +++ b/frontend/src/v2/__tests__/V2AgentBYOListenVerify.test.tsx @@ -143,6 +143,43 @@ describe('V2AgentBYO listening verification', () => { ).length).toBe(readsAfterFlip); }); + // TASK-721: a stranger copied the MCP snippet three times and never the + // Terminal command (walk 2026-10-31). After three minutes the page names it. + describe('the missed Terminal command', () => { + const copyBlock = (matcher) => fireEvent.copy(screen.getByText(matcher).closest('.v2-byo__command')); + const waitingText = () => screen.getByTestId('byo-listen-waiting').textContent; + + test('MCP snippet copied, listener command not, after three minutes: names the Terminal command', async () => { + jest.useFakeTimers(); + await issueToken(); + copyBlock(/claude mcp add/); + + await act(async () => { jest.advanceTimersByTime(2 * 60 * 1000); }); + expect(waitingText()).not.toMatch(/not the Terminal command/); + + await act(async () => { jest.advanceTimersByTime(61 * 1000); }); + expect(waitingText()).toMatch(/copied the MCP snippet, but not the Terminal command that starts sam-claude/); + }); + + test('the listener command copied too: keeps the plain waiting line', async () => { + jest.useFakeTimers(); + await issueToken(); + copyBlock(/claude mcp add/); + copyBlock(/commonly agent run sam-claude/); + + await act(async () => { jest.advanceTimersByTime(4 * 60 * 1000); }); + expect(waitingText()).not.toMatch(/not the Terminal command/); + }); + + test('nothing copied at all: keeps the plain waiting line', async () => { + jest.useFakeTimers(); + await issueToken(); + + await act(async () => { jest.advanceTimersByTime(4 * 60 * 1000); }); + expect(waitingText()).not.toMatch(/not the Terminal command/); + }); + }); + test('a transient poll failure keeps waiting rather than erroring', async () => { jest.useFakeTimers(); await issueToken(); diff --git a/frontend/src/v2/components/V2AgentBYO.tsx b/frontend/src/v2/components/V2AgentBYO.tsx index bab529d6f6..7b694dc4a6 100644 --- a/frontend/src/v2/components/V2AgentBYO.tsx +++ b/frontend/src/v2/components/V2AgentBYO.tsx @@ -128,6 +128,10 @@ const podIsConfirmedPrivate = (pod?: V2Pod): boolean => ( && !pod.communityListed && pod.joinPolicy === 'invite-only' ); +// How long a person may wait, MCP snippet copied and listener command not, before +// the page names the command they missed. +const MISSED_LISTENER_AFTER_MS = 3 * 60 * 1000; + const listenerRunsOn = (os: SetupOs, adapter: 'claude' | 'codex', pod?: V2Pod): boolean => ( os === 'mac' || (os === 'linux' && adapter === 'claude' && podIsConfirmedPrivate(pod)) ); @@ -260,6 +264,12 @@ const V2AgentBYO: React.FC = () => { // finish the page, mention the agent, and get silence with no way to know // which step they missed. 'waiting' → 'listening' | 'timeout'. const [listenState, setListenState] = useState<'waiting' | 'listening' | 'timeout'>('waiting'); + // Which command the person copied for THIS issue, and whether they have been + // waiting long enough to say so. A stranger copied the MCP snippet three + // times and never the Terminal command (walk 2026-10-31, TASK-721): the page + // then names the one they missed instead of waiting in silence. + const [copiedKinds, setCopiedKinds] = useState({ mcp: false, listen: false }); + const [missedWindowOver, setMissedWindowOver] = useState(false); // ADR-023 W2 "Run it here" — the zero-terminal path. Offered only when the // instance reports a configured hosted runtime, and the default there: the // stranger this page exists for has no runtime to bring. The kernel mints @@ -928,6 +938,16 @@ const V2AgentBYO: React.FC = () => { } }; + // A new issue starts a new copy record and a new clock. Three minutes is long + // enough to run a pasted command; the server nudge in the room waits longer. + useEffect(() => { + setCopiedKinds({ mcp: false, listen: false }); + setMissedWindowOver(false); + if (!issued) return undefined; + const timer = setTimeout(() => setMissedWindowOver(true), MISSED_LISTENER_AFTER_MS); + return () => clearTimeout(timer); + }, [issued?.issuedAt]); + // Poll the kernel for THIS agent's first listener check-in after // issuance (an events poll or socket connect, not a tool call). // `/api/users/me/agent-connection` reports the caller's latest @@ -1030,6 +1050,10 @@ const V2AgentBYO: React.FC = () => { }; }, [api, issued, offerSessionKey, hasCopied, hosting?.configured, listenState]); + const markCopiedKind = (kind: 'mcp' | 'listen') => { + setCopiedKinds((prev) => (prev[kind] ? prev : { ...prev, [kind]: true })); + }; + const copiedIssuedContent = () => { rememberHostedOffer(); setHasCopied(true); @@ -1040,6 +1064,8 @@ const V2AgentBYO: React.FC = () => { try { await navigator.clipboard.writeText(value); copiedIssuedContent(); + if (key === 'claude' || key === 'cursor') markCopiedKind('mcp'); + else if (key === 'listen') markCopiedKind('listen'); setCopied(key); setTimeout(() => setCopied((c) => (c === key ? null : c)), 1500); if (key === 'claude' || key === 'cursor') byoStep('mcp-command-copied'); @@ -1682,7 +1708,7 @@ const V2AgentBYO: React.FC = () => {

{listenerAvailable && ( -
+
{ copiedIssuedContent(); markCopiedKind('listen'); }}>
{listenSnippet}
{t('agentByo.snippets.cursorInstall')}

{t('agentByo.snippets.cursorInstallHint')}

-
+
{ copiedIssuedContent(); markCopiedKind('mcp'); }}>
{cursorSnippet}