import type { BeforeAgentStartEventResult, ExtensionContext, } from "@earendil-works/pi-coding-agent"; import { createActiveToolsCacheKey, createBeforeAgentStartPromptStateKey, } from "../before-agent-start-cache"; import type { PermissionSession } from "../permission-session"; import { resolveSkillPromptEntries } from "../skill-prompt-sanitizer"; import { sanitizeAvailableToolsSection } from "../system-prompt-sanitizer"; import { getToolNameFromValue, type ToolRegistry } from "../tool-registry"; import type { PermissionState } from "../types"; /** Minimal subset of BeforeAgentStartEvent used by this handler. */ interface BeforeAgentStartPayload { systemPrompt: string; } /** * Pure helper: returns true when the tool should be exposed to the agent. * Checks the tool-level permission (not command-level) so that a blanket * `bash: deny` hides the tool entirely before any invocation is attempted. */ export function shouldExposeTool( toolName: string, agentName: string | null, getToolPermission: (toolName: string, agentName?: string) => PermissionState, ): boolean { const toolPermission = getToolPermission(toolName, agentName ?? undefined); return toolPermission !== "deny"; } /** * Handles the `before_agent_start` event: tool filtering + prompt sanitization. * * Constructor deps: * - `session` — encapsulates all mutable session state * - `toolRegistry` — Pi tool API subset (getAll + setActive) */ export class AgentPrepHandler { constructor( private readonly session: PermissionSession, private readonly toolRegistry: ToolRegistry, ) {} async handle( event: BeforeAgentStartPayload, ctx: ExtensionContext, ): Promise { const { session } = this; session.activate(ctx); session.refreshConfig(ctx); const agentName = session.resolveAgentName(ctx, event.systemPrompt); const allTools = this.toolRegistry.getAll(); const allowedTools: string[] = []; for (const tool of allTools) { const toolName = getToolNameFromValue(tool); if (!toolName) { continue; } if ( shouldExposeTool(toolName, agentName, (t, a) => session.getToolPermission(t, a), ) ) { allowedTools.push(toolName); } } const activeToolsCacheKey = createActiveToolsCacheKey(allowedTools); if (session.shouldUpdateActiveTools(activeToolsCacheKey)) { this.toolRegistry.setActive(allowedTools); session.commitActiveToolsCacheKey(activeToolsCacheKey); } const promptStateCacheKey = createBeforeAgentStartPromptStateKey({ agentName, cwd: ctx.cwd, permissionStamp: session.getPolicyCacheStamp(agentName ?? undefined), systemPrompt: event.systemPrompt, allowedToolNames: allowedTools, }); if (!session.shouldUpdatePromptState(promptStateCacheKey)) { return {}; } session.commitPromptStateCacheKey(promptStateCacheKey); const toolPromptResult = sanitizeAvailableToolsSection( event.systemPrompt, allowedTools, ); const skillPromptResult = resolveSkillPromptEntries( toolPromptResult.prompt, session, agentName, ctx.cwd, ); session.setActiveSkillEntries(skillPromptResult.entries); if (skillPromptResult.prompt !== event.systemPrompt) { return { systemPrompt: skillPromptResult.prompt }; } return {}; } }