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 @@ -139,17 +139,30 @@ 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({});
jest.requireMock('../../../services/agentIdentityService').getOrCreateAgentUser.mockResolvedValueOnce({ _id: 'scout-bot' });
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 () => {
Expand Down
5 changes: 2 additions & 3 deletions backend/config/native-agents/scout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
23 changes: 16 additions & 7 deletions backend/services/approvalActionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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.';
Expand Down Expand Up @@ -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),
Expand Down
Loading