@dyyz1993/pi-coding-agent 0.69.26 → 0.70.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +15 -10
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +48 -1
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +3 -2
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +62 -20
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +21 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/large-input.d.ts +8 -0
- package/dist/core/large-input.d.ts.map +1 -0
- package/dist/core/large-input.js +37 -0
- package/dist/core/large-input.js.map +1 -0
- package/dist/core/messages.d.ts +10 -0
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/messages.js +20 -0
- package/dist/core/messages.js.map +1 -1
- package/dist/core/session-manager.d.ts +9 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +29 -2
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/storage.d.ts +34 -0
- package/dist/core/storage.d.ts.map +1 -1
- package/dist/core/storage.js +63 -1
- package/dist/core/storage.js.map +1 -1
- package/dist/core/tools/bash.d.ts +2 -0
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +31 -5
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/index.d.ts +1 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +1 -1
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/truncate.d.ts +1 -0
- package/dist/core/tools/truncate.d.ts.map +1 -1
- package/dist/core/tools/truncate.js +1 -0
- package/dist/core/tools/truncate.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +13 -2
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/docs/extensions.md +100 -0
- package/package.json +7 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../../src/core/extensions/runner.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Extension runner - executes extensions and manages their lifecycle.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport type { AgentMessage } from \"@dyyz1993/pi-agent-core\";\nimport type { ImageContent, Model } from \"@dyyz1993/pi-ai\";\nimport type { KeyId } from \"@dyyz1993/pi-tui\";\nimport { type Theme, theme } from \"../../modes/interactive/theme/theme.js\";\nimport type { ResourceDiagnostic } from \"../diagnostics.js\";\nimport type { KeybindingsConfig } from \"../keybindings.js\";\nimport type { ModelRegistry } from \"../model-registry.js\";\nimport type { SessionManager } from \"../session-manager.js\";\nimport type { BuildSystemPromptOptions } from \"../system-prompt.js\";\nimport type {\n\tBeforeAgentStartEvent,\n\tBeforeAgentStartEventResult,\n\tBeforeProviderRequestEvent,\n\tCompactOptions,\n\tContextEvent,\n\tContextEventResult,\n\tContextUsage,\n\tExtension,\n\tExtensionActions,\n\tExtensionCommandContext,\n\tExtensionCommandContextActions,\n\tExtensionContext,\n\tExtensionContextActions,\n\tExtensionError,\n\tExtensionEvent,\n\tExtensionFlag,\n\tExtensionRuntime,\n\tExtensionShortcut,\n\tExtensionUIContext,\n\tInputEvent,\n\tInputEventResult,\n\tInputSource,\n\tMessageRenderer,\n\tProviderConfig,\n\tRegisteredCommand,\n\tRegisteredTool,\n\tReplacedSessionContext,\n\tResolvedCommand,\n\tResourcesDiscoverEvent,\n\tResourcesDiscoverResult,\n\tSessionBeforeCompactResult,\n\tSessionBeforeForkResult,\n\tSessionBeforeSwitchResult,\n\tSessionBeforeTreeResult,\n\tSessionShutdownEvent,\n\tToolCallEvent,\n\tToolCallEventResult,\n\tToolResultEvent,\n\tToolResultEventResult,\n\tUIEvent,\n\tUIEventResult,\n\tUserBashEvent,\n\tUserBashEventResult,\n} from \"./types.js\";\n\n// Extension shortcuts compete with canonical keybinding ids from keybindings.json.\n// Only editor-global shortcuts are reserved here. Picker-specific bindings are not.\nconst RESERVED_KEYBINDINGS_FOR_EXTENSION_CONFLICTS = [\n\t\"app.interrupt\",\n\t\"app.clear\",\n\t\"app.exit\",\n\t\"app.suspend\",\n\t\"app.thinking.cycle\",\n\t\"app.model.cycleForward\",\n\t\"app.model.cycleBackward\",\n\t\"app.model.select\",\n\t\"app.tools.expand\",\n\t\"app.thinking.toggle\",\n\t\"app.editor.external\",\n\t\"app.message.followUp\",\n\t\"tui.input.submit\",\n\t\"tui.select.confirm\",\n\t\"tui.select.cancel\",\n\t\"tui.input.copy\",\n\t\"tui.editor.deleteToLineEnd\",\n] as const;\n\ntype BuiltInKeyBindings = Partial<Record<KeyId, { keybinding: string; restrictOverride: boolean }>>;\n\nconst buildBuiltinKeybindings = (resolvedKeybindings: KeybindingsConfig): BuiltInKeyBindings => {\n\tconst builtinKeybindings = {} as BuiltInKeyBindings;\n\tfor (const [keybinding, keys] of Object.entries(resolvedKeybindings)) {\n\t\tif (keys === undefined) continue;\n\t\tconst keyList = Array.isArray(keys) ? keys : [keys];\n\t\tconst restrictOverride = (RESERVED_KEYBINDINGS_FOR_EXTENSION_CONFLICTS as readonly string[]).includes(keybinding);\n\t\tfor (const key of keyList) {\n\t\t\tconst normalizedKey = key.toLowerCase() as KeyId;\n\t\t\t// If multiple actions bind the same key, the reserved action wins so extensions\n\t\t\t// remain blocked by reserved shortcuts regardless of iteration order.\n\t\t\tconst existing = builtinKeybindings[normalizedKey];\n\t\t\tif (existing?.restrictOverride && !restrictOverride) continue;\n\t\t\tbuiltinKeybindings[normalizedKey] = {\n\t\t\t\tkeybinding,\n\t\t\t\trestrictOverride,\n\t\t\t};\n\t\t}\n\t}\n\treturn builtinKeybindings;\n};\n\n/** Combined result from all before_agent_start handlers */\ninterface BeforeAgentStartCombinedResult {\n\tmessages?: NonNullable<BeforeAgentStartEventResult[\"message\"]>[];\n\tsystemPrompt?: string;\n}\n\n/**\n * Events handled by the generic emit() method.\n * Events with dedicated emitXxx() methods are excluded for stronger type safety.\n */\ntype RunnerEmitEvent = Exclude<\n\tExtensionEvent,\n\t| ToolCallEvent\n\t| ToolResultEvent\n\t| UserBashEvent\n\t| ContextEvent\n\t| BeforeProviderRequestEvent\n\t| BeforeAgentStartEvent\n\t| ResourcesDiscoverEvent\n\t| InputEvent\n>;\n\ntype SessionBeforeEvent = Extract<\n\tRunnerEmitEvent,\n\t{ type: \"session_before_switch\" | \"session_before_fork\" | \"session_before_compact\" | \"session_before_tree\" }\n>;\n\ntype SessionBeforeEventResult =\n\t| SessionBeforeSwitchResult\n\t| SessionBeforeForkResult\n\t| SessionBeforeCompactResult\n\t| SessionBeforeTreeResult;\n\ntype RunnerEmitResult<TEvent extends RunnerEmitEvent> = TEvent extends { type: \"session_before_switch\" }\n\t? SessionBeforeSwitchResult | undefined\n\t: TEvent extends { type: \"session_before_fork\" }\n\t\t? SessionBeforeForkResult | undefined\n\t\t: TEvent extends { type: \"session_before_compact\" }\n\t\t\t? SessionBeforeCompactResult | undefined\n\t\t\t: TEvent extends { type: \"session_before_tree\" }\n\t\t\t\t? SessionBeforeTreeResult | undefined\n\t\t\t\t: undefined;\n\nexport type ExtensionErrorListener = (error: ExtensionError) => void;\n\nexport type NewSessionHandler = (options?: {\n\tparentSession?: string;\n\tsetup?: (sessionManager: SessionManager) => Promise<void>;\n\twithSession?: (ctx: ReplacedSessionContext) => Promise<void>;\n}) => Promise<{ cancelled: boolean }>;\n\nexport type ForkHandler = (\n\tentryId: string,\n\toptions?: { position?: \"before\" | \"at\"; withSession?: (ctx: ReplacedSessionContext) => Promise<void> },\n) => Promise<{ cancelled: boolean }>;\n\nexport type NavigateTreeHandler = (\n\ttargetId: string,\n\toptions?: { summarize?: boolean; customInstructions?: string; replaceInstructions?: boolean; label?: string },\n) => Promise<{ cancelled: boolean }>;\n\nexport type SwitchSessionHandler = (\n\tsessionPath: string,\n\toptions?: { withSession?: (ctx: ReplacedSessionContext) => Promise<void> },\n) => Promise<{ cancelled: boolean }>;\n\nexport type ReloadHandler = () => Promise<void>;\n\nexport type ShutdownHandler = () => void;\n\n/**\n * Helper function to emit session_shutdown event to extensions.\n * Returns true if the event was emitted, false if there were no handlers.\n */\nexport async function emitSessionShutdownEvent(\n\textensionRunner: ExtensionRunner,\n\tevent: SessionShutdownEvent,\n): Promise<boolean> {\n\tif (extensionRunner.hasHandlers(\"session_shutdown\")) {\n\t\tawait extensionRunner.emit(event);\n\t\treturn true;\n\t}\n\treturn false;\n}\n\nconst noOpUIContext: ExtensionUIContext = {\n\tselect: async () => undefined,\n\tconfirm: async () => false,\n\tinput: async () => undefined,\n\tnotify: () => {},\n\tonTerminalInput: () => () => {},\n\tsetStatus: () => {},\n\tsetWorkingMessage: () => {},\n\tsetWorkingIndicator: () => {},\n\tsetHiddenThinkingLabel: () => {},\n\tsetWidget: () => {},\n\tsetFooter: () => {},\n\tsetHeader: () => {},\n\tsetTitle: () => {},\n\tcustom: async () => undefined as never,\n\tpasteToEditor: () => {},\n\tsetEditorText: () => {},\n\tgetEditorText: () => \"\",\n\teditor: async () => undefined,\n\taddAutocompleteProvider: () => {},\n\tsetEditorComponent: () => {},\n\tget theme() {\n\t\treturn theme;\n\t},\n\tgetAllThemes: () => [],\n\tgetTheme: () => undefined,\n\tsetTheme: (_theme: string | Theme) => ({ success: false, error: \"UI not available\" }),\n\tgetToolsExpanded: () => false,\n\tsetToolsExpanded: () => {},\n};\n\nexport class ExtensionRunner {\n\tprivate extensions: Extension[];\n\tprivate runtime: ExtensionRuntime;\n\tprivate uiContext: ExtensionUIContext;\n\tprivate uiContextOriginal: ExtensionUIContext | undefined;\n\tprivate cwd: string;\n\tprivate sessionManager: SessionManager;\n\tprivate modelRegistry: ModelRegistry;\n\tprivate errorListeners: Set<ExtensionErrorListener> = new Set();\n\tprivate getModel: () => Model<any> | undefined = () => undefined;\n\tprivate isIdleFn: () => boolean = () => true;\n\tprivate getSignalFn: () => AbortSignal | undefined = () => undefined;\n\tprivate getSessionSignalFn: () => AbortSignal = () => AbortSignal.abort();\n\tprivate waitForIdleFn: () => Promise<void> = async () => {};\n\tprivate abortFn: () => void = () => {};\n\tprivate hasPendingMessagesFn: () => boolean = () => false;\n\tprivate getContextUsageFn: () => ContextUsage | undefined = () => undefined;\n\tprivate compactFn: (options?: CompactOptions) => void = () => {};\n\tprivate getSystemPromptFn: () => string = () => \"\";\n\tprivate newSessionHandler: NewSessionHandler = async () => ({ cancelled: false });\n\tprivate forkHandler: ForkHandler = async () => ({ cancelled: false });\n\tprivate navigateTreeHandler: NavigateTreeHandler = async () => ({ cancelled: false });\n\tprivate switchSessionHandler: SwitchSessionHandler = async () => ({ cancelled: false });\n\tprivate reloadHandler: ReloadHandler = async () => {};\n\tprivate shutdownHandler: ShutdownHandler = () => {};\n\tprivate shortcutDiagnostics: ResourceDiagnostic[] = [];\n\tprivate commandDiagnostics: ResourceDiagnostic[] = [];\n\tprivate staleMessage: string | undefined;\n\tprivate pendingUIResponses: Map<string, (result: UIEventResult) => void> = new Map();\n\n\tconstructor(\n\t\textensions: Extension[],\n\t\truntime: ExtensionRuntime,\n\t\tcwd: string,\n\t\tsessionManager: SessionManager,\n\t\tmodelRegistry: ModelRegistry,\n\t) {\n\t\tthis.extensions = extensions;\n\t\tthis.runtime = runtime;\n\t\tthis.uiContext = noOpUIContext;\n\t\tthis.uiContextOriginal = undefined;\n\t\tthis.cwd = cwd;\n\t\tthis.sessionManager = sessionManager;\n\t\tthis.modelRegistry = modelRegistry;\n\t}\n\n\tbindCore(\n\t\tactions: ExtensionActions,\n\t\tcontextActions: ExtensionContextActions,\n\t\tproviderActions?: {\n\t\t\tregisterProvider?: (name: string, config: ProviderConfig) => void;\n\t\t\tunregisterProvider?: (name: string) => void;\n\t\t},\n\t): void {\n\t\t// Copy actions into the shared runtime (all extension APIs reference this)\n\t\tthis.runtime.sendMessage = actions.sendMessage;\n\t\tthis.runtime.sendUserMessage = actions.sendUserMessage;\n\t\tthis.runtime.appendEntry = actions.appendEntry;\n\t\tthis.runtime.setSessionName = actions.setSessionName;\n\t\tthis.runtime.getSessionName = actions.getSessionName;\n\t\tthis.runtime.setLabel = actions.setLabel;\n\t\tthis.runtime.getActiveTools = actions.getActiveTools;\n\t\tthis.runtime.getAllTools = actions.getAllTools;\n\t\tthis.runtime.setActiveTools = actions.setActiveTools;\n\t\tthis.runtime.refreshTools = actions.refreshTools;\n\t\tthis.runtime.getCommands = actions.getCommands;\n\t\tthis.runtime.setModel = actions.setModel;\n\t\tthis.runtime.getThinkingLevel = actions.getThinkingLevel;\n\t\tthis.runtime.setThinkingLevel = actions.setThinkingLevel;\n\t\tthis.runtime.registerChannel = actions.registerChannel;\n\t\tthis.runtime.callLLM = actions.callLLM;\n\t\tthis.runtime.callLLMStructured = actions.callLLMStructured;\n\t\tthis.runtime.background = actions.background;\n\n\t\t// Context actions (required)\n\t\tthis.getModel = contextActions.getModel;\n\t\tthis.isIdleFn = contextActions.isIdle;\n\t\tthis.getSignalFn = contextActions.getSignal;\n\t\tthis.getSessionSignalFn = contextActions.getSessionSignal;\n\t\tthis.abortFn = contextActions.abort;\n\t\tthis.hasPendingMessagesFn = contextActions.hasPendingMessages;\n\t\tthis.shutdownHandler = contextActions.shutdown;\n\t\tthis.getContextUsageFn = contextActions.getContextUsage;\n\t\tthis.compactFn = contextActions.compact;\n\t\tthis.getSystemPromptFn = contextActions.getSystemPrompt;\n\n\t\t// Flush provider registrations queued during extension loading\n\t\tfor (const { name, config, extensionPath } of this.runtime.pendingProviderRegistrations) {\n\t\t\ttry {\n\t\t\t\tif (providerActions?.registerProvider) {\n\t\t\t\t\tproviderActions.registerProvider(name, config);\n\t\t\t\t} else {\n\t\t\t\t\tthis.modelRegistry.registerProvider(name, config);\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tthis.emitError({\n\t\t\t\t\textensionPath,\n\t\t\t\t\tevent: \"register_provider\",\n\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t\tstack: err instanceof Error ? err.stack : undefined,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tthis.runtime.pendingProviderRegistrations = [];\n\n\t\t// Set registerChannel on the runtime. If this is the throwing stub (non-RPC mode),\n\t\t// pending channel registrations remain queued until bindExtensions() provides the\n\t\t// real implementation via flushPendingChannels().\n\t\tthis.runtime.registerChannel = actions.registerChannel;\n\n\t\t// From this point on, provider registration/unregistration takes effect immediately\n\t\t// without requiring a /reload.\n\t\tthis.runtime.registerProvider = (name, config) => {\n\t\t\tif (providerActions?.registerProvider) {\n\t\t\t\tproviderActions.registerProvider(name, config);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.modelRegistry.registerProvider(name, config);\n\t\t};\n\t\tthis.runtime.unregisterProvider = (name) => {\n\t\t\tif (providerActions?.unregisterProvider) {\n\t\t\t\tproviderActions.unregisterProvider(name);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.modelRegistry.unregisterProvider(name);\n\t\t};\n\t}\n\n\tbindCommandContext(actions?: ExtensionCommandContextActions): void {\n\t\tif (actions) {\n\t\t\tthis.waitForIdleFn = actions.waitForIdle;\n\t\t\tthis.newSessionHandler = actions.newSession;\n\t\t\tthis.forkHandler = actions.fork;\n\t\t\tthis.navigateTreeHandler = actions.navigateTree;\n\t\t\tthis.switchSessionHandler = actions.switchSession;\n\t\t\tthis.reloadHandler = actions.reload;\n\t\t\treturn;\n\t\t}\n\n\t\tthis.waitForIdleFn = async () => {};\n\t\tthis.newSessionHandler = async () => ({ cancelled: false });\n\t\tthis.forkHandler = async () => ({ cancelled: false });\n\t\tthis.navigateTreeHandler = async () => ({ cancelled: false });\n\t\tthis.switchSessionHandler = async () => ({ cancelled: false });\n\t\tthis.reloadHandler = async () => {};\n\t}\n\n\t/**\n\t * Flush pending channel registrations with the real registerChannel implementation.\n\t * Called from bindCore() and again from bindExtensions() when the real registerChannel\n\t * becomes available (e.g. in RPC mode).\n\t */\n\tflushPendingChannels(registerChannel: (name: string) => import(\"./channel-types.js\").Channel): void {\n\t\tif (this.runtime.pendingChannelRegistrations.length === 0) return;\n\n\t\tfor (const pending of this.runtime.pendingChannelRegistrations) {\n\t\t\ttry {\n\t\t\t\tconst channel = registerChannel(pending.name);\n\t\t\t\tthis.runtime.resolvedChannels.set(pending.name, channel);\n\t\t\t\tpending.resolve(channel);\n\t\t\t} catch (err) {\n\t\t\t\tpending.reject(err instanceof Error ? err : new Error(String(err)));\n\t\t\t}\n\t\t}\n\t\tthis.runtime.pendingChannelRegistrations = [];\n\t\tthis.runtime.registerChannel = registerChannel;\n\t}\n\n\tupdateRegisterChannel(registerChannel: (name: string) => import(\"./channel-types.js\").Channel): void {\n\t\tthis.runtime.registerChannel = registerChannel;\n\t}\n\n\tsetUIContext(uiContext?: ExtensionUIContext): void {\n\t\tthis.uiContextOriginal = uiContext;\n\t\tthis.uiContext = this.wrapUIForInterception(uiContext ?? noOpUIContext);\n\t}\n\n\tgetUIContext(): ExtensionUIContext {\n\t\treturn this.uiContext;\n\t}\n\n\thasUI(): boolean {\n\t\treturn this.uiContextOriginal !== undefined && this.uiContextOriginal !== noOpUIContext;\n\t}\n\n\tgetExtensionPaths(): string[] {\n\t\treturn this.extensions.map((e) => e.path);\n\t}\n\n\t/** Get all registered tools from all extensions (first registration per name wins). */\n\tgetAllRegisteredTools(): RegisteredTool[] {\n\t\tconst toolsByName = new Map<string, RegisteredTool>();\n\t\tfor (const ext of this.extensions) {\n\t\t\tfor (const tool of ext.tools.values()) {\n\t\t\t\tif (!toolsByName.has(tool.definition.name)) {\n\t\t\t\t\ttoolsByName.set(tool.definition.name, tool);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn Array.from(toolsByName.values());\n\t}\n\n\t/** Get a tool definition by name. Returns undefined if not found. */\n\tgetToolDefinition(toolName: string): RegisteredTool[\"definition\"] | undefined {\n\t\tfor (const ext of this.extensions) {\n\t\t\tconst tool = ext.tools.get(toolName);\n\t\t\tif (tool) {\n\t\t\t\treturn tool.definition;\n\t\t\t}\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tgetFlags(): Map<string, ExtensionFlag> {\n\t\tconst allFlags = new Map<string, ExtensionFlag>();\n\t\tfor (const ext of this.extensions) {\n\t\t\tfor (const [name, flag] of ext.flags) {\n\t\t\t\tif (!allFlags.has(name)) {\n\t\t\t\t\tallFlags.set(name, flag);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn allFlags;\n\t}\n\n\tsetFlagValue(name: string, value: boolean | string): void {\n\t\tthis.runtime.flagValues.set(name, value);\n\t}\n\n\tgetFlagValues(): Map<string, boolean | string> {\n\t\treturn new Map(this.runtime.flagValues);\n\t}\n\n\tgetShortcuts(resolvedKeybindings: KeybindingsConfig): Map<KeyId, ExtensionShortcut> {\n\t\tthis.shortcutDiagnostics = [];\n\t\tconst builtinKeybindings = buildBuiltinKeybindings(resolvedKeybindings);\n\t\tconst extensionShortcuts = new Map<KeyId, ExtensionShortcut>();\n\n\t\tconst addDiagnostic = (message: string, extensionPath: string) => {\n\t\t\tthis.shortcutDiagnostics.push({ type: \"warning\", message, path: extensionPath });\n\t\t\tif (!this.hasUI()) {\n\t\t\t\tconsole.warn(message);\n\t\t\t}\n\t\t};\n\n\t\tfor (const ext of this.extensions) {\n\t\t\tfor (const [key, shortcut] of ext.shortcuts) {\n\t\t\t\tconst normalizedKey = key.toLowerCase() as KeyId;\n\n\t\t\t\tconst builtInKeybinding = builtinKeybindings[normalizedKey];\n\t\t\t\tif (builtInKeybinding?.restrictOverride === true) {\n\t\t\t\t\taddDiagnostic(\n\t\t\t\t\t\t`Extension shortcut '${key}' from ${shortcut.extensionPath} conflicts with built-in shortcut. Skipping.`,\n\t\t\t\t\t\tshortcut.extensionPath,\n\t\t\t\t\t);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (builtInKeybinding?.restrictOverride === false) {\n\t\t\t\t\taddDiagnostic(\n\t\t\t\t\t\t`Extension shortcut conflict: '${key}' is built-in shortcut for ${builtInKeybinding.keybinding} and ${shortcut.extensionPath}. Using ${shortcut.extensionPath}.`,\n\t\t\t\t\t\tshortcut.extensionPath,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tconst existingExtensionShortcut = extensionShortcuts.get(normalizedKey);\n\t\t\t\tif (existingExtensionShortcut) {\n\t\t\t\t\taddDiagnostic(\n\t\t\t\t\t\t`Extension shortcut conflict: '${key}' registered by both ${existingExtensionShortcut.extensionPath} and ${shortcut.extensionPath}. Using ${shortcut.extensionPath}.`,\n\t\t\t\t\t\tshortcut.extensionPath,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\textensionShortcuts.set(normalizedKey, shortcut);\n\t\t\t}\n\t\t}\n\t\treturn extensionShortcuts;\n\t}\n\n\tgetShortcutDiagnostics(): ResourceDiagnostic[] {\n\t\treturn this.shortcutDiagnostics;\n\t}\n\n\tinvalidate(message = \"This extension instance is stale after session replacement or reload.\"): void {\n\t\tif (!this.staleMessage) {\n\t\t\tthis.staleMessage = message;\n\t\t\tthis.runtime.invalidate(message);\n\t\t}\n\t}\n\n\tprivate assertActive(): void {\n\t\tif (this.staleMessage) {\n\t\t\tthrow new Error(this.staleMessage);\n\t\t}\n\t}\n\n\tonError(listener: ExtensionErrorListener): () => void {\n\t\tthis.errorListeners.add(listener);\n\t\treturn () => this.errorListeners.delete(listener);\n\t}\n\n\temitError(error: ExtensionError): void {\n\t\tfor (const listener of this.errorListeners) {\n\t\t\tlistener(error);\n\t\t}\n\t}\n\n\thasHandlers(eventType: string): boolean {\n\t\tfor (const ext of this.extensions) {\n\t\t\tconst handlers = ext.handlers.get(eventType);\n\t\t\tif (handlers && handlers.length > 0) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\tgetMessageRenderer(customType: string): MessageRenderer | undefined {\n\t\tfor (const ext of this.extensions) {\n\t\t\tconst renderer = ext.messageRenderers.get(customType);\n\t\t\tif (renderer) {\n\t\t\t\treturn renderer;\n\t\t\t}\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tprivate resolveRegisteredCommands(): ResolvedCommand[] {\n\t\tconst commands: RegisteredCommand[] = [];\n\t\tconst counts = new Map<string, number>();\n\n\t\tfor (const ext of this.extensions) {\n\t\t\tfor (const command of ext.commands.values()) {\n\t\t\t\tcommands.push(command);\n\t\t\t\tcounts.set(command.name, (counts.get(command.name) ?? 0) + 1);\n\t\t\t}\n\t\t}\n\n\t\tconst seen = new Map<string, number>();\n\t\tconst takenInvocationNames = new Set<string>();\n\n\t\treturn commands.map((command) => {\n\t\t\tconst occurrence = (seen.get(command.name) ?? 0) + 1;\n\t\t\tseen.set(command.name, occurrence);\n\n\t\t\tlet invocationName = (counts.get(command.name) ?? 0) > 1 ? `${command.name}:${occurrence}` : command.name;\n\n\t\t\tif (takenInvocationNames.has(invocationName)) {\n\t\t\t\tlet suffix = occurrence;\n\t\t\t\tdo {\n\t\t\t\t\tsuffix++;\n\t\t\t\t\tinvocationName = `${command.name}:${suffix}`;\n\t\t\t\t} while (takenInvocationNames.has(invocationName));\n\t\t\t}\n\n\t\t\ttakenInvocationNames.add(invocationName);\n\t\t\treturn {\n\t\t\t\t...command,\n\t\t\t\tinvocationName,\n\t\t\t};\n\t\t});\n\t}\n\n\tgetRegisteredCommands(): ResolvedCommand[] {\n\t\tthis.commandDiagnostics = [];\n\t\treturn this.resolveRegisteredCommands();\n\t}\n\n\tgetCommandDiagnostics(): ResourceDiagnostic[] {\n\t\treturn this.commandDiagnostics;\n\t}\n\n\tgetCommand(name: string): ResolvedCommand | undefined {\n\t\treturn this.resolveRegisteredCommands().find((command) => command.invocationName === name);\n\t}\n\n\t/**\n\t * Request a graceful shutdown. Called by extension tools and event handlers.\n\t * The actual shutdown behavior is provided by the mode via bindExtensions().\n\t */\n\tshutdown(): void {\n\t\tthis.shutdownHandler();\n\t}\n\n\t/**\n\t * Create an ExtensionContext for use in event handlers and tool execution.\n\t * Context values are resolved at call time, so changes via bindCore/bindUI are reflected.\n\t */\n\tcreateContext(): ExtensionContext {\n\t\tconst runner = this;\n\t\tconst getModel = this.getModel;\n\t\treturn {\n\t\t\tget ui() {\n\t\t\t\trunner.assertActive();\n\t\t\t\treturn runner.uiContext;\n\t\t\t},\n\t\t\tget hasUI() {\n\t\t\t\trunner.assertActive();\n\t\t\t\treturn runner.hasUI();\n\t\t\t},\n\t\t\tget cwd() {\n\t\t\t\trunner.assertActive();\n\t\t\t\treturn runner.cwd;\n\t\t\t},\n\t\t\tget sessionManager() {\n\t\t\t\trunner.assertActive();\n\t\t\t\treturn runner.sessionManager;\n\t\t\t},\n\t\t\tget modelRegistry() {\n\t\t\t\trunner.assertActive();\n\t\t\t\treturn runner.modelRegistry;\n\t\t\t},\n\t\t\tget model() {\n\t\t\t\trunner.assertActive();\n\t\t\t\treturn getModel();\n\t\t\t},\n\t\t\tisIdle: () => {\n\t\t\t\trunner.assertActive();\n\t\t\t\treturn runner.isIdleFn();\n\t\t\t},\n\t\t\tget signal() {\n\t\t\t\trunner.assertActive();\n\t\t\t\treturn runner.getSignalFn();\n\t\t\t},\n\t\t\tget sessionSignal() {\n\t\t\t\trunner.assertActive();\n\t\t\t\treturn runner.getSessionSignalFn();\n\t\t\t},\n\t\t\tabort: () => {\n\t\t\t\trunner.assertActive();\n\t\t\t\trunner.abortFn();\n\t\t\t},\n\t\t\thasPendingMessages: () => {\n\t\t\t\trunner.assertActive();\n\t\t\t\treturn runner.hasPendingMessagesFn();\n\t\t\t},\n\t\t\tshutdown: () => {\n\t\t\t\trunner.assertActive();\n\t\t\t\trunner.shutdownHandler();\n\t\t\t},\n\t\t\tgetContextUsage: () => {\n\t\t\t\trunner.assertActive();\n\t\t\t\treturn runner.getContextUsageFn();\n\t\t\t},\n\t\t\tcompact: (options) => {\n\t\t\t\trunner.assertActive();\n\t\t\t\trunner.compactFn(options);\n\t\t\t},\n\t\t\tgetSystemPrompt: () => {\n\t\t\t\trunner.assertActive();\n\t\t\t\treturn runner.getSystemPromptFn();\n\t\t\t},\n\t\t\trespondUI: (id, result) => {\n\t\t\t\trunner.assertActive();\n\t\t\t\trunner.respondUI(id, result);\n\t\t\t},\n\t\t};\n\t}\n\n\tcreateCommandContext(): ExtensionCommandContext {\n\t\t// Use property descriptors instead of object spread so the guarded getters from\n\t\t// createContext() stay lazy. A spread would eagerly read them once and freeze the\n\t\t// old values into the returned object, bypassing stale-instance checks.\n\t\tconst context = Object.defineProperties(\n\t\t\t{},\n\t\t\tObject.getOwnPropertyDescriptors(this.createContext()),\n\t\t) as ExtensionCommandContext;\n\t\tcontext.waitForIdle = () => {\n\t\t\tthis.assertActive();\n\t\t\treturn this.waitForIdleFn();\n\t\t};\n\t\tcontext.newSession = (options) => {\n\t\t\tthis.assertActive();\n\t\t\treturn this.newSessionHandler(options);\n\t\t};\n\t\tcontext.fork = (entryId, options) => {\n\t\t\tthis.assertActive();\n\t\t\treturn this.forkHandler(entryId, options);\n\t\t};\n\t\tcontext.navigateTree = (targetId, options) => {\n\t\t\tthis.assertActive();\n\t\t\treturn this.navigateTreeHandler(targetId, options);\n\t\t};\n\t\tcontext.switchSession = (sessionPath, options) => {\n\t\t\tthis.assertActive();\n\t\t\treturn this.switchSessionHandler(sessionPath, options);\n\t\t};\n\t\tcontext.reload = () => {\n\t\t\tthis.assertActive();\n\t\t\treturn this.reloadHandler();\n\t\t};\n\t\treturn context;\n\t}\n\n\tprivate isSessionBeforeEvent(event: RunnerEmitEvent): event is SessionBeforeEvent {\n\t\treturn (\n\t\t\tevent.type === \"session_before_switch\" ||\n\t\t\tevent.type === \"session_before_fork\" ||\n\t\t\tevent.type === \"session_before_compact\" ||\n\t\t\tevent.type === \"session_before_tree\"\n\t\t);\n\t}\n\n\tasync emit<TEvent extends RunnerEmitEvent>(event: TEvent): Promise<RunnerEmitResult<TEvent>> {\n\t\tconst ctx = this.createContext();\n\t\tlet result: SessionBeforeEventResult | undefined;\n\n\t\tfor (const ext of this.extensions) {\n\t\t\tconst handlers = ext.handlers.get(event.type);\n\t\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\t\tfor (const handler of handlers) {\n\t\t\t\ttry {\n\t\t\t\t\tconst handlerResult = await handler(event, ctx);\n\n\t\t\t\t\tif (this.isSessionBeforeEvent(event) && handlerResult) {\n\t\t\t\t\t\tresult = handlerResult as SessionBeforeEventResult;\n\t\t\t\t\t\tif (result.cancel) {\n\t\t\t\t\t\t\treturn result as RunnerEmitResult<TEvent>;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\t\tconst stack = err instanceof Error ? err.stack : undefined;\n\t\t\t\t\tthis.emitError({\n\t\t\t\t\t\textensionPath: ext.path,\n\t\t\t\t\t\tevent: event.type,\n\t\t\t\t\t\terror: message,\n\t\t\t\t\t\tstack,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn result as RunnerEmitResult<TEvent>;\n\t}\n\n\tasync emitToolResult(event: ToolResultEvent): Promise<ToolResultEventResult | undefined> {\n\t\tconst ctx = this.createContext();\n\t\tconst currentEvent: ToolResultEvent = { ...event };\n\t\tlet modified = false;\n\n\t\tfor (const ext of this.extensions) {\n\t\t\tconst handlers = ext.handlers.get(\"tool_result\");\n\t\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\t\tfor (const handler of handlers) {\n\t\t\t\ttry {\n\t\t\t\t\tconst handlerResult = (await handler(currentEvent, ctx)) as ToolResultEventResult | undefined;\n\t\t\t\t\tif (!handlerResult) continue;\n\n\t\t\t\t\tif (handlerResult.content !== undefined) {\n\t\t\t\t\t\tcurrentEvent.content = handlerResult.content;\n\t\t\t\t\t\tmodified = true;\n\t\t\t\t\t}\n\t\t\t\t\tif (handlerResult.details !== undefined) {\n\t\t\t\t\t\tcurrentEvent.details = handlerResult.details;\n\t\t\t\t\t\tmodified = true;\n\t\t\t\t\t}\n\t\t\t\t\tif (handlerResult.isError !== undefined) {\n\t\t\t\t\t\tcurrentEvent.isError = handlerResult.isError;\n\t\t\t\t\t\tmodified = true;\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\t\tconst stack = err instanceof Error ? err.stack : undefined;\n\t\t\t\t\tthis.emitError({\n\t\t\t\t\t\textensionPath: ext.path,\n\t\t\t\t\t\tevent: \"tool_result\",\n\t\t\t\t\t\terror: message,\n\t\t\t\t\t\tstack,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (!modified) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\treturn {\n\t\t\tcontent: currentEvent.content,\n\t\t\tdetails: currentEvent.details,\n\t\t\tisError: currentEvent.isError,\n\t\t};\n\t}\n\n\tasync emitToolCall(event: ToolCallEvent): Promise<ToolCallEventResult | undefined> {\n\t\tconst ctx = this.createContext();\n\t\tlet result: ToolCallEventResult | undefined;\n\n\t\tfor (const ext of this.extensions) {\n\t\t\tconst handlers = ext.handlers.get(\"tool_call\");\n\t\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\t\tfor (const handler of handlers) {\n\t\t\t\tconst handlerResult = await handler(event, ctx);\n\n\t\t\t\tif (handlerResult) {\n\t\t\t\t\tresult = handlerResult as ToolCallEventResult;\n\t\t\t\t\tif (result.block) {\n\t\t\t\t\t\treturn result;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tasync emitUserBash(event: UserBashEvent): Promise<UserBashEventResult | undefined> {\n\t\tconst ctx = this.createContext();\n\n\t\tfor (const ext of this.extensions) {\n\t\t\tconst handlers = ext.handlers.get(\"user_bash\");\n\t\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\t\tfor (const handler of handlers) {\n\t\t\t\ttry {\n\t\t\t\t\tconst handlerResult = await handler(event, ctx);\n\t\t\t\t\tif (handlerResult) {\n\t\t\t\t\t\treturn handlerResult as UserBashEventResult;\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\t\tconst stack = err instanceof Error ? err.stack : undefined;\n\t\t\t\t\tthis.emitError({\n\t\t\t\t\t\textensionPath: ext.path,\n\t\t\t\t\t\tevent: \"user_bash\",\n\t\t\t\t\t\terror: message,\n\t\t\t\t\t\tstack,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\tprivate createAsyncUIPromise<T>(id: string, extract: (result: UIEventResult) => T): Promise<T> | undefined {\n\t\tif (!this.hasHandlers(\"ui\")) return undefined;\n\t\treturn new Promise<T>((resolve) => {\n\t\t\tthis.pendingUIResponses.set(id, (result) => {\n\t\t\t\tthis.pendingUIResponses.delete(id);\n\t\t\t\tresolve(extract(result));\n\t\t\t});\n\t\t});\n\t}\n\n\tprivate wrapUIForInterception(original: ExtensionUIContext): ExtensionUIContext {\n\t\treturn {\n\t\t\t...original,\n\t\t\tconfirm: async (title, message, opts) => {\n\t\t\t\tif (!this.hasHandlers(\"ui\")) return original.confirm(title, message, opts);\n\t\t\t\tconst id = randomUUID();\n\t\t\t\tconst asyncPromise = this.createAsyncUIPromise<boolean>(id, (r) =>\n\t\t\t\t\tr?.action === \"responded\" && r.confirmed !== undefined ? r.confirmed : false,\n\t\t\t\t);\n\t\t\t\tconst result = await this.emitUIEvent<UIEventResult>({\n\t\t\t\t\ttype: \"ui\",\n\t\t\t\t\tid,\n\t\t\t\t\tmethod: \"confirm\",\n\t\t\t\t\ttitle,\n\t\t\t\t\tmessage,\n\t\t\t\t\tsignal: opts?.signal,\n\t\t\t\t\ttimeout: opts?.timeout,\n\t\t\t\t});\n\t\t\t\tif (result?.action === \"responded\" && result.confirmed !== undefined) {\n\t\t\t\t\tthis.pendingUIResponses.delete(id);\n\t\t\t\t\treturn result.confirmed;\n\t\t\t\t}\n\t\t\t\treturn Promise.race([original.confirm(title, message, opts), asyncPromise!]);\n\t\t\t},\n\t\t\tselect: async (title, options, opts) => {\n\t\t\t\tif (!this.hasHandlers(\"ui\")) return original.select(title, options, opts);\n\t\t\t\tconst id = randomUUID();\n\t\t\t\tconst multiple = opts?.multiple;\n\t\t\t\tconst asyncPromise = this.createAsyncUIPromise<string | string[] | undefined>(id, (r) =>\n\t\t\t\t\tr?.action === \"responded\" ? r.value : undefined,\n\t\t\t\t);\n\t\t\t\tconst result = await this.emitUIEvent<UIEventResult>({\n\t\t\t\t\ttype: \"ui\",\n\t\t\t\t\tid,\n\t\t\t\t\tmethod: \"select\",\n\t\t\t\t\ttitle,\n\t\t\t\t\toptions,\n\t\t\t\t\tmultiple,\n\t\t\t\t\tsignal: opts?.signal,\n\t\t\t\t\ttimeout: opts?.timeout,\n\t\t\t\t});\n\t\t\t\tif (result?.action === \"responded\") {\n\t\t\t\t\tthis.pendingUIResponses.delete(id);\n\t\t\t\t\tif (multiple && result.value !== undefined) {\n\t\t\t\t\t\treturn Array.isArray(result.value) ? result.value : [result.value];\n\t\t\t\t\t}\n\t\t\t\t\treturn result.value;\n\t\t\t\t}\n\t\t\t\treturn Promise.race([original.select(title, options, opts), asyncPromise!]);\n\t\t\t},\n\t\t\tinput: async (title, placeholder, opts) => {\n\t\t\t\tif (!this.hasHandlers(\"ui\")) return original.input(title, placeholder, opts);\n\t\t\t\tconst id = randomUUID();\n\t\t\t\tconst asyncPromise = this.createAsyncUIPromise<string | undefined>(id, (r) =>\n\t\t\t\t\tr?.action === \"responded\" ? r.value : undefined,\n\t\t\t\t);\n\t\t\t\tconst result = await this.emitUIEvent<UIEventResult>({\n\t\t\t\t\ttype: \"ui\",\n\t\t\t\t\tid,\n\t\t\t\t\tmethod: \"input\",\n\t\t\t\t\ttitle,\n\t\t\t\t\tplaceholder,\n\t\t\t\t\tsignal: opts?.signal,\n\t\t\t\t\ttimeout: opts?.timeout,\n\t\t\t\t});\n\t\t\t\tif (result?.action === \"responded\") {\n\t\t\t\t\tthis.pendingUIResponses.delete(id);\n\t\t\t\t\treturn result.value;\n\t\t\t\t}\n\t\t\t\treturn Promise.race([original.input(title, placeholder, opts), asyncPromise!]);\n\t\t\t},\n\t\t\teditor: async (title, prefill) => {\n\t\t\t\tif (!this.hasHandlers(\"ui\")) return original.editor(title, prefill);\n\t\t\t\tconst id = randomUUID();\n\t\t\t\tconst asyncPromise = this.createAsyncUIPromise<string | undefined>(id, (r) =>\n\t\t\t\t\tr?.action === \"responded\" ? r.value : undefined,\n\t\t\t\t);\n\t\t\t\tconst result = await this.emitUIEvent<UIEventResult>({\n\t\t\t\t\ttype: \"ui\",\n\t\t\t\t\tid,\n\t\t\t\t\tmethod: \"editor\",\n\t\t\t\t\ttitle,\n\t\t\t\t\tprefill,\n\t\t\t\t});\n\t\t\t\tif (result?.action === \"responded\") {\n\t\t\t\t\tthis.pendingUIResponses.delete(id);\n\t\t\t\t\treturn result.value;\n\t\t\t\t}\n\t\t\t\treturn Promise.race([original.editor(title, prefill), asyncPromise!]);\n\t\t\t},\n\t\t\tnotify: (message, notifyType) => {\n\t\t\t\tif (this.hasHandlers(\"ui\")) {\n\t\t\t\t\tthis.emitUIEvent<UIEventResult>({\n\t\t\t\t\t\ttype: \"ui\",\n\t\t\t\t\t\tid: randomUUID(),\n\t\t\t\t\t\tmethod: \"notify\",\n\t\t\t\t\t\ttitle: message,\n\t\t\t\t\t\tmessage,\n\t\t\t\t\t\tnotifyType,\n\t\t\t\t\t}).catch(() => {});\n\t\t\t\t}\n\t\t\t\treturn original.notify(message, notifyType);\n\t\t\t},\n\t\t};\n\t}\n\n\tprivate async emitUIEvent<TResult extends { action: \"responded\" } | undefined>(\n\t\tevent: UIEvent,\n\t): Promise<TResult | undefined> {\n\t\tconst ctx = this.createContext();\n\n\t\tfor (const ext of this.extensions) {\n\t\t\tfor (const handler of ext.handlers.get(\"ui\") ?? []) {\n\t\t\t\ttry {\n\t\t\t\t\tconst result = (await handler(event, ctx)) as TResult | undefined;\n\t\t\t\t\tif (result?.action === \"responded\") return result;\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\t\tconst stack = err instanceof Error ? err.stack : undefined;\n\t\t\t\t\tthis.emitError({\n\t\t\t\t\t\textensionPath: ext.path,\n\t\t\t\t\t\tevent: \"ui\",\n\t\t\t\t\t\terror: message,\n\t\t\t\t\t\tstack,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\tasync emitUI(event: UIEvent): Promise<UIEventResult | undefined> {\n\t\treturn this.emitUIEvent<UIEventResult>(event);\n\t}\n\n\trespondUI(id: string, result: UIEventResult): void {\n\t\tconst resolve = this.pendingUIResponses.get(id);\n\t\tif (resolve) {\n\t\t\tthis.pendingUIResponses.delete(id);\n\t\t\tresolve(result);\n\t\t}\n\t}\n\n\tasync emitContext(messages: AgentMessage[]): Promise<AgentMessage[]> {\n\t\tconst ctx = this.createContext();\n\t\tlet currentMessages = structuredClone(messages);\n\n\t\tfor (const ext of this.extensions) {\n\t\t\tconst handlers = ext.handlers.get(\"context\");\n\t\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\t\tfor (const handler of handlers) {\n\t\t\t\ttry {\n\t\t\t\t\tconst event: ContextEvent = { type: \"context\", messages: currentMessages };\n\t\t\t\t\tconst handlerResult = await handler(event, ctx);\n\n\t\t\t\t\tif (handlerResult && (handlerResult as ContextEventResult).messages) {\n\t\t\t\t\t\tcurrentMessages = (handlerResult as ContextEventResult).messages!;\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\t\tconst stack = err instanceof Error ? err.stack : undefined;\n\t\t\t\t\tthis.emitError({\n\t\t\t\t\t\textensionPath: ext.path,\n\t\t\t\t\t\tevent: \"context\",\n\t\t\t\t\t\terror: message,\n\t\t\t\t\t\tstack,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn currentMessages;\n\t}\n\n\tasync emitBeforeProviderRequest(payload: unknown): Promise<unknown> {\n\t\tconst ctx = this.createContext();\n\t\tlet currentPayload = payload;\n\n\t\tfor (const ext of this.extensions) {\n\t\t\tconst handlers = ext.handlers.get(\"before_provider_request\");\n\t\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\t\tfor (const handler of handlers) {\n\t\t\t\ttry {\n\t\t\t\t\tconst event: BeforeProviderRequestEvent = {\n\t\t\t\t\t\ttype: \"before_provider_request\",\n\t\t\t\t\t\tpayload: currentPayload,\n\t\t\t\t\t};\n\t\t\t\t\tconst handlerResult = await handler(event, ctx);\n\t\t\t\t\tif (handlerResult !== undefined) {\n\t\t\t\t\t\tcurrentPayload = handlerResult;\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\t\tconst stack = err instanceof Error ? err.stack : undefined;\n\t\t\t\t\tthis.emitError({\n\t\t\t\t\t\textensionPath: ext.path,\n\t\t\t\t\t\tevent: \"before_provider_request\",\n\t\t\t\t\t\terror: message,\n\t\t\t\t\t\tstack,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn currentPayload;\n\t}\n\n\tasync emitBeforeAgentStart(\n\t\tprompt: string,\n\t\timages: ImageContent[] | undefined,\n\t\tsystemPrompt: string,\n\t\tsystemPromptOptions: BuildSystemPromptOptions,\n\t): Promise<BeforeAgentStartCombinedResult | undefined> {\n\t\tlet currentSystemPrompt = systemPrompt;\n\t\tconst ctx = Object.defineProperties(\n\t\t\t{},\n\t\t\tObject.getOwnPropertyDescriptors(this.createContext()),\n\t\t) as ExtensionContext;\n\t\tctx.getSystemPrompt = () => {\n\t\t\tthis.assertActive();\n\t\t\treturn currentSystemPrompt;\n\t\t};\n\t\tconst messages: NonNullable<BeforeAgentStartEventResult[\"message\"]>[] = [];\n\t\tlet systemPromptModified = false;\n\n\t\tfor (const ext of this.extensions) {\n\t\t\tconst handlers = ext.handlers.get(\"before_agent_start\");\n\t\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\t\tfor (const handler of handlers) {\n\t\t\t\ttry {\n\t\t\t\t\tconst event: BeforeAgentStartEvent = {\n\t\t\t\t\t\ttype: \"before_agent_start\",\n\t\t\t\t\t\tprompt,\n\t\t\t\t\t\timages,\n\t\t\t\t\t\tsystemPrompt: currentSystemPrompt,\n\t\t\t\t\t\tsystemPromptOptions,\n\t\t\t\t\t};\n\t\t\t\t\tconst handlerResult = await handler(event, ctx);\n\n\t\t\t\t\tif (handlerResult) {\n\t\t\t\t\t\tconst result = handlerResult as BeforeAgentStartEventResult;\n\t\t\t\t\t\tif (result.message) {\n\t\t\t\t\t\t\tmessages.push(result.message);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (result.systemPrompt !== undefined) {\n\t\t\t\t\t\t\tcurrentSystemPrompt = result.systemPrompt;\n\t\t\t\t\t\t\tsystemPromptModified = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\t\tconst stack = err instanceof Error ? err.stack : undefined;\n\t\t\t\t\tthis.emitError({\n\t\t\t\t\t\textensionPath: ext.path,\n\t\t\t\t\t\tevent: \"before_agent_start\",\n\t\t\t\t\t\terror: message,\n\t\t\t\t\t\tstack,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (messages.length > 0 || systemPromptModified) {\n\t\t\treturn {\n\t\t\t\tmessages: messages.length > 0 ? messages : undefined,\n\t\t\t\tsystemPrompt: systemPromptModified ? currentSystemPrompt : undefined,\n\t\t\t};\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\tasync emitResourcesDiscover(\n\t\tcwd: string,\n\t\treason: ResourcesDiscoverEvent[\"reason\"],\n\t): Promise<{\n\t\tskillPaths: Array<{ path: string; extensionPath: string }>;\n\t\tpromptPaths: Array<{ path: string; extensionPath: string }>;\n\t\tthemePaths: Array<{ path: string; extensionPath: string }>;\n\t}> {\n\t\tconst ctx = this.createContext();\n\t\tconst skillPaths: Array<{ path: string; extensionPath: string }> = [];\n\t\tconst promptPaths: Array<{ path: string; extensionPath: string }> = [];\n\t\tconst themePaths: Array<{ path: string; extensionPath: string }> = [];\n\n\t\tfor (const ext of this.extensions) {\n\t\t\tconst handlers = ext.handlers.get(\"resources_discover\");\n\t\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\t\tfor (const handler of handlers) {\n\t\t\t\ttry {\n\t\t\t\t\tconst event: ResourcesDiscoverEvent = { type: \"resources_discover\", cwd, reason };\n\t\t\t\t\tconst handlerResult = await handler(event, ctx);\n\t\t\t\t\tconst result = handlerResult as ResourcesDiscoverResult | undefined;\n\n\t\t\t\t\tif (result?.skillPaths?.length) {\n\t\t\t\t\t\tskillPaths.push(...result.skillPaths.map((path) => ({ path, extensionPath: ext.path })));\n\t\t\t\t\t}\n\t\t\t\t\tif (result?.promptPaths?.length) {\n\t\t\t\t\t\tpromptPaths.push(...result.promptPaths.map((path) => ({ path, extensionPath: ext.path })));\n\t\t\t\t\t}\n\t\t\t\t\tif (result?.themePaths?.length) {\n\t\t\t\t\t\tthemePaths.push(...result.themePaths.map((path) => ({ path, extensionPath: ext.path })));\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\t\tconst stack = err instanceof Error ? err.stack : undefined;\n\t\t\t\t\tthis.emitError({\n\t\t\t\t\t\textensionPath: ext.path,\n\t\t\t\t\t\tevent: \"resources_discover\",\n\t\t\t\t\t\terror: message,\n\t\t\t\t\t\tstack,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn { skillPaths, promptPaths, themePaths };\n\t}\n\n\t/** Emit input event. Transforms chain, \"handled\" short-circuits. */\n\tasync emitInput(text: string, images: ImageContent[] | undefined, source: InputSource): Promise<InputEventResult> {\n\t\tconst ctx = this.createContext();\n\t\tlet currentText = text;\n\t\tlet currentImages = images;\n\n\t\tfor (const ext of this.extensions) {\n\t\t\tfor (const handler of ext.handlers.get(\"input\") ?? []) {\n\t\t\t\ttry {\n\t\t\t\t\tconst event: InputEvent = { type: \"input\", text: currentText, images: currentImages, source };\n\t\t\t\t\tconst result = (await handler(event, ctx)) as InputEventResult | undefined;\n\t\t\t\t\tif (result?.action === \"handled\") return result;\n\t\t\t\t\tif (result?.action === \"transform\") {\n\t\t\t\t\t\tcurrentText = result.text;\n\t\t\t\t\t\tcurrentImages = result.images ?? currentImages;\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tthis.emitError({\n\t\t\t\t\t\textensionPath: ext.path,\n\t\t\t\t\t\tevent: \"input\",\n\t\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t\t\tstack: err instanceof Error ? err.stack : undefined,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn currentText !== text || currentImages !== images\n\t\t\t? { action: \"transform\", text: currentText, images: currentImages }\n\t\t\t: { action: \"continue\" };\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../../src/core/extensions/runner.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Extension runner - executes extensions and manages their lifecycle.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport type { AgentMessage } from \"@dyyz1993/pi-agent-core\";\nimport type { ImageContent, Model } from \"@dyyz1993/pi-ai\";\nimport type { KeyId } from \"@dyyz1993/pi-tui\";\nimport { type Theme, theme } from \"../../modes/interactive/theme/theme.js\";\nimport type { ResourceDiagnostic } from \"../diagnostics.js\";\nimport type { KeybindingsConfig } from \"../keybindings.js\";\nimport type { ModelRegistry } from \"../model-registry.js\";\nimport type { SessionManager } from \"../session-manager.js\";\nimport { getCwdDataDir, getGlobalDataDir, getProjectDataDir, getSessionDataDir, resolveProjectRoot } from \"../storage.js\";\nimport type { BuildSystemPromptOptions } from \"../system-prompt.js\";\nimport type {\n\tBeforeAgentStartEvent,\n\tBeforeAgentStartEventResult,\n\tBeforeProviderRequestEvent,\n\tCompactOptions,\n\tContextEvent,\n\tContextEventResult,\n\tContextUsage,\n\tExtension,\n\tExtensionActions,\n\tExtensionCommandContext,\n\tExtensionCommandContextActions,\n\tExtensionContext,\n\tExtensionContextActions,\n\tExtensionError,\n\tExtensionEvent,\n\tExtensionFlag,\n\tExtensionRuntime,\n\tExtensionShortcut,\n\tExtensionUIContext,\n\tInputEvent,\n\tInputEventResult,\n\tInputSource,\n\tMessageRenderer,\n\tProviderConfig,\n\tRegisteredCommand,\n\tRegisteredTool,\n\tReplacedSessionContext,\n\tResolvedCommand,\n\tResourcesDiscoverEvent,\n\tResourcesDiscoverResult,\n\tSessionBeforeCompactResult,\n\tSessionBeforeForkResult,\n\tSessionBeforeSwitchResult,\n\tSessionBeforeTreeResult,\n\tSessionShutdownEvent,\n\tToolCallEvent,\n\tToolCallEventResult,\n\tToolResultEvent,\n\tToolResultEventResult,\n\tUIEvent,\n\tUIEventResult,\n\tUserBashEvent,\n\tUserBashEventResult,\n} from \"./types.js\";\n\n// Extension shortcuts compete with canonical keybinding ids from keybindings.json.\n// Only editor-global shortcuts are reserved here. Picker-specific bindings are not.\nconst RESERVED_KEYBINDINGS_FOR_EXTENSION_CONFLICTS = [\n\t\"app.interrupt\",\n\t\"app.clear\",\n\t\"app.exit\",\n\t\"app.suspend\",\n\t\"app.thinking.cycle\",\n\t\"app.model.cycleForward\",\n\t\"app.model.cycleBackward\",\n\t\"app.model.select\",\n\t\"app.tools.expand\",\n\t\"app.thinking.toggle\",\n\t\"app.editor.external\",\n\t\"app.message.followUp\",\n\t\"tui.input.submit\",\n\t\"tui.select.confirm\",\n\t\"tui.select.cancel\",\n\t\"tui.input.copy\",\n\t\"tui.editor.deleteToLineEnd\",\n] as const;\n\ntype BuiltInKeyBindings = Partial<Record<KeyId, { keybinding: string; restrictOverride: boolean }>>;\n\nconst buildBuiltinKeybindings = (resolvedKeybindings: KeybindingsConfig): BuiltInKeyBindings => {\n\tconst builtinKeybindings = {} as BuiltInKeyBindings;\n\tfor (const [keybinding, keys] of Object.entries(resolvedKeybindings)) {\n\t\tif (keys === undefined) continue;\n\t\tconst keyList = Array.isArray(keys) ? keys : [keys];\n\t\tconst restrictOverride = (RESERVED_KEYBINDINGS_FOR_EXTENSION_CONFLICTS as readonly string[]).includes(keybinding);\n\t\tfor (const key of keyList) {\n\t\t\tconst normalizedKey = key.toLowerCase() as KeyId;\n\t\t\t// If multiple actions bind the same key, the reserved action wins so extensions\n\t\t\t// remain blocked by reserved shortcuts regardless of iteration order.\n\t\t\tconst existing = builtinKeybindings[normalizedKey];\n\t\t\tif (existing?.restrictOverride && !restrictOverride) continue;\n\t\t\tbuiltinKeybindings[normalizedKey] = {\n\t\t\t\tkeybinding,\n\t\t\t\trestrictOverride,\n\t\t\t};\n\t\t}\n\t}\n\treturn builtinKeybindings;\n};\n\n/** Combined result from all before_agent_start handlers */\ninterface BeforeAgentStartCombinedResult {\n\tmessages?: NonNullable<BeforeAgentStartEventResult[\"message\"]>[];\n\tsystemPrompt?: string;\n}\n\n/**\n * Events handled by the generic emit() method.\n * Events with dedicated emitXxx() methods are excluded for stronger type safety.\n */\ntype RunnerEmitEvent = Exclude<\n\tExtensionEvent,\n\t| ToolCallEvent\n\t| ToolResultEvent\n\t| UserBashEvent\n\t| ContextEvent\n\t| BeforeProviderRequestEvent\n\t| BeforeAgentStartEvent\n\t| ResourcesDiscoverEvent\n\t| InputEvent\n>;\n\ntype SessionBeforeEvent = Extract<\n\tRunnerEmitEvent,\n\t{ type: \"session_before_switch\" | \"session_before_fork\" | \"session_before_compact\" | \"session_before_tree\" }\n>;\n\ntype SessionBeforeEventResult =\n\t| SessionBeforeSwitchResult\n\t| SessionBeforeForkResult\n\t| SessionBeforeCompactResult\n\t| SessionBeforeTreeResult;\n\ntype RunnerEmitResult<TEvent extends RunnerEmitEvent> = TEvent extends { type: \"session_before_switch\" }\n\t? SessionBeforeSwitchResult | undefined\n\t: TEvent extends { type: \"session_before_fork\" }\n\t\t? SessionBeforeForkResult | undefined\n\t\t: TEvent extends { type: \"session_before_compact\" }\n\t\t\t? SessionBeforeCompactResult | undefined\n\t\t\t: TEvent extends { type: \"session_before_tree\" }\n\t\t\t\t? SessionBeforeTreeResult | undefined\n\t\t\t\t: undefined;\n\nexport type ExtensionErrorListener = (error: ExtensionError) => void;\n\nexport type NewSessionHandler = (options?: {\n\tparentSession?: string;\n\tsetup?: (sessionManager: SessionManager) => Promise<void>;\n\twithSession?: (ctx: ReplacedSessionContext) => Promise<void>;\n}) => Promise<{ cancelled: boolean }>;\n\nexport type ForkHandler = (\n\tentryId: string,\n\toptions?: { position?: \"before\" | \"at\"; withSession?: (ctx: ReplacedSessionContext) => Promise<void> },\n) => Promise<{ cancelled: boolean }>;\n\nexport type NavigateTreeHandler = (\n\ttargetId: string,\n\toptions?: { summarize?: boolean; customInstructions?: string; replaceInstructions?: boolean; label?: string },\n) => Promise<{ cancelled: boolean }>;\n\nexport type SwitchSessionHandler = (\n\tsessionPath: string,\n\toptions?: { withSession?: (ctx: ReplacedSessionContext) => Promise<void> },\n) => Promise<{ cancelled: boolean }>;\n\nexport type ReloadHandler = () => Promise<void>;\n\nexport type ShutdownHandler = () => void;\n\n/**\n * Helper function to emit session_shutdown event to extensions.\n * Returns true if the event was emitted, false if there were no handlers.\n */\nexport async function emitSessionShutdownEvent(\n\textensionRunner: ExtensionRunner,\n\tevent: SessionShutdownEvent,\n): Promise<boolean> {\n\tif (extensionRunner.hasHandlers(\"session_shutdown\")) {\n\t\tawait extensionRunner.emit(event);\n\t\treturn true;\n\t}\n\treturn false;\n}\n\nconst noOpUIContext: ExtensionUIContext = {\n\tselect: async () => undefined,\n\tconfirm: async () => false,\n\tinput: async () => undefined,\n\tnotify: () => {},\n\tonTerminalInput: () => () => {},\n\tsetStatus: () => {},\n\tsetWorkingMessage: () => {},\n\tsetWorkingIndicator: () => {},\n\tsetHiddenThinkingLabel: () => {},\n\tsetWidget: () => {},\n\tsetFooter: () => {},\n\tsetHeader: () => {},\n\tsetTitle: () => {},\n\tcustom: async () => undefined as never,\n\tpasteToEditor: () => {},\n\tsetEditorText: () => {},\n\tgetEditorText: () => \"\",\n\teditor: async () => undefined,\n\taddAutocompleteProvider: () => {},\n\tsetEditorComponent: () => {},\n\tget theme() {\n\t\treturn theme;\n\t},\n\tgetAllThemes: () => [],\n\tgetTheme: () => undefined,\n\tsetTheme: (_theme: string | Theme) => ({ success: false, error: \"UI not available\" }),\n\tgetToolsExpanded: () => false,\n\tsetToolsExpanded: () => {},\n};\n\nexport class ExtensionRunner {\n\tprivate extensions: Extension[];\n\tprivate runtime: ExtensionRuntime;\n\tprivate uiContext: ExtensionUIContext;\n\tprivate uiContextOriginal: ExtensionUIContext | undefined;\n\tprivate cwd: string;\n\tprivate sessionManager: SessionManager;\n\tprivate modelRegistry: ModelRegistry;\n\tprivate errorListeners: Set<ExtensionErrorListener> = new Set();\n\tprivate getModel: () => Model<any> | undefined = () => undefined;\n\tprivate isIdleFn: () => boolean = () => true;\n\tprivate getSignalFn: () => AbortSignal | undefined = () => undefined;\n\tprivate getSessionSignalFn: () => AbortSignal = () => AbortSignal.abort();\n\tprivate waitForIdleFn: () => Promise<void> = async () => {};\n\tprivate abortFn: () => void = () => {};\n\tprivate hasPendingMessagesFn: () => boolean = () => false;\n\tprivate getContextUsageFn: () => ContextUsage | undefined = () => undefined;\n\tprivate compactFn: (options?: CompactOptions) => void = () => {};\n\tprivate getSystemPromptFn: () => string = () => \"\";\n\tprivate newSessionHandler: NewSessionHandler = async () => ({ cancelled: false });\n\tprivate forkHandler: ForkHandler = async () => ({ cancelled: false });\n\tprivate navigateTreeHandler: NavigateTreeHandler = async () => ({ cancelled: false });\n\tprivate switchSessionHandler: SwitchSessionHandler = async () => ({ cancelled: false });\n\tprivate reloadHandler: ReloadHandler = async () => {};\n\tprivate shutdownHandler: ShutdownHandler = () => {};\n\tprivate shortcutDiagnostics: ResourceDiagnostic[] = [];\n\tprivate commandDiagnostics: ResourceDiagnostic[] = [];\n\tprivate staleMessage: string | undefined;\n\tprivate pendingUIResponses: Map<string, (result: UIEventResult) => void> = new Map();\n\n\tconstructor(\n\t\textensions: Extension[],\n\t\truntime: ExtensionRuntime,\n\t\tcwd: string,\n\t\tsessionManager: SessionManager,\n\t\tmodelRegistry: ModelRegistry,\n\t) {\n\t\tthis.extensions = extensions;\n\t\tthis.runtime = runtime;\n\t\tthis.uiContext = noOpUIContext;\n\t\tthis.uiContextOriginal = undefined;\n\t\tthis.cwd = cwd;\n\t\tthis.sessionManager = sessionManager;\n\t\tthis.modelRegistry = modelRegistry;\n\t}\n\n\tbindCore(\n\t\tactions: ExtensionActions,\n\t\tcontextActions: ExtensionContextActions,\n\t\tproviderActions?: {\n\t\t\tregisterProvider?: (name: string, config: ProviderConfig) => void;\n\t\t\tunregisterProvider?: (name: string) => void;\n\t\t},\n\t): void {\n\t\t// Copy actions into the shared runtime (all extension APIs reference this)\n\t\tthis.runtime.sendMessage = actions.sendMessage;\n\t\tthis.runtime.sendUserMessage = actions.sendUserMessage;\n\t\tthis.runtime.appendEntry = actions.appendEntry;\n\t\tthis.runtime.foldEntry = actions.foldEntry;\n\t\tthis.runtime.setSessionName = actions.setSessionName;\n\t\tthis.runtime.getSessionName = actions.getSessionName;\n\t\tthis.runtime.setLabel = actions.setLabel;\n\t\tthis.runtime.getActiveTools = actions.getActiveTools;\n\t\tthis.runtime.getAllTools = actions.getAllTools;\n\t\tthis.runtime.setActiveTools = actions.setActiveTools;\n\t\tthis.runtime.refreshTools = actions.refreshTools;\n\t\tthis.runtime.getCommands = actions.getCommands;\n\t\tthis.runtime.setModel = actions.setModel;\n\t\tthis.runtime.getThinkingLevel = actions.getThinkingLevel;\n\t\tthis.runtime.setThinkingLevel = actions.setThinkingLevel;\n\t\tthis.runtime.registerChannel = actions.registerChannel;\n\t\tthis.runtime.callLLM = actions.callLLM;\n\t\tthis.runtime.callLLMStructured = actions.callLLMStructured;\n\t\tthis.runtime.background = actions.background;\n\n\t\t// Context actions (required)\n\t\tthis.getModel = contextActions.getModel;\n\t\tthis.isIdleFn = contextActions.isIdle;\n\t\tthis.getSignalFn = contextActions.getSignal;\n\t\tthis.getSessionSignalFn = contextActions.getSessionSignal;\n\t\tthis.abortFn = contextActions.abort;\n\t\tthis.hasPendingMessagesFn = contextActions.hasPendingMessages;\n\t\tthis.shutdownHandler = contextActions.shutdown;\n\t\tthis.getContextUsageFn = contextActions.getContextUsage;\n\t\tthis.compactFn = contextActions.compact;\n\t\tthis.getSystemPromptFn = contextActions.getSystemPrompt;\n\n\t\t// Flush provider registrations queued during extension loading\n\t\tfor (const { name, config, extensionPath } of this.runtime.pendingProviderRegistrations) {\n\t\t\ttry {\n\t\t\t\tif (providerActions?.registerProvider) {\n\t\t\t\t\tproviderActions.registerProvider(name, config);\n\t\t\t\t} else {\n\t\t\t\t\tthis.modelRegistry.registerProvider(name, config);\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tthis.emitError({\n\t\t\t\t\textensionPath,\n\t\t\t\t\tevent: \"register_provider\",\n\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t\tstack: err instanceof Error ? err.stack : undefined,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tthis.runtime.pendingProviderRegistrations = [];\n\n\t\t// Set registerChannel on the runtime. If this is the throwing stub (non-RPC mode),\n\t\t// pending channel registrations remain queued until bindExtensions() provides the\n\t\t// real implementation via flushPendingChannels().\n\t\tthis.runtime.registerChannel = actions.registerChannel;\n\n\t\t// From this point on, provider registration/unregistration takes effect immediately\n\t\t// without requiring a /reload.\n\t\tthis.runtime.registerProvider = (name, config) => {\n\t\t\tif (providerActions?.registerProvider) {\n\t\t\t\tproviderActions.registerProvider(name, config);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.modelRegistry.registerProvider(name, config);\n\t\t};\n\t\tthis.runtime.unregisterProvider = (name) => {\n\t\t\tif (providerActions?.unregisterProvider) {\n\t\t\t\tproviderActions.unregisterProvider(name);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.modelRegistry.unregisterProvider(name);\n\t\t};\n\t}\n\n\tbindCommandContext(actions?: ExtensionCommandContextActions): void {\n\t\tif (actions) {\n\t\t\tthis.waitForIdleFn = actions.waitForIdle;\n\t\t\tthis.newSessionHandler = actions.newSession;\n\t\t\tthis.forkHandler = actions.fork;\n\t\t\tthis.navigateTreeHandler = actions.navigateTree;\n\t\t\tthis.switchSessionHandler = actions.switchSession;\n\t\t\tthis.reloadHandler = actions.reload;\n\t\t\treturn;\n\t\t}\n\n\t\tthis.waitForIdleFn = async () => {};\n\t\tthis.newSessionHandler = async () => ({ cancelled: false });\n\t\tthis.forkHandler = async () => ({ cancelled: false });\n\t\tthis.navigateTreeHandler = async () => ({ cancelled: false });\n\t\tthis.switchSessionHandler = async () => ({ cancelled: false });\n\t\tthis.reloadHandler = async () => {};\n\t}\n\n\t/**\n\t * Flush pending channel registrations with the real registerChannel implementation.\n\t * Called from bindCore() and again from bindExtensions() when the real registerChannel\n\t * becomes available (e.g. in RPC mode).\n\t */\n\tflushPendingChannels(registerChannel: (name: string) => import(\"./channel-types.js\").Channel): void {\n\t\tif (this.runtime.pendingChannelRegistrations.length === 0) return;\n\n\t\tfor (const pending of this.runtime.pendingChannelRegistrations) {\n\t\t\ttry {\n\t\t\t\tconst channel = registerChannel(pending.name);\n\t\t\t\tthis.runtime.resolvedChannels.set(pending.name, channel);\n\t\t\t\tpending.resolve(channel);\n\t\t\t} catch (err) {\n\t\t\t\tpending.reject(err instanceof Error ? err : new Error(String(err)));\n\t\t\t}\n\t\t}\n\t\tthis.runtime.pendingChannelRegistrations = [];\n\t\tthis.runtime.registerChannel = registerChannel;\n\t}\n\n\tupdateRegisterChannel(registerChannel: (name: string) => import(\"./channel-types.js\").Channel): void {\n\t\tthis.runtime.registerChannel = registerChannel;\n\t}\n\n\tsetUIContext(uiContext?: ExtensionUIContext): void {\n\t\tthis.uiContextOriginal = uiContext;\n\t\tthis.uiContext = this.wrapUIForInterception(uiContext ?? noOpUIContext);\n\t}\n\n\tgetUIContext(): ExtensionUIContext {\n\t\treturn this.uiContext;\n\t}\n\n\thasUI(): boolean {\n\t\treturn this.uiContextOriginal !== undefined && this.uiContextOriginal !== noOpUIContext;\n\t}\n\n\tgetExtensionPaths(): string[] {\n\t\treturn this.extensions.map((e) => e.path);\n\t}\n\n\tgetExtensionNameByPath(extensionPath: string): string | undefined {\n\t\tfor (const ext of this.extensions) {\n\t\t\tif (ext.path === extensionPath) {\n\t\t\t\treturn ext.name;\n\t\t\t}\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t/** Get all registered tools from all extensions (first registration per name wins). */\n\tgetAllRegisteredTools(): RegisteredTool[] {\n\t\tconst toolsByName = new Map<string, RegisteredTool>();\n\t\tfor (const ext of this.extensions) {\n\t\t\tfor (const tool of ext.tools.values()) {\n\t\t\t\tif (!toolsByName.has(tool.definition.name)) {\n\t\t\t\t\ttoolsByName.set(tool.definition.name, tool);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn Array.from(toolsByName.values());\n\t}\n\n\t/** Get a tool definition by name. Returns undefined if not found. */\n\tgetToolDefinition(toolName: string): RegisteredTool[\"definition\"] | undefined {\n\t\tfor (const ext of this.extensions) {\n\t\t\tconst tool = ext.tools.get(toolName);\n\t\t\tif (tool) {\n\t\t\t\treturn tool.definition;\n\t\t\t}\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tgetFlags(): Map<string, ExtensionFlag> {\n\t\tconst allFlags = new Map<string, ExtensionFlag>();\n\t\tfor (const ext of this.extensions) {\n\t\t\tfor (const [name, flag] of ext.flags) {\n\t\t\t\tif (!allFlags.has(name)) {\n\t\t\t\t\tallFlags.set(name, flag);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn allFlags;\n\t}\n\n\tsetFlagValue(name: string, value: boolean | string): void {\n\t\tthis.runtime.flagValues.set(name, value);\n\t}\n\n\tgetFlagValues(): Map<string, boolean | string> {\n\t\treturn new Map(this.runtime.flagValues);\n\t}\n\n\tgetShortcuts(resolvedKeybindings: KeybindingsConfig): Map<KeyId, ExtensionShortcut> {\n\t\tthis.shortcutDiagnostics = [];\n\t\tconst builtinKeybindings = buildBuiltinKeybindings(resolvedKeybindings);\n\t\tconst extensionShortcuts = new Map<KeyId, ExtensionShortcut>();\n\n\t\tconst addDiagnostic = (message: string, extensionPath: string) => {\n\t\t\tthis.shortcutDiagnostics.push({ type: \"warning\", message, path: extensionPath });\n\t\t\tif (!this.hasUI()) {\n\t\t\t\tconsole.warn(message);\n\t\t\t}\n\t\t};\n\n\t\tfor (const ext of this.extensions) {\n\t\t\tfor (const [key, shortcut] of ext.shortcuts) {\n\t\t\t\tconst normalizedKey = key.toLowerCase() as KeyId;\n\n\t\t\t\tconst builtInKeybinding = builtinKeybindings[normalizedKey];\n\t\t\t\tif (builtInKeybinding?.restrictOverride === true) {\n\t\t\t\t\taddDiagnostic(\n\t\t\t\t\t\t`Extension shortcut '${key}' from ${shortcut.extensionPath} conflicts with built-in shortcut. Skipping.`,\n\t\t\t\t\t\tshortcut.extensionPath,\n\t\t\t\t\t);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (builtInKeybinding?.restrictOverride === false) {\n\t\t\t\t\taddDiagnostic(\n\t\t\t\t\t\t`Extension shortcut conflict: '${key}' is built-in shortcut for ${builtInKeybinding.keybinding} and ${shortcut.extensionPath}. Using ${shortcut.extensionPath}.`,\n\t\t\t\t\t\tshortcut.extensionPath,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tconst existingExtensionShortcut = extensionShortcuts.get(normalizedKey);\n\t\t\t\tif (existingExtensionShortcut) {\n\t\t\t\t\taddDiagnostic(\n\t\t\t\t\t\t`Extension shortcut conflict: '${key}' registered by both ${existingExtensionShortcut.extensionPath} and ${shortcut.extensionPath}. Using ${shortcut.extensionPath}.`,\n\t\t\t\t\t\tshortcut.extensionPath,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\textensionShortcuts.set(normalizedKey, shortcut);\n\t\t\t}\n\t\t}\n\t\treturn extensionShortcuts;\n\t}\n\n\tgetShortcutDiagnostics(): ResourceDiagnostic[] {\n\t\treturn this.shortcutDiagnostics;\n\t}\n\n\tinvalidate(message = \"This extension instance is stale after session replacement or reload.\"): void {\n\t\tif (!this.staleMessage) {\n\t\t\tthis.staleMessage = message;\n\t\t\tthis.runtime.invalidate(message);\n\t\t}\n\t}\n\n\tprivate assertActive(): void {\n\t\tif (this.staleMessage) {\n\t\t\tthrow new Error(this.staleMessage);\n\t\t}\n\t}\n\n\tonError(listener: ExtensionErrorListener): () => void {\n\t\tthis.errorListeners.add(listener);\n\t\treturn () => this.errorListeners.delete(listener);\n\t}\n\n\temitError(error: ExtensionError): void {\n\t\tfor (const listener of this.errorListeners) {\n\t\t\tlistener(error);\n\t\t}\n\t}\n\n\thasHandlers(eventType: string): boolean {\n\t\tfor (const ext of this.extensions) {\n\t\t\tconst handlers = ext.handlers.get(eventType);\n\t\t\tif (handlers && handlers.length > 0) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\tgetMessageRenderer(customType: string): MessageRenderer | undefined {\n\t\tfor (const ext of this.extensions) {\n\t\t\tconst renderer = ext.messageRenderers.get(customType);\n\t\t\tif (renderer) {\n\t\t\t\treturn renderer;\n\t\t\t}\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tprivate resolveRegisteredCommands(): ResolvedCommand[] {\n\t\tconst commands: RegisteredCommand[] = [];\n\t\tconst counts = new Map<string, number>();\n\n\t\tfor (const ext of this.extensions) {\n\t\t\tfor (const command of ext.commands.values()) {\n\t\t\t\tcommands.push(command);\n\t\t\t\tcounts.set(command.name, (counts.get(command.name) ?? 0) + 1);\n\t\t\t}\n\t\t}\n\n\t\tconst seen = new Map<string, number>();\n\t\tconst takenInvocationNames = new Set<string>();\n\n\t\treturn commands.map((command) => {\n\t\t\tconst occurrence = (seen.get(command.name) ?? 0) + 1;\n\t\t\tseen.set(command.name, occurrence);\n\n\t\t\tlet invocationName = (counts.get(command.name) ?? 0) > 1 ? `${command.name}:${occurrence}` : command.name;\n\n\t\t\tif (takenInvocationNames.has(invocationName)) {\n\t\t\t\tlet suffix = occurrence;\n\t\t\t\tdo {\n\t\t\t\t\tsuffix++;\n\t\t\t\t\tinvocationName = `${command.name}:${suffix}`;\n\t\t\t\t} while (takenInvocationNames.has(invocationName));\n\t\t\t}\n\n\t\t\ttakenInvocationNames.add(invocationName);\n\t\t\treturn {\n\t\t\t\t...command,\n\t\t\t\tinvocationName,\n\t\t\t};\n\t\t});\n\t}\n\n\tgetRegisteredCommands(): ResolvedCommand[] {\n\t\tthis.commandDiagnostics = [];\n\t\treturn this.resolveRegisteredCommands();\n\t}\n\n\tgetCommandDiagnostics(): ResourceDiagnostic[] {\n\t\treturn this.commandDiagnostics;\n\t}\n\n\tgetCommand(name: string): ResolvedCommand | undefined {\n\t\treturn this.resolveRegisteredCommands().find((command) => command.invocationName === name);\n\t}\n\n\t/**\n\t * Request a graceful shutdown. Called by extension tools and event handlers.\n\t * The actual shutdown behavior is provided by the mode via bindExtensions().\n\t */\n\tshutdown(): void {\n\t\tthis.shutdownHandler();\n\t}\n\n\t/**\n\t * Create an ExtensionContext for use in event handlers and tool execution.\n\t * Context values are resolved at call time, so changes via bindCore/bindUI are reflected.\n\t */\n\tcreateContext(ext?: Extension): ExtensionContext {\n\t\tconst runner = this;\n\t\tconst getModel = this.getModel;\n\t\treturn {\n\t\t\tget ui() {\n\t\t\t\trunner.assertActive();\n\t\t\t\treturn runner.uiContext;\n\t\t\t},\n\t\t\tget hasUI() {\n\t\t\t\trunner.assertActive();\n\t\t\t\treturn runner.hasUI();\n\t\t\t},\n\t\t\tget cwd() {\n\t\t\t\trunner.assertActive();\n\t\t\t\treturn runner.cwd;\n\t\t\t},\n\t\t\tget extensionName() {\n\t\t\t\trunner.assertActive();\n\t\t\t\treturn ext?.name ?? \"unknown\";\n\t\t\t},\n\t\t\tget projectRoot() {\n\t\t\t\trunner.assertActive();\n\t\t\t\treturn resolveProjectRoot(runner.cwd);\n\t\t\t},\n\t\t\tget sessionDataDir() {\n\t\t\t\trunner.assertActive();\n\t\t\t\tconst sessionDir = runner.sessionManager.getSessionDir();\n\t\t\t\tconst sessionId = runner.sessionManager.getSessionId();\n\t\t\t\treturn getSessionDataDir(sessionDir, sessionId, ext?.name ?? \"unknown\");\n\t\t\t},\n\t\t\tget projectDataDir() {\n\t\t\t\trunner.assertActive();\n\t\t\t\treturn getProjectDataDir(resolveProjectRoot(runner.cwd), ext?.name ?? \"unknown\");\n\t\t\t},\n\t\t\tget cwdDataDir() {\n\t\t\t\trunner.assertActive();\n\t\t\t\treturn getCwdDataDir(runner.cwd, ext?.name ?? \"unknown\");\n\t\t\t},\n\t\t\tget globalDataDir() {\n\t\t\t\trunner.assertActive();\n\t\t\t\treturn getGlobalDataDir(ext?.name ?? \"unknown\");\n\t\t\t},\n\t\t\tget sessionManager() {\n\t\t\t\trunner.assertActive();\n\t\t\t\treturn runner.sessionManager;\n\t\t\t},\n\t\t\tget modelRegistry() {\n\t\t\t\trunner.assertActive();\n\t\t\t\treturn runner.modelRegistry;\n\t\t\t},\n\t\t\tget model() {\n\t\t\t\trunner.assertActive();\n\t\t\t\treturn getModel();\n\t\t\t},\n\t\t\tisIdle: () => {\n\t\t\t\trunner.assertActive();\n\t\t\t\treturn runner.isIdleFn();\n\t\t\t},\n\t\t\tget signal() {\n\t\t\t\trunner.assertActive();\n\t\t\t\treturn runner.getSignalFn();\n\t\t\t},\n\t\t\tget sessionSignal() {\n\t\t\t\trunner.assertActive();\n\t\t\t\treturn runner.getSessionSignalFn();\n\t\t\t},\n\t\t\tabort: () => {\n\t\t\t\trunner.assertActive();\n\t\t\t\trunner.abortFn();\n\t\t\t},\n\t\t\thasPendingMessages: () => {\n\t\t\t\trunner.assertActive();\n\t\t\t\treturn runner.hasPendingMessagesFn();\n\t\t\t},\n\t\t\tshutdown: () => {\n\t\t\t\trunner.assertActive();\n\t\t\t\trunner.shutdownHandler();\n\t\t\t},\n\t\t\tgetContextUsage: () => {\n\t\t\t\trunner.assertActive();\n\t\t\t\treturn runner.getContextUsageFn();\n\t\t\t},\n\t\t\tcompact: (options) => {\n\t\t\t\trunner.assertActive();\n\t\t\t\trunner.compactFn(options);\n\t\t\t},\n\t\t\tgetSystemPrompt: () => {\n\t\t\t\trunner.assertActive();\n\t\t\t\treturn runner.getSystemPromptFn();\n\t\t\t},\n\t\t\trespondUI: (id, result) => {\n\t\t\t\trunner.assertActive();\n\t\t\t\trunner.respondUI(id, result);\n\t\t\t},\n\t\t};\n\t}\n\n\tcreateCommandContext(commandName?: string): ExtensionCommandContext {\n\t\tlet cmdExt: Extension | undefined;\n\t\tif (commandName) {\n\t\t\tfor (const ext of this.extensions) {\n\t\t\t\tif (ext.commands.has(commandName)) {\n\t\t\t\t\tcmdExt = ext;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tconst context = Object.defineProperties(\n\t\t\t{},\n\t\t\tObject.getOwnPropertyDescriptors(this.createContext(cmdExt)),\n\t\t) as ExtensionCommandContext;\n\t\tcontext.waitForIdle = () => {\n\t\t\tthis.assertActive();\n\t\t\treturn this.waitForIdleFn();\n\t\t};\n\t\tcontext.newSession = (options) => {\n\t\t\tthis.assertActive();\n\t\t\treturn this.newSessionHandler(options);\n\t\t};\n\t\tcontext.fork = (entryId, options) => {\n\t\t\tthis.assertActive();\n\t\t\treturn this.forkHandler(entryId, options);\n\t\t};\n\t\tcontext.navigateTree = (targetId, options) => {\n\t\t\tthis.assertActive();\n\t\t\treturn this.navigateTreeHandler(targetId, options);\n\t\t};\n\t\tcontext.switchSession = (sessionPath, options) => {\n\t\t\tthis.assertActive();\n\t\t\treturn this.switchSessionHandler(sessionPath, options);\n\t\t};\n\t\tcontext.reload = () => {\n\t\t\tthis.assertActive();\n\t\t\treturn this.reloadHandler();\n\t\t};\n\t\treturn context;\n\t}\n\n\tprivate isSessionBeforeEvent(event: RunnerEmitEvent): event is SessionBeforeEvent {\n\t\treturn (\n\t\t\tevent.type === \"session_before_switch\" ||\n\t\t\tevent.type === \"session_before_fork\" ||\n\t\t\tevent.type === \"session_before_compact\" ||\n\t\t\tevent.type === \"session_before_tree\"\n\t\t);\n\t}\n\n\tasync emit<TEvent extends RunnerEmitEvent>(event: TEvent): Promise<RunnerEmitResult<TEvent>> {\n\t\tlet result: SessionBeforeEventResult | undefined;\n\n\t\tfor (const ext of this.extensions) {\n\t\t\tconst handlers = ext.handlers.get(event.type);\n\t\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\t\tconst ctx = this.createContext(ext);\n\t\t\tfor (const handler of handlers) {\n\t\t\t\ttry {\n\t\t\t\t\tconst handlerResult = await handler(event, ctx);\n\n\t\t\t\t\tif (this.isSessionBeforeEvent(event) && handlerResult) {\n\t\t\t\t\t\tresult = handlerResult as SessionBeforeEventResult;\n\t\t\t\t\t\tif (result.cancel) {\n\t\t\t\t\t\t\treturn result as RunnerEmitResult<TEvent>;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\t\tconst stack = err instanceof Error ? err.stack : undefined;\n\t\t\t\t\tthis.emitError({\n\t\t\t\t\t\textensionPath: ext.path,\n\t\t\t\t\t\tevent: event.type,\n\t\t\t\t\t\terror: message,\n\t\t\t\t\t\tstack,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn result as RunnerEmitResult<TEvent>;\n\t}\n\n\tasync emitToolResult(event: ToolResultEvent): Promise<ToolResultEventResult | undefined> {\n\t\tconst currentEvent: ToolResultEvent = { ...event };\n\t\tlet modified = false;\n\n\t\tfor (const ext of this.extensions) {\n\t\t\tconst handlers = ext.handlers.get(\"tool_result\");\n\t\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\t\tconst ctx = this.createContext(ext);\n\t\t\tfor (const handler of handlers) {\n\t\t\t\ttry {\n\t\t\t\t\tconst handlerResult = (await handler(currentEvent, ctx)) as ToolResultEventResult | undefined;\n\t\t\t\t\tif (!handlerResult) continue;\n\n\t\t\t\t\tif (handlerResult.content !== undefined) {\n\t\t\t\t\t\tcurrentEvent.content = handlerResult.content;\n\t\t\t\t\t\tmodified = true;\n\t\t\t\t\t}\n\t\t\t\t\tif (handlerResult.details !== undefined) {\n\t\t\t\t\t\tcurrentEvent.details = handlerResult.details;\n\t\t\t\t\t\tmodified = true;\n\t\t\t\t\t}\n\t\t\t\t\tif (handlerResult.isError !== undefined) {\n\t\t\t\t\t\tcurrentEvent.isError = handlerResult.isError;\n\t\t\t\t\t\tmodified = true;\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\t\tconst stack = err instanceof Error ? err.stack : undefined;\n\t\t\t\t\tthis.emitError({\n\t\t\t\t\t\textensionPath: ext.path,\n\t\t\t\t\t\tevent: \"tool_result\",\n\t\t\t\t\t\terror: message,\n\t\t\t\t\t\tstack,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (!modified) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\treturn {\n\t\t\tcontent: currentEvent.content,\n\t\t\tdetails: currentEvent.details,\n\t\t\tisError: currentEvent.isError,\n\t\t};\n\t}\n\n\tasync emitToolCall(event: ToolCallEvent): Promise<ToolCallEventResult | undefined> {\n\t\tlet result: ToolCallEventResult | undefined;\n\n\t\tfor (const ext of this.extensions) {\n\t\t\tconst handlers = ext.handlers.get(\"tool_call\");\n\t\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\t\tconst ctx = this.createContext(ext);\n\t\t\tfor (const handler of handlers) {\n\t\t\t\tconst handlerResult = await handler(event, ctx);\n\n\t\t\t\tif (handlerResult) {\n\t\t\t\t\tresult = handlerResult as ToolCallEventResult;\n\t\t\t\t\tif (result.block) {\n\t\t\t\t\t\treturn result;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tasync emitUserBash(event: UserBashEvent): Promise<UserBashEventResult | undefined> {\n\t\tfor (const ext of this.extensions) {\n\t\t\tconst handlers = ext.handlers.get(\"user_bash\");\n\t\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\t\tconst ctx = this.createContext(ext);\n\t\t\tfor (const handler of handlers) {\n\t\t\t\ttry {\n\t\t\t\t\tconst handlerResult = await handler(event, ctx);\n\t\t\t\t\tif (handlerResult) {\n\t\t\t\t\t\treturn handlerResult as UserBashEventResult;\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\t\tconst stack = err instanceof Error ? err.stack : undefined;\n\t\t\t\t\tthis.emitError({\n\t\t\t\t\t\textensionPath: ext.path,\n\t\t\t\t\t\tevent: \"user_bash\",\n\t\t\t\t\t\terror: message,\n\t\t\t\t\t\tstack,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\tprivate createAsyncUIPromise<T>(id: string, extract: (result: UIEventResult) => T): Promise<T> | undefined {\n\t\tif (!this.hasHandlers(\"ui\")) return undefined;\n\t\treturn new Promise<T>((resolve) => {\n\t\t\tthis.pendingUIResponses.set(id, (result) => {\n\t\t\t\tthis.pendingUIResponses.delete(id);\n\t\t\t\tresolve(extract(result));\n\t\t\t});\n\t\t});\n\t}\n\n\tprivate wrapUIForInterception(original: ExtensionUIContext): ExtensionUIContext {\n\t\treturn {\n\t\t\t...original,\n\t\t\tconfirm: async (title, message, opts) => {\n\t\t\t\tif (!this.hasHandlers(\"ui\")) return original.confirm(title, message, opts);\n\t\t\t\tconst id = randomUUID();\n\t\t\t\tconst asyncPromise = this.createAsyncUIPromise<boolean>(id, (r) =>\n\t\t\t\t\tr?.action === \"responded\" && r.confirmed !== undefined ? r.confirmed : false,\n\t\t\t\t);\n\t\t\t\tconst result = await this.emitUIEvent<UIEventResult>({\n\t\t\t\t\ttype: \"ui\",\n\t\t\t\t\tid,\n\t\t\t\t\tmethod: \"confirm\",\n\t\t\t\t\ttitle,\n\t\t\t\t\tmessage,\n\t\t\t\t\tsignal: opts?.signal,\n\t\t\t\t\ttimeout: opts?.timeout,\n\t\t\t\t});\n\t\t\t\tif (result?.action === \"responded\" && result.confirmed !== undefined) {\n\t\t\t\t\tthis.pendingUIResponses.delete(id);\n\t\t\t\t\treturn result.confirmed;\n\t\t\t\t}\n\t\t\t\treturn Promise.race([original.confirm(title, message, opts), asyncPromise!]);\n\t\t\t},\n\t\t\tselect: async (title, options, opts) => {\n\t\t\t\tif (!this.hasHandlers(\"ui\")) return original.select(title, options, opts);\n\t\t\t\tconst id = randomUUID();\n\t\t\t\tconst multiple = opts?.multiple;\n\t\t\t\tconst asyncPromise = this.createAsyncUIPromise<string | string[] | undefined>(id, (r) =>\n\t\t\t\t\tr?.action === \"responded\" ? r.value : undefined,\n\t\t\t\t);\n\t\t\t\tconst result = await this.emitUIEvent<UIEventResult>({\n\t\t\t\t\ttype: \"ui\",\n\t\t\t\t\tid,\n\t\t\t\t\tmethod: \"select\",\n\t\t\t\t\ttitle,\n\t\t\t\t\toptions,\n\t\t\t\t\tmultiple,\n\t\t\t\t\tsignal: opts?.signal,\n\t\t\t\t\ttimeout: opts?.timeout,\n\t\t\t\t});\n\t\t\t\tif (result?.action === \"responded\") {\n\t\t\t\t\tthis.pendingUIResponses.delete(id);\n\t\t\t\t\tif (multiple && result.value !== undefined) {\n\t\t\t\t\t\treturn Array.isArray(result.value) ? result.value : [result.value];\n\t\t\t\t\t}\n\t\t\t\t\treturn result.value;\n\t\t\t\t}\n\t\t\t\treturn Promise.race([original.select(title, options, opts), asyncPromise!]);\n\t\t\t},\n\t\t\tinput: async (title, placeholder, opts) => {\n\t\t\t\tif (!this.hasHandlers(\"ui\")) return original.input(title, placeholder, opts);\n\t\t\t\tconst id = randomUUID();\n\t\t\t\tconst asyncPromise = this.createAsyncUIPromise<string | undefined>(id, (r) =>\n\t\t\t\t\tr?.action === \"responded\" ? r.value : undefined,\n\t\t\t\t);\n\t\t\t\tconst result = await this.emitUIEvent<UIEventResult>({\n\t\t\t\t\ttype: \"ui\",\n\t\t\t\t\tid,\n\t\t\t\t\tmethod: \"input\",\n\t\t\t\t\ttitle,\n\t\t\t\t\tplaceholder,\n\t\t\t\t\tsignal: opts?.signal,\n\t\t\t\t\ttimeout: opts?.timeout,\n\t\t\t\t});\n\t\t\t\tif (result?.action === \"responded\") {\n\t\t\t\t\tthis.pendingUIResponses.delete(id);\n\t\t\t\t\treturn result.value;\n\t\t\t\t}\n\t\t\t\treturn Promise.race([original.input(title, placeholder, opts), asyncPromise!]);\n\t\t\t},\n\t\t\teditor: async (title, prefill) => {\n\t\t\t\tif (!this.hasHandlers(\"ui\")) return original.editor(title, prefill);\n\t\t\t\tconst id = randomUUID();\n\t\t\t\tconst asyncPromise = this.createAsyncUIPromise<string | undefined>(id, (r) =>\n\t\t\t\t\tr?.action === \"responded\" ? r.value : undefined,\n\t\t\t\t);\n\t\t\t\tconst result = await this.emitUIEvent<UIEventResult>({\n\t\t\t\t\ttype: \"ui\",\n\t\t\t\t\tid,\n\t\t\t\t\tmethod: \"editor\",\n\t\t\t\t\ttitle,\n\t\t\t\t\tprefill,\n\t\t\t\t});\n\t\t\t\tif (result?.action === \"responded\") {\n\t\t\t\t\tthis.pendingUIResponses.delete(id);\n\t\t\t\t\treturn result.value;\n\t\t\t\t}\n\t\t\t\treturn Promise.race([original.editor(title, prefill), asyncPromise!]);\n\t\t\t},\n\t\t\tnotify: (message, notifyType) => {\n\t\t\t\tif (this.hasHandlers(\"ui\")) {\n\t\t\t\t\tthis.emitUIEvent<UIEventResult>({\n\t\t\t\t\t\ttype: \"ui\",\n\t\t\t\t\t\tid: randomUUID(),\n\t\t\t\t\t\tmethod: \"notify\",\n\t\t\t\t\t\ttitle: message,\n\t\t\t\t\t\tmessage,\n\t\t\t\t\t\tnotifyType,\n\t\t\t\t\t}).catch(() => {});\n\t\t\t\t}\n\t\t\t\treturn original.notify(message, notifyType);\n\t\t\t},\n\t\t};\n\t}\n\n\tprivate async emitUIEvent<TResult extends { action: \"responded\" } | undefined>(\n\t\tevent: UIEvent,\n\t): Promise<TResult | undefined> {\n\t\tfor (const ext of this.extensions) {\n\t\t\tconst ctx = this.createContext(ext);\n\t\t\tfor (const handler of ext.handlers.get(\"ui\") ?? []) {\n\t\t\t\ttry {\n\t\t\t\t\tconst result = (await handler(event, ctx)) as TResult | undefined;\n\t\t\t\t\tif (result?.action === \"responded\") return result;\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\t\tconst stack = err instanceof Error ? err.stack : undefined;\n\t\t\t\t\tthis.emitError({\n\t\t\t\t\t\textensionPath: ext.path,\n\t\t\t\t\t\tevent: \"ui\",\n\t\t\t\t\t\terror: message,\n\t\t\t\t\t\tstack,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\tasync emitUI(event: UIEvent): Promise<UIEventResult | undefined> {\n\t\treturn this.emitUIEvent<UIEventResult>(event);\n\t}\n\n\trespondUI(id: string, result: UIEventResult): void {\n\t\tconst resolve = this.pendingUIResponses.get(id);\n\t\tif (resolve) {\n\t\t\tthis.pendingUIResponses.delete(id);\n\t\t\tresolve(result);\n\t\t}\n\t}\n\n\tasync emitContext(messages: AgentMessage[]): Promise<AgentMessage[]> {\n\t\tlet currentMessages = structuredClone(messages);\n\n\t\tfor (const ext of this.extensions) {\n\t\t\tconst handlers = ext.handlers.get(\"context\");\n\t\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\t\tconst ctx = this.createContext(ext);\n\t\t\tfor (const handler of handlers) {\n\t\t\t\ttry {\n\t\t\t\t\tconst event: ContextEvent = { type: \"context\", messages: currentMessages };\n\t\t\t\t\tconst handlerResult = await handler(event, ctx);\n\n\t\t\t\t\tif (handlerResult && (handlerResult as ContextEventResult).messages) {\n\t\t\t\t\t\tcurrentMessages = (handlerResult as ContextEventResult).messages!;\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\t\tconst stack = err instanceof Error ? err.stack : undefined;\n\t\t\t\t\tthis.emitError({\n\t\t\t\t\t\textensionPath: ext.path,\n\t\t\t\t\t\tevent: \"context\",\n\t\t\t\t\t\terror: message,\n\t\t\t\t\t\tstack,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn currentMessages;\n\t}\n\n\tasync emitBeforeProviderRequest(payload: unknown): Promise<unknown> {\n\t\tlet currentPayload = payload;\n\n\t\tfor (const ext of this.extensions) {\n\t\t\tconst handlers = ext.handlers.get(\"before_provider_request\");\n\t\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\t\tconst ctx = this.createContext(ext);\n\t\t\tfor (const handler of handlers) {\n\t\t\t\ttry {\n\t\t\t\t\tconst event: BeforeProviderRequestEvent = {\n\t\t\t\t\t\ttype: \"before_provider_request\",\n\t\t\t\t\t\tpayload: currentPayload,\n\t\t\t\t\t};\n\t\t\t\t\tconst handlerResult = await handler(event, ctx);\n\t\t\t\t\tif (handlerResult !== undefined) {\n\t\t\t\t\t\tcurrentPayload = handlerResult;\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\t\tconst stack = err instanceof Error ? err.stack : undefined;\n\t\t\t\t\tthis.emitError({\n\t\t\t\t\t\textensionPath: ext.path,\n\t\t\t\t\t\tevent: \"before_provider_request\",\n\t\t\t\t\t\terror: message,\n\t\t\t\t\t\tstack,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn currentPayload;\n\t}\n\n\tasync emitBeforeAgentStart(\n\t\tprompt: string,\n\t\timages: ImageContent[] | undefined,\n\t\tsystemPrompt: string,\n\t\tsystemPromptOptions: BuildSystemPromptOptions,\n\t): Promise<BeforeAgentStartCombinedResult | undefined> {\n\t\tlet currentSystemPrompt = systemPrompt;\n\t\tconst messages: NonNullable<BeforeAgentStartEventResult[\"message\"]>[] = [];\n\t\tlet systemPromptModified = false;\n\n\t\tfor (const ext of this.extensions) {\n\t\t\tconst handlers = ext.handlers.get(\"before_agent_start\");\n\t\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\t\tconst ctx = Object.defineProperties(\n\t\t\t\t{},\n\t\t\t\tObject.getOwnPropertyDescriptors(this.createContext(ext)),\n\t\t\t) as ExtensionContext;\n\t\t\tctx.getSystemPrompt = () => {\n\t\t\t\tthis.assertActive();\n\t\t\t\treturn currentSystemPrompt;\n\t\t\t};\n\n\t\t\tfor (const handler of handlers) {\n\t\t\t\ttry {\n\t\t\t\t\tconst event: BeforeAgentStartEvent = {\n\t\t\t\t\t\ttype: \"before_agent_start\",\n\t\t\t\t\t\tprompt,\n\t\t\t\t\t\timages,\n\t\t\t\t\t\tsystemPrompt: currentSystemPrompt,\n\t\t\t\t\t\tsystemPromptOptions,\n\t\t\t\t\t};\n\t\t\t\t\tconst handlerResult = await handler(event, ctx);\n\n\t\t\t\t\tif (handlerResult) {\n\t\t\t\t\t\tconst result = handlerResult as BeforeAgentStartEventResult;\n\t\t\t\t\t\tif (result.message) {\n\t\t\t\t\t\t\tmessages.push(result.message);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (result.systemPrompt !== undefined) {\n\t\t\t\t\t\t\tcurrentSystemPrompt = result.systemPrompt;\n\t\t\t\t\t\t\tsystemPromptModified = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\t\tconst stack = err instanceof Error ? err.stack : undefined;\n\t\t\t\t\tthis.emitError({\n\t\t\t\t\t\textensionPath: ext.path,\n\t\t\t\t\t\tevent: \"before_agent_start\",\n\t\t\t\t\t\terror: message,\n\t\t\t\t\t\tstack,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (messages.length > 0 || systemPromptModified) {\n\t\t\treturn {\n\t\t\t\tmessages: messages.length > 0 ? messages : undefined,\n\t\t\t\tsystemPrompt: systemPromptModified ? currentSystemPrompt : undefined,\n\t\t\t};\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\tasync emitResourcesDiscover(\n\t\tcwd: string,\n\t\treason: ResourcesDiscoverEvent[\"reason\"],\n\t): Promise<{\n\t\tskillPaths: Array<{ path: string; extensionPath: string }>;\n\t\tpromptPaths: Array<{ path: string; extensionPath: string }>;\n\t\tthemePaths: Array<{ path: string; extensionPath: string }>;\n\t}> {\n\t\tconst skillPaths: Array<{ path: string; extensionPath: string }> = [];\n\t\tconst promptPaths: Array<{ path: string; extensionPath: string }> = [];\n\t\tconst themePaths: Array<{ path: string; extensionPath: string }> = [];\n\n\t\tfor (const ext of this.extensions) {\n\t\t\tconst handlers = ext.handlers.get(\"resources_discover\");\n\t\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\t\tconst ctx = this.createContext(ext);\n\t\t\tfor (const handler of handlers) {\n\t\t\t\ttry {\n\t\t\t\t\tconst event: ResourcesDiscoverEvent = { type: \"resources_discover\", cwd, reason };\n\t\t\t\t\tconst handlerResult = await handler(event, ctx);\n\t\t\t\t\tconst result = handlerResult as ResourcesDiscoverResult | undefined;\n\n\t\t\t\t\tif (result?.skillPaths?.length) {\n\t\t\t\t\t\tskillPaths.push(...result.skillPaths.map((path) => ({ path, extensionPath: ext.path })));\n\t\t\t\t\t}\n\t\t\t\t\tif (result?.promptPaths?.length) {\n\t\t\t\t\t\tpromptPaths.push(...result.promptPaths.map((path) => ({ path, extensionPath: ext.path })));\n\t\t\t\t\t}\n\t\t\t\t\tif (result?.themePaths?.length) {\n\t\t\t\t\t\tthemePaths.push(...result.themePaths.map((path) => ({ path, extensionPath: ext.path })));\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\t\tconst stack = err instanceof Error ? err.stack : undefined;\n\t\t\t\t\tthis.emitError({\n\t\t\t\t\t\textensionPath: ext.path,\n\t\t\t\t\t\tevent: \"resources_discover\",\n\t\t\t\t\t\terror: message,\n\t\t\t\t\t\tstack,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn { skillPaths, promptPaths, themePaths };\n\t}\n\n\t/** Emit input event. Transforms chain, \"handled\" short-circuits. */\n\tasync emitInput(text: string, images: ImageContent[] | undefined, source: InputSource): Promise<InputEventResult> {\n\t\tlet currentText = text;\n\t\tlet currentImages = images;\n\n\t\tfor (const ext of this.extensions) {\n\t\t\tconst ctx = this.createContext(ext);\n\t\t\tfor (const handler of ext.handlers.get(\"input\") ?? []) {\n\t\t\t\ttry {\n\t\t\t\t\tconst event: InputEvent = { type: \"input\", text: currentText, images: currentImages, source };\n\t\t\t\t\tconst result = (await handler(event, ctx)) as InputEventResult | undefined;\n\t\t\t\t\tif (result?.action === \"handled\") return result;\n\t\t\t\t\tif (result?.action === \"transform\") {\n\t\t\t\t\t\tcurrentText = result.text;\n\t\t\t\t\t\tcurrentImages = result.images ?? currentImages;\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tthis.emitError({\n\t\t\t\t\t\textensionPath: ext.path,\n\t\t\t\t\t\tevent: \"input\",\n\t\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t\t\tstack: err instanceof Error ? err.stack : undefined,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn currentText !== text || currentImages !== images\n\t\t\t? { action: \"transform\", text: currentText, images: currentImages }\n\t\t\t: { action: \"continue\" };\n\t}\n}\n"]}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
5
|
import { theme } from "../../modes/interactive/theme/theme.js";
|
|
6
|
+
import { getCwdDataDir, getGlobalDataDir, getProjectDataDir, getSessionDataDir, resolveProjectRoot } from "../storage.js";
|
|
6
7
|
// Extension shortcuts compete with canonical keybinding ids from keybindings.json.
|
|
7
8
|
// Only editor-global shortcuts are reserved here. Picker-specific bindings are not.
|
|
8
9
|
const RESERVED_KEYBINDINGS_FOR_EXTENSION_CONFLICTS = [
|
|
@@ -130,6 +131,7 @@ export class ExtensionRunner {
|
|
|
130
131
|
this.runtime.sendMessage = actions.sendMessage;
|
|
131
132
|
this.runtime.sendUserMessage = actions.sendUserMessage;
|
|
132
133
|
this.runtime.appendEntry = actions.appendEntry;
|
|
134
|
+
this.runtime.foldEntry = actions.foldEntry;
|
|
133
135
|
this.runtime.setSessionName = actions.setSessionName;
|
|
134
136
|
this.runtime.getSessionName = actions.getSessionName;
|
|
135
137
|
this.runtime.setLabel = actions.setLabel;
|
|
@@ -251,6 +253,14 @@ export class ExtensionRunner {
|
|
|
251
253
|
getExtensionPaths() {
|
|
252
254
|
return this.extensions.map((e) => e.path);
|
|
253
255
|
}
|
|
256
|
+
getExtensionNameByPath(extensionPath) {
|
|
257
|
+
for (const ext of this.extensions) {
|
|
258
|
+
if (ext.path === extensionPath) {
|
|
259
|
+
return ext.name;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return undefined;
|
|
263
|
+
}
|
|
254
264
|
/** Get all registered tools from all extensions (first registration per name wins). */
|
|
255
265
|
getAllRegisteredTools() {
|
|
256
266
|
const toolsByName = new Map();
|
|
@@ -411,7 +421,7 @@ export class ExtensionRunner {
|
|
|
411
421
|
* Create an ExtensionContext for use in event handlers and tool execution.
|
|
412
422
|
* Context values are resolved at call time, so changes via bindCore/bindUI are reflected.
|
|
413
423
|
*/
|
|
414
|
-
createContext() {
|
|
424
|
+
createContext(ext) {
|
|
415
425
|
const runner = this;
|
|
416
426
|
const getModel = this.getModel;
|
|
417
427
|
return {
|
|
@@ -427,6 +437,32 @@ export class ExtensionRunner {
|
|
|
427
437
|
runner.assertActive();
|
|
428
438
|
return runner.cwd;
|
|
429
439
|
},
|
|
440
|
+
get extensionName() {
|
|
441
|
+
runner.assertActive();
|
|
442
|
+
return ext?.name ?? "unknown";
|
|
443
|
+
},
|
|
444
|
+
get projectRoot() {
|
|
445
|
+
runner.assertActive();
|
|
446
|
+
return resolveProjectRoot(runner.cwd);
|
|
447
|
+
},
|
|
448
|
+
get sessionDataDir() {
|
|
449
|
+
runner.assertActive();
|
|
450
|
+
const sessionDir = runner.sessionManager.getSessionDir();
|
|
451
|
+
const sessionId = runner.sessionManager.getSessionId();
|
|
452
|
+
return getSessionDataDir(sessionDir, sessionId, ext?.name ?? "unknown");
|
|
453
|
+
},
|
|
454
|
+
get projectDataDir() {
|
|
455
|
+
runner.assertActive();
|
|
456
|
+
return getProjectDataDir(resolveProjectRoot(runner.cwd), ext?.name ?? "unknown");
|
|
457
|
+
},
|
|
458
|
+
get cwdDataDir() {
|
|
459
|
+
runner.assertActive();
|
|
460
|
+
return getCwdDataDir(runner.cwd, ext?.name ?? "unknown");
|
|
461
|
+
},
|
|
462
|
+
get globalDataDir() {
|
|
463
|
+
runner.assertActive();
|
|
464
|
+
return getGlobalDataDir(ext?.name ?? "unknown");
|
|
465
|
+
},
|
|
430
466
|
get sessionManager() {
|
|
431
467
|
runner.assertActive();
|
|
432
468
|
return runner.sessionManager;
|
|
@@ -481,11 +517,17 @@ export class ExtensionRunner {
|
|
|
481
517
|
},
|
|
482
518
|
};
|
|
483
519
|
}
|
|
484
|
-
createCommandContext() {
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
520
|
+
createCommandContext(commandName) {
|
|
521
|
+
let cmdExt;
|
|
522
|
+
if (commandName) {
|
|
523
|
+
for (const ext of this.extensions) {
|
|
524
|
+
if (ext.commands.has(commandName)) {
|
|
525
|
+
cmdExt = ext;
|
|
526
|
+
break;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
const context = Object.defineProperties({}, Object.getOwnPropertyDescriptors(this.createContext(cmdExt)));
|
|
489
531
|
context.waitForIdle = () => {
|
|
490
532
|
this.assertActive();
|
|
491
533
|
return this.waitForIdleFn();
|
|
@@ -519,12 +561,12 @@ export class ExtensionRunner {
|
|
|
519
561
|
event.type === "session_before_tree");
|
|
520
562
|
}
|
|
521
563
|
async emit(event) {
|
|
522
|
-
const ctx = this.createContext();
|
|
523
564
|
let result;
|
|
524
565
|
for (const ext of this.extensions) {
|
|
525
566
|
const handlers = ext.handlers.get(event.type);
|
|
526
567
|
if (!handlers || handlers.length === 0)
|
|
527
568
|
continue;
|
|
569
|
+
const ctx = this.createContext(ext);
|
|
528
570
|
for (const handler of handlers) {
|
|
529
571
|
try {
|
|
530
572
|
const handlerResult = await handler(event, ctx);
|
|
@@ -550,13 +592,13 @@ export class ExtensionRunner {
|
|
|
550
592
|
return result;
|
|
551
593
|
}
|
|
552
594
|
async emitToolResult(event) {
|
|
553
|
-
const ctx = this.createContext();
|
|
554
595
|
const currentEvent = { ...event };
|
|
555
596
|
let modified = false;
|
|
556
597
|
for (const ext of this.extensions) {
|
|
557
598
|
const handlers = ext.handlers.get("tool_result");
|
|
558
599
|
if (!handlers || handlers.length === 0)
|
|
559
600
|
continue;
|
|
601
|
+
const ctx = this.createContext(ext);
|
|
560
602
|
for (const handler of handlers) {
|
|
561
603
|
try {
|
|
562
604
|
const handlerResult = (await handler(currentEvent, ctx));
|
|
@@ -597,12 +639,12 @@ export class ExtensionRunner {
|
|
|
597
639
|
};
|
|
598
640
|
}
|
|
599
641
|
async emitToolCall(event) {
|
|
600
|
-
const ctx = this.createContext();
|
|
601
642
|
let result;
|
|
602
643
|
for (const ext of this.extensions) {
|
|
603
644
|
const handlers = ext.handlers.get("tool_call");
|
|
604
645
|
if (!handlers || handlers.length === 0)
|
|
605
646
|
continue;
|
|
647
|
+
const ctx = this.createContext(ext);
|
|
606
648
|
for (const handler of handlers) {
|
|
607
649
|
const handlerResult = await handler(event, ctx);
|
|
608
650
|
if (handlerResult) {
|
|
@@ -616,11 +658,11 @@ export class ExtensionRunner {
|
|
|
616
658
|
return result;
|
|
617
659
|
}
|
|
618
660
|
async emitUserBash(event) {
|
|
619
|
-
const ctx = this.createContext();
|
|
620
661
|
for (const ext of this.extensions) {
|
|
621
662
|
const handlers = ext.handlers.get("user_bash");
|
|
622
663
|
if (!handlers || handlers.length === 0)
|
|
623
664
|
continue;
|
|
665
|
+
const ctx = this.createContext(ext);
|
|
624
666
|
for (const handler of handlers) {
|
|
625
667
|
try {
|
|
626
668
|
const handlerResult = await handler(event, ctx);
|
|
@@ -754,8 +796,8 @@ export class ExtensionRunner {
|
|
|
754
796
|
};
|
|
755
797
|
}
|
|
756
798
|
async emitUIEvent(event) {
|
|
757
|
-
const ctx = this.createContext();
|
|
758
799
|
for (const ext of this.extensions) {
|
|
800
|
+
const ctx = this.createContext(ext);
|
|
759
801
|
for (const handler of ext.handlers.get("ui") ?? []) {
|
|
760
802
|
try {
|
|
761
803
|
const result = (await handler(event, ctx));
|
|
@@ -787,12 +829,12 @@ export class ExtensionRunner {
|
|
|
787
829
|
}
|
|
788
830
|
}
|
|
789
831
|
async emitContext(messages) {
|
|
790
|
-
const ctx = this.createContext();
|
|
791
832
|
let currentMessages = structuredClone(messages);
|
|
792
833
|
for (const ext of this.extensions) {
|
|
793
834
|
const handlers = ext.handlers.get("context");
|
|
794
835
|
if (!handlers || handlers.length === 0)
|
|
795
836
|
continue;
|
|
837
|
+
const ctx = this.createContext(ext);
|
|
796
838
|
for (const handler of handlers) {
|
|
797
839
|
try {
|
|
798
840
|
const event = { type: "context", messages: currentMessages };
|
|
@@ -816,12 +858,12 @@ export class ExtensionRunner {
|
|
|
816
858
|
return currentMessages;
|
|
817
859
|
}
|
|
818
860
|
async emitBeforeProviderRequest(payload) {
|
|
819
|
-
const ctx = this.createContext();
|
|
820
861
|
let currentPayload = payload;
|
|
821
862
|
for (const ext of this.extensions) {
|
|
822
863
|
const handlers = ext.handlers.get("before_provider_request");
|
|
823
864
|
if (!handlers || handlers.length === 0)
|
|
824
865
|
continue;
|
|
866
|
+
const ctx = this.createContext(ext);
|
|
825
867
|
for (const handler of handlers) {
|
|
826
868
|
try {
|
|
827
869
|
const event = {
|
|
@@ -849,17 +891,17 @@ export class ExtensionRunner {
|
|
|
849
891
|
}
|
|
850
892
|
async emitBeforeAgentStart(prompt, images, systemPrompt, systemPromptOptions) {
|
|
851
893
|
let currentSystemPrompt = systemPrompt;
|
|
852
|
-
const ctx = Object.defineProperties({}, Object.getOwnPropertyDescriptors(this.createContext()));
|
|
853
|
-
ctx.getSystemPrompt = () => {
|
|
854
|
-
this.assertActive();
|
|
855
|
-
return currentSystemPrompt;
|
|
856
|
-
};
|
|
857
894
|
const messages = [];
|
|
858
895
|
let systemPromptModified = false;
|
|
859
896
|
for (const ext of this.extensions) {
|
|
860
897
|
const handlers = ext.handlers.get("before_agent_start");
|
|
861
898
|
if (!handlers || handlers.length === 0)
|
|
862
899
|
continue;
|
|
900
|
+
const ctx = Object.defineProperties({}, Object.getOwnPropertyDescriptors(this.createContext(ext)));
|
|
901
|
+
ctx.getSystemPrompt = () => {
|
|
902
|
+
this.assertActive();
|
|
903
|
+
return currentSystemPrompt;
|
|
904
|
+
};
|
|
863
905
|
for (const handler of handlers) {
|
|
864
906
|
try {
|
|
865
907
|
const event = {
|
|
@@ -902,7 +944,6 @@ export class ExtensionRunner {
|
|
|
902
944
|
return undefined;
|
|
903
945
|
}
|
|
904
946
|
async emitResourcesDiscover(cwd, reason) {
|
|
905
|
-
const ctx = this.createContext();
|
|
906
947
|
const skillPaths = [];
|
|
907
948
|
const promptPaths = [];
|
|
908
949
|
const themePaths = [];
|
|
@@ -910,6 +951,7 @@ export class ExtensionRunner {
|
|
|
910
951
|
const handlers = ext.handlers.get("resources_discover");
|
|
911
952
|
if (!handlers || handlers.length === 0)
|
|
912
953
|
continue;
|
|
954
|
+
const ctx = this.createContext(ext);
|
|
913
955
|
for (const handler of handlers) {
|
|
914
956
|
try {
|
|
915
957
|
const event = { type: "resources_discover", cwd, reason };
|
|
@@ -941,10 +983,10 @@ export class ExtensionRunner {
|
|
|
941
983
|
}
|
|
942
984
|
/** Emit input event. Transforms chain, "handled" short-circuits. */
|
|
943
985
|
async emitInput(text, images, source) {
|
|
944
|
-
const ctx = this.createContext();
|
|
945
986
|
let currentText = text;
|
|
946
987
|
let currentImages = images;
|
|
947
988
|
for (const ext of this.extensions) {
|
|
989
|
+
const ctx = this.createContext(ext);
|
|
948
990
|
for (const handler of ext.handlers.get("input") ?? []) {
|
|
949
991
|
try {
|
|
950
992
|
const event = { type: "input", text: currentText, images: currentImages, source };
|