Move hook command execution to AgentSession.prompt()

Hook commands registered via pi.registerCommand() are now handled in
AgentSession.prompt() alongside file-based slash commands. This:

- Removes duplicate tryHandleHookCommand from interactive-mode and rpc-mode
- All modes (interactive, RPC, print) share the same command handling logic
- AgentSession._tryExecuteHookCommand() builds CommandContext using:
  - UI context from hookRunner (set by mode)
  - sessionManager, modelRegistry from AgentSession
  - sendMessage via sendHookMessage
  - exec via exported execCommand
- Handler returning string uses it as prompt, undefined returns early

Also:
- Export execCommand from hooks/runner.ts
- Add getUIContext() and getHasUI() to HookRunner
- Make HookRunner.emitError() public for error reporting
This commit is contained in:
Mario Zechner 2025-12-27 01:13:52 +01:00
parent ba185b0571
commit c8d9382aaa
6 changed files with 149 additions and 19 deletions

View file

@ -34,7 +34,12 @@ export type HookErrorListener = (error: HookError) => void;
* Execute a command and return stdout/stderr/code.
* Supports cancellation via AbortSignal and timeout.
*/
async function exec(command: string, args: string[], cwd: string, options?: ExecOptions): Promise<ExecResult> {
export async function execCommand(
command: string,
args: string[],
cwd: string,
options?: ExecOptions,
): Promise<ExecResult> {
return new Promise((resolve) => {
const proc = spawn(command, args, { cwd, shell: false });
@ -150,6 +155,20 @@ export class HookRunner {
this.hasUI = hasUI;
}
/**
* Get the UI context (set by mode).
*/
getUIContext(): HookUIContext | null {
return this.uiContext;
}
/**
* Get whether UI is available.
*/
getHasUI(): boolean {
return this.hasUI;
}
/**
* Get the paths of all loaded hooks.
*/
@ -196,7 +215,10 @@ export class HookRunner {
/**
* Emit an error to all listeners.
*/
private emitError(error: HookError): void {
/**
* Emit an error to all error listeners.
*/
emitError(error: HookError): void {
for (const listener of this.errorListeners) {
listener(error);
}
@ -261,7 +283,8 @@ export class HookRunner {
*/
private createContext(): HookEventContext {
return {
exec: (command: string, args: string[], options?: ExecOptions) => exec(command, args, this.cwd, options),
exec: (command: string, args: string[], options?: ExecOptions) =>
execCommand(command, args, this.cwd, options),
ui: this.uiContext,
hasUI: this.hasUI,
cwd: this.cwd,