Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ describe('Scout\'s welcome', () => {
const { SCOUT_WELCOME } = require('../../../config/native-agents/scout');

test('is in English, with no invitation to write in another language', () => {
expect(SCOUT_WELCOME).toBe('I\'m Scout. Pick a start or tell me your goal.');
expect(SCOUT_WELCOME).toBe('What would you like to do first?');
expect(SCOUT_WELCOME).not.toMatch(/[\u3040-\u30ff\u3400-\u9fff\uac00-\ud7af\uff00-\uffef]/);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ describe('workspace onboarding guide identity fork (2026-08-13)', () => {

const welcome = mockPostMessage.mock.calls[0][0];
expect(welcome.content).toBe(SCOUT_WELCOME);
expect(welcome.content.split(/\s+/)).toHaveLength(10);
expect(welcome.content).toBe('What would you like to do first?');
expect(welcome.payload).toEqual({
kind: 'starts',
starts: [
Expand Down
4 changes: 2 additions & 2 deletions backend/__tests__/unit/services/approvalActionService.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ describe('approved create_pod execution — D2: user authority owns', () => {
expect(mockPostMessage).toHaveBeenCalledTimes(1);
const posted = mockPostMessage.mock.calls[0][0];
expect(posted).toEqual(expect.objectContaining({ agentName: 'guide', podId: 'newpod-3', displayName: 'Scout' }));
expect(posted.content).toMatch(/Which GitHub repo or weekly workflow/);
expect(posted.content).toBe('Which GitHub repo or weekly workflow should we start with?');
expect(posted.payload).toEqual({ kind: 'starts', starts: [{
id: 'bring-your-own', label: 'Bring your own', href: '/v2/agents/byo?pod=newpod-3',
}] });
Expand All @@ -274,7 +274,7 @@ describe('approved create_pod execution — D2: user authority owns', () => {

await resolveApproval({ approvalId: 'appr-1', callerUserId: OWNER, decision: 'approved' });

expect(mockPostMessage.mock.calls[0][0].content).toMatch(/每周工作流程/);
expect(mockPostMessage.mock.calls[0][0].content).toBe('先从哪个 GitHub 仓库或每周工作流程开始?');
expect(mockPostMessage.mock.calls[0][0].payload.starts[0].label).toBe('接入自己的智能体');
expect(mockPodUpdateOne).toHaveBeenCalledWith({ _id: 'newpod-zh' }, {
$set: { firstTaskOnboarding: expect.objectContaining({ locale: 'zh-CN' }) },
Expand Down
2 changes: 1 addition & 1 deletion backend/config/native-agents/scout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const SCOUT_STARTS = [
export const SCOUT_HOSTED_START = { id: 'run-hosted', label: 'Run an agent here' } as const;

/** Scout's first message in a new workspace; the starts render under it. */
export const SCOUT_WELCOME = 'I\'m Scout. Pick a start or tell me your goal.';
export const SCOUT_WELCOME = 'What would you like to do first?';

export const scoutApp = {
agentName: 'scout',
Expand Down
5 changes: 2 additions & 3 deletions backend/services/approvalActionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1143,8 +1143,7 @@ const executeConnectLocalAgent = async (row: IApprovalAction): Promise<unknown>
* produces a server-composed task proposal with two actionable buttons.
* Never fatal: the pod exists either way, and the proposer's reply still lands.
*/
export const NEW_POD_NEXT_STEPS_TEXT = 'Which GitHub repo or weekly workflow should we start with? '
+ 'I’ll suggest one first task.';
export const NEW_POD_NEXT_STEPS_TEXT = 'Which GitHub repo or weekly workflow should we start with?';

const postNewPodNextSteps = async (
row: IApprovalAction,
Expand All @@ -1163,7 +1162,7 @@ const postNewPodNextSteps = async (
podId,
displayName,
content: locale === 'zh-CN'
? '先从哪个 GitHub 仓库或每周工作流程开始?我会建议第一项任务。'
? '先从哪个 GitHub 仓库或每周工作流程开始?'
: NEW_POD_NEXT_STEPS_TEXT,
payload: { kind: 'starts', starts: [{
id: 'bring-your-own', label: locale === 'zh-CN' ? '接入自己的智能体' : 'Bring your own', href,
Expand Down
2 changes: 2 additions & 0 deletions frontend/design-system/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ Other surfaces (Feed, Digest, Analytics, Skills) exist in code but are intention

## Content fundamentals

**Copy budget:** Layout and icons carry meaning; at most one short hint per screen; no sentence subtitles. Keep actionable errors, safety warnings, and live state visible.

### Voice — confident, technical, peer-to-peer

Commonly speaks to developers building with AI agents. The voice is **plainly technical, confident without being salesy**, and treats agents as first-class members of the team — not bots, not assistants, not magic.
Expand Down
17 changes: 3 additions & 14 deletions frontend/src/__tests__/timingClaims.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,14 @@ describe('timing claims in the copy', () => {
// at 2.54 s (install 1.83 + provision 0.63); walk 4: 2.47 s.
// ~/.commonly/state/notes/qa-hosted-oob-5/report.md, timing table.
test('click to live says "a few seconds", in both languages', () => {
expect(en.agentByo.mode.hostedMeta).toBe('Live in a few seconds — no terminal, no keys');
expect(en.agentByo.compact.lead).toContain('live a few seconds after you press the button');
expect(en.agentByo.mode.hostedMeta).toBe('Live in a few seconds · Beta: {{turns}} turns a day');
expect(en.agentByo.compact.lead).toBe('Live in a few seconds.');
expect(zh.agentByo.mode.hostedMeta).toContain('几秒');
expect(zh.agentByo.compact.lead).toContain('几秒');
});

// Reply time: n=8 from two walks by one tester, asks a-d in the comparison
// table of the same report. Walk 5: 10.5, 13.4, 7.8, 8.0 s; walk 4: 14.3,
// 10.4 (the retry), 8.0, 10.4 s; mean 10.35 s. If a later walk measures
// well off ten, this line changes. The zh says "about", never "within": two
// of the eight were over 13 s.
test('reply time says "about ten seconds", never a ceiling', () => {
expect(en.agentByo.hosted.latencyHint).toBe('Replies typically land in about ten seconds.');
expect(zh.agentByo.hosted.latencyHint).toBe('回复通常需要10秒左右。');
expect(zh.agentByo.hosted.latencyHint).not.toMatch(/内/);
});

test('no other English copy makes a seconds claim', () => {
const cited = new Set(['agentByo.mode.hostedMeta', 'agentByo.compact.lead', 'agentByo.hosted.latencyHint']);
const cited = new Set(['agentByo.mode.hostedMeta', 'agentByo.compact.lead']);
const claims = leaves(en)
.filter(([key, value]) => !cited.has(key)
&& /\b(within|in|a few|several|~?\s?\d+) seconds?\b|\bin seconds\b/i.test(value))
Expand Down
28 changes: 8 additions & 20 deletions frontend/src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1361,20 +1361,17 @@
},
"agentByo": {
"title": "Bring your own agent",
"description": "Connect the Claude Code or Codex you already use, so it answers you in your pods. One command in Terminal, about 2 minutes.",
"description": "A running agent answers @mentions in its pod.",
"form": {
"nameLabel": "Agent name",
"nameHint": "Lower-case letters, digits, and dashes. It posts under this name.",
"podLabel": "Pod it joins",
"podHint": "It joins this pod. You can add it to more pods later.",
"loadingPods": "Loading pods…",
"machineLabel": "Computer",
"machineHint": "The daemon on this computer will adopt and start the agent.",
"adapterLabel": "Adapter",
"adapterDefault": "Auto-detect on that computer",
"adapterClaude": "Claude Code",
"adapterCodex": "Codex",
"adapterHint": "Pick which CLI on that computer runs this agent. Leave it on auto-detect to use whichever is installed.",
"modelLabel": "Model",
"modelDefault": "Adapter default",
"modelOpus": "Opus — strongest reasoning",
Expand Down Expand Up @@ -1451,8 +1448,7 @@
},
"listen": {
"title": "Run this in Terminal",
"body": "This is what makes {{name}} answer @mentions in your pods.",
"note": "It replies as {{name}} while this Terminal window stays open. Close it and {{name}} stays silent when mentioned.",
"note": "MCP alone won't wake {{name}} for @mentions. Keep this Terminal open.",
"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.",
Expand All @@ -1472,15 +1468,12 @@
"mode": {
"label": "Where should it run?",
"hosted": "Run it here",
"hostedHint": "Commonly runs it for you. Nothing to install. Beta: {{turns}} turns a day.",
"byo": "Use my own agent",
"byoHint": "Your Claude Code or Codex, on your computer.",
"onCommonly": "on Commonly",
"yours": "your machine",
"hostedMeta": "Live in a few seconds — no terminal, no keys",
"hostedMeta": "Live in a few seconds · Beta: {{turns}} turns a day",
"byoMeta": "One command in Terminal",
"machine": "On my computer",
"machineHint": "Runs on a computer you've added — your files, your CLIs",
"machineMeta": "Needs the Commonly daemon running there"
},
"hosted": {
Expand All @@ -1491,8 +1484,7 @@
"slow": "{{name}} hasn't checked in yet. Mention it anyway; it picks up messages when it starts.",
"meter": "Beta limits: {{turns}} turns a day, one hosted agent per account.",
"statTurns": "turns / day",
"statAgents": "hosted agent",
"latencyHint": "Replies typically land in about ten seconds."
"statAgents": "hosted agent"
},
"preview": {
"title": "your agent",
Expand All @@ -1501,10 +1493,7 @@
"waiting": "not listening yet",
"starting": "starting…",
"live": "listening",
"noteHosted": "This is how it will appear in the pod. The face follows the name.",
"noteByo": "Appears in the pod once the command is running.",
"noteMachine": "Appears in the pod once the daemon on {{machine}} starts it.",
"noteUnchosen": "This is how it will appear in the pod. The face follows the name."
"noteMachine": "Appears when the daemon on {{machine}} starts it."
},
"persona": {
"hiring": "Hiring"
Expand All @@ -1520,19 +1509,18 @@
},
"addComputer": {
"title": "Add a computer",
"lead": "Want an agent running on your own machine? One paste in its terminal:",
"after": "Then reload this page — an “On my computer” option appears, and adding agents to that machine never needs a terminal again.",
"after": "Reload to select this computer.",
"copy": "Copy",
"copied": "Copied"
},
"compactByo": {
"titleClaude": "Connect your Claude Code",
"titleCodex": "Connect your Codex",
"lead": "One command in Terminal, about 2 minutes."
"lead": "About 2 minutes in Terminal."
},
"compact": {
"title": "Run an agent here",
"lead": "Commonly runs it for you. Nothing to install, and it is live a few seconds after you press the button.",
"lead": "Live in a few seconds.",
"joins": "It joins {{pod}} as {{name}}.",
"change": "Change",
"limits": "Beta: {{agents}} hosted agent per account, {{turns}} turns a day.",
Expand Down
28 changes: 8 additions & 20 deletions frontend/src/i18n/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -1421,20 +1421,17 @@
},
"agentByo": {
"title": "接入你自己的智能体",
"description": "接入你已在用的 Claude Code 或 Codex,让它在你的 Pod 里回复你。只需在终端里运行一条命令,约2分钟完成。",
"description": "智能体运行期间会在所在 Pod 回复 @提及。",
"form": {
"nameLabel": "智能体名称",
"nameHint": "仅支持小写字母、数字和连字符。它会用这个名字发言。",
"podLabel": "要加入的 Pod",
"podHint": "它会加入这个 Pod,之后你还可以把它加到更多 Pod。",
"loadingPods": "正在加载 Pod…",
"machineLabel": "电脑",
"machineHint": "这台电脑上的守护进程会接管并启动该智能体。",
"adapterLabel": "适配器",
"adapterDefault": "在这台电脑上自动检测",
"adapterClaude": "Claude Code",
"adapterCodex": "Codex",
"adapterHint": "选择在那台电脑上用哪个 CLI 运行这个智能体。保持自动检测,就会用已安装的那个。",
"modelLabel": "模型",
"modelDefault": "适配器默认",
"modelOpus": "Opus——推理最强",
Expand Down Expand Up @@ -1511,8 +1508,7 @@
},
"listen": {
"title": "在终端里运行这段命令",
"body": "有了它,{{name}} 才会在你的 Pod 里回复 @提及。",
"note": "终端窗口开着时,它会以 {{name}} 的身份回复。关掉窗口,{{name}} 被提及时就不会回复。",
"note": "仅连接 MCP 不会唤醒 {{name}} 回复 @提及。请保持终端窗口开启。",
"waiting": "正在等待 {{name}} 接入…命令一接通,这里就会变绿。",
"verified": "{{name}} 已在监听 —— @提及 会唤醒它。",
"stillWaiting": "{{name}} 还没接入。请在你电脑的终端里运行上面的命令,这里会自动确认。",
Expand All @@ -1532,15 +1528,12 @@
"mode": {
"label": "在哪里运行?",
"hosted": "在这里运行",
"hostedHint": "由 Commonly 代为运行,无需安装。测试期:每天{{turns}}轮。",
"byo": "用我自己的智能体",
"byoHint": "你电脑上的 Claude Code 或 Codex。",
"onCommonly": "在 Commonly 上",
"yours": "你的电脑",
"hostedMeta": "几秒即可上线——无需终端与密钥",
"hostedMeta": "几秒即可上线 · 测试期每天{{turns}}轮",
"byoMeta": "只需一条终端命令",
"machine": "在我的电脑上",
"machineHint": "在你已添加的电脑上运行——你的文件、你的 CLI",
"machineMeta": "需要那台电脑运行 Commonly 守护进程"
},
"hosted": {
Expand All @@ -1551,8 +1544,7 @@
"slow": "{{name}} 尚未接入。可以先 @ 它,启动后会处理消息。",
"meter": "测试期限制:每天{{turns}}轮,每个账号一个托管智能体。",
"statTurns": "轮 / 天",
"statAgents": "托管智能体",
"latencyHint": "回复通常需要10秒左右。"
"statAgents": "托管智能体"
},
"preview": {
"title": "你的智能体",
Expand All @@ -1561,10 +1553,7 @@
"waiting": "尚未监听",
"starting": "正在启动…",
"live": "监听中",
"noteHosted": "这就是它在 Pod 中的样子。头像跟随名字生成。",
"noteByo": "命令运行起来后,它就会出现在 Pod 里。",
"noteMachine": "{{machine}} 上的守护进程启动它后即会出现在 Pod 中。",
"noteUnchosen": "这就是它在 Pod 中的样子。头像跟随名字生成。"
"noteMachine": "{{machine}} 上的守护进程启动后会显示在 Pod 中。"
},
"persona": {
"hiring": "正在雇佣"
Expand All @@ -1580,19 +1569,18 @@
},
"addComputer": {
"title": "添加电脑",
"lead": "想让智能体在你自己的电脑上运行?在它的终端里粘贴一次:",
"after": "然后刷新本页——会出现“在我的电脑上”选项,之后往那台电脑添加智能体再也不需要终端。",
"after": "刷新页面后即可选择这台电脑。",
"copy": "复制",
"copied": "已复制"
},
"compactByo": {
"titleClaude": "接入你的 Claude Code",
"titleCodex": "接入你的 Codex",
"lead": "在终端里运行一条命令,约2分钟完成。"
"lead": "在终端里约2分钟完成。"
},
"compact": {
"title": "在这里运行智能体",
"lead": "由 Commonly 代为运行,无需安装,点下按钮几秒后即可上线。",
"lead": "几秒后即可上线。",
"joins": "它会以 {{name}} 的身份加入 {{pod}}。",
"change": "更改",
"limits": "测试期:每个账号{{agents}}个托管智能体,每天{{turns}}轮。",
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/v2/V2App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ const V2App: React.FC = () => {
/>
<Route
path="connect"
element={<V2FeaturePage title="Connect" hint="Set up the CLI, MCP, and the agents your team runs."><V2ConnectPage /></V2FeaturePage>}
element={<V2FeaturePage title="Connect" hint="MCP uses an agent token created during setup."><V2ConnectPage /></V2FeaturePage>}
/>
<Route
path="connectors"
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/v2/__tests__/V2AgentBYOFromCard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ describe('the command page puts only what the paste needs above the copy', () =>
expect(screen.getByRole('heading', { level: 1 })).toHaveTextContent('Connect your Codex');
});

test('the key-once line and what the command does come after the command', async () => {
test('the key-once and keep-running warnings follow the command', async () => {
renderAt(CARD);
const act = await screen.findByRole('button', { name: /get my connect command/i });
await waitFor(() => expect(act).toBeEnabled());
Expand All @@ -254,7 +254,7 @@ describe('the command page puts only what the paste needs above the copy', () =>
expect(command.compareDocumentPosition(el) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
};
after(screen.getByTestId('byo-key-once'));
after(screen.getByText(/This is what makes sam-claude answer @mentions/));
after(screen.getByText(/MCP alone won't wake .* for @mentions\. Keep this Terminal open/));
// The page lead is behind them now: no eyebrow or description above the copy.
expect(document.querySelector('.v2-feature__description')).toBeNull();
expect(document.querySelector('.v2-feature__eyebrow')).toBeNull();
Expand Down
10 changes: 5 additions & 5 deletions frontend/src/v2/__tests__/V2AgentBYOMachine.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,18 +89,18 @@ describe('BYO on-my-computer mode', () => {
renderPage();
await waitFor(() => expect(screen.getByTestId('byo-mode-machine')).toBeInTheDocument());

// Nothing is preselected, so the note promises no path yet.
expect(screen.getByTestId('byo-preview')).toHaveTextContent('This is how it will appear in the pod. The face follows the name.');
// The preview's name and status carry the meaning until a machine is chosen.
expect(screen.getByTestId('byo-preview').querySelector('.v2-byo__preview-note')).toBeNull();

// On "Bring your own runtime", the token wording is correct.
fireEvent.click(screen.getByTestId('byo-mode-byo'));
expect(screen.getByTestId('byo-preview')).toHaveTextContent('Appears in the pod once the command is running.');
expect(screen.getByTestId('byo-preview').querySelector('.v2-byo__preview-note')).toBeNull();

fireEvent.click(screen.getByTestId('byo-mode-machine'));
expect(screen.getByTestId('byo-preview')).toHaveTextContent('Appears in the pod once the daemon on Sam’s MacBook starts it.');
expect(screen.getByTestId('byo-preview')).toHaveTextContent('Appears when the daemon on Sam’s MacBook starts it.');

fireEvent.change(screen.getByTestId('byo-machine-select'), { target: { value: 'mach-b' } });
expect(screen.getByTestId('byo-preview')).toHaveTextContent('Appears in the pod once the daemon on Studio starts it.');
expect(screen.getByTestId('byo-preview')).toHaveTextContent('Appears when the daemon on Studio starts it.');
});

test('no machines — no card, and the one-paste setup panel shows instead', async () => {
Expand Down
5 changes: 2 additions & 3 deletions frontend/src/v2/__tests__/V2ConnectHonesty.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,14 @@ describe('the connect flow states what it does not do', () => {

test('a listening step exists at all', () => {
expect(listen?.title).toBeTruthy();
expect(listen?.body).toBeTruthy();
expect(listen?.note).toBeTruthy();
});

test('it says plainly that MCP alone will not answer mentions', () => {
const all = `${listen.title} ${listen.body} ${listen.note}`;
const all = `${listen.title} ${listen.note}`;
// The whole point: name the gap rather than only describing the fix.
expect(all).toMatch(/@|提及/);
expect(all.length).toBeGreaterThan(60);
expect(all).toMatch(/MCP/);
});

test('the note names the agent, so the consequence is concrete', () => {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/v2/__tests__/V2ConnectPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ describe('V2ConnectPage', () => {
expect(screen.getByText(/commonly login/)).toBeInTheDocument();
expect(screen.getByText(/COMMONLY_AGENT_TOKEN=cm_agent_…/)).toBeInTheDocument();
expect(screen.getByRole('link', { name: 'Manage connected CLI devices' })).toHaveAttribute('href', '/v2/settings');
expect(document.querySelectorAll('.v2-connect__lane-heading p, .v2-connect__note')).toHaveLength(0);
expect(screen.queryByRole('heading', { name: 'Instance keys' })).not.toBeInTheDocument();
expect(screen.queryByRole('link', { name: 'Open global integrations' })).not.toBeInTheDocument();

Expand Down
Loading
Loading