mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 10:05:14 +00:00
refactor(hooks): address PR feedback
- Rename getTools/setTools to getActiveTools/setActiveTools - Add getAllTools to enumerate all configured tools - Remove text_delta event (use turn_end/agent_end instead) - Add shortcut conflict detection: - Skip shortcuts that conflict with built-in shortcuts (with warning) - Log warnings when hooks register same shortcut (last wins) - Add note about prompt cache invalidation in setActiveTools - Update plan-mode hook to use agent_end for [DONE:id] parsing
This commit is contained in:
parent
5b634ddf75
commit
4ecf3f9422
13 changed files with 175 additions and 153 deletions
|
|
@ -37,12 +37,12 @@
|
|||
### Added
|
||||
|
||||
- `$ARGUMENTS` syntax for custom slash commands as alternative to `$@` for all arguments joined. Aligns with patterns used by Claude, Codex, and OpenCode. Both syntaxes remain fully supported. ([#418](https://github.com/badlogic/pi-mono/pull/418) by [@skuridin](https://github.com/skuridin))
|
||||
- Hook API: `pi.getTools()` and `pi.setTools(toolNames)` for dynamically enabling/disabling tools from hooks
|
||||
- Hook API: `pi.getActiveTools()` and `pi.setActiveTools(toolNames)` for dynamically enabling/disabling tools from hooks
|
||||
- Hook API: `pi.getAllTools()` to enumerate all configured tools (built-in via --tools or default, plus custom tools)
|
||||
- Hook API: `pi.registerFlag(name, options)` and `pi.getFlag(name)` for hooks to register custom CLI flags (parsed automatically)
|
||||
- Hook API: `pi.registerShortcut(shortcut, options)` for hooks to register custom keyboard shortcuts (e.g., `shift+p`, `ctrl+shift+x`)
|
||||
- Hook API: `pi.registerShortcut(shortcut, options)` for hooks to register custom keyboard shortcuts (e.g., `shift+p`, `ctrl+shift+x`). Conflicts with built-in shortcuts are skipped, conflicts between hooks logged as warnings.
|
||||
- Hook API: `ctx.ui.setWidget(key, lines)` for multi-line status displays above the editor (todo lists, progress tracking)
|
||||
- Hook API: `theme.strikethrough(text)` for strikethrough text styling
|
||||
- Hook API: `text_delta` event for monitoring streaming assistant text in real-time
|
||||
- `/hotkeys` command now shows hook-registered shortcuts in a separate "Hooks" section
|
||||
- New example hook: `plan-mode.ts` - Claude Code-style read-only exploration mode:
|
||||
- Toggle via `/plan` command, `Shift+P` shortcut, or `--plan` CLI flag
|
||||
|
|
@ -51,7 +51,7 @@
|
|||
- Interactive prompt after each response: execute plan, stay in plan mode, or refine
|
||||
- Todo list widget showing progress with checkboxes and strikethrough for completed items
|
||||
- Each todo has a unique ID; agent marks items done by outputting `[DONE:id]`
|
||||
- Real-time progress updates via streaming text monitoring
|
||||
- Progress updates via `agent_end` hook (parses completed items from final message)
|
||||
- `/todos` command to view current plan progress
|
||||
- Shows `⏸ plan` indicator in footer when in plan mode, `📋 2/5` when executing
|
||||
- State persists across sessions (including todo progress)
|
||||
|
|
|
|||
|
|
@ -306,21 +306,6 @@ pi.on("turn_end", async (event, ctx) => {
|
|||
});
|
||||
```
|
||||
|
||||
#### text_delta
|
||||
|
||||
Fired for each chunk of streaming text from the assistant. Useful for real-time monitoring of agent output.
|
||||
|
||||
```typescript
|
||||
pi.on("text_delta", async (event, ctx) => {
|
||||
// event.text - the new text chunk
|
||||
|
||||
// Example: watch for specific patterns in streaming output
|
||||
if (event.text.includes("[DONE:")) {
|
||||
// Handle completion marker
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
#### context
|
||||
|
||||
Fired before each LLM call. Modify messages non-destructively (session unchanged).
|
||||
|
|
@ -782,25 +767,35 @@ const result = await pi.exec("git", ["status"], {
|
|||
// result.stdout, result.stderr, result.code, result.killed
|
||||
```
|
||||
|
||||
### pi.getTools()
|
||||
### pi.getActiveTools()
|
||||
|
||||
Get the names of currently active tools:
|
||||
|
||||
```typescript
|
||||
const toolNames = pi.getTools();
|
||||
const toolNames = pi.getActiveTools();
|
||||
// ["read", "bash", "edit", "write"]
|
||||
```
|
||||
|
||||
### pi.setTools(toolNames)
|
||||
### pi.getAllTools()
|
||||
|
||||
Get all configured tools (built-in via --tools or default, plus custom tools):
|
||||
|
||||
```typescript
|
||||
const allTools = pi.getAllTools();
|
||||
// ["read", "bash", "edit", "write", "my-custom-tool"]
|
||||
```
|
||||
|
||||
### pi.setActiveTools(toolNames)
|
||||
|
||||
Set the active tools by name. Changes take effect on the next agent turn.
|
||||
Note: This will invalidate prompt caching for the next request.
|
||||
|
||||
```typescript
|
||||
// Switch to read-only mode (plan mode)
|
||||
pi.setTools(["read", "bash", "grep", "find", "ls"]);
|
||||
pi.setActiveTools(["read", "bash", "grep", "find", "ls"]);
|
||||
|
||||
// Restore full access
|
||||
pi.setTools(["read", "bash", "edit", "write"]);
|
||||
pi.setActiveTools(["read", "bash", "edit", "write"]);
|
||||
```
|
||||
|
||||
Only built-in tools can be enabled/disabled. Custom tools are always active. Unknown tool names are ignored.
|
||||
|
|
|
|||
|
|
@ -232,10 +232,10 @@ export default function planModeHook(pi: HookAPI) {
|
|||
todoItems = [];
|
||||
|
||||
if (planModeEnabled) {
|
||||
pi.setTools(PLAN_MODE_TOOLS);
|
||||
pi.setActiveTools(PLAN_MODE_TOOLS);
|
||||
ctx.ui.notify(`Plan mode enabled. Tools: ${PLAN_MODE_TOOLS.join(", ")}`);
|
||||
} else {
|
||||
pi.setTools(NORMAL_MODE_TOOLS);
|
||||
pi.setActiveTools(NORMAL_MODE_TOOLS);
|
||||
ctx.ui.notify("Plan mode disabled. Full access restored.");
|
||||
}
|
||||
updateStatus(ctx);
|
||||
|
|
@ -291,39 +291,6 @@ export default function planModeHook(pi: HookAPI) {
|
|||
}
|
||||
});
|
||||
|
||||
// Buffer for accumulating text to handle [DONE:id] split across chunks
|
||||
let textBuffer = "";
|
||||
|
||||
// Watch for [DONE:id] tags in streaming text
|
||||
pi.on("text_delta", async (event, ctx) => {
|
||||
if (!executionMode || todoItems.length === 0) return;
|
||||
|
||||
// Accumulate text in buffer
|
||||
textBuffer += event.text;
|
||||
|
||||
// Look for complete [DONE:id] patterns
|
||||
const doneIds = findDoneTags(textBuffer);
|
||||
if (doneIds.length === 0) return;
|
||||
|
||||
let changed = false;
|
||||
for (const id of doneIds) {
|
||||
const item = todoItems.find((t) => t.id === id);
|
||||
if (item && !item.completed) {
|
||||
item.completed = true;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear processed patterns from buffer (keep last 20 chars for partial matches)
|
||||
if (textBuffer.length > 50) {
|
||||
textBuffer = textBuffer.slice(-20);
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
updateStatus(ctx);
|
||||
}
|
||||
});
|
||||
|
||||
// Inject plan mode context
|
||||
pi.on("before_agent_start", async () => {
|
||||
if (!planModeEnabled && !executionMode) return;
|
||||
|
|
@ -372,10 +339,7 @@ IMPORTANT: After completing each step, output [DONE:id] where id is the step's I
|
|||
|
||||
// After agent finishes in plan mode
|
||||
pi.on("agent_end", async (event, ctx) => {
|
||||
// Clear text buffer
|
||||
textBuffer = "";
|
||||
|
||||
// Check for done tags in the final message too
|
||||
// Check for done tags in the final message
|
||||
if (executionMode && todoItems.length > 0) {
|
||||
const messages = event.messages;
|
||||
const lastAssistant = [...messages].reverse().find((m) => m.role === "assistant");
|
||||
|
|
@ -399,9 +363,7 @@ IMPORTANT: After completing each step, output [DONE:id] where id is the step's I
|
|||
const allComplete = todoItems.every((t) => t.completed);
|
||||
if (allComplete) {
|
||||
// Show final completed list in chat
|
||||
const completedList = todoItems
|
||||
.map((t) => `~~${t.text}~~`)
|
||||
.join("\n");
|
||||
const completedList = todoItems.map((t) => `~~${t.text}~~`).join("\n");
|
||||
pi.sendMessage(
|
||||
{
|
||||
customType: "plan-complete",
|
||||
|
|
@ -412,9 +374,9 @@ IMPORTANT: After completing each step, output [DONE:id] where id is the step's I
|
|||
);
|
||||
|
||||
executionMode = false;
|
||||
const completedItems = [...todoItems]; // Keep for reference
|
||||
const _completedItems = [...todoItems]; // Keep for reference
|
||||
todoItems = [];
|
||||
pi.setTools(NORMAL_MODE_TOOLS);
|
||||
pi.setActiveTools(NORMAL_MODE_TOOLS);
|
||||
updateStatus(ctx);
|
||||
}
|
||||
return;
|
||||
|
|
@ -464,7 +426,7 @@ IMPORTANT: After completing each step, output [DONE:id] where id is the step's I
|
|||
if (choice?.startsWith("Execute")) {
|
||||
planModeEnabled = false;
|
||||
executionMode = hasTodos;
|
||||
pi.setTools(NORMAL_MODE_TOOLS);
|
||||
pi.setActiveTools(NORMAL_MODE_TOOLS);
|
||||
updateStatus(ctx);
|
||||
|
||||
const execMessage = hasTodos
|
||||
|
|
@ -511,7 +473,7 @@ IMPORTANT: After completing each step, output [DONE:id] where id is the step's I
|
|||
}
|
||||
|
||||
if (planModeEnabled) {
|
||||
pi.setTools(PLAN_MODE_TOOLS);
|
||||
pi.setActiveTools(PLAN_MODE_TOOLS);
|
||||
}
|
||||
updateStatus(ctx);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -224,15 +224,6 @@ export class AgentSession {
|
|||
|
||||
/** Internal handler for agent events - shared by subscribe and reconnect */
|
||||
private _handleAgentEvent = async (event: AgentEvent): Promise<void> => {
|
||||
// Emit text_delta events to hooks for streaming text monitoring
|
||||
if (
|
||||
event.type === "message_update" &&
|
||||
event.assistantMessageEvent.type === "text_delta" &&
|
||||
this._hookRunner
|
||||
) {
|
||||
await this._hookRunner.emit({ type: "text_delta", text: event.assistantMessageEvent.delta });
|
||||
}
|
||||
|
||||
// When a user message starts, check if it's from either queue and remove it BEFORE emitting
|
||||
// This ensures the UI sees the updated queue state
|
||||
if (event.type === "message_start" && event.message.role === "user") {
|
||||
|
|
@ -447,6 +438,13 @@ export class AgentSession {
|
|||
return this.agent.state.tools.map((t) => t.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all configured tool names (built-in via --tools or default, plus custom tools).
|
||||
*/
|
||||
getAllToolNames(): string[] {
|
||||
return Array.from(this._toolRegistry.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set active tools by name.
|
||||
* Only tools in the registry can be enabled. Unknown tool names are ignored.
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ export {
|
|||
loadHooks,
|
||||
type AppendEntryHandler,
|
||||
type BranchHandler,
|
||||
type GetToolsHandler,
|
||||
type GetActiveToolsHandler,
|
||||
type GetAllToolsHandler,
|
||||
type HookFlag,
|
||||
type HookShortcut,
|
||||
type LoadedHook,
|
||||
|
|
@ -12,7 +13,7 @@ export {
|
|||
type NavigateTreeHandler,
|
||||
type NewSessionHandler,
|
||||
type SendMessageHandler,
|
||||
type SetToolsHandler,
|
||||
type SetActiveToolsHandler,
|
||||
} from "./loader.js";
|
||||
export { execCommand, HookRunner, type HookErrorListener } from "./runner.js";
|
||||
export { wrapToolsWithHooks, wrapToolWithHooks } from "./tool-wrapper.js";
|
||||
|
|
|
|||
|
|
@ -62,14 +62,19 @@ export type SendMessageHandler = <T = unknown>(
|
|||
export type AppendEntryHandler = <T = unknown>(customType: string, data?: T) => void;
|
||||
|
||||
/**
|
||||
* Get tools handler type for pi.getTools().
|
||||
* Get active tools handler type for pi.getActiveTools().
|
||||
*/
|
||||
export type GetToolsHandler = () => string[];
|
||||
export type GetActiveToolsHandler = () => string[];
|
||||
|
||||
/**
|
||||
* Set tools handler type for pi.setTools().
|
||||
* Get all tools handler type for pi.getAllTools().
|
||||
*/
|
||||
export type SetToolsHandler = (toolNames: string[]) => void;
|
||||
export type GetAllToolsHandler = () => string[];
|
||||
|
||||
/**
|
||||
* Set active tools handler type for pi.setActiveTools().
|
||||
*/
|
||||
export type SetActiveToolsHandler = (toolNames: string[]) => void;
|
||||
|
||||
/**
|
||||
* CLI flag definition registered by a hook.
|
||||
|
|
@ -146,10 +151,12 @@ export interface LoadedHook {
|
|||
setSendMessageHandler: (handler: SendMessageHandler) => void;
|
||||
/** Set the append entry handler for this hook's pi.appendEntry() */
|
||||
setAppendEntryHandler: (handler: AppendEntryHandler) => void;
|
||||
/** Set the get tools handler for this hook's pi.getTools() */
|
||||
setGetToolsHandler: (handler: GetToolsHandler) => void;
|
||||
/** Set the set tools handler for this hook's pi.setTools() */
|
||||
setSetToolsHandler: (handler: SetToolsHandler) => void;
|
||||
/** Set the get active tools handler for this hook's pi.getActiveTools() */
|
||||
setGetActiveToolsHandler: (handler: GetActiveToolsHandler) => void;
|
||||
/** Set the get all tools handler for this hook's pi.getAllTools() */
|
||||
setGetAllToolsHandler: (handler: GetAllToolsHandler) => void;
|
||||
/** Set the set active tools handler for this hook's pi.setActiveTools() */
|
||||
setSetActiveToolsHandler: (handler: SetActiveToolsHandler) => void;
|
||||
/** Set a flag value (called after CLI parsing) */
|
||||
setFlagValue: (name: string, value: boolean | string) => void;
|
||||
}
|
||||
|
|
@ -215,8 +222,9 @@ function createHookAPI(
|
|||
shortcuts: Map<string, HookShortcut>;
|
||||
setSendMessageHandler: (handler: SendMessageHandler) => void;
|
||||
setAppendEntryHandler: (handler: AppendEntryHandler) => void;
|
||||
setGetToolsHandler: (handler: GetToolsHandler) => void;
|
||||
setSetToolsHandler: (handler: SetToolsHandler) => void;
|
||||
setGetActiveToolsHandler: (handler: GetActiveToolsHandler) => void;
|
||||
setGetAllToolsHandler: (handler: GetAllToolsHandler) => void;
|
||||
setSetActiveToolsHandler: (handler: SetActiveToolsHandler) => void;
|
||||
setFlagValue: (name: string, value: boolean | string) => void;
|
||||
} {
|
||||
let sendMessageHandler: SendMessageHandler = () => {
|
||||
|
|
@ -225,8 +233,9 @@ function createHookAPI(
|
|||
let appendEntryHandler: AppendEntryHandler = () => {
|
||||
// Default no-op until mode sets the handler
|
||||
};
|
||||
let getToolsHandler: GetToolsHandler = () => [];
|
||||
let setToolsHandler: SetToolsHandler = () => {
|
||||
let getActiveToolsHandler: GetActiveToolsHandler = () => [];
|
||||
let getAllToolsHandler: GetAllToolsHandler = () => [];
|
||||
let setActiveToolsHandler: SetActiveToolsHandler = () => {
|
||||
// Default no-op until mode sets the handler
|
||||
};
|
||||
const messageRenderers = new Map<string, HookMessageRenderer>();
|
||||
|
|
@ -261,11 +270,14 @@ function createHookAPI(
|
|||
exec(command: string, args: string[], options?: ExecOptions) {
|
||||
return execCommand(command, args, options?.cwd ?? cwd, options);
|
||||
},
|
||||
getTools(): string[] {
|
||||
return getToolsHandler();
|
||||
getActiveTools(): string[] {
|
||||
return getActiveToolsHandler();
|
||||
},
|
||||
setTools(toolNames: string[]): void {
|
||||
setToolsHandler(toolNames);
|
||||
getAllTools(): string[] {
|
||||
return getAllToolsHandler();
|
||||
},
|
||||
setActiveTools(toolNames: string[]): void {
|
||||
setActiveToolsHandler(toolNames);
|
||||
},
|
||||
registerFlag(
|
||||
name: string,
|
||||
|
|
@ -304,11 +316,14 @@ function createHookAPI(
|
|||
setAppendEntryHandler: (handler: AppendEntryHandler) => {
|
||||
appendEntryHandler = handler;
|
||||
},
|
||||
setGetToolsHandler: (handler: GetToolsHandler) => {
|
||||
getToolsHandler = handler;
|
||||
setGetActiveToolsHandler: (handler: GetActiveToolsHandler) => {
|
||||
getActiveToolsHandler = handler;
|
||||
},
|
||||
setSetToolsHandler: (handler: SetToolsHandler) => {
|
||||
setToolsHandler = handler;
|
||||
setGetAllToolsHandler: (handler: GetAllToolsHandler) => {
|
||||
getAllToolsHandler = handler;
|
||||
},
|
||||
setSetActiveToolsHandler: (handler: SetActiveToolsHandler) => {
|
||||
setActiveToolsHandler = handler;
|
||||
},
|
||||
setFlagValue: (name: string, value: boolean | string) => {
|
||||
flagValues.set(name, value);
|
||||
|
|
@ -349,8 +364,9 @@ async function loadHook(hookPath: string, cwd: string): Promise<{ hook: LoadedHo
|
|||
shortcuts,
|
||||
setSendMessageHandler,
|
||||
setAppendEntryHandler,
|
||||
setGetToolsHandler,
|
||||
setSetToolsHandler,
|
||||
setGetActiveToolsHandler,
|
||||
setGetAllToolsHandler,
|
||||
setSetActiveToolsHandler,
|
||||
setFlagValue,
|
||||
} = createHookAPI(handlers, cwd, hookPath);
|
||||
|
||||
|
|
@ -369,8 +385,9 @@ async function loadHook(hookPath: string, cwd: string): Promise<{ hook: LoadedHo
|
|||
shortcuts,
|
||||
setSendMessageHandler,
|
||||
setAppendEntryHandler,
|
||||
setGetToolsHandler,
|
||||
setSetToolsHandler,
|
||||
setGetActiveToolsHandler,
|
||||
setGetAllToolsHandler,
|
||||
setSetActiveToolsHandler,
|
||||
setFlagValue,
|
||||
},
|
||||
error: null,
|
||||
|
|
|
|||
|
|
@ -99,10 +99,12 @@ export class HookRunner {
|
|||
sendMessageHandler: SendMessageHandler;
|
||||
/** Handler for hooks to append entries */
|
||||
appendEntryHandler: AppendEntryHandler;
|
||||
/** Handler for getting current tools */
|
||||
getToolsHandler: () => string[];
|
||||
/** Handler for setting tools */
|
||||
setToolsHandler: (toolNames: string[]) => void;
|
||||
/** Handler for getting current active tools */
|
||||
getActiveToolsHandler: () => string[];
|
||||
/** Handler for getting all configured tools */
|
||||
getAllToolsHandler: () => string[];
|
||||
/** Handler for setting active tools */
|
||||
setActiveToolsHandler: (toolNames: string[]) => void;
|
||||
/** Handler for creating new sessions (for HookCommandContext) */
|
||||
newSessionHandler?: NewSessionHandler;
|
||||
/** Handler for branching sessions (for HookCommandContext) */
|
||||
|
|
@ -137,12 +139,13 @@ export class HookRunner {
|
|||
if (options.navigateTreeHandler) {
|
||||
this.navigateTreeHandler = options.navigateTreeHandler;
|
||||
}
|
||||
// Set per-hook handlers for pi.sendMessage(), pi.appendEntry(), pi.getTools(), pi.setTools()
|
||||
// Set per-hook handlers for pi.sendMessage(), pi.appendEntry(), pi.getActiveTools(), pi.getAllTools(), pi.setActiveTools()
|
||||
for (const hook of this.hooks) {
|
||||
hook.setSendMessageHandler(options.sendMessageHandler);
|
||||
hook.setAppendEntryHandler(options.appendEntryHandler);
|
||||
hook.setGetToolsHandler(options.getToolsHandler);
|
||||
hook.setSetToolsHandler(options.setToolsHandler);
|
||||
hook.setGetActiveToolsHandler(options.getActiveToolsHandler);
|
||||
hook.setGetAllToolsHandler(options.getAllToolsHandler);
|
||||
hook.setSetActiveToolsHandler(options.setActiveToolsHandler);
|
||||
}
|
||||
this.uiContext = options.uiContext ?? noOpUIContext;
|
||||
this.hasUI = options.hasUI ?? false;
|
||||
|
|
@ -193,14 +196,52 @@ export class HookRunner {
|
|||
}
|
||||
}
|
||||
|
||||
// Built-in shortcuts that hooks should not override
|
||||
private static readonly RESERVED_SHORTCUTS = new Set([
|
||||
"ctrl+c",
|
||||
"ctrl+d",
|
||||
"ctrl+z",
|
||||
"ctrl+k",
|
||||
"ctrl+p",
|
||||
"ctrl+l",
|
||||
"ctrl+o",
|
||||
"ctrl+t",
|
||||
"ctrl+g",
|
||||
"shift+tab",
|
||||
"shift+ctrl+p",
|
||||
"alt+enter",
|
||||
"escape",
|
||||
"enter",
|
||||
]);
|
||||
|
||||
/**
|
||||
* Get all keyboard shortcuts registered by hooks.
|
||||
* When multiple hooks register the same shortcut, the last one wins.
|
||||
* Conflicts with built-in shortcuts are skipped with a warning.
|
||||
* Conflicts between hooks are logged as warnings.
|
||||
*/
|
||||
getShortcuts(): Map<string, import("./loader.js").HookShortcut> {
|
||||
const allShortcuts = new Map<string, import("./loader.js").HookShortcut>();
|
||||
for (const hook of this.hooks) {
|
||||
for (const [key, shortcut] of hook.shortcuts) {
|
||||
allShortcuts.set(key, shortcut);
|
||||
const normalizedKey = key.toLowerCase();
|
||||
|
||||
// Check for built-in shortcut conflicts
|
||||
if (HookRunner.RESERVED_SHORTCUTS.has(normalizedKey)) {
|
||||
console.warn(
|
||||
`Hook shortcut '${key}' from ${shortcut.hookPath} conflicts with built-in shortcut. Skipping.`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const existing = allShortcuts.get(normalizedKey);
|
||||
if (existing) {
|
||||
// Log conflict between hooks - last one wins
|
||||
console.warn(
|
||||
`Hook shortcut conflict: '${key}' registered by both ${existing.hookPath} and ${shortcut.hookPath}. Using ${shortcut.hookPath}.`,
|
||||
);
|
||||
}
|
||||
allShortcuts.set(normalizedKey, shortcut);
|
||||
}
|
||||
}
|
||||
return allShortcuts;
|
||||
|
|
|
|||
|
|
@ -392,16 +392,6 @@ export interface AgentEndEvent {
|
|||
messages: AgentMessage[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Event data for text_delta event.
|
||||
* Fired when new text is streamed from the assistant.
|
||||
*/
|
||||
export interface TextDeltaEvent {
|
||||
type: "text_delta";
|
||||
/** The new text chunk */
|
||||
text: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event data for turn_start event.
|
||||
*/
|
||||
|
|
@ -545,7 +535,6 @@ export type HookEvent =
|
|||
| BeforeAgentStartEvent
|
||||
| AgentStartEvent
|
||||
| AgentEndEvent
|
||||
| TextDeltaEvent
|
||||
| TurnStartEvent
|
||||
| TurnEndEvent
|
||||
| ToolCallEvent
|
||||
|
|
@ -712,7 +701,6 @@ export interface HookAPI {
|
|||
on(event: "turn_end", handler: HookHandler<TurnEndEvent>): void;
|
||||
on(event: "tool_call", handler: HookHandler<ToolCallEvent, ToolCallEventResult>): void;
|
||||
on(event: "tool_result", handler: HookHandler<ToolResultEvent, ToolResultEventResult>): void;
|
||||
on(event: "text_delta", handler: HookHandler<TextDeltaEvent>): void;
|
||||
|
||||
/**
|
||||
* Send a custom message to the session. Creates a CustomMessageEntry that
|
||||
|
|
@ -788,23 +776,30 @@ export interface HookAPI {
|
|||
* Get the list of currently active tool names.
|
||||
* @returns Array of tool names (e.g., ["read", "bash", "edit", "write"])
|
||||
*/
|
||||
getTools(): string[];
|
||||
getActiveTools(): string[];
|
||||
|
||||
/**
|
||||
* Get all configured tools (built-in via --tools or default, plus custom tools).
|
||||
* @returns Array of all tool names
|
||||
*/
|
||||
getAllTools(): string[];
|
||||
|
||||
/**
|
||||
* Set the active tools by name.
|
||||
* Only built-in tools can be enabled/disabled. Custom tools are always active.
|
||||
* Changes take effect on the next agent turn.
|
||||
* Note: This will invalidate prompt caching for the next request.
|
||||
*
|
||||
* @param toolNames - Array of tool names to enable (e.g., ["read", "bash", "grep", "find", "ls"])
|
||||
*
|
||||
* @example
|
||||
* // Switch to read-only mode (plan mode)
|
||||
* pi.setTools(["read", "bash", "grep", "find", "ls"]);
|
||||
* pi.setActiveTools(["read", "bash", "grep", "find", "ls"]);
|
||||
*
|
||||
* // Restore full access
|
||||
* pi.setTools(["read", "bash", "edit", "write"]);
|
||||
* pi.setActiveTools(["read", "bash", "edit", "write"]);
|
||||
*/
|
||||
setTools(toolNames: string[]): void;
|
||||
setActiveTools(toolNames: string[]): void;
|
||||
|
||||
/**
|
||||
* Register a CLI flag for this hook.
|
||||
|
|
|
|||
|
|
@ -355,8 +355,9 @@ function createLoadedHooksFromDefinitions(definitions: Array<{ path?: string; fa
|
|||
options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" },
|
||||
) => void = () => {};
|
||||
let appendEntryHandler: (customType: string, data?: any) => void = () => {};
|
||||
let getToolsHandler: () => string[] = () => [];
|
||||
let setToolsHandler: (toolNames: string[]) => void = () => {};
|
||||
let getActiveToolsHandler: () => string[] = () => [];
|
||||
let getAllToolsHandler: () => string[] = () => [];
|
||||
let setActiveToolsHandler: (toolNames: string[]) => void = () => {};
|
||||
let newSessionHandler: (options?: any) => Promise<{ cancelled: boolean }> = async () => ({ cancelled: false });
|
||||
let branchHandler: (entryId: string) => Promise<{ cancelled: boolean }> = async () => ({ cancelled: false });
|
||||
let navigateTreeHandler: (targetId: string, options?: any) => Promise<{ cancelled: boolean }> = async () => ({
|
||||
|
|
@ -394,8 +395,9 @@ function createLoadedHooksFromDefinitions(definitions: Array<{ path?: string; fa
|
|||
newSession: (options?: any) => newSessionHandler(options),
|
||||
branch: (entryId: string) => branchHandler(entryId),
|
||||
navigateTree: (targetId: string, options?: any) => navigateTreeHandler(targetId, options),
|
||||
getTools: () => getToolsHandler(),
|
||||
setTools: (toolNames: string[]) => setToolsHandler(toolNames),
|
||||
getActiveTools: () => getActiveToolsHandler(),
|
||||
getAllTools: () => getAllToolsHandler(),
|
||||
setActiveTools: (toolNames: string[]) => setActiveToolsHandler(toolNames),
|
||||
};
|
||||
|
||||
def.factory(api as any);
|
||||
|
|
@ -426,11 +428,14 @@ function createLoadedHooksFromDefinitions(definitions: Array<{ path?: string; fa
|
|||
setNavigateTreeHandler: (handler: (targetId: string, options?: any) => Promise<{ cancelled: boolean }>) => {
|
||||
navigateTreeHandler = handler;
|
||||
},
|
||||
setGetToolsHandler: (handler: () => string[]) => {
|
||||
getToolsHandler = handler;
|
||||
setGetActiveToolsHandler: (handler: () => string[]) => {
|
||||
getActiveToolsHandler = handler;
|
||||
},
|
||||
setSetToolsHandler: (handler: (toolNames: string[]) => void) => {
|
||||
setToolsHandler = handler;
|
||||
setGetAllToolsHandler: (handler: () => string[]) => {
|
||||
getAllToolsHandler = handler;
|
||||
},
|
||||
setSetActiveToolsHandler: (handler: (toolNames: string[]) => void) => {
|
||||
setActiveToolsHandler = handler;
|
||||
},
|
||||
setFlagValue: (name: string, value: boolean | string) => {
|
||||
flagValues.set(name, value);
|
||||
|
|
|
|||
|
|
@ -447,8 +447,9 @@ export class InteractiveMode {
|
|||
appendEntryHandler: (customType, data) => {
|
||||
this.sessionManager.appendCustomEntry(customType, data);
|
||||
},
|
||||
getToolsHandler: () => this.session.getActiveToolNames(),
|
||||
setToolsHandler: (toolNames) => this.session.setActiveToolsByName(toolNames),
|
||||
getActiveToolsHandler: () => this.session.getActiveToolNames(),
|
||||
getAllToolsHandler: () => this.session.getAllToolNames(),
|
||||
setActiveToolsHandler: (toolNames) => this.session.setActiveToolsByName(toolNames),
|
||||
newSessionHandler: async (options) => {
|
||||
// Stop any loading animation
|
||||
if (this.loadingAnimation) {
|
||||
|
|
|
|||
|
|
@ -40,8 +40,9 @@ export async function runPrintMode(
|
|||
appendEntryHandler: (customType, data) => {
|
||||
session.sessionManager.appendCustomEntry(customType, data);
|
||||
},
|
||||
getToolsHandler: () => session.getActiveToolNames(),
|
||||
setToolsHandler: (toolNames) => session.setActiveToolsByName(toolNames),
|
||||
getActiveToolsHandler: () => session.getActiveToolNames(),
|
||||
getAllToolsHandler: () => session.getAllToolNames(),
|
||||
setActiveToolsHandler: (toolNames: string[]) => session.setActiveToolsByName(toolNames),
|
||||
});
|
||||
hookRunner.onError((err) => {
|
||||
console.error(`Hook error (${err.hookPath}): ${err.error}`);
|
||||
|
|
|
|||
|
|
@ -200,8 +200,9 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|||
appendEntryHandler: (customType, data) => {
|
||||
session.sessionManager.appendCustomEntry(customType, data);
|
||||
},
|
||||
getToolsHandler: () => session.getActiveToolNames(),
|
||||
setToolsHandler: (toolNames) => session.setActiveToolsByName(toolNames),
|
||||
getActiveToolsHandler: () => session.getActiveToolNames(),
|
||||
getAllToolsHandler: () => session.getAllToolNames(),
|
||||
setActiveToolsHandler: (toolNames: string[]) => session.setActiveToolsByName(toolNames),
|
||||
uiContext: createHookUIContext(),
|
||||
hasUI: false,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -83,8 +83,9 @@ describe.skipIf(!API_KEY)("Compaction hooks", () => {
|
|||
shortcuts: new Map(),
|
||||
setSendMessageHandler: () => {},
|
||||
setAppendEntryHandler: () => {},
|
||||
setGetToolsHandler: () => {},
|
||||
setSetToolsHandler: () => {},
|
||||
setGetActiveToolsHandler: () => {},
|
||||
setGetAllToolsHandler: () => {},
|
||||
setSetActiveToolsHandler: () => {},
|
||||
setFlagValue: () => {},
|
||||
};
|
||||
}
|
||||
|
|
@ -110,8 +111,9 @@ describe.skipIf(!API_KEY)("Compaction hooks", () => {
|
|||
getModel: () => session.model,
|
||||
sendMessageHandler: async () => {},
|
||||
appendEntryHandler: async () => {},
|
||||
getToolsHandler: () => [],
|
||||
setToolsHandler: () => {},
|
||||
getActiveToolsHandler: () => [],
|
||||
getAllToolsHandler: () => [],
|
||||
setActiveToolsHandler: () => {},
|
||||
uiContext: {
|
||||
select: async () => undefined,
|
||||
confirm: async () => false,
|
||||
|
|
@ -279,8 +281,9 @@ describe.skipIf(!API_KEY)("Compaction hooks", () => {
|
|||
shortcuts: new Map(),
|
||||
setSendMessageHandler: () => {},
|
||||
setAppendEntryHandler: () => {},
|
||||
setGetToolsHandler: () => {},
|
||||
setSetToolsHandler: () => {},
|
||||
setGetActiveToolsHandler: () => {},
|
||||
setGetAllToolsHandler: () => {},
|
||||
setSetActiveToolsHandler: () => {},
|
||||
setFlagValue: () => {},
|
||||
};
|
||||
|
||||
|
|
@ -332,8 +335,9 @@ describe.skipIf(!API_KEY)("Compaction hooks", () => {
|
|||
shortcuts: new Map(),
|
||||
setSendMessageHandler: () => {},
|
||||
setAppendEntryHandler: () => {},
|
||||
setGetToolsHandler: () => {},
|
||||
setSetToolsHandler: () => {},
|
||||
setGetActiveToolsHandler: () => {},
|
||||
setGetAllToolsHandler: () => {},
|
||||
setSetActiveToolsHandler: () => {},
|
||||
setFlagValue: () => {},
|
||||
};
|
||||
|
||||
|
|
@ -367,8 +371,9 @@ describe.skipIf(!API_KEY)("Compaction hooks", () => {
|
|||
shortcuts: new Map(),
|
||||
setSendMessageHandler: () => {},
|
||||
setAppendEntryHandler: () => {},
|
||||
setGetToolsHandler: () => {},
|
||||
setSetToolsHandler: () => {},
|
||||
setGetActiveToolsHandler: () => {},
|
||||
setGetAllToolsHandler: () => {},
|
||||
setSetActiveToolsHandler: () => {},
|
||||
setFlagValue: () => {},
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue