From c21f36f5653828946ca1b74d1ad083d267ed3985 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sat, 24 Oct 2026 00:50:06 -0700 Subject: [PATCH] fix(scout): give exact first-pod confirmation (TASK-463) Use the approved English or Chinese one-line reply in the create_pod approval wake. Keep the generic proposer instruction from competing with that line, and teach Scout to follow the cue verbatim. Written by Sprint Impl, a Commonly agent Co-authored-by: sprint-impl Agent-Model: codex/gpt-6-sol/medium --- ...approvalActionService.proposerWake.test.js | 21 +++++++++++++---- backend/config/native-agents/scout.ts | 5 ++-- backend/services/approvalActionService.ts | 23 +++++++++++++------ 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/backend/__tests__/unit/services/approvalActionService.proposerWake.test.js b/backend/__tests__/unit/services/approvalActionService.proposerWake.test.js index 1e0a7d2909..4fa3e6821d 100644 --- a/backend/__tests__/unit/services/approvalActionService.proposerWake.test.js +++ b/backend/__tests__/unit/services/approvalActionService.proposerWake.test.js @@ -139,7 +139,7 @@ describe('approving a proposal wakes the agent that proposed it', () => { expect(mockEnqueue.mock.calls[0][0].payload.content).toContain('The pod "Design Studio" now exists (pod-new).'); }); - test('create_pod: once Scout asked the project question, the cue points there in one line', async () => { + test('create_pod: English cue gives Scout the exact one-line reply', async () => { mockPodCreate.mockResolvedValue({ _id: 'pod-new', name: 'Design Studio' }); mockInstallFindOne.mockReturnValue({ lean: jest.fn().mockResolvedValue({ displayName: 'Scout', config: {} }) }); mockInstallUpsert.mockResolvedValue({}); @@ -147,9 +147,22 @@ describe('approving a proposal wakes the agent that proposed it', () => { await decide('approved', { actionType: 'create_pod', params: { name: 'Design Studio', type: 'chat' }, summary: 'Create a pod for design work' }); expect(jest.requireMock('../../../services/agentMessageService').postMessage).toHaveBeenCalledTimes(1); const cue = mockEnqueue.mock.calls[0][0].payload.content; - expect(cue).toContain('asked for a repo link or weekly workflow'); - expect(cue).toContain('will propose one first task'); - expect(cue).toMatch(/one short line/); + expect(cue).toContain('Reply with exactly this one line: Design Studio is ready. Open it and answer one question to get your first task.'); + expect(cue).not.toContain('weekly workflow'); + expect(cue).not.toContain('in a sentence or two'); + }); + + test('create_pod: Chinese cue gives Scout the exact one-line reply', async () => { + mockPodCreate.mockResolvedValue({ _id: 'pod-new', name: '设计工作室' }); + mockInstallFindOne.mockReturnValue({ lean: jest.fn().mockResolvedValue({ displayName: 'Scout', config: {} }) }); + mockInstallUpsert.mockResolvedValue({}); + jest.requireMock('../../../services/agentIdentityService').getOrCreateAgentUser.mockResolvedValueOnce({ _id: 'scout-bot' }); + await decide('approved', { actionType: 'create_pod', params: { name: '设计工作室', type: 'chat', locale: 'zh-CN' }, summary: '创建一个设计工作室' }); + expect(jest.requireMock('../../../services/agentMessageService').postMessage).toHaveBeenCalledTimes(1); + const cue = mockEnqueue.mock.calls[0][0].payload.content; + expect(cue).toContain('Reply with exactly this one line: 设计工作室 已建好。打开它,回答一个问题,就能拿到第一个任务。'); + expect(cue).not.toContain('weekly workflow'); + expect(cue).not.toContain('in a sentence or two'); }); test('create_pod: an agent that could not join posted nothing, and the cue claims no prompt', async () => { diff --git a/backend/config/native-agents/scout.ts b/backend/config/native-agents/scout.ts index 248eff8450..20cb215225 100644 --- a/backend/config/native-agents/scout.ts +++ b/backend/config/native-agents/scout.ts @@ -78,9 +78,8 @@ export const scoutApp = { + `For "${SCOUT_STARTS[1].label}", ask what the project is if ` + 'they have not said, then propose create_pod with a short name for it. ' + 'Set create_pod params.locale to the person\'s language (en or zh-CN). ' - + 'Once they approve it, the new pod asks for a repo link or weekly workflow ' - + 'and proposes one first task with buttons, so point them to the new pod ' - + 'in one short line instead of listing steps.\n' + + 'Once they approve it, reply with the exact one-line wording in the ' + + 'approval.resolved cue. The new pod asks its own first question.\n' + '- How a user brings their own agent in. On the Agents page, the "Bring ' + 'your own" button (or "Set it up" on the "Your own agent" card) opens ' + 'the "Bring your own agent" page (/v2/agents/byo). It offers:\n' diff --git a/backend/services/approvalActionService.ts b/backend/services/approvalActionService.ts index 1013946d83..576b124043 100644 --- a/backend/services/approvalActionService.ts +++ b/backend/services/approvalActionService.ts @@ -622,8 +622,8 @@ const recordToolCallDecision = async ( }; /** - * What happened, in one line the proposing agent can act on. States facts the - * executor produced; what to say about them is the agent's own knowledge. + * What happened, in one line the proposing agent can act on. The create_pod + * success cue includes the exact reply so Scout cannot paraphrase the next step. */ const describeApprovedOutcome = (row: IApprovalAction): string => { if (row.executionError) { @@ -639,14 +639,18 @@ const describeApprovedOutcome = (row: IApprovalAction): string => { + 'That page issues its token and shows the commands that connect their agent to it.'; } if (row.actionType === 'create_pod') { + const params = (row.params || {}) as { locale?: string }; + const locale = params.locale === 'zh-CN' || /\p{Script=Han}/u.test(row.summary || '') ? 'zh-CN' : 'en'; + const podName = String(result.podName || ''); const joinNote = result.agentJoinError ? ` You could not be added to it: ${String(result.agentJoinError).slice(0, 200)}.` : ''; const stepsNote = result.nextStepsPosted - ? ' Scout has asked for a repo link or weekly workflow there and will propose one first task.' - + ' Point them to the new pod in one short line; do not repeat the prompt.' + ? ` Reply with exactly this one line: ${locale === 'zh-CN' + ? `${podName} 已建好。打开它,回答一个问题,就能拿到第一个任务。` + : `${podName} is ready. Open it and answer one question to get your first task.`}` : ''; - const pod = `The pod "${String(result.podName || '')}" now exists (${String(result.podId || '')}).`; + const pod = `The pod "${podName}" now exists (${String(result.podId || '')}).`; return `${pod}${joinNote}${stepsNote}`; } return 'The action ran.'; @@ -678,13 +682,18 @@ const enqueueApprovalResolvedEvent = async ( // eslint-disable-next-line global-require, @typescript-eslint/no-require-imports const AgentEventService = require('./agentEventService'); const summary = String(row.summary || row.actionType).trim().slice(0, 200); + const exactPodReply = row.actionType === 'create_pod' + && !row.executionError + && Boolean((row.executionResult as { nextStepsPosted?: boolean } | undefined)?.nextStepsPosted); const content = [ '[Your proposal was approved]', `${approvedBy.username} approved your proposal: ${summary}`, describeApprovedOutcome(row), '', - 'Reply to them with the one next step, in a sentence or two. Do not repeat the card. ' - + 'Return NO_REPLY only if nothing is left for them to do.', + exactPodReply + ? 'Use only the exact line above. Do not add another question or repeat the card.' + : 'Reply to them with the one next step, in a sentence or two. Do not repeat the card. ' + + 'Return NO_REPLY only if nothing is left for them to do.', ].join('\n'); await AgentEventService.enqueue({ agentName: String(row.agentName),