From 30cd723411c0eeb2fc86d2558e96115ac20d83c9 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Sat, 27 Dec 2025 01:21:18 +0100 Subject: [PATCH] Hook commands: remove string return, use sendMessage() for prompting - Command handler now returns Promise instead of Promise - To trigger LLM response, use sendMessage() with triggerTurn: true - Simplify _tryExecuteHookCommand to return boolean Added example hook and slash command in .pi/: - .pi/hooks/test-command.ts - /greet command using sendMessage - .pi/commands/review.md - file-based /review command --- .pi/commands/review.md | 12 +++++++ .pi/hooks/test-command.ts | 24 ++++++++++++++ .../coding-agent/docs/session-tree-plan.md | 3 +- .../coding-agent/src/core/agent-session.ts | 32 +++++++------------ packages/coding-agent/src/core/hooks/types.ts | 2 +- 5 files changed, 50 insertions(+), 23 deletions(-) create mode 100644 .pi/commands/review.md create mode 100644 .pi/hooks/test-command.ts diff --git a/.pi/commands/review.md b/.pi/commands/review.md new file mode 100644 index 00000000..3e1db779 --- /dev/null +++ b/.pi/commands/review.md @@ -0,0 +1,12 @@ +--- +description: Review a file for issues +--- +Please review the following file for potential issues, bugs, or improvements: + +$1 + +Focus on: +- Logic errors +- Edge cases +- Code style +- Performance concerns diff --git a/.pi/hooks/test-command.ts b/.pi/hooks/test-command.ts new file mode 100644 index 00000000..07354c87 --- /dev/null +++ b/.pi/hooks/test-command.ts @@ -0,0 +1,24 @@ +/** + * Test hook that registers a /greet command. + * Usage: /greet [name] + */ +import type { HookAPI } from "@mariozechner/pi-coding-agent/hooks"; + +export default function (pi: HookAPI) { + pi.registerCommand("greet", { + description: "Send a greeting message to the LLM", + handler: async (ctx) => { + const name = ctx.args.trim() || "world"; + + // Insert a custom message and trigger LLM response + ctx.sendMessage( + { + customType: "greeting", + content: `Hello, ${name}! Please say something nice about them.`, + display: true, + }, + true, // triggerTurn - get LLM to respond + ); + }, + }); +} diff --git a/packages/coding-agent/docs/session-tree-plan.md b/packages/coding-agent/docs/session-tree-plan.md index 26d9b01c..af7efc47 100644 --- a/packages/coding-agent/docs/session-tree-plan.md +++ b/packages/coding-agent/docs/session-tree-plan.md @@ -183,8 +183,7 @@ registerCommand(name: string, options: { ``` Handler return: -- `undefined` - command completed -- `string` - text to send as prompt (like file-based slash commands) +- `void` - command completed (use `sendMessage()` with `triggerTurn: true` to prompt LLM) Wiring (all in AgentSession.prompt()): - [x] Add hook commands to autocomplete in interactive-mode diff --git a/packages/coding-agent/src/core/agent-session.ts b/packages/coding-agent/src/core/agent-session.ts index cf0942f8..45c6f3c0 100644 --- a/packages/coding-agent/src/core/agent-session.ts +++ b/packages/coding-agent/src/core/agent-session.ts @@ -462,15 +462,10 @@ export class AgentSession { // Handle hook commands first (if enabled and text is a slash command) if (expandCommands && text.startsWith("/")) { - const result = await this._tryExecuteHookCommand(text); - if (result.handled) { - if (result.prompt) { - // Hook returned text to use as prompt - text = result.prompt; - } else { - // Hook command executed, no prompt to send - return; - } + const handled = await this._tryExecuteHookCommand(text); + if (handled) { + // Hook command executed, no prompt to send + return; } } @@ -506,10 +501,10 @@ export class AgentSession { } /** - * Try to execute a hook command. Returns whether it was handled and optional prompt text. + * Try to execute a hook command. Returns true if command was found and executed. */ - private async _tryExecuteHookCommand(text: string): Promise<{ handled: boolean; prompt?: string }> { - if (!this._hookRunner) return { handled: false }; + private async _tryExecuteHookCommand(text: string): Promise { + if (!this._hookRunner) return false; // Parse command name and args const spaceIndex = text.indexOf(" "); @@ -517,11 +512,11 @@ export class AgentSession { const args = spaceIndex === -1 ? "" : text.slice(spaceIndex + 1); const command = this._hookRunner.getCommand(commandName); - if (!command) return { handled: false }; + if (!command) return false; // Get UI context from hook runner (set by mode) const uiContext = this._hookRunner.getUIContext(); - if (!uiContext) return { handled: false }; + if (!uiContext) return false; // Build command context const cwd = process.cwd(); @@ -541,11 +536,8 @@ export class AgentSession { }; try { - const result = await command.handler(ctx); - if (typeof result === "string") { - return { handled: true, prompt: result }; - } - return { handled: true }; + await command.handler(ctx); + return true; } catch (err) { // Emit error via hook runner this._hookRunner.emitError({ @@ -553,7 +545,7 @@ export class AgentSession { event: "command", error: err instanceof Error ? err.message : String(err), }); - return { handled: true }; + return true; } } diff --git a/packages/coding-agent/src/core/hooks/types.ts b/packages/coding-agent/src/core/hooks/types.ts index 28902cb7..7a3e72f7 100644 --- a/packages/coding-agent/src/core/hooks/types.ts +++ b/packages/coding-agent/src/core/hooks/types.ts @@ -426,7 +426,7 @@ export interface CommandContext { export interface RegisteredCommand { name: string; description?: string; - handler: (ctx: CommandContext) => Promise; + handler: (ctx: CommandContext) => Promise; } /**