diff --git a/packages/coding-agent/src/core/extensions/runner.ts b/packages/coding-agent/src/core/extensions/runner.ts index 79ea591b..9f7931af 100644 --- a/packages/coding-agent/src/core/extensions/runner.ts +++ b/packages/coding-agent/src/core/extensions/runner.ts @@ -296,7 +296,11 @@ export class ExtensionRunner { this.shutdownHandler(); } - private createContext(): ExtensionContext { + /** + * Create an ExtensionContext for use in event handlers and tool execution. + * Context values are resolved at call time, so changes via initialize() are reflected. + */ + createContext(): ExtensionContext { return { ui: this.uiContext, hasUI: this.hasUI(), diff --git a/packages/coding-agent/src/core/extensions/wrapper.ts b/packages/coding-agent/src/core/extensions/wrapper.ts index cd98fa97..0626afaf 100644 --- a/packages/coding-agent/src/core/extensions/wrapper.ts +++ b/packages/coding-agent/src/core/extensions/wrapper.ts @@ -4,12 +4,13 @@ import type { AgentTool, AgentToolUpdateCallback } from "@mariozechner/pi-agent-core"; import type { ExtensionRunner } from "./runner.js"; -import type { ExtensionContext, RegisteredTool, ToolCallEventResult, ToolResultEventResult } from "./types.js"; +import type { RegisteredTool, ToolCallEventResult, ToolResultEventResult } from "./types.js"; /** * Wrap a RegisteredTool into an AgentTool. + * Uses the runner's createContext() for consistent context across tools and event handlers. */ -export function wrapRegisteredTool(registeredTool: RegisteredTool, getContext: () => ExtensionContext): AgentTool { +export function wrapRegisteredTool(registeredTool: RegisteredTool, runner: ExtensionRunner): AgentTool { const { definition } = registeredTool; return { name: definition.name, @@ -17,18 +18,16 @@ export function wrapRegisteredTool(registeredTool: RegisteredTool, getContext: ( description: definition.description, parameters: definition.parameters, execute: (toolCallId, params, signal, onUpdate) => - definition.execute(toolCallId, params, onUpdate, getContext(), signal), + definition.execute(toolCallId, params, onUpdate, runner.createContext(), signal), }; } /** * Wrap all registered tools into AgentTools. + * Uses the runner's createContext() for consistent context across tools and event handlers. */ -export function wrapRegisteredTools( - registeredTools: RegisteredTool[], - getContext: () => ExtensionContext, -): AgentTool[] { - return registeredTools.map((rt) => wrapRegisteredTool(rt, getContext)); +export function wrapRegisteredTools(registeredTools: RegisteredTool[], runner: ExtensionRunner): AgentTool[] { + return registeredTools.map((rt) => wrapRegisteredTool(rt, runner)); } /** diff --git a/packages/coding-agent/src/core/sdk.ts b/packages/coding-agent/src/core/sdk.ts index b8e6e75e..82d7a403 100644 --- a/packages/coding-agent/src/core/sdk.ts +++ b/packages/coding-agent/src/core/sdk.ts @@ -475,9 +475,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} } } - // Create extension runner if we have extensions + // Create extension runner if we have extensions or SDK custom tools + // The runner provides consistent context for tool execution (shutdown, abort, etc.) let extensionRunner: ExtensionRunner | undefined; - if (extensionsResult.extensions.length > 0) { + const hasExtensions = extensionsResult.extensions.length > 0; + const hasCustomTools = options.customTools && options.customTools.length > 0; + if (hasExtensions || hasCustomTools) { extensionRunner = new ExtensionRunner( extensionsResult.extensions, extensionsResult.runtime, @@ -487,50 +490,18 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} ); } - // Wrap extension-registered tools and SDK-provided custom tools with context getter - // (agent/session assigned below, accessed at execute time) + // Wrap extension-registered tools and SDK-provided custom tools + // Tools use runner.createContext() for consistent context with event handlers let agent: Agent; - let session: AgentSession; const registeredTools = extensionRunner?.getAllRegisteredTools() ?? []; // Combine extension-registered tools with SDK-provided custom tools const allCustomTools = [ ...registeredTools, ...(options.customTools?.map((def) => ({ definition: def, extensionPath: "" })) ?? []), ]; - const wrappedExtensionTools = wrapRegisteredTools(allCustomTools, () => ({ - ui: extensionRunner?.getUIContext() ?? { - select: async () => undefined, - confirm: async () => false, - input: async () => undefined, - notify: () => {}, - setStatus: () => {}, - setWidget: () => {}, - setFooter: () => {}, - setHeader: () => {}, - setTitle: () => {}, - custom: async () => undefined as never, - setEditorText: () => {}, - getEditorText: () => "", - editor: async () => undefined, - setEditorComponent: () => {}, - get theme() { - return {} as any; - }, - }, - hasUI: extensionRunner?.hasUI() ?? false, - cwd, - sessionManager, - modelRegistry, - model: agent.state.model, - isIdle: () => !session.isStreaming, - hasPendingMessages: () => session.pendingMessageCount > 0, - abort: () => { - session.abort(); - }, - shutdown: () => { - extensionRunner?.shutdown(); - }, - })); + + // Wrap tools using runner's context (ensures shutdown, abort, etc. work correctly) + const wrappedExtensionTools = extensionRunner ? wrapRegisteredTools(allCustomTools, extensionRunner) : []; // Create tool registry mapping name -> tool (for extension getTools/setTools) // Registry contains ALL built-in tools so extensions can enable any of them @@ -673,7 +644,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} sessionManager.appendThinkingLevelChange(thinkingLevel); } - session = new AgentSession({ + const session = new AgentSession({ agent, sessionManager, settingsManager,