diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 19eec49f..7eca501e 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -15,7 +15,7 @@ - `queuedMessageCount` → `pendingMessageCount` - `getQueuedMessages()` → `getSteeringMessages()` and `getFollowUpMessages()` - `clearQueue()` now returns `{ steering: string[], followUp: string[] }` -- **sendHookMessage() signature changed**: Second parameter changed from `triggerTurn?: boolean` to `options?: { triggerTurn?, deliverAs? }`. Use `deliverAs: "followUp"` for follow-up delivery. +- **Hook API signature changed**: `pi.sendMessage()` second parameter changed from `triggerTurn?: boolean` to `options?: { triggerTurn?, deliverAs? }`. Use `deliverAs: "followUp"` for follow-up delivery. Affects both hooks and internal `sendHookMessage()` method. - **RPC API changes**: - `queue_message` command → `steer` and `follow_up` commands - `set_queue_mode` command → `set_steering_mode` and `set_follow_up_mode` commands diff --git a/packages/coding-agent/examples/hooks/file-trigger.ts b/packages/coding-agent/examples/hooks/file-trigger.ts index e3f69b1f..08c995ad 100644 --- a/packages/coding-agent/examples/hooks/file-trigger.ts +++ b/packages/coding-agent/examples/hooks/file-trigger.ts @@ -25,7 +25,7 @@ export default function (pi: HookAPI) { content: `External trigger: ${content}`, display: true, }, - true, // triggerTurn - get LLM to respond + { triggerTurn: true }, // triggerTurn - get LLM to respond ); fs.writeFileSync(triggerFile, ""); // Clear after reading } diff --git a/packages/coding-agent/src/core/hooks/loader.ts b/packages/coding-agent/src/core/hooks/loader.ts index f876aed9..e0bbe9ac 100644 --- a/packages/coding-agent/src/core/hooks/loader.ts +++ b/packages/coding-agent/src/core/hooks/loader.ts @@ -53,7 +53,7 @@ type HandlerFn = (...args: unknown[]) => Promise; */ export type SendMessageHandler = ( message: Pick, "customType" | "content" | "display" | "details">, - triggerTurn?: boolean, + options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" }, ) => void; /** @@ -177,8 +177,11 @@ function createHookAPI( list.push(handler); handlers.set(event, list); }, - sendMessage(message: HookMessage, triggerTurn?: boolean): void { - sendMessageHandler(message, triggerTurn); + sendMessage( + message: HookMessage, + options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" }, + ): void { + sendMessageHandler(message, options); }, appendEntry(customType: string, data?: T): void { appendEntryHandler(customType, data); diff --git a/packages/coding-agent/src/core/hooks/types.ts b/packages/coding-agent/src/core/hooks/types.ts index f6503a34..50597c42 100644 --- a/packages/coding-agent/src/core/hooks/types.ts +++ b/packages/coding-agent/src/core/hooks/types.ts @@ -692,12 +692,15 @@ export interface HookAPI { * @param message.content - Message content (string or TextContent/ImageContent array) * @param message.display - Whether to show in TUI (true = styled display, false = hidden) * @param message.details - Optional hook-specific metadata (not sent to LLM) - * @param triggerTurn - If true and agent is idle, triggers a new LLM turn. Default: false. - * If agent is streaming, message is queued and triggerTurn is ignored. + * @param options.triggerTurn - If true and agent is idle, triggers a new LLM turn. Default: false. + * If agent is streaming, message is queued and triggerTurn is ignored. + * @param options.deliverAs - How to deliver when agent is streaming. Default: "steer". + * - "steer": Interrupt mid-run, delivered after current tool execution. + * - "followUp": Wait until agent finishes all work before delivery. */ sendMessage( message: Pick, "customType" | "content" | "display" | "details">, - triggerTurn?: boolean, + options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" }, ): void; /** diff --git a/packages/coding-agent/src/core/sdk.ts b/packages/coding-agent/src/core/sdk.ts index 5e6b663e..c7e49f27 100644 --- a/packages/coding-agent/src/core/sdk.ts +++ b/packages/coding-agent/src/core/sdk.ts @@ -344,7 +344,10 @@ function createLoadedHooksFromDefinitions(definitions: Array<{ path?: string; fa const handlers = new Map Promise>>(); const messageRenderers = new Map(); const commands = new Map(); - let sendMessageHandler: (message: any, triggerTurn?: boolean) => void = () => {}; + let sendMessageHandler: ( + message: any, + options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" }, + ) => void = () => {}; let appendEntryHandler: (customType: string, data?: any) => void = () => {}; let newSessionHandler: (options?: any) => Promise<{ cancelled: boolean }> = async () => ({ cancelled: false }); let branchHandler: (entryId: string) => Promise<{ cancelled: boolean }> = async () => ({ cancelled: false }); @@ -358,8 +361,8 @@ function createLoadedHooksFromDefinitions(definitions: Array<{ path?: string; fa list.push(handler); handlers.set(event, list); }, - sendMessage: (message: any, triggerTurn?: boolean) => { - sendMessageHandler(message, triggerTurn); + sendMessage: (message: any, options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" }) => { + sendMessageHandler(message, options); }, appendEntry: (customType: string, data?: any) => { appendEntryHandler(customType, data); @@ -383,7 +386,9 @@ function createLoadedHooksFromDefinitions(definitions: Array<{ path?: string; fa handlers, messageRenderers, commands, - setSendMessageHandler: (handler: (message: any, triggerTurn?: boolean) => void) => { + setSendMessageHandler: ( + handler: (message: any, options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" }) => void, + ) => { sendMessageHandler = handler; }, setAppendEntryHandler: (handler: (customType: string, data?: any) => void) => { diff --git a/packages/coding-agent/src/modes/interactive/interactive-mode.ts b/packages/coding-agent/src/modes/interactive/interactive-mode.ts index 7acae1fd..0996f993 100644 --- a/packages/coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive/interactive-mode.ts @@ -404,10 +404,10 @@ export class InteractiveMode { hookRunner.initialize({ getModel: () => this.session.model, - sendMessageHandler: (message, triggerTurn) => { + sendMessageHandler: (message, options) => { const wasStreaming = this.session.isStreaming; this.session - .sendHookMessage(message, { triggerTurn }) + .sendHookMessage(message, options) .then(() => { // For non-streaming cases with display=true, update UI // (streaming cases update via message_end event) diff --git a/packages/coding-agent/src/modes/print-mode.ts b/packages/coding-agent/src/modes/print-mode.ts index e2b51544..56c53420 100644 --- a/packages/coding-agent/src/modes/print-mode.ts +++ b/packages/coding-agent/src/modes/print-mode.ts @@ -32,8 +32,8 @@ export async function runPrintMode( if (hookRunner) { hookRunner.initialize({ getModel: () => session.model, - sendMessageHandler: (message, triggerTurn) => { - session.sendHookMessage(message, { triggerTurn }).catch((e) => { + sendMessageHandler: (message, options) => { + session.sendHookMessage(message, options).catch((e) => { console.error(`Hook sendMessage failed: ${e instanceof Error ? e.message : String(e)}`); }); }, diff --git a/packages/coding-agent/src/modes/rpc/rpc-mode.ts b/packages/coding-agent/src/modes/rpc/rpc-mode.ts index 1d22781e..c57534e9 100644 --- a/packages/coding-agent/src/modes/rpc/rpc-mode.ts +++ b/packages/coding-agent/src/modes/rpc/rpc-mode.ts @@ -181,8 +181,8 @@ export async function runRpcMode(session: AgentSession): Promise { if (hookRunner) { hookRunner.initialize({ getModel: () => session.agent.state.model, - sendMessageHandler: (message, triggerTurn) => { - session.sendHookMessage(message, { triggerTurn }).catch((e) => { + sendMessageHandler: (message, options) => { + session.sendHookMessage(message, options).catch((e) => { output(error(undefined, "hook_send", e.message)); }); },