Hook commands: remove string return, use sendMessage() for prompting

- Command handler now returns Promise<void> instead of Promise<string | undefined>
- 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
This commit is contained in:
Mario Zechner 2025-12-27 01:21:18 +01:00
parent c8d9382aaa
commit 30cd723411
5 changed files with 50 additions and 23 deletions

12
.pi/commands/review.md Normal file
View file

@ -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

24
.pi/hooks/test-command.ts Normal file
View file

@ -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
);
},
});
}

View file

@ -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

View file

@ -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<boolean> {
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;
}
}

View file

@ -426,7 +426,7 @@ export interface CommandContext {
export interface RegisteredCommand {
name: string;
description?: string;
handler: (ctx: CommandContext) => Promise<string | undefined>;
handler: (ctx: CommandContext) => Promise<void>;
}
/**