From 3bb63bcb88a3c8332f7a7ad0153a055de65e92f9 Mon Sep 17 00:00:00 2001
From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com>
Date: Sat, 31 Oct 2026 16:16:51 -0700
Subject: [PATCH 1/2] feat(byo): fold the MCP block under the listener, and say
first when there is no listener (TASK-720)
Co-Authored-By: Claude Sonnet 5.5
Co-authored-by: Ember
Agent-Model: claude/claude-opus-5-5/medium
---
frontend/src/i18n/locales/en.json | 4 +-
frontend/src/i18n/locales/zh-CN.json | 4 +-
.../V2AgentBYOListenSnippet.test.tsx | 38 ++++++++-
frontend/src/v2/components/V2AgentBYO.tsx | 79 +++++++++++++------
4 files changed, 98 insertions(+), 27 deletions(-)
diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json
index 0bc407ef8b..9f4722335c 100644
--- a/frontend/src/i18n/locales/en.json
+++ b/frontend/src/i18n/locales/en.json
@@ -1700,7 +1700,8 @@
"claudeCodeBody": "Gives your Claude Code sessions Commonly’s tools. Answering @mentions needs the command above.",
"claudeCodeBodyOwn": "Gives your own Claude Code sessions Commonly’s tools.",
"claudeCodeBodyWindows": "Paste both lines in PowerShell. They give your own Claude Code sessions on this machine Commonly’s tools.",
- "claudeCodeMain": "Let Claude Code use the pod in your own sessions"
+ "claudeCodeMain": "Let Claude Code use the pod in your own sessions",
+ "mcpSummary": "Claude Code in your own sessions. This does not answer @mentions."
},
"memory": {
"doneLead": "Memory imported —",
@@ -1751,6 +1752,7 @@
"unavailableClaudeLinux": "On Linux, Claude Code answers @mentions only in an invite-only pod, and this one isn’t. Its owner can switch it to Invite-only under “Who can join” in pod settings. Or connect Claude Code from your own sessions with the command below, or choose “Run it here” for a hosted agent.",
"unavailableClaude": "Claude Code can’t answer @mentions from {{os}} yet: Commonly runs it in a sandbox, and on {{os}} that sandbox can’t run it. Connect Claude Code from your own sessions with the command below, or choose “Run it here” for a hosted agent.",
"unavailableCodex": "The Codex CLI can’t answer @mentions from {{os}} yet. Choose “Run it here” for a hosted agent, or run the command on a Mac. Linux is not tested yet.",
+ "wontAnswer": "{{name}} will not answer @mentions from here.",
"useDifferentRunner": "Use a different adapter",
"repo": {
"label": "Work in my repo",
diff --git a/frontend/src/i18n/locales/zh-CN.json b/frontend/src/i18n/locales/zh-CN.json
index 3fdb47e2c4..86754ad1e5 100644
--- a/frontend/src/i18n/locales/zh-CN.json
+++ b/frontend/src/i18n/locales/zh-CN.json
@@ -1759,7 +1759,8 @@
"claudeCodeBody": "为你的 Claude Code 会话加上 Commonly 的工具。回复 @提及需要上面的命令。",
"claudeCodeBodyOwn": "为你自己的 Claude Code 会话加上 Commonly 的工具。",
"claudeCodeBodyWindows": "两行都粘贴到 PowerShell 里运行。这样你在这台电脑上的 Claude Code 会话就能用上 Commonly 的工具。",
- "claudeCodeMain": "让 Claude Code 在你自己的会话里使用这个 Pod"
+ "claudeCodeMain": "让 Claude Code 在你自己的会话里使用这个 Pod",
+ "mcpSummary": "在你自己的会话里使用 Claude Code。这不会回复 @提及。"
},
"memory": {
"doneLead": "记忆已导入——",
@@ -1809,6 +1810,7 @@
"unavailableClaudeLinux": "在 Linux 上,Claude Code 只在仅限邀请的 Pod 里回复 @提及,而这个 Pod 不是。Pod 的所有者可以在 Pod 设置的「谁可以加入」里改成「仅限邀请」。你也可以用下面的命令把 Commonly 的工具接进自己的 Claude Code 会话,或者选择「在这里运行」,改用托管智能体。",
"unavailableClaude": "Claude Code 在 {{os}} 上暂时不能回复 @提及:Commonly 要把它放进沙箱里运行,而 Commonly 用的沙箱在 {{os}} 上还做不到。你可以用下面的命令把 Commonly 的工具接进自己的 Claude Code 会话,或者选择“在这里运行”,改用托管智能体。",
"unavailableCodex": "Codex CLI 在 {{os}} 上暂时不能回复 @提及。你可以选择“在这里运行”,改用托管智能体;也可以换到 Mac 机器上运行这条命令(Linux 尚未测试)。",
+ "wontAnswer": "{{name}} 在这里不会回复 @提及。",
"toolCodex": "Codex CLI",
"useDifferentRunner": "换一种适配器",
"repo": {
diff --git a/frontend/src/v2/__tests__/V2AgentBYOListenSnippet.test.tsx b/frontend/src/v2/__tests__/V2AgentBYOListenSnippet.test.tsx
index dae9d81d8d..da7dc4893c 100644
--- a/frontend/src/v2/__tests__/V2AgentBYOListenSnippet.test.tsx
+++ b/frontend/src/v2/__tests__/V2AgentBYOListenSnippet.test.tsx
@@ -7,7 +7,7 @@ jest.mock('../utils/byoTelemetry', () => ({ recordByoStep: jest.fn() }));
// CLI skipped the install and failed on the last line with "command not found"
// (the #887 class the snippet was moved here to prevent).
import React from 'react';
-import { render, screen, fireEvent, waitFor } from '@testing-library/react';
+import { render, screen, fireEvent, waitFor, within } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import V2AgentBYO from '../components/V2AgentBYO';
import { AuthContext } from '../../context/AuthContext';
@@ -47,9 +47,12 @@ const authValue = {
updateProfile: jest.fn(),
};
+let hostedConfigured = false;
+
const issueToken = async (runner?: 'codex', pod = {}) => {
axios.get.mockImplementation((url) => {
if (url === '/api/pods') return Promise.resolve({ data: [{ _id: 'p1', name: 'Workspace', type: 'chat', ...pod }] });
+ if (url.startsWith('/api/hosted/availability')) return Promise.resolve({ data: { configured: hostedConfigured, caps: { agentsPerUser: 3, turnsPerDay: 20 } } });
if (url.startsWith('/api/registry/agents/next-free-name?')) return Promise.resolve({ data: { name: new URLSearchParams(url.split('?')[1]).get('base') } });
return Promise.resolve({ data: {} });
});
@@ -64,6 +67,7 @@ const issueToken = async (runner?: 'codex', pod = {}) => {
,
);
await waitFor(() => expect(screen.getByText('Workspace (chat)')).toBeInTheDocument());
+ if (hostedConfigured) fireEvent.click(await screen.findByTestId('byo-mode-byo'));
await waitFor(() => expect(screen.getByRole('button', { name: /get my connect command/i })).toBeEnabled());
if (runner) fireEvent.click(screen.getByTestId(`byo-runner-${runner}`));
// The runner sets the default name, which re-checks it, so wait for the button again.
@@ -129,6 +133,16 @@ describe('BYO listener snippet', () => {
// "Claude Code" was the MCP line, which does not make the agent answer. The
// command that does is THE box: first, and the only one not marked optional
// or folded away.
+ test('with a listener the MCP block is folded under it and says it does not answer @mentions', async () => {
+ await issueToken();
+ const details = screen.getByTestId('byo-mcp-details');
+ expect(details.tagName).toBe('DETAILS');
+ expect(details).not.toHaveAttribute('open');
+ expect(details.querySelector('summary')).toHaveTextContent(/does not answer @mentions/);
+ expect(details).toContainElement(screen.getByTestId('byo-mcp-claude'));
+ expect(screen.queryByTestId('byo-listen-wont-answer')).toBeNull();
+ });
+
test('the connect command is the first box; the MCP line is optional; the bare key is folded away', async () => {
await issueToken();
const pres = Array.from(document.querySelectorAll('.v2-byo__pre')).map((el) => el.textContent || '');
@@ -421,6 +435,28 @@ describe('BYO listener snippet', () => {
expect(screen.queryByTestId('byo-mcp-claude')).toBeNull();
});
+ test('no listener: the first line says it will not answer @mentions, MCP stays open, no Run it here without hosting', async () => {
+ setUserAgent(WINDOWS);
+ await issueToken();
+ const wont = screen.getByTestId('byo-listen-wont-answer');
+ expect(wont).toHaveTextContent(/will not answer @mentions from here/);
+ expect(wont.compareDocumentPosition(screen.getByTestId('byo-listen-unavailable')) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
+ expect(screen.queryByTestId('byo-listen-run-here')).toBeNull();
+ expect(screen.queryByTestId('byo-mcp-details')).toBeNull();
+ expect(screen.getByTestId('byo-mcp-claude')).toBeInTheDocument();
+ });
+
+ test('no listener with hosting on: the first line carries a Run it here button', async () => {
+ setUserAgent(WINDOWS);
+ hostedConfigured = true;
+ try {
+ await issueToken();
+ expect(within(screen.getByTestId('byo-listen-wont-answer')).getByRole('button', { name: 'Run it here' })).toBeInTheDocument();
+ } finally {
+ hostedConfigured = false;
+ }
+ });
+
test('the switch overrides the browser: Windows to Mac brings the listener back', async () => {
setUserAgent(WINDOWS);
await issueToken();
diff --git a/frontend/src/v2/components/V2AgentBYO.tsx b/frontend/src/v2/components/V2AgentBYO.tsx
index a2a7ea5fde..63461a38a2 100644
--- a/frontend/src/v2/components/V2AgentBYO.tsx
+++ b/frontend/src/v2/components/V2AgentBYO.tsx
@@ -1641,6 +1641,25 @@ const V2AgentBYO: React.FC = () => {
))}
- {/* Where no listener runs, this is the way in, not an extra. */}
- {t(listenerAvailable ? 'agentByo.snippets.claudeCode' : 'agentByo.snippets.claudeCodeMain')}
-
+ {/* Where no listener runs, this is the way in, not an extra. */}
+ {t(listenerAvailable ? 'agentByo.snippets.claudeCode' : 'agentByo.snippets.claudeCodeMain')}
+
- )}
+ );
+ // With a listener, the MCP command is the extra: folded away and
+ // labelled, because a stranger copied it three times, never ran the
+ // listener, and the agent never answered (walk 2026-10-31). Without a
+ // listener it is the only way in, so it stays open.
+ return listenerAvailable ? (
+
+ {t('agentByo.snippets.mcpSummary')}
+ {mcpBlock}
+
+ ) : mcpBlock;
+ })()}
{t('agentByo.otherWays.title')}
From 8d49db73421acd9a3f11c7cc4e51d340c594f090 Mon Sep 17 00:00:00 2001
From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com>
Date: Sat, 31 Oct 2026 16:18:15 -0700
Subject: [PATCH 2/2] fix(byo): the Run it here button shows its own error and
is the only one without a listener (TASK-720)
Co-Authored-By: Claude Sonnet 5.5
Co-authored-by: Ember
Agent-Model: claude/claude-opus-5-5/medium
---
frontend/src/v2/__tests__/V2AgentBYOListenSnippet.test.tsx | 7 +++++++
frontend/src/v2/components/V2AgentBYO.tsx | 3 ++-
2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/frontend/src/v2/__tests__/V2AgentBYOListenSnippet.test.tsx b/frontend/src/v2/__tests__/V2AgentBYOListenSnippet.test.tsx
index da7dc4893c..9c60df3af9 100644
--- a/frontend/src/v2/__tests__/V2AgentBYOListenSnippet.test.tsx
+++ b/frontend/src/v2/__tests__/V2AgentBYOListenSnippet.test.tsx
@@ -452,6 +452,13 @@ describe('BYO listener snippet', () => {
try {
await issueToken();
expect(within(screen.getByTestId('byo-listen-wont-answer')).getByRole('button', { name: 'Run it here' })).toBeInTheDocument();
+ // The hosted offer section would repeat the same action: one button only.
+ expect(screen.getAllByRole('button', { name: /Run it here/ })).toHaveLength(1);
+ // A failed start shows its error beside the button, even after a copy.
+ axios.get.mockImplementation((url) => (url.startsWith('/api/registry/agents/next-free-name')
+ ? Promise.reject(new Error('boom')) : Promise.resolve({ data: { configured: true, caps: { agentsPerUser: 3, turnsPerDay: 20 } } })));
+ fireEvent.click(screen.getByTestId('byo-listen-run-here'));
+ await waitFor(() => expect(within(screen.getByTestId('byo-listen-wont-answer')).getByRole('alert')).toBeInTheDocument());
} finally {
hostedConfigured = false;
}
diff --git a/frontend/src/v2/components/V2AgentBYO.tsx b/frontend/src/v2/components/V2AgentBYO.tsx
index 63461a38a2..f719ad13ad 100644
--- a/frontend/src/v2/components/V2AgentBYO.tsx
+++ b/frontend/src/v2/components/V2AgentBYO.tsx
@@ -1658,6 +1658,7 @@ const V2AgentBYO: React.FC = () => {
>
)}
+ {error && {error}}
)}