(
messagesForQuery: Message[],
assistantMessages: AssistantMessage[],
systemPrompt: SystemPrompt,
userContext: { [k: string]: string },
systemContext: { [k: string]: string },
toolUseContext: ToolUseContext,
querySource: QuerySource,
stopHookActive?: boolean,
)
| 63 | } |
| 64 | |
| 65 | export async function* handleStopHooks( |
| 66 | messagesForQuery: Message[], |
| 67 | assistantMessages: AssistantMessage[], |
| 68 | systemPrompt: SystemPrompt, |
| 69 | userContext: { [k: string]: string }, |
| 70 | systemContext: { [k: string]: string }, |
| 71 | toolUseContext: ToolUseContext, |
| 72 | querySource: QuerySource, |
| 73 | stopHookActive?: boolean, |
| 74 | ): AsyncGenerator< |
| 75 | | StreamEvent |
| 76 | | RequestStartEvent |
| 77 | | Message |
| 78 | | TombstoneMessage |
| 79 | | ToolUseSummaryMessage, |
| 80 | StopHookResult |
| 81 | > { |
| 82 | const hookStartTime = Date.now() |
| 83 | |
| 84 | const stopHookContext: REPLHookContext = { |
| 85 | messages: [...messagesForQuery, ...assistantMessages], |
| 86 | systemPrompt, |
| 87 | userContext, |
| 88 | systemContext, |
| 89 | toolUseContext, |
| 90 | querySource, |
| 91 | } |
| 92 | // Only save params for main session queries — subagents must not overwrite. |
| 93 | // Outside the prompt-suggestion gate: the REPL /btw command and the |
| 94 | // side_question SDK control_request both read this snapshot, and neither |
| 95 | // depends on prompt suggestions being enabled. |
| 96 | if (querySource === 'repl_main_thread' || querySource === 'sdk') { |
| 97 | saveCacheSafeParams(createCacheSafeParams(stopHookContext)) |
| 98 | } |
| 99 | |
| 100 | // Template job classification: when running as a dispatched job, classify |
| 101 | // state after each turn. Gate on repl_main_thread so background forks |
| 102 | // (extract-memories, auto-dream) don't pollute the timeline with their own |
| 103 | // assistant messages. Await the classifier so state.json is written before |
| 104 | // the turn returns — otherwise `claude list` shows stale state for the gap. |
| 105 | // Env key hardcoded (vs importing JOB_ENV_KEY from jobs/state) to match the |
| 106 | // require()-gated jobs/ import pattern above; spawn.test.ts asserts the |
| 107 | // string matches. |
| 108 | if ( |
| 109 | feature('TEMPLATES') && |
| 110 | process.env.CLAUDE_JOB_DIR && |
| 111 | querySource.startsWith('repl_main_thread') && |
| 112 | !toolUseContext.agentId |
| 113 | ) { |
| 114 | // Full turn history — assistantMessages resets each queryLoop iteration, |
| 115 | // so tool calls from earlier iterations (Agent spawn, then summary) need |
| 116 | // messagesForQuery to be visible in the tool-call summary. |
| 117 | const turnAssistantMessages = stopHookContext.messages.filter( |
| 118 | (m): m is AssistantMessage => m.type === 'assistant', |
| 119 | ) |
| 120 | const p = jobClassifierModule! |
| 121 | .classifyAndWriteState(process.env.CLAUDE_JOB_DIR, turnAssistantMessages) |
| 122 | .catch(err => { |
no test coverage detected