diff --git a/src/main/codex/codex-structured-journal-translation-subagents.test.ts b/src/main/codex/codex-structured-journal-translation-subagents.test.ts index 4399d864a09..f06b639929c 100644 --- a/src/main/codex/codex-structured-journal-translation-subagents.test.ts +++ b/src/main/codex/codex-structured-journal-translation-subagents.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from 'vitest' import { agentJournalItemKey } from '../../shared/agent-session-journal-item-key' +import type { AgentSessionTurnActivity } from '../../shared/agent-session-wire' import type { AgentJournalItemBody, AgentJournalItemIdentity @@ -17,11 +18,13 @@ type Row = { key: string; body: AgentJournalItemBody } function harness() { const rows: Row[] = [] + const activities: (AgentSessionTurnActivity | null)[] = [] const sink: StructuredAgentSessionEventSink = { appendItem: (identity: AgentJournalItemIdentity, body) => rows.push({ key: agentJournalItemKey(identity), body }), appendTombstone: () => {}, - publish: () => {} + publish: () => {}, + setActivity: (activity) => activities.push(activity) } const translator = createCodexJournalTranslator({ sink, @@ -31,7 +34,7 @@ function harness() { return () => {} } }) - return { translator, rows } + return { translator, rows, activities } } function notification(method: string, params: unknown): CodexStructuredSessionEvent { @@ -88,6 +91,22 @@ describe('codex journal translation — subagents', () => { expect(rows.filter((row) => row.key.startsWith('orca:codex-subagents'))).toHaveLength(1) }) + // The roster claims the item, but claiming it must not take the turn tail with + // it: the activity table is reached only through the publish arm, so a bare + // return leaves the tail stuck on whatever the previous frame said. + it('still publishes the turn tail for an item the roster claims', () => { + const { translator, activities } = harness() + + translator.handle(notification('turn/started', { turn: { id: TURN_ID } })) + activities.length = 0 + deliverActivity(translator, subagentItem('started', 'child-1', '/root/read')) + + expect(activities.at(-1)).toEqual({ + turnId: TURN_ID, + text: 'Coordinating with another agent' + }) + }) + it('consumes thread/tokenUsage/updated instead of swallowing it as chrome', () => { const { translator, rows } = harness() diff --git a/src/main/codex/codex-structured-journal-translation.ts b/src/main/codex/codex-structured-journal-translation.ts index 7693dbc4ac4..4caf6292e04 100644 --- a/src/main/codex/codex-structured-journal-translation.ts +++ b/src/main/codex/codex-structured-journal-translation.ts @@ -191,7 +191,9 @@ export function createCodexJournalTranslator( }) : null if (subagentAdmission) { - return subagentAdmission + // Not a bare return: the roster claiming the item must not skip the + // turn-tail arm, which is the only publisher of its activity copy. + return publishActivity(event, subagentAdmission) } const translated = items.handle(event) return publishActivity( @@ -224,7 +226,8 @@ export function createCodexJournalTranslator( } } - /** Routes a `subAgentActivity` item to the roster; null when it is not one. */ + /** Settles the item a notification the transport refused to carry left + * mid-flight; null when the frame is not one. */ function settleOversizedNotification(event: { sessionId: string threadId: string diff --git a/src/main/native-chat/agent-session-wire/provider-frame-activity.test.ts b/src/main/native-chat/agent-session-wire/provider-frame-activity.test.ts index 79d9e4205cf..40504873282 100644 --- a/src/main/native-chat/agent-session-wire/provider-frame-activity.test.ts +++ b/src/main/native-chat/agent-session-wire/provider-frame-activity.test.ts @@ -28,6 +28,16 @@ describe('provider frame activity', () => { expect(codexProviderFrameActivity('item/reasoning/summaryPartAdded', {})).toBeNull() }) + it('names a fan-out from either Codex item type that reports one', () => { + for (const type of ['collabAgentToolCall', 'subAgentActivity']) { + expect( + codexProviderFrameActivity('item/started', { + item: { type, kind: 'started', agentThreadId: 'child-1', agentPath: '/root/read' } + }) + ).toBe('Coordinating with another agent') + } + }) + it('uses Claude descriptions and safe semantic status without exposing tool labels', () => { expect( claudeProviderFrameActivity('message:system:task_started', {