WIP: Add hook API for dynamic tool control with plan-mode hook example

- Add pi.getTools() and pi.setTools(toolNames) to HookAPI
- Hooks can now enable/disable tools dynamically
- Changes take effect on next agent turn

New example hook: plan-mode.ts
- Claude Code-style read-only exploration mode
- /plan command toggles plan mode on/off
- Plan mode tools: read, bash, grep, find, ls
- Edit/write tools disabled in plan mode
- Injects context telling agent about restrictions
- After each response, prompts to execute/stay/refine
- State persists across sessions
This commit is contained in:
Helmut Januschka 2026-01-03 09:31:39 +01:00 committed by Mario Zechner
parent 5b95ccf830
commit 059292ead1
14 changed files with 304 additions and 8 deletions

View file

@ -13,7 +13,14 @@
* Modes use this class and add their own I/O layer on top.
*/
import type { Agent, AgentEvent, AgentMessage, AgentState, ThinkingLevel } from "@mariozechner/pi-agent-core";
import type {
Agent,
AgentEvent,
AgentMessage,
AgentState,
AgentTool,
ThinkingLevel,
} from "@mariozechner/pi-agent-core";
import type { AssistantMessage, ImageContent, Message, Model, TextContent } from "@mariozechner/pi-ai";
import { isContextOverflow, modelsAreEqual, supportsXhigh } from "@mariozechner/pi-ai";
import { getAuthPath } from "../config.js";
@ -75,6 +82,8 @@ export interface AgentSessionConfig {
skillsSettings?: Required<SkillsSettings>;
/** Model registry for API key resolution and model discovery */
modelRegistry: ModelRegistry;
/** Tool registry for hook getTools/setTools - maps name to tool */
toolRegistry?: Map<string, AgentTool>;
}
/** Options for AgentSession.prompt() */
@ -174,6 +183,9 @@ export class AgentSession {
// Model registry for API key resolution
private _modelRegistry: ModelRegistry;
// Tool registry for hook getTools/setTools
private _toolRegistry: Map<string, AgentTool>;
constructor(config: AgentSessionConfig) {
this.agent = config.agent;
this.sessionManager = config.sessionManager;
@ -184,6 +196,7 @@ export class AgentSession {
this._customTools = config.customTools ?? [];
this._skillsSettings = config.skillsSettings;
this._modelRegistry = config.modelRegistry;
this._toolRegistry = config.toolRegistry ?? new Map();
// Always subscribe to agent events for internal handling
// (session persistence, hooks, auto-compaction, retry logic)
@ -417,6 +430,30 @@ export class AgentSession {
return this.agent.state.isStreaming;
}
/**
* Get the names of currently active tools.
* Returns the names of tools currently set on the agent.
*/
getActiveToolNames(): string[] {
return this.agent.state.tools.map((t) => t.name);
}
/**
* Set active tools by name.
* Only tools in the registry can be enabled. Unknown tool names are ignored.
* Changes take effect on the next agent turn.
*/
setActiveToolsByName(toolNames: string[]): void {
const tools: AgentTool[] = [];
for (const name of toolNames) {
const tool = this._toolRegistry.get(name);
if (tool) {
tools.push(tool);
}
}
this.agent.setTools(tools);
}
/** Whether auto-compaction is currently running */
get isCompacting(): boolean {
return this._autoCompactionAbortController !== undefined || this._compactionAbortController !== undefined;