diff --git a/packages/agent-core-v2/src/agent/loop/turnEvents.ts b/packages/agent-core-v2/src/agent/loop/turnEvents.ts index 389a841523..a9bf7a178c 100644 --- a/packages/agent-core-v2/src/agent/loop/turnEvents.ts +++ b/packages/agent-core-v2/src/agent/loop/turnEvents.ts @@ -42,6 +42,10 @@ export function turnPromptText( export function isDisplayablePromptOrigin(origin: PromptOrigin): boolean { if (origin.kind === 'user') return true; + // Goal continuations surface as a visible prompt bubble in chat UIs (like a + // cron fire), so the prompt text must travel with turn.started for the live + // view to render it — history already carries it on the persisted message. + if (origin.kind === 'system_trigger') return origin.name === 'goal_continuation'; return ( (origin.kind === 'skill_activation' || origin.kind === 'plugin_command') && origin.trigger === 'user-slash' diff --git a/packages/agent-core-v2/test/agent/loop/loop.test.ts b/packages/agent-core-v2/test/agent/loop/loop.test.ts index bf78addf34..4dbf8e131f 100644 --- a/packages/agent-core-v2/test/agent/loop/loop.test.ts +++ b/packages/agent-core-v2/test/agent/loop/loop.test.ts @@ -758,15 +758,16 @@ describe('Agent loop', () => { ); }); - it('omits the turn.started prompt for system-triggered turns', async () => { + it('omits the turn.started prompt for system-triggered turns except goal continuations', async () => { const prompts: Array = []; const subscription = ctx.get(IEventBus).subscribe(TurnStarted, (event) => { prompts.push(event.prompt); }); ctx.mockNextResponse({ type: 'text', text: 'continued' }); + ctx.mockNextResponse({ type: 'text', text: 'subagent work' }); ctx.mockNextResponse({ type: 'text', text: 'hi there' }); - const system = ( + const goal = ( await loop.enqueue( new MessageStepRequest( { @@ -779,12 +780,28 @@ describe('Agent loop', () => { ), ).assigned ).turn; - await system.result; + await goal.result; + const subagent = ( + await loop.enqueue( + new MessageStepRequest( + { + role: 'user', + content: [{ type: 'text', text: 'subagent hidden prompt' }], + toolCalls: [], + origin: { kind: 'system_trigger', name: 'subagent' }, + }, + { admission: 'newTurn' }, + ), + ).assigned + ).turn; + await subagent.result; const user = (await loop.enqueue(nextTurnMessage('hi')).assigned).turn; await user.result; subscription.dispose(); - expect(prompts).toEqual([undefined, 'hi']); + // Goal continuations carry their prompt (chat UIs render it as a visible + // bubble, like a cron fire); other system triggers stay hidden. + expect(prompts).toEqual(['continue the goal', undefined, 'hi']); }); });