diff --git a/src/main/pi/agent-status-extension-source.test.ts b/src/main/pi/agent-status-extension-source.test.ts index aa2a2176d8e..5ecee4fb0f2 100644 --- a/src/main/pi/agent-status-extension-source.test.ts +++ b/src/main/pi/agent-status-extension-source.test.ts @@ -221,6 +221,35 @@ describe('getPiAgentStatusExtensionSource', () => { ]) }) + it('does not let a nested OMP session replace the root resume identity', async () => { + const harness = createHarness({ kind: 'omp' }) + const root = { + getSessionId: () => 'root-session', + getSessionFile: () => '/tmp/root.jsonl', + getHeader: () => ({ parentSession: undefined }) + } + const child = { + getSessionId: () => 'child-session', + getSessionFile: () => '/tmp/root-artifacts/worker.jsonl', + getHeader: () => ({ parentSession: '/tmp/root.jsonl' }) + } + + await harness.callHook('agent_start', undefined, { sessionManager: child }) + await harness.callHook('agent_start', undefined, { sessionManager: root }) + + await vi.waitFor(() => expect(harness.fetchMock).toHaveBeenCalledTimes(1)) + const payloads = harness.fetchMock.mock.calls.map( + ([_event, init]) => JSON.parse(String(init?.body)).payload + ) + expect(payloads).toEqual([ + { + hook_event_name: 'agent_start', + session_id: 'root-session', + session_file: '/tmp/root.jsonl' + } + ]) + }) + it.each([ ['OMP extension', { kind: 'omp' as const }], ['runtime-routed OMP', { kind: 'pi' as const, title: 'omp' }] diff --git a/src/main/pi/agent-status-extension-test-harness.ts b/src/main/pi/agent-status-extension-test-harness.ts index a6e9379f898..696153064f1 100644 --- a/src/main/pi/agent-status-extension-test-harness.ts +++ b/src/main/pi/agent-status-extension-test-harness.ts @@ -15,6 +15,7 @@ export type HookContext = { sessionManager?: { getSessionId?: () => unknown getSessionFile?: () => unknown + getHeader?: () => unknown } } diff --git a/src/main/pi/omp-session-status-owner-source.ts b/src/main/pi/omp-session-status-owner-source.ts index fe16868e2ca..9e29126f014 100644 --- a/src/main/pi/omp-session-status-owner-source.ts +++ b/src/main/pi/omp-session-status-owner-source.ts @@ -30,6 +30,9 @@ export function getOmpSessionOwnerHandlerSourceLines(): string[] { ' if (ctx?.agentKind === "sub") return false', ' const current = sessionProvenance(ctx)', ' if (!current) return true', + " // A task transcript is never the pane's resumable root, even when its", + ' // callback arrives before the root session reports on the shared hook.', + ' if (current.parent) return false', ' // Keep ownership through module reload and shutdown while child sessions drain.', " const key = Symbol.for('orca.omp.status-session-owners')", ' let owners = Reflect.get(globalThis, key)', diff --git a/tests/tools/omp-transcript-runtime-smoke.mjs b/tests/tools/omp-transcript-runtime-smoke.mjs index 4cce04d2c33..d5fde3d34ca 100644 --- a/tests/tools/omp-transcript-runtime-smoke.mjs +++ b/tests/tools/omp-transcript-runtime-smoke.mjs @@ -62,11 +62,16 @@ try { await new Promise((resolve) => setTimeout(resolve, 60)) } const extension = await load() + const childExtension = await load() await emit(extension, 'session_start', root) for (const phase of ['initial', 'new']) { if (phase === 'new') { await root.newSession() } + await child.newSession({ parentSession: root.getSessionFile() }) + const beforeChildFirst = posts.length + await emit(childExtension, 'agent_start', child) + assert.equal(posts.length, beforeChildFirst) assert.equal(root.isSessionOnDisk(), false) await emit(extension, 'agent_start', root) const session = extractAgentProviderSession('omp', posts.at(-1)) @@ -94,7 +99,6 @@ try { assert.ok(JSON.stringify(transcript.messages).includes(`Transcript proof ${kind} ${phase}`)) transcripts.push({ kind, phase, sessionId: session.id, messages: transcript.messages }) const beforeChild = posts.length - const childExtension = await load() await emit(childExtension, 'session_start', child) await emit(childExtension, 'agent_start', child) await emit(childExtension, 'agent_end', child)