diff --git a/src/cli/services/__tests__/hookDispatchService.test.ts b/src/cli/services/__tests__/hookDispatchService.test.ts index 4452583..8513209 100644 --- a/src/cli/services/__tests__/hookDispatchService.test.ts +++ b/src/cli/services/__tests__/hookDispatchService.test.ts @@ -589,7 +589,7 @@ describe('HookDispatchService session lifecycle', () => { expect(stopResult.output).toEqual({ continue: true }); }); - it('emits Stop workflow guidance when a PREVC workflow is active', async () => { + it('emits Codex Stop workflow guidance as a systemMessage when a PREVC workflow is active', async () => { const workflowService = await WorkflowService.create(tempDir); await workflowService.init({ name: 'feature-x', @@ -615,12 +615,11 @@ describe('HookDispatchService session lifecycle', () => { expect(stopResult.exitCode).toBe(0); expect(stopResult.output).toMatchObject({ - hookSpecificOutput: { - hookEventName: 'Stop', - additionalContext: expect.stringContaining('feature-x'), - }, + continue: true, + systemMessage: expect.stringContaining('feature-x'), }); expect(stopResult.output).not.toHaveProperty('source'); + expect(stopResult.output).not.toHaveProperty('hookSpecificOutput'); }); it.each(['Stop', 'SubagentStop'])( diff --git a/src/integrations/__tests__/hookRoundTrip.test.ts b/src/integrations/__tests__/hookRoundTrip.test.ts index 2620703..bb2f4b0 100644 --- a/src/integrations/__tests__/hookRoundTrip.test.ts +++ b/src/integrations/__tests__/hookRoundTrip.test.ts @@ -218,6 +218,26 @@ describe('hook mapper unit tests', () => { }); }); + it('emits Stop workflow guidance as a Codex systemMessage', () => { + const output = mapCodexResponse(codexStopFixture, { + ok: true, + tool: 'workflow-guide', + source: 'codex', + result: { + kind: 'json', + data: { + workflow: { active: true }, + excerpt: 'dotcontext workflow guide:\nWorkflow "feature-x" - phase V.', + }, + }, + }); + + expect(output).toEqual({ + continue: true, + systemMessage: expect.stringContaining('feature-x'), + }); + }); + it('suppresses Stop guidance during reentry', () => { const output = mapCodexResponse( { ...codexStopFixture, sessionEndActive: true }, @@ -535,8 +555,9 @@ describe('hook round-trip integrations', () => { }, (output: ReturnType) => { expect(output).not.toHaveProperty('source'); - expect(output.hookSpecificOutput?.hookEventName).toBe('Stop'); - expect(output.hookSpecificOutput?.additionalContext).toContain('feature-x'); + expect(output).not.toHaveProperty('hookSpecificOutput'); + expect(output.continue).toBe(true); + expect(output.systemMessage).toContain('feature-x'); }, ], ])('%s envelope round-trips through adapter and response mapper', async ( diff --git a/src/integrations/codex/hooks/mapCodexResponse.ts b/src/integrations/codex/hooks/mapCodexResponse.ts index 8208dbd..c777e72 100644 --- a/src/integrations/codex/hooks/mapCodexResponse.ts +++ b/src/integrations/codex/hooks/mapCodexResponse.ts @@ -10,6 +10,21 @@ import type { CodexHookInput } from './mapCodexEvent'; export type CodexHookOutput = Omit; +function mapCodexStopOutput(output: CodexHookOutput): CodexHookOutput { + const { hookSpecificOutput, ...rest } = output; + const additionalContext = hookSpecificOutput?.additionalContext; + + if (!additionalContext) { + return output; + } + + return { + ...rest, + continue: rest.continue ?? true, + systemMessage: rest.systemMessage ?? additionalContext, + }; +} + export function mapCodexResponse( event: CodexHookInput, response: HarnessHookResponse @@ -17,7 +32,13 @@ export function mapCodexResponse( const hookEventName = typeof event.hook_event_name === 'string' ? event.hook_event_name : 'unknown'; - return mapHostHookResponseForSource('codex', hookEventName, response, { + const output = mapHostHookResponseForSource('codex', hookEventName, response, { suppressAdditionalContext: isSessionEndReentry(event), }) as CodexHookOutput; + + if (hookEventName === 'Stop') { + return mapCodexStopOutput(output); + } + + return output; }