@greppleai/grip 0.0.1 → 0.1.4
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 +114 -0
- package/README.md +13 -7
- package/dist/bun/cli.d.ts.map +1 -1
- package/dist/bun/cli.js +4 -2
- package/dist/bun/cli.js.map +1 -1
- package/dist/bun/embedded-assets.d.ts +3 -0
- package/dist/bun/embedded-assets.d.ts.map +1 -0
- package/dist/bun/embedded-assets.js +52 -0
- package/dist/bun/embedded-assets.js.map +1 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +1 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -3
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +17 -2
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/types.d.ts +5 -1
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/remote-workspace-protocol/contract.d.ts +13 -4
- package/dist/core/remote-workspace-protocol/contract.d.ts.map +1 -1
- package/dist/core/remote-workspace-protocol/contract.js +17 -1
- package/dist/core/remote-workspace-protocol/contract.js.map +1 -1
- package/dist/core/remote-workspace-protocol/guest-shared-client.d.ts +16 -0
- package/dist/core/remote-workspace-protocol/guest-shared-client.d.ts.map +1 -0
- package/dist/core/remote-workspace-protocol/guest-shared-client.js +54 -0
- package/dist/core/remote-workspace-protocol/guest-shared-client.js.map +1 -0
- package/dist/core/remote-workspace-protocol/guest-shared-outbound-scheduler.d.ts +23 -0
- package/dist/core/remote-workspace-protocol/guest-shared-outbound-scheduler.d.ts.map +1 -0
- package/dist/core/remote-workspace-protocol/guest-shared-outbound-scheduler.js +112 -0
- package/dist/core/remote-workspace-protocol/guest-shared-outbound-scheduler.js.map +1 -0
- package/dist/core/remote-workspace-protocol/guest-shared-uplink.d.ts +30 -0
- package/dist/core/remote-workspace-protocol/guest-shared-uplink.d.ts.map +1 -0
- package/dist/core/remote-workspace-protocol/guest-shared-uplink.js +243 -0
- package/dist/core/remote-workspace-protocol/guest-shared-uplink.js.map +1 -0
- package/dist/core/remote-workspace-protocol/lima-owner-directory.d.ts +3 -0
- package/dist/core/remote-workspace-protocol/lima-owner-directory.d.ts.map +1 -0
- package/dist/core/remote-workspace-protocol/lima-owner-directory.js +31 -0
- package/dist/core/remote-workspace-protocol/lima-owner-directory.js.map +1 -0
- package/dist/core/remote-workspace-protocol/lima-owner-lock.d.ts +9 -0
- package/dist/core/remote-workspace-protocol/lima-owner-lock.d.ts.map +1 -0
- package/dist/core/remote-workspace-protocol/lima-owner-lock.js +89 -0
- package/dist/core/remote-workspace-protocol/lima-owner-lock.js.map +1 -0
- package/dist/core/remote-workspace-protocol/session.d.ts +2 -0
- package/dist/core/remote-workspace-protocol/session.d.ts.map +1 -1
- package/dist/core/remote-workspace-protocol/session.js +3 -0
- package/dist/core/remote-workspace-protocol/session.js.map +1 -1
- package/dist/core/remote-workspace-protocol/shared-transport.d.ts +71 -0
- package/dist/core/remote-workspace-protocol/shared-transport.d.ts.map +1 -0
- package/dist/core/remote-workspace-protocol/shared-transport.js +68 -0
- package/dist/core/remote-workspace-protocol/shared-transport.js.map +1 -0
- package/dist/core/sandbox/command.d.ts +2 -4
- package/dist/core/sandbox/command.d.ts.map +1 -1
- package/dist/core/sandbox/command.js +24 -46
- package/dist/core/sandbox/command.js.map +1 -1
- package/dist/core/sandbox/config.d.ts +91 -0
- package/dist/core/sandbox/config.d.ts.map +1 -0
- package/dist/core/sandbox/config.js +88 -0
- package/dist/core/sandbox/config.js.map +1 -0
- package/dist/core/sandbox/lima-guest-binary.d.ts +11 -0
- package/dist/core/sandbox/lima-guest-binary.d.ts.map +1 -0
- package/dist/core/sandbox/lima-guest-binary.js +140 -0
- package/dist/core/sandbox/lima-guest-binary.js.map +1 -0
- package/dist/core/sandbox/lima.d.ts +22 -3
- package/dist/core/sandbox/lima.d.ts.map +1 -1
- package/dist/core/sandbox/lima.js +434 -42
- package/dist/core/sandbox/lima.js.map +1 -1
- package/dist/core/sandbox/shared-lima-roots.d.ts +11 -0
- package/dist/core/sandbox/shared-lima-roots.d.ts.map +1 -0
- package/dist/core/sandbox/shared-lima-roots.js +25 -0
- package/dist/core/sandbox/shared-lima-roots.js.map +1 -0
- package/dist/core/sandbox/update-shared-guest-binary.d.ts +8 -0
- package/dist/core/sandbox/update-shared-guest-binary.d.ts.map +1 -0
- package/dist/core/sandbox/update-shared-guest-binary.js +100 -0
- package/dist/core/sandbox/update-shared-guest-binary.js.map +1 -0
- package/dist/core/settings-manager.d.ts +1 -5
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +25 -14
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/file-mutation-queue.d.ts +2 -0
- package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
- package/dist/core/tools/file-mutation-queue.js +9 -0
- package/dist/core/tools/file-mutation-queue.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +2 -3
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +2 -3
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +2 -3
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/operations.d.ts +13 -2
- package/dist/core/tools/operations.d.ts.map +1 -1
- package/dist/core/tools/operations.js +132 -36
- package/dist/core/tools/operations.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +3 -3
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/render-utils.d.ts +1 -0
- package/dist/core/tools/render-utils.d.ts.map +1 -1
- package/dist/core/tools/render-utils.js +5 -0
- package/dist/core/tools/render-utils.js.map +1 -1
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +2 -3
- package/dist/core/tools/write.js.map +1 -1
- package/dist/daemon/activity-budget.d.ts +10 -0
- package/dist/daemon/activity-budget.d.ts.map +1 -0
- package/dist/daemon/activity-budget.js +34 -0
- package/dist/daemon/activity-budget.js.map +1 -0
- package/dist/daemon/config.d.ts +2 -0
- package/dist/daemon/config.d.ts.map +1 -1
- package/dist/daemon/config.js +15 -0
- package/dist/daemon/config.js.map +1 -1
- package/dist/daemon/outbound-budget.d.ts +10 -0
- package/dist/daemon/outbound-budget.d.ts.map +1 -0
- package/dist/daemon/outbound-budget.js +30 -0
- package/dist/daemon/outbound-budget.js.map +1 -0
- package/dist/daemon/security.d.ts.map +1 -1
- package/dist/daemon/security.js +3 -1
- package/dist/daemon/security.js.map +1 -1
- package/dist/daemon/server.d.ts.map +1 -1
- package/dist/daemon/server.js +104 -24
- package/dist/daemon/server.js.map +1 -1
- package/dist/daemon/shared-connection.d.ts +33 -0
- package/dist/daemon/shared-connection.d.ts.map +1 -0
- package/dist/daemon/shared-connection.js +271 -0
- package/dist/daemon/shared-connection.js.map +1 -0
- package/dist/daemon/shared-revision.d.ts +5 -0
- package/dist/daemon/shared-revision.d.ts.map +1 -0
- package/dist/daemon/shared-revision.js +61 -0
- package/dist/daemon/shared-revision.js.map +1 -0
- package/dist/daemon/shared-roots.d.ts +9 -0
- package/dist/daemon/shared-roots.d.ts.map +1 -0
- package/dist/daemon/shared-roots.js +33 -0
- package/dist/daemon/shared-roots.js.map +1 -0
- package/dist/daemon/workspace-runtime.d.ts +1 -1
- package/dist/daemon/workspace-runtime.d.ts.map +1 -1
- package/dist/daemon/workspace-runtime.js +42 -10
- package/dist/daemon/workspace-runtime.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +5 -3
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts +1 -0
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +13 -3
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts +4 -3
- package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.js +5 -4
- package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts +4 -3
- package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.js +5 -4
- package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-message.js +4 -3
- package/dist/modes/interactive/components/custom-message.js.map +1 -1
- package/dist/modes/interactive/components/hook-execution.d.ts +3 -0
- package/dist/modes/interactive/components/hook-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/hook-execution.js +20 -2
- package/dist/modes/interactive/components/hook-execution.js.map +1 -1
- package/dist/modes/interactive/components/message-card.d.ts +9 -0
- package/dist/modes/interactive/components/message-card.d.ts.map +1 -0
- package/dist/modes/interactive/components/message-card.js +24 -0
- package/dist/modes/interactive/components/message-card.js.map +1 -0
- package/dist/modes/interactive/components/skill-invocation-message.d.ts +4 -3
- package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.js +5 -4
- package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +27 -21
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message.js +3 -2
- package/dist/modes/interactive/components/user-message.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +1 -3
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +106 -47
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/theme.d.ts +3 -0
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +10 -0
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/utils/clipboard-native.d.ts.map +1 -1
- package/dist/utils/clipboard-native.js +1 -1
- package/dist/utils/clipboard-native.js.map +1 -1
- package/dist/utils/image-resize.d.ts.map +1 -1
- package/dist/utils/image-resize.js +5 -0
- package/dist/utils/image-resize.js.map +1 -1
- package/dist/utils/photon.d.ts.map +1 -1
- package/dist/utils/photon.js +1 -0
- package/dist/utils/photon.js.map +1 -1
- package/docs/daemon.md +6 -4
- package/docs/docs.json +1 -1
- package/docs/extensions.md +3 -0
- package/docs/index.md +8 -12
- package/docs/packages.md +3 -3
- package/docs/quickstart.md +11 -19
- package/docs/sandbox.md +40 -117
- package/docs/settings.md +12 -11
- package/docs/termux.md +1 -18
- package/docs/usage.md +2 -2
- package/examples/README.md +0 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +12 -12
- package/package.json +5 -6
- package/dist/core/sandbox/docker.d.ts +0 -169
- package/dist/core/sandbox/docker.d.ts.map +0 -1
- package/dist/core/sandbox/docker.js +0 -633
- package/dist/core/sandbox/docker.js.map +0 -1
- package/examples/sandbox-docker/Dockerfile +0 -274
- package/examples/sandbox-docker/README.md +0 -88
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-session.d.ts","sourceRoot":"","sources":["../../src/core/agent-session.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * AgentSession - Core abstraction for agent lifecycle and session management.\n *\n * This class is shared between all run modes (interactive, print, rpc).\n * It encapsulates:\n * - Agent state access\n * - Event subscription with automatic session persistence\n * - Model and thinking level management\n * - Compaction (manual and auto)\n * - Bash execution\n * - Tree navigation and branching\n *\n * Modes use this class and add their own I/O layer on top.\n */\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport { randomUUID } from \"node:crypto\";\nimport { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { basename, dirname, posix, sep, win32 } from \"node:path\";\nimport type {\n\tAfterToolCallContext,\n\tAfterToolCallResult,\n\tAgent,\n\tAgentContext,\n\tAgentEvent,\n\tAgentLoopTurnUpdate,\n\tAgentMessage,\n\tAgentState,\n\tAgentTool,\n\tBeforeToolCallContext,\n\tBeforeToolCallResult,\n\tCompactionSettings,\n\tPrepareNextTurnContext,\n\tQueueMode,\n\tThinkingLevel,\n} from \"@greppleai/grip-agent-core\";\nimport type {\n\tApi,\n\tAssistantMessage,\n\tImageContent,\n\tMessage,\n\tModel,\n\tProviderHeaders,\n\tRetryCallbacks,\n\tTextContent,\n\tTool,\n\tToolCall,\n\tToolResultMessage,\n\tUserMessage,\n} from \"@greppleai/grip-ai\";\nimport {\n\tclampThinkingLevel,\n\tcleanupSessionResources,\n\tgetSupportedThinkingLevels,\n\tisContextOverflow,\n\tisRetryableAssistantError,\n\tmodelsAreEqual,\n\tresetApiProviders,\n\tstreamSimple,\n\tvalidateToolArguments,\n} from \"@greppleai/grip-ai\";\nimport { Text } from \"@greppleai/grip-tui\";\nimport { type Static, type TSchema, Type } from \"typebox\";\nimport { theme } from \"../modes/interactive/theme/theme.ts\";\nimport { stripFrontmatter } from \"../utils/frontmatter.ts\";\nimport { canonicalizePath, resolvePath } from \"../utils/paths.ts\";\nimport { sleep } from \"../utils/sleep.ts\";\nimport { formatNoApiKeyFoundMessage, formatNoModelSelectedMessage } from \"./auth-guidance.ts\";\nimport { type BashResult, executeBashWithOperations } from \"./bash-executor.ts\";\nimport { collectEntriesForBranchSummary, generateBranchSummary } from \"./compaction/branch-summarization.ts\";\nimport {\n\ttype CompactionPreparation,\n\ttype CompactionResult,\n\tcalculateContextTokens,\n\tcompact,\n\testimateContextTokens,\n\tprepareCompaction,\n\tshouldCompact,\n} from \"./compaction/compaction.ts\";\nimport {\n\tappendCompressionDetectionEvent,\n\treadCompressionDetectionLedger,\n} from \"./compaction/compression-detection-ledger.ts\";\nimport {\n\ttype CompressionDetectionCounts,\n\ttype CompressionDetectionVerdict,\n\tCompressionDetector,\n} from \"./compaction/compression-detector.ts\";\nimport {\n\tCOMPRESSION_ECONOMICS_ENTRY_TYPE,\n\tCOMPRESSION_SAVINGS_SNAPSHOT_TYPE,\n\tcreateCompressionSavingsSnapshot,\n\testimateCompressionEconomics,\n} from \"./compaction/compression-economics.ts\";\nimport { DEFAULT_THINKING_LEVEL } from \"./defaults.ts\";\nimport { exportSessionToHtml, type ToolHtmlRenderer } from \"./export-html/index.ts\";\nimport { createToolHtmlRenderer } from \"./export-html/tool-renderer.ts\";\nimport {\n\ttype ExtensionErrorListener,\n\tExtensionRunner,\n\temitSessionShutdownEvent,\n\ttype ShutdownHandler,\n} from \"./extensions/runner.ts\";\nimport type {\n\tCompactOptions,\n\tContextUsage,\n\tExtensionCommandContextActions,\n\tExtensionNavigateTreeOptions,\n\tExtensionSendMessageOptions,\n\tExtensionUIContext,\n\tInputSource,\n\tMessageEndEvent,\n\tMessageStartEvent,\n\tMessageUpdateEvent,\n\tModelSelectSource,\n\tReplacedSessionContext,\n\tResourcesDiscoverReason,\n\tSessionBeforeCompactResult,\n\tSessionBeforeTreeResult,\n\tSessionBeforeTreeSummary,\n\tSessionStartEvent,\n\tStreamingBehavior,\n\tToolDefinition,\n\tToolExecutionEndEvent,\n\tToolExecutionStartEvent,\n\tToolExecutionUpdateEvent,\n\tToolInfo,\n\tTreePreparation,\n\tTurnEndEvent,\n\tTurnStartEvent,\n} from \"./extensions/types.ts\";\nimport { wrapRegisteredTools } from \"./extensions/wrapper.ts\";\nimport { capModelVisibleHookField, runHooks } from \"./hooks/index.ts\";\nimport { matchHookValue } from \"./hooks/matcher.ts\";\nimport type {\n\tHookAggregateResult,\n\tHookDiagnostic,\n\tHookEventName,\n\tHookExecutionActivityListener,\n\tHookExecutionListener,\n\tHookExecutionNotice,\n\tHookExecutionResult,\n\tHookInput,\n\tHookRunOptions,\n\tJsonObject,\n\tJsonValue,\n\tLoadedHooks,\n\tSessionEndReason,\n} from \"./hooks/types.ts\";\nimport { HOOK_EXECUTION_CUSTOM_TYPE } from \"./hooks/types.ts\";\nimport type { InstructionResource } from \"./instruction-resource-loader.ts\";\nimport {\n\ttype LspConfigurationLayer,\n\tparseLspConfiguration,\n\ttype ResolvedLspConfiguration,\n\tresolveLspConfiguration,\n} from \"./lsp/config.ts\";\nimport {\n\ttype LoadLspConfigurationResult,\n\ttype LspConfigurationSourceDiagnostic,\n\tresolveLspConfigurationLayerPaths,\n} from \"./lsp/config-loader.ts\";\nimport { formatAutoDiagnosticsForChangedFile } from \"./lsp/diagnostics.ts\";\nimport {\n\tcreateLspRuntimeState,\n\tcreateLspToolDefinitions,\n\tLSP_TOOL_NAMES,\n\ttype LspRuntimeState,\n\ttype LspSessionStatus,\n} from \"./lsp/integration.ts\";\nimport type { LspConnectionFactoryRegistry } from \"./lsp/transport.ts\";\nimport {\n\ttype BashExecutionMessage,\n\ttype CustomMessage,\n\tcreateCustomMessage,\n\tnormalizeMessageContent,\n\tSTRUCTURED_RESPONSE_INTERNAL_CUSTOM_TYPE,\n} from \"./messages.ts\";\nimport type { ModelRegistry } from \"./model-registry.ts\";\nimport type { ScopedModel } from \"./model-resolver.ts\";\nimport { expandPromptTemplate, type PromptTemplate } from \"./prompt-templates.ts\";\nimport type { ResourceExtensionPaths, ResourceLoader, ResourcePathEntry } from \"./resource-loader.ts\";\nimport type { Rule } from \"./rules.ts\";\nimport { CURRENT_SESSION_VERSION } from \"./session/constants.ts\";\nimport { getLatestCompactionEntry, projectSessionContextEntries } from \"./session/context.ts\";\nimport { annotateContextMetadata } from \"./session/context-metadata.ts\";\nimport type { Session } from \"./session/session.ts\";\nimport type {\n\tBranchSummaryEntry,\n\tCompactionEntry,\n\tSessionContext,\n\tSessionEntry,\n\tSessionHeader,\n} from \"./session/types.ts\";\nimport { readUsageLedger, USAGE_LEDGER_ENTRY_TYPE } from \"./session/usage-ledger.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\nimport type { Skill } from \"./skills.ts\";\nimport { HIDDEN_BUILTIN_SLASH_COMMAND_NAMES, type SlashCommandInfo } from \"./slash-commands.ts\";\nimport { createSyntheticSourceInfo, getSourceBackend, type SourceInfo } from \"./source-info.ts\";\nimport { type BuildSystemPromptOptions, buildSystemPrompt } from \"./system-prompt.ts\";\nimport { createLocalBashOperations } from \"./tools/bash.ts\";\nimport {\n\tcreateCompressContextToolDefinition,\n\tgetCompressibleEntries,\n\tgetSuggestedCompressionRangeError,\n\tisValidSuggestedCompressionRange,\n\ttype PendingContextCompression,\n} from \"./tools/compress-context.ts\";\nimport {\n\tDeferredRemoteToolOperations,\n\tLocalToolOperations,\n\ttype ToolBackendInfo,\n\ttype ToolOperations,\n} from \"./tools/operations.ts\";\nimport type { ReadToolOperationsSelection } from \"./tools/read.ts\";\nimport { createSessionEntryGetToolDefinition, createSessionSearchToolDefinition } from \"./tools/session-history.ts\";\nimport {\n\tcreateCreateSubagentToolDefinition,\n\tcreateSubagentRunsToolDefinition,\n\tcreateSubagentToolDefinition,\n\tformatSubagentModelCatalog,\n\ttype SubagentConfigRegistry,\n\ttype SubagentModelHint,\n\ttype SubagentRunner,\n\ttype SubagentRunRegistry,\n} from \"./tools/subagent.ts\";\nimport { createToolDefinitionFromAgentTool } from \"./tools/tool-definition-wrapper.ts\";\nimport { createWebsearchToolDefinition, parseWebsearchToolOptions } from \"./tools/websearch.ts\";\nimport { WORKSPACE_TOOL_NAMES, WorkspaceToolHost } from \"./tools/workspace-tool-host.ts\";\n\n// ============================================================================\n// Skill Block Parsing\n// ============================================================================\n\nconst CORE_DEFAULT_TOOL_NAMES = [\"read\", \"bash\", \"edit\", \"write\", \"websearch\"] as const;\nconst SESSION_HISTORY_TOOL_NAMES = [\"session_search\", \"session_entry_get\"] as const;\nexport const SUBAGENT_TOOL_NAMES = [\"subagent\", \"subagent_runs\", \"create_subagent\"] as const;\nexport const DEFAULT_ACTIVE_TOOL_NAMES = [...CORE_DEFAULT_TOOL_NAMES, ...SESSION_HISTORY_TOOL_NAMES] as const;\n\nexport function getDefaultActiveToolNames(enableSubagents = false, enableLspTools = false): string[] {\n\treturn [\n\t\t...DEFAULT_ACTIVE_TOOL_NAMES,\n\t\t...(process.env.GRIP_CONTEXT_COMPRESSION === \"1\" || process.env.GRIP_CONTEXT_COMPRESSION_DETECTION === \"1\"\n\t\t\t? [\"compress_context\"]\n\t\t\t: []),\n\t\t...(enableLspTools ? LSP_TOOL_NAMES : []),\n\t\t...(enableSubagents ? SUBAGENT_TOOL_NAMES : []),\n\t];\n}\n\n// ============================================================================\n\n/** Parsed skill block from a user message */\nexport interface ParsedSkillBlock {\n\tname: string;\n\tlocation: string;\n\tcontent: string;\n\tuserMessage: string | undefined;\n}\n\n/**\n * Parse a skill block from message text.\n * Returns null if the text doesn't contain a skill block.\n */\nexport function parseSkillBlock(text: string): ParsedSkillBlock | null {\n\tconst match = text.match(/^<skill name=\"([^\"]+)\" location=\"([^\"]+)\">\\n([\\s\\S]*?)\\n<\\/skill>(?:\\n\\n([\\s\\S]+))?$/);\n\tif (!match) return null;\n\treturn {\n\t\tname: match[1],\n\t\tlocation: match[2],\n\t\tcontent: match[3],\n\t\tuserMessage: match[4]?.trim() || undefined,\n\t};\n}\n\n/** Session-specific events that extend the core AgentEvent */\nexport type AgentSessionEvent =\n\t| Exclude<AgentEvent, { type: \"agent_end\" }>\n\t| {\n\t\t\ttype: \"agent_end\";\n\t\t\tmessages: AgentMessage[];\n\t\t\twillRetry: boolean;\n\t }\n\t| {\n\t\t\ttype: \"queue_update\";\n\t\t\tsteering: readonly string[];\n\t\t\tfollowUp: readonly string[];\n\t }\n\t| { type: \"compaction_start\"; reason: \"manual\" | \"threshold\" | \"overflow\" }\n\t| { type: \"state_compressed\" }\n\t| { type: \"compression_detection_activity\"; active: boolean }\n\t| { type: \"compression_detection_result\"; verdict: CompressionDetectionVerdict }\n\t| { type: \"session_info_changed\"; name: string | undefined }\n\t| { type: \"thinking_level_changed\"; level: ThinkingLevel }\n\t| {\n\t\t\ttype: \"compaction_end\";\n\t\t\treason: \"manual\" | \"threshold\" | \"overflow\";\n\t\t\tresult: CompactionResult | undefined;\n\t\t\taborted: boolean;\n\t\t\twillRetry: boolean;\n\t\t\terrorMessage?: string;\n\t }\n\t| { type: \"agent_settled\" }\n\t| { type: \"auto_retry_start\"; attempt: number; maxAttempts: number; delayMs: number; errorMessage: string }\n\t| { type: \"auto_retry_end\"; success: boolean; attempt: number; finalError?: string }\n\t| {\n\t\t\ttype: \"summarization_retry_scheduled\";\n\t\t\tattempt: number;\n\t\t\tmaxAttempts: number;\n\t\t\tdelayMs: number;\n\t\t\terrorMessage: string;\n\t }\n\t| { type: \"summarization_retry_attempt_start\"; source: \"branchSummary\" }\n\t| {\n\t\t\ttype: \"summarization_retry_attempt_start\";\n\t\t\tsource: \"compaction\";\n\t\t\treason: \"manual\" | \"threshold\" | \"overflow\";\n\t }\n\t| { type: \"summarization_retry_finished\" };\n\n/** Listener function for agent session events */\nexport type AgentSessionEventListener = (event: AgentSessionEvent) => void;\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ExtensionRunnerRef {\n\tcurrent?: ExtensionRunner;\n}\n\nexport interface SessionStatsTokenTotals {\n\tinput: number;\n\toutput: number;\n\tcacheRead: number;\n\tcacheWrite: number;\n\ttotal: number;\n}\n\nexport interface AgentSessionConfig {\n\tagent: Agent;\n\tsession: Session;\n\tsettingsManager: SettingsManager;\n\tcwd: string;\n\t/** Models to cycle through with Ctrl+P (from --models flag) */\n\tscopedModels?: ScopedModel[];\n\t/** Resource loader for skills, rules, prompts, themes, context files, system prompt */\n\tresourceLoader: ResourceLoader;\n\t/** SDK custom tools registered outside extensions */\n\tcustomTools?: ToolDefinition[];\n\t/** Tool operation backend used by built-in tools. */\n\ttoolOperations?: ToolOperations;\n\t/** Model registry for API key resolution and model discovery */\n\tmodelRegistry: ModelRegistry;\n\t/** Initial active built-in tool names. Defaults to DEFAULT_ACTIVE_TOOL_NAMES. */\n\tinitialActiveToolNames?: string[];\n\t/** CLI/SDK override for exposing configured LSP tools by default; explicit tool allowlists remain authoritative. */\n\tenableLspTools?: boolean;\n\t/** Optional allowlist of tool names. When provided, only these tool names are exposed. */\n\tallowedToolNames?: string[];\n\t/** Tool names that must never be exposed in this session. */\n\texcludedToolNames?: string[];\n\t/**\n\t * Override base tools (useful for custom runtimes).\n\t *\n\t * These are synthesized into minimal ToolDefinitions internally so AgentSession can keep\n\t * a definition-first registry even when callers provide plain AgentTool instances.\n\t */\n\tbaseToolsOverride?: Record<string, AgentTool>;\n\t/** Mutable ref used by Agent to access the current ExtensionRunner */\n\textensionRunnerRef?: ExtensionRunnerRef;\n\t/** Runner used to create isolated subagent sessions. */\n\tsubagentRunner?: SubagentRunner;\n\tsubagentRunRegistry?: SubagentRunRegistry;\n\tsubagentConfigRegistry?: SubagentConfigRegistry;\n\t/** Host-controlled trust grant for project-local subagent presets. */\n\ttrustProjectAgents?: boolean;\n\t/** Validated configuration for the AgentSession-owned LSP runtime. */\n\tlspConfiguration?: ResolvedLspConfiguration;\n\t/** Host-provided factories referenced by LSP transports with type `connection`. */\n\tlspConnectionFactories?: LspConnectionFactoryRegistry;\n\t/** Re-resolves settings/file-backed LSP configuration after settings reload. */\n\tresolveLspConfiguration?: () => Promise<LoadLspConfigurationResult>;\n\t/** Receives current source-attributed diagnostics after each LSP configuration re-resolution. */\n\tonLspConfigurationDiagnostics?: (diagnostics: readonly LspConfigurationSourceDiagnostic[]) => void;\n\t/** Session start event metadata emitted when extensions bind to this runtime. */\n\tsessionStartEvent?: SessionStartEvent;\n\t/** Immutable Claude-compatible hook configuration snapshot for this session. */\n\tloadedHooks?: LoadedHooks;\n\t/** Re-resolves file-backed hook configuration after settings reload. */\n\tresolveHooks?: () => Promise<LoadedHooks | undefined>;\n\t/** Receives nonfatal hook execution/runtime diagnostics. */\n\tonHookDiagnostic?: (diagnostic: HookDiagnostic) => void;\n\t/** Host restrictions applied to hook execution. */\n\thookRunOptions?: Pick<HookRunOptions, \"allowedHttpHookUrls\" | \"httpHookAllowedEnvVars\">;\n}\n\nexport interface ExtensionBindings {\n\tuiContext?: ExtensionUIContext;\n\tcommandContextActions?: ExtensionCommandContextActions;\n\tabortHandler?: () => void;\n\tshutdownHandler?: ShutdownHandler;\n\tonError?: ExtensionErrorListener;\n}\n\nexport interface AgentSessionReloadResult {\n\tlsp?: LoadLspConfigurationResult;\n}\n\n/** Options for AgentSession.prompt() */\nexport interface PromptOptions {\n\t/** Whether to expand file-based prompt templates (default: true) */\n\texpandPromptTemplates?: boolean;\n\t/** Image attachments */\n\timages?: ImageContent[];\n\t/** When streaming, how to queue the message: \"steer\" (interrupt) or \"followUp\" (wait). Required if streaming. */\n\tstreamingBehavior?: StreamingBehavior;\n\t/** Source of input for extension input event handlers. Defaults to \"interactive\". */\n\tsource?: InputSource;\n\t/** Internal hook used by RPC mode to observe prompt preflight acceptance or rejection. */\n\tpreflightResult?: (success: boolean) => void;\n}\n\nexport type StructuredResponseScope = \"latest\" | \"conversation\";\nexport type StructuredResponseSource = \"json\" | \"tool\";\n\nexport interface StructuredResponseOptions<TSchemaValue extends TSchema> {\n\t/** TypeBox object schema for the structured response. */\n\tschema: TSchemaValue;\n\t/** Schema/tool name shown to the model. Defaults to \"structured_output\". */\n\tname?: string;\n\t/** Optional description for the temporary structured output tool. */\n\tdescription?: string;\n\t/** Maximum correction calls after an invalid response. Defaults to 2. */\n\tmaxCorrections?: number;\n\t/** Source context for extraction. Defaults to the latest assistant answer. */\n\tscope?: StructuredResponseScope;\n}\n\nexport interface StructuredResponse<T> {\n\toutput: T;\n\tattempts: number;\n\tsource: StructuredResponseSource;\n\tmessage: AssistantMessage;\n}\n\n/** Direction used when cycling through configured models. */\nexport type ModelCycleDirection = \"forward\" | \"backward\";\n\n/** Result from cycleModel() */\nexport interface ModelCycleResult {\n\tmodel: Model<any>;\n\tthinkingLevel: ThinkingLevel;\n\t/** Whether cycling through scoped models (--models flag) or all available */\n\tisScoped: boolean;\n}\n\n/** Session statistics for /session command */\nexport interface SessionStats {\n\tsessionFile: string | undefined;\n\tsessionId: string;\n\tuserMessages: number;\n\tassistantMessages: number;\n\ttoolCalls: number;\n\ttoolResults: number;\n\ttotalMessages: number;\n\ttokens: SessionStatsTokenTotals;\n\tcost: number;\n\tcontextUsage?: ContextUsage;\n}\n\ninterface ToolDefinitionEntry {\n\tdefinition: ToolDefinition;\n\tsourceInfo: SourceInfo;\n}\n\ninterface SessionToolEntry extends ToolDefinitionEntry {\n\tlazy: boolean;\n\tloaded: boolean;\n}\n\ninterface ExtensionLspOwnership {\n\tconfiguration: ResolvedLspConfiguration;\n\truntimeState: LspRuntimeState | undefined;\n}\n\ninterface RuntimeLifecycleOperationToken {\n\tactive: boolean;\n\tchildren: Set<Promise<unknown>>;\n\tchildQueue: Promise<void>;\n}\n\ninterface SessionActivityToken {\n\tactive: boolean;\n\tkind: string;\n}\n\ninterface PromptPreparationState {\n\treadonly text: string;\n\treadonly options: PromptOptions | undefined;\n\treadonly promptId: string;\n\treadonly expandPromptTemplates: boolean;\n\treadonly hookContextMessages: CustomMessage[];\n\tpromptActivity: SessionActivityToken | undefined;\n\tqueueIntoActiveRun: boolean;\n\tsessionContext: CustomMessage | undefined;\n\tcurrentText: string;\n\tcurrentImages: ImageContent[] | undefined;\n\texpandedText: string;\n}\n\ninterface IdleWaiter {\n\tresolve: () => void;\n\treject: (error: Error) => void;\n}\n\ninterface BarrierOwnerScope {\n\tactive: boolean;\n\tlabel: string;\n}\n\ntype StructuredInternalStage = \"request\" | \"assistant\" | \"tool_result\" | \"result\";\n\ninterface StructuredInternalDetails {\n\tstage: StructuredInternalStage;\n\tschemaName: string;\n\tattempt: number;\n\tsource?: StructuredResponseSource;\n\tvalidationError?: string;\n}\n\ninterface HookToolOutput {\n\tcontent: (TextContent | ImageContent)[];\n\tdetails?: JsonValue;\n}\n\ninterface PreToolHookExecution {\n\ttoolCall: BeforeToolCallContext[\"toolCall\"];\n\targs: unknown;\n\tagentContext: AgentContext;\n\tbackend: ToolBackendInfo;\n\tflavor: HookPathFlavor;\n\tnormalizedToolName: string;\n\tadaptToolInput: boolean;\n}\n\ntype PreToolInputAdaptation = { ok: true; input: JsonObject } | { ok: false; blockResult: BeforeToolCallResult };\n\ntype ToolHookEventName = \"PreToolUse\" | \"PostToolUse\" | \"PostToolUseFailure\";\ntype PostToolHookEvent = \"PostToolUse\" | \"PostToolUseFailure\";\n\ninterface PostToolCallState {\n\ttoolCall: AfterToolCallContext[\"toolCall\"];\n\targs: unknown;\n\tcontent: AfterToolCallContext[\"result\"][\"content\"];\n\tdetails: AfterToolCallContext[\"result\"][\"details\"];\n\tisError: boolean;\n}\n\ntype PostToolHookRunResult = { ok: true; hookResult: HookAggregateResult } | { ok: false };\n\ninterface SessionToolNameArgs {\n\tname?: string | string[];\n}\n\ninterface SessionToolNameParameters {\n\tname: string | string[];\n}\n\ninterface RequiredRequestAuth {\n\tapiKey: string;\n\theaders?: ProviderHeaders;\n}\n\ntype CompactionRequestAuth = Partial<RequiredRequestAuth>;\n\ninterface LspToolResult {\n\tcontent: ToolResultMessage[\"content\"];\n\tdetails?: unknown;\n}\n\ninterface LspToolResultUpdate extends LspToolResult {\n\tisError?: boolean;\n}\n\ntype CoreAgentEndEvent = Extract<AgentEvent, { type: \"agent_end\" }>;\n\ntype StructuredValidationResult<T> = { ok: true; output: T } | { ok: false; error: string };\n\ninterface StructuredAttemptContext<TSchemaValue extends TSchema> {\n\ttool: Tool<TSchemaValue>;\n\tassistantMessage: AssistantMessage;\n\tmessages: Message[];\n\tschemaName: string;\n\tattempt: number;\n}\n\ntype StructuredAttemptResult<T> = { ok: true; response: StructuredResponse<T> } | { ok: false; error: string };\n\ninterface CompactionExecutionOptions {\n\tabortActiveRun: boolean;\n}\n\ninterface CompactionPlan extends CompactionRequestAuth {\n\tmodel: Model<Api>;\n\tpreparation: CompactionPreparation;\n\tpathEntries: SessionEntry[];\n}\n\ntype AutoCompactionReason = \"overflow\" | \"threshold\";\ntype ExtensionCompactionQueueMode = \"idle\" | \"between-turns\";\n\ninterface AutoCompactionInterception {\n\tcancelled: boolean;\n\tcompaction?: CompactionResult;\n}\n\ninterface ResolvedAutoCompaction {\n\tresult: CompactionResult;\n\tfromExtension: boolean;\n}\n\ninterface ExtensionResourcePath {\n\tpath: string;\n\textensionPath: string;\n}\n\ninterface ToolRegistryRefreshOptions {\n\tactiveToolNames?: string[];\n\tincludeAllExtensionTools?: boolean;\n}\n\ninterface BuildRuntimeOptions extends ToolRegistryRefreshOptions {\n\tflagValues?: Map<string, boolean | string>;\n}\n\ninterface RuntimeBuildSnapshot {\n\tbaseToolDefinitions: Map<string, ToolDefinition>;\n\tlspRuntimeState: LspRuntimeState | undefined;\n\textensionRunner: ExtensionRunner | undefined;\n\ttoolRegistry: Map<string, AgentTool>;\n\ttoolDefinitions: Map<string, ToolDefinitionEntry>;\n\ttoolPromptSnippets: Map<string, string>;\n\ttoolPromptGuidelines: Map<string, string[]>;\n\tbaseSystemPrompt: string;\n\tbaseSystemPromptOptions: BuildSystemPromptOptions | undefined;\n\tagentTools: AgentTool[];\n\tagentSystemPrompt: string;\n\textensionRunnerRef: ExtensionRunner | undefined;\n\tworkspaceToolHost: WorkspaceToolHost | undefined;\n}\ninterface StopContinuationProgressState {\n\tbest: number;\n\tconsecutiveNonImprovingCalls: number;\n}\n\ntype SummarizationRetrySource =\n\t| { source: \"branchSummary\" }\n\t| { source: \"compaction\"; reason: \"manual\" | \"threshold\" | \"overflow\" };\n\nexport interface ConfigureRemoteSandboxOptions {\n\ttype: \"daemon\";\n\turl: string;\n\ttoken?: string;\n}\n\nexport interface ActivateSandboxDaemonOptions {\n\turl: string;\n\ttoken: string;\n\texpectedCwd: string;\n}\n\nexport type SessionSkillRegistration = Omit<Skill, \"sourceInfo\" | \"baseDir\"> & {\n\tbaseDir?: string;\n\tsourceInfo?: SourceInfo;\n};\n\nexport type SessionRuleRegistration = Omit<Rule, \"sourceInfo\" | \"baseDir\"> & {\n\tbaseDir?: string;\n\tsourceInfo?: SourceInfo;\n};\n\nexport interface RegisterSessionToolOptions {\n\tlazy?: boolean;\n\tsourceInfo?: SourceInfo;\n}\n\nexport type SendCustomMessageOptions = ExtensionSendMessageOptions;\n\nexport interface SendUserMessageOptions {\n\tdeliverAs?: StreamingBehavior;\n}\n\nexport interface QueuedMessages {\n\tsteering: string[];\n\tfollowUp: string[];\n}\n\nexport interface ExecuteBashOptions {\n\texcludeFromContext?: boolean;\n\toperations?: ToolOperations;\n\trecord?: boolean;\n\ttruncate?: boolean;\n}\n\nexport interface RecordBashResultOptions {\n\texcludeFromContext?: boolean;\n}\n\nexport type NavigateTreeOptions = ExtensionNavigateTreeOptions;\n\nexport interface NavigateTreeResult {\n\teditorText?: string;\n\tcancelled: boolean;\n\taborted?: boolean;\n\tsummaryEntry?: BranchSummaryEntry;\n}\n\ninterface TreeNavigationSettings {\n\tcustomInstructions?: string;\n\treplaceInstructions?: boolean;\n\tlabel?: string;\n}\n\ninterface TreeExtensionPreparationResult extends TreeNavigationSettings {\n\tcancelled: boolean;\n\textensionSummary?: SessionBeforeTreeSummary;\n\tfromExtension: boolean;\n}\n\ninterface TreeSummaryResolution {\n\tcancelled: boolean;\n\taborted?: boolean;\n\tsummaryText?: string;\n\tsummaryDetails?: unknown;\n\tfromExtension: boolean;\n}\n\ninterface TreeTargetResolution {\n\tnewLeafId: string | null;\n\teditorText?: string;\n}\n\nexport interface ForkableUserMessage {\n\tentryId: string;\n\ttext: string;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** Standard thinking levels */\nconst THINKING_LEVELS: ThinkingLevel[] = [\"off\", \"minimal\", \"low\", \"medium\", \"high\"];\nconst DEFAULT_STRUCTURED_RESPONSE_TOOL_NAME = \"structured_output\";\nconst DEFAULT_STRUCTURED_RESPONSE_CORRECTIONS = 2;\nconst DEFAULT_STOP_HOOK_CONTINUATION_LIMIT = 8;\nconst STOP_HOOK_PROGRESS_REGRESSION_TOLERANCE = 3;\nconst STALE_EXTENSION_CONTEXT_MESSAGE =\n\t\"This extension ctx is stale after session replacement or reload. Do not use a captured extension API or command ctx after ctx.newSession(), ctx.fork(), ctx.switchSession(), or ctx.reload(). For newSession, fork, and switchSession, move post-replacement work into withSession and use the ctx passed to withSession. For reload, do not use the old ctx after await ctx.reload().\";\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nconst CLAUDE_TOOL_NAMES: Readonly<Record<string, string>> = {\n\tread: \"Read\",\n\tbash: \"Bash\",\n\tedit: \"Edit\",\n\twrite: \"Write\",\n\twebsearch: \"WebSearch\",\n\tgrep: \"Grep\",\n\tfind: \"Glob\",\n\tls: \"Glob\",\n};\nfunction claudeToolName(name: string): string {\n\treturn CLAUDE_TOOL_NAMES[name] ?? name;\n}\n\nexport type HookPathFlavor = \"posix\" | \"windows\";\n\nfunction hookPathFlavor(backend: ToolBackendInfo): HookPathFlavor {\n\tif (backend.type === \"remote\" && backend.configured) return backend.workspace.pathFlavor;\n\tif (backend.type === \"remote\") return /^[A-Za-z]:[\\\\/]/.test(backend.cwd) ? \"windows\" : \"posix\";\n\treturn process.platform === \"win32\" ? \"windows\" : \"posix\";\n}\n\nfunction hookPathHome(backend: ToolBackendInfo, flavor: HookPathFlavor): string | null {\n\treturn backend.type === \"local\" && flavor === \"posix\" ? homedir() : null;\n}\n\nfunction isClaudeFileTool(name: string): boolean {\n\treturn name === \"read\" || name === \"edit\" || name === \"write\";\n}\n\nexport function classifyStopFailure(errorMessage: string): string {\n\tconst normalized = errorMessage.toLowerCase();\n\tif (/rate[\\s_-]*limit|too many requests|\\b429\\b/.test(normalized)) return \"rate_limit\";\n\tif (/overload|capacity|\\b529\\b/.test(normalized)) return \"overloaded\";\n\tif (/oauth.*org|organization.*oauth/.test(normalized)) return \"oauth_org_not_allowed\";\n\tif (/billing|credit|payment|quota.*(?:fund|spend)/.test(normalized)) return \"billing_error\";\n\tif (/model.*not[\\s_-]*found|unknown model|\\b404\\b/.test(normalized)) return \"model_not_found\";\n\tif (/max(?:imum)?[\\s_-]*output.*token|output.*token.*limit/.test(normalized)) return \"max_output_tokens\";\n\tif (/auth|unauthorized|forbidden|invalid.*(?:api[\\s_-]*)?key|\\b401\\b|\\b403\\b/.test(normalized))\n\t\treturn \"authentication_failed\";\n\tif (/invalid.*request|bad request|context.*(?:length|window)|\\b400\\b|\\b422\\b/.test(normalized))\n\t\treturn \"invalid_request\";\n\tif (/server|internal|gateway|network|connection|timeout|\\b5\\d\\d\\b/.test(normalized)) return \"server_error\";\n\treturn \"unknown\";\n}\nfunction resolveClaudeHookFilePath(source: string, cwd: string, flavor: HookPathFlavor, home: string | null): string {\n\tconst isTildePath = source === \"~\" || source.startsWith(\"~/\") || source.startsWith(\"~\\\\\");\n\tif (isTildePath) {\n\t\tif (flavor !== \"posix\" || home === null) {\n\t\t\tthrow new Error(\"cannot resolve a ~ path without a known POSIX backend home\");\n\t\t}\n\t\treturn posix.resolve(home, source === \"~\" ? \".\" : source.slice(2));\n\t}\n\tif (source.startsWith(\"~\")) throw new Error(\"~user paths are unsupported in hook file paths\");\n\tconst api = flavor === \"windows\" ? win32 : posix;\n\treturn api.resolve(cwd, source);\n}\n\n/** Adapt grip's file-tool arguments to Claude's absolute, backend-lexical file_path shape. */\nexport function adaptFileToolInputForClaudeHook(\n\ttoolName: string,\n\tinput: JsonObject,\n\tcwd: string,\n\tflavor: HookPathFlavor,\n\thome: string | null = homedir(),\n): JsonObject {\n\tif (!isClaudeFileTool(toolName)) return input;\n\tconst source = typeof input.path === \"string\" ? input.path : input.file_path;\n\tif (typeof source !== \"string\") return input;\n\tconst adapted: JsonObject = { ...input, file_path: resolveClaudeHookFilePath(source, cwd, flavor, home) };\n\tdelete adapted.path;\n\treturn adapted;\n}\n\n/** Reverse a Claude file_path replacement to grip's path field before schema validation. */\nexport function adaptFileToolUpdatedInputFromClaudeHook(\n\ttoolName: string,\n\tinput: JsonObject,\n\tcwd: string,\n\tflavor: HookPathFlavor,\n\thome: string | null = homedir(),\n): JsonObject {\n\tif (!isClaudeFileTool(toolName)) return input;\n\tif (typeof input.file_path !== \"string\" || \"path\" in input) {\n\t\tthrow new Error(\"file-tool replacement must contain a string file_path and must not contain path\");\n\t}\n\tconst adapted: JsonObject = {\n\t\t...input,\n\t\tpath: resolveClaudeHookFilePath(input.file_path, cwd, flavor, home),\n\t};\n\tdelete adapted.file_path;\n\treturn adapted;\n}\n\nfunction normalizeHookToolOutput(value: JsonValue): HookToolOutput | undefined {\n\tif (typeof value === \"string\") return { content: [{ type: \"text\", text: value }] };\n\tif (!isRecord(value) || !Array.isArray(value.content)) return undefined;\n\tconst content: (TextContent | ImageContent)[] = [];\n\tfor (const part of value.content) {\n\t\tif (!isRecord(part) || typeof part.type !== \"string\") return undefined;\n\t\tif (part.type === \"text\" && typeof part.text === \"string\") content.push({ type: \"text\", text: part.text });\n\t\telse if (part.type === \"image\" && typeof part.data === \"string\" && typeof part.mimeType === \"string\")\n\t\t\tcontent.push({ type: \"image\", data: part.data, mimeType: part.mimeType });\n\t\telse return undefined;\n\t}\n\treturn { content, ...(value.details !== undefined ? { details: value.details as JsonValue } : {}) };\n}\n\nfunction toJsonValue(value: unknown): JsonValue {\n\tif (value === undefined) return null;\n\ttry {\n\t\treturn JSON.parse(JSON.stringify(value)) as JsonValue;\n\t} catch {\n\t\treturn String(value);\n\t}\n}\n\nfunction hookExecutionSubject(input: HookInput): string | undefined {\n\tswitch (input.hook_event_name) {\n\t\tcase \"PreToolUse\":\n\t\tcase \"PostToolUse\":\n\t\tcase \"PostToolUseFailure\":\n\t\t\treturn input.tool_name;\n\t\tcase \"PreCompact\":\n\t\tcase \"PostCompact\":\n\t\t\treturn input.trigger;\n\t\tcase \"SessionStart\":\n\t\t\treturn input.source;\n\t\tcase \"UserPromptSubmit\":\n\t\tcase \"Stop\":\n\t\tcase \"StopFailure\":\n\t\tcase \"SessionEnd\":\n\t\t\treturn undefined;\n\t}\n}\n\nfunction hookExecutionLabel(result: HookExecutionResult): string {\n\tconst handler = result.hook.handler;\n\tif (handler.statusMessage) return handler.statusMessage;\n\tif (handler.type === \"command\") return [handler.command, ...(handler.args ?? [])].join(\" \");\n\tif (handler.type === \"http\") {\n\t\ttry {\n\t\t\tconst url = new URL(handler.url);\n\t\t\treturn `${url.origin}${url.pathname}`;\n\t\t} catch {\n\t\t\treturn handler.url.split(/[?#]/, 1)[0];\n\t\t}\n\t}\n\treturn handler.type;\n}\n\nfunction hookResultContainsStructuredReason(input: HookInput, result: HookAggregateResult, reason: string): boolean {\n\treturn result.results.some((execution) => {\n\t\tif (execution.status !== \"completed\" || execution.output.kind !== \"json\") return false;\n\t\tconst output = execution.output.value;\n\t\tif (input.hook_event_name !== \"SessionEnd\" && output.continue === false && output.stopReason === reason) {\n\t\t\treturn true;\n\t\t}\n\t\tif (output.reason === reason && output.decision === \"block\") {\n\t\t\tswitch (input.hook_event_name) {\n\t\t\t\tcase \"UserPromptSubmit\":\n\t\t\t\tcase \"PostToolUse\":\n\t\t\t\tcase \"PostToolUseFailure\":\n\t\t\t\tcase \"Stop\":\n\t\t\t\tcase \"PreCompact\":\n\t\t\t\t\treturn true;\n\t\t\t\tcase \"SessionStart\":\n\t\t\t\tcase \"PreToolUse\":\n\t\t\t\tcase \"StopFailure\":\n\t\t\t\tcase \"PostCompact\":\n\t\t\t\tcase \"SessionEnd\":\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tconst specific = output.hookSpecificOutput;\n\t\treturn (\n\t\t\tinput.hook_event_name === \"PreToolUse\" &&\n\t\t\tisRecord(specific) &&\n\t\t\tspecific.hookEventName === input.hook_event_name &&\n\t\t\tspecific.permissionDecision === \"deny\" &&\n\t\t\tspecific.permissionDecisionReason === reason\n\t\t);\n\t});\n}\n\nfunction createHookExecutionNotice(input: HookInput, result: HookAggregateResult): HookExecutionNotice {\n\tconst returnedPrompts = [...result.additionalContext];\n\tif (\n\t\tresult.blocked &&\n\t\tresult.reason &&\n\t\t!returnedPrompts.includes(result.reason) &&\n\t\thookResultContainsStructuredReason(input, result, result.reason)\n\t) {\n\t\treturnedPrompts.push(result.reason);\n\t}\n\treturn {\n\t\tevent: input.hook_event_name,\n\t\tsubject: hookExecutionSubject(input),\n\t\tcalls: result.results.map((execution) => ({\n\t\t\ttype: execution.hook.handler.type,\n\t\t\tlabel: hookExecutionLabel(execution),\n\t\t\tsource: execution.hook.source,\n\t\t\tstatus: execution.status,\n\t\t\texitCode: execution.exitCode,\n\t\t\tdurationMs: execution.durationMs,\n\t\t})),\n\t\treturnedPrompts,\n\t};\n}\n\nfunction getAssistantText(message: AssistantMessage): string {\n\treturn message.content\n\t\t.filter((block) => block.type === \"text\")\n\t\t.map((block) => block.text)\n\t\t.join(\"\\n\");\n}\n\nfunction escapeXmlAttribute(value: string): string {\n\treturn value.replace(/&/g, \"&\").replace(/\"/g, \""\").replace(/</g, \"<\").replace(/>/g, \">\");\n}\n\nfunction normalizeSessionToolNames(value: unknown): string[] {\n\tconst raw = Array.isArray(value) ? value : [value];\n\tconst names: string[] = [];\n\tconst seenNames = new Set<string>();\n\tfor (const entry of raw) {\n\t\tif (typeof entry !== \"string\") continue;\n\t\tconst trimmed = entry.trim();\n\t\tif (trimmed.length > 0 && !seenNames.has(trimmed)) {\n\t\t\tseenNames.add(trimmed);\n\t\t\tnames.push(trimmed);\n\t\t}\n\t}\n\treturn names;\n}\n\nfunction getToolNameArgs(args: SessionToolNameArgs | undefined): string[] {\n\tconst names = normalizeSessionToolNames(args?.name);\n\treturn names.length > 0 ? names : [\"...\"];\n}\n\nfunction formatSessionToolLifecycleCall(label: string, toolNames: string[]): string {\n\treturn (\n\t\ttheme.fg(\"customMessageLabel\", `\\x1b[1m[tool]\\x1b[22m `) +\n\t\ttheme.fg(\"toolTitle\", label) +\n\t\t\" \" +\n\t\ttheme.fg(\"customMessageText\", toolNames.join(\", \"))\n\t);\n}\n\ntype SessionToolLifecycleAction = \"loaded\" | \"unloaded\";\n\nfunction formatSessionToolLifecycleResult(\n\taction: SessionToolLifecycleAction,\n\tsucceeded: string[],\n\tnotFound: string[],\n): string {\n\tconst prefix = theme.fg(\"customMessageLabel\", `\\x1b[1m[tool]\\x1b[22m `);\n\tconst parts: string[] = [];\n\tif (succeeded.length > 0) {\n\t\tparts.push(`${theme.fg(\"success\", action)} ${theme.fg(\"customMessageText\", succeeded.join(\", \"))}`);\n\t}\n\tif (notFound.length > 0) {\n\t\tparts.push(`${theme.fg(\"error\", \"not found\")} ${theme.fg(\"customMessageText\", notFound.join(\", \"))}`);\n\t}\n\tif (parts.length === 0) {\n\t\tparts.push(theme.fg(\"error\", \"not found\"));\n\t}\n\treturn prefix + parts.join(\" \");\n}\n\nfunction extractJsonCandidates(text: string): unknown[] {\n\tconst candidates: unknown[] = [];\n\tconst trimmed = text.trim();\n\tif (trimmed) {\n\t\ttry {\n\t\t\tcandidates.push(JSON.parse(trimmed));\n\t\t} catch {\n\t\t\t// Try fenced JSON blocks below.\n\t\t}\n\t}\n\n\tconst fencePattern = /```(?:json)?\\s*([\\s\\S]*?)```/gi;\n\tlet match = fencePattern.exec(text);\n\twhile (match) {\n\t\ttry {\n\t\t\tcandidates.push(JSON.parse(match[1].trim()));\n\t\t} catch {\n\t\t\t// Ignore invalid fenced blocks.\n\t\t}\n\t\tmatch = fencePattern.exec(text);\n\t}\n\treturn candidates;\n}\n\n// ============================================================================\n// AgentSession Class\n// ============================================================================\n\nexport class AgentSession {\n\treadonly agent: Agent;\n\treadonly session: Session;\n\treadonly settingsManager: SettingsManager;\n\n\tprivate _scopedModels: ScopedModel[];\n\n\t// Event subscription state\n\tprivate _unsubscribeAgent?: () => void;\n\tprivate _eventListeners: AgentSessionEventListener[] = [];\n\tprivate _disposePromise?: Promise<void>;\n\tprivate _shutdownPreparationPromise?: Promise<void>;\n\tprivate _disposed = false;\n\n\t/** Tracks pending steering messages for UI display. Removed when delivered. */\n\tprivate _steeringMessages: string[] = [];\n\t/** Tracks pending follow-up messages for UI display. Removed when delivered. */\n\tprivate _followUpMessages: string[] = [];\n\t/** Messages queued to be included with the next user prompt as context (\"asides\"). */\n\tprivate _pendingNextTurnMessages: CustomMessage[] = [];\n\n\t// Compaction state\n\tprivate _compactionInFlight?: Promise<unknown>;\n\tprivate _compactionAbortController: AbortController | undefined = undefined;\n\tprivate _autoCompactionAbortController: AbortController | undefined = undefined;\n\tprivate _extensionCompactionQueue: CompactOptions[] = [];\n\tprivate _extensionCompactionTimer: ReturnType<typeof setTimeout> | undefined = undefined;\n\tprivate _extensionCompactionActivity?: SessionActivityToken;\n\tprivate _extensionCompactionRunning = false;\n\tprivate _overflowRecoveryAttempted = false;\n\n\t// Branch summarization state\n\tprivate _branchSummaryAbortController: AbortController | undefined = undefined;\n\n\t// Retry state\n\tprivate _retryAbortController: AbortController | undefined = undefined;\n\tprivate _retryAttempt = 0;\n\n\t// Bash execution state\n\tprivate readonly _bashAbortControllers = new Set<AbortController>();\n\tprivate _pendingBashMessages: BashExecutionMessage[] = [];\n\n\t// Extension system\n\tprivate _extensionRunner!: ExtensionRunner;\n\tprivate readonly _extensionLspOwnership = new WeakMap<ExtensionRunner, ExtensionLspOwnership>();\n\tprivate _turnIndex = 0;\n\n\tprivate _resourceLoader: ResourceLoader;\n\tprivate _customTools: ToolDefinition[];\n\tprivate _baseToolDefinitions: Map<string, ToolDefinition> = new Map();\n\tprivate _workspaceToolHost?: WorkspaceToolHost;\n\tprivate _retiredWorkspaceToolHosts: WorkspaceToolHost[] = [];\n\tprivate _cwd: string;\n\tprivate _extensionRunnerRef?: { current?: ExtensionRunner };\n\tprivate _initialActiveToolNames?: string[];\n\tprivate readonly _enableLspTools?: boolean;\n\tprivate _allowedToolNames?: Set<string>;\n\tprivate _excludedToolNames: Set<string>;\n\tprivate _baseToolsOverride?: Record<string, AgentTool>;\n\tprivate _toolOperations?: ToolOperations;\n\tprivate _runtimeToolOperations?: LocalToolOperations;\n\tprivate _subagentRunner?: SubagentRunner;\n\tprivate _subagentRunRegistry?: SubagentRunRegistry;\n\tprivate _subagentConfigRegistry?: SubagentConfigRegistry;\n\tprivate _trustProjectAgents: boolean;\n\tprivate _localResourceToolOperations?: ToolOperations;\n\tprivate _sandboxToolOperations?: DeferredRemoteToolOperations;\n\tprivate _preSandboxToolOperations?: ToolOperations;\n\tprivate _lspRuntimeState?: LspRuntimeState;\n\tprivate _lspConfiguration: ResolvedLspConfiguration;\n\tprivate readonly _lspConnectionFactories: LspConnectionFactoryRegistry;\n\tprivate readonly _resolveLspConfiguration?: () => Promise<LoadLspConfigurationResult>;\n\tprivate readonly _onLspConfigurationDiagnostics?: (diagnostics: readonly LspConfigurationSourceDiagnostic[]) => void;\n\tprivate _runtimeLifecycleQueue: Promise<void> = Promise.resolve();\n\tprivate _sandboxTransitionQueue: Promise<void> = Promise.resolve();\n\tprivate readonly _runtimeLifecycleContext = new AsyncLocalStorage<RuntimeLifecycleOperationToken>();\n\tprivate readonly _activities = new Set<SessionActivityToken>();\n\tprivate _activityGeneration = 0;\n\tprivate _activeCycle = false;\n\tprivate _settlementScheduled = false;\n\tprivate _settlementRunning = false;\n\tprivate readonly _idleWaiters = new Set<IdleWaiter>();\n\tprivate readonly _barrierOwnerContext = new AsyncLocalStorage<BarrierOwnerScope>();\n\tprivate _sessionStartEvent: SessionStartEvent;\n\tprivate _extensionUIContext?: ExtensionUIContext;\n\tprivate _extensionCommandContextActions?: ExtensionCommandContextActions;\n\tprivate _extensionAbortHandler?: () => void;\n\tprivate _extensionShutdownHandler?: ShutdownHandler;\n\tprivate _extensionErrorListener?: ExtensionErrorListener;\n\tprivate _extensionErrorUnsubscriber?: () => void;\n\tprivate _loadedHooks?: LoadedHooks;\n\tprivate readonly _resolveHooks?: () => Promise<LoadedHooks | undefined>;\n\tprivate _hooksEnabled = true;\n\tprivate readonly _onHookDiagnostic?: (diagnostic: HookDiagnostic) => void;\n\tprivate readonly _hookExecutionListeners = new Set<HookExecutionListener>();\n\tprivate readonly _hookExecutionActivityListeners = new Set<HookExecutionActivityListener>();\n\tprivate readonly _hookRunOptions: Pick<HookRunOptions, \"allowedHttpHookUrls\" | \"httpHookAllowedEnvVars\">;\n\tprivate readonly _hookAbortController = new AbortController();\n\tprivate _hookSessionStartPromise?: Promise<CustomMessage | undefined>;\n\tprivate _hookSessionStartTerminationReason?: string;\n\tprivate _pendingHookSessionStartContext?: CustomMessage;\n\tprivate _hookSessionEnded = false;\n\tprivate _activePromptId: string | undefined;\n\tprivate _stopHookContinuations = 0;\n\tprivate _stopHookProgress: StopContinuationProgressState | undefined;\n\tprivate readonly _queuedPromptIds = new Map<string, string[]>();\n\tprivate readonly _toolStartedAt = new Map<string, number>();\n\tprivate readonly _preToolHookContext = new Map<string, string[]>();\n\tprivate readonly _compressionDetector: CompressionDetector;\n\tprivate _contextCompressionEnabledOverride?: boolean;\n\tprivate _compressionDetectionEnabled = process.env.GRIP_CONTEXT_COMPRESSION_DETECTION === \"1\";\n\tprivate _agentRunEnding = false;\n\tprivate _waitingForDetectorAtRequest = false;\n\tprivate _pendingContextCompression?: PendingContextCompression;\n\tprivate _stateCompressionApplied = false;\n\n\t// Model registry for API key resolution\n\tprivate _modelRegistry: ModelRegistry;\n\n\t// Tool registry for extension getTools/setTools\n\tprivate _toolRegistry: Map<string, AgentTool> = new Map();\n\tprivate _toolDefinitions: Map<string, ToolDefinitionEntry> = new Map();\n\tprivate _sessionTools: Map<string, SessionToolEntry> = new Map();\n\tprivate _sessionSkills: Map<string, Skill> = new Map();\n\tprivate _sessionRules: Map<string, Rule> = new Map();\n\tprivate _toolPromptSnippets: Map<string, string> = new Map();\n\tprivate _toolPromptGuidelines: Map<string, string[]> = new Map();\n\n\t// Base system prompt (without extension appends) - used to apply fresh appends each turn\n\tprivate _baseSystemPrompt = \"\";\n\tprivate _baseSystemPromptOptions!: BuildSystemPromptOptions;\n\tprivate _systemPromptOverride?: string;\n\n\tconstructor(config: AgentSessionConfig) {\n\t\tthis.agent = config.agent;\n\t\tthis.session = config.session;\n\t\tthis.settingsManager = config.settingsManager;\n\t\tthis._scopedModels = config.scopedModels ?? [];\n\t\tthis._resourceLoader = config.resourceLoader;\n\t\tthis._customTools = config.customTools ?? [];\n\t\tthis._cwd = config.cwd;\n\t\tthis._modelRegistry = config.modelRegistry;\n\t\tthis._compressionDetector = new CompressionDetector(\n\t\t\tthis.settingsManager,\n\t\t\tthis._modelRegistry,\n\t\t\t(active) => this._emit({ type: \"compression_detection_activity\", active }),\n\t\t\t(verdict) => {\n\t\t\t\tthis._emit({ type: \"compression_detection_result\", verdict });\n\t\t\t\tif (\n\t\t\t\t\tverdict === \"COMPRESS\" &&\n\t\t\t\t\t!this._disposed &&\n\t\t\t\t\t!this._agentRunEnding &&\n\t\t\t\t\t!this._waitingForDetectorAtRequest &&\n\t\t\t\t\tthis.agent.state.isStreaming &&\n\t\t\t\t\tthis.compressionDetectionEnabled\n\t\t\t\t) {\n\t\t\t\t\tthis.agent.steer(this._createCompressionAdvisory());\n\t\t\t\t}\n\t\t\t},\n\t\t\tundefined,\n\t\t\t{\n\t\t\t\tgetBranch: () => getCompressibleEntries(this.session, \"\"),\n\t\t\t\tsession: this.session,\n\t\t\t\tgetCostInfo: () => {\n\t\t\t\t\tconst model = this.model;\n\t\t\t\t\treturn model\n\t\t\t\t\t\t? `Primary model catalog rates ($/million tokens): input ${model.cost.input}, cacheRead ${model.cost.cacheRead}, cacheWrite ${model.cost.cacheWrite}, output ${model.cost.output}. Subscription: ${this._modelRegistry.isUsingOAuth(model)}. Actual cache hit rate and future request count are unknown.`\n\t\t\t\t\t\t: \"Primary model pricing is unavailable.\";\n\t\t\t\t},\n\t\t\t\tgetRangeError: (start, end) => getSuggestedCompressionRangeError(this.session, start, end),\n\t\t\t\trecordEvent: (event) => appendCompressionDetectionEvent(this.session, event),\n\t\t\t},\n\t\t);\n\t\tthis._extensionRunnerRef = config.extensionRunnerRef;\n\t\tthis._initialActiveToolNames = config.initialActiveToolNames;\n\t\tthis._enableLspTools = config.enableLspTools;\n\t\tthis._excludedToolNames = new Set(config.excludedToolNames ?? []);\n\t\tif (!config.subagentRunner) {\n\t\t\tthis._excludedToolNames.add(\"subagent\");\n\t\t\tthis._excludedToolNames.add(\"subagent_runs\");\n\t\t\tthis._excludedToolNames.add(\"create_subagent\");\n\t\t}\n\t\tthis._allowedToolNames = config.allowedToolNames ? new Set(config.allowedToolNames) : undefined;\n\t\tthis._baseToolsOverride = config.baseToolsOverride;\n\t\tthis._toolOperations = config.toolOperations;\n\t\tthis._subagentRunner = config.subagentRunner;\n\t\tthis._subagentRunRegistry = config.subagentRunRegistry;\n\t\tthis._subagentConfigRegistry = config.subagentConfigRegistry;\n\t\tthis._trustProjectAgents = config.trustProjectAgents === true;\n\t\tthis._lspConfiguration = structuredClone(config.lspConfiguration ?? { enabled: false, servers: [] });\n\t\tthis._lspConnectionFactories = config.lspConnectionFactories ?? {};\n\t\tthis._resolveLspConfiguration = config.resolveLspConfiguration;\n\t\tthis._onLspConfigurationDiagnostics = config.onLspConfigurationDiagnostics;\n\t\tthis._sessionStartEvent = config.sessionStartEvent ?? { type: \"session_start\", reason: \"startup\" };\n\t\tthis._loadedHooks = config.loadedHooks;\n\t\tthis._resolveHooks = config.resolveHooks;\n\t\tthis._onHookDiagnostic = config.onHookDiagnostic;\n\t\tthis._hookRunOptions = config.hookRunOptions ?? {};\n\n\t\t// Always subscribe to agent events for internal handling\n\t\t// (session persistence, extensions, auto-compaction, retry logic)\n\t\tthis._unsubscribeAgent = this.agent.subscribe(this._handleAgentEvent);\n\t\tthis._installAgentToolHooks();\n\t\tthis._installAgentTurnPreparation();\n\t\tthis._installContextMetadata();\n\n\t\tthis._buildRuntime({\n\t\t\tactiveToolNames: this._initialActiveToolNames\n\t\t\t\t? this._withCurrentDefaultTools(this._initialActiveToolNames)\n\t\t\t\t: undefined,\n\t\t\tincludeAllExtensionTools: true,\n\t\t});\n\t}\n\n\t/** Model registry for API key resolution and model discovery */\n\tget modelRegistry(): ModelRegistry {\n\t\treturn this._modelRegistry;\n\t}\n\n\tgetToolOperations(): ToolOperations {\n\t\tif (this._toolOperations) return this._toolOperations;\n\t\tconst shellPath = this.settingsManager.getShellPath();\n\t\tthis._runtimeToolOperations ??= new LocalToolOperations(this.session.getCwd(), { shellPath });\n\t\tthis._runtimeToolOperations.setShellPath(shellPath);\n\t\treturn this._runtimeToolOperations;\n\t}\n\n\t/** Snapshot of the sole AgentSession-owned LSP runtime and its configured/instantiated servers. */\n\tgetLspStatus(): LspSessionStatus {\n\t\tconst daemonOwned = this.getToolBackendInfo().type === \"remote\";\n\t\tconst remoteStatus = daemonOwned ? this.getToolOperations().getRemoteLspStatus?.() : undefined;\n\t\treturn {\n\t\t\towner: daemonOwned ? \"daemon\" : \"agent-session\",\n\t\t\tenabled: daemonOwned ? (remoteStatus?.enabled ?? false) : this._lspConfiguration.enabled,\n\t\t\tconfiguration: daemonOwned\n\t\t\t\t? { enabled: remoteStatus?.enabled ?? false, servers: [] }\n\t\t\t\t: structuredClone(this._lspConfiguration),\n\t\t\tservers: daemonOwned ? (remoteStatus?.servers ?? []) : (this._lspRuntimeState?.manager.getStatus() ?? []),\n\t\t};\n\t}\n\n\t/** Validate and replace the AgentSession-owned LSP configuration. Relative paths resolve from the session cwd. */\n\tasync configureLsp(configuration: LspConfigurationLayer): Promise<ResolvedLspConfiguration> {\n\t\tif (this._disposed) throw new Error(\"Cannot configure LSP after AgentSession disposal\");\n\t\tif (this.getToolBackendInfo().type === \"remote\") {\n\t\t\tthrow new Error(\"LSP for a daemon workspace is operator-owned and cannot be configured by AgentSession\");\n\t\t}\n\t\tconst parsed = parseLspConfiguration(configuration);\n\t\tif (!parsed.configuration) {\n\t\t\tthrow new Error(\n\t\t\t\t`Invalid LSP configuration: ${parsed.diagnostics.map((diagnostic) => `${diagnostic.path}: ${diagnostic.message}`).join(\"; \")}`,\n\t\t\t);\n\t\t}\n\t\tconst resolvedLayer = resolveLspConfigurationLayerPaths(parsed.configuration, this._cwd);\n\t\tconst resolvedValidation = parseLspConfiguration(resolvedLayer);\n\t\tif (!resolvedValidation.configuration) {\n\t\t\tthrow new Error(\n\t\t\t\t`Invalid resolved LSP configuration: ${resolvedValidation.diagnostics.map((diagnostic) => `${diagnostic.path}: ${diagnostic.message}`).join(\"; \")}`,\n\t\t\t);\n\t\t}\n\t\tconst resolved = resolveLspConfiguration([resolvedValidation.configuration]);\n\t\tawait this._enqueueRuntimeLifecycle(() => this._replaceLspRuntime(resolved));\n\t\treturn structuredClone(resolved);\n\t}\n\n\tgetToolBackendInfo(): ToolBackendInfo {\n\t\treturn this.getToolOperations().getBackendInfo?.() ?? { type: \"local\", cwd: this._cwd };\n\t}\n\n\tsupportsDirectoryChange(): boolean {\n\t\treturn this._toolOperations === undefined && this.getToolBackendInfo().type === \"local\";\n\t}\n\n\tprivate _getLocalResourceToolOperations(shellPath?: string): ToolOperations {\n\t\tthis._localResourceToolOperations ??= new LocalToolOperations(this._cwd, { shellPath });\n\t\treturn this._localResourceToolOperations;\n\t}\n\n\tprivate _getReadOperationsForPath(\n\t\tabsolutePath: string,\n\t\tshellPath?: string,\n\t): ReadToolOperationsSelection | undefined {\n\t\tconst isSameOrChild = (target: string, root: string): boolean => {\n\t\t\tconst normalizedRoot = resolvePath(root);\n\t\t\tif (target === normalizedRoot) return true;\n\t\t\tconst prefix = normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`;\n\t\t\treturn target.startsWith(prefix);\n\t\t};\n\t\tconst resources = [\n\t\t\t...this.getRegisteredSkills().map((skill) => ({\n\t\t\t\tpath: skill.filePath,\n\t\t\t\tbaseDir: skill.baseDir,\n\t\t\t\tsourceInfo: skill.sourceInfo,\n\t\t\t})),\n\t\t\t...this.getRegisteredRules().map((rule) => ({\n\t\t\t\tpath: rule.filePath,\n\t\t\t\tbaseDir: rule.baseDir,\n\t\t\t\tsourceInfo: rule.sourceInfo,\n\t\t\t})),\n\t\t\t...this._resourceLoader.getPrompts().prompts.map((prompt) => ({\n\t\t\t\tpath: prompt.filePath,\n\t\t\t\tbaseDir: dirname(prompt.filePath),\n\t\t\t\tsourceInfo: prompt.sourceInfo,\n\t\t\t})),\n\t\t];\n\t\tlet canonicalTarget: string;\n\t\ttry {\n\t\t\tcanonicalTarget = realpathSync(absolutePath);\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t\tfor (const resource of resources) {\n\t\t\tif (getSourceBackend(resource.sourceInfo) !== \"local\") continue;\n\t\t\ttry {\n\t\t\t\tconst resourcePath = realpathSync(resource.path);\n\t\t\t\tconst baseDir = realpathSync(resource.baseDir);\n\t\t\t\tif (canonicalTarget === resourcePath || isSameOrChild(canonicalTarget, baseDir)) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tkind: \"selection\",\n\t\t\t\t\t\toperations: this._getLocalResourceToolOperations(shellPath),\n\t\t\t\t\t\tpath: canonicalTarget,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t} catch {}\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tasync configureRemoteSandbox(options: ConfigureRemoteSandboxOptions): Promise<ToolBackendInfo> {\n\t\treturn this._runActivity(\"remote backend configuration\", () =>\n\t\t\tthis._enqueueSandboxTransition(async () => {\n\t\t\t\tif (!(this._toolOperations instanceof DeferredRemoteToolOperations) || this._sandboxToolOperations) {\n\t\t\t\t\tthrow new Error(\"Remote backend can only be configured when grip is started with --remote-deferred\");\n\t\t\t\t}\n\t\t\t\tconst currentInfo = this._toolOperations.getBackendInfo();\n\t\t\t\tif (currentInfo.type !== \"remote\" || currentInfo.configured) {\n\t\t\t\t\tthrow new Error(\"Remote backend is already configured\");\n\t\t\t\t}\n\t\t\t\treturn this._activateSessionRemoteOperations(currentInfo.cwd, (operations) =>\n\t\t\t\t\toperations.configureRemote(options.url, { token: options.token }),\n\t\t\t\t);\n\t\t\t}),\n\t\t);\n\t}\n\n\tasync activateSandboxDaemon(options: ActivateSandboxDaemonOptions): Promise<ToolBackendInfo> {\n\t\treturn this._runActivity(\"sandbox daemon activation\", () =>\n\t\t\tthis._enqueueSandboxTransition(() =>\n\t\t\t\tthis._activateSessionRemoteOperations(options.expectedCwd, (operations) =>\n\t\t\t\t\toperations.configureRemote(options.url, { token: options.token, expectedCwd: options.expectedCwd }),\n\t\t\t\t),\n\t\t\t),\n\t\t);\n\t}\n\n\tprivate async _activateSessionRemoteOperations(\n\t\texpectedCwd: string,\n\t\tconfigure: (operations: DeferredRemoteToolOperations) => Promise<ToolBackendInfo>,\n\t): Promise<ToolBackendInfo> {\n\t\tif (this._sandboxToolOperations) throw new Error(\"A session sandbox backend is already active\");\n\t\tconst previousToolOperations = this._toolOperations;\n\t\tconst operations = new DeferredRemoteToolOperations(expectedCwd);\n\t\ttry {\n\t\t\tawait configure(operations);\n\t\t} catch (error) {\n\t\t\tawait operations.dispose();\n\t\t\tthrow error;\n\t\t}\n\t\tthis._preSandboxToolOperations = previousToolOperations;\n\t\tthis._toolOperations = operations;\n\t\tthis._sandboxToolOperations = operations;\n\t\ttry {\n\t\t\tawait this.applyToolOperationsBackendChange(operations);\n\t\t} catch (error) {\n\t\t\tthis._toolOperations = previousToolOperations;\n\t\t\tthis._sandboxToolOperations = undefined;\n\t\t\tthis._preSandboxToolOperations = undefined;\n\t\t\tlet rollbackError: unknown;\n\t\t\ttry {\n\t\t\t\tawait this.applyToolOperationsBackendChange(this.getToolOperations());\n\t\t\t} catch (caught) {\n\t\t\t\trollbackError = caught;\n\t\t\t}\n\t\t\tawait operations.dispose();\n\t\t\tif (rollbackError) {\n\t\t\t\tthrow new AggregateError([error, rollbackError], \"Sandbox activation and backend rollback both failed\");\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t\treturn operations.getBackendInfo();\n\t}\n\n\tasync clearRemoteSandbox(): Promise<void> {\n\t\treturn this._runActivity(\"remote backend clearing\", () =>\n\t\t\tthis._enqueueSandboxTransition(async () => {\n\t\t\t\tconst operations = this._sandboxToolOperations;\n\t\t\t\tif (!operations || operations !== this._toolOperations) {\n\t\t\t\t\tthrow new Error(\"No session sandbox backend is active\");\n\t\t\t\t}\n\t\t\t\tconst previousToolOperations = this._preSandboxToolOperations;\n\t\t\t\tthis._toolOperations = previousToolOperations;\n\t\t\t\tthis._sandboxToolOperations = undefined;\n\t\t\t\tthis._preSandboxToolOperations = undefined;\n\t\t\t\ttry {\n\t\t\t\t\tawait this.applyToolOperationsBackendChange(this.getToolOperations());\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthis._toolOperations = operations;\n\t\t\t\t\tthis._sandboxToolOperations = operations;\n\t\t\t\t\tthis._preSandboxToolOperations = previousToolOperations;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait this.applyToolOperationsBackendChange(operations);\n\t\t\t\t\t} catch (rollbackError) {\n\t\t\t\t\t\tthrow new AggregateError([error, rollbackError], \"Sandbox clearing and backend rollback both failed\");\n\t\t\t\t\t}\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\tawait operations.dispose();\n\t\t\t}),\n\t\t);\n\t}\n\n\tprivate _enqueueSandboxTransition<T>(operation: () => Promise<T>): Promise<T> {\n\t\tconst result = this._sandboxTransitionQueue.then(operation, operation);\n\t\tthis._sandboxTransitionQueue = result.then(\n\t\t\t() => undefined,\n\t\t\t() => undefined,\n\t\t);\n\t\treturn result;\n\t}\n\n\tasync uploadFile(sourcePath: string, destinationPath: string): Promise<void> {\n\t\treturn this._runActivity(\"remote file upload\", async () => {\n\t\t\tconst operations = this.getToolOperations();\n\t\t\tif (!operations.uploadFile) throw new Error(\"Active sandbox backend does not support file upload\");\n\t\t\tawait operations.uploadFile(sourcePath, destinationPath);\n\t\t});\n\t}\n\n\tasync downloadFile(sourcePath: string, destinationPath: string): Promise<void> {\n\t\treturn this._runActivity(\"remote file download\", async () => {\n\t\t\tconst operations = this.getToolOperations();\n\t\t\tif (!operations.downloadFile) throw new Error(\"Active sandbox backend does not support file download\");\n\t\t\tawait operations.downloadFile(sourcePath, destinationPath);\n\t\t});\n\t}\n\n\tprivate async applyToolOperationsBackendChange(operations: ToolOperations): Promise<void> {\n\t\tthis._resourceLoader.setToolOperations?.(operations);\n\t\tawait this._lspRuntimeState?.manager.setToolOperations(operations);\n\t\tawait this.reload();\n\t}\n\n\tprivate async _getRequiredRequestAuth(model: Model<any>): Promise<RequiredRequestAuth> {\n\t\tconst result = await this._modelRegistry.getApiKeyAndHeaders(model);\n\t\tif (!result.ok) {\n\t\t\tif (result.error.startsWith(\"No API key found\")) {\n\t\t\t\tthrow new Error(formatNoApiKeyFoundMessage(model.provider));\n\t\t\t}\n\t\t\tthrow new Error(result.error);\n\t\t}\n\t\tif (result.apiKey) {\n\t\t\treturn { apiKey: result.apiKey, headers: result.headers };\n\t\t}\n\n\t\tconst isOAuth = this._modelRegistry.isUsingOAuth(model);\n\t\tif (isOAuth) {\n\t\t\tthrow new Error(\n\t\t\t\t`Authentication failed for \"${model.provider}\". ` +\n\t\t\t\t\t`Credentials may have expired or network is unavailable. ` +\n\t\t\t\t\t`Run '/login ${model.provider}' to re-authenticate.`,\n\t\t\t);\n\t\t}\n\t\tthrow new Error(formatNoApiKeyFoundMessage(model.provider));\n\t}\n\n\tprivate async _getCompactionRequestAuth(model: Model<any>): Promise<CompactionRequestAuth> {\n\t\tif (this.agent.streamFn === streamSimple) {\n\t\t\treturn this._getRequiredRequestAuth(model);\n\t\t}\n\n\t\tconst result = await this._modelRegistry.getApiKeyAndHeaders(model);\n\t\treturn result.ok ? { apiKey: result.apiKey, headers: result.headers } : {};\n\t}\n\n\tprivate _hookCommon(event: HookEventName, promptId = this._activePromptId) {\n\t\tconst reference = this.session.getSessionReference();\n\t\tconst hostPathApi = process.platform === \"win32\" ? win32 : posix;\n\t\treturn {\n\t\t\tsession_id: this.sessionId,\n\t\t\t// Session references are host-owned. Normalize lexically; never probe a remote filesystem.\n\t\t\ttranscript_path: reference ? (process.platform === \"win32\" ? win32 : posix).normalize(reference) : \"\",\n\t\t\tcwd: hostPathApi.normalize(this._cwd),\n\t\t\thook_event_name: event,\n\t\t\t...(promptId ? { prompt_id: promptId } : {}),\n\t\t};\n\t}\n\tprivate _emitHookExecution(notice: HookExecutionNotice): void {\n\t\tthis.session.appendCustomMessageEntry(HOOK_EXECUTION_CUSTOM_TYPE, JSON.stringify(notice), true);\n\t\tfor (const listener of [...this._hookExecutionListeners]) {\n\t\t\ttry {\n\t\t\t\tlistener(notice);\n\t\t\t} catch {\n\t\t\t\t// UI observers must not affect hook behavior.\n\t\t\t}\n\t\t}\n\t}\n\tprivate _emitHookExecutionActivity(active: boolean): void {\n\t\tfor (const listener of [...this._hookExecutionActivityListeners]) {\n\t\t\ttry {\n\t\t\t\tlistener(active);\n\t\t\t} catch {\n\t\t\t\t// UI observers must not affect hook behavior.\n\t\t\t}\n\t\t}\n\t}\n\tprivate async _runHook(input: HookInput, activeSignal?: AbortSignal): Promise<HookAggregateResult> {\n\t\tconst loadedHooks = this._loadedHooks;\n\t\tif (!this._hooksEnabled || !loadedHooks?.handlers.some((handler) => handler.event === input.hook_event_name)) {\n\t\t\treturn {\n\t\t\t\tcontinue: true,\n\t\t\t\tblocked: false,\n\t\t\t\tadditionalContext: [],\n\t\t\t\tsystemMessages: [],\n\t\t\t\tplainText: [],\n\t\t\t\tresults: [],\n\t\t\t};\n\t\t}\n\t\tconst signals = [this._hookAbortController.signal, activeSignal].filter(\n\t\t\t(signal): signal is AbortSignal => signal !== undefined,\n\t\t);\n\t\tconst signal = signals.length === 1 ? signals[0] : AbortSignal.any(signals);\n\t\tthis._emitHookExecutionActivity(true);\n\t\ttry {\n\t\t\tconst aggregate = await runHooks(loadedHooks, input, {\n\t\t\t\t...this._hookRunOptions,\n\t\t\t\tsignal,\n\t\t\t\ttoolOperations: this.getToolOperations(),\n\t\t\t});\n\t\t\tfor (const result of aggregate.results) {\n\t\t\t\tif (result.diagnostic) this._onHookDiagnostic?.(result.diagnostic);\n\t\t\t}\n\t\t\tfor (const message of aggregate.systemMessages) {\n\t\t\t\tthis._reportHookDiagnostic({\n\t\t\t\t\tlevel: \"warning\",\n\t\t\t\t\tcode: \"system-message\",\n\t\t\t\t\tevent: input.hook_event_name,\n\t\t\t\t\tmessage: `Hook systemMessage: ${message}`,\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (aggregate.results.length > 0) this._emitHookExecution(createHookExecutionNotice(input, aggregate));\n\t\t\treturn aggregate;\n\t\t} finally {\n\t\t\tthis._emitHookExecutionActivity(false);\n\t\t}\n\t}\n\n\tprivate _hasMatchingToolHook(event: ToolHookEventName, toolName: string): boolean {\n\t\tif (!this._hooksEnabled) return false;\n\t\tconst handlerMayMatchTool = (condition: string | undefined): boolean => {\n\t\t\tif (condition === undefined) return true;\n\t\t\tconst open = condition.indexOf(\"(\");\n\t\t\treturn open < 1\n\t\t\t\t? matchHookValue(condition, toolName).matches\n\t\t\t\t: condition.endsWith(\")\") && condition.slice(0, open) === toolName;\n\t\t};\n\t\treturn (\n\t\t\tthis._loadedHooks?.handlers.some(\n\t\t\t\t(handler) =>\n\t\t\t\t\thandler.event === event &&\n\t\t\t\t\tmatchHookValue(handler.matcher, toolName).matches &&\n\t\t\t\t\thandlerMayMatchTool(handler.handler.if),\n\t\t\t) ?? false\n\t\t);\n\t}\n\n\tprivate _hookContextMessage(context: readonly string[]): CustomMessage | undefined {\n\t\tif (context.length === 0) return undefined;\n\t\treturn {\n\t\t\trole: \"custom\",\n\t\t\tcustomType: \"claude-hook-context\",\n\t\t\tcontent: [{ type: \"text\", text: context.join(\"\\n\\n\") }],\n\t\t\tdisplay: false,\n\t\t\ttimestamp: Date.now(),\n\t\t};\n\t}\n\n\tprivate async _ensureHookSessionStarted(promptId: string): Promise<CustomMessage | undefined> {\n\t\tthis._hookSessionStartPromise ??= (async () => {\n\t\t\tconst reason = this._sessionStartEvent.reason;\n\t\t\tconst source =\n\t\t\t\treason === \"resume\" ? \"resume\" : reason === \"fork\" ? \"fork\" : reason === \"new\" ? \"clear\" : \"startup\";\n\t\t\tconst result = await this._runHook({\n\t\t\t\t...this._hookCommon(\"SessionStart\", promptId),\n\t\t\t\thook_event_name: \"SessionStart\",\n\t\t\t\tsource,\n\t\t\t\tmodel: this.model ? `${this.model.provider}/${this.model.id}` : undefined,\n\t\t\t\tsession_title: this.sessionName,\n\t\t\t});\n\t\t\tif (!result.continue) {\n\t\t\t\tthis._hookSessionStartTerminationReason = result.reason ?? \"Session start terminated by hook\";\n\t\t\t\tthrow new Error(this._hookSessionStartTerminationReason);\n\t\t\t}\n\t\t\tthis._pendingHookSessionStartContext = this._hookContextMessage(result.additionalContext);\n\t\t\treturn this._pendingHookSessionStartContext;\n\t\t})();\n\t\treturn await this._hookSessionStartPromise;\n\t}\n\n\tprivate _reportHookDiagnostic(diagnostic: HookDiagnostic): void {\n\t\tthis._onHookDiagnostic?.(diagnostic);\n\t}\n\n\tprivate _adaptPreToolHookInput(execution: PreToolHookExecution): PreToolInputAdaptation {\n\t\ttry {\n\t\t\tconst jsonArgs = toJsonValue(execution.args) as JsonObject;\n\t\t\tconst input = execution.adaptToolInput\n\t\t\t\t? adaptFileToolInputForClaudeHook(\n\t\t\t\t\t\texecution.toolCall.name,\n\t\t\t\t\t\tjsonArgs,\n\t\t\t\t\t\texecution.backend.cwd,\n\t\t\t\t\t\texecution.flavor,\n\t\t\t\t\t\thookPathHome(execution.backend, execution.flavor),\n\t\t\t\t\t)\n\t\t\t\t: jsonArgs;\n\t\t\treturn { ok: true, input };\n\t\t} catch (error) {\n\t\t\tconst reason = `Hook file path adaptation failed closed: ${error instanceof Error ? error.message : String(error)}`;\n\t\t\tthis._reportHookDiagnostic({ level: \"error\", code: \"policy\", event: \"PreToolUse\", message: reason });\n\t\t\treturn { ok: false, blockResult: { block: true, reason } };\n\t\t}\n\t}\n\n\tprivate _resolvePreToolHookPolicy(hookResult: HookAggregateResult): BeforeToolCallResult | undefined {\n\t\tif (!hookResult.continue) {\n\t\t\tthis.agent.abort();\n\t\t\treturn { block: true, reason: hookResult.reason ?? \"Hook terminated the active agent loop\" };\n\t\t}\n\t\tif (hookResult.permissionDecision === \"ask\" || hookResult.permissionDecision === \"defer\") {\n\t\t\tconst reason =\n\t\t\t\thookResult.permissionDecision === \"ask\"\n\t\t\t\t\t? \"Tool execution blocked: hook requested permission, but no permission engine is available\"\n\t\t\t\t\t: \"Tool execution blocked: hook deferred permission, but no permission engine is available\";\n\t\t\treturn {\n\t\t\t\tblock: true,\n\t\t\t\treason: capModelVisibleHookField([reason, ...hookResult.additionalContext].join(\"\\n\\n\")),\n\t\t\t};\n\t\t}\n\t\tif (!hookResult.blocked && hookResult.permissionDecision !== \"deny\") return undefined;\n\t\tconst feedback = [\n\t\t\t...new Set(\n\t\t\t\t[hookResult.reason, ...hookResult.additionalContext].filter(\n\t\t\t\t\t(value): value is string => value !== undefined && value.length > 0,\n\t\t\t\t),\n\t\t\t),\n\t\t];\n\t\treturn {\n\t\t\tblock: true,\n\t\t\treason: feedback.length\n\t\t\t\t? capModelVisibleHookField(feedback.map(capModelVisibleHookField).join(\"\\n\\n\"))\n\t\t\t\t: \"Tool execution denied by hook\",\n\t\t};\n\t}\n\n\tprivate _applyPreToolHookInputUpdate(\n\t\texecution: PreToolHookExecution,\n\t\thookResult: HookAggregateResult,\n\t): BeforeToolCallResult | undefined {\n\t\tif (!hookResult.updatedInput) return undefined;\n\t\tconst tool = execution.agentContext.tools?.find((candidate) => candidate.name === execution.toolCall.name);\n\t\tconst mutableArgs = execution.args;\n\t\tif (!tool || !isRecord(mutableArgs)) {\n\t\t\tthis._reportHookDiagnostic({\n\t\t\t\tlevel: \"error\",\n\t\t\t\tcode: \"unsupported-update\",\n\t\t\t\tevent: \"PreToolUse\",\n\t\t\t\tmessage: `Hook replaced input for ${execution.toolCall.name}, but validated replacement is unsupported`,\n\t\t\t});\n\t\t\treturn { block: true, reason: \"Hook tool input replacement could not be validated\" };\n\t\t}\n\t\ttry {\n\t\t\tconst replacement = execution.adaptToolInput\n\t\t\t\t? adaptFileToolUpdatedInputFromClaudeHook(\n\t\t\t\t\t\texecution.toolCall.name,\n\t\t\t\t\t\thookResult.updatedInput,\n\t\t\t\t\t\texecution.backend.cwd,\n\t\t\t\t\t\texecution.flavor,\n\t\t\t\t\t\thookPathHome(execution.backend, execution.flavor),\n\t\t\t\t\t)\n\t\t\t\t: hookResult.updatedInput;\n\t\t\tconst validated = validateToolArguments(tool, { ...execution.toolCall, arguments: replacement });\n\t\t\tif (!isRecord(validated)) throw new Error(\"replacement did not validate to an object\");\n\t\t\tfor (const key of Object.keys(mutableArgs)) delete mutableArgs[key];\n\t\t\tObject.assign(mutableArgs, validated);\n\t\t\treturn undefined;\n\t\t} catch (error) {\n\t\t\tthis._reportHookDiagnostic({\n\t\t\t\tlevel: \"error\",\n\t\t\t\tcode: \"unsupported-update\",\n\t\t\t\tevent: \"PreToolUse\",\n\t\t\t\tmessage: `Hook replacement for ${execution.toolCall.name} failed validation: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t});\n\t\t\treturn { block: true, reason: \"Hook tool input replacement failed validation\" };\n\t\t}\n\t}\n\n\tprivate async _emitExtensionToolCall(execution: PreToolHookExecution): Promise<BeforeToolCallResult | undefined> {\n\t\tconst runner = this._extensionRunner;\n\t\tif (!runner.hasHandlers(\"tool_call\")) return undefined;\n\t\ttry {\n\t\t\tconst extensionResult = await runner.emitToolCall({\n\t\t\t\ttype: \"tool_call\",\n\t\t\t\ttoolName: execution.toolCall.name,\n\t\t\t\ttoolCallId: execution.toolCall.id,\n\t\t\t\tinput: execution.args as Record<string, unknown>,\n\t\t\t});\n\t\t\tif (extensionResult?.block) {\n\t\t\t\tthis._toolStartedAt.delete(execution.toolCall.id);\n\t\t\t\tthis._preToolHookContext.delete(execution.toolCall.id);\n\t\t\t}\n\t\t\treturn extensionResult;\n\t\t} catch (error) {\n\t\t\tthis._toolStartedAt.delete(execution.toolCall.id);\n\t\t\tthis._preToolHookContext.delete(execution.toolCall.id);\n\t\t\tif (error instanceof Error) throw error;\n\t\t\tthrow new Error(`Extension failed, blocking execution: ${String(error)}`);\n\t\t}\n\t}\n\n\tprivate async _handleBeforeToolCall(\n\t\t{ toolCall, args, context: agentContext }: BeforeToolCallContext,\n\t\tsignal?: AbortSignal,\n\t): Promise<BeforeToolCallResult | undefined> {\n\t\tconst backend = this.getToolBackendInfo();\n\t\tconst flavor = hookPathFlavor(backend);\n\t\tconst normalizedToolName = claudeToolName(toolCall.name);\n\t\tconst execution: PreToolHookExecution = {\n\t\t\ttoolCall,\n\t\t\targs,\n\t\t\tagentContext,\n\t\t\tbackend,\n\t\t\tflavor,\n\t\t\tnormalizedToolName,\n\t\t\tadaptToolInput: this._hasMatchingToolHook(\"PreToolUse\", normalizedToolName),\n\t\t};\n\t\tconst adaptation = this._adaptPreToolHookInput(execution);\n\t\tif (!adaptation.ok) return adaptation.blockResult;\n\t\tconst hookResult = await this._runHook(\n\t\t\t{\n\t\t\t\t...this._hookCommon(\"PreToolUse\"),\n\t\t\t\thook_event_name: \"PreToolUse\",\n\t\t\t\ttool_name: normalizedToolName,\n\t\t\t\ttool_input: adaptation.input,\n\t\t\t\ttool_use_id: toolCall.id,\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t\tconst policyBlock = this._resolvePreToolHookPolicy(hookResult);\n\t\tif (policyBlock) return policyBlock;\n\t\tconst updateBlock = this._applyPreToolHookInputUpdate(execution, hookResult);\n\t\tif (updateBlock) return updateBlock;\n\t\tthis._toolStartedAt.set(toolCall.id, Date.now());\n\t\tif (hookResult.additionalContext.length > 0) {\n\t\t\tthis._preToolHookContext.set(toolCall.id, [...hookResult.additionalContext]);\n\t\t}\n\t\treturn this._emitExtensionToolCall(execution);\n\t}\n\n\tprivate async _applyExtensionToolResult(state: PostToolCallState): Promise<void> {\n\t\tconst runner = this._extensionRunner;\n\t\tif (!runner.hasHandlers(\"tool_result\")) return;\n\t\tconst extensionResult = await runner.emitToolResult({\n\t\t\ttype: \"tool_result\",\n\t\t\ttoolName: state.toolCall.name,\n\t\t\ttoolCallId: state.toolCall.id,\n\t\t\tinput: state.args as Record<string, unknown>,\n\t\t\tcontent: state.content,\n\t\t\tdetails: state.details,\n\t\t\tisError: state.isError,\n\t\t});\n\t\tif (!extensionResult) return;\n\t\tstate.content = extensionResult.content ?? state.content;\n\t\tstate.details = extensionResult.details;\n\t\tstate.isError = extensionResult.isError ?? state.isError;\n\t}\n\n\tprivate async _runPostToolHook(\n\t\tstate: PostToolCallState,\n\t\tstarted: number | undefined,\n\t\tsignal?: AbortSignal,\n\t): Promise<PostToolHookRunResult> {\n\t\tconst event: PostToolHookEvent = state.isError ? \"PostToolUseFailure\" : \"PostToolUse\";\n\t\tconst normalizedToolName = claudeToolName(state.toolCall.name);\n\t\tconst backend = this.getToolBackendInfo();\n\t\tconst flavor = hookPathFlavor(backend);\n\t\tconst jsonArgs = toJsonValue(state.args) as JsonObject;\n\t\tlet toolInput = jsonArgs;\n\t\tif (this._hasMatchingToolHook(event, normalizedToolName)) {\n\t\t\ttry {\n\t\t\t\ttoolInput = adaptFileToolInputForClaudeHook(\n\t\t\t\t\tstate.toolCall.name,\n\t\t\t\t\tjsonArgs,\n\t\t\t\t\tbackend.cwd,\n\t\t\t\t\tflavor,\n\t\t\t\t\thookPathHome(backend, flavor),\n\t\t\t\t);\n\t\t\t} catch (error) {\n\t\t\t\tthis._preToolHookContext.delete(state.toolCall.id);\n\t\t\t\tthis._reportHookDiagnostic({\n\t\t\t\t\tlevel: \"warning\",\n\t\t\t\t\tcode: \"execution\",\n\t\t\t\t\tevent,\n\t\t\t\t\tmessage: `Skipped ${event} for ${state.toolCall.name}: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t\t});\n\t\t\t\treturn { ok: false };\n\t\t\t}\n\t\t}\n\t\tconst common = {\n\t\t\t...this._hookCommon(event),\n\t\t\ttool_name: normalizedToolName,\n\t\t\ttool_input: toolInput,\n\t\t\ttool_use_id: state.toolCall.id,\n\t\t\t...(started ? { duration_ms: Date.now() - started } : {}),\n\t\t};\n\t\tconst hookResult = state.isError\n\t\t\t? await this._runHook(\n\t\t\t\t\t{\n\t\t\t\t\t\t...common,\n\t\t\t\t\t\thook_event_name: \"PostToolUseFailure\",\n\t\t\t\t\t\terror: state.content.map((part) => (part.type === \"text\" ? part.text : \"[image]\")).join(\"\\n\"),\n\t\t\t\t\t},\n\t\t\t\t\tsignal,\n\t\t\t\t)\n\t\t\t: await this._runHook(\n\t\t\t\t\t{\n\t\t\t\t\t\t...common,\n\t\t\t\t\t\thook_event_name: \"PostToolUse\",\n\t\t\t\t\t\ttool_response: toJsonValue({ content: state.content, details: state.details }),\n\t\t\t\t\t},\n\t\t\t\t\tsignal,\n\t\t\t\t);\n\t\treturn { ok: true, hookResult };\n\t}\n\n\tprivate _applyPostToolOutputUpdate(state: PostToolCallState, hookResult: HookAggregateResult): void {\n\t\tif (hookResult.updatedToolOutput === undefined) return;\n\t\tconst builtIn = Object.hasOwn(CLAUDE_TOOL_NAMES, state.toolCall.name);\n\t\tconst replacement =\n\t\t\tbuiltIn && typeof hookResult.updatedToolOutput !== \"string\"\n\t\t\t\t? undefined\n\t\t\t\t: normalizeHookToolOutput(hookResult.updatedToolOutput);\n\t\tif (replacement) {\n\t\t\tstate.content = replacement.content;\n\t\t\tif (replacement.details !== undefined) state.details = replacement.details;\n\t\t\treturn;\n\t\t}\n\t\tthis._reportHookDiagnostic({\n\t\t\tlevel: \"warning\",\n\t\t\tcode: \"unsupported-update\",\n\t\t\tevent: state.isError ? \"PostToolUseFailure\" : \"PostToolUse\",\n\t\t\tmessage: builtIn\n\t\t\t\t? \"Unsupported built-in tool output replacement was ignored; only a string replacement is safe\"\n\t\t\t\t: \"Invalid tool output replacement was ignored; expected a string or { content } result shape\",\n\t\t});\n\t}\n\n\tprivate _finalizePostToolHookResult(state: PostToolCallState, hookResult: HookAggregateResult): AfterToolCallResult {\n\t\tconst feedback = [\n\t\t\t...(this._preToolHookContext.get(state.toolCall.id) ?? []),\n\t\t\t...hookResult.additionalContext,\n\t\t\t...(hookResult.blocked && hookResult.reason ? [hookResult.reason] : []),\n\t\t];\n\t\tthis._preToolHookContext.delete(state.toolCall.id);\n\t\tif (feedback.length > 0) {\n\t\t\tstate.content = [\n\t\t\t\t...state.content,\n\t\t\t\t{ type: \"text\", text: `Hook feedback:\\n${[...new Set(feedback)].join(\"\\n\\n\")}` },\n\t\t\t];\n\t\t}\n\t\tif (!hookResult.continue) this.agent.abort();\n\t\treturn { content: state.content, details: state.details, isError: state.isError };\n\t}\n\n\tprivate async _handleAfterToolCall(\n\t\t{ toolCall, args, result, isError }: AfterToolCallContext,\n\t\tsignal?: AbortSignal,\n\t): Promise<AfterToolCallResult> {\n\t\tthis._loadAssociatedToolsForReadToolCall(toolCall.name, args, isError);\n\t\tconst lspResult = await this._syncLspToolResult(toolCall.name, args, result, isError);\n\t\tconst state: PostToolCallState = {\n\t\t\ttoolCall,\n\t\t\targs,\n\t\t\tcontent: lspResult?.content ?? result.content,\n\t\t\tdetails: lspResult?.details ?? result.details,\n\t\t\tisError: lspResult?.isError ?? isError,\n\t\t};\n\t\tawait this._applyExtensionToolResult(state);\n\t\tconst started = this._toolStartedAt.get(toolCall.id);\n\t\tthis._toolStartedAt.delete(toolCall.id);\n\t\tconst hookRun = await this._runPostToolHook(state, started, signal);\n\t\tif (!hookRun.ok) {\n\t\t\treturn { content: state.content, details: state.details, isError: state.isError };\n\t\t}\n\t\tthis._applyPostToolOutputUpdate(state, hookRun.hookResult);\n\t\treturn this._finalizePostToolHookResult(state, hookRun.hookResult);\n\t}\n\n\t/**\n\t * Install tool hooks once on the Agent instance.\n\t *\n\t * The callbacks read `this._extensionRunner` at execution time, so extension reload swaps in the\n\t * new runner without reinstalling hooks. Extension-specific tool wrappers are still used to adapt\n\t * registered tool execution to the extension context. Tool call and tool result interception now\n\t * happens here instead of in wrappers.\n\t */\n\tprivate _installAgentToolHooks(): void {\n\t\tthis.agent.beforeToolCall = (context, signal) => this._handleBeforeToolCall(context, signal);\n\n\t\t// Deterministic post order: built-in LSP synchronization, extension tool_result, then Claude PostToolUse*.\n\t\tthis.agent.afterToolCall = (context, signal) => this._handleAfterToolCall(context, signal);\n\t}\n\n\tprivate _beginActivity(kind: string): SessionActivityToken {\n\t\tconst token: SessionActivityToken = { active: true, kind };\n\t\tthis._activities.add(token);\n\t\tthis._activityGeneration++;\n\t\tthis._activeCycle = true;\n\t\treturn token;\n\t}\n\n\tprivate _endActivity(token: SessionActivityToken): void {\n\t\tif (!token.active) return;\n\t\ttoken.active = false;\n\t\tthis._activities.delete(token);\n\t\tthis._activityGeneration++;\n\t\tthis._scheduleSettlementCheck();\n\t}\n\n\tprivate _runActivity<T>(kind: string, operation: () => Promise<T>): Promise<T> {\n\t\tif (this._disposed) return Promise.reject(new Error(\"Agent session is disposed\"));\n\t\tconst token = this._beginActivity(kind);\n\t\tconst owner: BarrierOwnerScope = { active: true, label: kind };\n\t\treturn this._barrierOwnerContext.run(owner, async () => {\n\t\t\ttry {\n\t\t\t\treturn await operation();\n\t\t\t} finally {\n\t\t\t\towner.active = false;\n\t\t\t\tthis._endActivity(token);\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate _runSynchronousActivity<T>(kind: string, operation: () => T): T {\n\t\tconst token = this._beginActivity(kind);\n\t\ttry {\n\t\t\treturn this._runBarrierCallback(kind, operation);\n\t\t} finally {\n\t\t\tthis._endActivity(token);\n\t\t}\n\t}\n\n\tprivate _runAsyncBarrierCallback<T>(label: string, callback: () => Promise<T>): Promise<T> {\n\t\tconst owner: BarrierOwnerScope = { active: true, label };\n\t\treturn this._barrierOwnerContext.run(owner, async () => {\n\t\t\ttry {\n\t\t\t\treturn await callback();\n\t\t\t} finally {\n\t\t\t\towner.active = false;\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate _runBarrierCallback<T>(label: string, callback: () => T): T {\n\t\tconst owner: BarrierOwnerScope = { active: true, label };\n\t\treturn this._barrierOwnerContext.run(owner, () => {\n\t\t\ttry {\n\t\t\t\treturn callback();\n\t\t\t} finally {\n\t\t\t\towner.active = false;\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate _isSettledPredicate(): boolean {\n\t\treturn (\n\t\t\tthis._activities.size === 0 &&\n\t\t\t!this.agent.state.isStreaming &&\n\t\t\tthis._retryAbortController === undefined &&\n\t\t\tthis._compactionInFlight === undefined &&\n\t\t\tthis._compactionAbortController === undefined &&\n\t\t\tthis._autoCompactionAbortController === undefined &&\n\t\t\tthis._branchSummaryAbortController === undefined &&\n\t\t\tthis._extensionCompactionQueue.length === 0 &&\n\t\t\tthis._extensionCompactionTimer === undefined &&\n\t\t\t!this._extensionCompactionRunning &&\n\t\t\tthis._bashAbortControllers.size === 0 &&\n\t\t\tthis._pendingBashMessages.length === 0 &&\n\t\t\tthis._steeringMessages.length === 0 &&\n\t\t\tthis._followUpMessages.length === 0 &&\n\t\t\t!this.agent.hasQueuedMessages()\n\t\t);\n\t}\n\n\tprivate _scheduleSettlementCheck(): void {\n\t\tif (this._settlementScheduled || this._settlementRunning) return;\n\t\tthis._settlementScheduled = true;\n\t\tqueueMicrotask(() => {\n\t\t\tthis._settlementScheduled = false;\n\t\t\tvoid this._drainSettlement();\n\t\t});\n\t}\n\tprivate _isCurrentSettlementGeneration(generation: number): boolean {\n\t\treturn this._isSettledPredicate() && generation === this._activityGeneration;\n\t}\n\n\tprivate async _finishSettledAgentCycle(generation: number): Promise<boolean> {\n\t\tif (!this._activeCycle) return true;\n\t\tawait this._runAsyncBarrierCallback(\"agent_settled extension handler\", () =>\n\t\t\tthis._extensionRunner.emit({ type: \"agent_settled\" }),\n\t\t);\n\t\tif (!this._isCurrentSettlementGeneration(generation)) return false;\n\t\tthis._emitAgentSettled();\n\t\tif (!this._isCurrentSettlementGeneration(generation)) return false;\n\t\tthis._activeCycle = false;\n\t\treturn true;\n\t}\n\n\tprivate async _drainSettlement(): Promise<void> {\n\t\tif (this._settlementRunning) return;\n\t\tthis._settlementRunning = true;\n\t\ttry {\n\t\t\tlet finished = false;\n\t\t\twhile (this._isSettledPredicate()) {\n\t\t\t\tconst generation = this._activityGeneration;\n\t\t\t\tif (!(await this._finishSettledAgentCycle(generation))) continue;\n\t\t\t\tfinished = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (!finished) return;\n\t\t\tfor (const waiter of this._idleWaiters) waiter.resolve();\n\t\t\tthis._idleWaiters.clear();\n\t\t} finally {\n\t\t\tthis._settlementRunning = false;\n\t\t\tif ((this._activeCycle || this._idleWaiters.size > 0) && this._isSettledPredicate()) {\n\t\t\t\tthis._scheduleSettlementCheck();\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate _runRuntimeLifecycleOperation<T>(operation: () => Promise<T>): Promise<T> {\n\t\tconst token: RuntimeLifecycleOperationToken = {\n\t\t\tactive: true,\n\t\t\tchildren: new Set(),\n\t\t\tchildQueue: Promise.resolve(),\n\t\t};\n\t\treturn this._runtimeLifecycleContext.run(token, async () => {\n\t\t\ttry {\n\t\t\t\treturn await this._runAsyncBarrierCallback(\"runtime lifecycle operation\", operation);\n\t\t\t} finally {\n\t\t\t\twhile (token.children.size > 0) {\n\t\t\t\t\tawait Promise.allSettled([...token.children]);\n\t\t\t\t}\n\t\t\t\ttoken.active = false;\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate _enqueueRuntimeLifecycle<T>(operation: () => Promise<T>): Promise<T> {\n\t\tconst activity = this._beginActivity(\"runtime lifecycle\");\n\t\tconst currentToken = this._runtimeLifecycleContext.getStore();\n\t\tif (currentToken?.active) {\n\t\t\tconst child = currentToken.childQueue.then(() => this._runRuntimeLifecycleOperation(operation));\n\t\t\tcurrentToken.childQueue = child.then(\n\t\t\t\t() => undefined,\n\t\t\t\t() => undefined,\n\t\t\t);\n\t\t\tcurrentToken.children.add(child);\n\t\t\tvoid child.then(\n\t\t\t\t() => {\n\t\t\t\t\tcurrentToken.children.delete(child);\n\t\t\t\t\tthis._endActivity(activity);\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\tcurrentToken.children.delete(child);\n\t\t\t\t\tthis._endActivity(activity);\n\t\t\t\t},\n\t\t\t);\n\t\t\treturn child;\n\t\t}\n\t\tconst result = this._runtimeLifecycleQueue.then(() => this._runRuntimeLifecycleOperation(operation));\n\t\tthis._runtimeLifecycleQueue = result.then(\n\t\t\t() => undefined,\n\t\t\t() => undefined,\n\t\t);\n\t\tvoid result.then(\n\t\t\t() => this._endActivity(activity),\n\t\t\t() => this._endActivity(activity),\n\t\t);\n\t\treturn result;\n\t}\n\n\tprivate async _syncLspToolResult(\n\t\ttoolName: string,\n\t\targs: unknown,\n\t\tresult: LspToolResult,\n\t\tisError: boolean,\n\t): Promise<LspToolResultUpdate | undefined> {\n\t\tconst runtime = this._lspRuntimeState;\n\t\tif (isError || !runtime || typeof args !== \"object\" || args === null || !(\"path\" in args)) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst filePath = typeof args.path === \"string\" ? args.path : undefined;\n\t\tif (!filePath) return undefined;\n\n\t\ttry {\n\t\t\tif (toolName === \"read\") {\n\t\t\t\tawait runtime.fileSync.handleFileRead(filePath, this.getToolOperations());\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\tif (toolName === \"write\" || toolName === \"edit\") {\n\t\t\t\tawait runtime.fileSync.handleFileWrite(filePath, this.getToolOperations());\n\t\t\t\tconst diagnostics = await formatAutoDiagnosticsForChangedFile(runtime, filePath);\n\t\t\t\tif (!diagnostics) return undefined;\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [...result.content, { type: \"text\" as const, text: `\\n\\n${diagnostics}` }],\n\t\t\t\t\tdetails: result.details,\n\t\t\t\t\tisError,\n\t\t\t\t};\n\t\t\t}\n\t\t} catch {\n\t\t\t// LSP synchronization is best-effort and must not affect tool results.\n\t\t}\n\n\t\treturn undefined;\n\t}\n\tprivate _loadAssociatedInstructionTools(resource: InstructionResource): void {\n\t\tfor (const toolName of resource.tools ?? []) {\n\t\t\tthis.loadSessionTool(toolName);\n\t\t}\n\t}\n\n\tprivate _loadAssociatedToolsForReadToolCall(toolName: string, args: unknown, isError: boolean): void {\n\t\tif (toolName !== \"read\" || isError || typeof args !== \"object\" || args === null) {\n\t\t\treturn;\n\t\t}\n\t\tconst readArgs = args as { path?: unknown; file_path?: unknown };\n\t\tconst rawPath = typeof readArgs.path === \"string\" ? readArgs.path : readArgs.file_path;\n\t\tif (typeof rawPath !== \"string\" || rawPath.trim().length === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst readPath = canonicalizePath(resolvePath(rawPath, this._cwd));\n\t\tfor (const skill of this.getRegisteredSkills()) {\n\t\t\tif (canonicalizePath(resolvePath(skill.filePath, this._cwd)) === readPath) {\n\t\t\t\tthis._loadAssociatedInstructionTools(skill);\n\t\t\t}\n\t\t}\n\t\tfor (const rule of this.getRegisteredRules()) {\n\t\t\tif (canonicalizePath(resolvePath(rule.filePath, this._cwd)) === readPath) {\n\t\t\t\tthis._loadAssociatedInstructionTools(rule);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate _installAgentTurnPreparation(): void {\n\t\tconst previousPrepareNextTurnWithContext =\n\t\t\tthis.agent.prepareNextTurnWithContext ??\n\t\t\t(this.agent.prepareNextTurn\n\t\t\t\t? async (_turn: PrepareNextTurnContext, signal?: AbortSignal) => await this.agent.prepareNextTurn?.(signal)\n\t\t\t\t: undefined);\n\t\tthis.agent.prepareNextTurnWithContext = async (turn, signal) => {\n\t\t\tconst previousUpdate = await previousPrepareNextTurnWithContext?.(turn, signal);\n\t\t\tconst compacted = await this._drainExtensionCompactionQueue(\"between-turns\");\n\t\t\tconst contextReplaced = compacted || this._stateCompressionApplied;\n\t\t\tthis._stateCompressionApplied = false;\n\t\t\t// Rebuild the loop context so session-driven changes to tools and the prompt\n\t\t\t// take effect on the next turn within this run.\n\t\t\tconst context = this._buildCurrentAgentContext(\n\t\t\t\tcontextReplaced ? undefined : (previousUpdate?.context ?? turn.context),\n\t\t\t);\n\t\t\tif (this.compressionDetectionEnabled) {\n\t\t\t\tthis._compressionDetector.check(\n\t\t\t\t\tthis.getContextUsage()?.percent,\n\t\t\t\t\tcontext.messages,\n\t\t\t\t\tcontext.systemPrompt,\n\t\t\t\t\tgetCompressibleEntries(this.session, \"\"),\n\t\t\t\t);\n\t\t\t\tawait this._compressionDetector.waitForIdle(signal);\n\t\t\t}\n\t\t\treturn { ...previousUpdate, context } satisfies AgentLoopTurnUpdate;\n\t\t};\n\t}\n\n\tprivate _createCompressionAdvisory(): CustomMessage {\n\t\tconst suggested = this._compressionDetector.suggestedRange;\n\t\tconst urgency = this._compressionDetector.urgency;\n\t\tlet range = \"\";\n\t\tif (suggested && isValidSuggestedCompressionRange(this.session, suggested.startEntryId, suggested.endEntryId)) {\n\t\t\trange = ` Suggested inclusive range: startEntryId ${suggested.startEntryId}, endEntryId ${suggested.endEntryId}; verify before acting.`;\n\t\t\tconst model = this.model;\n\t\t\tconst estimate = model\n\t\t\t\t? estimateCompressionEconomics(\n\t\t\t\t\t\tgetCompressibleEntries(this.session, \"\"),\n\t\t\t\t\t\tsuggested.startEntryId,\n\t\t\t\t\t\tsuggested.endEntryId,\n\t\t\t\t\t\tmodel,\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\tthis._modelRegistry.isUsingOAuth(model),\n\t\t\t\t\t)\n\t\t\t\t: undefined;\n\t\t\tif (estimate) {\n\t\t\t\trange += ` Estimated if summary is 25% of range: ~${estimate.removedTokens} fewer context tokens per request`;\n\t\t\t\tif (estimate.breakEvenRequests != null)\n\t\t\t\t\trange += `, ~${estimate.breakEvenRequests} requests to break even at catalog rates`;\n\t\t\t\trange += \"; estimates depend on caching and future requests.\";\n\t\t\t}\n\t\t}\n\t\treturn createCustomMessage(\n\t\t\t\"compression_detection\",\n\t\t\t`[Background compression detector: COMPRESS${urgency === undefined ? \".\" : ` (urgency ${urgency}%).`}${range} At the next safe checkpoint, consider using compress_context to summarize older context. Preserve essential decisions and unfinished work. This is advisory; continue if details are still needed.]`,\n\t\t\tfalse,\n\t\t\t{ verdictCount: this._compressionDetector.counts.compress },\n\t\t\tnew Date().toISOString(),\n\t\t);\n\t}\n\n\t/** Add model-only entry ID notices without persisting them. */\n\tprivate _installContextMetadata(): void {\n\t\tconst previousTransformContext = this.agent.transformContext;\n\t\tthis.agent.transformContext = async (messages, signal) => {\n\t\t\tthis._waitingForDetectorAtRequest = true;\n\t\t\ttry {\n\t\t\t\tawait this._compressionDetector.waitForIdle(signal);\n\t\t\t} finally {\n\t\t\t\tthis._waitingForDetectorAtRequest = false;\n\t\t\t}\n\t\t\tconst transformed = (await previousTransformContext?.(messages, signal)) ?? messages;\n\t\t\tconst annotated = this.getActiveToolNames().includes(\"compress_context\")\n\t\t\t\t? annotateContextMetadata(transformed, this.session.getBranch())\n\t\t\t\t: transformed;\n\t\t\tif (!this.compressionDetectionEnabled || !this._compressionDetector.shouldCompress) return annotated;\n\t\t\tconst verdictCount = this._compressionDetector.counts.compress;\n\t\t\tif (\n\t\t\t\tannotated.some(\n\t\t\t\t\t(message) =>\n\t\t\t\t\t\tmessage.role === \"custom\" &&\n\t\t\t\t\t\tmessage.customType === \"compression_detection\" &&\n\t\t\t\t\t\t(message.details as { verdictCount?: number } | undefined)?.verdictCount === verdictCount,\n\t\t\t\t)\n\t\t\t)\n\t\t\t\treturn annotated;\n\t\t\treturn [...annotated, this._createCompressionAdvisory()];\n\t\t};\n\t}\n\n\tprivate _buildCurrentAgentContext(previousContext?: AgentContext): AgentContext {\n\t\treturn {\n\t\t\tsystemPrompt: this._systemPromptOverride ?? this._baseSystemPrompt,\n\t\t\tmessages: previousContext?.messages ?? this.agent.state.messages.slice(),\n\t\t\ttools: this.agent.state.tools.slice(),\n\t\t};\n\t}\n\n\t// =========================================================================\n\t// Event Subscription\n\t// =========================================================================\n\n\t/** Emit an event to all listeners */\n\tprivate _emit(event: AgentSessionEvent): void {\n\t\tthis._runBarrierCallback(`public ${event.type} listener`, () => {\n\t\t\tfor (const listener of this._eventListeners) listener(event);\n\t\t});\n\t}\n\n\tprivate _emitAgentSettled(): void {\n\t\tfor (const listener of [...this._eventListeners]) {\n\t\t\ttry {\n\t\t\t\tthis._runBarrierCallback(\"public agent_settled listener\", () => listener({ type: \"agent_settled\" }));\n\t\t\t} catch (error) {\n\t\t\t\ttry {\n\t\t\t\t\tthis._extensionRunner.emitError({\n\t\t\t\t\t\textensionPath: \"<session-subscriber>\",\n\t\t\t\t\t\tevent: \"agent_settled\",\n\t\t\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t});\n\t\t\t\t} catch {\n\t\t\t\t\t// Subscriber diagnostics must never prevent settlement or later listeners.\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate _emitQueueUpdate(): void {\n\t\tthis._emit({\n\t\t\ttype: \"queue_update\",\n\t\t\tsteering: [...this._steeringMessages],\n\t\t\tfollowUp: [...this._followUpMessages],\n\t\t});\n\t}\n\n\t// Track last assistant message for auto-compaction check\n\tprivate _lastAssistantMessage: AssistantMessage | undefined = undefined;\n\n\tprivate _consumeStartedQueuedMessage(event: AgentEvent): void {\n\t\tif (event.type !== \"message_start\" || event.message.role !== \"user\") return;\n\t\tthis._overflowRecoveryAttempted = false;\n\t\tconst messageText = this._getUserMessageText(event.message);\n\t\tif (!messageText) return;\n\n\t\tconst queuedIds = this._queuedPromptIds.get(messageText);\n\t\tconst queuedId = queuedIds?.shift();\n\t\tif (queuedId) this._activePromptId = queuedId;\n\t\tif (queuedIds?.length === 0) this._queuedPromptIds.delete(messageText);\n\n\t\tconst steeringIndex = this._steeringMessages.indexOf(messageText);\n\t\tif (steeringIndex !== -1) {\n\t\t\tthis._steeringMessages.splice(steeringIndex, 1);\n\t\t\tthis._emitQueueUpdate();\n\t\t\treturn;\n\t\t}\n\t\tconst followUpIndex = this._followUpMessages.indexOf(messageText);\n\t\tif (followUpIndex === -1) return;\n\t\tthis._followUpMessages.splice(followUpIndex, 1);\n\t\tthis._emitQueueUpdate();\n\t}\n\n\tprivate _persistCompletedMessage(message: AgentMessage): void {\n\t\tif (message.role === \"custom\") {\n\t\t\tif (message.customType === \"compression_detection\") return;\n\t\t\tthis.session.appendCustomMessageEntry(message.customType, message.content, message.display, message.details);\n\t\t\treturn;\n\t\t}\n\t\tif (message.role === \"user\" || message.role === \"assistant\" || message.role === \"toolResult\") {\n\t\t\tthis.session.appendMessage(message);\n\t\t}\n\t}\n\n\tprivate _applyPendingContextCompression(message: AgentMessage): void {\n\t\tconst pending = this._pendingContextCompression;\n\t\tif (!pending || message.role !== \"toolResult\" || message.toolCallId !== pending.toolCallId) return;\n\t\tthis._pendingContextCompression = undefined;\n\t\tif (message.isError) return;\n\n\t\tconst branch = this.session.getBranch();\n\t\tconst start = branch.find((entry) => entry.id === pending.startEntryId);\n\t\tconst assistant = branch.find((entry) => entry.id === pending.assistantEntryId);\n\t\tconst last = branch.at(-1);\n\t\tif (!start || !assistant || last?.type !== \"message\" || last.message !== message) return;\n\t\tconst assistantIndex = branch.indexOf(assistant);\n\t\tconst triggerIndex = branch\n\t\t\t.slice(0, assistantIndex)\n\t\t\t.map((entry) => entry.type === \"message\" && entry.message.role === \"user\")\n\t\t\t.lastIndexOf(true);\n\t\tif (triggerIndex < 0) return;\n\t\tconst earlierEntries = new Set(branch.slice(0, triggerIndex).map((entry) => entry.id));\n\t\tconst visible = getCompressibleEntries(this.session, pending.toolCallId).filter((entry) =>\n\t\t\tearlierEntries.has(entry.id),\n\t\t);\n\t\tconst startIndex = visible.findIndex((entry) => entry.id === start.id);\n\t\tconst endIndex = pending.endEntryId\n\t\t\t? visible.findIndex((entry) => entry.id === pending.endEntryId)\n\t\t\t: visible.length - 1;\n\t\tif (startIndex < 0 || endIndex < startIndex) return;\n\t\tconst economicsSnapshot = createCompressionSavingsSnapshot(branch);\n\t\t// The tool call and result were not in the request that produced this compression.\n\t\t// Only the earlier visible suffix could have been cached before the replacement.\n\t\tconst contextEntries = getCompressibleEntries(this.session, \"\");\n\t\tconst economicsEntries = contextEntries.slice(\n\t\t\t0,\n\t\t\tcontextEntries.findIndex((entry) => entry.id === assistant.id),\n\t\t);\n\t\tconst model = this.model;\n\t\tconst previous = this.session.buildSessionContext();\n\t\tconst oldLeafId = last.id;\n\t\tconst summaryEntryId = this.session.appendCustomMessageEntry(\"compress_context\", pending.summary, true, {\n\t\t\treplacementVersion: 1,\n\t\t\tfromId: oldLeafId,\n\t\t\tstartEntryId: start.id,\n\t\t\tendEntryId: visible[endIndex].id,\n\t\t});\n\t\tif (economicsSnapshot.totalRemovedTokens) {\n\t\t\tconst retainedIds = new Set(projectSessionContextEntries(this.session.getBranch()).map((entry) => entry.id));\n\t\t\teconomicsSnapshot.activeSavings = economicsSnapshot.activeSavings.filter((saving) =>\n\t\t\t\tretainedIds.has(saving.summaryEntryId),\n\t\t\t);\n\t\t\teconomicsSnapshot.liveRemovedTokens = economicsSnapshot.activeSavings.reduce(\n\t\t\t\t(total, saving) => total + saving.removedTokens,\n\t\t\t\t0,\n\t\t\t);\n\t\t\tthis.session.appendCustomEntry(COMPRESSION_SAVINGS_SNAPSHOT_TYPE, economicsSnapshot);\n\t\t}\n\t\tthis._recordCompressionEconomics(\n\t\t\teconomicsEntries,\n\t\t\tstart.id,\n\t\t\tvisible[endIndex].id,\n\t\t\tpending.summary,\n\t\t\tsummaryEntryId,\n\t\t\tmodel,\n\t\t);\n\t\tthis._restoreCompressionContextState(previous);\n\t\tthis.agent.state.messages = this.session.buildSessionContext().messages;\n\t\tthis._compressionDetector.reset(this.agent.state.messages, this.getContextUsage()?.percent ?? null);\n\t\tthis._stateCompressionApplied = true;\n\t\tthis._emit({ type: \"state_compressed\" });\n\t}\n\tprivate _recordCompressionEconomics(\n\t\tbranch: SessionEntry[],\n\t\tstartEntryId: string,\n\t\tendEntryId: string,\n\t\tsummary: string,\n\t\tsummaryEntryId: string,\n\t\tmodel: Model<Api> | undefined,\n\t): void {\n\t\tif (!model) return;\n\t\tconst economics = estimateCompressionEconomics(\n\t\t\tbranch,\n\t\t\tstartEntryId,\n\t\t\tendEntryId,\n\t\t\tmodel,\n\t\t\tsummary,\n\t\t\tthis._modelRegistry.isUsingOAuth(model),\n\t\t);\n\t\tif (economics)\n\t\t\tthis.session.appendCustomEntry(COMPRESSION_ECONOMICS_ENTRY_TYPE, { ...economics, version: 1, summaryEntryId });\n\t}\n\tprivate _restoreCompressionContextState(previous: SessionContext): void {\n\t\tconst rebuilt = this.session.buildSessionContext();\n\t\tif (\n\t\t\tprevious.model &&\n\t\t\t(previous.model.provider !== rebuilt.model?.provider || previous.model.modelId !== rebuilt.model?.modelId)\n\t\t) {\n\t\t\tthis.session.appendModelChange(previous.model.provider, previous.model.modelId);\n\t\t}\n\t\tif (previous.thinkingLevel !== rebuilt.thinkingLevel) {\n\t\t\tthis.session.appendThinkingLevelChange(previous.thinkingLevel);\n\t\t}\n\t}\n\tprivate _updateRetryStateAfterAssistantMessage(message: AgentMessage): void {\n\t\tif (message.role !== \"assistant\") return;\n\t\tconst assistantMessage = message as AssistantMessage;\n\t\tthis._lastAssistantMessage = assistantMessage;\n\t\tif (assistantMessage.stopReason === \"error\") return;\n\t\tthis._overflowRecoveryAttempted = false;\n\t\tif (this._retryAttempt === 0) return;\n\t\tthis._emit({\n\t\t\ttype: \"auto_retry_end\",\n\t\t\tsuccess: true,\n\t\t\tattempt: this._retryAttempt,\n\t\t});\n\t\tthis._retryAttempt = 0;\n\t}\n\n\t/** Internal handler for agent events - shared by subscribe and reconnect */\n\tprivate _handleAgentEvent = async (event: AgentEvent): Promise<void> => {\n\t\tif (event.type === \"agent_start\") this._agentRunEnding = false;\n\t\t// afterToolCall can be skipped when a prepared call is aborted. Never retain per-run tool state.\n\t\tif (event.type === \"agent_end\") {\n\t\t\tthis._agentRunEnding = true;\n\t\t\tthis._toolStartedAt.clear();\n\t\t\tthis._preToolHookContext.clear();\n\t\t\tthis._pendingContextCompression = undefined;\n\t\t}\n\t\tthis._consumeStartedQueuedMessage(event);\n\n\t\t// Extensions observe the event before public session listeners.\n\t\tawait this._runAsyncBarrierCallback(`extension ${event.type} handler`, () => this._emitExtensionEvent(event));\n\t\tthis._emit(event.type === \"agent_end\" ? { ...event, willRetry: this._willRetryAfterAgentEnd(event) } : event);\n\t\tif (event.type === \"agent_end\") {\n\t\t\tconst entries = this.session.getEntries();\n\t\t\tconst ledger = readUsageLedger(entries);\n\t\t\tconst lastEntry = entries.at(-1);\n\t\t\tif (ledger.checkpointId && !(lastEntry?.type === \"custom\" && lastEntry.customType === USAGE_LEDGER_ENTRY_TYPE))\n\t\t\t\tthis.session.appendCustomEntry(USAGE_LEDGER_ENTRY_TYPE, ledger);\n\t\t\tthis._scheduleExtensionCompactions();\n\t\t\tif (!this._disposed && this.compressionDetectionEnabled)\n\t\t\t\tthis._compressionDetector.check(\n\t\t\t\t\tthis.getContextUsage()?.percent,\n\t\t\t\t\tthis.agent.state.messages,\n\t\t\t\t\tthis._buildCurrentAgentContext().systemPrompt,\n\t\t\t\t\tgetCompressibleEntries(this.session, \"\"),\n\t\t\t\t);\n\t\t}\n\t\tif (event.type === \"message_end\") {\n\t\t\tthis._persistCompletedMessage(event.message);\n\t\t\tthis._applyPendingContextCompression(event.message);\n\t\t\tthis._updateRetryStateAfterAssistantMessage(event.message);\n\t\t}\n\t};\n\n\tprivate _willRetryAfterAgentEnd(event: CoreAgentEndEvent): boolean {\n\t\tconst settings = this.settingsManager.getRetrySettings();\n\t\tif (!settings.enabled || this._retryAttempt >= settings.maxRetries) {\n\t\t\treturn false;\n\t\t}\n\n\t\tfor (let i = event.messages.length - 1; i >= 0; i--) {\n\t\t\tconst message = event.messages[i];\n\t\t\tif (message.role === \"assistant\") {\n\t\t\t\treturn this._isRetryableError(message as AssistantMessage);\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\t/** Extract text content from a message */\n\tprivate _getUserMessageText(message: Message): string {\n\t\tif (message.role !== \"user\") return \"\";\n\t\tconst content = message.content;\n\t\tif (typeof content === \"string\") return content;\n\t\tconst textBlocks = content.filter((c) => c.type === \"text\");\n\t\treturn textBlocks.map((c) => (c as TextContent).text).join(\"\");\n\t}\n\n\t/** Find the last assistant message in agent state (including aborted ones) */\n\tprivate _findLastAssistantMessage(): AssistantMessage | undefined {\n\t\tconst messages = this.agent.state.messages;\n\t\tfor (let i = messages.length - 1; i >= 0; i--) {\n\t\t\tconst msg = messages[i];\n\t\t\tif (msg.role === \"assistant\") {\n\t\t\t\treturn msg as AssistantMessage;\n\t\t\t}\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tprivate _replaceMessageInPlace(target: AgentMessage, replacement: AgentMessage): void {\n\t\t// Agent-core stores the finalized message object in its state before emitting message_end.\n\t\t// Session persistence happens later in _handleAgentEvent() with event.message.\n\t\t// Mutating this object in place keeps agent state, later turn/agent events, listeners,\n\t\t// and the eventual SessionManager.appendMessage(event.message) persistence in sync.\n\t\tif (target === replacement) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst targetRecord = target as unknown as Record<string, unknown>;\n\t\tfor (const key of Object.keys(targetRecord)) {\n\t\t\tdelete targetRecord[key];\n\t\t}\n\t\tObject.assign(targetRecord, replacement);\n\t}\n\n\t/** Emit extension events based on agent events */\n\tprivate async _emitExtensionEvent(event: AgentEvent): Promise<void> {\n\t\tif (event.type === \"agent_start\") {\n\t\t\tthis._turnIndex = 0;\n\t\t\tawait this._extensionRunner.emit({ type: \"agent_start\" });\n\t\t} else if (event.type === \"agent_end\") {\n\t\t\tawait this._extensionRunner.emit({ type: \"agent_end\", messages: event.messages });\n\t\t} else if (event.type === \"turn_start\") {\n\t\t\tconst extensionEvent: TurnStartEvent = {\n\t\t\t\ttype: \"turn_start\",\n\t\t\t\tturnIndex: this._turnIndex,\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t};\n\t\t\tawait this._extensionRunner.emit(extensionEvent);\n\t\t} else if (event.type === \"turn_end\") {\n\t\t\tconst extensionEvent: TurnEndEvent = {\n\t\t\t\ttype: \"turn_end\",\n\t\t\t\tturnIndex: this._turnIndex,\n\t\t\t\tmessage: event.message,\n\t\t\t\ttoolResults: event.toolResults,\n\t\t\t};\n\t\t\tawait this._extensionRunner.emit(extensionEvent);\n\t\t\tthis._turnIndex++;\n\t\t} else if (event.type === \"message_start\") {\n\t\t\tconst extensionEvent: MessageStartEvent = {\n\t\t\t\ttype: \"message_start\",\n\t\t\t\tmessage: event.message,\n\t\t\t};\n\t\t\tawait this._extensionRunner.emit(extensionEvent);\n\t\t} else if (event.type === \"message_update\") {\n\t\t\tconst extensionEvent: MessageUpdateEvent = {\n\t\t\t\ttype: \"message_update\",\n\t\t\t\tmessage: event.message,\n\t\t\t\tassistantMessageEvent: event.assistantMessageEvent,\n\t\t\t};\n\t\t\tawait this._extensionRunner.emit(extensionEvent);\n\t\t} else if (event.type === \"message_end\") {\n\t\t\tconst extensionEvent: MessageEndEvent = {\n\t\t\t\ttype: \"message_end\",\n\t\t\t\tmessage: event.message,\n\t\t\t};\n\t\t\tconst replacement = await this._extensionRunner.emitMessageEnd(extensionEvent);\n\t\t\tif (replacement) {\n\t\t\t\tthis._replaceMessageInPlace(event.message, normalizeMessageContent(replacement));\n\t\t\t}\n\t\t} else if (event.type === \"tool_execution_start\") {\n\t\t\tconst extensionEvent: ToolExecutionStartEvent = {\n\t\t\t\ttype: \"tool_execution_start\",\n\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\ttoolName: event.toolName,\n\t\t\t\targs: event.args,\n\t\t\t};\n\t\t\tawait this._extensionRunner.emit(extensionEvent);\n\t\t} else if (event.type === \"tool_execution_update\") {\n\t\t\tconst extensionEvent: ToolExecutionUpdateEvent = {\n\t\t\t\ttype: \"tool_execution_update\",\n\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\ttoolName: event.toolName,\n\t\t\t\targs: event.args,\n\t\t\t\tpartialResult: event.partialResult,\n\t\t\t};\n\t\t\tawait this._extensionRunner.emit(extensionEvent);\n\t\t} else if (event.type === \"tool_execution_end\") {\n\t\t\tconst extensionEvent: ToolExecutionEndEvent = {\n\t\t\t\ttype: \"tool_execution_end\",\n\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\ttoolName: event.toolName,\n\t\t\t\tresult: event.result,\n\t\t\t\tisError: event.isError,\n\t\t\t};\n\t\t\tawait this._extensionRunner.emit(extensionEvent);\n\t\t}\n\t}\n\n\t/**\n\t * Subscribe to agent events.\n\t * Session persistence is handled internally (saves messages on message_end).\n\t * Multiple listeners can be added. Returns unsubscribe function for this listener.\n\t */\n\tsubscribe(listener: AgentSessionEventListener): () => void {\n\t\tthis._eventListeners.push(listener);\n\n\t\t// Return unsubscribe function for this specific listener\n\t\treturn () => {\n\t\t\tconst index = this._eventListeners.indexOf(listener);\n\t\t\tif (index !== -1) {\n\t\t\t\tthis._eventListeners.splice(index, 1);\n\t\t\t}\n\t\t};\n\t}\n\n\t/** Subscribe to completed hook executions for host UI rendering. Notices are not persisted or added to model context. */\n\tsubscribeToHookExecutions(listener: HookExecutionListener): () => void {\n\t\tthis._hookExecutionListeners.add(listener);\n\t\treturn () => this._hookExecutionListeners.delete(listener);\n\t}\n\n\t/** Subscribe to active hook execution changes for host UI rendering. */\n\tsubscribeToHookExecutionActivity(listener: HookExecutionActivityListener): () => void {\n\t\tthis._hookExecutionActivityListeners.add(listener);\n\t\treturn () => this._hookExecutionActivityListeners.delete(listener);\n\t}\n\t/** Whether this session will dispatch subsequent configured hook events. */\n\tget hooksEnabled(): boolean {\n\t\treturn this._hooksEnabled;\n\t}\n\n\t/** Enable or disable subsequent hook dispatches for this session. Active hook executions are not cancelled. */\n\tsetHooksEnabled(enabled: boolean): void {\n\t\tthis._hooksEnabled = enabled;\n\t}\n\t/** Disconnect from agent events during disposal. */\n\tprivate _disconnectFromAgent(): void {\n\t\tif (this._unsubscribeAgent) {\n\t\t\tthis._unsubscribeAgent();\n\t\t\tthis._unsubscribeAgent = undefined;\n\t\t}\n\t}\n\n\t/** Emit the bounded host-local SessionEnd hook once before replacement/disposal. */\n\tasync emitHookSessionEnd(reason: SessionEndReason): Promise<void> {\n\t\tif (this._hookSessionEnded) return;\n\t\tthis._hookSessionEnded = true;\n\t\tawait this._runHook({\n\t\t\t...this._hookCommon(\"SessionEnd\"),\n\t\t\thook_event_name: \"SessionEnd\",\n\t\t\treason,\n\t\t});\n\t}\n\n\t/**\n\t * Remove all listeners and disconnect from agent.\n\t * Call this when completely done with the session.\n\t */\n\tdispose(): Promise<void> {\n\t\tthis._disposed = true;\n\t\tthis._disposePromise ??= this._dispose();\n\t\treturn this._disposePromise;\n\t}\n\n\t/** Stop new prompt admission, abort active work, and wait for the session boundary. */\n\tprepareForShutdown(): Promise<void> {\n\t\tthis._disposed = true;\n\t\tthis._shutdownPreparationPromise ??= this._prepareForShutdown();\n\t\treturn this._shutdownPreparationPromise;\n\t}\n\n\tprivate async _prepareForShutdown(): Promise<void> {\n\t\tthis._compressionDetector.reset();\n\t\tthis._hookAbortController.abort();\n\t\tif (this._steeringMessages.length > 0 || this._followUpMessages.length > 0 || this.agent.hasQueuedMessages()) {\n\t\t\tthis.clearQueue();\n\t\t}\n\t\tfor (const abort of [\n\t\t\t() => this.abortRetry(),\n\t\t\t() => this.abortCompaction(),\n\t\t\t() => this.abortBranchSummary(),\n\t\t\t() => this.abortBash(),\n\t\t\t() => this.agent.abort(),\n\t\t]) {\n\t\t\ttry {\n\t\t\t\tabort();\n\t\t\t} catch {\n\t\t\t\t// Shutdown continues if an abort hook throws.\n\t\t\t}\n\t\t}\n\n\t\tif (this._extensionCompactionTimer !== undefined) {\n\t\t\tclearTimeout(this._extensionCompactionTimer);\n\t\t\tthis._extensionCompactionTimer = undefined;\n\t\t}\n\t\tthis._extensionCompactionQueue = [];\n\t\tif (this._extensionCompactionActivity) {\n\t\t\tthis._endActivity(this._extensionCompactionActivity);\n\t\t\tthis._extensionCompactionActivity = undefined;\n\t\t}\n\t\tawait this._runtimeLifecycleQueue;\n\t\tawait this.waitForIdle();\n\t}\n\n\tprivate async _dispose(): Promise<void> {\n\t\tawait this.prepareForShutdown();\n\t\tawait this._sandboxTransitionQueue;\n\n\t\tthis._extensionRunner.invalidate(STALE_EXTENSION_CONTEXT_MESSAGE);\n\t\tthis._disconnectFromAgent();\n\t\tthis._eventListeners = [];\n\t\tthis._hookExecutionListeners.clear();\n\t\tthis._hookExecutionActivityListeners.clear();\n\t\tcleanupSessionResources(this.sessionId);\n\n\t\tconst localResourceToolOperations = this._localResourceToolOperations;\n\t\tconst lspRuntimeState = this._lspRuntimeState;\n\t\tconst workspaceToolHost = this._workspaceToolHost;\n\t\tconst retiredWorkspaceToolHosts = this._retiredWorkspaceToolHosts;\n\t\tconst sandboxToolOperations = this._sandboxToolOperations;\n\t\tthis._localResourceToolOperations = undefined;\n\t\tthis._lspRuntimeState = undefined;\n\t\tthis._workspaceToolHost = undefined;\n\t\tthis._retiredWorkspaceToolHosts = [];\n\t\tthis._sandboxToolOperations = undefined;\n\t\tthis._preSandboxToolOperations = undefined;\n\t\tawait Promise.allSettled([\n\t\t\tPromise.resolve().then(() => localResourceToolOperations?.dispose?.()),\n\t\t\tPromise.resolve().then(() => lspRuntimeState?.manager.shutdownAll()),\n\t\t\tPromise.resolve().then(() => workspaceToolHost?.dispose()),\n\t\t\tPromise.resolve().then(() => sandboxToolOperations?.dispose()),\n\t\t\t...retiredWorkspaceToolHosts.map((host) => Promise.resolve().then(() => host.dispose())),\n\t\t]);\n\t}\n\n\t// =========================================================================\n\t// Read-only State Access\n\t// =========================================================================\n\n\t/** Full agent state */\n\tget state(): AgentState {\n\t\treturn this.agent.state;\n\t}\n\n\t/** Current model (may be undefined if not yet selected) */\n\tget model(): Model<any> | undefined {\n\t\treturn this.agent.state.model;\n\t}\n\n\t/** Current thinking level */\n\tget thinkingLevel(): ThinkingLevel {\n\t\treturn this.agent.state.thinkingLevel;\n\t}\n\n\t/** Whether the underlying Agent is currently streaming a response. */\n\tget isStreaming(): boolean {\n\t\treturn this.agent.state.isStreaming;\n\t}\n\n\tprivate get _hasActiveAgentRun(): boolean {\n\t\tif (this.agent.state.isStreaming) return true;\n\t\tfor (const activity of this._activities) {\n\t\t\tif (activity.kind === \"agent run\") return true;\n\t\t}\n\t\treturn false;\n\t}\n\n\tprivate _hasPromptAdmission(excluded?: SessionActivityToken): boolean {\n\t\tfor (const activity of this._activities) {\n\t\t\tif (activity !== excluded && activity.kind === \"prompt admission\") return true;\n\t\t}\n\t\treturn false;\n\t}\n\n\tprivate get _isAgentRunActive(): boolean {\n\t\treturn this._hasActiveAgentRun || this._hasPromptAdmission();\n\t}\n\n\t/** Whether all session-owned work and settlement callbacks have completed. */\n\tget isIdle(): boolean {\n\t\treturn this._isSettledPredicate() && !this._activeCycle && !this._settlementRunning && !this._settlementScheduled;\n\t}\n\n\t/** Current effective system prompt (includes any per-turn extension modifications) */\n\tget systemPrompt(): string {\n\t\treturn this.agent.state.systemPrompt;\n\t}\n\n\t/** Current retry attempt (0 if not retrying) */\n\tget retryAttempt(): number {\n\t\treturn this._retryAttempt;\n\t}\n\n\t/**\n\t * Get the names of currently active tools.\n\t * Returns the names of tools currently set on the agent.\n\t */\n\tgetActiveToolNames(): string[] {\n\t\treturn this.agent.state.tools.map((t) => t.name);\n\t}\n\n\t/**\n\t * Get all configured tools with name, description, parameter schema, and source metadata.\n\t */\n\tgetAllTools(): ToolInfo[] {\n\t\treturn Array.from(this._toolDefinitions.values())\n\t\t\t.filter(({ definition }) => this._isToolPermitted(definition.name))\n\t\t\t.map(({ definition, sourceInfo }) => ({\n\t\t\t\tname: definition.name,\n\t\t\t\tdescription: definition.description,\n\t\t\t\tparameters: definition.parameters,\n\t\t\t\tsourceInfo,\n\t\t\t}));\n\t}\n\n\tgetToolDefinition(name: string): ToolDefinition | undefined {\n\t\tif (!this._isToolPermitted(name)) return undefined;\n\t\treturn this._toolDefinitions.get(name)?.definition ?? this._sessionTools.get(name)?.definition;\n\t}\n\n\tgetRegisteredSkills(): Skill[] {\n\t\tconst sessionSkillNames = new Set(this._sessionSkills.keys());\n\t\treturn [\n\t\t\t...Array.from(this._sessionSkills.values()),\n\t\t\t...this._resourceLoader.getSkills().skills.filter((skill) => !sessionSkillNames.has(skill.name)),\n\t\t];\n\t}\n\n\tgetRegisteredRules(): Rule[] {\n\t\tconst sessionRuleNames = new Set(this._sessionRules.keys());\n\t\treturn [\n\t\t\t...Array.from(this._sessionRules.values()),\n\t\t\t...this._resourceLoader.getRules().rules.filter((rule) => !sessionRuleNames.has(rule.name)),\n\t\t];\n\t}\n\n\tregisterSessionSkill(skill: SessionSkillRegistration): void {\n\t\tconst filePath = skill.filePath || `<session-skill:${skill.name}>`;\n\t\tconst baseDir = skill.baseDir || (skill.filePath ? dirname(skill.filePath) : this._cwd);\n\t\tthis._sessionSkills.set(skill.name, {\n\t\t\t...skill,\n\t\t\tfilePath,\n\t\t\tbaseDir,\n\t\t\tsourceInfo: skill.sourceInfo ?? createSyntheticSourceInfo(filePath, { source: \"session\", baseDir }),\n\t\t});\n\t\tthis.setActiveToolsByName(this.getActiveToolNames());\n\t}\n\n\tunregisterSessionSkill(name: string): boolean {\n\t\tconst deleted = this._sessionSkills.delete(name);\n\t\tif (deleted) {\n\t\t\tthis.setActiveToolsByName(this.getActiveToolNames());\n\t\t}\n\t\treturn deleted;\n\t}\n\n\tregisterSessionRule(rule: SessionRuleRegistration): void {\n\t\tconst filePath = rule.filePath || `<session-rule:${rule.name}>`;\n\t\tconst baseDir = rule.baseDir || (rule.filePath ? dirname(rule.filePath) : this._cwd);\n\t\tthis._sessionRules.set(rule.name, {\n\t\t\t...rule,\n\t\t\tfilePath,\n\t\t\tbaseDir,\n\t\t\tsourceInfo: rule.sourceInfo ?? createSyntheticSourceInfo(filePath, { source: \"session\", baseDir }),\n\t\t});\n\t\tthis.setActiveToolsByName(this.getActiveToolNames());\n\t}\n\n\tunregisterSessionRule(name: string): boolean {\n\t\tconst deleted = this._sessionRules.delete(name);\n\t\tif (deleted) {\n\t\t\tthis.setActiveToolsByName(this.getActiveToolNames());\n\t\t}\n\t\treturn deleted;\n\t}\n\n\tregisterSessionTool(definition: ToolDefinition, options: RegisterSessionToolOptions = {}): void {\n\t\tif (!this._isToolPermitted(definition.name)) return;\n\t\tthis._sessionTools.set(definition.name, {\n\t\t\tdefinition,\n\t\t\tsourceInfo:\n\t\t\t\toptions.sourceInfo ?? createSyntheticSourceInfo(`<session:${definition.name}>`, { source: \"session\" }),\n\t\t\tlazy: options.lazy === true,\n\t\t\tloaded: options.lazy !== true,\n\t\t});\n\t\tthis._refreshToolRegistry();\n\t\tif (options.lazy === true) {\n\t\t\tconst activeTools = new Set(this.getActiveToolNames());\n\t\t\tactiveTools.add(\"load_tool\");\n\t\t\tactiveTools.add(\"unload_tool\");\n\t\t\tthis.setActiveToolsByName(Array.from(activeTools));\n\t\t}\n\t}\n\n\tunregisterSessionTool(name: string): boolean {\n\t\tconst deleted = this._sessionTools.delete(name);\n\t\tif (deleted) {\n\t\t\tthis._refreshToolRegistry();\n\t\t}\n\t\treturn deleted;\n\t}\n\n\tloadSessionTool(name: string): boolean {\n\t\treturn this._runSynchronousActivity(\"lazy tool load\", () => this._loadSessionTool(name));\n\t}\n\n\tprivate _loadSessionTool(name: string): boolean {\n\t\tif (!this._isToolPermitted(name)) return false;\n\t\tconst tool = this._sessionTools.get(name);\n\t\tif (tool) {\n\t\t\tif (!tool.loaded) {\n\t\t\t\ttool.loaded = true;\n\t\t\t\tthis._refreshToolRegistry();\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t\tconst loaded = this._extensionRunner.loadRegisteredTool(name);\n\t\tif (loaded) {\n\t\t\tthis._refreshToolRegistry();\n\t\t}\n\t\treturn loaded;\n\t}\n\n\tunloadSessionTool(name: string): boolean {\n\t\treturn this._runSynchronousActivity(\"lazy tool unload\", () => this._unloadSessionTool(name));\n\t}\n\n\tprivate _unloadSessionTool(name: string): boolean {\n\t\tif (!this._isToolPermitted(name)) return false;\n\t\tconst tool = this._sessionTools.get(name);\n\t\tif (tool) {\n\t\t\tif (tool.loaded) {\n\t\t\t\ttool.loaded = false;\n\t\t\t\tthis._refreshToolRegistry();\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t\tconst unloaded = this._extensionRunner.unloadRegisteredTool(name);\n\t\tif (unloaded) {\n\t\t\tthis._refreshToolRegistry();\n\t\t}\n\t\treturn unloaded;\n\t}\n\n\tgetAvailableSessionTools(): ToolInfo[] {\n\t\treturn [...Array.from(this._sessionTools.values()), ...this._extensionRunner.getAvailableRegisteredTools()]\n\t\t\t.filter(({ definition }) => this._isToolPermitted(definition.name))\n\t\t\t.map(({ definition, sourceInfo }) => ({\n\t\t\t\tname: definition.name,\n\t\t\t\tdescription: definition.description,\n\t\t\t\tparameters: definition.parameters,\n\t\t\t\tsourceInfo,\n\t\t\t}));\n\t}\n\n\t/**\n\t * Set active tools by name.\n\t * Only tools in the registry can be enabled. Unknown tool names are ignored.\n\t * Also rebuilds the system prompt to reflect the new tool set.\n\t * Changes take effect on the next agent turn.\n\t */\n\tsetActiveToolsByName(toolNames: string[]): void {\n\t\tconst tools: AgentTool[] = [];\n\t\tconst validToolNames: string[] = [];\n\t\tfor (const name of toolNames) {\n\t\t\tif (name === \"compress_context\" && this._contextCompressionEnabledOverride === false) continue;\n\t\t\tconst tool = this._toolRegistry.get(name);\n\t\t\tif (tool) {\n\t\t\t\ttools.push(tool);\n\t\t\t\tvalidToolNames.push(name);\n\t\t\t}\n\t\t}\n\t\tthis.agent.state.tools = tools;\n\t\tif (!validToolNames.includes(\"compress_context\")) this._compressionDetector.reset();\n\n\t\t// Rebuild base system prompt with new tool set\n\t\tthis._baseSystemPrompt = this._rebuildSystemPrompt(validToolNames);\n\t\tthis.agent.state.systemPrompt = this._systemPromptOverride ?? this._baseSystemPrompt;\n\t}\n\n\t/** Whether compaction or branch summarization is currently running */\n\tget isCompacting(): boolean {\n\t\treturn (\n\t\t\tthis._compactionInFlight !== undefined ||\n\t\t\tthis._autoCompactionAbortController !== undefined ||\n\t\t\tthis._compactionAbortController !== undefined ||\n\t\t\tthis._branchSummaryAbortController !== undefined\n\t\t);\n\t}\n\n\t/** All messages including custom types like BashExecutionMessage */\n\tget messages(): AgentMessage[] {\n\t\treturn this.agent.state.messages;\n\t}\n\n\t/** Current steering mode */\n\tget steeringMode(): QueueMode {\n\t\treturn this.agent.steeringMode;\n\t}\n\n\t/** Current follow-up mode */\n\tget followUpMode(): QueueMode {\n\t\treturn this.agent.followUpMode;\n\t}\n\n\t/** Current session reference, or undefined for ephemeral sessions. */\n\tget sessionReference(): string | undefined {\n\t\treturn this.session.getSessionReference();\n\t}\n\n\t/** Current local session file path, or undefined for non-file-backed sessions. Prefer sessionReference for backend-neutral code. */\n\tget sessionFile(): string | undefined {\n\t\treturn this.sessionReference;\n\t}\n\n\t/** Current session ID */\n\tget sessionId(): string {\n\t\treturn this.session.getSessionId();\n\t}\n\n\t/** Current session display name, if set */\n\tget sessionName(): string | undefined {\n\t\treturn this.session.getSessionName();\n\t}\n\n\t/** Scoped models for cycling (from --models flag) */\n\tget scopedModels(): ReadonlyArray<ScopedModel> {\n\t\treturn this._scopedModels;\n\t}\n\n\t/** Update scoped models for cycling */\n\tsetScopedModels(scopedModels: ScopedModel[]): void {\n\t\tthis._scopedModels = scopedModels;\n\t\tthis._refreshModelDependentRuntime();\n\t}\n\n\t/** File-based prompt templates */\n\tget promptTemplates(): ReadonlyArray<PromptTemplate> {\n\t\treturn this._resourceLoader.getPrompts().prompts;\n\t}\n\n\tprivate _normalizePromptSnippet(text: string | undefined): string | undefined {\n\t\tif (!text) return undefined;\n\t\tconst oneLine = text\n\t\t\t.replace(/[\\r\\n]+/g, \" \")\n\t\t\t.replace(/\\s+/g, \" \")\n\t\t\t.trim();\n\t\treturn oneLine.length > 0 ? oneLine : undefined;\n\t}\n\n\tprivate _normalizePromptGuidelines(guidelines: string[] | undefined): string[] {\n\t\tif (!guidelines || guidelines.length === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst unique = new Set<string>();\n\t\tfor (const guideline of guidelines) {\n\t\t\tconst normalized = guideline.trim();\n\t\t\tif (normalized.length > 0) {\n\t\t\t\tunique.add(normalized);\n\t\t\t}\n\t\t}\n\t\treturn Array.from(unique);\n\t}\n\n\tprivate _rebuildSystemPrompt(toolNames: string[]): string {\n\t\tconst validToolNames = toolNames.filter((name) => this._toolRegistry.has(name));\n\t\tconst toolSnippets: Record<string, string> = {};\n\t\tconst promptGuidelines: string[] = [];\n\t\tfor (const name of validToolNames) {\n\t\t\tconst snippet = this._toolPromptSnippets.get(name);\n\t\t\tif (snippet) {\n\t\t\t\ttoolSnippets[name] = snippet;\n\t\t\t}\n\n\t\t\tconst toolGuidelines = this._toolPromptGuidelines.get(name);\n\t\t\tif (toolGuidelines) {\n\t\t\t\tpromptGuidelines.push(...toolGuidelines);\n\t\t\t}\n\t\t}\n\n\t\tconst loaderSystemPrompt = this._resourceLoader.getSystemPrompt();\n\t\tconst loaderAppendSystemPrompt = this._resourceLoader.getAppendSystemPrompt();\n\t\tconst appendSystemPromptParts = [...loaderAppendSystemPrompt];\n\t\tconst availableToolsPrompt = this._buildAvailableToolsPrompt();\n\t\tif (availableToolsPrompt) {\n\t\t\tappendSystemPromptParts.push(availableToolsPrompt);\n\t\t}\n\t\tconst appendSystemPrompt = appendSystemPromptParts.length > 0 ? appendSystemPromptParts.join(\"\\n\\n\") : undefined;\n\t\tconst loadedSkills = this.getRegisteredSkills();\n\t\tconst loadedRules = this.getRegisteredRules();\n\t\tconst loadedContextFiles = this._resourceLoader.getAgentsFiles().agentsFiles;\n\n\t\tthis._baseSystemPromptOptions = {\n\t\t\tcwd: this._toolOperations?.cwd ?? this._cwd,\n\t\t\tskills: loadedSkills,\n\t\t\trules: loadedRules,\n\t\t\tcontextFiles: loadedContextFiles,\n\t\t\tcustomPrompt: loaderSystemPrompt,\n\t\t\tappendSystemPrompt,\n\t\t\tselectedTools: validToolNames,\n\t\t\ttoolSnippets,\n\t\t\tpromptGuidelines,\n\t\t};\n\t\treturn buildSystemPrompt(this._baseSystemPromptOptions);\n\t}\n\n\tprivate _buildAvailableToolsPrompt(): string | undefined {\n\t\tconst tools = [\n\t\t\t...Array.from(this._sessionTools.values()).filter((tool) => tool.lazy),\n\t\t\t...this._extensionRunner.getAvailableRegisteredTools(),\n\t\t].filter((tool) => this._isToolPermitted(tool.definition.name));\n\t\tif (tools.length === 0) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst lines = [\n\t\t\t\"<available-tools>\",\n\t\t\t\"These tools are session-scoped and can be loaded into the active tool context with load_tool when needed, then unloaded with unload_tool when no longer needed.\",\n\t\t];\n\t\tfor (const tool of tools) {\n\t\t\tconst loaded = tool.loaded ? ' loaded=\"true\"' : \"\";\n\t\t\tlines.push(\n\t\t\t\t` <tool name=\"${escapeXmlAttribute(tool.definition.name)}\"${loaded} description=\"${escapeXmlAttribute(\n\t\t\t\t\ttool.definition.description,\n\t\t\t\t)}\" />`,\n\t\t\t);\n\t\t}\n\t\tlines.push(\"</available-tools>\");\n\t\treturn lines.join(\"\\n\");\n\t}\n\n\tprivate _refreshBaseSystemPrompt(): void {\n\t\tthis._baseSystemPrompt = this._rebuildSystemPrompt(this.getActiveToolNames());\n\t\tthis.agent.state.systemPrompt = this._systemPromptOverride ?? this._baseSystemPrompt;\n\t}\n\n\tprivate _getSubagentModels(): Model<any>[] {\n\t\treturn this._scopedModels.length > 0\n\t\t\t? this._scopedModels.map((scoped) => scoped.model)\n\t\t\t: this._modelRegistry.getAvailable();\n\t}\n\n\tprivate _resolveSubagentModelHint(hint: SubagentModelHint): string | undefined {\n\t\tconst currentModel = this.model;\n\t\tif (!currentModel) return undefined;\n\t\tconst formatModel = (model: Model<any>): string => `${model.provider}/${model.id}`;\n\t\tif (hint === \"same-as-parent\") return formatModel(currentModel);\n\t\tconst candidates = this._getSubagentModels().filter((model) => model.provider === currentModel.provider);\n\t\tif (candidates.length === 0) return undefined;\n\t\tconst cost = (model: Model<any>): number => model.cost.input + model.cost.output;\n\t\tconst byCost = [...candidates].sort((a, b) => cost(a) - cost(b));\n\t\tif (hint === \"cheapest\" || hint === \"fastest\") return formatModel(byCost[0]);\n\t\tif (hint === \"large-context\") {\n\t\t\treturn formatModel([...candidates].sort((a, b) => b.contextWindow - a.contextWindow)[0]);\n\t\t}\n\t\tif (hint === \"best-reasoning\") {\n\t\t\tconst reasoning = candidates.filter((model) => model.reasoning);\n\t\t\treturn formatModel(\n\t\t\t\t(reasoning.length > 0 ? reasoning : candidates).sort(\n\t\t\t\t\t(a, b) => b.contextWindow + b.maxTokens - (a.contextWindow + a.maxTokens),\n\t\t\t\t)[0],\n\t\t\t);\n\t\t}\n\t\tif (hint === \"strongest\") {\n\t\t\treturn formatModel(\n\t\t\t\t[...candidates].sort(\n\t\t\t\t\t(a, b) =>\n\t\t\t\t\t\tNumber(b.reasoning) - Number(a.reasoning) ||\n\t\t\t\t\t\tb.contextWindow + b.maxTokens - (a.contextWindow + a.maxTokens),\n\t\t\t\t)[0],\n\t\t\t);\n\t\t}\n\t\tconst reasoningByCost = byCost.find((model) => model.reasoning);\n\t\treturn formatModel(reasoningByCost ?? byCost[0]);\n\t}\n\n\tprivate _refreshModelDependentRuntime(): void {\n\t\tif (!this._baseToolsOverride && this._baseToolDefinitions.has(\"subagent\")) {\n\t\t\tthis._baseToolDefinitions.set(\n\t\t\t\t\"subagent\",\n\t\t\t\tcreateSubagentToolDefinition({\n\t\t\t\t\trunner: this._subagentRunner,\n\t\t\t\t\tmodelCatalog: formatSubagentModelCatalog(this.model, this._getSubagentModels()),\n\t\t\t\t\tresolveModelHint: (hint) => this._resolveSubagentModelHint(hint),\n\t\t\t\t\ttrustProjectAgents: this._trustProjectAgents,\n\t\t\t\t\trunRegistry: this._subagentRunRegistry,\n\t\t\t\t\tconfigRegistry: this._subagentConfigRegistry,\n\t\t\t\t}) as unknown as ToolDefinition,\n\t\t\t);\n\t\t\tthis._refreshToolRegistry({ activeToolNames: this.getActiveToolNames() });\n\t\t\treturn;\n\t\t}\n\t\tthis._refreshBaseSystemPrompt();\n\t}\n\n\t// =========================================================================\n\t// Prompting\n\t// =========================================================================\n\n\tprivate async _continueAgentIfReady(): Promise<boolean> {\n\t\tconst lastMessage = this.agent.state.messages.at(-1);\n\t\tif (lastMessage?.role === \"assistant\" && !this.agent.hasQueuedMessages()) {\n\t\t\treturn false;\n\t\t}\n\t\tawait this.agent.continue();\n\t\treturn true;\n\t}\n\n\tprivate _runAgentPrompt(messages: AgentMessage | AgentMessage[]): Promise<void> {\n\t\treturn this._runActivity(\"agent run\", async () => {\n\t\t\ttry {\n\t\t\t\tawait this.agent.prompt(messages);\n\t\t\t\twhile (await this._handlePostAgentRun()) {\n\t\t\t\t\tif (!(await this._continueAgentIfReady())) break;\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tthis._systemPromptOverride = undefined;\n\t\t\t\tthis._flushPendingBashMessages();\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate _finishFailedRetry(message: AssistantMessage): void {\n\t\tif (message.stopReason !== \"error\" || this._retryAttempt <= 0) return;\n\t\tthis._emit({\n\t\t\ttype: \"auto_retry_end\",\n\t\t\tsuccess: false,\n\t\t\tattempt: this._retryAttempt,\n\t\t\tfinalError: message.errorMessage,\n\t\t});\n\t\tthis._retryAttempt = 0;\n\t}\n\tprivate _resetStopHookContinuationState(): void {\n\t\tthis._stopHookContinuations = 0;\n\t\tthis._stopHookProgress = undefined;\n\t}\n\n\tprivate _recordStopHookProgress(progress: number | undefined): boolean {\n\t\tif (progress === undefined) {\n\t\t\tthis._stopHookProgress = undefined;\n\t\t\treturn false;\n\t\t}\n\t\tif (!this._stopHookProgress || progress < this._stopHookProgress.best) {\n\t\t\tthis._stopHookProgress = { best: progress, consecutiveNonImprovingCalls: 0 };\n\t\t\treturn true;\n\t\t}\n\t\tthis._stopHookProgress.consecutiveNonImprovingCalls++;\n\t\treturn this._stopHookProgress.consecutiveNonImprovingCalls <= STOP_HOOK_PROGRESS_REGRESSION_TOLERANCE;\n\t}\n\n\tprivate async _runStopFailureHook(message: AssistantMessage): Promise<void> {\n\t\tconst details = message.errorMessage ?? \"Assistant response failed\";\n\t\tawait this._runHook({\n\t\t\t...this._hookCommon(\"StopFailure\"),\n\t\t\thook_event_name: \"StopFailure\",\n\t\t\terror: classifyStopFailure(details),\n\t\t\terror_details: details,\n\t\t\tlast_assistant_message: getAssistantText(message),\n\t\t});\n\t\tthis._resetStopHookContinuationState();\n\t}\n\n\tprivate async _runStopHook(message: AssistantMessage): Promise<boolean> {\n\t\tconst stopResult = await this._runHook({\n\t\t\t...this._hookCommon(\"Stop\"),\n\t\t\thook_event_name: \"Stop\",\n\t\t\tstop_hook_active: this._stopHookContinuations > 0,\n\t\t\tlast_assistant_message: getAssistantText(message),\n\t\t});\n\t\tif (!stopResult.continue) {\n\t\t\t// Aggregate termination is not a request for another model turn.\n\t\t\tthis._resetStopHookContinuationState();\n\t\t\treturn false;\n\t\t}\n\t\tif (!stopResult.blocked) {\n\t\t\tthis._resetStopHookContinuationState();\n\t\t\treturn false;\n\t\t}\n\t\tconst progressAllowsExtendedContinuation = this._recordStopHookProgress(stopResult.stopContinuationProgress);\n\t\tif (this._stopHookContinuations >= DEFAULT_STOP_HOOK_CONTINUATION_LIMIT && !progressAllowsExtendedContinuation) {\n\t\t\tthis._reportHookDiagnostic({\n\t\t\t\tlevel: \"warning\",\n\t\t\t\tcode: \"continuation-cap\",\n\t\t\t\tevent: \"Stop\",\n\t\t\t\tmessage: `Stop hook continuation limit (${DEFAULT_STOP_HOOK_CONTINUATION_LIMIT}) reached without recent progress; settling the session`,\n\t\t\t});\n\t\t\tthis._resetStopHookContinuationState();\n\t\t\treturn false;\n\t\t}\n\t\tconst values = [stopResult.reason, ...stopResult.additionalContext].filter((value): value is string =>\n\t\t\tBoolean(value),\n\t\t);\n\t\tconst feedback =\n\t\t\tcapModelVisibleHookField([...new Set(values)].join(\"\\n\\n\")) || \"A Stop hook requested another model turn.\";\n\t\tthis._stopHookContinuations++;\n\t\tthis.agent.followUp({\n\t\t\trole: \"custom\",\n\t\t\tcustomType: \"claude-stop-hook-feedback\",\n\t\t\tcontent: [{ type: \"text\", text: feedback }],\n\t\t\tdisplay: false,\n\t\t\ttimestamp: Date.now(),\n\t\t});\n\t\treturn true;\n\t}\n\n\tprivate async _handlePostAgentRun(): Promise<boolean> {\n\t\tconst message = this._lastAssistantMessage;\n\t\tthis._lastAssistantMessage = undefined;\n\t\tif (!message) return false;\n\n\t\t// A user abort is not an assistant stop/failure lifecycle event.\n\t\tif (message.stopReason === \"aborted\") {\n\t\t\tthis._resetStopHookContinuationState();\n\t\t\treturn this.agent.hasQueuedMessages();\n\t\t}\n\t\tif (this._isRetryableError(message) && (await this._prepareRetry(message))) return true;\n\t\tthis._finishFailedRetry(message);\n\t\tif (await this._checkCompaction(message)) return true;\n\t\tif (message.stopReason === \"error\") await this._runStopFailureHook(message);\n\t\telse if (await this._runStopHook(message)) return true;\n\n\t\t// The agent loop drains both queues before emitting agent_end. Any messages\n\t\t// here were queued by agent_end extension handlers and need a continuation.\n\t\treturn this.agent.hasQueuedMessages();\n\t}\n\n\tgetStructuredResponse<TSchemaValue extends TSchema>(\n\t\toptions: StructuredResponseOptions<TSchemaValue>,\n\t): Promise<StructuredResponse<Static<TSchemaValue>>> {\n\t\treturn this._runActivity(\"structured response\", () => this._getStructuredResponse(options));\n\t}\n\tprivate _requireStructuredResponseModel(): Model<Api> {\n\t\tif (this._isAgentRunActive) {\n\t\t\tthrow new Error(\"Agent is already processing. Wait for completion before requesting structured output.\");\n\t\t}\n\t\tconst model = this.model;\n\t\tif (!model) throw new Error(formatNoModelSelectedMessage());\n\t\tif (!this._modelRegistry.hasConfiguredAuth(model)) {\n\t\t\tthrow new Error(formatNoApiKeyFoundMessage(model.provider));\n\t\t}\n\t\treturn model;\n\t}\n\tprivate async _getStructuredResponse<TSchemaValue extends TSchema>(\n\t\toptions: StructuredResponseOptions<TSchemaValue>,\n\t): Promise<StructuredResponse<Static<TSchemaValue>>> {\n\t\tconst model = this._requireStructuredResponseModel();\n\n\t\tconst schemaName = options.name ?? DEFAULT_STRUCTURED_RESPONSE_TOOL_NAME;\n\t\tconst tool: Tool<TSchemaValue> = {\n\t\t\tname: schemaName,\n\t\t\tdescription:\n\t\t\t\toptions.description ??\n\t\t\t\t\"Return the requested structured response. Call this tool exactly once with arguments matching the schema.\",\n\t\t\tparameters: options.schema,\n\t\t};\n\t\tconst lastAssistant = this._findLastAssistantMessage();\n\t\tif (!lastAssistant) {\n\t\t\tthrow new Error(\"No assistant response is available to structure.\");\n\t\t}\n\n\t\tconst direct = this._tryParseStructuredAssistantText(tool, lastAssistant);\n\t\tif (direct.ok) {\n\t\t\tthis._appendStructuredInternalEntry(\n\t\t\t\t\"result\",\n\t\t\t\tschemaName,\n\t\t\t\t0,\n\t\t\t\t\"Validated structured response from assistant JSON.\",\n\t\t\t\t{\n\t\t\t\t\tstage: \"result\",\n\t\t\t\t\tschemaName,\n\t\t\t\t\tattempt: 0,\n\t\t\t\t\tsource: \"json\",\n\t\t\t\t},\n\t\t\t);\n\t\t\treturn { output: direct.output, attempts: 0, source: \"json\", message: lastAssistant };\n\t\t}\n\n\t\tconst maxCorrections = options.maxCorrections ?? DEFAULT_STRUCTURED_RESPONSE_CORRECTIONS;\n\t\tconst maxAttempts = maxCorrections + 1;\n\t\tconst { apiKey, headers } = await this._getRequiredRequestAuth(model);\n\t\tconst messages = this._buildStructuredResponseMessages(options.scope ?? \"latest\", lastAssistant, schemaName);\n\t\tlet lastError = direct.error;\n\n\t\tfor (let attempt = 1; attempt <= maxAttempts; attempt++) {\n\t\t\tconst requestText =\n\t\t\t\tattempt === 1\n\t\t\t\t\t? `Extract the structured response by calling ${schemaName} exactly once.`\n\t\t\t\t\t: `Correct the structured response by calling ${schemaName} exactly once.\\n\\nValidation error:\\n${lastError}`;\n\t\t\tconst userMessage: Message = {\n\t\t\t\trole: \"user\",\n\t\t\t\tcontent: [{ type: \"text\", text: requestText }],\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t};\n\t\t\tmessages.push(userMessage);\n\t\t\tthis._appendStructuredInternalEntry(\"request\", schemaName, attempt, requestText, {\n\t\t\t\tstage: \"request\",\n\t\t\t\tschemaName,\n\t\t\t\tattempt,\n\t\t\t\tvalidationError: attempt === 1 ? undefined : lastError,\n\t\t\t});\n\n\t\t\tconst responseStream = await this.agent.streamFn(\n\t\t\t\tmodel,\n\t\t\t\t{\n\t\t\t\t\tsystemPrompt:\n\t\t\t\t\t\t\"You are a structured data extraction assistant. Do not answer in prose. Use the provided tool to return the structured data.\",\n\t\t\t\t\tmessages,\n\t\t\t\t\ttools: [tool],\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tapiKey,\n\t\t\t\t\theaders,\n\t\t\t\t\tsessionId: this.agent.sessionId,\n\t\t\t\t\ttransport: this.agent.transport,\n\t\t\t\t\tthinkingBudgets: this.agent.thinkingBudgets,\n\t\t\t\t\tmaxRetryDelayMs: this.agent.maxRetryDelayMs,\n\t\t\t\t},\n\t\t\t);\n\t\t\tconst assistantMessage = await responseStream.result();\n\t\t\tmessages.push(assistantMessage);\n\t\t\tthis._appendStructuredInternalEntry(\n\t\t\t\t\"assistant\",\n\t\t\t\tschemaName,\n\t\t\t\tattempt,\n\t\t\t\tthis._formatStructuredAssistantLog(assistantMessage),\n\t\t\t\t{\n\t\t\t\t\tstage: \"assistant\",\n\t\t\t\t\tschemaName,\n\t\t\t\t\tattempt,\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tconst attemptResult = this._evaluateStructuredResponseAttempt({\n\t\t\t\ttool,\n\t\t\t\tassistantMessage,\n\t\t\t\tmessages,\n\t\t\t\tschemaName,\n\t\t\t\tattempt,\n\t\t\t});\n\t\t\tif (attemptResult.ok) return attemptResult.response;\n\t\t\tlastError = attemptResult.error;\n\t\t}\n\n\t\tthrow new Error(`Structured response validation failed after ${maxAttempts} attempt(s):\\n${lastError}`);\n\t}\n\tprivate _evaluateStructuredResponseAttempt<TSchemaValue extends TSchema>({\n\t\ttool,\n\t\tassistantMessage,\n\t\tmessages,\n\t\tschemaName,\n\t\tattempt,\n\t}: StructuredAttemptContext<TSchemaValue>): StructuredAttemptResult<Static<TSchemaValue>> {\n\t\tconst toolCall = assistantMessage.content.find(\n\t\t\t(block): block is ToolCall => block.type === \"toolCall\" && block.name === schemaName,\n\t\t);\n\t\tif (toolCall) {\n\t\t\tconst validation = this._validateStructuredArguments(tool, toolCall.arguments);\n\t\t\tif (validation.ok) {\n\t\t\t\tthis._appendStructuredInternalEntry(\"result\", schemaName, attempt, \"Validated structured tool response.\", {\n\t\t\t\t\tstage: \"result\",\n\t\t\t\t\tschemaName,\n\t\t\t\t\tattempt,\n\t\t\t\t\tsource: \"tool\",\n\t\t\t\t});\n\t\t\t\treturn {\n\t\t\t\t\tok: true,\n\t\t\t\t\tresponse: { output: validation.output, attempts: attempt, source: \"tool\", message: assistantMessage },\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst toolResult: ToolResultMessage = {\n\t\t\t\trole: \"toolResult\",\n\t\t\t\ttoolCallId: toolCall.id,\n\t\t\t\ttoolName: toolCall.name,\n\t\t\t\tcontent: [{ type: \"text\", text: validation.error }],\n\t\t\t\tisError: true,\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t};\n\t\t\tmessages.push(toolResult);\n\t\t\tthis._appendStructuredInternalEntry(\"tool_result\", schemaName, attempt, validation.error, {\n\t\t\t\tstage: \"tool_result\",\n\t\t\t\tschemaName,\n\t\t\t\tattempt,\n\t\t\t\tvalidationError: validation.error,\n\t\t\t});\n\t\t\treturn { ok: false, error: validation.error };\n\t\t}\n\n\t\tconst textValidation = this._tryParseStructuredAssistantText(tool, assistantMessage);\n\t\tif (!textValidation.ok) return textValidation;\n\t\tthis._appendStructuredInternalEntry(\"result\", schemaName, attempt, \"Validated structured JSON response.\", {\n\t\t\tstage: \"result\",\n\t\t\tschemaName,\n\t\t\tattempt,\n\t\t\tsource: \"json\",\n\t\t});\n\t\treturn {\n\t\t\tok: true,\n\t\t\tresponse: { output: textValidation.output, attempts: attempt, source: \"json\", message: assistantMessage },\n\t\t};\n\t}\n\n\tprivate _buildStructuredResponseMessages(\n\t\tscope: StructuredResponseScope,\n\t\tlastAssistant: AssistantMessage,\n\t\tschemaName: string,\n\t): Message[] {\n\t\tif (scope === \"conversation\") {\n\t\t\treturn this.agent.state.messages.filter(\n\t\t\t\t(message): message is Message =>\n\t\t\t\t\tmessage.role === \"user\" || message.role === \"assistant\" || message.role === \"toolResult\",\n\t\t\t);\n\t\t}\n\n\t\treturn [\n\t\t\t{\n\t\t\t\trole: \"user\",\n\t\t\t\tcontent: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\ttext:\n\t\t\t\t\t\t\t`Extract structured data from the latest assistant response below. Call ${schemaName} exactly once.\\n\\n` +\n\t\t\t\t\t\t\t`<assistant_response>\\n${getAssistantText(lastAssistant)}\\n</assistant_response>`,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t},\n\t\t];\n\t}\n\n\tprivate _tryParseStructuredAssistantText<TSchemaValue extends TSchema>(\n\t\ttool: Tool<TSchemaValue>,\n\t\tmessage: AssistantMessage,\n\t): StructuredValidationResult<Static<TSchemaValue>> {\n\t\tconst text = getAssistantText(message);\n\t\tfor (const candidate of extractJsonCandidates(text)) {\n\t\t\tif (!isRecord(candidate)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst validation = this._validateStructuredArguments(tool, candidate);\n\t\t\tif (validation.ok) {\n\t\t\t\treturn validation;\n\t\t\t}\n\t\t}\n\t\treturn { ok: false, error: \"Assistant response did not contain valid JSON matching the requested schema.\" };\n\t}\n\n\tprivate _validateStructuredArguments<TSchemaValue extends TSchema>(\n\t\ttool: Tool<TSchemaValue>,\n\t\targuments_: Record<string, unknown>,\n\t): StructuredValidationResult<Static<TSchemaValue>> {\n\t\ttry {\n\t\t\tconst output = validateToolArguments(tool, {\n\t\t\t\ttype: \"toolCall\",\n\t\t\t\tid: \"structured-response-validation\",\n\t\t\t\tname: tool.name,\n\t\t\t\targuments: arguments_,\n\t\t\t}) as Static<TSchemaValue>;\n\t\t\treturn { ok: true, output };\n\t\t} catch (error) {\n\t\t\treturn { ok: false, error: error instanceof Error ? error.message : String(error) };\n\t\t}\n\t}\n\n\tprivate _appendStructuredInternalEntry(\n\t\tstage: StructuredInternalStage,\n\t\tschemaName: string,\n\t\tattempt: number,\n\t\tcontent: string,\n\t\tdetails: StructuredInternalDetails,\n\t): void {\n\t\tthis.session.appendCustomMessageEntry(STRUCTURED_RESPONSE_INTERNAL_CUSTOM_TYPE, content, false, {\n\t\t\t...details,\n\t\t\tstage,\n\t\t\tschemaName,\n\t\t\tattempt,\n\t\t});\n\t}\n\n\tprivate _formatStructuredAssistantLog(message: AssistantMessage): string {\n\t\tconst parts = message.content.map((block) => {\n\t\t\tif (block.type === \"text\") {\n\t\t\t\treturn block.text;\n\t\t\t}\n\t\t\tif (block.type === \"thinking\") {\n\t\t\t\treturn \"[thinking omitted]\";\n\t\t\t}\n\t\t\treturn `tool:${block.name} ${JSON.stringify(block.arguments)}`;\n\t\t});\n\t\treturn parts.join(\"\\n\");\n\t}\n\n\tprivate async _runPromptAdmission(state: PromptPreparationState): Promise<boolean> {\n\t\tif (this._disposed) throw new Error(\"Agent session is disposed\");\n\t\t// Reject every prompt submission, including extension commands, before hooks or message mutation.\n\t\t// _compactionInFlight is set synchronously, before manual compaction aborts an active agent run.\n\t\tif (this._compactionInFlight !== undefined) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Cannot submit a prompt while compaction is in progress. Wait for compaction to finish and retry.\",\n\t\t\t);\n\t\t}\n\t\tstate.queueIntoActiveRun = this._hasActiveAgentRun;\n\t\tif (!state.queueIntoActiveRun && this._hasPromptAdmission()) {\n\t\t\tthrow new Error(\"Agent is already processing. Wait for prompt admission to complete.\");\n\t\t}\n\n\t\t// Admission is synchronous and precedes hooks so concurrent first prompts cannot race SessionStart.\n\t\tstate.promptActivity = this._beginActivity(\"prompt admission\");\n\t\tthis._resetStopHookContinuationState();\n\t\tstate.sessionContext = await this._ensureHookSessionStarted(state.promptId);\n\t\tif (state.sessionContext) state.hookContextMessages.push(state.sessionContext);\n\t\tconst promptHook = await this._runHook({\n\t\t\t...this._hookCommon(\"UserPromptSubmit\", state.promptId),\n\t\t\thook_event_name: \"UserPromptSubmit\",\n\t\t\tprompt: state.text,\n\t\t});\n\t\tif (promptHook.blocked || !promptHook.continue) {\n\t\t\tthrow new Error(promptHook.reason ?? \"Prompt submission blocked by hook\");\n\t\t}\n\t\tconst promptContext = this._hookContextMessage(promptHook.additionalContext);\n\t\tif (promptContext) state.hookContextMessages.push(promptContext);\n\n\t\t// Hooks run before extension commands, input interception, and template expansion.\n\t\tif (state.expandPromptTemplates && state.text.startsWith(\"/\")) {\n\t\t\tconst handled = await this._tryExecuteExtensionCommand(state.text, () => {\n\t\t\t\t// Session-replacing extension commands must not wait on their own admission token.\n\t\t\t\tif (state.promptActivity) this._endActivity(state.promptActivity);\n\t\t\t\tstate.promptActivity = undefined;\n\t\t\t});\n\t\t\tif (handled) {\n\t\t\t\tstate.options?.preflightResult?.(true);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\tif (this._disposed) throw new Error(\"Agent session is disposed\");\n\t\treturn false;\n\t}\n\n\tprivate async _interceptPromptInput(state: PromptPreparationState): Promise<boolean> {\n\t\tif (!this._extensionRunner.hasHandlers(\"input\")) return false;\n\t\tconst inputResult = await this._runAsyncBarrierCallback(\"extension input handler\", () =>\n\t\t\tthis._extensionRunner.emitInput(\n\t\t\t\tstate.currentText,\n\t\t\t\tstate.currentImages,\n\t\t\t\tstate.options?.source ?? \"interactive\",\n\t\t\t\tstate.queueIntoActiveRun ? state.options?.streamingBehavior : undefined,\n\t\t\t),\n\t\t);\n\t\tif (inputResult.action === \"handled\") {\n\t\t\tstate.options?.preflightResult?.(true);\n\t\t\treturn true;\n\t\t}\n\t\tif (inputResult.action === \"transform\") {\n\t\t\tstate.currentText = inputResult.text;\n\t\t\tstate.currentImages = inputResult.images ?? state.currentImages;\n\t\t}\n\t\treturn false;\n\t}\n\n\tprivate _expandPromptInput(state: PromptPreparationState): void {\n\t\tstate.queueIntoActiveRun = this._hasActiveAgentRun;\n\t\tif (!state.queueIntoActiveRun && this._hasPromptAdmission(state.promptActivity)) {\n\t\t\tthrow new Error(\"Agent is already processing. Wait for prompt admission to complete.\");\n\t\t}\n\t\tstate.expandedText = state.currentText;\n\t\tif (!state.expandPromptTemplates) return;\n\t\tstate.expandedText = this._expandSkillCommand(state.expandedText);\n\t\tstate.expandedText = this._expandRuleCommand(state.expandedText);\n\t\tstate.expandedText = expandPromptTemplate(state.expandedText, [...this.promptTemplates]);\n\t}\n\n\tprivate async _queuePromptIntoActiveRun(state: PromptPreparationState): Promise<boolean> {\n\t\tif (!state.queueIntoActiveRun) return false;\n\t\tconst streamingBehavior = state.options?.streamingBehavior;\n\t\tif (!streamingBehavior) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Agent is already processing. Specify streamingBehavior ('steer' or 'followUp') to queue the message.\",\n\t\t\t);\n\t\t}\n\t\tif (streamingBehavior === \"followUp\") {\n\t\t\tawait this._queueFollowUp(state.expandedText, state.currentImages, state.hookContextMessages, state.promptId);\n\t\t} else {\n\t\t\tawait this._queueSteer(state.expandedText, state.currentImages, state.hookContextMessages, state.promptId);\n\t\t}\n\t\tif (state.sessionContext) this._pendingHookSessionStartContext = undefined;\n\t\tstate.options?.preflightResult?.(true);\n\t\treturn true;\n\t}\n\n\tprivate _validatePromptModelAuthentication(): void {\n\t\tconst model = this.model;\n\t\tif (!model) throw new Error(formatNoModelSelectedMessage());\n\t\tif (this._modelRegistry.hasConfiguredAuth(model)) return;\n\t\tif (this._modelRegistry.isUsingOAuth(model)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Authentication failed for \"${model.provider}\". ` +\n\t\t\t\t\t`Credentials may have expired or network is unavailable. ` +\n\t\t\t\t\t`Run '/login ${model.provider}' to re-authenticate.`,\n\t\t\t);\n\t\t}\n\t\tthrow new Error(formatNoApiKeyFoundMessage(model.provider));\n\t}\n\n\tprivate async _resumeAfterPromptCompaction(): Promise<void> {\n\t\tconst lastAssistant = this._findLastAssistantMessage();\n\t\tif (!lastAssistant || !(await this._checkCompaction(lastAssistant, false))) return;\n\t\ttry {\n\t\t\tif (!(await this._continueAgentIfReady())) return;\n\t\t\twhile (await this._handlePostAgentRun()) {\n\t\t\t\tif (!(await this._continueAgentIfReady())) break;\n\t\t\t}\n\t\t} finally {\n\t\t\tthis._flushPendingBashMessages();\n\t\t}\n\t}\n\n\tprivate async _buildPromptMessages(state: PromptPreparationState): Promise<AgentMessage[]> {\n\t\tconst userContent: (TextContent | ImageContent)[] = [{ type: \"text\", text: state.expandedText }];\n\t\tif (state.currentImages) userContent.push(...state.currentImages);\n\t\tconst messages: AgentMessage[] = [\n\t\t\t{ role: \"user\", content: userContent, timestamp: Date.now() },\n\t\t\t...state.hookContextMessages,\n\t\t\t...this._pendingNextTurnMessages,\n\t\t];\n\t\tif (state.sessionContext) this._pendingHookSessionStartContext = undefined;\n\t\tthis._pendingNextTurnMessages = [];\n\n\t\tconst result = await this._runAsyncBarrierCallback(\"extension before_agent_start handler\", () =>\n\t\t\tthis._extensionRunner.emitBeforeAgentStart(\n\t\t\t\tstate.expandedText,\n\t\t\t\tstate.currentImages,\n\t\t\t\tthis._baseSystemPrompt,\n\t\t\t\tthis._baseSystemPromptOptions,\n\t\t\t),\n\t\t);\n\t\tif (result?.messages) {\n\t\t\tfor (const msg of result.messages) {\n\t\t\t\tmessages.push({\n\t\t\t\t\trole: \"custom\",\n\t\t\t\t\tcustomType: msg.customType,\n\t\t\t\t\tcontent: msg.content ?? [],\n\t\t\t\t\tdisplay: msg.display,\n\t\t\t\t\tdetails: msg.details,\n\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tif (result?.systemPrompt !== undefined) {\n\t\t\tthis._systemPromptOverride = result.systemPrompt;\n\t\t\tthis.agent.state.systemPrompt = result.systemPrompt;\n\t\t} else {\n\t\t\tthis._systemPromptOverride = undefined;\n\t\t\tthis.agent.state.systemPrompt = this._baseSystemPrompt;\n\t\t}\n\t\treturn messages;\n\t}\n\n\tprivate async _preparePrompt(state: PromptPreparationState): Promise<AgentMessage[] | undefined> {\n\t\tif (await this._runPromptAdmission(state)) return undefined;\n\t\tif (await this._interceptPromptInput(state)) return undefined;\n\t\tthis._expandPromptInput(state);\n\t\tif (await this._queuePromptIntoActiveRun(state)) return undefined;\n\t\tthis._flushPendingBashMessages();\n\t\tthis._validatePromptModelAuthentication();\n\t\tawait this._resumeAfterPromptCompaction();\n\t\treturn this._buildPromptMessages(state);\n\t}\n\n\t/**\n\t * Send a prompt to the agent.\n\t * - Handles extension commands (registered via the extension API's registerCommand) immediately, even during streaming, when no compaction is active\n\t * - Expands file-based prompt templates by default\n\t * - During streaming, queues via steer() or followUp() based on streamingBehavior option\n\t * - Validates model and API key before sending (when not streaming)\n\t * @throws Error if streaming and no streamingBehavior specified\n\t * @throws Error if no model selected or no API key available (when not streaming)\n\t * @throws Error if manual or extension-requested compaction is in progress\n\t */\n\tasync prompt(text: string, options?: PromptOptions): Promise<void> {\n\t\tconst promptId = randomUUID();\n\t\tconst state: PromptPreparationState = {\n\t\t\ttext,\n\t\t\toptions,\n\t\t\tpromptId,\n\t\t\texpandPromptTemplates: options?.expandPromptTemplates ?? true,\n\t\t\thookContextMessages: [],\n\t\t\tpromptActivity: undefined,\n\t\t\tqueueIntoActiveRun: false,\n\t\t\tsessionContext: undefined,\n\t\t\tcurrentText: text,\n\t\t\tcurrentImages: options?.images,\n\t\t\texpandedText: text,\n\t\t};\n\t\ttry {\n\t\t\tlet messages: AgentMessage[] | undefined;\n\t\t\ttry {\n\t\t\t\tmessages = await this._preparePrompt(state);\n\t\t\t} catch (error) {\n\t\t\t\toptions?.preflightResult?.(false);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tif (!messages) return;\n\t\t\toptions?.preflightResult?.(true);\n\t\t\tthis._activePromptId = promptId;\n\t\t\tawait this._runAgentPrompt(messages);\n\t\t} finally {\n\t\t\tif (this._activePromptId === promptId) this._activePromptId = undefined;\n\t\t\tif (state.promptActivity) this._endActivity(state.promptActivity);\n\t\t}\n\t}\n\n\t/**\n\t * Try to execute an extension command. Returns true if command was found and executed.\n\t */\n\tprivate async _tryExecuteExtensionCommand(text: string, onFound?: () => void): Promise<boolean> {\n\t\t// Parse command name and args\n\t\tconst spaceIndex = text.indexOf(\" \");\n\t\tconst commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);\n\t\tconst args = spaceIndex === -1 ? \"\" : text.slice(spaceIndex + 1);\n\n\t\tconst command = this._extensionRunner.getCommand(commandName);\n\t\tif (!command) return false;\n\t\tonFound?.();\n\n\t\t// Get command context from extension runner (includes session control methods)\n\t\tconst ctx = this._extensionRunner.createCommandContext();\n\n\t\ttry {\n\t\t\tawait command.handler(args, ctx);\n\t\t\treturn true;\n\t\t} catch (err) {\n\t\t\t// Emit error via extension runner\n\t\t\tthis._extensionRunner.emitError({\n\t\t\t\textensionPath: `command:${commandName}`,\n\t\t\t\tevent: \"command\",\n\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t});\n\t\t\treturn true;\n\t\t}\n\t}\n\n\t/**\n\t * Expand skill commands (/skill:name args) to their full content.\n\t * Returns the expanded text, or the original text if not a skill command or skill not found.\n\t * Emits errors via extension runner if file read fails.\n\t */\n\tprivate _expandSkillCommand(text: string): string {\n\t\tif (!text.startsWith(\"/skill:\")) return text;\n\n\t\tconst spaceIndex = text.indexOf(\" \");\n\t\tconst skillName = spaceIndex === -1 ? text.slice(7) : text.slice(7, spaceIndex);\n\t\tconst args = spaceIndex === -1 ? \"\" : text.slice(spaceIndex + 1).trim();\n\n\t\tconst skill = this.getRegisteredSkills().find((s) => s.name === skillName);\n\t\tif (!skill) return text; // Unknown skill, pass through\n\n\t\ttry {\n\t\t\tthis._loadAssociatedInstructionTools(skill);\n\t\t\tconst content = skill.content ?? readFileSync(skill.filePath, \"utf-8\");\n\t\t\tconst body = stripFrontmatter(content).trim();\n\t\t\tconst skillBlock = `<skill name=\"${skill.name}\" location=\"${skill.filePath}\">\\nReferences are relative to ${skill.baseDir}.\\n\\n${body}\\n</skill>`;\n\t\t\treturn args ? `${skillBlock}\\n\\n${args}` : skillBlock;\n\t\t} catch (err) {\n\t\t\t// Emit error like extension commands do\n\t\t\tthis._extensionRunner.emitError({\n\t\t\t\textensionPath: skill.filePath,\n\t\t\t\tevent: \"skill_expansion\",\n\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t});\n\t\t\treturn text; // Return original on error\n\t\t}\n\t}\n\n\tprivate _expandRuleCommand(text: string): string {\n\t\tif (!text.startsWith(\"/rule:\")) return text;\n\n\t\tconst spaceIndex = text.indexOf(\" \");\n\t\tconst ruleName = spaceIndex === -1 ? text.slice(6) : text.slice(6, spaceIndex);\n\t\tconst args = spaceIndex === -1 ? \"\" : text.slice(spaceIndex + 1).trim();\n\n\t\tconst rule = this.getRegisteredRules().find((r) => r.name === ruleName);\n\t\tif (!rule) return text; // Unknown rule, pass through\n\n\t\ttry {\n\t\t\tthis._loadAssociatedInstructionTools(rule);\n\t\t\tconst content = rule.content ?? readFileSync(rule.filePath, \"utf-8\");\n\t\t\tconst body = stripFrontmatter(content).trim();\n\t\t\tconst ruleBlock = `<rule name=\"${rule.name}\" location=\"${rule.filePath}\">\\nReferences are relative to ${rule.baseDir}.\\n\\n${body}\\n</rule>`;\n\t\t\treturn args ? `${ruleBlock}\\n\\n${args}` : ruleBlock;\n\t\t} catch (err) {\n\t\t\tthis._extensionRunner.emitError({\n\t\t\t\textensionPath: rule.filePath,\n\t\t\t\tevent: \"rule_expansion\",\n\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t});\n\t\t\treturn text;\n\t\t}\n\t}\n\n\t/**\n\t * Queue a steering message while the agent is running.\n\t * Delivered after the current assistant turn finishes executing its tool calls,\n\t * before the next LLM call.\n\t * Expands skill/rule commands and prompt templates. Errors on extension commands.\n\t * @param images Optional image attachments to include with the message\n\t * @throws Error if text is an extension command\n\t */\n\tasync steer(text: string, images?: ImageContent[]): Promise<void> {\n\t\t// Check for extension commands (cannot be queued)\n\t\tif (text.startsWith(\"/\")) {\n\t\t\tthis._throwIfExtensionCommand(text);\n\t\t}\n\t\tif (!this._hasActiveAgentRun) throw new Error(\"Cannot steer while the agent is idle\");\n\n\t\t// Expand skill/rule commands and prompt templates\n\t\tlet expandedText = this._expandSkillCommand(text);\n\t\texpandedText = this._expandRuleCommand(expandedText);\n\t\texpandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);\n\n\t\tawait this._queueSteer(expandedText, images);\n\t}\n\n\t/**\n\t * Queue a follow-up message to be processed after the agent finishes.\n\t * Delivered only when agent has no more tool calls or steering messages.\n\t * Expands skill/rule commands and prompt templates. Errors on extension commands.\n\t * @param images Optional image attachments to include with the message\n\t * @throws Error if text is an extension command\n\t */\n\tasync followUp(text: string, images?: ImageContent[]): Promise<void> {\n\t\t// Check for extension commands (cannot be queued)\n\t\tif (text.startsWith(\"/\")) {\n\t\t\tthis._throwIfExtensionCommand(text);\n\t\t}\n\t\tif (!this._hasActiveAgentRun) throw new Error(\"Cannot queue a follow-up while the agent is idle\");\n\n\t\t// Expand skill/rule commands and prompt templates\n\t\tlet expandedText = this._expandSkillCommand(text);\n\t\texpandedText = this._expandRuleCommand(expandedText);\n\t\texpandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);\n\n\t\tawait this._queueFollowUp(expandedText, images);\n\t}\n\n\t/**\n\t * Internal: Queue a steering message (already expanded, no extension command check).\n\t */\n\tprivate async _queueSteer(\n\t\ttext: string,\n\t\timages?: ImageContent[],\n\t\thookContexts: CustomMessage[] = [],\n\t\tpromptId?: string,\n\t): Promise<void> {\n\t\tif (this._disposed || !this._hasActiveAgentRun) throw new Error(\"Cannot steer without an active agent run\");\n\t\tthis._steeringMessages.push(text);\n\t\tthis._emitQueueUpdate();\n\t\tconst content: (TextContent | ImageContent)[] = [{ type: \"text\", text }];\n\t\tif (images) {\n\t\t\tcontent.push(...images);\n\t\t}\n\t\tthis.agent.steer({\n\t\t\trole: \"user\",\n\t\t\tcontent,\n\t\t\ttimestamp: Date.now(),\n\t\t});\n\t\tfor (const context of hookContexts) this.agent.steer(context);\n\t\tif (promptId) {\n\t\t\tconst ids = this._queuedPromptIds.get(text) ?? [];\n\t\t\tids.push(promptId);\n\t\t\tthis._queuedPromptIds.set(text, ids);\n\t\t}\n\t}\n\n\t/**\n\t * Internal: Queue a follow-up message (already expanded, no extension command check).\n\t */\n\tprivate async _queueFollowUp(\n\t\ttext: string,\n\t\timages?: ImageContent[],\n\t\thookContexts: CustomMessage[] = [],\n\t\tpromptId?: string,\n\t): Promise<void> {\n\t\tif (this._disposed || !this._hasActiveAgentRun) {\n\t\t\tthrow new Error(\"Cannot queue a follow-up without an active agent run\");\n\t\t}\n\t\tthis._followUpMessages.push(text);\n\t\tthis._emitQueueUpdate();\n\t\tconst content: (TextContent | ImageContent)[] = [{ type: \"text\", text }];\n\t\tif (images) {\n\t\t\tcontent.push(...images);\n\t\t}\n\t\tthis.agent.followUp({\n\t\t\trole: \"user\",\n\t\t\tcontent,\n\t\t\ttimestamp: Date.now(),\n\t\t});\n\t\tfor (const context of hookContexts) this.agent.followUp(context);\n\t\tif (promptId) {\n\t\t\tconst ids = this._queuedPromptIds.get(text) ?? [];\n\t\t\tids.push(promptId);\n\t\t\tthis._queuedPromptIds.set(text, ids);\n\t\t}\n\t}\n\n\t/**\n\t * Throw an error if the text is an extension command.\n\t */\n\tprivate _throwIfExtensionCommand(text: string): void {\n\t\tconst spaceIndex = text.indexOf(\" \");\n\t\tconst commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);\n\t\tconst command = this._extensionRunner.getCommand(commandName);\n\n\t\tif (command) {\n\t\t\tthrow new Error(\n\t\t\t\t`Extension command \"/${commandName}\" cannot be queued. Use prompt() or execute the command when not streaming.`,\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Send a custom message to the session. Creates a CustomMessageEntry.\n\t *\n\t * Handles three cases:\n\t * - Streaming: queues message, processed when loop pulls from queue\n\t * - Not streaming + triggerTurn: appends to state/session, starts new turn\n\t * - Not streaming + no trigger: appends to state/session, no turn\n\t *\n\t * @param message Custom message with customType, content, display, details\n\t * @param options.triggerTurn If true and not streaming, triggers a new LLM turn\n\t * @param options.deliverAs Delivery mode: \"steer\", \"followUp\", or \"nextTurn\"\n\t */\n\tsendCustomMessage<T = unknown>(\n\t\tmessage: Pick<CustomMessage<T>, \"customType\" | \"content\" | \"display\" | \"details\">,\n\t\toptions?: SendCustomMessageOptions,\n\t): Promise<void> {\n\t\treturn this._runActivity(\"custom message\", () => this._sendCustomMessage(message, options));\n\t}\n\n\tprivate async _sendCustomMessage<T = unknown>(\n\t\tmessage: Pick<CustomMessage<T>, \"customType\" | \"content\" | \"display\" | \"details\">,\n\t\toptions?: SendCustomMessageOptions,\n\t): Promise<void> {\n\t\tconst appMessage = {\n\t\t\trole: \"custom\" as const,\n\t\t\tcustomType: message.customType,\n\t\t\tcontent: message.content ?? [],\n\t\t\tdisplay: message.display,\n\t\t\tdetails: message.details,\n\t\t\ttimestamp: Date.now(),\n\t\t} satisfies CustomMessage<T>;\n\t\tif (options?.deliverAs === \"nextTurn\") {\n\t\t\tthis._pendingNextTurnMessages.push(appMessage);\n\t\t} else if (this._isAgentRunActive) {\n\t\t\tif (options?.deliverAs === \"followUp\") {\n\t\t\t\tthis.agent.followUp(appMessage);\n\t\t\t} else {\n\t\t\t\tthis.agent.steer(appMessage);\n\t\t\t}\n\t\t} else if (options?.triggerTurn) {\n\t\t\tawait this._runAgentPrompt(appMessage);\n\t\t} else {\n\t\t\tthis.agent.state.messages.push(appMessage);\n\t\t\tthis.session.appendCustomMessageEntry(\n\t\t\t\tappMessage.customType,\n\t\t\t\tappMessage.content,\n\t\t\t\tappMessage.display,\n\t\t\t\tappMessage.details,\n\t\t\t);\n\t\t\tawait this._emit({ type: \"message_start\", message: appMessage });\n\t\t\tawait this._emit({ type: \"message_end\", message: appMessage });\n\t\t}\n\t}\n\n\t/**\n\t * Send a user message to the agent. Always triggers a turn.\n\t * When the agent is streaming, use deliverAs to specify how to queue the message.\n\t *\n\t * @param content User message content (string or content array)\n\t * @param options.deliverAs Delivery mode when streaming: \"steer\" or \"followUp\"\n\t */\n\tasync sendUserMessage(\n\t\tcontent: string | (TextContent | ImageContent)[],\n\t\toptions?: SendUserMessageOptions,\n\t): Promise<void> {\n\t\t// Normalize content to text string + optional images\n\t\tlet text: string;\n\t\tlet images: ImageContent[] | undefined;\n\n\t\tif (typeof content === \"string\") {\n\t\t\ttext = content;\n\t\t} else {\n\t\t\tconst textParts: string[] = [];\n\t\t\timages = [];\n\t\t\tfor (const part of content) {\n\t\t\t\tif (part.type === \"text\") {\n\t\t\t\t\ttextParts.push(part.text);\n\t\t\t\t} else {\n\t\t\t\t\timages.push(part);\n\t\t\t\t}\n\t\t\t}\n\t\t\ttext = textParts.join(\"\\n\");\n\t\t\tif (images.length === 0) images = undefined;\n\t\t}\n\n\t\t// Use prompt() with expandPromptTemplates: false to skip command handling and template expansion\n\t\tawait this.prompt(text, {\n\t\t\texpandPromptTemplates: false,\n\t\t\tstreamingBehavior: options?.deliverAs,\n\t\t\timages,\n\t\t\tsource: \"extension\",\n\t\t});\n\t}\n\n\t/**\n\t * Clear all queued messages and return them.\n\t * Useful for restoring to editor when user aborts.\n\t * @returns Object with steering and followUp arrays\n\t */\n\tclearQueue(): QueuedMessages {\n\t\tconst steering = [...this._steeringMessages];\n\t\tconst followUp = [...this._followUpMessages];\n\t\tthis._steeringMessages = [];\n\t\tthis._followUpMessages = [];\n\t\tthis.agent.clearAllQueues();\n\t\tthis._emitQueueUpdate();\n\t\tthis._activityGeneration++;\n\t\tthis._scheduleSettlementCheck();\n\t\treturn { steering, followUp };\n\t}\n\n\t/** Number of pending messages (includes both steering and follow-up) */\n\tget pendingMessageCount(): number {\n\t\treturn this._steeringMessages.length + this._followUpMessages.length;\n\t}\n\n\t/** Get pending steering messages (read-only) */\n\tgetSteeringMessages(): readonly string[] {\n\t\treturn this._steeringMessages;\n\t}\n\n\t/** Get pending follow-up messages (read-only) */\n\tgetFollowUpMessages(): readonly string[] {\n\t\treturn this._followUpMessages;\n\t}\n\n\tget resourceLoader(): ResourceLoader {\n\t\treturn this._resourceLoader;\n\t}\n\n\t/**\n\t * Abort current operation and wait for agent to become idle.\n\t */\n\tasync abort(): Promise<void> {\n\t\tthis.abortRetry();\n\t\tthis.agent.abort();\n\t\tif (this._barrierOwnerContext.getStore()?.active) return;\n\t\tawait this.waitForIdle();\n\t}\n\n\tprivate async _abortAgentAndDrain(): Promise<void> {\n\t\tthis.abortRetry();\n\t\tthis.agent.abort();\n\t\tawait this.agent.waitForIdle();\n\t}\n\n\t/** Wait for the full AgentSession lifecycle to settle. */\n\twaitForIdle(): Promise<void> {\n\t\tconst owner = this._barrierOwnerContext.getStore();\n\t\tif (owner?.active) {\n\t\t\tthrow new Error(\n\t\t\t\t`AgentSession.waitForIdle() cannot be awaited from ${owner.label} because it is part of the same settlement barrier`,\n\t\t\t);\n\t\t}\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\tthis._idleWaiters.add({ resolve, reject });\n\t\t\tthis._scheduleSettlementCheck();\n\t\t});\n\t}\n\n\t// =========================================================================\n\t// Model Management\n\t// =========================================================================\n\n\tprivate async _emitModelSelect(\n\t\tnextModel: Model<any>,\n\t\tpreviousModel: Model<any> | undefined,\n\t\tsource: ModelSelectSource,\n\t): Promise<void> {\n\t\tif (modelsAreEqual(previousModel, nextModel)) return;\n\t\tawait this._extensionRunner.emit({\n\t\t\ttype: \"model_select\",\n\t\t\tmodel: nextModel,\n\t\t\tpreviousModel,\n\t\t\tsource,\n\t\t});\n\t}\n\n\t/** Whether agent-initiated context compression is active in this session. */\n\tget contextCompressionEnabled(): boolean {\n\t\treturn this.getActiveToolNames().includes(\"compress_context\");\n\t}\n\n\t/** Toggle compression for this session without changing global settings. */\n\tsetContextCompressionEnabled(enabled: boolean): void {\n\t\tif (enabled && !this._toolRegistry.has(\"compress_context\")) {\n\t\t\tthrow new Error(\"Context compression is unavailable under this session's tool restrictions.\");\n\t\t}\n\t\tthis._contextCompressionEnabledOverride = enabled;\n\t\tconst tools = this.getActiveToolNames().filter((name) => name !== \"compress_context\");\n\t\tif (enabled) tools.push(\"compress_context\");\n\t\tthis.setActiveToolsByName(tools);\n\t}\n\n\t/** Detection requires both its session opt-in and an active compression tool. */\n\tget compressionDetectionEnabled(): boolean {\n\t\treturn this._compressionDetectionEnabled && this.contextCompressionEnabled;\n\t}\n\n\tsetCompressionDetectionEnabled(enabled: boolean): void {\n\t\tif (enabled) {\n\t\t\tif (!this.settingsManager.getCompressionDetectionModel()) {\n\t\t\t\tthrow new Error(\"Choose a detection model with /compress-detection-model first.\");\n\t\t\t}\n\t\t\tthis.setContextCompressionEnabled(true);\n\t\t}\n\t\tthis._compressionDetectionEnabled = enabled;\n\t\tthis._compressionDetector.reset();\n\t}\n\n\t/** Save the detector model and opt in for this session only, without changing the primary model. */\n\tsetCompressionDetectionModel(reference: string | undefined): void {\n\t\tif (reference) this.setContextCompressionEnabled(true);\n\t\tthis.settingsManager.setCompressionDetectionModel(reference);\n\t\tthis.setCompressionDetectionEnabled(!!reference);\n\t}\n\n\t/**\n\t * Set model directly.\n\t * Validates that auth is configured, saves to session and settings.\n\t * @throws Error if no auth is configured for the model\n\t */\n\tasync setModel(model: Model<any>): Promise<void> {\n\t\tif (!this._modelRegistry.hasConfiguredAuth(model)) {\n\t\t\tthrow new Error(`No API key for ${model.provider}/${model.id}`);\n\t\t}\n\n\t\tconst previousModel = this.model;\n\t\tconst thinkingLevel = this._getThinkingLevelForModelSwitch();\n\t\tthis.agent.state.model = model;\n\t\tthis.session.appendModelChange(model.provider, model.id);\n\t\tthis.settingsManager.setDefaultModelAndProvider(model.provider, model.id);\n\n\t\t// Re-clamp thinking level for new model's capabilities\n\t\tthis.setThinkingLevel(thinkingLevel);\n\t\tthis._refreshModelDependentRuntime();\n\n\t\tawait this._emitModelSelect(model, previousModel, \"set\");\n\t}\n\n\t/**\n\t * Cycle to next/previous model.\n\t * Uses scoped models (from --models flag) if available, otherwise all available models.\n\t * @param direction - \"forward\" (default) or \"backward\"\n\t * @returns The new model info, or undefined if only one model available\n\t */\n\tasync cycleModel(direction: ModelCycleDirection = \"forward\"): Promise<ModelCycleResult | undefined> {\n\t\tif (this._scopedModels.length > 0) {\n\t\t\treturn this._cycleScopedModel(direction);\n\t\t}\n\t\treturn this._cycleAvailableModel(direction);\n\t}\n\n\tprivate async _cycleScopedModel(direction: ModelCycleDirection): Promise<ModelCycleResult | undefined> {\n\t\tconst scopedModels = this._scopedModels.filter((scoped) => this._modelRegistry.hasConfiguredAuth(scoped.model));\n\t\tif (scopedModels.length <= 1) return undefined;\n\n\t\tconst currentModel = this.model;\n\t\tlet currentIndex = scopedModels.findIndex((sm) => modelsAreEqual(sm.model, currentModel));\n\n\t\tif (currentIndex === -1) currentIndex = 0;\n\t\tconst len = scopedModels.length;\n\t\tconst nextIndex = direction === \"forward\" ? (currentIndex + 1) % len : (currentIndex - 1 + len) % len;\n\t\tconst next = scopedModels[nextIndex];\n\t\tconst thinkingLevel = this._getThinkingLevelForModelSwitch(next.thinkingLevel);\n\n\t\t// Apply model\n\t\tthis.agent.state.model = next.model;\n\t\tthis.session.appendModelChange(next.model.provider, next.model.id);\n\t\tthis.settingsManager.setDefaultModelAndProvider(next.model.provider, next.model.id);\n\n\t\t// Apply thinking level.\n\t\t// - Explicit scoped model thinking level overrides current session level\n\t\t// - Undefined scoped model thinking level inherits the current session preference\n\t\t// setThinkingLevel clamps to model capabilities.\n\t\tthis.setThinkingLevel(thinkingLevel);\n\t\tthis._refreshModelDependentRuntime();\n\n\t\tawait this._emitModelSelect(next.model, currentModel, \"cycle\");\n\n\t\treturn { model: next.model, thinkingLevel: this.thinkingLevel, isScoped: true };\n\t}\n\n\tprivate async _cycleAvailableModel(direction: ModelCycleDirection): Promise<ModelCycleResult | undefined> {\n\t\tconst availableModels = await this._modelRegistry.getAvailable();\n\t\tif (availableModels.length <= 1) return undefined;\n\n\t\tconst currentModel = this.model;\n\t\tlet currentIndex = availableModels.findIndex((m) => modelsAreEqual(m, currentModel));\n\n\t\tif (currentIndex === -1) currentIndex = 0;\n\t\tconst len = availableModels.length;\n\t\tconst nextIndex = direction === \"forward\" ? (currentIndex + 1) % len : (currentIndex - 1 + len) % len;\n\t\tconst nextModel = availableModels[nextIndex];\n\n\t\tconst thinkingLevel = this._getThinkingLevelForModelSwitch();\n\t\tthis.agent.state.model = nextModel;\n\t\tthis.session.appendModelChange(nextModel.provider, nextModel.id);\n\t\tthis.settingsManager.setDefaultModelAndProvider(nextModel.provider, nextModel.id);\n\n\t\t// Re-clamp thinking level for new model's capabilities\n\t\tthis.setThinkingLevel(thinkingLevel);\n\t\tthis._refreshModelDependentRuntime();\n\n\t\tawait this._emitModelSelect(nextModel, currentModel, \"cycle\");\n\n\t\treturn { model: nextModel, thinkingLevel: this.thinkingLevel, isScoped: false };\n\t}\n\n\t// =========================================================================\n\t// Thinking Level Management\n\t// =========================================================================\n\n\t/**\n\t * Set thinking level.\n\t * Clamps to model capabilities based on available thinking levels.\n\t * Saves to session and settings only if the level actually changes.\n\t */\n\tsetThinkingLevel(level: ThinkingLevel): void {\n\t\tconst availableLevels = this.getAvailableThinkingLevels();\n\t\tconst effectiveLevel = availableLevels.includes(level) ? level : this._clampThinkingLevel(level, availableLevels);\n\n\t\t// Only persist if actually changing\n\t\tconst previousLevel = this.agent.state.thinkingLevel;\n\t\tconst isChanging = effectiveLevel !== previousLevel;\n\n\t\tthis.agent.state.thinkingLevel = effectiveLevel;\n\n\t\tif (isChanging) {\n\t\t\tthis.session.appendThinkingLevelChange(effectiveLevel);\n\t\t\tif (this.supportsThinking() || effectiveLevel !== \"off\") {\n\t\t\t\tthis.settingsManager.setDefaultThinkingLevel(effectiveLevel);\n\t\t\t}\n\t\t\tthis._emit({ type: \"thinking_level_changed\", level: effectiveLevel });\n\t\t\tvoid this._extensionRunner.emit({\n\t\t\t\ttype: \"thinking_level_select\",\n\t\t\t\tlevel: effectiveLevel,\n\t\t\t\tpreviousLevel,\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Cycle to next thinking level.\n\t * @returns New level, or undefined if model doesn't support thinking\n\t */\n\tcycleThinkingLevel(): ThinkingLevel | undefined {\n\t\tif (!this.supportsThinking()) return undefined;\n\n\t\tconst levels = this.getAvailableThinkingLevels();\n\t\tconst currentIndex = levels.indexOf(this.thinkingLevel);\n\t\tconst nextIndex = (currentIndex + 1) % levels.length;\n\t\tconst nextLevel = levels[nextIndex];\n\n\t\tthis.setThinkingLevel(nextLevel);\n\t\treturn nextLevel;\n\t}\n\n\t/**\n\t * Get available thinking levels for current model.\n\t * The provider will clamp to what the specific model supports internally.\n\t */\n\tgetAvailableThinkingLevels(): ThinkingLevel[] {\n\t\tif (!this.model) return THINKING_LEVELS;\n\t\treturn getSupportedThinkingLevels(this.model) as ThinkingLevel[];\n\t}\n\n\t/**\n\t * Check if current model supports thinking/reasoning.\n\t */\n\tsupportsThinking(): boolean {\n\t\treturn !!this.model?.reasoning;\n\t}\n\n\tprivate _getThinkingLevelForModelSwitch(explicitLevel?: ThinkingLevel): ThinkingLevel {\n\t\tif (explicitLevel !== undefined) {\n\t\t\treturn explicitLevel;\n\t\t}\n\t\tif (!this.supportsThinking()) {\n\t\t\treturn this.settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL;\n\t\t}\n\t\treturn this.thinkingLevel;\n\t}\n\n\tprivate _clampThinkingLevel(level: ThinkingLevel, _availableLevels: ThinkingLevel[]): ThinkingLevel {\n\t\treturn this.model ? (clampThinkingLevel(this.model, level) as ThinkingLevel) : \"off\";\n\t}\n\n\t// =========================================================================\n\t// Queue Mode Management\n\t// =========================================================================\n\n\t/**\n\t * Set steering message mode.\n\t * Saves to settings.\n\t */\n\tsetSteeringMode(mode: QueueMode): void {\n\t\tthis.agent.steeringMode = mode;\n\t\tthis.settingsManager.setSteeringMode(mode);\n\t}\n\n\t/**\n\t * Set follow-up message mode.\n\t * Saves to settings.\n\t */\n\tsetFollowUpMode(mode: QueueMode): void {\n\t\tthis.agent.followUpMode = mode;\n\t\tthis.settingsManager.setFollowUpMode(mode);\n\t}\n\n\t// =========================================================================\n\t// Compaction\n\t// =========================================================================\n\n\t/** Queue extension-triggered compaction outside the currently active agent turn. */\n\tprivate _requestExtensionCompaction(options?: CompactOptions): void {\n\t\tthis._extensionCompactionActivity ??= this._beginActivity(\"extension compaction request\");\n\t\tthis._extensionCompactionQueue.push(options ?? {});\n\t\tif (this._isAgentRunActive) return;\n\t\tthis._scheduleExtensionCompactions();\n\t}\n\n\tprivate _scheduleExtensionCompactions(): void {\n\t\tconst alreadyScheduled = this._extensionCompactionTimer !== undefined;\n\t\tif (this._extensionCompactionRunning || alreadyScheduled || this._extensionCompactionQueue.length === 0) {\n\t\t\treturn;\n\t\t}\n\t\tthis._extensionCompactionTimer = setTimeout(() => {\n\t\t\tthis._extensionCompactionTimer = undefined;\n\t\t\tvoid this._drainExtensionCompactionQueue();\n\t\t}, 0);\n\t}\n\n\tprivate async _drainExtensionCompactionQueue(mode: ExtensionCompactionQueueMode = \"idle\"): Promise<boolean> {\n\t\tif (this._extensionCompactionRunning) {\n\t\t\treturn false;\n\t\t}\n\t\tthis._extensionCompactionRunning = true;\n\t\tlet compacted = false;\n\t\ttry {\n\t\t\twhile (mode === \"between-turns\" || !this._isAgentRunActive) {\n\t\t\t\tconst options = this._extensionCompactionQueue.shift();\n\t\t\t\tif (!options) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcompacted = (await this._runRequestedExtensionCompaction(options, mode === \"idle\")) || compacted;\n\t\t\t}\n\t\t} finally {\n\t\t\tthis._extensionCompactionRunning = false;\n\t\t}\n\t\tif (mode === \"idle\" && !this._isAgentRunActive) this._scheduleExtensionCompactions();\n\t\tif (\n\t\t\tthis._extensionCompactionQueue.length === 0 &&\n\t\t\tthis._extensionCompactionTimer === undefined &&\n\t\t\t!this._extensionCompactionRunning &&\n\t\t\tthis._extensionCompactionActivity\n\t\t) {\n\t\t\tthis._endActivity(this._extensionCompactionActivity);\n\t\t\tthis._extensionCompactionActivity = undefined;\n\t\t}\n\t\treturn compacted;\n\t}\n\n\tprivate async _runRequestedExtensionCompaction(options: CompactOptions, abortActiveRun: boolean): Promise<boolean> {\n\t\ttry {\n\t\t\tconst result = await this._compactSession(options.customInstructions, { abortActiveRun });\n\t\t\toptions.onComplete?.(result);\n\t\t\treturn true;\n\t\t} catch (error) {\n\t\t\tconst err = error instanceof Error ? error : new Error(String(error));\n\t\t\toptions.onError?.(err);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Manually compact the session context.\n\t * Aborts current agent operation first.\n\t * @param customInstructions Optional instructions for the compaction summary\n\t */\n\tasync compact(customInstructions?: string): Promise<CompactionResult> {\n\t\treturn this._compactSession(customInstructions, { abortActiveRun: true });\n\t}\n\n\tprivate async _runWithCompactionGuard<T>(operation: () => Promise<T>): Promise<T> {\n\t\tif (this._disposed) {\n\t\t\tthrow new Error(\"Agent session is disposed\");\n\t\t}\n\t\tif (this._compactionInFlight) {\n\t\t\tthrow new Error(\"Compaction already in progress\");\n\t\t}\n\t\tconst inFlight = this._runActivity(\"compaction\", async () => {\n\t\t\tawait Promise.resolve();\n\t\t\tif (this._disposed) throw new Error(\"Agent session is disposed\");\n\t\t\treturn operation();\n\t\t});\n\t\tthis._compactionInFlight = inFlight;\n\t\ttry {\n\t\t\treturn await inFlight;\n\t\t} finally {\n\t\t\tif (this._compactionInFlight === inFlight) {\n\t\t\t\tthis._compactionInFlight = undefined;\n\t\t\t\tthis._scheduleSettlementCheck();\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate _compactSession(\n\t\tcustomInstructions: string | undefined,\n\t\toptions: CompactionExecutionOptions,\n\t): Promise<CompactionResult> {\n\t\treturn this._runWithCompactionGuard(() => this._compactSessionUnlocked(customInstructions, options));\n\t}\n\n\tprivate async _prepareForManualCompaction(options: CompactionExecutionOptions): Promise<void> {\n\t\tif (!options.abortActiveRun) return;\n\t\tawait this._abortAgentAndDrain();\n\t\tif (this._disposed) throw new Error(\"Agent session is disposed\");\n\t}\n\n\tprivate async _buildManualCompactionPlan(): Promise<CompactionPlan> {\n\t\tconst model = this.model;\n\t\tif (!model) throw new Error(formatNoModelSelectedMessage());\n\t\tconst auth = await this._getCompactionRequestAuth(model);\n\t\tconst pathEntries = this.session.getBranch();\n\t\tconst preparation = prepareCompaction(pathEntries, this.settingsManager.getCompactionSettings());\n\t\tif (!preparation) {\n\t\t\tconst lastEntry = pathEntries[pathEntries.length - 1];\n\t\t\tif (lastEntry?.type === \"compaction\") throw new Error(\"Already compacted\");\n\t\t\tthrow new Error(\"Nothing to compact (session too small)\");\n\t\t}\n\t\treturn { ...auth, model, preparation, pathEntries };\n\t}\n\n\tprivate async _runManualCompactionInterceptors(\n\t\tplan: CompactionPlan,\n\t\tcustomInstructions: string | undefined,\n\t\tsignal: AbortSignal,\n\t): Promise<CompactionResult | undefined> {\n\t\tconst preHook = await this._runHook({\n\t\t\t...this._hookCommon(\"PreCompact\"),\n\t\t\thook_event_name: \"PreCompact\",\n\t\t\ttrigger: \"manual\",\n\t\t\tcustom_instructions: customInstructions ?? \"\",\n\t\t});\n\t\tif (preHook.blocked || !preHook.continue) throw new Error(preHook.reason ?? \"Compaction blocked by hook\");\n\t\tif (!this._extensionRunner.hasHandlers(\"session_before_compact\")) return undefined;\n\t\tconst result = (await this._extensionRunner.emit({\n\t\t\ttype: \"session_before_compact\",\n\t\t\tpreparation: plan.preparation,\n\t\t\tbranchEntries: plan.pathEntries,\n\t\t\tcustomInstructions,\n\t\t\tsignal,\n\t\t})) as SessionBeforeCompactResult | undefined;\n\t\tif (result?.cancel) throw new Error(\"Compaction cancelled\");\n\t\treturn result?.compaction;\n\t}\n\n\tprivate async _resolveManualCompaction(\n\t\tplan: CompactionPlan,\n\t\tcustomInstructions: string | undefined,\n\t\textensionCompaction: CompactionResult | undefined,\n\t\tsignal: AbortSignal,\n\t): Promise<CompactionResult> {\n\t\tif (extensionCompaction) return extensionCompaction;\n\t\treturn compact(\n\t\t\tplan.preparation,\n\t\t\tplan.model,\n\t\t\tplan.apiKey,\n\t\t\tplan.headers,\n\t\t\tcustomInstructions,\n\t\t\tsignal,\n\t\t\tthis.thinkingLevel,\n\t\t\tthis.agent.streamFn,\n\t\t\tthis.settingsManager.getRetrySettings(),\n\t\t\tthis._summarizationRetryCallbacks({ source: \"compaction\", reason: \"manual\" }),\n\t\t);\n\t}\n\n\tprivate async _commitManualCompaction(result: CompactionResult, fromExtension: boolean): Promise<void> {\n\t\tthis.session.appendCompaction(\n\t\t\tresult.summary,\n\t\t\tresult.firstKeptEntryId,\n\t\t\tresult.tokensBefore,\n\t\t\tresult.details,\n\t\t\tfromExtension,\n\t\t);\n\t\tconst newEntries = this.session.getEntries();\n\t\tthis.agent.state.messages = this.session.buildSessionContext().messages;\n\t\tthis._compressionDetector.reset(this.agent.state.messages, this.getContextUsage()?.percent ?? null);\n\t\tconst savedCompactionEntry = newEntries.find(\n\t\t\t(entry): entry is CompactionEntry => entry.type === \"compaction\" && entry.summary === result.summary,\n\t\t);\n\t\tif (savedCompactionEntry) {\n\t\t\tawait this._extensionRunner.emit({\n\t\t\t\ttype: \"session_compact\",\n\t\t\t\tcompactionEntry: savedCompactionEntry,\n\t\t\t\tfromExtension,\n\t\t\t});\n\t\t}\n\t\tawait this._runHook({\n\t\t\t...this._hookCommon(\"PostCompact\"),\n\t\t\thook_event_name: \"PostCompact\",\n\t\t\ttrigger: \"manual\",\n\t\t\tcompact_summary: result.summary,\n\t\t});\n\t}\n\n\tprivate async _compactSessionUnlocked(\n\t\tcustomInstructions: string | undefined,\n\t\toptions: CompactionExecutionOptions,\n\t): Promise<CompactionResult> {\n\t\tawait this._prepareForManualCompaction(options);\n\t\tconst abortController = new AbortController();\n\t\tthis._compactionAbortController = abortController;\n\t\tthis._emit({ type: \"compaction_start\", reason: \"manual\" });\n\t\ttry {\n\t\t\tconst plan = await this._buildManualCompactionPlan();\n\t\t\tconst extensionCompaction = await this._runManualCompactionInterceptors(\n\t\t\t\tplan,\n\t\t\t\tcustomInstructions,\n\t\t\t\tabortController.signal,\n\t\t\t);\n\t\t\tconst result = await this._resolveManualCompaction(\n\t\t\t\tplan,\n\t\t\t\tcustomInstructions,\n\t\t\t\textensionCompaction,\n\t\t\t\tabortController.signal,\n\t\t\t);\n\t\t\tif (abortController.signal.aborted) throw new Error(\"Compaction cancelled\");\n\t\t\tconst fromExtension = extensionCompaction !== undefined;\n\t\t\tawait this._commitManualCompaction(result, fromExtension);\n\t\t\tconst compactionResult: CompactionResult = {\n\t\t\t\tsummary: result.summary,\n\t\t\t\tfirstKeptEntryId: result.firstKeptEntryId,\n\t\t\t\ttokensBefore: result.tokensBefore,\n\t\t\t\tdetails: result.details,\n\t\t\t};\n\t\t\t// compaction_end listeners may submit queued prompts, so expose idle state before notifying them.\n\t\t\tthis._compactionAbortController = undefined;\n\t\t\tthis._compactionInFlight = undefined;\n\t\t\tthis._emit({\n\t\t\t\ttype: \"compaction_end\",\n\t\t\t\treason: \"manual\",\n\t\t\t\tresult: compactionResult,\n\t\t\t\taborted: false,\n\t\t\t\twillRetry: false,\n\t\t\t});\n\t\t\treturn compactionResult;\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tconst aborted = message === \"Compaction cancelled\" || (error instanceof Error && error.name === \"AbortError\");\n\t\t\tthis._compactionAbortController = undefined;\n\t\t\tthis._compactionInFlight = undefined;\n\t\t\tthis._emit({\n\t\t\t\ttype: \"compaction_end\",\n\t\t\t\treason: \"manual\",\n\t\t\t\tresult: undefined,\n\t\t\t\taborted,\n\t\t\t\twillRetry: false,\n\t\t\t\terrorMessage: aborted ? undefined : `Compaction failed: ${message}`,\n\t\t\t});\n\t\t\tthrow error;\n\t\t} finally {\n\t\t\tthis._compactionAbortController = undefined;\n\t\t}\n\t}\n\n\t/**\n\t * Cancel in-progress compaction (manual or auto).\n\t */\n\tabortCompaction(): void {\n\t\tthis._compactionAbortController?.abort();\n\t\tthis._autoCompactionAbortController?.abort();\n\t}\n\n\t/**\n\t * Cancel in-progress branch summarization.\n\t */\n\tabortBranchSummary(): void {\n\t\tthis._branchSummaryAbortController?.abort();\n\t}\n\n\tprivate _isAtOrBeforeCompactionBoundary(timestamp: number, compactionEntry: CompactionEntry | null): boolean {\n\t\treturn compactionEntry !== null && timestamp <= new Date(compactionEntry.timestamp).getTime();\n\t}\n\n\tprivate async _runOverflowCompactionRecovery(): Promise<boolean> {\n\t\tif (this._overflowRecoveryAttempted) {\n\t\t\tthis._emit({\n\t\t\t\ttype: \"compaction_end\",\n\t\t\t\treason: \"overflow\",\n\t\t\t\tresult: undefined,\n\t\t\t\taborted: false,\n\t\t\t\twillRetry: false,\n\t\t\t\terrorMessage:\n\t\t\t\t\t\"Context overflow recovery failed after one compact-and-retry attempt. Try reducing context or switching to a larger-context model.\",\n\t\t\t});\n\t\t\treturn false;\n\t\t}\n\t\tthis._overflowRecoveryAttempted = true;\n\t\t// Keep the persisted error in session history, but remove it from retry context.\n\t\tconst messages = this.agent.state.messages;\n\t\tif (messages.length > 0 && messages[messages.length - 1].role === \"assistant\") {\n\t\t\tthis.agent.state.messages = messages.slice(0, -1);\n\t\t}\n\t\treturn await this._runAutoCompaction(\"overflow\", true);\n\t}\n\n\tprivate _resolveCompactionThresholdTokens(\n\t\tassistantMessage: AssistantMessage,\n\t\tcompactionEntry: CompactionEntry | null,\n\t): number | undefined {\n\t\tif (assistantMessage.stopReason !== \"error\") return calculateContextTokens(assistantMessage.usage);\n\t\tconst messages = this.agent.state.messages;\n\t\tconst estimate = estimateContextTokens(messages);\n\t\tif (estimate.lastUsageIndex === null) return undefined;\n\t\t// Kept pre-compaction usage reflects the old context and must not trigger another compaction.\n\t\tconst usageMessage = messages[estimate.lastUsageIndex];\n\t\tif (\n\t\t\tusageMessage.role === \"assistant\" &&\n\t\t\tthis._isAtOrBeforeCompactionBoundary((usageMessage as AssistantMessage).timestamp, compactionEntry)\n\t\t) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn estimate.tokens;\n\t}\n\n\t/**\n\t * Check if compaction is needed and run it.\n\t * Called after agent_end and before prompt submission.\n\t *\n\t * Two cases:\n\t * 1. Overflow: LLM returned context overflow error, remove error message from agent state, compact, auto-retry\n\t * 2. Threshold: Context over threshold, compact, NO auto-retry (user continues manually)\n\t *\n\t * @param assistantMessage The assistant message to check\n\t * @param skipAbortedCheck If false, include aborted messages (for pre-prompt check). Default: true\n\t */\n\tprivate async _checkCompaction(assistantMessage: AssistantMessage, skipAbortedCheck = true): Promise<boolean> {\n\t\tconst settings = this.settingsManager.getCompactionSettings();\n\t\tif (!settings.enabled) return false;\n\t\tif (skipAbortedCheck && assistantMessage.stopReason === \"aborted\") return false;\n\t\tconst contextWindow = this.model?.contextWindow ?? 0;\n\t\tconst sameModel =\n\t\t\tthis.model && assistantMessage.provider === this.model.provider && assistantMessage.model === this.model.id;\n\t\tconst compactionEntry = getLatestCompactionEntry(this.session.getBranch());\n\t\tif (this._isAtOrBeforeCompactionBoundary(assistantMessage.timestamp, compactionEntry)) return false;\n\t\tif (sameModel && isContextOverflow(assistantMessage, contextWindow)) {\n\t\t\treturn await this._runOverflowCompactionRecovery();\n\t\t}\n\t\tconst contextTokens = this._resolveCompactionThresholdTokens(assistantMessage, compactionEntry);\n\t\tif (contextTokens === undefined || !shouldCompact(contextTokens, contextWindow, settings)) return false;\n\t\treturn await this._runAutoCompaction(\"threshold\", false);\n\t}\n\n\t/**\n\t * Internal: Run auto-compaction with events.\n\t */\n\tprivate _runAutoCompaction(reason: AutoCompactionReason, willRetry: boolean): Promise<boolean> {\n\t\tif (this._compactionInFlight || this._disposed) return Promise.resolve(false);\n\t\treturn this._runWithCompactionGuard(() => this._runAutoCompactionUnlocked(reason, willRetry));\n\t}\n\n\tprivate _emitAutoCompactionWithoutResult(\n\t\treason: AutoCompactionReason,\n\t\taborted: boolean,\n\t\terrorMessage?: string,\n\t): void {\n\t\tthis._emit({\n\t\t\ttype: \"compaction_end\",\n\t\t\treason,\n\t\t\tresult: undefined,\n\t\t\taborted,\n\t\t\twillRetry: false,\n\t\t\t...(errorMessage === undefined ? {} : { errorMessage }),\n\t\t});\n\t}\n\n\tprivate async _buildAutoCompactionPlan(\n\t\treason: AutoCompactionReason,\n\t\tsettings: CompactionSettings,\n\t): Promise<CompactionPlan | undefined> {\n\t\tconst model = this.model;\n\t\tif (!model) {\n\t\t\tthis._emitAutoCompactionWithoutResult(reason, false);\n\t\t\treturn undefined;\n\t\t}\n\t\tlet auth: CompactionRequestAuth;\n\t\tif (this.agent.streamFn === streamSimple) {\n\t\t\tconst authResult = await this._modelRegistry.getApiKeyAndHeaders(model);\n\t\t\tif (!authResult.ok || !authResult.apiKey) {\n\t\t\t\tthis._emitAutoCompactionWithoutResult(reason, false);\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\tauth = { apiKey: authResult.apiKey, headers: authResult.headers };\n\t\t} else {\n\t\t\tauth = await this._getCompactionRequestAuth(model);\n\t\t}\n\t\tconst pathEntries = this.session.getBranch();\n\t\tconst preparation = prepareCompaction(pathEntries, settings);\n\t\tif (!preparation) {\n\t\t\tthis._emitAutoCompactionWithoutResult(reason, false);\n\t\t\treturn undefined;\n\t\t}\n\t\treturn { ...auth, model, preparation, pathEntries };\n\t}\n\n\tprivate async _runAutoCompactionInterceptors(\n\t\tplan: CompactionPlan,\n\t\tsignal: AbortSignal,\n\t): Promise<AutoCompactionInterception> {\n\t\tconst preHook = await this._runHook({\n\t\t\t...this._hookCommon(\"PreCompact\"),\n\t\t\thook_event_name: \"PreCompact\",\n\t\t\ttrigger: \"auto\",\n\t\t\tcustom_instructions: \"\",\n\t\t});\n\t\tif (preHook.blocked || !preHook.continue) return { cancelled: true };\n\t\tif (!this._extensionRunner.hasHandlers(\"session_before_compact\")) return { cancelled: false };\n\t\tconst extensionResult = (await this._extensionRunner.emit({\n\t\t\ttype: \"session_before_compact\",\n\t\t\tpreparation: plan.preparation,\n\t\t\tbranchEntries: plan.pathEntries,\n\t\t\tcustomInstructions: undefined,\n\t\t\tsignal,\n\t\t})) as SessionBeforeCompactResult | undefined;\n\t\tif (extensionResult?.cancel) return { cancelled: true };\n\t\treturn { cancelled: false, compaction: extensionResult?.compaction };\n\t}\n\n\tprivate async _resolveAutoCompaction(\n\t\tplan: CompactionPlan,\n\t\tinterception: AutoCompactionInterception,\n\t\treason: AutoCompactionReason,\n\t\tsignal: AbortSignal,\n\t): Promise<ResolvedAutoCompaction> {\n\t\tif (interception.compaction) return { result: interception.compaction, fromExtension: true };\n\t\tconst result = await compact(\n\t\t\tplan.preparation,\n\t\t\tplan.model,\n\t\t\tplan.apiKey,\n\t\t\tplan.headers,\n\t\t\tundefined,\n\t\t\tsignal,\n\t\t\tthis.thinkingLevel,\n\t\t\tthis.agent.streamFn,\n\t\t\tthis.settingsManager.getRetrySettings(),\n\t\t\tthis._summarizationRetryCallbacks({ source: \"compaction\", reason }),\n\t\t);\n\t\treturn { result, fromExtension: false };\n\t}\n\n\tprivate async _commitAutoCompaction(compaction: ResolvedAutoCompaction): Promise<void> {\n\t\tconst { result, fromExtension } = compaction;\n\t\tthis.session.appendCompaction(\n\t\t\tresult.summary,\n\t\t\tresult.firstKeptEntryId,\n\t\t\tresult.tokensBefore,\n\t\t\tresult.details,\n\t\t\tfromExtension,\n\t\t);\n\t\tconst newEntries = this.session.getEntries();\n\t\tthis.agent.state.messages = this.session.buildSessionContext().messages;\n\t\tthis._compressionDetector.reset(this.agent.state.messages, this.getContextUsage()?.percent ?? null);\n\t\tconst savedCompactionEntry = newEntries.find(\n\t\t\t(entry): entry is CompactionEntry => entry.type === \"compaction\" && entry.summary === result.summary,\n\t\t);\n\t\tif (savedCompactionEntry) {\n\t\t\tawait this._extensionRunner.emit({\n\t\t\t\ttype: \"session_compact\",\n\t\t\t\tcompactionEntry: savedCompactionEntry,\n\t\t\t\tfromExtension,\n\t\t\t});\n\t\t}\n\t\tawait this._runHook({\n\t\t\t...this._hookCommon(\"PostCompact\"),\n\t\t\thook_event_name: \"PostCompact\",\n\t\t\ttrigger: \"auto\",\n\t\t\tcompact_summary: result.summary,\n\t\t});\n\t}\n\n\tprivate async _runAutoCompactionUnlocked(reason: AutoCompactionReason, willRetry: boolean): Promise<boolean> {\n\t\tconst settings = this.settingsManager.getCompactionSettings();\n\t\tthis._emit({ type: \"compaction_start\", reason });\n\t\tconst abortController = new AbortController();\n\t\tthis._autoCompactionAbortController = abortController;\n\n\t\ttry {\n\t\t\tconst plan = await this._buildAutoCompactionPlan(reason, settings);\n\t\t\tif (!plan) return false;\n\t\t\tconst interception = await this._runAutoCompactionInterceptors(plan, abortController.signal);\n\t\t\tif (interception.cancelled) {\n\t\t\t\tthis._emitAutoCompactionWithoutResult(reason, true);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tconst compaction = await this._resolveAutoCompaction(plan, interception, reason, abortController.signal);\n\t\t\tif (abortController.signal.aborted) {\n\t\t\t\tthis._emitAutoCompactionWithoutResult(reason, true);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tawait this._commitAutoCompaction(compaction);\n\t\t\tthis._emit({ type: \"compaction_end\", reason, result: compaction.result, aborted: false, willRetry });\n\n\t\t\tif (willRetry) {\n\t\t\t\tconst messages = this.agent.state.messages;\n\t\t\t\tconst lastMessage = messages[messages.length - 1];\n\t\t\t\tif (lastMessage?.role === \"assistant\" && (lastMessage as AssistantMessage).stopReason === \"error\") {\n\t\t\t\t\tthis.agent.state.messages = messages.slice(0, -1);\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\t// Auto-compaction can complete while follow-up/steering/custom messages are waiting.\n\t\t\t// Continue once so queued messages are delivered.\n\t\t\treturn this.agent.hasQueuedMessages();\n\t\t} catch (error) {\n\t\t\tconst errorMessage = error instanceof Error ? error.message : \"compaction failed\";\n\t\t\tthis._emitAutoCompactionWithoutResult(\n\t\t\t\treason,\n\t\t\t\tfalse,\n\t\t\t\treason === \"overflow\"\n\t\t\t\t\t? `Context overflow recovery failed: ${errorMessage}`\n\t\t\t\t\t: `Auto-compaction failed: ${errorMessage}`,\n\t\t\t);\n\t\t\treturn false;\n\t\t} finally {\n\t\t\tthis._autoCompactionAbortController = undefined;\n\t\t}\n\t}\n\n\t/**\n\t * Toggle auto-compaction setting.\n\t */\n\tsetAutoCompactionEnabled(enabled: boolean): void {\n\t\tthis.settingsManager.setCompactionEnabled(enabled);\n\t}\n\n\t/** Whether auto-compaction is enabled */\n\tget autoCompactionEnabled(): boolean {\n\t\treturn this.settingsManager.getCompactionEnabled();\n\t}\n\n\t/** Whether built-in subagent tools are enabled by default. */\n\tget subagentsEnabled(): boolean {\n\t\treturn this.settingsManager.getEnableSubagents();\n\t}\n\n\t/** Persist and immediately apply built-in subagent tool activation. */\n\tsetSubagentsEnabled(enabled: boolean): void {\n\t\tthis.settingsManager.setEnableSubagents(enabled);\n\t\tthis._refreshToolRegistry({ activeToolNames: this._withCurrentDefaultTools(this.getActiveToolNames()) });\n\t}\n\n\tbindExtensions(bindings: ExtensionBindings): Promise<void> {\n\t\treturn this._runActivity(\"extension binding\", () => this._bindExtensions(bindings));\n\t}\n\n\tprivate async _bindExtensions(bindings: ExtensionBindings): Promise<void> {\n\t\tif (bindings.uiContext !== undefined) {\n\t\t\tthis._extensionUIContext = bindings.uiContext;\n\t\t}\n\t\tif (bindings.commandContextActions !== undefined) {\n\t\t\tthis._extensionCommandContextActions = bindings.commandContextActions;\n\t\t}\n\t\tif (bindings.abortHandler !== undefined) {\n\t\t\tthis._extensionAbortHandler = bindings.abortHandler;\n\t\t}\n\t\tif (bindings.shutdownHandler !== undefined) {\n\t\t\tthis._extensionShutdownHandler = bindings.shutdownHandler;\n\t\t}\n\t\tif (bindings.onError !== undefined) {\n\t\t\tthis._extensionErrorListener = bindings.onError;\n\t\t}\n\n\t\tthis._applyExtensionBindings(this._extensionRunner);\n\t\tawait this._extensionRunner.emit(this._sessionStartEvent);\n\t\tawait this.extendResourcesFromExtensions(this._sessionStartEvent.reason === \"reload\" ? \"reload\" : \"startup\");\n\t}\n\n\tprivate async extendResourcesFromExtensions(reason: ResourcesDiscoverReason): Promise<void> {\n\t\tif (!this._extensionRunner.hasHandlers(\"resources_discover\")) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst { skillPaths, rulePaths, promptPaths, themePaths } = await this._extensionRunner.emitResourcesDiscover(\n\t\t\tthis._cwd,\n\t\t\treason,\n\t\t);\n\n\t\tif (skillPaths.length === 0 && rulePaths.length === 0 && promptPaths.length === 0 && themePaths.length === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst extensionPaths: ResourceExtensionPaths = {\n\t\t\tskillPaths: this.buildExtensionResourcePaths(skillPaths),\n\t\t\trulePaths: this.buildExtensionResourcePaths(rulePaths),\n\t\t\tpromptPaths: this.buildExtensionResourcePaths(promptPaths),\n\t\t\tthemePaths: this.buildExtensionResourcePaths(themePaths),\n\t\t};\n\n\t\tthis._resourceLoader.extendResources(extensionPaths);\n\t\tthis._baseSystemPrompt = this._rebuildSystemPrompt(this.getActiveToolNames());\n\t\tthis.agent.state.systemPrompt = this._systemPromptOverride ?? this._baseSystemPrompt;\n\t}\n\n\tprivate buildExtensionResourcePaths(entries: ExtensionResourcePath[]): ResourcePathEntry[] {\n\t\treturn entries.map((entry) => {\n\t\t\tconst source = this.getExtensionSourceLabel(entry.extensionPath);\n\t\t\tconst baseDir = entry.extensionPath.startsWith(\"<\") ? undefined : dirname(entry.extensionPath);\n\t\t\treturn {\n\t\t\t\tpath: entry.path,\n\t\t\t\tmetadata: {\n\t\t\t\t\tsource,\n\t\t\t\t\tscope: \"temporary\",\n\t\t\t\t\torigin: \"top-level\",\n\t\t\t\t\tbaseDir,\n\t\t\t\t},\n\t\t\t};\n\t\t});\n\t}\n\n\tprivate getExtensionSourceLabel(extensionPath: string): string {\n\t\tif (extensionPath.startsWith(\"<\")) {\n\t\t\treturn `extension:${extensionPath.replace(/[<>]/g, \"\")}`;\n\t\t}\n\t\tconst base = basename(extensionPath);\n\t\tconst name = base.replace(/\\.(ts|js)$/, \"\");\n\t\treturn `extension:${name}`;\n\t}\n\n\tprivate _applyExtensionBindings(runner: ExtensionRunner): void {\n\t\trunner.setUIContext(this._extensionUIContext);\n\t\trunner.bindCommandContext(this._extensionCommandContextActions);\n\n\t\tthis._extensionErrorUnsubscriber?.();\n\t\tthis._extensionErrorUnsubscriber = this._extensionErrorListener\n\t\t\t? runner.onError(this._extensionErrorListener)\n\t\t\t: undefined;\n\t}\n\n\tprivate _refreshCurrentModelFromRegistry(): void {\n\t\tconst currentModel = this.model;\n\t\tif (!currentModel) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst refreshedModel = this._modelRegistry.find(currentModel.provider, currentModel.id);\n\t\tif (refreshedModel && refreshedModel !== currentModel) {\n\t\t\tthis.agent.state.model = refreshedModel;\n\t\t}\n\t\tthis._refreshModelDependentRuntime();\n\t}\n\n\tprivate _updateCurrentRunnerLspOwnership(): void {\n\t\tconst ownership = this._extensionLspOwnership.get(this._extensionRunner);\n\t\tif (!ownership) return;\n\t\townership.configuration = structuredClone(this._lspConfiguration);\n\t\townership.runtimeState = this._lspRuntimeState;\n\t}\n\n\tprivate _bindExtensionCore(runner: ExtensionRunner): void {\n\t\tconst getToolOperations = (): ToolOperations => this.getToolOperations();\n\t\tconst boundLspOwnership: ExtensionLspOwnership = {\n\t\t\truntimeState: this._lspRuntimeState,\n\t\t\tconfiguration: structuredClone(this._lspConfiguration),\n\t\t};\n\t\tthis._extensionLspOwnership.set(runner, boundLspOwnership);\n\t\tconst getBoundLspStatus = (): LspSessionStatus =>\n\t\t\trunner === this._extensionRunner\n\t\t\t\t? this.getLspStatus()\n\t\t\t\t: {\n\t\t\t\t\t\towner: \"agent-session\",\n\t\t\t\t\t\tenabled: boundLspOwnership.configuration.enabled,\n\t\t\t\t\t\tconfiguration: structuredClone(boundLspOwnership.configuration),\n\t\t\t\t\t\tservers: boundLspOwnership.runtimeState?.manager.getStatus() ?? [],\n\t\t\t\t\t};\n\n\t\tconst getCommands = (): SlashCommandInfo[] => {\n\t\t\tconst extensionCommands: SlashCommandInfo[] = runner\n\t\t\t\t.getRegisteredCommands()\n\t\t\t\t.filter((command) => !HIDDEN_BUILTIN_SLASH_COMMAND_NAMES.has(command.name))\n\t\t\t\t.map((command) => ({\n\t\t\t\t\tname: command.invocationName,\n\t\t\t\t\tdescription: command.description,\n\t\t\t\t\tsource: \"extension\",\n\t\t\t\t\tsourceInfo: command.sourceInfo,\n\t\t\t\t}));\n\n\t\t\tconst templates: SlashCommandInfo[] = this.promptTemplates.map((template) => ({\n\t\t\t\tname: template.name,\n\t\t\t\tdescription: template.description,\n\t\t\t\tsource: \"prompt\",\n\t\t\t\tsourceInfo: template.sourceInfo,\n\t\t\t}));\n\n\t\t\tconst skills: SlashCommandInfo[] = this.getRegisteredSkills().map((skill) => ({\n\t\t\t\tname: `skill:${skill.name}`,\n\t\t\t\tdescription: skill.description,\n\t\t\t\tsource: \"skill\",\n\t\t\t\tsourceInfo: skill.sourceInfo,\n\t\t\t}));\n\n\t\t\tconst rules: SlashCommandInfo[] = this.getRegisteredRules().map((rule) => ({\n\t\t\t\tname: `rule:${rule.name}`,\n\t\t\t\tdescription: rule.description,\n\t\t\t\tsource: \"rule\",\n\t\t\t\tsourceInfo: rule.sourceInfo,\n\t\t\t}));\n\n\t\t\treturn [...extensionCommands, ...templates, ...skills, ...rules];\n\t\t};\n\n\t\trunner.bindCore(\n\t\t\t{\n\t\t\t\tsendMessage: (message, options) => {\n\t\t\t\t\tthis.sendCustomMessage(message, options).catch((err) => {\n\t\t\t\t\t\trunner.emitError({\n\t\t\t\t\t\t\textensionPath: \"<runtime>\",\n\t\t\t\t\t\t\tevent: \"send_message\",\n\t\t\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tsendUserMessage: (content, options) => {\n\t\t\t\t\tthis.sendUserMessage(content, options).catch((err) => {\n\t\t\t\t\t\trunner.emitError({\n\t\t\t\t\t\t\textensionPath: \"<runtime>\",\n\t\t\t\t\t\t\tevent: \"send_user_message\",\n\t\t\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tappendEntry: (customType, data) => {\n\t\t\t\t\tthis.session.appendCustomEntry(customType, data);\n\t\t\t\t},\n\t\t\t\tsetSessionName: (name) => {\n\t\t\t\t\tthis.setSessionName(name);\n\t\t\t\t},\n\t\t\t\tgetSessionName: () => {\n\t\t\t\t\treturn this.session.getSessionName();\n\t\t\t\t},\n\t\t\t\tsetLabel: (entryId, label) => {\n\t\t\t\t\tthis.session.appendLabelChange(entryId, label);\n\t\t\t\t},\n\t\t\t\tgetActiveTools: () => this.getActiveToolNames(),\n\t\t\t\tgetAllTools: () => this.getAllTools(),\n\t\t\t\tgetAvailableTools: () => this.getAvailableSessionTools(),\n\t\t\t\tsetActiveTools: (toolNames) => this.setActiveToolsByName(toolNames),\n\t\t\t\tunregisterTool: (name) => this.unregisterSessionTool(name),\n\t\t\t\tloadTool: (name) => this.loadSessionTool(name),\n\t\t\t\tunloadTool: (name) => this.unloadSessionTool(name),\n\t\t\t\trefreshTools: () => this._refreshToolRegistry(),\n\t\t\t\tgetCommands,\n\t\t\t\tsetModel: async (model) => {\n\t\t\t\t\tif (!this.modelRegistry.hasConfiguredAuth(model)) return false;\n\t\t\t\t\tawait this.setModel(model);\n\t\t\t\t\treturn true;\n\t\t\t\t},\n\t\t\t\tgetThinkingLevel: () => this.thinkingLevel,\n\t\t\t\tsetThinkingLevel: (level) => this.setThinkingLevel(level),\n\t\t\t},\n\t\t\t{\n\t\t\t\tgetModel: () => this.model,\n\t\t\t\tisIdle: () => this.isIdle,\n\t\t\t\tgetSignal: () => this.agent.signal,\n\t\t\t\tabort: () => {\n\t\t\t\t\tif (this._extensionAbortHandler) {\n\t\t\t\t\t\tthis._extensionAbortHandler();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tvoid this.abort();\n\t\t\t\t},\n\t\t\t\thasPendingMessages: () => this.pendingMessageCount > 0,\n\t\t\t\tshutdown: () => {\n\t\t\t\t\tthis._extensionShutdownHandler?.();\n\t\t\t\t},\n\t\t\t\tgetContextUsage: () => this.getContextUsage(),\n\t\t\t\tcompact: (options) => {\n\t\t\t\t\tthis._requestExtensionCompaction(options);\n\t\t\t\t},\n\t\t\t\tgetSystemPrompt: () => this.systemPrompt,\n\t\t\t\tgetToolOperations,\n\t\t\t\tgetToolBackendInfo: () => this.getToolBackendInfo(),\n\t\t\t\tgetLspStatus: getBoundLspStatus,\n\t\t\t\tconfigureLsp: (configuration) => {\n\t\t\t\t\tif (runner !== this._extensionRunner) throw new Error(STALE_EXTENSION_CONTEXT_MESSAGE);\n\t\t\t\t\treturn this.configureLsp(configuration);\n\t\t\t\t},\n\t\t\t\texecToolBackend: (command, options) =>\n\t\t\t\t\tthis._runActivity(\"extension backend command\", async () => {\n\t\t\t\t\t\tconst operations = getToolOperations();\n\t\t\t\t\t\tconst prefix = this.settingsManager.getShellCommandPrefix();\n\t\t\t\t\t\tconst resolvedCommand = prefix ? `${prefix}\\n${command}` : command;\n\t\t\t\t\t\treturn executeBashWithOperations(\n\t\t\t\t\t\t\tresolvedCommand,\n\t\t\t\t\t\t\toptions?.cwd ?? operations.cwd,\n\t\t\t\t\t\t\toperations,\n\t\t\t\t\t\t\toptions,\n\t\t\t\t\t\t);\n\t\t\t\t\t}),\n\t\t\t},\n\t\t\t{\n\t\t\t\tregisterProvider: (name, config) => {\n\t\t\t\t\tthis._modelRegistry.registerProvider(name, config);\n\t\t\t\t\tthis._refreshCurrentModelFromRegistry();\n\t\t\t\t},\n\t\t\t\tunregisterProvider: (name) => {\n\t\t\t\t\tthis._modelRegistry.unregisterProvider(name);\n\t\t\t\t\tthis._refreshCurrentModelFromRegistry();\n\t\t\t\t},\n\t\t\t},\n\t\t);\n\t}\n\n\tprivate _createLoadToolDefinition(): ToolDefinition {\n\t\tconst parameters = Type.Object({\n\t\t\tname: Type.Union([Type.String(), Type.Array(Type.String())], {\n\t\t\t\tdescription:\n\t\t\t\t\t\"Name of the available session-scoped tool to load, or an array of names to load several at once\",\n\t\t\t}),\n\t\t});\n\t\treturn {\n\t\t\tname: \"load_tool\",\n\t\t\tlabel: \"Load tool\",\n\t\t\tdescription:\n\t\t\t\t\"Load one or more session-scoped available tools into the active tool context for subsequent turns. Pass a single name or an array of names to load several in one call.\",\n\t\t\tpromptSnippet:\n\t\t\t\t\"Use load_tool to load session-scoped available tools listed in <available-tools> before calling them; pass an array of names to load several at once.\",\n\t\t\tparameters,\n\t\t\trenderCall: (args: unknown) => {\n\t\t\t\treturn new Text(\n\t\t\t\t\tformatSessionToolLifecycleCall(\"load\", getToolNameArgs(args as SessionToolNameArgs | undefined)),\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t);\n\t\t\t},\n\t\t\trenderResult: (result, _options, _theme, context) => {\n\t\t\t\tconst details = result.details as { loaded?: string[]; notFound?: string[] } | undefined;\n\t\t\t\tconst loaded = details?.loaded ?? getToolNameArgs(context.args as SessionToolNameArgs | undefined);\n\t\t\t\treturn new Text(formatSessionToolLifecycleResult(\"loaded\", loaded, details?.notFound ?? []), 0, 0);\n\t\t\t},\n\t\t\texecutionMode: \"sequential\",\n\t\t\texecute: async (_toolCallId, params: SessionToolNameParameters) => {\n\t\t\t\tconst names = normalizeSessionToolNames(params.name);\n\t\t\t\tconst loaded: string[] = [];\n\t\t\t\tconst notFound: string[] = [];\n\t\t\t\tfor (const name of names) {\n\t\t\t\t\tif (this.loadSessionTool(name)) loaded.push(name);\n\t\t\t\t\telse notFound.push(name);\n\t\t\t\t}\n\t\t\t\tconst parts: string[] = [];\n\t\t\t\tif (loaded.length > 0) {\n\t\t\t\t\tparts.push(\n\t\t\t\t\t\t`Loaded tool${loaded.length > 1 ? \"s\" : \"\"}: ${loaded.join(\", \")}. ${loaded.length > 1 ? \"They are\" : \"It is\"} now active and can be called.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (notFound.length > 0) {\n\t\t\t\t\tparts.push(`Tool${notFound.length > 1 ? \"s\" : \"\"} not found in this session: ${notFound.join(\", \")}.`);\n\t\t\t\t}\n\t\t\t\tif (parts.length === 0) {\n\t\t\t\t\tparts.push(\"No tool names provided.\");\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: parts.join(\" \") }],\n\t\t\t\t\tdetails: { names, loaded, notFound },\n\t\t\t\t};\n\t\t\t},\n\t\t};\n\t}\n\n\tprivate _createUnloadToolDefinition(): ToolDefinition {\n\t\tconst parameters = Type.Object({\n\t\t\tname: Type.Union([Type.String(), Type.Array(Type.String())], {\n\t\t\t\tdescription: \"Name of the session-scoped tool to unload, or an array of names to unload several at once\",\n\t\t\t}),\n\t\t});\n\t\treturn {\n\t\t\tname: \"unload_tool\",\n\t\t\tlabel: \"Unload tool\",\n\t\t\tdescription:\n\t\t\t\t\"Unload one or more session-scoped tools from the active tool context while keeping them available in this session. Pass a single name or an array of names.\",\n\t\t\tpromptSnippet:\n\t\t\t\t\"Use unload_tool to remove session-scoped loaded tools from active context when no longer needed; pass an array of names to unload several at once.\",\n\t\t\tparameters,\n\t\t\trenderCall: (args: unknown) => {\n\t\t\t\treturn new Text(\n\t\t\t\t\tformatSessionToolLifecycleCall(\"unload\", getToolNameArgs(args as SessionToolNameArgs | undefined)),\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t);\n\t\t\t},\n\t\t\trenderResult: (result, _options, _theme, context) => {\n\t\t\t\tconst details = result.details as { unloaded?: string[]; notFound?: string[] } | undefined;\n\t\t\t\tconst unloaded = details?.unloaded ?? getToolNameArgs(context.args as SessionToolNameArgs | undefined);\n\t\t\t\treturn new Text(formatSessionToolLifecycleResult(\"unloaded\", unloaded, details?.notFound ?? []), 0, 0);\n\t\t\t},\n\t\t\texecutionMode: \"sequential\",\n\t\t\texecute: async (_toolCallId, params: SessionToolNameParameters) => {\n\t\t\t\tconst names = normalizeSessionToolNames(params.name);\n\t\t\t\tconst unloaded: string[] = [];\n\t\t\t\tconst notFound: string[] = [];\n\t\t\t\tfor (const name of names) {\n\t\t\t\t\tif (this.unloadSessionTool(name)) unloaded.push(name);\n\t\t\t\t\telse notFound.push(name);\n\t\t\t\t}\n\t\t\t\tconst parts: string[] = [];\n\t\t\t\tif (unloaded.length > 0) {\n\t\t\t\t\tparts.push(`Unloaded tool${unloaded.length > 1 ? \"s\" : \"\"}: ${unloaded.join(\", \")}.`);\n\t\t\t\t}\n\t\t\t\tif (notFound.length > 0) {\n\t\t\t\t\tparts.push(`Tool${notFound.length > 1 ? \"s\" : \"\"} not found in this session: ${notFound.join(\", \")}.`);\n\t\t\t\t}\n\t\t\t\tif (parts.length === 0) {\n\t\t\t\t\tparts.push(\"No tool names provided.\");\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: parts.join(\" \") }],\n\t\t\t\t\tdetails: { names, unloaded, notFound },\n\t\t\t\t};\n\t\t\t},\n\t\t};\n\t}\n\n\tprivate _resolveRefreshedActiveToolNames(\n\t\toptions: ToolRegistryRefreshOptions | undefined,\n\t\tpreviousRegistryNames: Set<string>,\n\t\tpreviousActiveToolNames: string[],\n\t\twrappedExtensionTools: AgentTool[],\n\t\thasAvailableSessionTools: boolean,\n\t): string[] {\n\t\tconst nextActiveToolNames = (\n\t\t\toptions?.activeToolNames ? [...options.activeToolNames] : [...previousActiveToolNames]\n\t\t).filter((name) => this._isToolPermitted(name));\n\t\tif (this._allowedToolNames) {\n\t\t\tnextActiveToolNames.push(\n\t\t\t\t...Array.from(this._toolRegistry.keys()).filter((toolName) => this._allowedToolNames?.has(toolName)),\n\t\t\t);\n\t\t} else if (options?.includeAllExtensionTools) {\n\t\t\tnextActiveToolNames.push(...wrappedExtensionTools.map((tool) => tool.name));\n\t\t} else if (!options?.activeToolNames) {\n\t\t\tnextActiveToolNames.push(\n\t\t\t\t...Array.from(this._toolRegistry.keys()).filter((toolName) => !previousRegistryNames.has(toolName)),\n\t\t\t);\n\t\t}\n\t\tif (hasAvailableSessionTools) nextActiveToolNames.push(\"load_tool\", \"unload_tool\");\n\t\treturn nextActiveToolNames;\n\t}\n\n\tprivate _refreshToolRegistry(options?: ToolRegistryRefreshOptions): void {\n\t\tconst previousRegistryNames = new Set(this._toolRegistry.keys());\n\t\tconst previousActiveToolNames = this.getActiveToolNames();\n\t\tconst isAllowedTool = (name: string): boolean => this._isToolPermitted(name);\n\t\tconst lifecycleToolNames = new Set([\"load_tool\", \"unload_tool\"]);\n\t\tconst hasAvailableSessionTools = this.getAvailableSessionTools().length > 0;\n\t\tconst isVisibleBaseTool = (name: string): boolean =>\n\t\t\tisAllowedTool(name) && (hasAvailableSessionTools || !lifecycleToolNames.has(name));\n\t\tconst registeredTools = this._extensionRunner\n\t\t\t.getAllRegisteredTools()\n\t\t\t.filter((tool) => tool.lazy !== true || tool.loaded === true);\n\t\tconst activeSessionTools = Array.from(this._sessionTools.values()).filter((tool) => !tool.lazy || tool.loaded);\n\t\tconst allCustomTools = [\n\t\t\t...registeredTools,\n\t\t\t...this._customTools.map((definition) => ({\n\t\t\t\tdefinition,\n\t\t\t\tsourceInfo: createSyntheticSourceInfo(`<sdk:${definition.name}>`, { source: \"sdk\" }),\n\t\t\t})),\n\t\t\t...activeSessionTools,\n\t\t].filter((tool) => isAllowedTool(tool.definition.name));\n\t\tconst definitionRegistry = new Map<string, ToolDefinitionEntry>(\n\t\t\tArray.from(this._baseToolDefinitions.entries())\n\t\t\t\t.filter(([name]) => isVisibleBaseTool(name))\n\t\t\t\t.map(([name, definition]) => [\n\t\t\t\t\tname,\n\t\t\t\t\t{\n\t\t\t\t\t\tdefinition,\n\t\t\t\t\t\tsourceInfo: createSyntheticSourceInfo(`<builtin:${name}>`, { source: \"builtin\" }),\n\t\t\t\t\t},\n\t\t\t\t]),\n\t\t);\n\t\tfor (const tool of allCustomTools) {\n\t\t\tdefinitionRegistry.set(tool.definition.name, {\n\t\t\t\tdefinition: tool.definition,\n\t\t\t\tsourceInfo: tool.sourceInfo,\n\t\t\t});\n\t\t}\n\t\tthis._toolDefinitions = definitionRegistry;\n\t\tthis._toolPromptSnippets = new Map(\n\t\t\tArray.from(definitionRegistry.values())\n\t\t\t\t.map(({ definition }) => {\n\t\t\t\t\tconst snippet = this._normalizePromptSnippet(definition.promptSnippet);\n\t\t\t\t\treturn snippet ? ([definition.name, snippet] as const) : undefined;\n\t\t\t\t})\n\t\t\t\t.filter((entry): entry is readonly [string, string] => entry !== undefined),\n\t\t);\n\t\tthis._toolPromptGuidelines = new Map(\n\t\t\tArray.from(definitionRegistry.values())\n\t\t\t\t.map(({ definition }) => {\n\t\t\t\t\tconst guidelines = this._normalizePromptGuidelines(definition.promptGuidelines);\n\t\t\t\t\treturn guidelines.length > 0 ? ([definition.name, guidelines] as const) : undefined;\n\t\t\t\t})\n\t\t\t\t.filter((entry): entry is readonly [string, string[]] => entry !== undefined),\n\t\t);\n\t\tconst runner = this._extensionRunner;\n\t\tconst wrappedExtensionTools = wrapRegisteredTools(allCustomTools, runner);\n\t\tconst wrappedBuiltInTools = wrapRegisteredTools(\n\t\t\tArray.from(this._baseToolDefinitions.values())\n\t\t\t\t.filter((definition) => isVisibleBaseTool(definition.name))\n\t\t\t\t.map((definition) => ({\n\t\t\t\t\tdefinition,\n\t\t\t\t\tsourceInfo: createSyntheticSourceInfo(`<builtin:${definition.name}>`, { source: \"builtin\" }),\n\t\t\t\t})),\n\t\t\trunner,\n\t\t);\n\n\t\tconst toolRegistry = new Map(wrappedBuiltInTools.map((tool) => [tool.name, tool]));\n\t\tfor (const tool of wrappedExtensionTools as AgentTool[]) {\n\t\t\ttoolRegistry.set(tool.name, tool);\n\t\t}\n\t\tthis._toolRegistry = toolRegistry;\n\n\t\tconst nextActiveToolNames = this._resolveRefreshedActiveToolNames(\n\t\t\toptions,\n\t\t\tpreviousRegistryNames,\n\t\t\tpreviousActiveToolNames,\n\t\t\twrappedExtensionTools,\n\t\t\thasAvailableSessionTools,\n\t\t);\n\t\tthis.setActiveToolsByName([...new Set(nextActiveToolNames)]);\n\t}\n\n\tprivate _registerLspToolDefinitions(getState: () => LspRuntimeState, getOperations?: () => ToolOperations): void {\n\t\tfor (const definition of createLspToolDefinitions(getState, getOperations)) {\n\t\t\tthis._baseToolDefinitions.set(definition.name, definition as unknown as ToolDefinition);\n\t\t}\n\t}\n\n\tprivate async _replaceLspRuntime(configuration: ResolvedLspConfiguration): Promise<void> {\n\t\tconst nextConfiguration = structuredClone(configuration);\n\t\tconst previous = this._lspRuntimeState;\n\t\tif (previous && nextConfiguration.enabled && !this._baseToolsOverride) {\n\t\t\tawait previous.manager.setConfiguration(nextConfiguration);\n\t\t\tthis._lspConfiguration = nextConfiguration;\n\t\t\tthis._updateCurrentRunnerLspOwnership();\n\t\t\treturn;\n\t\t}\n\t\tconst operations = this.getToolOperations();\n\t\tconst next =\n\t\t\tnextConfiguration.enabled && !this._baseToolsOverride\n\t\t\t\t? createLspRuntimeState(operations.cwd, {\n\t\t\t\t\t\tconfiguration: nextConfiguration,\n\t\t\t\t\t\tconnectionFactories: this._lspConnectionFactories,\n\t\t\t\t\t\tgetToolBackendInfo: () => operations.getBackendInfo?.() ?? { type: \"local\", cwd: operations.cwd },\n\t\t\t\t\t\tgetToolOperations: () => operations,\n\t\t\t\t\t})\n\t\t\t\t: undefined;\n\t\tthis._lspConfiguration = nextConfiguration;\n\t\tthis._lspRuntimeState = next;\n\t\tthis._updateCurrentRunnerLspOwnership();\n\t\tfor (const name of LSP_TOOL_NAMES) this._baseToolDefinitions.delete(name);\n\t\tif (next) this._registerLspToolDefinitions(() => next);\n\t\tthis._refreshToolRegistry({ activeToolNames: this._withCurrentDefaultTools(this.getActiveToolNames()) });\n\t\tawait previous?.manager.shutdownAll();\n\t}\n\tprivate _createWorkspaceToolHost(operations: ToolOperations): WorkspaceToolHost {\n\t\tconst shellPath = this.settingsManager.getShellPath();\n\t\tconst daemonOwnedLsp = operations.getBackendInfo?.().type === \"remote\";\n\t\treturn new WorkspaceToolHost({\n\t\t\tcwd: operations.cwd,\n\t\t\toperations,\n\t\t\ttools: {\n\t\t\t\tread: {\n\t\t\t\t\tautoResizeImages: this.settingsManager.getImageAutoResize(),\n\t\t\t\t\toperationsForPath: (path) => this._getReadOperationsForPath(path, shellPath),\n\t\t\t\t},\n\t\t\t\tbash: { commandPrefix: this.settingsManager.getShellCommandPrefix() },\n\t\t\t},\n\t\t\tadditionalDefinitions: daemonOwnedLsp\n\t\t\t\t? createLspToolDefinitions(\n\t\t\t\t\t\t() => {\n\t\t\t\t\t\t\tthrow new Error(\"Remote LSP proxy attempted local execution\");\n\t\t\t\t\t\t},\n\t\t\t\t\t\t() => operations,\n\t\t\t\t\t)\n\t\t\t\t: undefined,\n\t\t\tonCatalogChanged: () => this._refreshRemoteWorkspaceToolCatalog(),\n\t\t});\n\t}\n\n\tprivate _refreshRemoteWorkspaceToolCatalog(): Promise<void> {\n\t\tif (this._disposed || this._baseToolsOverride) return Promise.resolve();\n\t\treturn this._enqueueRuntimeLifecycle(async () => {\n\t\t\tif (this._disposed || this._baseToolsOverride) return;\n\t\t\tconst previous = this._workspaceToolHost;\n\t\t\tconst next = this._createWorkspaceToolHost(this.getToolOperations());\n\t\t\tconst activeToolNames = this.getActiveToolNames();\n\t\t\tfor (const name of [...WORKSPACE_TOOL_NAMES, ...LSP_TOOL_NAMES]) this._baseToolDefinitions.delete(name);\n\t\t\tfor (const [name, definition] of next.getDefinitions()) {\n\t\t\t\tthis._baseToolDefinitions.set(name, definition as unknown as ToolDefinition);\n\t\t\t}\n\t\t\tthis._workspaceToolHost = next;\n\t\t\tthis._refreshToolRegistry({ activeToolNames: this._withCurrentDefaultTools(activeToolNames) });\n\t\t\tif (previous) {\n\t\t\t\tprevious.detachCatalogListener();\n\t\t\t\tthis._retiredWorkspaceToolHosts.push(previous);\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate _captureRuntimeBuildSnapshot(): RuntimeBuildSnapshot {\n\t\treturn {\n\t\t\tbaseToolDefinitions: this._baseToolDefinitions,\n\t\t\tlspRuntimeState: this._lspRuntimeState,\n\t\t\textensionRunner: this._extensionRunner as ExtensionRunner | undefined,\n\t\t\ttoolRegistry: this._toolRegistry,\n\t\t\ttoolDefinitions: this._toolDefinitions,\n\t\t\ttoolPromptSnippets: this._toolPromptSnippets,\n\t\t\ttoolPromptGuidelines: this._toolPromptGuidelines,\n\t\t\tbaseSystemPrompt: this._baseSystemPrompt,\n\t\t\tbaseSystemPromptOptions: this._baseSystemPromptOptions as BuildSystemPromptOptions | undefined,\n\t\t\tagentTools: this.agent.state.tools,\n\t\t\tagentSystemPrompt: this.agent.state.systemPrompt,\n\t\t\textensionRunnerRef: this._extensionRunnerRef?.current,\n\t\t\tworkspaceToolHost: this._workspaceToolHost,\n\t\t};\n\t}\n\n\tprivate _createRuntimeBaseToolDefinitions(\n\t\tworkspaceToolHost: WorkspaceToolHost | undefined,\n\t): Record<string, ToolDefinition> {\n\t\tif (this._baseToolsOverride) {\n\t\t\treturn Object.fromEntries(\n\t\t\t\tObject.entries(this._baseToolsOverride).map(([name, tool]) => [\n\t\t\t\t\tname,\n\t\t\t\t\tcreateToolDefinitionFromAgentTool(tool),\n\t\t\t\t]),\n\t\t\t);\n\t\t}\n\t\treturn {\n\t\t\t...Object.fromEntries(workspaceToolHost?.getDefinitions() ?? []),\n\t\t\tsession_search: createSessionSearchToolDefinition(this.session) as unknown as ToolDefinition,\n\t\t\tsession_entry_get: createSessionEntryGetToolDefinition(this.session) as unknown as ToolDefinition,\n\t\t\tcompress_context: createCompressContextToolDefinition(this.session, (request) => {\n\t\t\t\tthis._pendingContextCompression = request;\n\t\t\t}) as unknown as ToolDefinition,\n\t\t\twebsearch: createWebsearchToolDefinition(\n\t\t\t\tparseWebsearchToolOptions(this.settingsManager.getToolSettings(\"websearch\")),\n\t\t\t) as unknown as ToolDefinition,\n\t\t\tsubagent: createSubagentToolDefinition({\n\t\t\t\trunner: this._subagentRunner,\n\t\t\t\tmodelCatalog: formatSubagentModelCatalog(this.model, this._getSubagentModels()),\n\t\t\t\tresolveModelHint: (hint) => this._resolveSubagentModelHint(hint),\n\t\t\t\ttrustProjectAgents: this._trustProjectAgents,\n\t\t\t\trunRegistry: this._subagentRunRegistry,\n\t\t\t\tconfigRegistry: this._subagentConfigRegistry,\n\t\t\t}) as unknown as ToolDefinition,\n\t\t\tsubagent_runs: createSubagentRunsToolDefinition(this._subagentRunRegistry) as unknown as ToolDefinition,\n\t\t\tcreate_subagent: createCreateSubagentToolDefinition(this._subagentConfigRegistry) as unknown as ToolDefinition,\n\t\t};\n\t}\n\n\tprivate _initializeRuntimeLsp(operations: ToolOperations): void {\n\t\tconst daemonOwnedLsp = operations.getBackendInfo?.().type === \"remote\";\n\t\tif (daemonOwnedLsp || this._baseToolsOverride || !this._lspConfiguration.enabled) {\n\t\t\tthis._lspRuntimeState = undefined;\n\t\t\treturn;\n\t\t}\n\t\tthis._lspRuntimeState = createLspRuntimeState(operations.cwd, {\n\t\t\tconfiguration: this._lspConfiguration,\n\t\t\tconnectionFactories: this._lspConnectionFactories,\n\t\t\tgetToolBackendInfo: () => operations.getBackendInfo?.() ?? { type: \"local\", cwd: operations.cwd },\n\t\t\tgetToolOperations: () => operations,\n\t\t});\n\t\tthis._registerLspToolDefinitions(() => {\n\t\t\tif (!this._lspRuntimeState) throw new Error(\"LSP was disabled while a tool call was in progress\");\n\t\t\treturn this._lspRuntimeState;\n\t\t});\n\t}\n\n\tprivate _restoreRuntimeBuildSnapshot(\n\t\tsnapshot: RuntimeBuildSnapshot,\n\t\tworkspaceToolHost: WorkspaceToolHost | undefined,\n\t): void {\n\t\tconst failedExtensionRunner = this._extensionRunner as ExtensionRunner | undefined;\n\t\tconst failedLspRuntimeState = this._lspRuntimeState;\n\t\tthis._workspaceToolHost = snapshot.workspaceToolHost;\n\t\tthis._baseToolDefinitions = snapshot.baseToolDefinitions;\n\t\tthis._lspRuntimeState = snapshot.lspRuntimeState;\n\t\tthis._toolRegistry = snapshot.toolRegistry;\n\t\tthis._toolDefinitions = snapshot.toolDefinitions;\n\t\tthis._toolPromptSnippets = snapshot.toolPromptSnippets;\n\t\tthis._toolPromptGuidelines = snapshot.toolPromptGuidelines;\n\t\tthis._baseSystemPrompt = snapshot.baseSystemPrompt;\n\t\tif (snapshot.baseSystemPromptOptions) this._baseSystemPromptOptions = snapshot.baseSystemPromptOptions;\n\t\tthis.agent.state.tools = snapshot.agentTools;\n\t\tthis.agent.state.systemPrompt = snapshot.agentSystemPrompt;\n\t\tif (failedExtensionRunner !== snapshot.extensionRunner) {\n\t\t\tfailedExtensionRunner?.invalidate(STALE_EXTENSION_CONTEXT_MESSAGE);\n\t\t\tthis._extensionErrorUnsubscriber?.();\n\t\t\tthis._extensionErrorUnsubscriber = undefined;\n\t\t\tthis._extensionRunner = snapshot.extensionRunner as ExtensionRunner;\n\t\t\tif (snapshot.extensionRunner) this._applyExtensionBindings(snapshot.extensionRunner);\n\t\t}\n\t\tif (this._extensionRunnerRef) this._extensionRunnerRef.current = snapshot.extensionRunnerRef;\n\t\tif (failedLspRuntimeState && failedLspRuntimeState !== snapshot.lspRuntimeState) {\n\t\t\tvoid failedLspRuntimeState.manager.shutdownAll();\n\t\t}\n\t\tvoid workspaceToolHost?.dispose();\n\t}\n\n\tprivate _buildRuntime(options: BuildRuntimeOptions): void {\n\t\tconst operations = this.getToolOperations();\n\t\tconst snapshot = this._captureRuntimeBuildSnapshot();\n\t\tconst workspaceToolHost = this._baseToolsOverride ? undefined : this._createWorkspaceToolHost(operations);\n\t\ttry {\n\t\t\tconst baseToolDefinitions = this._createRuntimeBaseToolDefinitions(workspaceToolHost);\n\t\t\tthis._baseToolDefinitions = new Map(Object.entries(baseToolDefinitions));\n\t\t\tthis._initializeRuntimeLsp(operations);\n\n\t\t\tthis._baseToolDefinitions.set(\"load_tool\", this._createLoadToolDefinition());\n\t\t\tthis._baseToolDefinitions.set(\"unload_tool\", this._createUnloadToolDefinition());\n\n\t\t\tconst extensionsResult = this._resourceLoader.getExtensions();\n\t\t\tif (options.flagValues) {\n\t\t\t\tfor (const [name, value] of options.flagValues) {\n\t\t\t\t\textensionsResult.runtime.flagValues.set(name, value);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis._extensionRunner = new ExtensionRunner(\n\t\t\t\textensionsResult.extensions,\n\t\t\t\textensionsResult.runtime,\n\t\t\t\tthis._cwd,\n\t\t\t\tthis.session,\n\t\t\t\tthis._modelRegistry,\n\t\t\t);\n\t\t\tif (this._extensionRunnerRef) {\n\t\t\t\tthis._extensionRunnerRef.current = this._extensionRunner;\n\t\t\t}\n\t\t\tthis._bindExtensionCore(this._extensionRunner);\n\t\t\tthis._applyExtensionBindings(this._extensionRunner);\n\t\t\tthis._workspaceToolHost = workspaceToolHost;\n\n\t\t\tconst defaultActiveToolNames = this._baseToolsOverride\n\t\t\t\t? Object.keys(this._baseToolsOverride)\n\t\t\t\t: getDefaultActiveToolNames(\n\t\t\t\t\t\tthis.settingsManager.getEnableSubagents(),\n\t\t\t\t\t\tthis._enableLspTools ?? this.settingsManager.getEnableLspTools(),\n\t\t\t\t\t);\n\t\t\tconst baseActiveToolNames = this._withCurrentDefaultTools(options.activeToolNames ?? defaultActiveToolNames);\n\t\t\tthis._refreshToolRegistry({\n\t\t\t\tactiveToolNames: baseActiveToolNames,\n\t\t\t\tincludeAllExtensionTools: options.includeAllExtensionTools,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthis._restoreRuntimeBuildSnapshot(snapshot, workspaceToolHost);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprivate _withCurrentDefaultTools(activeToolNames: string[]): string[] {\n\t\tif (this._baseToolsOverride || this._allowedToolNames) {\n\t\t\treturn activeToolNames.filter((name) => !this._excludedToolNames.has(name));\n\t\t}\n\t\tconst enableLspTools = this._enableLspTools ?? this.settingsManager.getEnableLspTools();\n\t\tconst configuredDefaults = getDefaultActiveToolNames(this.settingsManager.getEnableSubagents(), enableLspTools);\n\t\tconst subagentToolNames = new Set<string>(SUBAGENT_TOOL_NAMES);\n\t\tconst lspToolNames = new Set<string>(LSP_TOOL_NAMES);\n\t\tconst retainedToolNames = activeToolNames.filter(\n\t\t\t(name) =>\n\t\t\t\t(enableLspTools || !lspToolNames.has(name)) &&\n\t\t\t\t(this.settingsManager.getEnableSubagents() || !subagentToolNames.has(name)),\n\t\t);\n\t\tconst active = new Set(retainedToolNames);\n\t\tconst usesDefaultCoreTools = CORE_DEFAULT_TOOL_NAMES.every((toolName) => active.has(toolName));\n\t\tconst expanded = usesDefaultCoreTools\n\t\t\t? [...retainedToolNames, ...configuredDefaults.filter((toolName) => !active.has(toolName))]\n\t\t\t: retainedToolNames;\n\t\treturn expanded.filter((toolName) => !this._excludedToolNames.has(toolName));\n\t}\n\tprivate _isToolPermitted(name: string): boolean {\n\t\treturn !this._excludedToolNames.has(name) && (!this._allowedToolNames || this._allowedToolNames.has(name));\n\t}\n\n\treload(): Promise<AgentSessionReloadResult> {\n\t\tif (this._disposed) return Promise.reject(new Error(\"Cannot reload after AgentSession disposal\"));\n\t\treturn this._enqueueRuntimeLifecycle(() => this._reload());\n\t}\n\n\tprivate async _reload(): Promise<AgentSessionReloadResult> {\n\t\tconst previousRunner = this._extensionRunner;\n\t\tconst previousLspRuntimeState = this._lspRuntimeState;\n\t\tconst previousWorkspaceToolHost = this._workspaceToolHost;\n\t\tconst previousRetiredWorkspaceToolHosts = [...this._retiredWorkspaceToolHosts];\n\t\tconst previousFlagValues = previousRunner.getFlagValues();\n\t\tawait this.settingsManager.reload();\n\t\tlet lspResult: LoadLspConfigurationResult | undefined;\n\t\tif (this._resolveLspConfiguration) {\n\t\t\tlspResult = await this._resolveLspConfiguration();\n\t\t\tthis._lspConfiguration = structuredClone(lspResult.configuration);\n\t\t\tthis._onLspConfigurationDiagnostics?.(structuredClone(lspResult.diagnostics));\n\t\t}\n\t\tif (this._resolveHooks) this._loadedHooks = await this._resolveHooks();\n\t\tawait this._resourceLoader.reload();\n\t\tresetApiProviders();\n\t\tconst activeToolNames = this._withCurrentDefaultTools(this.getActiveToolNames());\n\t\tthis._buildRuntime({\n\t\t\tactiveToolNames,\n\t\t\tflagValues: previousFlagValues,\n\t\t\tincludeAllExtensionTools: true,\n\t\t});\n\t\tthis._retiredWorkspaceToolHosts = [];\n\t\ttry {\n\t\t\tawait emitSessionShutdownEvent(previousRunner, { type: \"session_shutdown\", reason: \"reload\" });\n\t\t} finally {\n\t\t\tpreviousRunner.invalidate(STALE_EXTENSION_CONTEXT_MESSAGE);\n\t\t\tawait Promise.all([\n\t\t\t\tpreviousLspRuntimeState?.manager.shutdownAll(),\n\t\t\t\tpreviousWorkspaceToolHost?.dispose(),\n\t\t\t\t...previousRetiredWorkspaceToolHosts.map((host) => host.dispose()),\n\t\t\t]);\n\t\t}\n\n\t\tconst hasBindings =\n\t\t\tthis._extensionUIContext ||\n\t\t\tthis._extensionCommandContextActions ||\n\t\t\tthis._extensionShutdownHandler ||\n\t\t\tthis._extensionErrorListener;\n\t\tif (hasBindings) {\n\t\t\tawait this._extensionRunner.emit({ type: \"session_start\", reason: \"reload\" });\n\t\t\tawait this.extendResourcesFromExtensions(\"reload\");\n\t\t}\n\t\treturn lspResult ? { lsp: structuredClone(lspResult) } : {};\n\t}\n\n\t// =========================================================================\n\t// Auto-Retry\n\t// =========================================================================\n\n\t/**\n\t * Check if an error is retryable (overloaded, rate limit, server errors).\n\t * Context overflow errors are NOT retryable (handled by compaction instead).\n\t */\n\tprivate _isRetryableError(message: AssistantMessage): boolean {\n\t\t// Context overflow is handled by compaction, not retry.\n\t\tif (isContextOverflow(message, this.model?.contextWindow ?? 0)) return false;\n\t\treturn isRetryableAssistantError(message);\n\t}\n\n\tprivate _summarizationRetryCallbacks(source: SummarizationRetrySource): RetryCallbacks {\n\t\treturn {\n\t\t\tonRetryScheduled: (attempt, maxAttempts, delayMs, errorMessage) => {\n\t\t\t\tthis._emit({\n\t\t\t\t\ttype: \"summarization_retry_scheduled\",\n\t\t\t\t\tattempt,\n\t\t\t\t\tmaxAttempts,\n\t\t\t\t\tdelayMs,\n\t\t\t\t\terrorMessage,\n\t\t\t\t});\n\t\t\t},\n\t\t\tonRetryAttemptStart: () => {\n\t\t\t\tthis._emit({ type: \"summarization_retry_attempt_start\", ...source });\n\t\t\t},\n\t\t\tonRetryFinished: () => {\n\t\t\t\tthis._emit({ type: \"summarization_retry_finished\" });\n\t\t\t},\n\t\t};\n\t}\n\n\t/**\n\t * Prepare a retryable error for continuation with exponential backoff.\n\t * @returns true if the caller should continue the agent, false otherwise\n\t */\n\tprivate async _prepareRetry(message: AssistantMessage): Promise<boolean> {\n\t\tconst settings = this.settingsManager.getRetrySettings();\n\t\tif (!settings.enabled) {\n\t\t\treturn false;\n\t\t}\n\n\t\tthis._retryAttempt++;\n\n\t\tif (this._retryAttempt > settings.maxRetries) {\n\t\t\t// Preserve the completed attempt count so post-run handling can emit the final failure.\n\t\t\tthis._retryAttempt--;\n\t\t\treturn false;\n\t\t}\n\n\t\tconst delayMs = settings.baseDelayMs * 2 ** (this._retryAttempt - 1);\n\n\t\tthis._emit({\n\t\t\ttype: \"auto_retry_start\",\n\t\t\tattempt: this._retryAttempt,\n\t\t\tmaxAttempts: settings.maxRetries,\n\t\t\tdelayMs,\n\t\t\terrorMessage: message.errorMessage || \"Unknown error\",\n\t\t});\n\n\t\t// Remove error message from agent state (keep in session for history)\n\t\tconst messages = this.agent.state.messages;\n\t\tif (messages.length > 0 && messages[messages.length - 1].role === \"assistant\") {\n\t\t\tthis.agent.state.messages = messages.slice(0, -1);\n\t\t}\n\n\t\t// Wait with exponential backoff (abortable)\n\t\tthis._retryAbortController = new AbortController();\n\t\ttry {\n\t\t\tawait sleep(delayMs, this._retryAbortController.signal);\n\t\t} catch {\n\t\t\t// Aborted during sleep - emit end event so UI can clean up\n\t\t\tconst attempt = this._retryAttempt;\n\t\t\tthis._retryAttempt = 0;\n\t\t\tthis._emit({\n\t\t\t\ttype: \"auto_retry_end\",\n\t\t\t\tsuccess: false,\n\t\t\t\tattempt,\n\t\t\t\tfinalError: \"Retry cancelled\",\n\t\t\t});\n\t\t\treturn false;\n\t\t} finally {\n\t\t\tthis._retryAbortController = undefined;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * Cancel in-progress retry.\n\t */\n\tabortRetry(): void {\n\t\tthis._retryAbortController?.abort();\n\t}\n\n\t/** Whether auto-retry is currently in progress */\n\tget isRetrying(): boolean {\n\t\treturn this._retryAbortController !== undefined;\n\t}\n\n\t/** Whether auto-retry is enabled */\n\tget autoRetryEnabled(): boolean {\n\t\treturn this.settingsManager.getRetryEnabled();\n\t}\n\n\t/**\n\t * Toggle auto-retry setting.\n\t */\n\tsetAutoRetryEnabled(enabled: boolean): void {\n\t\tthis.settingsManager.setRetryEnabled(enabled);\n\t}\n\n\t// =========================================================================\n\t// Bash Execution\n\t// =========================================================================\n\n\t/**\n\t * Execute a bash command.\n\t * Adds result to agent context and session unless options.record is false.\n\t * @param command The bash command to execute\n\t * @param onChunk Optional streaming callback for output\n\t * @param options.excludeFromContext If true, command output won't be sent to LLM (!! prefix)\n\t * @param options.operations Custom ToolOperations for remote execution\n\t * @param options.record If false, result won't be stored in agent state or session history\n\t * @param options.truncate If false, returned output won't be truncated\n\t */\n\tasync executeBash(\n\t\tcommand: string,\n\t\tonChunk?: (chunk: string) => void,\n\t\toptions?: ExecuteBashOptions,\n\t): Promise<BashResult> {\n\t\treturn this._runActivity(\"bash execution\", async () => {\n\t\t\tconst abortController = new AbortController();\n\t\t\tthis._bashAbortControllers.add(abortController);\n\n\t\t\t// Apply command prefix if configured (e.g., \"shopt -s expand_aliases\" for alias support)\n\t\t\tconst prefix = this.settingsManager.getShellCommandPrefix();\n\t\t\tconst shellPath = this.settingsManager.getShellPath();\n\t\t\tconst resolvedCommand = prefix ? `${prefix}\\n${command}` : command;\n\n\t\t\ttry {\n\t\t\t\tconst operations =\n\t\t\t\t\toptions?.operations ??\n\t\t\t\t\tthis._toolOperations ??\n\t\t\t\t\tcreateLocalBashOperations({ cwd: this.session.getCwd(), shellPath });\n\t\t\t\tconst result = await executeBashWithOperations(resolvedCommand, operations.cwd, operations, {\n\t\t\t\t\tonChunk,\n\t\t\t\t\tsignal: abortController.signal,\n\t\t\t\t\ttruncate: options?.truncate,\n\t\t\t\t});\n\n\t\t\t\tif (options?.record !== false) this._recordBashResult(command, result, options);\n\t\t\t\treturn result;\n\t\t\t} finally {\n\t\t\t\tthis._bashAbortControllers.delete(abortController);\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Record a bash execution result in session history.\n\t * Used by executeBash and by extensions that handle bash execution themselves.\n\t */\n\trecordBashResult(command: string, result: BashResult, options?: RecordBashResultOptions): void {\n\t\tif (this._disposed) throw new Error(\"Agent session is disposed\");\n\t\tthis._runSynchronousActivity(\"bash result recording\", () => this._recordBashResult(command, result, options));\n\t}\n\n\tprivate _recordBashResult(command: string, result: BashResult, options?: RecordBashResultOptions): void {\n\t\tconst bashMessage: BashExecutionMessage = {\n\t\t\trole: \"bashExecution\",\n\t\t\tcommand,\n\t\t\toutput: result.output,\n\t\t\texitCode: result.exitCode,\n\t\t\tcancelled: result.cancelled,\n\t\t\ttruncated: result.truncated,\n\t\t\tfullOutputPath: result.fullOutputPath,\n\t\t\ttimestamp: Date.now(),\n\t\t\texcludeFromContext: options?.excludeFromContext,\n\t\t};\n\n\t\t// If agent is streaming, defer adding to avoid breaking tool_use/tool_result ordering\n\t\tif (this._isAgentRunActive) {\n\t\t\t// Queue for later - will be flushed on agent_end\n\t\t\tthis._pendingBashMessages.push(bashMessage);\n\t\t} else {\n\t\t\t// Add to agent state immediately\n\t\t\tthis.agent.state.messages.push(bashMessage);\n\n\t\t\t// Save to session\n\t\t\tthis.session.appendMessage(bashMessage);\n\t\t}\n\t}\n\n\t/**\n\t * Cancel running bash command.\n\t */\n\tabortBash(): void {\n\t\tfor (const controller of this._bashAbortControllers) controller.abort();\n\t}\n\n\t/** Whether a bash command is currently running */\n\tget isBashRunning(): boolean {\n\t\treturn this._bashAbortControllers.size > 0;\n\t}\n\n\t/** Whether there are pending bash messages waiting to be flushed */\n\tget hasPendingBashMessages(): boolean {\n\t\treturn this._pendingBashMessages.length > 0;\n\t}\n\n\t/**\n\t * Flush pending bash messages to agent state and session.\n\t * Called after agent turn completes to maintain proper message ordering.\n\t */\n\tprivate _flushPendingBashMessages(): void {\n\t\tif (this._pendingBashMessages.length === 0) return;\n\n\t\tfor (const bashMessage of this._pendingBashMessages) {\n\t\t\t// Add to agent state\n\t\t\tthis.agent.state.messages.push(bashMessage);\n\n\t\t\t// Save to session\n\t\t\tthis.session.appendMessage(bashMessage);\n\t\t}\n\n\t\tthis._pendingBashMessages = [];\n\t}\n\n\tprivate async _prepareTreeNavigation(\n\t\tpreparation: TreePreparation,\n\t\tsettings: TreeNavigationSettings,\n\t\tshouldSummarize: boolean,\n\t\tsignal: AbortSignal,\n\t): Promise<TreeExtensionPreparationResult> {\n\t\tif (!this._extensionRunner.hasHandlers(\"session_before_tree\")) {\n\t\t\treturn { ...settings, cancelled: false, fromExtension: false };\n\t\t}\n\t\tconst result = (await this._extensionRunner.emit({\n\t\t\ttype: \"session_before_tree\",\n\t\t\tpreparation,\n\t\t\tsignal,\n\t\t})) as SessionBeforeTreeResult | undefined;\n\t\tif (result?.cancel) return { ...settings, cancelled: true, fromExtension: false };\n\t\treturn {\n\t\t\tcustomInstructions: result?.customInstructions ?? settings.customInstructions,\n\t\t\treplaceInstructions: result?.replaceInstructions ?? settings.replaceInstructions,\n\t\t\tlabel: result?.label ?? settings.label,\n\t\t\tcancelled: false,\n\t\t\textensionSummary: shouldSummarize ? result?.summary : undefined,\n\t\t\tfromExtension: shouldSummarize && result?.summary !== undefined,\n\t\t};\n\t}\n\n\tprivate async _resolveTreeSummary(\n\t\tentriesToSummarize: SessionEntry[],\n\t\tshouldSummarize: boolean,\n\t\tpreparation: TreeExtensionPreparationResult,\n\t\tsignal: AbortSignal,\n\t): Promise<TreeSummaryResolution> {\n\t\tif (preparation.extensionSummary) {\n\t\t\treturn {\n\t\t\t\tcancelled: false,\n\t\t\t\tsummaryText: preparation.extensionSummary.summary,\n\t\t\t\tsummaryDetails: preparation.extensionSummary.details,\n\t\t\t\tfromExtension: true,\n\t\t\t};\n\t\t}\n\t\tif (!shouldSummarize || entriesToSummarize.length === 0) {\n\t\t\treturn { cancelled: false, fromExtension: false };\n\t\t}\n\t\tconst model = this.model!;\n\t\tconst { apiKey, headers } = await this._getCompactionRequestAuth(model);\n\t\tconst branchSummarySettings = this.settingsManager.getBranchSummarySettings();\n\t\tconst result = await generateBranchSummary(entriesToSummarize, {\n\t\t\tmodel,\n\t\t\tapiKey,\n\t\t\theaders,\n\t\t\tsignal,\n\t\t\tcustomInstructions: preparation.customInstructions,\n\t\t\treplaceInstructions: preparation.replaceInstructions,\n\t\t\treserveTokens: branchSummarySettings.reserveTokens,\n\t\t\tstreamFn: this.agent.streamFn,\n\t\t\tretry: this.settingsManager.getRetrySettings(),\n\t\t\tcallbacks: this._summarizationRetryCallbacks({ source: \"branchSummary\" }),\n\t\t});\n\t\tif (result.aborted) return { cancelled: true, aborted: true, fromExtension: false };\n\t\tif (result.error) throw new Error(result.error);\n\t\treturn {\n\t\t\tcancelled: false,\n\t\t\tsummaryText: result.summary,\n\t\t\tsummaryDetails: {\n\t\t\t\treadFiles: result.readFiles || [],\n\t\t\t\tmodifiedFiles: result.modifiedFiles || [],\n\t\t\t},\n\t\t\tfromExtension: false,\n\t\t};\n\t}\n\n\tprivate _resolveTreeTarget(targetEntry: SessionEntry, targetId: string): TreeTargetResolution {\n\t\tif (targetEntry.type === \"message\" && targetEntry.message.role === \"user\") {\n\t\t\treturn {\n\t\t\t\tnewLeafId: targetEntry.parentId,\n\t\t\t\teditorText: this._extractUserMessageText(targetEntry.message),\n\t\t\t};\n\t\t}\n\t\tif (targetEntry.type === \"custom_message\") {\n\t\t\tconst editorText =\n\t\t\t\ttypeof targetEntry.content === \"string\"\n\t\t\t\t\t? targetEntry.content\n\t\t\t\t\t: targetEntry.content\n\t\t\t\t\t\t\t.filter((content): content is TextContent => content.type === \"text\")\n\t\t\t\t\t\t\t.map((content) => content.text)\n\t\t\t\t\t\t\t.join(\"\");\n\t\t\treturn { newLeafId: targetEntry.parentId, editorText };\n\t\t}\n\t\treturn { newLeafId: targetId };\n\t}\n\n\tprivate _switchTreeLeaf(\n\t\ttargetId: string,\n\t\tnewLeafId: string | null,\n\t\tsummary: TreeSummaryResolution,\n\t\tlabel: string | undefined,\n\t): BranchSummaryEntry | undefined {\n\t\tlet summaryEntry: BranchSummaryEntry | undefined;\n\t\tif (summary.summaryText) {\n\t\t\tconst summaryId = this.session.branchWithSummary(\n\t\t\t\tnewLeafId,\n\t\t\t\tsummary.summaryText,\n\t\t\t\tsummary.summaryDetails,\n\t\t\t\tsummary.fromExtension,\n\t\t\t);\n\t\t\tsummaryEntry = this.session.getEntry(summaryId) as BranchSummaryEntry;\n\t\t\tif (label) this.session.appendLabelChange(summaryId, label);\n\t\t} else if (newLeafId === null) {\n\t\t\tthis.session.resetLeaf();\n\t\t} else {\n\t\t\tthis.session.branch(newLeafId);\n\t\t}\n\t\tif (label && !summary.summaryText) this.session.appendLabelChange(targetId, label);\n\t\treturn summaryEntry;\n\t}\n\n\t// =========================================================================\n\t// Session Management\n\t// =========================================================================\n\n\t/**\n\t * Set a display name for the current session.\n\t */\n\tsetSessionName(name: string): void {\n\t\tthis.session.appendSessionInfo(name);\n\t\tconst event = { type: \"session_info_changed\", name: this.session.getSessionName() } as const;\n\t\tvoid this._emit(event);\n\t\tvoid this._extensionRunner.emit(event);\n\t}\n\n\t// =========================================================================\n\t// Tree Navigation\n\t// =========================================================================\n\n\t/**\n\t * Navigate to a different node in the session tree.\n\t * Unlike fork() which creates a new session file, this stays in the same file.\n\t *\n\t * @param targetId The entry ID to navigate to\n\t * @param options.summarize Whether user wants to summarize abandoned branch\n\t * @param options.customInstructions Custom instructions for summarizer\n\t * @param options.replaceInstructions If true, customInstructions replaces the default prompt\n\t * @param options.label Label to attach to the branch summary entry\n\t * @returns Result with editorText (if user message) and cancelled status\n\t */\n\tnavigateTree(targetId: string, options: NavigateTreeOptions = {}): Promise<NavigateTreeResult> {\n\t\treturn this._runActivity(\"tree navigation\", () => this._navigateTree(targetId, options));\n\t}\n\n\tprivate async _navigateTree(targetId: string, options: NavigateTreeOptions = {}): Promise<NavigateTreeResult> {\n\t\tconst oldLeafId = this.session.getLeafId();\n\n\t\t// No-op if already at target\n\t\tif (targetId === oldLeafId) {\n\t\t\treturn { cancelled: false };\n\t\t}\n\n\t\t// Model required for summarization\n\t\tif (options.summarize && !this.model) {\n\t\t\tthrow new Error(\"No model available for summarization\");\n\t\t}\n\n\t\tconst targetEntry = this.session.getEntry(targetId);\n\t\tif (!targetEntry) {\n\t\t\tthrow new Error(`Entry ${targetId} not found`);\n\t\t}\n\n\t\t// Collect entries to summarize (from old leaf to common ancestor)\n\t\tconst { entries: entriesToSummarize, commonAncestorId } = collectEntriesForBranchSummary(\n\t\t\tthis.session,\n\t\t\toldLeafId,\n\t\t\ttargetId,\n\t\t);\n\n\t\t// Prepare event data - mutable so extensions can override\n\t\tconst navigationSettings: TreeNavigationSettings = {\n\t\t\tcustomInstructions: options.customInstructions,\n\t\t\treplaceInstructions: options.replaceInstructions,\n\t\t\tlabel: options.label,\n\t\t};\n\n\t\tconst preparation: TreePreparation = {\n\t\t\ttargetId,\n\t\t\toldLeafId,\n\t\t\tcommonAncestorId,\n\t\t\tentriesToSummarize,\n\t\t\tuserWantsSummary: options.summarize ?? false,\n\t\t\tcustomInstructions: navigationSettings.customInstructions,\n\t\t\treplaceInstructions: navigationSettings.replaceInstructions,\n\t\t\tlabel: navigationSettings.label,\n\t\t};\n\n\t\t// Set up abort controller for summarization\n\t\tthis._branchSummaryAbortController = new AbortController();\n\n\t\ttry {\n\t\t\tconst extensionPreparation = await this._prepareTreeNavigation(\n\t\t\t\tpreparation,\n\t\t\t\tnavigationSettings,\n\t\t\t\toptions.summarize ?? false,\n\t\t\t\tthis._branchSummaryAbortController.signal,\n\t\t\t);\n\t\t\tif (extensionPreparation.cancelled) return { cancelled: true };\n\t\t\tconst summary = await this._resolveTreeSummary(\n\t\t\t\tentriesToSummarize,\n\t\t\t\toptions.summarize ?? false,\n\t\t\t\textensionPreparation,\n\t\t\t\tthis._branchSummaryAbortController.signal,\n\t\t\t);\n\t\t\tif (summary.cancelled) return { cancelled: true, aborted: summary.aborted };\n\t\t\tconst { newLeafId, editorText } = this._resolveTreeTarget(targetEntry, targetId);\n\t\t\tconst summaryEntry = this._switchTreeLeaf(targetId, newLeafId, summary, extensionPreparation.label);\n\n\t\t\t// Update agent state\n\t\t\tconst sessionContext = this.session.buildSessionContext();\n\t\t\tthis.agent.state.messages = sessionContext.messages;\n\n\t\t\t// Emit session_tree event\n\t\t\tawait this._extensionRunner.emit({\n\t\t\t\ttype: \"session_tree\",\n\t\t\t\tnewLeafId: this.session.getLeafId(),\n\t\t\t\toldLeafId,\n\t\t\t\tsummaryEntry,\n\t\t\t\tfromExtension: summary.summaryText ? summary.fromExtension : undefined,\n\t\t\t});\n\n\t\t\t// Emit to custom tools\n\n\t\t\treturn { editorText, cancelled: false, summaryEntry };\n\t\t} finally {\n\t\t\tthis._branchSummaryAbortController = undefined;\n\t\t}\n\t}\n\n\t/**\n\t * Get all user messages from session for fork selector.\n\t */\n\tgetUserMessagesForForking(): ForkableUserMessage[] {\n\t\tconst entries = this.session.getEntries();\n\t\tconst result: ForkableUserMessage[] = [];\n\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.type !== \"message\") continue;\n\t\t\tif (entry.message.role !== \"user\") continue;\n\n\t\t\tconst text = this._extractUserMessageText(entry.message);\n\t\t\tif (text) {\n\t\t\t\tresult.push({ entryId: entry.id, text });\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tprivate _extractUserMessageText(message: UserMessage): string {\n\t\tconst { content } = message;\n\t\tif (typeof content === \"string\") return content;\n\t\tif (Array.isArray(content)) {\n\t\t\treturn content\n\t\t\t\t.filter((c): c is TextContent => c.type === \"text\")\n\t\t\t\t.map((c) => c.text)\n\t\t\t\t.join(\"\");\n\t\t}\n\t\treturn \"\";\n\t}\n\n\t/**\n\t * Get session statistics.\n\t */\n\tgetSessionStats(): SessionStats {\n\t\tconst state = this.state;\n\t\tconst userMessages = state.messages.filter((m) => m.role === \"user\").length;\n\t\tconst assistantMessages = state.messages.filter((m) => m.role === \"assistant\").length;\n\t\tconst toolResults = state.messages.filter((m) => m.role === \"toolResult\").length;\n\n\t\tlet toolCalls = 0;\n\t\tfor (const message of state.messages) {\n\t\t\tif (message.role === \"assistant\") toolCalls += message.content.filter((c) => c.type === \"toolCall\").length;\n\t\t}\n\t\tconst usage = readUsageLedger(this.session.getEntries());\n\t\treturn {\n\t\t\tsessionFile: this.sessionFile,\n\t\t\tsessionId: this.sessionId,\n\t\t\tuserMessages,\n\t\t\tassistantMessages,\n\t\t\ttoolCalls,\n\t\t\ttoolResults,\n\t\t\ttotalMessages: state.messages.length,\n\t\t\ttokens: {\n\t\t\t\tinput: usage.input,\n\t\t\t\toutput: usage.output,\n\t\t\t\tcacheRead: usage.cacheRead,\n\t\t\t\tcacheWrite: usage.cacheWrite,\n\t\t\t\ttotal: usage.input + usage.output + usage.cacheRead + usage.cacheWrite,\n\t\t\t},\n\t\t\tcost: usage.catalogCost,\n\t\t\tcontextUsage: this.getContextUsage(),\n\t\t};\n\t}\n\n\tprivate _hasUsableAssistantUsageAfterBoundary(branchEntries: SessionEntry[], boundary: SessionEntry): boolean {\n\t\tconst compactionIndex = branchEntries.lastIndexOf(boundary);\n\t\tfor (let index = branchEntries.length - 1; index > compactionIndex; index--) {\n\t\t\tconst entry = branchEntries[index];\n\t\t\tif (entry.type !== \"message\" || entry.message.role !== \"assistant\") continue;\n\t\t\tconst assistant = entry.message;\n\t\t\tif (assistant.stopReason === \"aborted\" || assistant.stopReason === \"error\") continue;\n\t\t\treturn calculateContextTokens(assistant.usage) > 0;\n\t\t}\n\t\treturn false;\n\t}\n\n\tgetCompressionDetectionCounts(): CompressionDetectionCounts {\n\t\tconst { keep, compress } = readCompressionDetectionLedger(this.session.getEntries());\n\t\treturn { keep, compress };\n\t}\n\n\tgetCompressionDetectionCost(): number {\n\t\treturn readCompressionDetectionLedger(this.session.getEntries()).cost;\n\t}\n\n\tgetContextUsage(): ContextUsage | undefined {\n\t\tconst model = this.model;\n\t\tif (!model) return undefined;\n\n\t\tconst contextWindow = model.contextWindow ?? 0;\n\t\tif (contextWindow <= 0) return undefined;\n\n\t\t// After compaction, the last assistant usage reflects pre-compaction context size.\n\t\t// We can only trust usage from an assistant that responded after the latest compaction.\n\t\t// If no such assistant exists, context token count is unknown until the next LLM response.\n\t\tconst branchEntries = this.session.getBranch();\n\t\tconst latestCompaction = getLatestCompactionEntry(branchEntries);\n\t\tconst latestCompression = [...branchEntries]\n\t\t\t.reverse()\n\t\t\t.find(\n\t\t\t\t(entry) =>\n\t\t\t\t\tentry.type === \"custom_message\" &&\n\t\t\t\t\tentry.customType === \"compress_context\" &&\n\t\t\t\t\t!!entry.details &&\n\t\t\t\t\ttypeof entry.details === \"object\" &&\n\t\t\t\t\t\"replacementVersion\" in entry.details &&\n\t\t\t\t\tentry.details.replacementVersion === 1,\n\t\t\t);\n\t\tconst boundary =\n\t\t\tlatestCompression &&\n\t\t\t(!latestCompaction || branchEntries.indexOf(latestCompression) > branchEntries.indexOf(latestCompaction))\n\t\t\t\t? latestCompression\n\t\t\t\t: latestCompaction;\n\t\tif (boundary && !this._hasUsableAssistantUsageAfterBoundary(branchEntries, boundary)) {\n\t\t\treturn { tokens: null, contextWindow, percent: null };\n\t\t}\n\n\t\tconst estimate = estimateContextTokens(this.messages);\n\t\tconst percent = (estimate.tokens / contextWindow) * 100;\n\n\t\treturn {\n\t\t\ttokens: estimate.tokens,\n\t\t\tcontextWindow,\n\t\t\tpercent,\n\t\t};\n\t}\n\n\t/**\n\t * Export session to HTML.\n\t * @param outputPath Optional output path (defaults to session directory)\n\t * @returns Path to exported file\n\t */\n\tasync exportToHtml(outputPath?: string): Promise<string> {\n\t\tconst themeName = this.settingsManager.getTheme();\n\n\t\t// Create tool renderer if we have an extension runner (for custom tool HTML rendering)\n\t\tconst toolRenderer: ToolHtmlRenderer = createToolHtmlRenderer({\n\t\t\tgetToolDefinition: (name) => this.getToolDefinition(name),\n\t\t\ttheme,\n\t\t\tcwd: this.session.getCwd(),\n\t\t});\n\n\t\treturn await exportSessionToHtml(this.session, this.state, {\n\t\t\toutputPath,\n\t\t\tthemeName,\n\t\t\ttoolRenderer,\n\t\t});\n\t}\n\n\t/**\n\t * Export the current session branch to a JSONL file.\n\t * Writes the session header followed by all entries on the current branch path.\n\t * @param outputPath Target file path. If omitted, generates a timestamped file in cwd.\n\t * @returns The resolved output file path.\n\t */\n\texportToJsonl(outputPath?: string): string {\n\t\tconst filePath = resolvePath(\n\t\t\toutputPath ?? `session-${new Date().toISOString().replace(/[:.]/g, \"-\")}.jsonl`,\n\t\t\tprocess.cwd(),\n\t\t);\n\t\tconst dir = dirname(filePath);\n\t\tif (!existsSync(dir)) {\n\t\t\tmkdirSync(dir, { recursive: true });\n\t\t}\n\n\t\tconst header: SessionHeader = {\n\t\t\ttype: \"session\",\n\t\t\tversion: CURRENT_SESSION_VERSION,\n\t\t\tid: this.session.getSessionId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tcwd: this.session.getCwd(),\n\t\t};\n\n\t\tconst branchEntries = this.session.getBranch();\n\t\tconst lines = [JSON.stringify(header)];\n\n\t\t// Re-chain parentIds to form a linear sequence\n\t\tlet prevId: string | null = null;\n\t\tfor (const entry of branchEntries) {\n\t\t\tconst linear = { ...entry, parentId: prevId };\n\t\t\tlines.push(JSON.stringify(linear));\n\t\t\tprevId = entry.id;\n\t\t}\n\n\t\twriteFileSync(filePath, `${lines.join(\"\\n\")}\\n`);\n\t\treturn filePath;\n\t}\n\n\t// =========================================================================\n\t// Utilities\n\t// =========================================================================\n\n\t/**\n\t * Get text content of last assistant message.\n\t * Useful for /copy command.\n\t * @returns Text content, or undefined if no assistant message exists\n\t */\n\tgetLastAssistantText(): string | undefined {\n\t\tconst lastAssistant = this.messages\n\t\t\t.slice()\n\t\t\t.reverse()\n\t\t\t.find((m) => {\n\t\t\t\tif (m.role !== \"assistant\") return false;\n\t\t\t\tconst msg = m as AssistantMessage;\n\t\t\t\t// Skip aborted messages with no content\n\t\t\t\tif (msg.stopReason === \"aborted\" && msg.content.length === 0) return false;\n\t\t\t\treturn true;\n\t\t\t});\n\n\t\tif (!lastAssistant) return undefined;\n\n\t\tlet text = \"\";\n\t\tfor (const content of (lastAssistant as AssistantMessage).content) {\n\t\t\tif (content.type === \"text\") {\n\t\t\t\ttext += content.text;\n\t\t\t}\n\t\t}\n\n\t\treturn text.trim() || undefined;\n\t}\n\n\t// =========================================================================\n\t// Extension System\n\t// =========================================================================\n\n\tcreateReplacedSessionContext(): ReplacedSessionContext {\n\t\tconst context = Object.defineProperties(\n\t\t\t{},\n\t\t\tObject.getOwnPropertyDescriptors(this._extensionRunner.createCommandContext()),\n\t\t) as ReplacedSessionContext;\n\t\tcontext.sendMessage = (message, options) => this.sendCustomMessage(message, options);\n\t\tcontext.sendUserMessage = (content, options) => this.sendUserMessage(content, options);\n\t\treturn context;\n\t}\n\n\t/**\n\t * Check if extensions have handlers for a specific event type.\n\t */\n\thasExtensionHandlers(eventType: string): boolean {\n\t\treturn this._extensionRunner.hasHandlers(eventType);\n\t}\n\n\t/**\n\t * Get the extension runner (for setting UI context and error handlers).\n\t */\n\tget extensionRunner(): ExtensionRunner {\n\t\treturn this._extensionRunner;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"agent-session.d.ts","sourceRoot":"","sources":["../../src/core/agent-session.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * AgentSession - Core abstraction for agent lifecycle and session management.\n *\n * This class is shared between all run modes (interactive, print, rpc).\n * It encapsulates:\n * - Agent state access\n * - Event subscription with automatic session persistence\n * - Model and thinking level management\n * - Compaction (manual and auto)\n * - Bash execution\n * - Tree navigation and branching\n *\n * Modes use this class and add their own I/O layer on top.\n */\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport { randomUUID } from \"node:crypto\";\nimport { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { basename, dirname, posix, sep, win32 } from \"node:path\";\nimport type {\n\tAfterToolCallContext,\n\tAfterToolCallResult,\n\tAgent,\n\tAgentContext,\n\tAgentEvent,\n\tAgentLoopTurnUpdate,\n\tAgentMessage,\n\tAgentState,\n\tAgentTool,\n\tBeforeToolCallContext,\n\tBeforeToolCallResult,\n\tCompactionSettings,\n\tPrepareNextTurnContext,\n\tQueueMode,\n\tThinkingLevel,\n} from \"@greppleai/grip-agent-core\";\nimport type {\n\tApi,\n\tAssistantMessage,\n\tImageContent,\n\tMessage,\n\tModel,\n\tProviderHeaders,\n\tRetryCallbacks,\n\tTextContent,\n\tTool,\n\tToolCall,\n\tToolResultMessage,\n\tUserMessage,\n} from \"@greppleai/grip-ai\";\nimport {\n\tclampThinkingLevel,\n\tcleanupSessionResources,\n\tgetSupportedThinkingLevels,\n\tisContextOverflow,\n\tisRetryableAssistantError,\n\tmodelsAreEqual,\n\tresetApiProviders,\n\tstreamSimple,\n\tvalidateToolArguments,\n} from \"@greppleai/grip-ai\";\nimport { Text } from \"@greppleai/grip-tui\";\nimport { type Static, type TSchema, Type } from \"typebox\";\nimport { theme } from \"../modes/interactive/theme/theme.ts\";\nimport { stripFrontmatter } from \"../utils/frontmatter.ts\";\nimport { canonicalizePath, resolvePath } from \"../utils/paths.ts\";\nimport { sleep } from \"../utils/sleep.ts\";\nimport { formatNoApiKeyFoundMessage, formatNoModelSelectedMessage } from \"./auth-guidance.ts\";\nimport { type BashResult, executeBashWithOperations } from \"./bash-executor.ts\";\nimport { collectEntriesForBranchSummary, generateBranchSummary } from \"./compaction/branch-summarization.ts\";\nimport {\n\ttype CompactionPreparation,\n\ttype CompactionResult,\n\tcalculateContextTokens,\n\tcompact,\n\testimateContextTokens,\n\tprepareCompaction,\n\tshouldCompact,\n} from \"./compaction/compaction.ts\";\nimport {\n\tappendCompressionDetectionEvent,\n\treadCompressionDetectionLedger,\n} from \"./compaction/compression-detection-ledger.ts\";\nimport {\n\ttype CompressionDetectionCounts,\n\ttype CompressionDetectionVerdict,\n\tCompressionDetector,\n} from \"./compaction/compression-detector.ts\";\nimport {\n\tCOMPRESSION_ECONOMICS_ENTRY_TYPE,\n\tCOMPRESSION_SAVINGS_SNAPSHOT_TYPE,\n\tcreateCompressionSavingsSnapshot,\n\testimateCompressionEconomics,\n} from \"./compaction/compression-economics.ts\";\nimport { DEFAULT_THINKING_LEVEL } from \"./defaults.ts\";\nimport { exportSessionToHtml, type ToolHtmlRenderer } from \"./export-html/index.ts\";\nimport { createToolHtmlRenderer } from \"./export-html/tool-renderer.ts\";\nimport {\n\ttype ExtensionErrorListener,\n\tExtensionRunner,\n\temitSessionShutdownEvent,\n\ttype ShutdownHandler,\n} from \"./extensions/runner.ts\";\nimport type {\n\tCompactOptions,\n\tContextUsage,\n\tExtensionCommandContextActions,\n\tExtensionNavigateTreeOptions,\n\tExtensionSendMessageOptions,\n\tExtensionUIContext,\n\tInputSource,\n\tMessageEndEvent,\n\tMessageStartEvent,\n\tMessageUpdateEvent,\n\tModelSelectSource,\n\tReplacedSessionContext,\n\tResourcesDiscoverReason,\n\tSessionBeforeCompactResult,\n\tSessionBeforeTreeResult,\n\tSessionBeforeTreeSummary,\n\tSessionStartEvent,\n\tStreamingBehavior,\n\tToolDefinition,\n\tToolExecutionEndEvent,\n\tToolExecutionStartEvent,\n\tToolExecutionUpdateEvent,\n\tToolInfo,\n\tTreePreparation,\n\tTurnEndEvent,\n\tTurnStartEvent,\n} from \"./extensions/types.ts\";\nimport { wrapRegisteredTools } from \"./extensions/wrapper.ts\";\nimport { capModelVisibleHookField, runHooks } from \"./hooks/index.ts\";\nimport { matchHookValue } from \"./hooks/matcher.ts\";\nimport type {\n\tHookAggregateResult,\n\tHookDiagnostic,\n\tHookEventName,\n\tHookExecutionActivityListener,\n\tHookExecutionListener,\n\tHookExecutionNotice,\n\tHookExecutionResult,\n\tHookInput,\n\tHookRunOptions,\n\tJsonObject,\n\tJsonValue,\n\tLoadedHooks,\n\tSessionEndReason,\n} from \"./hooks/types.ts\";\nimport { HOOK_EXECUTION_CUSTOM_TYPE } from \"./hooks/types.ts\";\nimport type { InstructionResource } from \"./instruction-resource-loader.ts\";\nimport {\n\ttype LspConfigurationLayer,\n\tparseLspConfiguration,\n\ttype ResolvedLspConfiguration,\n\tresolveLspConfiguration,\n} from \"./lsp/config.ts\";\nimport {\n\ttype LoadLspConfigurationResult,\n\ttype LspConfigurationSourceDiagnostic,\n\tresolveLspConfigurationLayerPaths,\n} from \"./lsp/config-loader.ts\";\nimport { formatAutoDiagnosticsForChangedFile } from \"./lsp/diagnostics.ts\";\nimport {\n\tcreateLspRuntimeState,\n\tcreateLspToolDefinitions,\n\tLSP_TOOL_NAMES,\n\ttype LspRuntimeState,\n\ttype LspSessionStatus,\n} from \"./lsp/integration.ts\";\nimport type { LspConnectionFactoryRegistry } from \"./lsp/transport.ts\";\nimport {\n\ttype BashExecutionMessage,\n\ttype CustomMessage,\n\tcreateCustomMessage,\n\tnormalizeMessageContent,\n\tSTRUCTURED_RESPONSE_INTERNAL_CUSTOM_TYPE,\n} from \"./messages.ts\";\nimport type { ModelRegistry } from \"./model-registry.ts\";\nimport type { ScopedModel } from \"./model-resolver.ts\";\nimport { expandPromptTemplate, type PromptTemplate } from \"./prompt-templates.ts\";\nimport type { ResourceExtensionPaths, ResourceLoader, ResourcePathEntry } from \"./resource-loader.ts\";\nimport type { Rule } from \"./rules.ts\";\nimport { CURRENT_SESSION_VERSION } from \"./session/constants.ts\";\nimport { getLatestCompactionEntry, projectSessionContextEntries } from \"./session/context.ts\";\nimport { annotateContextMetadata } from \"./session/context-metadata.ts\";\nimport type { Session } from \"./session/session.ts\";\nimport type {\n\tBranchSummaryEntry,\n\tCompactionEntry,\n\tSessionContext,\n\tSessionEntry,\n\tSessionHeader,\n} from \"./session/types.ts\";\nimport { readUsageLedger, USAGE_LEDGER_ENTRY_TYPE } from \"./session/usage-ledger.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\nimport type { Skill } from \"./skills.ts\";\nimport { HIDDEN_BUILTIN_SLASH_COMMAND_NAMES, type SlashCommandInfo } from \"./slash-commands.ts\";\nimport { createSyntheticSourceInfo, getSourceBackend, type SourceInfo } from \"./source-info.ts\";\nimport { type BuildSystemPromptOptions, buildSystemPrompt } from \"./system-prompt.ts\";\nimport { createLocalBashOperations } from \"./tools/bash.ts\";\nimport {\n\tcreateCompressContextToolDefinition,\n\tgetCompressibleEntries,\n\tgetSuggestedCompressionRangeError,\n\tisValidSuggestedCompressionRange,\n\ttype PendingContextCompression,\n} from \"./tools/compress-context.ts\";\nimport {\n\tDeferredRemoteToolOperations,\n\tLocalToolOperations,\n\ttype ToolBackendInfo,\n\ttype ToolOperations,\n} from \"./tools/operations.ts\";\nimport type { ReadToolOperationsSelection } from \"./tools/read.ts\";\nimport { createSessionEntryGetToolDefinition, createSessionSearchToolDefinition } from \"./tools/session-history.ts\";\nimport {\n\tcreateCreateSubagentToolDefinition,\n\tcreateSubagentRunsToolDefinition,\n\tcreateSubagentToolDefinition,\n\tformatSubagentModelCatalog,\n\ttype SubagentConfigRegistry,\n\ttype SubagentModelHint,\n\ttype SubagentRunner,\n\ttype SubagentRunRegistry,\n} from \"./tools/subagent.ts\";\nimport { createToolDefinitionFromAgentTool } from \"./tools/tool-definition-wrapper.ts\";\nimport { createWebsearchToolDefinition, parseWebsearchToolOptions } from \"./tools/websearch.ts\";\nimport { WORKSPACE_TOOL_NAMES, WorkspaceToolHost } from \"./tools/workspace-tool-host.ts\";\n\n// ============================================================================\n// Skill Block Parsing\n// ============================================================================\n\nconst CORE_DEFAULT_TOOL_NAMES = [\"read\", \"bash\", \"edit\", \"write\", \"websearch\"] as const;\nconst SESSION_HISTORY_TOOL_NAMES = [\"session_search\", \"session_entry_get\"] as const;\nexport const SUBAGENT_TOOL_NAMES = [\"subagent\", \"subagent_runs\", \"create_subagent\"] as const;\nexport const DEFAULT_ACTIVE_TOOL_NAMES = [...CORE_DEFAULT_TOOL_NAMES, ...SESSION_HISTORY_TOOL_NAMES] as const;\n\nexport function getDefaultActiveToolNames(enableSubagents = false, enableLspTools = false): string[] {\n\treturn [\n\t\t...DEFAULT_ACTIVE_TOOL_NAMES,\n\t\t...(process.env.GRIP_CONTEXT_COMPRESSION === \"1\" || process.env.GRIP_CONTEXT_COMPRESSION_DETECTION === \"1\"\n\t\t\t? [\"compress_context\"]\n\t\t\t: []),\n\t\t...(enableLspTools ? LSP_TOOL_NAMES : []),\n\t\t...(enableSubagents ? SUBAGENT_TOOL_NAMES : []),\n\t];\n}\n\n// ============================================================================\n\n/** Parsed skill block from a user message */\nexport interface ParsedSkillBlock {\n\tname: string;\n\tlocation: string;\n\tcontent: string;\n\tuserMessage: string | undefined;\n}\n\n/**\n * Parse a skill block from message text.\n * Returns null if the text doesn't contain a skill block.\n */\nexport function parseSkillBlock(text: string): ParsedSkillBlock | null {\n\tconst match = text.match(/^<skill name=\"([^\"]+)\" location=\"([^\"]+)\">\\n([\\s\\S]*?)\\n<\\/skill>(?:\\n\\n([\\s\\S]+))?$/);\n\tif (!match) return null;\n\treturn {\n\t\tname: match[1],\n\t\tlocation: match[2],\n\t\tcontent: match[3],\n\t\tuserMessage: match[4]?.trim() || undefined,\n\t};\n}\n\n/** Session-specific events that extend the core AgentEvent */\nexport type AgentSessionEvent =\n\t| Exclude<AgentEvent, { type: \"agent_end\" }>\n\t| {\n\t\t\ttype: \"agent_end\";\n\t\t\tmessages: AgentMessage[];\n\t\t\twillRetry: boolean;\n\t }\n\t| {\n\t\t\ttype: \"queue_update\";\n\t\t\tsteering: readonly string[];\n\t\t\tfollowUp: readonly string[];\n\t }\n\t| { type: \"compaction_start\"; reason: \"manual\" | \"threshold\" | \"overflow\" }\n\t| { type: \"state_compressed\" }\n\t| { type: \"compression_detection_activity\"; active: boolean }\n\t| { type: \"compression_detection_result\"; verdict: CompressionDetectionVerdict }\n\t| { type: \"session_info_changed\"; name: string | undefined }\n\t| { type: \"thinking_level_changed\"; level: ThinkingLevel }\n\t| {\n\t\t\ttype: \"compaction_end\";\n\t\t\treason: \"manual\" | \"threshold\" | \"overflow\";\n\t\t\tresult: CompactionResult | undefined;\n\t\t\taborted: boolean;\n\t\t\twillRetry: boolean;\n\t\t\terrorMessage?: string;\n\t }\n\t| { type: \"agent_settled\" }\n\t| { type: \"auto_retry_start\"; attempt: number; maxAttempts: number; delayMs: number; errorMessage: string }\n\t| { type: \"auto_retry_end\"; success: boolean; attempt: number; finalError?: string }\n\t| {\n\t\t\ttype: \"summarization_retry_scheduled\";\n\t\t\tattempt: number;\n\t\t\tmaxAttempts: number;\n\t\t\tdelayMs: number;\n\t\t\terrorMessage: string;\n\t }\n\t| { type: \"summarization_retry_attempt_start\"; source: \"branchSummary\" }\n\t| {\n\t\t\ttype: \"summarization_retry_attempt_start\";\n\t\t\tsource: \"compaction\";\n\t\t\treason: \"manual\" | \"threshold\" | \"overflow\";\n\t }\n\t| { type: \"summarization_retry_finished\" };\n\n/** Listener function for agent session events */\nexport type AgentSessionEventListener = (event: AgentSessionEvent) => void;\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ExtensionRunnerRef {\n\tcurrent?: ExtensionRunner;\n}\n\nexport interface SessionStatsTokenTotals {\n\tinput: number;\n\toutput: number;\n\tcacheRead: number;\n\tcacheWrite: number;\n\ttotal: number;\n}\n\nexport interface AgentSessionConfig {\n\tagent: Agent;\n\tsession: Session;\n\tsettingsManager: SettingsManager;\n\tcwd: string;\n\t/** Models to cycle through with Ctrl+P (from --models flag) */\n\tscopedModels?: ScopedModel[];\n\t/** Resource loader for skills, rules, prompts, themes, context files, system prompt */\n\tresourceLoader: ResourceLoader;\n\t/** SDK custom tools registered outside extensions */\n\tcustomTools?: ToolDefinition[];\n\t/** Tool operation backend used by built-in tools. */\n\ttoolOperations?: ToolOperations;\n\t/** Model registry for API key resolution and model discovery */\n\tmodelRegistry: ModelRegistry;\n\t/** Initial active built-in tool names. Defaults to DEFAULT_ACTIVE_TOOL_NAMES. */\n\tinitialActiveToolNames?: string[];\n\t/** CLI/SDK override for exposing configured LSP tools by default; explicit tool allowlists remain authoritative. */\n\tenableLspTools?: boolean;\n\t/** Optional allowlist of tool names. When provided, only these tool names are exposed. */\n\tallowedToolNames?: string[];\n\t/** Tool names that must never be exposed in this session. */\n\texcludedToolNames?: string[];\n\t/**\n\t * Override base tools (useful for custom runtimes).\n\t *\n\t * These are synthesized into minimal ToolDefinitions internally so AgentSession can keep\n\t * a definition-first registry even when callers provide plain AgentTool instances.\n\t */\n\tbaseToolsOverride?: Record<string, AgentTool>;\n\t/** Mutable ref used by Agent to access the current ExtensionRunner */\n\textensionRunnerRef?: ExtensionRunnerRef;\n\t/** Runner used to create isolated subagent sessions. */\n\tsubagentRunner?: SubagentRunner;\n\tsubagentRunRegistry?: SubagentRunRegistry;\n\tsubagentConfigRegistry?: SubagentConfigRegistry;\n\t/** Host-controlled trust grant for project-local subagent presets. */\n\ttrustProjectAgents?: boolean;\n\t/** Validated configuration for the AgentSession-owned LSP runtime. */\n\tlspConfiguration?: ResolvedLspConfiguration;\n\t/** Host-provided factories referenced by LSP transports with type `connection`. */\n\tlspConnectionFactories?: LspConnectionFactoryRegistry;\n\t/** Re-resolves settings/file-backed LSP configuration after settings reload. */\n\tresolveLspConfiguration?: () => Promise<LoadLspConfigurationResult>;\n\t/** Receives current source-attributed diagnostics after each LSP configuration re-resolution. */\n\tonLspConfigurationDiagnostics?: (diagnostics: readonly LspConfigurationSourceDiagnostic[]) => void;\n\t/** Session start event metadata emitted when extensions bind to this runtime. */\n\tsessionStartEvent?: SessionStartEvent;\n\t/** Immutable Claude-compatible hook configuration snapshot for this session. */\n\tloadedHooks?: LoadedHooks;\n\t/** Re-resolves file-backed hook configuration after settings reload. */\n\tresolveHooks?: () => Promise<LoadedHooks | undefined>;\n\t/** Receives nonfatal hook execution/runtime diagnostics. */\n\tonHookDiagnostic?: (diagnostic: HookDiagnostic) => void;\n\t/** Host restrictions applied to hook execution. */\n\thookRunOptions?: Pick<HookRunOptions, \"allowedHttpHookUrls\" | \"httpHookAllowedEnvVars\">;\n}\n\nexport interface ExtensionBindings {\n\tuiContext?: ExtensionUIContext;\n\tcommandContextActions?: ExtensionCommandContextActions;\n\tabortHandler?: () => void;\n\tshutdownHandler?: ShutdownHandler;\n\tonError?: ExtensionErrorListener;\n}\n\nexport interface AgentSessionReloadResult {\n\tlsp?: LoadLspConfigurationResult;\n}\n\n/** Options for AgentSession.prompt() */\nexport interface PromptOptions {\n\t/** Whether to expand file-based prompt templates (default: true) */\n\texpandPromptTemplates?: boolean;\n\t/** Image attachments */\n\timages?: ImageContent[];\n\t/** When streaming, how to queue the message: \"steer\" (interrupt) or \"followUp\" (wait). Required if streaming. */\n\tstreamingBehavior?: StreamingBehavior;\n\t/** Source of input for extension input event handlers. Defaults to \"interactive\". */\n\tsource?: InputSource;\n\t/** Internal hook used by RPC mode to observe prompt preflight acceptance or rejection. */\n\tpreflightResult?: (success: boolean) => void;\n}\n\nexport type StructuredResponseScope = \"latest\" | \"conversation\";\nexport type StructuredResponseSource = \"json\" | \"tool\";\n\nexport interface StructuredResponseOptions<TSchemaValue extends TSchema> {\n\t/** TypeBox object schema for the structured response. */\n\tschema: TSchemaValue;\n\t/** Schema/tool name shown to the model. Defaults to \"structured_output\". */\n\tname?: string;\n\t/** Optional description for the temporary structured output tool. */\n\tdescription?: string;\n\t/** Maximum correction calls after an invalid response. Defaults to 2. */\n\tmaxCorrections?: number;\n\t/** Source context for extraction. Defaults to the latest assistant answer. */\n\tscope?: StructuredResponseScope;\n}\n\nexport interface StructuredResponse<T> {\n\toutput: T;\n\tattempts: number;\n\tsource: StructuredResponseSource;\n\tmessage: AssistantMessage;\n}\n\n/** Direction used when cycling through configured models. */\nexport type ModelCycleDirection = \"forward\" | \"backward\";\n\n/** Result from cycleModel() */\nexport interface ModelCycleResult {\n\tmodel: Model<any>;\n\tthinkingLevel: ThinkingLevel;\n\t/** Whether cycling through scoped models (--models flag) or all available */\n\tisScoped: boolean;\n}\n\n/** Session statistics for /session command */\nexport interface SessionStats {\n\tsessionFile: string | undefined;\n\tsessionId: string;\n\tuserMessages: number;\n\tassistantMessages: number;\n\ttoolCalls: number;\n\ttoolResults: number;\n\ttotalMessages: number;\n\ttokens: SessionStatsTokenTotals;\n\tcost: number;\n\tcontextUsage?: ContextUsage;\n}\n\ninterface ToolDefinitionEntry {\n\tdefinition: ToolDefinition;\n\tsourceInfo: SourceInfo;\n}\n\ninterface SessionToolEntry extends ToolDefinitionEntry {\n\tlazy: boolean;\n\tloaded: boolean;\n}\n\ninterface ExtensionLspOwnership {\n\tconfiguration: ResolvedLspConfiguration;\n\truntimeState: LspRuntimeState | undefined;\n}\n\ninterface RuntimeLifecycleOperationToken {\n\tactive: boolean;\n\tchildren: Set<Promise<unknown>>;\n\tchildQueue: Promise<void>;\n}\n\ninterface SessionActivityToken {\n\tactive: boolean;\n\tkind: string;\n}\n\ninterface PromptPreparationState {\n\treadonly text: string;\n\treadonly options: PromptOptions | undefined;\n\treadonly promptId: string;\n\treadonly expandPromptTemplates: boolean;\n\treadonly hookContextMessages: CustomMessage[];\n\tpromptActivity: SessionActivityToken | undefined;\n\tqueueIntoActiveRun: boolean;\n\tsessionContext: CustomMessage | undefined;\n\tcurrentText: string;\n\tcurrentImages: ImageContent[] | undefined;\n\texpandedText: string;\n}\n\ninterface IdleWaiter {\n\tresolve: () => void;\n\treject: (error: Error) => void;\n}\n\ninterface BarrierOwnerScope {\n\tactive: boolean;\n\tlabel: string;\n}\n\ntype StructuredInternalStage = \"request\" | \"assistant\" | \"tool_result\" | \"result\";\n\ninterface StructuredInternalDetails {\n\tstage: StructuredInternalStage;\n\tschemaName: string;\n\tattempt: number;\n\tsource?: StructuredResponseSource;\n\tvalidationError?: string;\n}\n\ninterface HookToolOutput {\n\tcontent: (TextContent | ImageContent)[];\n\tdetails?: JsonValue;\n}\n\ninterface PreToolHookExecution {\n\ttoolCall: BeforeToolCallContext[\"toolCall\"];\n\targs: unknown;\n\tagentContext: AgentContext;\n\tbackend: ToolBackendInfo;\n\tflavor: HookPathFlavor;\n\tnormalizedToolName: string;\n\tadaptToolInput: boolean;\n}\n\ntype PreToolInputAdaptation = { ok: true; input: JsonObject } | { ok: false; blockResult: BeforeToolCallResult };\n\ntype ToolHookEventName = \"PreToolUse\" | \"PostToolUse\" | \"PostToolUseFailure\";\ntype PostToolHookEvent = \"PostToolUse\" | \"PostToolUseFailure\";\n\ninterface PostToolCallState {\n\ttoolCall: AfterToolCallContext[\"toolCall\"];\n\targs: unknown;\n\tcontent: AfterToolCallContext[\"result\"][\"content\"];\n\tdetails: AfterToolCallContext[\"result\"][\"details\"];\n\tisError: boolean;\n}\n\ntype PostToolHookRunResult = { ok: true; hookResult: HookAggregateResult } | { ok: false };\n\ninterface SessionToolNameArgs {\n\tname?: string | string[];\n}\n\ninterface SessionToolNameParameters {\n\tname: string | string[];\n}\n\ninterface RequiredRequestAuth {\n\tapiKey: string;\n\theaders?: ProviderHeaders;\n}\n\ntype CompactionRequestAuth = Partial<RequiredRequestAuth>;\n\ninterface LspToolResult {\n\tcontent: ToolResultMessage[\"content\"];\n\tdetails?: unknown;\n}\n\ninterface LspToolResultUpdate extends LspToolResult {\n\tisError?: boolean;\n}\n\ntype CoreAgentEndEvent = Extract<AgentEvent, { type: \"agent_end\" }>;\n\ntype StructuredValidationResult<T> = { ok: true; output: T } | { ok: false; error: string };\n\ninterface StructuredAttemptContext<TSchemaValue extends TSchema> {\n\ttool: Tool<TSchemaValue>;\n\tassistantMessage: AssistantMessage;\n\tmessages: Message[];\n\tschemaName: string;\n\tattempt: number;\n}\n\ntype StructuredAttemptResult<T> = { ok: true; response: StructuredResponse<T> } | { ok: false; error: string };\n\ninterface CompactionExecutionOptions {\n\tabortActiveRun: boolean;\n}\n\ninterface CompactionPlan extends CompactionRequestAuth {\n\tmodel: Model<Api>;\n\tpreparation: CompactionPreparation;\n\tpathEntries: SessionEntry[];\n}\n\ntype AutoCompactionReason = \"overflow\" | \"threshold\";\ntype ExtensionCompactionQueueMode = \"idle\" | \"between-turns\";\n\ninterface AutoCompactionInterception {\n\tcancelled: boolean;\n\tcompaction?: CompactionResult;\n}\n\ninterface ResolvedAutoCompaction {\n\tresult: CompactionResult;\n\tfromExtension: boolean;\n}\n\ninterface ExtensionResourcePath {\n\tpath: string;\n\textensionPath: string;\n}\n\ninterface ToolRegistryRefreshOptions {\n\tactiveToolNames?: string[];\n\tincludeAllExtensionTools?: boolean;\n}\n\ninterface BuildRuntimeOptions extends ToolRegistryRefreshOptions {\n\tflagValues?: Map<string, boolean | string>;\n}\n\ninterface RuntimeBuildSnapshot {\n\tbaseToolDefinitions: Map<string, ToolDefinition>;\n\tlspRuntimeState: LspRuntimeState | undefined;\n\textensionRunner: ExtensionRunner | undefined;\n\ttoolRegistry: Map<string, AgentTool>;\n\ttoolDefinitions: Map<string, ToolDefinitionEntry>;\n\ttoolPromptSnippets: Map<string, string>;\n\ttoolPromptGuidelines: Map<string, string[]>;\n\tbaseSystemPrompt: string;\n\tbaseSystemPromptOptions: BuildSystemPromptOptions | undefined;\n\tagentTools: AgentTool[];\n\tagentSystemPrompt: string;\n\textensionRunnerRef: ExtensionRunner | undefined;\n\tworkspaceToolHost: WorkspaceToolHost | undefined;\n}\ninterface StopContinuationProgressState {\n\tbest: number;\n\tconsecutiveNonImprovingCalls: number;\n}\n\ntype SummarizationRetrySource =\n\t| { source: \"branchSummary\" }\n\t| { source: \"compaction\"; reason: \"manual\" | \"threshold\" | \"overflow\" };\n\nexport interface ConfigureRemoteSandboxOptions {\n\ttype: \"daemon\";\n\turl: string;\n\ttoken?: string;\n}\n\nexport interface ActivateSandboxDaemonOptions {\n\turl: string;\n\ttoken: string;\n\texpectedCwd: string;\n}\n\nexport type SessionSkillRegistration = Omit<Skill, \"sourceInfo\" | \"baseDir\"> & {\n\tbaseDir?: string;\n\tsourceInfo?: SourceInfo;\n};\n\nexport type SessionRuleRegistration = Omit<Rule, \"sourceInfo\" | \"baseDir\"> & {\n\tbaseDir?: string;\n\tsourceInfo?: SourceInfo;\n};\n\nexport interface RegisterSessionToolOptions {\n\tlazy?: boolean;\n\tsourceInfo?: SourceInfo;\n}\n\nexport type SendCustomMessageOptions = ExtensionSendMessageOptions;\n\nexport interface SendUserMessageOptions {\n\tdeliverAs?: StreamingBehavior;\n}\n\nexport interface QueuedMessages {\n\tsteering: string[];\n\tfollowUp: string[];\n}\n\nexport interface ExecuteBashOptions {\n\texcludeFromContext?: boolean;\n\toperations?: ToolOperations;\n\trecord?: boolean;\n\ttruncate?: boolean;\n}\n\nexport interface RecordBashResultOptions {\n\texcludeFromContext?: boolean;\n}\n\nexport type NavigateTreeOptions = ExtensionNavigateTreeOptions;\n\nexport interface NavigateTreeResult {\n\teditorText?: string;\n\tcancelled: boolean;\n\taborted?: boolean;\n\tsummaryEntry?: BranchSummaryEntry;\n}\n\ninterface TreeNavigationSettings {\n\tcustomInstructions?: string;\n\treplaceInstructions?: boolean;\n\tlabel?: string;\n}\n\ninterface TreeExtensionPreparationResult extends TreeNavigationSettings {\n\tcancelled: boolean;\n\textensionSummary?: SessionBeforeTreeSummary;\n\tfromExtension: boolean;\n}\n\ninterface TreeSummaryResolution {\n\tcancelled: boolean;\n\taborted?: boolean;\n\tsummaryText?: string;\n\tsummaryDetails?: unknown;\n\tfromExtension: boolean;\n}\n\ninterface TreeTargetResolution {\n\tnewLeafId: string | null;\n\teditorText?: string;\n}\n\nexport interface ForkableUserMessage {\n\tentryId: string;\n\ttext: string;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** Standard thinking levels */\nconst THINKING_LEVELS: ThinkingLevel[] = [\"off\", \"minimal\", \"low\", \"medium\", \"high\"];\nconst DEFAULT_STRUCTURED_RESPONSE_TOOL_NAME = \"structured_output\";\nconst DEFAULT_STRUCTURED_RESPONSE_CORRECTIONS = 2;\nconst DEFAULT_STOP_HOOK_CONTINUATION_LIMIT = 8;\nconst STOP_HOOK_PROGRESS_REGRESSION_TOLERANCE = 3;\nconst STALE_EXTENSION_CONTEXT_MESSAGE =\n\t\"This extension ctx is stale after session replacement or reload. Do not use a captured extension API or command ctx after ctx.newSession(), ctx.fork(), ctx.switchSession(), or ctx.reload(). For newSession, fork, and switchSession, move post-replacement work into withSession and use the ctx passed to withSession. For reload, do not use the old ctx after await ctx.reload().\";\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nconst CLAUDE_TOOL_NAMES: Readonly<Record<string, string>> = {\n\tread: \"Read\",\n\tbash: \"Bash\",\n\tedit: \"Edit\",\n\twrite: \"Write\",\n\twebsearch: \"WebSearch\",\n\tgrep: \"Grep\",\n\tfind: \"Glob\",\n\tls: \"Glob\",\n};\nfunction claudeToolName(name: string): string {\n\treturn CLAUDE_TOOL_NAMES[name] ?? name;\n}\n\nexport type HookPathFlavor = \"posix\" | \"windows\";\n\nfunction hookPathFlavor(backend: ToolBackendInfo): HookPathFlavor {\n\tif (backend.type === \"remote\" && backend.configured) return backend.workspace.pathFlavor;\n\tif (backend.type === \"remote\") return /^[A-Za-z]:[\\\\/]/.test(backend.cwd) ? \"windows\" : \"posix\";\n\treturn process.platform === \"win32\" ? \"windows\" : \"posix\";\n}\n\nfunction hookPathHome(backend: ToolBackendInfo, flavor: HookPathFlavor): string | null {\n\treturn backend.type === \"local\" && flavor === \"posix\" ? homedir() : null;\n}\n\nfunction isClaudeFileTool(name: string): boolean {\n\treturn name === \"read\" || name === \"edit\" || name === \"write\";\n}\n\nexport function classifyStopFailure(errorMessage: string): string {\n\tconst normalized = errorMessage.toLowerCase();\n\tif (/rate[\\s_-]*limit|too many requests|\\b429\\b/.test(normalized)) return \"rate_limit\";\n\tif (/overload|capacity|\\b529\\b/.test(normalized)) return \"overloaded\";\n\tif (/oauth.*org|organization.*oauth/.test(normalized)) return \"oauth_org_not_allowed\";\n\tif (/billing|credit|payment|quota.*(?:fund|spend)/.test(normalized)) return \"billing_error\";\n\tif (/model.*not[\\s_-]*found|unknown model|\\b404\\b/.test(normalized)) return \"model_not_found\";\n\tif (/max(?:imum)?[\\s_-]*output.*token|output.*token.*limit/.test(normalized)) return \"max_output_tokens\";\n\tif (/auth|unauthorized|forbidden|invalid.*(?:api[\\s_-]*)?key|\\b401\\b|\\b403\\b/.test(normalized))\n\t\treturn \"authentication_failed\";\n\tif (/invalid.*request|bad request|context.*(?:length|window)|\\b400\\b|\\b422\\b/.test(normalized))\n\t\treturn \"invalid_request\";\n\tif (/server|internal|gateway|network|connection|timeout|\\b5\\d\\d\\b/.test(normalized)) return \"server_error\";\n\treturn \"unknown\";\n}\nfunction resolveClaudeHookFilePath(source: string, cwd: string, flavor: HookPathFlavor, home: string | null): string {\n\tconst isTildePath = source === \"~\" || source.startsWith(\"~/\") || source.startsWith(\"~\\\\\");\n\tif (isTildePath) {\n\t\tif (flavor !== \"posix\" || home === null) {\n\t\t\tthrow new Error(\"cannot resolve a ~ path without a known POSIX backend home\");\n\t\t}\n\t\treturn posix.resolve(home, source === \"~\" ? \".\" : source.slice(2));\n\t}\n\tif (source.startsWith(\"~\")) throw new Error(\"~user paths are unsupported in hook file paths\");\n\tconst api = flavor === \"windows\" ? win32 : posix;\n\treturn api.resolve(cwd, source);\n}\n\n/** Adapt grip's file-tool arguments to Claude's absolute, backend-lexical file_path shape. */\nexport function adaptFileToolInputForClaudeHook(\n\ttoolName: string,\n\tinput: JsonObject,\n\tcwd: string,\n\tflavor: HookPathFlavor,\n\thome: string | null = homedir(),\n): JsonObject {\n\tif (!isClaudeFileTool(toolName)) return input;\n\tconst source = typeof input.path === \"string\" ? input.path : input.file_path;\n\tif (typeof source !== \"string\") return input;\n\tconst adapted: JsonObject = { ...input, file_path: resolveClaudeHookFilePath(source, cwd, flavor, home) };\n\tdelete adapted.path;\n\treturn adapted;\n}\n\n/** Reverse a Claude file_path replacement to grip's path field before schema validation. */\nexport function adaptFileToolUpdatedInputFromClaudeHook(\n\ttoolName: string,\n\tinput: JsonObject,\n\tcwd: string,\n\tflavor: HookPathFlavor,\n\thome: string | null = homedir(),\n): JsonObject {\n\tif (!isClaudeFileTool(toolName)) return input;\n\tif (typeof input.file_path !== \"string\" || \"path\" in input) {\n\t\tthrow new Error(\"file-tool replacement must contain a string file_path and must not contain path\");\n\t}\n\tconst adapted: JsonObject = {\n\t\t...input,\n\t\tpath: resolveClaudeHookFilePath(input.file_path, cwd, flavor, home),\n\t};\n\tdelete adapted.file_path;\n\treturn adapted;\n}\n\nfunction normalizeHookToolOutput(value: JsonValue): HookToolOutput | undefined {\n\tif (typeof value === \"string\") return { content: [{ type: \"text\", text: value }] };\n\tif (!isRecord(value) || !Array.isArray(value.content)) return undefined;\n\tconst content: (TextContent | ImageContent)[] = [];\n\tfor (const part of value.content) {\n\t\tif (!isRecord(part) || typeof part.type !== \"string\") return undefined;\n\t\tif (part.type === \"text\" && typeof part.text === \"string\") content.push({ type: \"text\", text: part.text });\n\t\telse if (part.type === \"image\" && typeof part.data === \"string\" && typeof part.mimeType === \"string\")\n\t\t\tcontent.push({ type: \"image\", data: part.data, mimeType: part.mimeType });\n\t\telse return undefined;\n\t}\n\treturn { content, ...(value.details !== undefined ? { details: value.details as JsonValue } : {}) };\n}\n\nfunction toJsonValue(value: unknown): JsonValue {\n\tif (value === undefined) return null;\n\ttry {\n\t\treturn JSON.parse(JSON.stringify(value)) as JsonValue;\n\t} catch {\n\t\treturn String(value);\n\t}\n}\n\nfunction hookExecutionSubject(input: HookInput): string | undefined {\n\tswitch (input.hook_event_name) {\n\t\tcase \"PreToolUse\":\n\t\tcase \"PostToolUse\":\n\t\tcase \"PostToolUseFailure\":\n\t\t\treturn input.tool_name;\n\t\tcase \"PreCompact\":\n\t\tcase \"PostCompact\":\n\t\t\treturn input.trigger;\n\t\tcase \"SessionStart\":\n\t\t\treturn input.source;\n\t\tcase \"UserPromptSubmit\":\n\t\tcase \"Stop\":\n\t\tcase \"StopFailure\":\n\t\tcase \"SessionEnd\":\n\t\t\treturn undefined;\n\t}\n}\n\nfunction hookExecutionLabel(result: HookExecutionResult): string {\n\tconst handler = result.hook.handler;\n\tif (handler.statusMessage) return handler.statusMessage;\n\tif (handler.type === \"command\") return [handler.command, ...(handler.args ?? [])].join(\" \");\n\tif (handler.type === \"http\") {\n\t\ttry {\n\t\t\tconst url = new URL(handler.url);\n\t\t\treturn `${url.origin}${url.pathname}`;\n\t\t} catch {\n\t\t\treturn handler.url.split(/[?#]/, 1)[0];\n\t\t}\n\t}\n\treturn handler.type;\n}\n\nfunction hookResultContainsStructuredReason(input: HookInput, result: HookAggregateResult, reason: string): boolean {\n\treturn result.results.some((execution) => {\n\t\tif (execution.status !== \"completed\" || execution.output.kind !== \"json\") return false;\n\t\tconst output = execution.output.value;\n\t\tif (input.hook_event_name !== \"SessionEnd\" && output.continue === false && output.stopReason === reason) {\n\t\t\treturn true;\n\t\t}\n\t\tif (output.reason === reason && output.decision === \"block\") {\n\t\t\tswitch (input.hook_event_name) {\n\t\t\t\tcase \"UserPromptSubmit\":\n\t\t\t\tcase \"PostToolUse\":\n\t\t\t\tcase \"PostToolUseFailure\":\n\t\t\t\tcase \"Stop\":\n\t\t\t\tcase \"PreCompact\":\n\t\t\t\t\treturn true;\n\t\t\t\tcase \"SessionStart\":\n\t\t\t\tcase \"PreToolUse\":\n\t\t\t\tcase \"StopFailure\":\n\t\t\t\tcase \"PostCompact\":\n\t\t\t\tcase \"SessionEnd\":\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tconst specific = output.hookSpecificOutput;\n\t\treturn (\n\t\t\tinput.hook_event_name === \"PreToolUse\" &&\n\t\t\tisRecord(specific) &&\n\t\t\tspecific.hookEventName === input.hook_event_name &&\n\t\t\tspecific.permissionDecision === \"deny\" &&\n\t\t\tspecific.permissionDecisionReason === reason\n\t\t);\n\t});\n}\n\nfunction createHookExecutionNotice(input: HookInput, result: HookAggregateResult): HookExecutionNotice {\n\tconst returnedPrompts = [...result.additionalContext];\n\tif (\n\t\tresult.blocked &&\n\t\tresult.reason &&\n\t\t!returnedPrompts.includes(result.reason) &&\n\t\thookResultContainsStructuredReason(input, result, result.reason)\n\t) {\n\t\treturnedPrompts.push(result.reason);\n\t}\n\treturn {\n\t\tevent: input.hook_event_name,\n\t\tsubject: hookExecutionSubject(input),\n\t\tcalls: result.results.map((execution) => ({\n\t\t\ttype: execution.hook.handler.type,\n\t\t\tlabel: hookExecutionLabel(execution),\n\t\t\tsource: execution.hook.source,\n\t\t\tstatus: execution.status,\n\t\t\texitCode: execution.exitCode,\n\t\t\tdurationMs: execution.durationMs,\n\t\t})),\n\t\treturnedPrompts,\n\t};\n}\n\nfunction getAssistantText(message: AssistantMessage): string {\n\treturn message.content\n\t\t.filter((block) => block.type === \"text\")\n\t\t.map((block) => block.text)\n\t\t.join(\"\\n\");\n}\n\nfunction escapeXmlAttribute(value: string): string {\n\treturn value.replace(/&/g, \"&\").replace(/\"/g, \""\").replace(/</g, \"<\").replace(/>/g, \">\");\n}\n\nfunction normalizeSessionToolNames(value: unknown): string[] {\n\tconst raw = Array.isArray(value) ? value : [value];\n\tconst names: string[] = [];\n\tconst seenNames = new Set<string>();\n\tfor (const entry of raw) {\n\t\tif (typeof entry !== \"string\") continue;\n\t\tconst trimmed = entry.trim();\n\t\tif (trimmed.length > 0 && !seenNames.has(trimmed)) {\n\t\t\tseenNames.add(trimmed);\n\t\t\tnames.push(trimmed);\n\t\t}\n\t}\n\treturn names;\n}\n\nfunction getToolNameArgs(args: SessionToolNameArgs | undefined): string[] {\n\tconst names = normalizeSessionToolNames(args?.name);\n\treturn names.length > 0 ? names : [\"...\"];\n}\n\nfunction formatSessionToolLifecycleCall(label: string, toolNames: string[]): string {\n\treturn (\n\t\ttheme.fg(\"customMessageLabel\", `\\x1b[1m[tool]\\x1b[22m `) +\n\t\ttheme.fg(\"toolTitle\", label) +\n\t\t\" \" +\n\t\ttheme.fg(\"customMessageText\", toolNames.join(\", \"))\n\t);\n}\n\ntype SessionToolLifecycleAction = \"loaded\" | \"unloaded\";\n\nfunction formatSessionToolLifecycleResult(\n\taction: SessionToolLifecycleAction,\n\tsucceeded: string[],\n\tnotFound: string[],\n): string {\n\tconst prefix = theme.fg(\"customMessageLabel\", `\\x1b[1m[tool]\\x1b[22m `);\n\tconst parts: string[] = [];\n\tif (succeeded.length > 0) {\n\t\tparts.push(`${theme.fg(\"success\", action)} ${theme.fg(\"customMessageText\", succeeded.join(\", \"))}`);\n\t}\n\tif (notFound.length > 0) {\n\t\tparts.push(`${theme.fg(\"error\", \"not found\")} ${theme.fg(\"customMessageText\", notFound.join(\", \"))}`);\n\t}\n\tif (parts.length === 0) {\n\t\tparts.push(theme.fg(\"error\", \"not found\"));\n\t}\n\treturn prefix + parts.join(\" \");\n}\n\nfunction extractJsonCandidates(text: string): unknown[] {\n\tconst candidates: unknown[] = [];\n\tconst trimmed = text.trim();\n\tif (trimmed) {\n\t\ttry {\n\t\t\tcandidates.push(JSON.parse(trimmed));\n\t\t} catch {\n\t\t\t// Try fenced JSON blocks below.\n\t\t}\n\t}\n\n\tconst fencePattern = /```(?:json)?\\s*([\\s\\S]*?)```/gi;\n\tlet match = fencePattern.exec(text);\n\twhile (match) {\n\t\ttry {\n\t\t\tcandidates.push(JSON.parse(match[1].trim()));\n\t\t} catch {\n\t\t\t// Ignore invalid fenced blocks.\n\t\t}\n\t\tmatch = fencePattern.exec(text);\n\t}\n\treturn candidates;\n}\n\n// ============================================================================\n// AgentSession Class\n// ============================================================================\n\nexport class AgentSession {\n\treadonly agent: Agent;\n\treadonly session: Session;\n\treadonly settingsManager: SettingsManager;\n\n\tprivate _scopedModels: ScopedModel[];\n\n\t// Event subscription state\n\tprivate _unsubscribeAgent?: () => void;\n\tprivate _eventListeners: AgentSessionEventListener[] = [];\n\tprivate _disposePromise?: Promise<void>;\n\tprivate _shutdownPreparationPromise?: Promise<void>;\n\tprivate _disposed = false;\n\n\t/** Tracks pending steering messages for UI display. Removed when delivered. */\n\tprivate _steeringMessages: string[] = [];\n\t/** Tracks pending follow-up messages for UI display. Removed when delivered. */\n\tprivate _followUpMessages: string[] = [];\n\t/** Messages queued to be included with the next user prompt as context (\"asides\"). */\n\tprivate _pendingNextTurnMessages: CustomMessage[] = [];\n\n\t// Compaction state\n\tprivate _compactionInFlight?: Promise<unknown>;\n\tprivate _compactionAbortController: AbortController | undefined = undefined;\n\tprivate _autoCompactionAbortController: AbortController | undefined = undefined;\n\tprivate _extensionCompactionQueue: CompactOptions[] = [];\n\tprivate _extensionCompactionTimer: ReturnType<typeof setTimeout> | undefined = undefined;\n\tprivate _extensionCompactionActivity?: SessionActivityToken;\n\tprivate _extensionCompactionRunning = false;\n\tprivate _overflowRecoveryAttempted = false;\n\n\t// Branch summarization state\n\tprivate _branchSummaryAbortController: AbortController | undefined = undefined;\n\n\t// Retry state\n\tprivate _retryAbortController: AbortController | undefined = undefined;\n\tprivate _retryAttempt = 0;\n\n\t// Bash execution state\n\tprivate readonly _bashAbortControllers = new Set<AbortController>();\n\tprivate _pendingBashMessages: BashExecutionMessage[] = [];\n\n\t// Extension system\n\tprivate _extensionRunner!: ExtensionRunner;\n\tprivate readonly _extensionLspOwnership = new WeakMap<ExtensionRunner, ExtensionLspOwnership>();\n\tprivate _turnIndex = 0;\n\n\tprivate _resourceLoader: ResourceLoader;\n\tprivate _customTools: ToolDefinition[];\n\tprivate _baseToolDefinitions: Map<string, ToolDefinition> = new Map();\n\tprivate _workspaceToolHost?: WorkspaceToolHost;\n\tprivate _retiredWorkspaceToolHosts: WorkspaceToolHost[] = [];\n\tprivate _cwd: string;\n\tprivate _extensionRunnerRef?: { current?: ExtensionRunner };\n\tprivate _initialActiveToolNames?: string[];\n\tprivate readonly _enableLspTools?: boolean;\n\tprivate _allowedToolNames?: Set<string>;\n\tprivate _excludedToolNames: Set<string>;\n\tprivate _baseToolsOverride?: Record<string, AgentTool>;\n\tprivate _toolOperations?: ToolOperations;\n\tprivate _runtimeToolOperations?: LocalToolOperations;\n\tprivate _subagentRunner?: SubagentRunner;\n\tprivate _subagentRunRegistry?: SubagentRunRegistry;\n\tprivate _subagentConfigRegistry?: SubagentConfigRegistry;\n\tprivate _trustProjectAgents: boolean;\n\tprivate _localResourceToolOperations?: ToolOperations;\n\tprivate _sandboxToolOperations?: DeferredRemoteToolOperations;\n\tprivate _preSandboxToolOperations?: ToolOperations;\n\tprivate _lspRuntimeState?: LspRuntimeState;\n\tprivate _lspConfiguration: ResolvedLspConfiguration;\n\tprivate readonly _lspConnectionFactories: LspConnectionFactoryRegistry;\n\tprivate readonly _resolveLspConfiguration?: () => Promise<LoadLspConfigurationResult>;\n\tprivate readonly _onLspConfigurationDiagnostics?: (diagnostics: readonly LspConfigurationSourceDiagnostic[]) => void;\n\tprivate _runtimeLifecycleQueue: Promise<void> = Promise.resolve();\n\tprivate _sandboxTransitionQueue: Promise<void> = Promise.resolve();\n\tprivate readonly _runtimeLifecycleContext = new AsyncLocalStorage<RuntimeLifecycleOperationToken>();\n\tprivate readonly _activities = new Set<SessionActivityToken>();\n\tprivate _activityGeneration = 0;\n\tprivate _activeCycle = false;\n\tprivate _settlementScheduled = false;\n\tprivate _settlementRunning = false;\n\tprivate readonly _idleWaiters = new Set<IdleWaiter>();\n\tprivate readonly _barrierOwnerContext = new AsyncLocalStorage<BarrierOwnerScope>();\n\tprivate _sessionStartEvent: SessionStartEvent;\n\tprivate _extensionUIContext?: ExtensionUIContext;\n\tprivate _extensionCommandContextActions?: ExtensionCommandContextActions;\n\tprivate _extensionAbortHandler?: () => void;\n\tprivate _extensionShutdownHandler?: ShutdownHandler;\n\tprivate _extensionErrorListener?: ExtensionErrorListener;\n\tprivate _extensionErrorUnsubscriber?: () => void;\n\tprivate _loadedHooks?: LoadedHooks;\n\tprivate readonly _resolveHooks?: () => Promise<LoadedHooks | undefined>;\n\tprivate _hooksEnabled = true;\n\tprivate readonly _onHookDiagnostic?: (diagnostic: HookDiagnostic) => void;\n\tprivate readonly _hookExecutionListeners = new Set<HookExecutionListener>();\n\tprivate readonly _hookExecutionActivityListeners = new Set<HookExecutionActivityListener>();\n\tprivate readonly _hookRunOptions: Pick<HookRunOptions, \"allowedHttpHookUrls\" | \"httpHookAllowedEnvVars\">;\n\tprivate readonly _hookAbortController = new AbortController();\n\tprivate _hookSessionStartPromise?: Promise<CustomMessage | undefined>;\n\tprivate _hookSessionStartTerminationReason?: string;\n\tprivate _pendingHookSessionStartContext?: CustomMessage;\n\tprivate _hookSessionEnded = false;\n\tprivate _activePromptId: string | undefined;\n\tprivate _stopHookContinuations = 0;\n\tprivate _stopHookProgress: StopContinuationProgressState | undefined;\n\tprivate readonly _queuedPromptIds = new Map<string, string[]>();\n\tprivate readonly _toolStartedAt = new Map<string, number>();\n\tprivate readonly _preToolHookContext = new Map<string, string[]>();\n\tprivate readonly _compressionDetector: CompressionDetector;\n\tprivate _contextCompressionEnabledOverride?: boolean;\n\tprivate _compressionDetectionEnabled = process.env.GRIP_CONTEXT_COMPRESSION_DETECTION === \"1\";\n\tprivate _agentRunEnding = false;\n\tprivate _waitingForDetectorAtRequest = false;\n\tprivate _pendingContextCompression?: PendingContextCompression;\n\tprivate _stateCompressionApplied = false;\n\n\t// Model registry for API key resolution\n\tprivate _modelRegistry: ModelRegistry;\n\n\t// Tool registry for extension getTools/setTools\n\tprivate _toolRegistry: Map<string, AgentTool> = new Map();\n\tprivate _toolDefinitions: Map<string, ToolDefinitionEntry> = new Map();\n\tprivate _sessionTools: Map<string, SessionToolEntry> = new Map();\n\tprivate _sessionSkills: Map<string, Skill> = new Map();\n\tprivate _sessionRules: Map<string, Rule> = new Map();\n\tprivate _toolPromptSnippets: Map<string, string> = new Map();\n\tprivate _toolPromptGuidelines: Map<string, string[]> = new Map();\n\n\t// Base system prompt (without extension appends) - used to apply fresh appends each turn\n\tprivate _baseSystemPrompt = \"\";\n\tprivate _baseSystemPromptOptions!: BuildSystemPromptOptions;\n\tprivate _systemPromptOverride?: string;\n\n\tconstructor(config: AgentSessionConfig) {\n\t\tthis.agent = config.agent;\n\t\tthis.session = config.session;\n\t\tthis.settingsManager = config.settingsManager;\n\t\tthis._scopedModels = config.scopedModels ?? [];\n\t\tthis._resourceLoader = config.resourceLoader;\n\t\tthis._customTools = config.customTools ?? [];\n\t\tthis._cwd = config.cwd;\n\t\tthis._modelRegistry = config.modelRegistry;\n\t\tthis._compressionDetector = new CompressionDetector(\n\t\t\tthis.settingsManager,\n\t\t\tthis._modelRegistry,\n\t\t\t(active) => this._emit({ type: \"compression_detection_activity\", active }),\n\t\t\t(verdict) => {\n\t\t\t\tthis._emit({ type: \"compression_detection_result\", verdict });\n\t\t\t\tif (\n\t\t\t\t\tverdict === \"COMPRESS\" &&\n\t\t\t\t\t!this._disposed &&\n\t\t\t\t\t!this._agentRunEnding &&\n\t\t\t\t\t!this._waitingForDetectorAtRequest &&\n\t\t\t\t\tthis.agent.state.isStreaming &&\n\t\t\t\t\tthis.compressionDetectionEnabled\n\t\t\t\t) {\n\t\t\t\t\tthis.agent.steer(this._createCompressionAdvisory());\n\t\t\t\t}\n\t\t\t},\n\t\t\tundefined,\n\t\t\t{\n\t\t\t\tgetBranch: () => getCompressibleEntries(this.session, \"\"),\n\t\t\t\tsession: this.session,\n\t\t\t\tgetCostInfo: () => {\n\t\t\t\t\tconst model = this.model;\n\t\t\t\t\treturn model\n\t\t\t\t\t\t? `Primary model catalog rates ($/million tokens): input ${model.cost.input}, cacheRead ${model.cost.cacheRead}, cacheWrite ${model.cost.cacheWrite}, output ${model.cost.output}. Subscription: ${this._modelRegistry.isUsingOAuth(model)}. Actual cache hit rate and future request count are unknown.`\n\t\t\t\t\t\t: \"Primary model pricing is unavailable.\";\n\t\t\t\t},\n\t\t\t\tgetRangeError: (start, end) => getSuggestedCompressionRangeError(this.session, start, end),\n\t\t\t\trecordEvent: (event) => appendCompressionDetectionEvent(this.session, event),\n\t\t\t},\n\t\t);\n\t\tthis._extensionRunnerRef = config.extensionRunnerRef;\n\t\tthis._initialActiveToolNames = config.initialActiveToolNames;\n\t\tthis._enableLspTools = config.enableLspTools;\n\t\tthis._excludedToolNames = new Set(config.excludedToolNames ?? []);\n\t\tif (!config.subagentRunner) {\n\t\t\tthis._excludedToolNames.add(\"subagent\");\n\t\t\tthis._excludedToolNames.add(\"subagent_runs\");\n\t\t\tthis._excludedToolNames.add(\"create_subagent\");\n\t\t}\n\t\tthis._allowedToolNames = config.allowedToolNames ? new Set(config.allowedToolNames) : undefined;\n\t\tthis._baseToolsOverride = config.baseToolsOverride;\n\t\tthis._toolOperations = config.toolOperations;\n\t\tthis._subagentRunner = config.subagentRunner;\n\t\tthis._subagentRunRegistry = config.subagentRunRegistry;\n\t\tthis._subagentConfigRegistry = config.subagentConfigRegistry;\n\t\tthis._trustProjectAgents = config.trustProjectAgents === true;\n\t\tthis._lspConfiguration = structuredClone(config.lspConfiguration ?? { enabled: false, servers: [] });\n\t\tthis._lspConnectionFactories = config.lspConnectionFactories ?? {};\n\t\tthis._resolveLspConfiguration = config.resolveLspConfiguration;\n\t\tthis._onLspConfigurationDiagnostics = config.onLspConfigurationDiagnostics;\n\t\tthis._sessionStartEvent = config.sessionStartEvent ?? { type: \"session_start\", reason: \"startup\" };\n\t\tthis._loadedHooks = config.loadedHooks;\n\t\tthis._resolveHooks = config.resolveHooks;\n\t\tthis._onHookDiagnostic = config.onHookDiagnostic;\n\t\tthis._hookRunOptions = config.hookRunOptions ?? {};\n\n\t\t// Always subscribe to agent events for internal handling\n\t\t// (session persistence, extensions, auto-compaction, retry logic)\n\t\tthis._unsubscribeAgent = this.agent.subscribe(this._handleAgentEvent);\n\t\tthis._installAgentToolHooks();\n\t\tthis._installAgentTurnPreparation();\n\t\tthis._installContextMetadata();\n\n\t\tthis._buildRuntime({\n\t\t\tactiveToolNames: this._initialActiveToolNames\n\t\t\t\t? this._withCurrentDefaultTools(this._initialActiveToolNames)\n\t\t\t\t: undefined,\n\t\t\tincludeAllExtensionTools: true,\n\t\t});\n\t}\n\n\t/** Model registry for API key resolution and model discovery */\n\tget modelRegistry(): ModelRegistry {\n\t\treturn this._modelRegistry;\n\t}\n\n\tgetToolOperations(): ToolOperations {\n\t\tif (this._toolOperations) return this._toolOperations;\n\t\tconst shellPath = this.settingsManager.getShellPath();\n\t\tthis._runtimeToolOperations ??= new LocalToolOperations(this.session.getCwd(), { shellPath });\n\t\tthis._runtimeToolOperations.setShellPath(shellPath);\n\t\treturn this._runtimeToolOperations;\n\t}\n\n\t/** Snapshot of the sole AgentSession-owned LSP runtime and its configured/instantiated servers. */\n\tgetLspStatus(): LspSessionStatus {\n\t\tconst daemonOwned = this.getToolBackendInfo().type === \"remote\";\n\t\tconst remoteStatus = daemonOwned ? this.getToolOperations().getRemoteLspStatus?.() : undefined;\n\t\treturn {\n\t\t\towner: daemonOwned ? \"daemon\" : \"agent-session\",\n\t\t\tenabled: daemonOwned ? (remoteStatus?.enabled ?? false) : this._lspConfiguration.enabled,\n\t\t\tconfiguration: daemonOwned\n\t\t\t\t? { enabled: remoteStatus?.enabled ?? false, servers: [] }\n\t\t\t\t: structuredClone(this._lspConfiguration),\n\t\t\tservers: daemonOwned ? (remoteStatus?.servers ?? []) : (this._lspRuntimeState?.manager.getStatus() ?? []),\n\t\t};\n\t}\n\n\t/** Validate and replace the AgentSession-owned LSP configuration. Relative paths resolve from the session cwd. */\n\tasync configureLsp(configuration: LspConfigurationLayer): Promise<ResolvedLspConfiguration> {\n\t\tif (this._disposed) throw new Error(\"Cannot configure LSP after AgentSession disposal\");\n\t\tif (this.getToolBackendInfo().type === \"remote\") {\n\t\t\tthrow new Error(\"LSP for a daemon workspace is operator-owned and cannot be configured by AgentSession\");\n\t\t}\n\t\tconst parsed = parseLspConfiguration(configuration);\n\t\tif (!parsed.configuration) {\n\t\t\tthrow new Error(\n\t\t\t\t`Invalid LSP configuration: ${parsed.diagnostics.map((diagnostic) => `${diagnostic.path}: ${diagnostic.message}`).join(\"; \")}`,\n\t\t\t);\n\t\t}\n\t\tconst resolvedLayer = resolveLspConfigurationLayerPaths(parsed.configuration, this._cwd);\n\t\tconst resolvedValidation = parseLspConfiguration(resolvedLayer);\n\t\tif (!resolvedValidation.configuration) {\n\t\t\tthrow new Error(\n\t\t\t\t`Invalid resolved LSP configuration: ${resolvedValidation.diagnostics.map((diagnostic) => `${diagnostic.path}: ${diagnostic.message}`).join(\"; \")}`,\n\t\t\t);\n\t\t}\n\t\tconst resolved = resolveLspConfiguration([resolvedValidation.configuration]);\n\t\tawait this._enqueueRuntimeLifecycle(() => this._replaceLspRuntime(resolved));\n\t\treturn structuredClone(resolved);\n\t}\n\n\tgetToolBackendInfo(): ToolBackendInfo {\n\t\treturn this.getToolOperations().getBackendInfo?.() ?? { type: \"local\", cwd: this._cwd };\n\t}\n\n\tsupportsDirectoryChange(): boolean {\n\t\treturn this._toolOperations === undefined && this.getToolBackendInfo().type === \"local\";\n\t}\n\n\tprivate _getLocalResourceToolOperations(shellPath?: string): ToolOperations {\n\t\tthis._localResourceToolOperations ??= new LocalToolOperations(this._cwd, { shellPath });\n\t\treturn this._localResourceToolOperations;\n\t}\n\n\tprivate _getReadOperationsForPath(\n\t\tabsolutePath: string,\n\t\tshellPath?: string,\n\t): ReadToolOperationsSelection | undefined {\n\t\tconst isSameOrChild = (target: string, root: string): boolean => {\n\t\t\tconst normalizedRoot = resolvePath(root);\n\t\t\tif (target === normalizedRoot) return true;\n\t\t\tconst prefix = normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`;\n\t\t\treturn target.startsWith(prefix);\n\t\t};\n\t\tconst resources = [\n\t\t\t...this.getRegisteredSkills().map((skill) => ({\n\t\t\t\tpath: skill.filePath,\n\t\t\t\tbaseDir: skill.baseDir,\n\t\t\t\tsourceInfo: skill.sourceInfo,\n\t\t\t})),\n\t\t\t...this.getRegisteredRules().map((rule) => ({\n\t\t\t\tpath: rule.filePath,\n\t\t\t\tbaseDir: rule.baseDir,\n\t\t\t\tsourceInfo: rule.sourceInfo,\n\t\t\t})),\n\t\t\t...this._resourceLoader.getPrompts().prompts.map((prompt) => ({\n\t\t\t\tpath: prompt.filePath,\n\t\t\t\tbaseDir: dirname(prompt.filePath),\n\t\t\t\tsourceInfo: prompt.sourceInfo,\n\t\t\t})),\n\t\t];\n\t\tlet canonicalTarget: string;\n\t\ttry {\n\t\t\tcanonicalTarget = realpathSync(absolutePath);\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t\tfor (const resource of resources) {\n\t\t\tif (getSourceBackend(resource.sourceInfo) !== \"local\") continue;\n\t\t\ttry {\n\t\t\t\tconst resourcePath = realpathSync(resource.path);\n\t\t\t\tconst baseDir = realpathSync(resource.baseDir);\n\t\t\t\tif (canonicalTarget === resourcePath || isSameOrChild(canonicalTarget, baseDir)) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tkind: \"selection\",\n\t\t\t\t\t\toperations: this._getLocalResourceToolOperations(shellPath),\n\t\t\t\t\t\tpath: canonicalTarget,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t} catch {}\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tasync configureRemoteSandbox(options: ConfigureRemoteSandboxOptions): Promise<ToolBackendInfo> {\n\t\treturn this._runActivity(\"remote backend configuration\", () =>\n\t\t\tthis._enqueueSandboxTransition(async () => {\n\t\t\t\tif (!(this._toolOperations instanceof DeferredRemoteToolOperations) || this._sandboxToolOperations) {\n\t\t\t\t\tthrow new Error(\"Remote backend can only be configured when grip is started with --remote-deferred\");\n\t\t\t\t}\n\t\t\t\tconst currentInfo = this._toolOperations.getBackendInfo();\n\t\t\t\tif (currentInfo.type !== \"remote\" || currentInfo.configured) {\n\t\t\t\t\tthrow new Error(\"Remote backend is already configured\");\n\t\t\t\t}\n\t\t\t\treturn this._activateSessionRemoteOperations(currentInfo.cwd, (operations) =>\n\t\t\t\t\toperations.configureRemote(options.url, { token: options.token }),\n\t\t\t\t);\n\t\t\t}),\n\t\t);\n\t}\n\n\tasync activateSandboxDaemon(options: ActivateSandboxDaemonOptions): Promise<ToolBackendInfo> {\n\t\treturn this._runActivity(\"sandbox daemon activation\", () =>\n\t\t\tthis._enqueueSandboxTransition(() =>\n\t\t\t\tthis._activateSessionRemoteOperations(options.expectedCwd, (operations) =>\n\t\t\t\t\toperations.configureRemote(options.url, { token: options.token, expectedCwd: options.expectedCwd }),\n\t\t\t\t),\n\t\t\t),\n\t\t);\n\t}\n\n\tprivate async _activateSessionRemoteOperations(\n\t\texpectedCwd: string,\n\t\tconfigure: (operations: DeferredRemoteToolOperations) => Promise<ToolBackendInfo>,\n\t): Promise<ToolBackendInfo> {\n\t\tif (this._sandboxToolOperations) throw new Error(\"A session sandbox backend is already active\");\n\t\tconst previousToolOperations = this._toolOperations;\n\t\tconst operations = new DeferredRemoteToolOperations(expectedCwd, () => {\n\t\t\tif (this._sandboxToolOperations !== operations || this._toolOperations !== operations) return;\n\t\t\tthis.abortRetry();\n\t\t\tthis.agent.abort();\n\t\t});\n\t\ttry {\n\t\t\tawait configure(operations);\n\t\t} catch (error) {\n\t\t\tawait operations.dispose();\n\t\t\tthrow error;\n\t\t}\n\t\tthis._preSandboxToolOperations = previousToolOperations;\n\t\tthis._toolOperations = operations;\n\t\tthis._sandboxToolOperations = operations;\n\t\ttry {\n\t\t\tif (operations.isDisconnected()) throw new Error(\"Sandbox connection lost during activation\");\n\t\t\tawait this.applyToolOperationsBackendChange(operations);\n\t\t} catch (error) {\n\t\t\tthis._toolOperations = previousToolOperations;\n\t\t\tthis._sandboxToolOperations = undefined;\n\t\t\tthis._preSandboxToolOperations = undefined;\n\t\t\tlet rollbackError: unknown;\n\t\t\ttry {\n\t\t\t\tawait this.applyToolOperationsBackendChange(this.getToolOperations());\n\t\t\t} catch (caught) {\n\t\t\t\trollbackError = caught;\n\t\t\t}\n\t\t\tawait operations.dispose();\n\t\t\tif (rollbackError) {\n\t\t\t\tthrow new AggregateError([error, rollbackError], \"Sandbox activation and backend rollback both failed\");\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t\treturn operations.getBackendInfo();\n\t}\n\n\tasync clearRemoteSandbox(): Promise<void> {\n\t\treturn this._runActivity(\"remote backend clearing\", () =>\n\t\t\tthis._enqueueSandboxTransition(async () => {\n\t\t\t\tconst operations = this._sandboxToolOperations;\n\t\t\t\tif (!operations || operations !== this._toolOperations) {\n\t\t\t\t\tthrow new Error(\"No session sandbox backend is active\");\n\t\t\t\t}\n\t\t\t\tconst previousToolOperations = this._preSandboxToolOperations;\n\t\t\t\tthis._toolOperations = previousToolOperations;\n\t\t\t\tthis._sandboxToolOperations = undefined;\n\t\t\t\tthis._preSandboxToolOperations = undefined;\n\t\t\t\ttry {\n\t\t\t\t\tawait this.applyToolOperationsBackendChange(this.getToolOperations());\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthis._toolOperations = operations;\n\t\t\t\t\tthis._sandboxToolOperations = operations;\n\t\t\t\t\tthis._preSandboxToolOperations = previousToolOperations;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait this.applyToolOperationsBackendChange(operations);\n\t\t\t\t\t} catch (rollbackError) {\n\t\t\t\t\t\tthrow new AggregateError([error, rollbackError], \"Sandbox clearing and backend rollback both failed\");\n\t\t\t\t\t}\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\tawait operations.dispose();\n\t\t\t}),\n\t\t);\n\t}\n\n\tprivate _enqueueSandboxTransition<T>(operation: () => Promise<T>): Promise<T> {\n\t\tconst result = this._sandboxTransitionQueue.then(operation, operation);\n\t\tthis._sandboxTransitionQueue = result.then(\n\t\t\t() => undefined,\n\t\t\t() => undefined,\n\t\t);\n\t\treturn result;\n\t}\n\n\tasync uploadFile(sourcePath: string, destinationPath: string): Promise<void> {\n\t\treturn this._runActivity(\"remote file upload\", async () => {\n\t\t\tconst operations = this.getToolOperations();\n\t\t\tif (!operations.uploadFile) throw new Error(\"Active sandbox backend does not support file upload\");\n\t\t\tawait operations.uploadFile(sourcePath, destinationPath);\n\t\t});\n\t}\n\n\tasync downloadFile(sourcePath: string, destinationPath: string): Promise<void> {\n\t\treturn this._runActivity(\"remote file download\", async () => {\n\t\t\tconst operations = this.getToolOperations();\n\t\t\tif (!operations.downloadFile) throw new Error(\"Active sandbox backend does not support file download\");\n\t\t\tawait operations.downloadFile(sourcePath, destinationPath);\n\t\t});\n\t}\n\n\tprivate async applyToolOperationsBackendChange(operations: ToolOperations): Promise<void> {\n\t\tthis._resourceLoader.setToolOperations?.(operations);\n\t\tawait this._lspRuntimeState?.manager.setToolOperations(operations);\n\t\tawait this.reload();\n\t}\n\n\tprivate async _getRequiredRequestAuth(model: Model<any>): Promise<RequiredRequestAuth> {\n\t\tconst result = await this._modelRegistry.getApiKeyAndHeaders(model);\n\t\tif (!result.ok) {\n\t\t\tif (result.error.startsWith(\"No API key found\")) {\n\t\t\t\tthrow new Error(formatNoApiKeyFoundMessage(model.provider));\n\t\t\t}\n\t\t\tthrow new Error(result.error);\n\t\t}\n\t\tif (result.apiKey) {\n\t\t\treturn { apiKey: result.apiKey, headers: result.headers };\n\t\t}\n\n\t\tconst isOAuth = this._modelRegistry.isUsingOAuth(model);\n\t\tif (isOAuth) {\n\t\t\tthrow new Error(\n\t\t\t\t`Authentication failed for \"${model.provider}\". ` +\n\t\t\t\t\t`Credentials may have expired or network is unavailable. ` +\n\t\t\t\t\t`Run '/login ${model.provider}' to re-authenticate.`,\n\t\t\t);\n\t\t}\n\t\tthrow new Error(formatNoApiKeyFoundMessage(model.provider));\n\t}\n\n\tprivate async _getCompactionRequestAuth(model: Model<any>): Promise<CompactionRequestAuth> {\n\t\tif (this.agent.streamFn === streamSimple) {\n\t\t\treturn this._getRequiredRequestAuth(model);\n\t\t}\n\n\t\tconst result = await this._modelRegistry.getApiKeyAndHeaders(model);\n\t\treturn result.ok ? { apiKey: result.apiKey, headers: result.headers } : {};\n\t}\n\n\tprivate _hookCommon(event: HookEventName, promptId = this._activePromptId) {\n\t\tconst reference = this.session.getSessionReference();\n\t\tconst hostPathApi = process.platform === \"win32\" ? win32 : posix;\n\t\treturn {\n\t\t\tsession_id: this.sessionId,\n\t\t\t// Session references are host-owned. Normalize lexically; never probe a remote filesystem.\n\t\t\ttranscript_path: reference ? (process.platform === \"win32\" ? win32 : posix).normalize(reference) : \"\",\n\t\t\tcwd: hostPathApi.normalize(this._cwd),\n\t\t\thook_event_name: event,\n\t\t\t...(promptId ? { prompt_id: promptId } : {}),\n\t\t};\n\t}\n\tprivate _emitHookExecution(notice: HookExecutionNotice): void {\n\t\tthis.session.appendCustomMessageEntry(HOOK_EXECUTION_CUSTOM_TYPE, JSON.stringify(notice), true);\n\t\tfor (const listener of [...this._hookExecutionListeners]) {\n\t\t\ttry {\n\t\t\t\tlistener(notice);\n\t\t\t} catch {\n\t\t\t\t// UI observers must not affect hook behavior.\n\t\t\t}\n\t\t}\n\t}\n\tprivate _emitHookExecutionActivity(active: boolean): void {\n\t\tfor (const listener of [...this._hookExecutionActivityListeners]) {\n\t\t\ttry {\n\t\t\t\tlistener(active);\n\t\t\t} catch {\n\t\t\t\t// UI observers must not affect hook behavior.\n\t\t\t}\n\t\t}\n\t}\n\tprivate async _runHook(input: HookInput, activeSignal?: AbortSignal): Promise<HookAggregateResult> {\n\t\tconst loadedHooks = this._loadedHooks;\n\t\tif (!this._hooksEnabled || !loadedHooks?.handlers.some((handler) => handler.event === input.hook_event_name)) {\n\t\t\treturn {\n\t\t\t\tcontinue: true,\n\t\t\t\tblocked: false,\n\t\t\t\tadditionalContext: [],\n\t\t\t\tsystemMessages: [],\n\t\t\t\tplainText: [],\n\t\t\t\tresults: [],\n\t\t\t};\n\t\t}\n\t\tconst signals = [this._hookAbortController.signal, activeSignal].filter(\n\t\t\t(signal): signal is AbortSignal => signal !== undefined,\n\t\t);\n\t\tconst signal = signals.length === 1 ? signals[0] : AbortSignal.any(signals);\n\t\tthis._emitHookExecutionActivity(true);\n\t\ttry {\n\t\t\tconst aggregate = await runHooks(loadedHooks, input, {\n\t\t\t\t...this._hookRunOptions,\n\t\t\t\tsignal,\n\t\t\t\ttoolOperations: this.getToolOperations(),\n\t\t\t});\n\t\t\tfor (const result of aggregate.results) {\n\t\t\t\tif (result.diagnostic) this._onHookDiagnostic?.(result.diagnostic);\n\t\t\t}\n\t\t\tfor (const message of aggregate.systemMessages) {\n\t\t\t\tthis._reportHookDiagnostic({\n\t\t\t\t\tlevel: \"warning\",\n\t\t\t\t\tcode: \"system-message\",\n\t\t\t\t\tevent: input.hook_event_name,\n\t\t\t\t\tmessage: `Hook systemMessage: ${message}`,\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (aggregate.results.length > 0) this._emitHookExecution(createHookExecutionNotice(input, aggregate));\n\t\t\treturn aggregate;\n\t\t} finally {\n\t\t\tthis._emitHookExecutionActivity(false);\n\t\t}\n\t}\n\n\tprivate _hasMatchingToolHook(event: ToolHookEventName, toolName: string): boolean {\n\t\tif (!this._hooksEnabled) return false;\n\t\tconst handlerMayMatchTool = (condition: string | undefined): boolean => {\n\t\t\tif (condition === undefined) return true;\n\t\t\tconst open = condition.indexOf(\"(\");\n\t\t\treturn open < 1\n\t\t\t\t? matchHookValue(condition, toolName).matches\n\t\t\t\t: condition.endsWith(\")\") && condition.slice(0, open) === toolName;\n\t\t};\n\t\treturn (\n\t\t\tthis._loadedHooks?.handlers.some(\n\t\t\t\t(handler) =>\n\t\t\t\t\thandler.event === event &&\n\t\t\t\t\tmatchHookValue(handler.matcher, toolName).matches &&\n\t\t\t\t\thandlerMayMatchTool(handler.handler.if),\n\t\t\t) ?? false\n\t\t);\n\t}\n\n\tprivate _hookContextMessage(context: readonly string[]): CustomMessage | undefined {\n\t\tif (context.length === 0) return undefined;\n\t\treturn {\n\t\t\trole: \"custom\",\n\t\t\tcustomType: \"claude-hook-context\",\n\t\t\tcontent: [{ type: \"text\", text: context.join(\"\\n\\n\") }],\n\t\t\tdisplay: false,\n\t\t\ttimestamp: Date.now(),\n\t\t};\n\t}\n\n\tprivate async _ensureHookSessionStarted(promptId: string): Promise<CustomMessage | undefined> {\n\t\tthis._hookSessionStartPromise ??= (async () => {\n\t\t\tconst reason = this._sessionStartEvent.reason;\n\t\t\tconst source =\n\t\t\t\treason === \"resume\" ? \"resume\" : reason === \"fork\" ? \"fork\" : reason === \"new\" ? \"clear\" : \"startup\";\n\t\t\tconst result = await this._runHook({\n\t\t\t\t...this._hookCommon(\"SessionStart\", promptId),\n\t\t\t\thook_event_name: \"SessionStart\",\n\t\t\t\tsource,\n\t\t\t\tmodel: this.model ? `${this.model.provider}/${this.model.id}` : undefined,\n\t\t\t\tsession_title: this.sessionName,\n\t\t\t});\n\t\t\tif (!result.continue) {\n\t\t\t\tthis._hookSessionStartTerminationReason = result.reason ?? \"Session start terminated by hook\";\n\t\t\t\tthrow new Error(this._hookSessionStartTerminationReason);\n\t\t\t}\n\t\t\tthis._pendingHookSessionStartContext = this._hookContextMessage(result.additionalContext);\n\t\t\treturn this._pendingHookSessionStartContext;\n\t\t})();\n\t\treturn await this._hookSessionStartPromise;\n\t}\n\n\tprivate _reportHookDiagnostic(diagnostic: HookDiagnostic): void {\n\t\tthis._onHookDiagnostic?.(diagnostic);\n\t}\n\n\tprivate _adaptPreToolHookInput(execution: PreToolHookExecution): PreToolInputAdaptation {\n\t\ttry {\n\t\t\tconst jsonArgs = toJsonValue(execution.args) as JsonObject;\n\t\t\tconst input = execution.adaptToolInput\n\t\t\t\t? adaptFileToolInputForClaudeHook(\n\t\t\t\t\t\texecution.toolCall.name,\n\t\t\t\t\t\tjsonArgs,\n\t\t\t\t\t\texecution.backend.cwd,\n\t\t\t\t\t\texecution.flavor,\n\t\t\t\t\t\thookPathHome(execution.backend, execution.flavor),\n\t\t\t\t\t)\n\t\t\t\t: jsonArgs;\n\t\t\treturn { ok: true, input };\n\t\t} catch (error) {\n\t\t\tconst reason = `Hook file path adaptation failed closed: ${error instanceof Error ? error.message : String(error)}`;\n\t\t\tthis._reportHookDiagnostic({ level: \"error\", code: \"policy\", event: \"PreToolUse\", message: reason });\n\t\t\treturn { ok: false, blockResult: { block: true, reason } };\n\t\t}\n\t}\n\n\tprivate _resolvePreToolHookPolicy(hookResult: HookAggregateResult): BeforeToolCallResult | undefined {\n\t\tif (!hookResult.continue) {\n\t\t\tthis.agent.abort();\n\t\t\treturn { block: true, reason: hookResult.reason ?? \"Hook terminated the active agent loop\" };\n\t\t}\n\t\tif (hookResult.permissionDecision === \"ask\" || hookResult.permissionDecision === \"defer\") {\n\t\t\tconst reason =\n\t\t\t\thookResult.permissionDecision === \"ask\"\n\t\t\t\t\t? \"Tool execution blocked: hook requested permission, but no permission engine is available\"\n\t\t\t\t\t: \"Tool execution blocked: hook deferred permission, but no permission engine is available\";\n\t\t\treturn {\n\t\t\t\tblock: true,\n\t\t\t\treason: capModelVisibleHookField([reason, ...hookResult.additionalContext].join(\"\\n\\n\")),\n\t\t\t};\n\t\t}\n\t\tif (!hookResult.blocked && hookResult.permissionDecision !== \"deny\") return undefined;\n\t\tconst feedback = [\n\t\t\t...new Set(\n\t\t\t\t[hookResult.reason, ...hookResult.additionalContext].filter(\n\t\t\t\t\t(value): value is string => value !== undefined && value.length > 0,\n\t\t\t\t),\n\t\t\t),\n\t\t];\n\t\treturn {\n\t\t\tblock: true,\n\t\t\treason: feedback.length\n\t\t\t\t? capModelVisibleHookField(feedback.map(capModelVisibleHookField).join(\"\\n\\n\"))\n\t\t\t\t: \"Tool execution denied by hook\",\n\t\t};\n\t}\n\n\tprivate _applyPreToolHookInputUpdate(\n\t\texecution: PreToolHookExecution,\n\t\thookResult: HookAggregateResult,\n\t): BeforeToolCallResult | undefined {\n\t\tif (!hookResult.updatedInput) return undefined;\n\t\tconst tool = execution.agentContext.tools?.find((candidate) => candidate.name === execution.toolCall.name);\n\t\tconst mutableArgs = execution.args;\n\t\tif (!tool || !isRecord(mutableArgs)) {\n\t\t\tthis._reportHookDiagnostic({\n\t\t\t\tlevel: \"error\",\n\t\t\t\tcode: \"unsupported-update\",\n\t\t\t\tevent: \"PreToolUse\",\n\t\t\t\tmessage: `Hook replaced input for ${execution.toolCall.name}, but validated replacement is unsupported`,\n\t\t\t});\n\t\t\treturn { block: true, reason: \"Hook tool input replacement could not be validated\" };\n\t\t}\n\t\ttry {\n\t\t\tconst replacement = execution.adaptToolInput\n\t\t\t\t? adaptFileToolUpdatedInputFromClaudeHook(\n\t\t\t\t\t\texecution.toolCall.name,\n\t\t\t\t\t\thookResult.updatedInput,\n\t\t\t\t\t\texecution.backend.cwd,\n\t\t\t\t\t\texecution.flavor,\n\t\t\t\t\t\thookPathHome(execution.backend, execution.flavor),\n\t\t\t\t\t)\n\t\t\t\t: hookResult.updatedInput;\n\t\t\tconst validated = validateToolArguments(tool, { ...execution.toolCall, arguments: replacement });\n\t\t\tif (!isRecord(validated)) throw new Error(\"replacement did not validate to an object\");\n\t\t\tfor (const key of Object.keys(mutableArgs)) delete mutableArgs[key];\n\t\t\tObject.assign(mutableArgs, validated);\n\t\t\treturn undefined;\n\t\t} catch (error) {\n\t\t\tthis._reportHookDiagnostic({\n\t\t\t\tlevel: \"error\",\n\t\t\t\tcode: \"unsupported-update\",\n\t\t\t\tevent: \"PreToolUse\",\n\t\t\t\tmessage: `Hook replacement for ${execution.toolCall.name} failed validation: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t});\n\t\t\treturn { block: true, reason: \"Hook tool input replacement failed validation\" };\n\t\t}\n\t}\n\n\tprivate async _emitExtensionToolCall(execution: PreToolHookExecution): Promise<BeforeToolCallResult | undefined> {\n\t\tconst runner = this._extensionRunner;\n\t\tif (!runner.hasHandlers(\"tool_call\")) return undefined;\n\t\ttry {\n\t\t\tconst extensionResult = await runner.emitToolCall({\n\t\t\t\ttype: \"tool_call\",\n\t\t\t\ttoolName: execution.toolCall.name,\n\t\t\t\ttoolCallId: execution.toolCall.id,\n\t\t\t\tinput: execution.args as Record<string, unknown>,\n\t\t\t});\n\t\t\tif (extensionResult?.block) {\n\t\t\t\tthis._toolStartedAt.delete(execution.toolCall.id);\n\t\t\t\tthis._preToolHookContext.delete(execution.toolCall.id);\n\t\t\t}\n\t\t\treturn extensionResult;\n\t\t} catch (error) {\n\t\t\tthis._toolStartedAt.delete(execution.toolCall.id);\n\t\t\tthis._preToolHookContext.delete(execution.toolCall.id);\n\t\t\tif (error instanceof Error) throw error;\n\t\t\tthrow new Error(`Extension failed, blocking execution: ${String(error)}`);\n\t\t}\n\t}\n\n\tprivate async _handleBeforeToolCall(\n\t\t{ toolCall, args, context: agentContext }: BeforeToolCallContext,\n\t\tsignal?: AbortSignal,\n\t): Promise<BeforeToolCallResult | undefined> {\n\t\tconst backend = this.getToolBackendInfo();\n\t\tconst flavor = hookPathFlavor(backend);\n\t\tconst normalizedToolName = claudeToolName(toolCall.name);\n\t\tconst execution: PreToolHookExecution = {\n\t\t\ttoolCall,\n\t\t\targs,\n\t\t\tagentContext,\n\t\t\tbackend,\n\t\t\tflavor,\n\t\t\tnormalizedToolName,\n\t\t\tadaptToolInput: this._hasMatchingToolHook(\"PreToolUse\", normalizedToolName),\n\t\t};\n\t\tconst adaptation = this._adaptPreToolHookInput(execution);\n\t\tif (!adaptation.ok) return adaptation.blockResult;\n\t\tconst hookResult = await this._runHook(\n\t\t\t{\n\t\t\t\t...this._hookCommon(\"PreToolUse\"),\n\t\t\t\thook_event_name: \"PreToolUse\",\n\t\t\t\ttool_name: normalizedToolName,\n\t\t\t\ttool_input: adaptation.input,\n\t\t\t\ttool_use_id: toolCall.id,\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t\tconst policyBlock = this._resolvePreToolHookPolicy(hookResult);\n\t\tif (policyBlock) return policyBlock;\n\t\tconst updateBlock = this._applyPreToolHookInputUpdate(execution, hookResult);\n\t\tif (updateBlock) return updateBlock;\n\t\tthis._toolStartedAt.set(toolCall.id, Date.now());\n\t\tif (hookResult.additionalContext.length > 0) {\n\t\t\tthis._preToolHookContext.set(toolCall.id, [...hookResult.additionalContext]);\n\t\t}\n\t\treturn this._emitExtensionToolCall(execution);\n\t}\n\n\tprivate async _applyExtensionToolResult(state: PostToolCallState): Promise<void> {\n\t\tconst runner = this._extensionRunner;\n\t\tif (!runner.hasHandlers(\"tool_result\")) return;\n\t\tconst extensionResult = await runner.emitToolResult({\n\t\t\ttype: \"tool_result\",\n\t\t\ttoolName: state.toolCall.name,\n\t\t\ttoolCallId: state.toolCall.id,\n\t\t\tinput: state.args as Record<string, unknown>,\n\t\t\tcontent: state.content,\n\t\t\tdetails: state.details,\n\t\t\tisError: state.isError,\n\t\t});\n\t\tif (!extensionResult) return;\n\t\tstate.content = extensionResult.content ?? state.content;\n\t\tstate.details = extensionResult.details;\n\t\tstate.isError = extensionResult.isError ?? state.isError;\n\t}\n\n\tprivate async _runPostToolHook(\n\t\tstate: PostToolCallState,\n\t\tstarted: number | undefined,\n\t\tsignal?: AbortSignal,\n\t): Promise<PostToolHookRunResult> {\n\t\tconst event: PostToolHookEvent = state.isError ? \"PostToolUseFailure\" : \"PostToolUse\";\n\t\tconst normalizedToolName = claudeToolName(state.toolCall.name);\n\t\tconst backend = this.getToolBackendInfo();\n\t\tconst flavor = hookPathFlavor(backend);\n\t\tconst jsonArgs = toJsonValue(state.args) as JsonObject;\n\t\tlet toolInput = jsonArgs;\n\t\tif (this._hasMatchingToolHook(event, normalizedToolName)) {\n\t\t\ttry {\n\t\t\t\ttoolInput = adaptFileToolInputForClaudeHook(\n\t\t\t\t\tstate.toolCall.name,\n\t\t\t\t\tjsonArgs,\n\t\t\t\t\tbackend.cwd,\n\t\t\t\t\tflavor,\n\t\t\t\t\thookPathHome(backend, flavor),\n\t\t\t\t);\n\t\t\t} catch (error) {\n\t\t\t\tthis._preToolHookContext.delete(state.toolCall.id);\n\t\t\t\tthis._reportHookDiagnostic({\n\t\t\t\t\tlevel: \"warning\",\n\t\t\t\t\tcode: \"execution\",\n\t\t\t\t\tevent,\n\t\t\t\t\tmessage: `Skipped ${event} for ${state.toolCall.name}: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t\t});\n\t\t\t\treturn { ok: false };\n\t\t\t}\n\t\t}\n\t\tconst common = {\n\t\t\t...this._hookCommon(event),\n\t\t\ttool_name: normalizedToolName,\n\t\t\ttool_input: toolInput,\n\t\t\ttool_use_id: state.toolCall.id,\n\t\t\t...(started ? { duration_ms: Date.now() - started } : {}),\n\t\t};\n\t\tconst hookResult = state.isError\n\t\t\t? await this._runHook(\n\t\t\t\t\t{\n\t\t\t\t\t\t...common,\n\t\t\t\t\t\thook_event_name: \"PostToolUseFailure\",\n\t\t\t\t\t\terror: state.content.map((part) => (part.type === \"text\" ? part.text : \"[image]\")).join(\"\\n\"),\n\t\t\t\t\t},\n\t\t\t\t\tsignal,\n\t\t\t\t)\n\t\t\t: await this._runHook(\n\t\t\t\t\t{\n\t\t\t\t\t\t...common,\n\t\t\t\t\t\thook_event_name: \"PostToolUse\",\n\t\t\t\t\t\ttool_response: toJsonValue({ content: state.content, details: state.details }),\n\t\t\t\t\t},\n\t\t\t\t\tsignal,\n\t\t\t\t);\n\t\treturn { ok: true, hookResult };\n\t}\n\n\tprivate _applyPostToolOutputUpdate(state: PostToolCallState, hookResult: HookAggregateResult): void {\n\t\tif (hookResult.updatedToolOutput === undefined) return;\n\t\tconst builtIn = Object.hasOwn(CLAUDE_TOOL_NAMES, state.toolCall.name);\n\t\tconst replacement =\n\t\t\tbuiltIn && typeof hookResult.updatedToolOutput !== \"string\"\n\t\t\t\t? undefined\n\t\t\t\t: normalizeHookToolOutput(hookResult.updatedToolOutput);\n\t\tif (replacement) {\n\t\t\tstate.content = replacement.content;\n\t\t\tif (replacement.details !== undefined) state.details = replacement.details;\n\t\t\treturn;\n\t\t}\n\t\tthis._reportHookDiagnostic({\n\t\t\tlevel: \"warning\",\n\t\t\tcode: \"unsupported-update\",\n\t\t\tevent: state.isError ? \"PostToolUseFailure\" : \"PostToolUse\",\n\t\t\tmessage: builtIn\n\t\t\t\t? \"Unsupported built-in tool output replacement was ignored; only a string replacement is safe\"\n\t\t\t\t: \"Invalid tool output replacement was ignored; expected a string or { content } result shape\",\n\t\t});\n\t}\n\n\tprivate _finalizePostToolHookResult(state: PostToolCallState, hookResult: HookAggregateResult): AfterToolCallResult {\n\t\tconst feedback = [\n\t\t\t...(this._preToolHookContext.get(state.toolCall.id) ?? []),\n\t\t\t...hookResult.additionalContext,\n\t\t\t...(hookResult.blocked && hookResult.reason ? [hookResult.reason] : []),\n\t\t];\n\t\tthis._preToolHookContext.delete(state.toolCall.id);\n\t\tif (feedback.length > 0) {\n\t\t\tstate.content = [\n\t\t\t\t...state.content,\n\t\t\t\t{ type: \"text\", text: `Hook feedback:\\n${[...new Set(feedback)].join(\"\\n\\n\")}` },\n\t\t\t];\n\t\t}\n\t\tif (!hookResult.continue) this.agent.abort();\n\t\treturn { content: state.content, details: state.details, isError: state.isError };\n\t}\n\n\tprivate async _handleAfterToolCall(\n\t\t{ toolCall, args, result, isError }: AfterToolCallContext,\n\t\tsignal?: AbortSignal,\n\t): Promise<AfterToolCallResult> {\n\t\tthis._loadAssociatedToolsForReadToolCall(toolCall.name, args, isError);\n\t\tconst lspResult = await this._syncLspToolResult(toolCall.name, args, result, isError);\n\t\tconst state: PostToolCallState = {\n\t\t\ttoolCall,\n\t\t\targs,\n\t\t\tcontent: lspResult?.content ?? result.content,\n\t\t\tdetails: lspResult?.details ?? result.details,\n\t\t\tisError: lspResult?.isError ?? isError,\n\t\t};\n\t\tawait this._applyExtensionToolResult(state);\n\t\tconst started = this._toolStartedAt.get(toolCall.id);\n\t\tthis._toolStartedAt.delete(toolCall.id);\n\t\tconst hookRun = await this._runPostToolHook(state, started, signal);\n\t\tif (!hookRun.ok) {\n\t\t\treturn { content: state.content, details: state.details, isError: state.isError };\n\t\t}\n\t\tthis._applyPostToolOutputUpdate(state, hookRun.hookResult);\n\t\treturn this._finalizePostToolHookResult(state, hookRun.hookResult);\n\t}\n\n\t/**\n\t * Install tool hooks once on the Agent instance.\n\t *\n\t * The callbacks read `this._extensionRunner` at execution time, so extension reload swaps in the\n\t * new runner without reinstalling hooks. Extension-specific tool wrappers are still used to adapt\n\t * registered tool execution to the extension context. Tool call and tool result interception now\n\t * happens here instead of in wrappers.\n\t */\n\tprivate _installAgentToolHooks(): void {\n\t\tthis.agent.beforeToolCall = (context, signal) => this._handleBeforeToolCall(context, signal);\n\n\t\t// Deterministic post order: built-in LSP synchronization, extension tool_result, then Claude PostToolUse*.\n\t\tthis.agent.afterToolCall = (context, signal) => this._handleAfterToolCall(context, signal);\n\t}\n\n\tprivate _beginActivity(kind: string): SessionActivityToken {\n\t\tconst token: SessionActivityToken = { active: true, kind };\n\t\tthis._activities.add(token);\n\t\tthis._activityGeneration++;\n\t\tthis._activeCycle = true;\n\t\treturn token;\n\t}\n\n\tprivate _endActivity(token: SessionActivityToken): void {\n\t\tif (!token.active) return;\n\t\ttoken.active = false;\n\t\tthis._activities.delete(token);\n\t\tthis._activityGeneration++;\n\t\tthis._scheduleSettlementCheck();\n\t}\n\n\tprivate _runActivity<T>(kind: string, operation: () => Promise<T>): Promise<T> {\n\t\tif (this._disposed) return Promise.reject(new Error(\"Agent session is disposed\"));\n\t\tconst token = this._beginActivity(kind);\n\t\tconst owner: BarrierOwnerScope = { active: true, label: kind };\n\t\treturn this._barrierOwnerContext.run(owner, async () => {\n\t\t\ttry {\n\t\t\t\treturn await operation();\n\t\t\t} finally {\n\t\t\t\towner.active = false;\n\t\t\t\tthis._endActivity(token);\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate _runSynchronousActivity<T>(kind: string, operation: () => T): T {\n\t\tconst token = this._beginActivity(kind);\n\t\ttry {\n\t\t\treturn this._runBarrierCallback(kind, operation);\n\t\t} finally {\n\t\t\tthis._endActivity(token);\n\t\t}\n\t}\n\n\tprivate _runAsyncBarrierCallback<T>(label: string, callback: () => Promise<T>): Promise<T> {\n\t\tconst owner: BarrierOwnerScope = { active: true, label };\n\t\treturn this._barrierOwnerContext.run(owner, async () => {\n\t\t\ttry {\n\t\t\t\treturn await callback();\n\t\t\t} finally {\n\t\t\t\towner.active = false;\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate _runBarrierCallback<T>(label: string, callback: () => T): T {\n\t\tconst owner: BarrierOwnerScope = { active: true, label };\n\t\treturn this._barrierOwnerContext.run(owner, () => {\n\t\t\ttry {\n\t\t\t\treturn callback();\n\t\t\t} finally {\n\t\t\t\towner.active = false;\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate _isSettledPredicate(): boolean {\n\t\treturn (\n\t\t\tthis._activities.size === 0 &&\n\t\t\t!this.agent.state.isStreaming &&\n\t\t\tthis._retryAbortController === undefined &&\n\t\t\tthis._compactionInFlight === undefined &&\n\t\t\tthis._compactionAbortController === undefined &&\n\t\t\tthis._autoCompactionAbortController === undefined &&\n\t\t\tthis._branchSummaryAbortController === undefined &&\n\t\t\tthis._extensionCompactionQueue.length === 0 &&\n\t\t\tthis._extensionCompactionTimer === undefined &&\n\t\t\t!this._extensionCompactionRunning &&\n\t\t\tthis._bashAbortControllers.size === 0 &&\n\t\t\tthis._pendingBashMessages.length === 0 &&\n\t\t\tthis._steeringMessages.length === 0 &&\n\t\t\tthis._followUpMessages.length === 0 &&\n\t\t\t!this.agent.hasQueuedMessages()\n\t\t);\n\t}\n\n\tprivate _scheduleSettlementCheck(): void {\n\t\tif (this._settlementScheduled || this._settlementRunning) return;\n\t\tthis._settlementScheduled = true;\n\t\tqueueMicrotask(() => {\n\t\t\tthis._settlementScheduled = false;\n\t\t\tvoid this._drainSettlement();\n\t\t});\n\t}\n\tprivate _isCurrentSettlementGeneration(generation: number): boolean {\n\t\treturn this._isSettledPredicate() && generation === this._activityGeneration;\n\t}\n\n\tprivate async _finishSettledAgentCycle(generation: number): Promise<boolean> {\n\t\tif (!this._activeCycle) return true;\n\t\tawait this._runAsyncBarrierCallback(\"agent_settled extension handler\", () =>\n\t\t\tthis._extensionRunner.emit({ type: \"agent_settled\" }),\n\t\t);\n\t\tif (!this._isCurrentSettlementGeneration(generation)) return false;\n\t\tthis._emitAgentSettled();\n\t\tif (!this._isCurrentSettlementGeneration(generation)) return false;\n\t\tthis._activeCycle = false;\n\t\treturn true;\n\t}\n\n\tprivate async _drainSettlement(): Promise<void> {\n\t\tif (this._settlementRunning) return;\n\t\tthis._settlementRunning = true;\n\t\ttry {\n\t\t\tlet finished = false;\n\t\t\twhile (this._isSettledPredicate()) {\n\t\t\t\tconst generation = this._activityGeneration;\n\t\t\t\tif (!(await this._finishSettledAgentCycle(generation))) continue;\n\t\t\t\tfinished = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (!finished) return;\n\t\t\tfor (const waiter of this._idleWaiters) waiter.resolve();\n\t\t\tthis._idleWaiters.clear();\n\t\t} finally {\n\t\t\tthis._settlementRunning = false;\n\t\t\tif ((this._activeCycle || this._idleWaiters.size > 0) && this._isSettledPredicate()) {\n\t\t\t\tthis._scheduleSettlementCheck();\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate _runRuntimeLifecycleOperation<T>(operation: () => Promise<T>): Promise<T> {\n\t\tconst token: RuntimeLifecycleOperationToken = {\n\t\t\tactive: true,\n\t\t\tchildren: new Set(),\n\t\t\tchildQueue: Promise.resolve(),\n\t\t};\n\t\treturn this._runtimeLifecycleContext.run(token, async () => {\n\t\t\ttry {\n\t\t\t\treturn await this._runAsyncBarrierCallback(\"runtime lifecycle operation\", operation);\n\t\t\t} finally {\n\t\t\t\twhile (token.children.size > 0) {\n\t\t\t\t\tawait Promise.allSettled([...token.children]);\n\t\t\t\t}\n\t\t\t\ttoken.active = false;\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate _enqueueRuntimeLifecycle<T>(operation: () => Promise<T>): Promise<T> {\n\t\tconst activity = this._beginActivity(\"runtime lifecycle\");\n\t\tconst currentToken = this._runtimeLifecycleContext.getStore();\n\t\tif (currentToken?.active) {\n\t\t\tconst child = currentToken.childQueue.then(() => this._runRuntimeLifecycleOperation(operation));\n\t\t\tcurrentToken.childQueue = child.then(\n\t\t\t\t() => undefined,\n\t\t\t\t() => undefined,\n\t\t\t);\n\t\t\tcurrentToken.children.add(child);\n\t\t\tvoid child.then(\n\t\t\t\t() => {\n\t\t\t\t\tcurrentToken.children.delete(child);\n\t\t\t\t\tthis._endActivity(activity);\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\tcurrentToken.children.delete(child);\n\t\t\t\t\tthis._endActivity(activity);\n\t\t\t\t},\n\t\t\t);\n\t\t\treturn child;\n\t\t}\n\t\tconst result = this._runtimeLifecycleQueue.then(() => this._runRuntimeLifecycleOperation(operation));\n\t\tthis._runtimeLifecycleQueue = result.then(\n\t\t\t() => undefined,\n\t\t\t() => undefined,\n\t\t);\n\t\tvoid result.then(\n\t\t\t() => this._endActivity(activity),\n\t\t\t() => this._endActivity(activity),\n\t\t);\n\t\treturn result;\n\t}\n\n\tprivate async _syncLspToolResult(\n\t\ttoolName: string,\n\t\targs: unknown,\n\t\tresult: LspToolResult,\n\t\tisError: boolean,\n\t): Promise<LspToolResultUpdate | undefined> {\n\t\tconst runtime = this._lspRuntimeState;\n\t\tif (isError || !runtime || typeof args !== \"object\" || args === null || !(\"path\" in args)) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst filePath = typeof args.path === \"string\" ? args.path : undefined;\n\t\tif (!filePath) return undefined;\n\n\t\ttry {\n\t\t\tif (toolName === \"read\") {\n\t\t\t\tawait runtime.fileSync.handleFileRead(filePath, this.getToolOperations());\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\tif (toolName === \"write\" || toolName === \"edit\") {\n\t\t\t\tawait runtime.fileSync.handleFileWrite(filePath, this.getToolOperations());\n\t\t\t\tconst diagnostics = await formatAutoDiagnosticsForChangedFile(runtime, filePath);\n\t\t\t\tif (!diagnostics) return undefined;\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [...result.content, { type: \"text\" as const, text: `\\n\\n${diagnostics}` }],\n\t\t\t\t\tdetails: result.details,\n\t\t\t\t\tisError,\n\t\t\t\t};\n\t\t\t}\n\t\t} catch {\n\t\t\t// LSP synchronization is best-effort and must not affect tool results.\n\t\t}\n\n\t\treturn undefined;\n\t}\n\tprivate _loadAssociatedInstructionTools(resource: InstructionResource): void {\n\t\tfor (const toolName of resource.tools ?? []) {\n\t\t\tthis.loadSessionTool(toolName);\n\t\t}\n\t}\n\n\tprivate _loadAssociatedToolsForReadToolCall(toolName: string, args: unknown, isError: boolean): void {\n\t\tif (toolName !== \"read\" || isError || typeof args !== \"object\" || args === null) {\n\t\t\treturn;\n\t\t}\n\t\tconst readArgs = args as { path?: unknown; file_path?: unknown };\n\t\tconst rawPath = typeof readArgs.path === \"string\" ? readArgs.path : readArgs.file_path;\n\t\tif (typeof rawPath !== \"string\" || rawPath.trim().length === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst readPath = canonicalizePath(resolvePath(rawPath, this._cwd));\n\t\tfor (const skill of this.getRegisteredSkills()) {\n\t\t\tif (canonicalizePath(resolvePath(skill.filePath, this._cwd)) === readPath) {\n\t\t\t\tthis._loadAssociatedInstructionTools(skill);\n\t\t\t}\n\t\t}\n\t\tfor (const rule of this.getRegisteredRules()) {\n\t\t\tif (canonicalizePath(resolvePath(rule.filePath, this._cwd)) === readPath) {\n\t\t\t\tthis._loadAssociatedInstructionTools(rule);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate _installAgentTurnPreparation(): void {\n\t\tconst previousPrepareNextTurnWithContext =\n\t\t\tthis.agent.prepareNextTurnWithContext ??\n\t\t\t(this.agent.prepareNextTurn\n\t\t\t\t? async (_turn: PrepareNextTurnContext, signal?: AbortSignal) => await this.agent.prepareNextTurn?.(signal)\n\t\t\t\t: undefined);\n\t\tthis.agent.prepareNextTurnWithContext = async (turn, signal) => {\n\t\t\tconst previousUpdate = await previousPrepareNextTurnWithContext?.(turn, signal);\n\t\t\tconst compacted = await this._drainExtensionCompactionQueue(\"between-turns\");\n\t\t\tconst contextReplaced = compacted || this._stateCompressionApplied;\n\t\t\tthis._stateCompressionApplied = false;\n\t\t\t// Rebuild the loop context so session-driven changes to tools and the prompt\n\t\t\t// take effect on the next turn within this run.\n\t\t\tconst context = this._buildCurrentAgentContext(\n\t\t\t\tcontextReplaced ? undefined : (previousUpdate?.context ?? turn.context),\n\t\t\t);\n\t\t\tif (this.compressionDetectionEnabled) {\n\t\t\t\tthis._compressionDetector.check(\n\t\t\t\t\tthis.getContextUsage()?.percent,\n\t\t\t\t\tcontext.messages,\n\t\t\t\t\tcontext.systemPrompt,\n\t\t\t\t\tgetCompressibleEntries(this.session, \"\"),\n\t\t\t\t);\n\t\t\t\tawait this._compressionDetector.waitForIdle(signal);\n\t\t\t}\n\t\t\treturn { ...previousUpdate, context } satisfies AgentLoopTurnUpdate;\n\t\t};\n\t}\n\n\tprivate _createCompressionAdvisory(): CustomMessage {\n\t\tconst suggested = this._compressionDetector.suggestedRange;\n\t\tconst urgency = this._compressionDetector.urgency;\n\t\tlet range = \"\";\n\t\tif (suggested && isValidSuggestedCompressionRange(this.session, suggested.startEntryId, suggested.endEntryId)) {\n\t\t\trange = ` Suggested inclusive range: startEntryId ${suggested.startEntryId}, endEntryId ${suggested.endEntryId}; verify before acting.`;\n\t\t\tconst model = this.model;\n\t\t\tconst estimate = model\n\t\t\t\t? estimateCompressionEconomics(\n\t\t\t\t\t\tgetCompressibleEntries(this.session, \"\"),\n\t\t\t\t\t\tsuggested.startEntryId,\n\t\t\t\t\t\tsuggested.endEntryId,\n\t\t\t\t\t\tmodel,\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\tthis._modelRegistry.isUsingOAuth(model),\n\t\t\t\t\t)\n\t\t\t\t: undefined;\n\t\t\tif (estimate) {\n\t\t\t\trange += ` Estimated if summary is 25% of range: ~${estimate.removedTokens} fewer context tokens per request`;\n\t\t\t\tif (estimate.breakEvenRequests != null)\n\t\t\t\t\trange += `, ~${estimate.breakEvenRequests} requests to break even at catalog rates`;\n\t\t\t\trange += \"; estimates depend on caching and future requests.\";\n\t\t\t}\n\t\t}\n\t\treturn createCustomMessage(\n\t\t\t\"compression_detection\",\n\t\t\t`[Background compression detector: COMPRESS${urgency === undefined ? \".\" : ` (urgency ${urgency}%).`}${range} At the next safe checkpoint, consider using compress_context to summarize older context. Preserve essential decisions and unfinished work. This is advisory; continue if details are still needed.]`,\n\t\t\tfalse,\n\t\t\t{ verdictCount: this._compressionDetector.counts.compress },\n\t\t\tnew Date().toISOString(),\n\t\t);\n\t}\n\n\t/** Add model-only entry ID notices without persisting them. */\n\tprivate _installContextMetadata(): void {\n\t\tconst previousTransformContext = this.agent.transformContext;\n\t\tthis.agent.transformContext = async (messages, signal) => {\n\t\t\tthis._waitingForDetectorAtRequest = true;\n\t\t\ttry {\n\t\t\t\tawait this._compressionDetector.waitForIdle(signal);\n\t\t\t} finally {\n\t\t\t\tthis._waitingForDetectorAtRequest = false;\n\t\t\t}\n\t\t\tconst transformed = (await previousTransformContext?.(messages, signal)) ?? messages;\n\t\t\tconst annotated = this.getActiveToolNames().includes(\"compress_context\")\n\t\t\t\t? annotateContextMetadata(transformed, this.session.getBranch())\n\t\t\t\t: transformed;\n\t\t\tif (!this.compressionDetectionEnabled || !this._compressionDetector.shouldCompress) return annotated;\n\t\t\tconst verdictCount = this._compressionDetector.counts.compress;\n\t\t\tif (\n\t\t\t\tannotated.some(\n\t\t\t\t\t(message) =>\n\t\t\t\t\t\tmessage.role === \"custom\" &&\n\t\t\t\t\t\tmessage.customType === \"compression_detection\" &&\n\t\t\t\t\t\t(message.details as { verdictCount?: number } | undefined)?.verdictCount === verdictCount,\n\t\t\t\t)\n\t\t\t)\n\t\t\t\treturn annotated;\n\t\t\treturn [...annotated, this._createCompressionAdvisory()];\n\t\t};\n\t}\n\n\tprivate _buildCurrentAgentContext(previousContext?: AgentContext): AgentContext {\n\t\treturn {\n\t\t\tsystemPrompt: this._systemPromptOverride ?? this._baseSystemPrompt,\n\t\t\tmessages: previousContext?.messages ?? this.agent.state.messages.slice(),\n\t\t\ttools: this.agent.state.tools.slice(),\n\t\t};\n\t}\n\n\t// =========================================================================\n\t// Event Subscription\n\t// =========================================================================\n\n\t/** Emit an event to all listeners */\n\tprivate _emit(event: AgentSessionEvent): void {\n\t\tthis._runBarrierCallback(`public ${event.type} listener`, () => {\n\t\t\tfor (const listener of this._eventListeners) listener(event);\n\t\t});\n\t}\n\n\tprivate _emitAgentSettled(): void {\n\t\tfor (const listener of [...this._eventListeners]) {\n\t\t\ttry {\n\t\t\t\tthis._runBarrierCallback(\"public agent_settled listener\", () => listener({ type: \"agent_settled\" }));\n\t\t\t} catch (error) {\n\t\t\t\ttry {\n\t\t\t\t\tthis._extensionRunner.emitError({\n\t\t\t\t\t\textensionPath: \"<session-subscriber>\",\n\t\t\t\t\t\tevent: \"agent_settled\",\n\t\t\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t});\n\t\t\t\t} catch {\n\t\t\t\t\t// Subscriber diagnostics must never prevent settlement or later listeners.\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate _emitQueueUpdate(): void {\n\t\tthis._emit({\n\t\t\ttype: \"queue_update\",\n\t\t\tsteering: [...this._steeringMessages],\n\t\t\tfollowUp: [...this._followUpMessages],\n\t\t});\n\t}\n\n\t// Track last assistant message for auto-compaction check\n\tprivate _lastAssistantMessage: AssistantMessage | undefined = undefined;\n\n\tprivate _consumeStartedQueuedMessage(event: AgentEvent): void {\n\t\tif (event.type !== \"message_start\" || event.message.role !== \"user\") return;\n\t\tthis._overflowRecoveryAttempted = false;\n\t\tconst messageText = this._getUserMessageText(event.message);\n\t\tif (!messageText) return;\n\n\t\tconst queuedIds = this._queuedPromptIds.get(messageText);\n\t\tconst queuedId = queuedIds?.shift();\n\t\tif (queuedId) this._activePromptId = queuedId;\n\t\tif (queuedIds?.length === 0) this._queuedPromptIds.delete(messageText);\n\n\t\tconst steeringIndex = this._steeringMessages.indexOf(messageText);\n\t\tif (steeringIndex !== -1) {\n\t\t\tthis._steeringMessages.splice(steeringIndex, 1);\n\t\t\tthis._emitQueueUpdate();\n\t\t\treturn;\n\t\t}\n\t\tconst followUpIndex = this._followUpMessages.indexOf(messageText);\n\t\tif (followUpIndex === -1) return;\n\t\tthis._followUpMessages.splice(followUpIndex, 1);\n\t\tthis._emitQueueUpdate();\n\t}\n\n\tprivate _persistCompletedMessage(message: AgentMessage): void {\n\t\tif (message.role === \"custom\") {\n\t\t\tif (message.customType === \"compression_detection\") return;\n\t\t\tthis.session.appendCustomMessageEntry(message.customType, message.content, message.display, message.details);\n\t\t\treturn;\n\t\t}\n\t\tif (message.role === \"user\" || message.role === \"assistant\" || message.role === \"toolResult\") {\n\t\t\tthis.session.appendMessage(message);\n\t\t}\n\t}\n\n\tprivate _applyPendingContextCompression(message: AgentMessage): void {\n\t\tconst pending = this._pendingContextCompression;\n\t\tif (!pending || message.role !== \"toolResult\" || message.toolCallId !== pending.toolCallId) return;\n\t\tthis._pendingContextCompression = undefined;\n\t\tif (message.isError) return;\n\n\t\tconst branch = this.session.getBranch();\n\t\tconst start = branch.find((entry) => entry.id === pending.startEntryId);\n\t\tconst assistant = branch.find((entry) => entry.id === pending.assistantEntryId);\n\t\tconst last = branch.at(-1);\n\t\tif (!start || !assistant || last?.type !== \"message\" || last.message !== message) return;\n\t\tconst assistantIndex = branch.indexOf(assistant);\n\t\tconst triggerIndex = branch\n\t\t\t.slice(0, assistantIndex)\n\t\t\t.map((entry) => entry.type === \"message\" && entry.message.role === \"user\")\n\t\t\t.lastIndexOf(true);\n\t\tif (triggerIndex < 0) return;\n\t\tconst earlierEntries = new Set(branch.slice(0, triggerIndex).map((entry) => entry.id));\n\t\tconst visible = getCompressibleEntries(this.session, pending.toolCallId).filter((entry) =>\n\t\t\tearlierEntries.has(entry.id),\n\t\t);\n\t\tconst startIndex = visible.findIndex((entry) => entry.id === start.id);\n\t\tconst endIndex = pending.endEntryId\n\t\t\t? visible.findIndex((entry) => entry.id === pending.endEntryId)\n\t\t\t: visible.length - 1;\n\t\tif (startIndex < 0 || endIndex < startIndex) return;\n\t\tconst economicsSnapshot = createCompressionSavingsSnapshot(branch);\n\t\t// The tool call and result were not in the request that produced this compression.\n\t\t// Only the earlier visible suffix could have been cached before the replacement.\n\t\tconst contextEntries = getCompressibleEntries(this.session, \"\");\n\t\tconst economicsEntries = contextEntries.slice(\n\t\t\t0,\n\t\t\tcontextEntries.findIndex((entry) => entry.id === assistant.id),\n\t\t);\n\t\tconst model = this.model;\n\t\tconst previous = this.session.buildSessionContext();\n\t\tconst oldLeafId = last.id;\n\t\tconst summaryEntryId = this.session.appendCustomMessageEntry(\"compress_context\", pending.summary, true, {\n\t\t\treplacementVersion: 1,\n\t\t\tfromId: oldLeafId,\n\t\t\tstartEntryId: start.id,\n\t\t\tendEntryId: visible[endIndex].id,\n\t\t});\n\t\tif (economicsSnapshot.totalRemovedTokens) {\n\t\t\tconst retainedIds = new Set(projectSessionContextEntries(this.session.getBranch()).map((entry) => entry.id));\n\t\t\teconomicsSnapshot.activeSavings = economicsSnapshot.activeSavings.filter((saving) =>\n\t\t\t\tretainedIds.has(saving.summaryEntryId),\n\t\t\t);\n\t\t\teconomicsSnapshot.liveRemovedTokens = economicsSnapshot.activeSavings.reduce(\n\t\t\t\t(total, saving) => total + saving.removedTokens,\n\t\t\t\t0,\n\t\t\t);\n\t\t\tthis.session.appendCustomEntry(COMPRESSION_SAVINGS_SNAPSHOT_TYPE, economicsSnapshot);\n\t\t}\n\t\tthis._recordCompressionEconomics(\n\t\t\teconomicsEntries,\n\t\t\tstart.id,\n\t\t\tvisible[endIndex].id,\n\t\t\tpending.summary,\n\t\t\tsummaryEntryId,\n\t\t\tmodel,\n\t\t);\n\t\tthis._restoreCompressionContextState(previous);\n\t\tthis.agent.state.messages = this.session.buildSessionContext().messages;\n\t\tthis._compressionDetector.reset(this.agent.state.messages, this.getContextUsage()?.percent ?? null);\n\t\tthis._stateCompressionApplied = true;\n\t\tthis._emit({ type: \"state_compressed\" });\n\t}\n\tprivate _recordCompressionEconomics(\n\t\tbranch: SessionEntry[],\n\t\tstartEntryId: string,\n\t\tendEntryId: string,\n\t\tsummary: string,\n\t\tsummaryEntryId: string,\n\t\tmodel: Model<Api> | undefined,\n\t): void {\n\t\tif (!model) return;\n\t\tconst economics = estimateCompressionEconomics(\n\t\t\tbranch,\n\t\t\tstartEntryId,\n\t\t\tendEntryId,\n\t\t\tmodel,\n\t\t\tsummary,\n\t\t\tthis._modelRegistry.isUsingOAuth(model),\n\t\t);\n\t\tif (economics)\n\t\t\tthis.session.appendCustomEntry(COMPRESSION_ECONOMICS_ENTRY_TYPE, { ...economics, version: 1, summaryEntryId });\n\t}\n\tprivate _restoreCompressionContextState(previous: SessionContext): void {\n\t\tconst rebuilt = this.session.buildSessionContext();\n\t\tif (\n\t\t\tprevious.model &&\n\t\t\t(previous.model.provider !== rebuilt.model?.provider || previous.model.modelId !== rebuilt.model?.modelId)\n\t\t) {\n\t\t\tthis.session.appendModelChange(previous.model.provider, previous.model.modelId);\n\t\t}\n\t\tif (previous.thinkingLevel !== rebuilt.thinkingLevel) {\n\t\t\tthis.session.appendThinkingLevelChange(previous.thinkingLevel);\n\t\t}\n\t}\n\tprivate _updateRetryStateAfterAssistantMessage(message: AgentMessage): void {\n\t\tif (message.role !== \"assistant\") return;\n\t\tconst assistantMessage = message as AssistantMessage;\n\t\tthis._lastAssistantMessage = assistantMessage;\n\t\tif (assistantMessage.stopReason === \"error\") return;\n\t\tthis._overflowRecoveryAttempted = false;\n\t\tif (this._retryAttempt === 0) return;\n\t\tthis._emit({\n\t\t\ttype: \"auto_retry_end\",\n\t\t\tsuccess: true,\n\t\t\tattempt: this._retryAttempt,\n\t\t});\n\t\tthis._retryAttempt = 0;\n\t}\n\n\t/** Internal handler for agent events - shared by subscribe and reconnect */\n\tprivate _handleAgentEvent = async (event: AgentEvent): Promise<void> => {\n\t\tif (event.type === \"agent_start\") this._agentRunEnding = false;\n\t\t// afterToolCall can be skipped when a prepared call is aborted. Never retain per-run tool state.\n\t\tif (event.type === \"agent_end\") {\n\t\t\tthis._agentRunEnding = true;\n\t\t\tthis._toolStartedAt.clear();\n\t\t\tthis._preToolHookContext.clear();\n\t\t\tthis._pendingContextCompression = undefined;\n\t\t}\n\t\tthis._consumeStartedQueuedMessage(event);\n\n\t\t// Extensions observe the event before public session listeners.\n\t\tawait this._runAsyncBarrierCallback(`extension ${event.type} handler`, () => this._emitExtensionEvent(event));\n\t\tthis._emit(event.type === \"agent_end\" ? { ...event, willRetry: this._willRetryAfterAgentEnd(event) } : event);\n\t\tif (event.type === \"agent_end\") {\n\t\t\tconst entries = this.session.getEntries();\n\t\t\tconst ledger = readUsageLedger(entries);\n\t\t\tconst lastEntry = entries.at(-1);\n\t\t\tif (ledger.checkpointId && !(lastEntry?.type === \"custom\" && lastEntry.customType === USAGE_LEDGER_ENTRY_TYPE))\n\t\t\t\tthis.session.appendCustomEntry(USAGE_LEDGER_ENTRY_TYPE, ledger);\n\t\t\tthis._scheduleExtensionCompactions();\n\t\t\tif (!this._disposed && this.compressionDetectionEnabled)\n\t\t\t\tthis._compressionDetector.check(\n\t\t\t\t\tthis.getContextUsage()?.percent,\n\t\t\t\t\tthis.agent.state.messages,\n\t\t\t\t\tthis._buildCurrentAgentContext().systemPrompt,\n\t\t\t\t\tgetCompressibleEntries(this.session, \"\"),\n\t\t\t\t);\n\t\t}\n\t\tif (event.type === \"message_end\") {\n\t\t\tthis._persistCompletedMessage(event.message);\n\t\t\tthis._applyPendingContextCompression(event.message);\n\t\t\tthis._updateRetryStateAfterAssistantMessage(event.message);\n\t\t}\n\t};\n\n\tprivate _willRetryAfterAgentEnd(event: CoreAgentEndEvent): boolean {\n\t\tconst settings = this.settingsManager.getRetrySettings();\n\t\tif (!settings.enabled || this._retryAttempt >= settings.maxRetries) {\n\t\t\treturn false;\n\t\t}\n\n\t\tfor (let i = event.messages.length - 1; i >= 0; i--) {\n\t\t\tconst message = event.messages[i];\n\t\t\tif (message.role === \"assistant\") {\n\t\t\t\treturn this._isRetryableError(message as AssistantMessage);\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\t/** Extract text content from a message */\n\tprivate _getUserMessageText(message: Message): string {\n\t\tif (message.role !== \"user\") return \"\";\n\t\tconst content = message.content;\n\t\tif (typeof content === \"string\") return content;\n\t\tconst textBlocks = content.filter((c) => c.type === \"text\");\n\t\treturn textBlocks.map((c) => (c as TextContent).text).join(\"\");\n\t}\n\n\t/** Find the last assistant message in agent state (including aborted ones) */\n\tprivate _findLastAssistantMessage(): AssistantMessage | undefined {\n\t\tconst messages = this.agent.state.messages;\n\t\tfor (let i = messages.length - 1; i >= 0; i--) {\n\t\t\tconst msg = messages[i];\n\t\t\tif (msg.role === \"assistant\") {\n\t\t\t\treturn msg as AssistantMessage;\n\t\t\t}\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tprivate _replaceMessageInPlace(target: AgentMessage, replacement: AgentMessage): void {\n\t\t// Agent-core stores the finalized message object in its state before emitting message_end.\n\t\t// Session persistence happens later in _handleAgentEvent() with event.message.\n\t\t// Mutating this object in place keeps agent state, later turn/agent events, listeners,\n\t\t// and the eventual SessionManager.appendMessage(event.message) persistence in sync.\n\t\tif (target === replacement) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst targetRecord = target as unknown as Record<string, unknown>;\n\t\tfor (const key of Object.keys(targetRecord)) {\n\t\t\tdelete targetRecord[key];\n\t\t}\n\t\tObject.assign(targetRecord, replacement);\n\t}\n\n\t/** Emit extension events based on agent events */\n\tprivate async _emitExtensionEvent(event: AgentEvent): Promise<void> {\n\t\tif (event.type === \"agent_start\") {\n\t\t\tthis._turnIndex = 0;\n\t\t\tawait this._extensionRunner.emit({ type: \"agent_start\" });\n\t\t} else if (event.type === \"agent_end\") {\n\t\t\tawait this._extensionRunner.emit({ type: \"agent_end\", messages: event.messages });\n\t\t} else if (event.type === \"turn_start\") {\n\t\t\tconst extensionEvent: TurnStartEvent = {\n\t\t\t\ttype: \"turn_start\",\n\t\t\t\tturnIndex: this._turnIndex,\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t};\n\t\t\tawait this._extensionRunner.emit(extensionEvent);\n\t\t} else if (event.type === \"turn_end\") {\n\t\t\tconst extensionEvent: TurnEndEvent = {\n\t\t\t\ttype: \"turn_end\",\n\t\t\t\tturnIndex: this._turnIndex,\n\t\t\t\tmessage: event.message,\n\t\t\t\ttoolResults: event.toolResults,\n\t\t\t};\n\t\t\tawait this._extensionRunner.emit(extensionEvent);\n\t\t\tthis._turnIndex++;\n\t\t} else if (event.type === \"message_start\") {\n\t\t\tconst extensionEvent: MessageStartEvent = {\n\t\t\t\ttype: \"message_start\",\n\t\t\t\tmessage: event.message,\n\t\t\t};\n\t\t\tawait this._extensionRunner.emit(extensionEvent);\n\t\t} else if (event.type === \"message_update\") {\n\t\t\tconst extensionEvent: MessageUpdateEvent = {\n\t\t\t\ttype: \"message_update\",\n\t\t\t\tmessage: event.message,\n\t\t\t\tassistantMessageEvent: event.assistantMessageEvent,\n\t\t\t};\n\t\t\tawait this._extensionRunner.emit(extensionEvent);\n\t\t} else if (event.type === \"message_end\") {\n\t\t\tconst extensionEvent: MessageEndEvent = {\n\t\t\t\ttype: \"message_end\",\n\t\t\t\tmessage: event.message,\n\t\t\t};\n\t\t\tconst replacement = await this._extensionRunner.emitMessageEnd(extensionEvent);\n\t\t\tif (replacement) {\n\t\t\t\tthis._replaceMessageInPlace(event.message, normalizeMessageContent(replacement));\n\t\t\t}\n\t\t} else if (event.type === \"tool_execution_start\") {\n\t\t\tconst extensionEvent: ToolExecutionStartEvent = {\n\t\t\t\ttype: \"tool_execution_start\",\n\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\ttoolName: event.toolName,\n\t\t\t\targs: event.args,\n\t\t\t};\n\t\t\tawait this._extensionRunner.emit(extensionEvent);\n\t\t} else if (event.type === \"tool_execution_update\") {\n\t\t\tconst extensionEvent: ToolExecutionUpdateEvent = {\n\t\t\t\ttype: \"tool_execution_update\",\n\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\ttoolName: event.toolName,\n\t\t\t\targs: event.args,\n\t\t\t\tpartialResult: event.partialResult,\n\t\t\t};\n\t\t\tawait this._extensionRunner.emit(extensionEvent);\n\t\t} else if (event.type === \"tool_execution_end\") {\n\t\t\tconst extensionEvent: ToolExecutionEndEvent = {\n\t\t\t\ttype: \"tool_execution_end\",\n\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\ttoolName: event.toolName,\n\t\t\t\tresult: event.result,\n\t\t\t\tisError: event.isError,\n\t\t\t};\n\t\t\tawait this._extensionRunner.emit(extensionEvent);\n\t\t}\n\t}\n\n\t/**\n\t * Subscribe to agent events.\n\t * Session persistence is handled internally (saves messages on message_end).\n\t * Multiple listeners can be added. Returns unsubscribe function for this listener.\n\t */\n\tsubscribe(listener: AgentSessionEventListener): () => void {\n\t\tthis._eventListeners.push(listener);\n\n\t\t// Return unsubscribe function for this specific listener\n\t\treturn () => {\n\t\t\tconst index = this._eventListeners.indexOf(listener);\n\t\t\tif (index !== -1) {\n\t\t\t\tthis._eventListeners.splice(index, 1);\n\t\t\t}\n\t\t};\n\t}\n\n\t/** Subscribe to completed hook executions for host UI rendering. Notices are not persisted or added to model context. */\n\tsubscribeToHookExecutions(listener: HookExecutionListener): () => void {\n\t\tthis._hookExecutionListeners.add(listener);\n\t\treturn () => this._hookExecutionListeners.delete(listener);\n\t}\n\n\t/** Subscribe to active hook execution changes for host UI rendering. */\n\tsubscribeToHookExecutionActivity(listener: HookExecutionActivityListener): () => void {\n\t\tthis._hookExecutionActivityListeners.add(listener);\n\t\treturn () => this._hookExecutionActivityListeners.delete(listener);\n\t}\n\t/** Whether this session will dispatch subsequent configured hook events. */\n\tget hooksEnabled(): boolean {\n\t\treturn this._hooksEnabled;\n\t}\n\n\t/** Enable or disable subsequent hook dispatches for this session. Active hook executions are not cancelled. */\n\tsetHooksEnabled(enabled: boolean): void {\n\t\tthis._hooksEnabled = enabled;\n\t}\n\t/** Disconnect from agent events during disposal. */\n\tprivate _disconnectFromAgent(): void {\n\t\tif (this._unsubscribeAgent) {\n\t\t\tthis._unsubscribeAgent();\n\t\t\tthis._unsubscribeAgent = undefined;\n\t\t}\n\t}\n\n\t/** Emit the bounded host-local SessionEnd hook once before replacement/disposal. */\n\tasync emitHookSessionEnd(reason: SessionEndReason): Promise<void> {\n\t\tif (this._hookSessionEnded) return;\n\t\tthis._hookSessionEnded = true;\n\t\tawait this._runHook({\n\t\t\t...this._hookCommon(\"SessionEnd\"),\n\t\t\thook_event_name: \"SessionEnd\",\n\t\t\treason,\n\t\t});\n\t}\n\n\t/**\n\t * Remove all listeners and disconnect from agent.\n\t * Call this when completely done with the session.\n\t */\n\tdispose(): Promise<void> {\n\t\tthis._disposed = true;\n\t\tthis._disposePromise ??= this._dispose();\n\t\treturn this._disposePromise;\n\t}\n\n\t/** Stop new prompt admission, abort active work, and wait for the session boundary. */\n\tprepareForShutdown(): Promise<void> {\n\t\tthis._disposed = true;\n\t\tthis._shutdownPreparationPromise ??= this._prepareForShutdown();\n\t\treturn this._shutdownPreparationPromise;\n\t}\n\n\tprivate async _prepareForShutdown(): Promise<void> {\n\t\tthis._compressionDetector.reset();\n\t\tthis._hookAbortController.abort();\n\t\tif (this._steeringMessages.length > 0 || this._followUpMessages.length > 0 || this.agent.hasQueuedMessages()) {\n\t\t\tthis.clearQueue();\n\t\t}\n\t\tfor (const abort of [\n\t\t\t() => this.abortRetry(),\n\t\t\t() => this.abortCompaction(),\n\t\t\t() => this.abortBranchSummary(),\n\t\t\t() => this.abortBash(),\n\t\t\t() => this.agent.abort(),\n\t\t]) {\n\t\t\ttry {\n\t\t\t\tabort();\n\t\t\t} catch {\n\t\t\t\t// Shutdown continues if an abort hook throws.\n\t\t\t}\n\t\t}\n\n\t\tif (this._extensionCompactionTimer !== undefined) {\n\t\t\tclearTimeout(this._extensionCompactionTimer);\n\t\t\tthis._extensionCompactionTimer = undefined;\n\t\t}\n\t\tthis._extensionCompactionQueue = [];\n\t\tif (this._extensionCompactionActivity) {\n\t\t\tthis._endActivity(this._extensionCompactionActivity);\n\t\t\tthis._extensionCompactionActivity = undefined;\n\t\t}\n\t\tawait this._runtimeLifecycleQueue;\n\t\tawait this.waitForIdle();\n\t}\n\n\tprivate async _dispose(): Promise<void> {\n\t\tawait this.prepareForShutdown();\n\t\tawait this._sandboxTransitionQueue;\n\n\t\tthis._extensionRunner.invalidate(STALE_EXTENSION_CONTEXT_MESSAGE);\n\t\tthis._disconnectFromAgent();\n\t\tthis._eventListeners = [];\n\t\tthis._hookExecutionListeners.clear();\n\t\tthis._hookExecutionActivityListeners.clear();\n\t\tcleanupSessionResources(this.sessionId);\n\n\t\tconst localResourceToolOperations = this._localResourceToolOperations;\n\t\tconst lspRuntimeState = this._lspRuntimeState;\n\t\tconst workspaceToolHost = this._workspaceToolHost;\n\t\tconst retiredWorkspaceToolHosts = this._retiredWorkspaceToolHosts;\n\t\tconst sandboxToolOperations = this._sandboxToolOperations;\n\t\tthis._localResourceToolOperations = undefined;\n\t\tthis._lspRuntimeState = undefined;\n\t\tthis._workspaceToolHost = undefined;\n\t\tthis._retiredWorkspaceToolHosts = [];\n\t\tthis._sandboxToolOperations = undefined;\n\t\tthis._preSandboxToolOperations = undefined;\n\t\tawait Promise.allSettled([\n\t\t\tPromise.resolve().then(() => localResourceToolOperations?.dispose?.()),\n\t\t\tPromise.resolve().then(() => lspRuntimeState?.manager.shutdownAll()),\n\t\t\tPromise.resolve().then(() => workspaceToolHost?.dispose()),\n\t\t\tPromise.resolve().then(() => sandboxToolOperations?.dispose()),\n\t\t\t...retiredWorkspaceToolHosts.map((host) => Promise.resolve().then(() => host.dispose())),\n\t\t]);\n\t}\n\n\t// =========================================================================\n\t// Read-only State Access\n\t// =========================================================================\n\n\t/** Full agent state */\n\tget state(): AgentState {\n\t\treturn this.agent.state;\n\t}\n\n\t/** Current model (may be undefined if not yet selected) */\n\tget model(): Model<any> | undefined {\n\t\treturn this.agent.state.model;\n\t}\n\n\t/** Current thinking level */\n\tget thinkingLevel(): ThinkingLevel {\n\t\treturn this.agent.state.thinkingLevel;\n\t}\n\n\t/** Whether the underlying Agent is currently streaming a response. */\n\tget isStreaming(): boolean {\n\t\treturn this.agent.state.isStreaming;\n\t}\n\n\tprivate get _hasActiveAgentRun(): boolean {\n\t\tif (this.agent.state.isStreaming) return true;\n\t\tfor (const activity of this._activities) {\n\t\t\tif (activity.kind === \"agent run\") return true;\n\t\t}\n\t\treturn false;\n\t}\n\n\tprivate _hasPromptAdmission(excluded?: SessionActivityToken): boolean {\n\t\tfor (const activity of this._activities) {\n\t\t\tif (activity !== excluded && activity.kind === \"prompt admission\") return true;\n\t\t}\n\t\treturn false;\n\t}\n\n\tprivate get _isAgentRunActive(): boolean {\n\t\treturn this._hasActiveAgentRun || this._hasPromptAdmission();\n\t}\n\n\t/** Whether all session-owned work and settlement callbacks have completed. */\n\tget isIdle(): boolean {\n\t\treturn this._isSettledPredicate() && !this._activeCycle && !this._settlementRunning && !this._settlementScheduled;\n\t}\n\n\t/** Current effective system prompt (includes any per-turn extension modifications) */\n\tget systemPrompt(): string {\n\t\treturn this.agent.state.systemPrompt;\n\t}\n\n\t/** Current retry attempt (0 if not retrying) */\n\tget retryAttempt(): number {\n\t\treturn this._retryAttempt;\n\t}\n\n\t/**\n\t * Get the names of currently active tools.\n\t * Returns the names of tools currently set on the agent.\n\t */\n\tgetActiveToolNames(): string[] {\n\t\treturn this.agent.state.tools.map((t) => t.name);\n\t}\n\n\t/**\n\t * Get all configured tools with name, description, parameter schema, and source metadata.\n\t */\n\tgetAllTools(): ToolInfo[] {\n\t\treturn Array.from(this._toolDefinitions.values())\n\t\t\t.filter(({ definition }) => this._isToolPermitted(definition.name))\n\t\t\t.map(({ definition, sourceInfo }) => ({\n\t\t\t\tname: definition.name,\n\t\t\t\tdescription: definition.description,\n\t\t\t\tparameters: definition.parameters,\n\t\t\t\tsourceInfo,\n\t\t\t}));\n\t}\n\n\tgetToolDefinition(name: string): ToolDefinition | undefined {\n\t\tif (!this._isToolPermitted(name)) return undefined;\n\t\treturn this._toolDefinitions.get(name)?.definition ?? this._sessionTools.get(name)?.definition;\n\t}\n\n\tgetRegisteredSkills(): Skill[] {\n\t\tconst sessionSkillNames = new Set(this._sessionSkills.keys());\n\t\treturn [\n\t\t\t...Array.from(this._sessionSkills.values()),\n\t\t\t...this._resourceLoader.getSkills().skills.filter((skill) => !sessionSkillNames.has(skill.name)),\n\t\t];\n\t}\n\n\tgetRegisteredRules(): Rule[] {\n\t\tconst sessionRuleNames = new Set(this._sessionRules.keys());\n\t\treturn [\n\t\t\t...Array.from(this._sessionRules.values()),\n\t\t\t...this._resourceLoader.getRules().rules.filter((rule) => !sessionRuleNames.has(rule.name)),\n\t\t];\n\t}\n\n\tregisterSessionSkill(skill: SessionSkillRegistration): void {\n\t\tconst filePath = skill.filePath || `<session-skill:${skill.name}>`;\n\t\tconst baseDir = skill.baseDir || (skill.filePath ? dirname(skill.filePath) : this._cwd);\n\t\tthis._sessionSkills.set(skill.name, {\n\t\t\t...skill,\n\t\t\tfilePath,\n\t\t\tbaseDir,\n\t\t\tsourceInfo: skill.sourceInfo ?? createSyntheticSourceInfo(filePath, { source: \"session\", baseDir }),\n\t\t});\n\t\tthis.setActiveToolsByName(this.getActiveToolNames());\n\t}\n\n\tunregisterSessionSkill(name: string): boolean {\n\t\tconst deleted = this._sessionSkills.delete(name);\n\t\tif (deleted) {\n\t\t\tthis.setActiveToolsByName(this.getActiveToolNames());\n\t\t}\n\t\treturn deleted;\n\t}\n\n\tregisterSessionRule(rule: SessionRuleRegistration): void {\n\t\tconst filePath = rule.filePath || `<session-rule:${rule.name}>`;\n\t\tconst baseDir = rule.baseDir || (rule.filePath ? dirname(rule.filePath) : this._cwd);\n\t\tthis._sessionRules.set(rule.name, {\n\t\t\t...rule,\n\t\t\tfilePath,\n\t\t\tbaseDir,\n\t\t\tsourceInfo: rule.sourceInfo ?? createSyntheticSourceInfo(filePath, { source: \"session\", baseDir }),\n\t\t});\n\t\tthis.setActiveToolsByName(this.getActiveToolNames());\n\t}\n\n\tunregisterSessionRule(name: string): boolean {\n\t\tconst deleted = this._sessionRules.delete(name);\n\t\tif (deleted) {\n\t\t\tthis.setActiveToolsByName(this.getActiveToolNames());\n\t\t}\n\t\treturn deleted;\n\t}\n\n\tregisterSessionTool(definition: ToolDefinition, options: RegisterSessionToolOptions = {}): void {\n\t\tif (!this._isToolPermitted(definition.name)) return;\n\t\tthis._sessionTools.set(definition.name, {\n\t\t\tdefinition,\n\t\t\tsourceInfo:\n\t\t\t\toptions.sourceInfo ?? createSyntheticSourceInfo(`<session:${definition.name}>`, { source: \"session\" }),\n\t\t\tlazy: options.lazy === true,\n\t\t\tloaded: options.lazy !== true,\n\t\t});\n\t\tthis._refreshToolRegistry();\n\t\tif (options.lazy === true) {\n\t\t\tconst activeTools = new Set(this.getActiveToolNames());\n\t\t\tactiveTools.add(\"load_tool\");\n\t\t\tactiveTools.add(\"unload_tool\");\n\t\t\tthis.setActiveToolsByName(Array.from(activeTools));\n\t\t}\n\t}\n\n\tunregisterSessionTool(name: string): boolean {\n\t\tconst deleted = this._sessionTools.delete(name);\n\t\tif (deleted) {\n\t\t\tthis._refreshToolRegistry();\n\t\t}\n\t\treturn deleted;\n\t}\n\n\tloadSessionTool(name: string): boolean {\n\t\treturn this._runSynchronousActivity(\"lazy tool load\", () => this._loadSessionTool(name));\n\t}\n\n\tprivate _loadSessionTool(name: string): boolean {\n\t\tif (!this._isToolPermitted(name)) return false;\n\t\tconst tool = this._sessionTools.get(name);\n\t\tif (tool) {\n\t\t\tif (!tool.loaded) {\n\t\t\t\ttool.loaded = true;\n\t\t\t\tthis._refreshToolRegistry();\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t\tconst loaded = this._extensionRunner.loadRegisteredTool(name);\n\t\tif (loaded) {\n\t\t\tthis._refreshToolRegistry();\n\t\t}\n\t\treturn loaded;\n\t}\n\n\tunloadSessionTool(name: string): boolean {\n\t\treturn this._runSynchronousActivity(\"lazy tool unload\", () => this._unloadSessionTool(name));\n\t}\n\n\tprivate _unloadSessionTool(name: string): boolean {\n\t\tif (!this._isToolPermitted(name)) return false;\n\t\tconst tool = this._sessionTools.get(name);\n\t\tif (tool) {\n\t\t\tif (tool.loaded) {\n\t\t\t\ttool.loaded = false;\n\t\t\t\tthis._refreshToolRegistry();\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t\tconst unloaded = this._extensionRunner.unloadRegisteredTool(name);\n\t\tif (unloaded) {\n\t\t\tthis._refreshToolRegistry();\n\t\t}\n\t\treturn unloaded;\n\t}\n\n\tgetAvailableSessionTools(): ToolInfo[] {\n\t\treturn [...Array.from(this._sessionTools.values()), ...this._extensionRunner.getAvailableRegisteredTools()]\n\t\t\t.filter(({ definition }) => this._isToolPermitted(definition.name))\n\t\t\t.map(({ definition, sourceInfo }) => ({\n\t\t\t\tname: definition.name,\n\t\t\t\tdescription: definition.description,\n\t\t\t\tparameters: definition.parameters,\n\t\t\t\tsourceInfo,\n\t\t\t}));\n\t}\n\n\t/**\n\t * Set active tools by name.\n\t * Only tools in the registry can be enabled. Unknown tool names are ignored.\n\t * Also rebuilds the system prompt to reflect the new tool set.\n\t * Changes take effect on the next agent turn.\n\t */\n\tsetActiveToolsByName(toolNames: string[]): void {\n\t\tconst tools: AgentTool[] = [];\n\t\tconst validToolNames: string[] = [];\n\t\tfor (const name of toolNames) {\n\t\t\tif (name === \"compress_context\" && this._contextCompressionEnabledOverride === false) continue;\n\t\t\tconst tool = this._toolRegistry.get(name);\n\t\t\tif (tool) {\n\t\t\t\ttools.push(tool);\n\t\t\t\tvalidToolNames.push(name);\n\t\t\t}\n\t\t}\n\t\tthis.agent.state.tools = tools;\n\t\tif (!validToolNames.includes(\"compress_context\")) this._compressionDetector.reset();\n\n\t\t// Rebuild base system prompt with new tool set\n\t\tthis._baseSystemPrompt = this._rebuildSystemPrompt(validToolNames);\n\t\tthis.agent.state.systemPrompt = this._systemPromptOverride ?? this._baseSystemPrompt;\n\t}\n\n\t/** Whether compaction or branch summarization is currently running */\n\tget isCompacting(): boolean {\n\t\treturn (\n\t\t\tthis._compactionInFlight !== undefined ||\n\t\t\tthis._autoCompactionAbortController !== undefined ||\n\t\t\tthis._compactionAbortController !== undefined ||\n\t\t\tthis._branchSummaryAbortController !== undefined\n\t\t);\n\t}\n\n\t/** All messages including custom types like BashExecutionMessage */\n\tget messages(): AgentMessage[] {\n\t\treturn this.agent.state.messages;\n\t}\n\n\t/** Current steering mode */\n\tget steeringMode(): QueueMode {\n\t\treturn this.agent.steeringMode;\n\t}\n\n\t/** Current follow-up mode */\n\tget followUpMode(): QueueMode {\n\t\treturn this.agent.followUpMode;\n\t}\n\n\t/** Current session reference, or undefined for ephemeral sessions. */\n\tget sessionReference(): string | undefined {\n\t\treturn this.session.getSessionReference();\n\t}\n\n\t/** Current local session file path, or undefined for non-file-backed sessions. Prefer sessionReference for backend-neutral code. */\n\tget sessionFile(): string | undefined {\n\t\treturn this.sessionReference;\n\t}\n\n\t/** Current session ID */\n\tget sessionId(): string {\n\t\treturn this.session.getSessionId();\n\t}\n\n\t/** Current session display name, if set */\n\tget sessionName(): string | undefined {\n\t\treturn this.session.getSessionName();\n\t}\n\n\t/** Scoped models for cycling (from --models flag) */\n\tget scopedModels(): ReadonlyArray<ScopedModel> {\n\t\treturn this._scopedModels;\n\t}\n\n\t/** Update scoped models for cycling */\n\tsetScopedModels(scopedModels: ScopedModel[]): void {\n\t\tthis._scopedModels = scopedModels;\n\t\tthis._refreshModelDependentRuntime();\n\t}\n\n\t/** File-based prompt templates */\n\tget promptTemplates(): ReadonlyArray<PromptTemplate> {\n\t\treturn this._resourceLoader.getPrompts().prompts;\n\t}\n\n\tprivate _normalizePromptSnippet(text: string | undefined): string | undefined {\n\t\tif (!text) return undefined;\n\t\tconst oneLine = text\n\t\t\t.replace(/[\\r\\n]+/g, \" \")\n\t\t\t.replace(/\\s+/g, \" \")\n\t\t\t.trim();\n\t\treturn oneLine.length > 0 ? oneLine : undefined;\n\t}\n\n\tprivate _normalizePromptGuidelines(guidelines: string[] | undefined): string[] {\n\t\tif (!guidelines || guidelines.length === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst unique = new Set<string>();\n\t\tfor (const guideline of guidelines) {\n\t\t\tconst normalized = guideline.trim();\n\t\t\tif (normalized.length > 0) {\n\t\t\t\tunique.add(normalized);\n\t\t\t}\n\t\t}\n\t\treturn Array.from(unique);\n\t}\n\n\tprivate _rebuildSystemPrompt(toolNames: string[]): string {\n\t\tconst validToolNames = toolNames.filter((name) => this._toolRegistry.has(name));\n\t\tconst toolSnippets: Record<string, string> = {};\n\t\tconst promptGuidelines: string[] = [];\n\t\tfor (const name of validToolNames) {\n\t\t\tconst snippet = this._toolPromptSnippets.get(name);\n\t\t\tif (snippet) {\n\t\t\t\ttoolSnippets[name] = snippet;\n\t\t\t}\n\n\t\t\tconst toolGuidelines = this._toolPromptGuidelines.get(name);\n\t\t\tif (toolGuidelines) {\n\t\t\t\tpromptGuidelines.push(...toolGuidelines);\n\t\t\t}\n\t\t}\n\n\t\tconst loaderSystemPrompt = this._resourceLoader.getSystemPrompt();\n\t\tconst loaderAppendSystemPrompt = this._resourceLoader.getAppendSystemPrompt();\n\t\tconst appendSystemPromptParts = [...loaderAppendSystemPrompt];\n\t\tconst availableToolsPrompt = this._buildAvailableToolsPrompt();\n\t\tif (availableToolsPrompt) {\n\t\t\tappendSystemPromptParts.push(availableToolsPrompt);\n\t\t}\n\t\tconst appendSystemPrompt = appendSystemPromptParts.length > 0 ? appendSystemPromptParts.join(\"\\n\\n\") : undefined;\n\t\tconst loadedSkills = this.getRegisteredSkills();\n\t\tconst loadedRules = this.getRegisteredRules();\n\t\tconst loadedContextFiles = this._resourceLoader.getAgentsFiles().agentsFiles;\n\n\t\tthis._baseSystemPromptOptions = {\n\t\t\tcwd: this._toolOperations?.cwd ?? this._cwd,\n\t\t\tskills: loadedSkills,\n\t\t\trules: loadedRules,\n\t\t\tcontextFiles: loadedContextFiles,\n\t\t\tcustomPrompt: loaderSystemPrompt,\n\t\t\tappendSystemPrompt,\n\t\t\tselectedTools: validToolNames,\n\t\t\ttoolSnippets,\n\t\t\tpromptGuidelines,\n\t\t};\n\t\treturn buildSystemPrompt(this._baseSystemPromptOptions);\n\t}\n\n\tprivate _buildAvailableToolsPrompt(): string | undefined {\n\t\tconst tools = [\n\t\t\t...Array.from(this._sessionTools.values()).filter((tool) => tool.lazy),\n\t\t\t...this._extensionRunner.getAvailableRegisteredTools(),\n\t\t].filter((tool) => this._isToolPermitted(tool.definition.name));\n\t\tif (tools.length === 0) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst lines = [\n\t\t\t\"<available-tools>\",\n\t\t\t\"These tools are session-scoped and can be loaded into the active tool context with load_tool when needed, then unloaded with unload_tool when no longer needed.\",\n\t\t];\n\t\tfor (const tool of tools) {\n\t\t\tconst loaded = tool.loaded ? ' loaded=\"true\"' : \"\";\n\t\t\tlines.push(\n\t\t\t\t` <tool name=\"${escapeXmlAttribute(tool.definition.name)}\"${loaded} description=\"${escapeXmlAttribute(\n\t\t\t\t\ttool.definition.description,\n\t\t\t\t)}\" />`,\n\t\t\t);\n\t\t}\n\t\tlines.push(\"</available-tools>\");\n\t\treturn lines.join(\"\\n\");\n\t}\n\n\tprivate _refreshBaseSystemPrompt(): void {\n\t\tthis._baseSystemPrompt = this._rebuildSystemPrompt(this.getActiveToolNames());\n\t\tthis.agent.state.systemPrompt = this._systemPromptOverride ?? this._baseSystemPrompt;\n\t}\n\n\tprivate _getSubagentModels(): Model<any>[] {\n\t\treturn this._scopedModels.length > 0\n\t\t\t? this._scopedModels.map((scoped) => scoped.model)\n\t\t\t: this._modelRegistry.getAvailable();\n\t}\n\n\tprivate _resolveSubagentModelHint(hint: SubagentModelHint): string | undefined {\n\t\tconst currentModel = this.model;\n\t\tif (!currentModel) return undefined;\n\t\tconst formatModel = (model: Model<any>): string => `${model.provider}/${model.id}`;\n\t\tif (hint === \"same-as-parent\") return formatModel(currentModel);\n\t\tconst candidates = this._getSubagentModels().filter((model) => model.provider === currentModel.provider);\n\t\tif (candidates.length === 0) return undefined;\n\t\tconst cost = (model: Model<any>): number => model.cost.input + model.cost.output;\n\t\tconst byCost = [...candidates].sort((a, b) => cost(a) - cost(b));\n\t\tif (hint === \"cheapest\" || hint === \"fastest\") return formatModel(byCost[0]);\n\t\tif (hint === \"large-context\") {\n\t\t\treturn formatModel([...candidates].sort((a, b) => b.contextWindow - a.contextWindow)[0]);\n\t\t}\n\t\tif (hint === \"best-reasoning\") {\n\t\t\tconst reasoning = candidates.filter((model) => model.reasoning);\n\t\t\treturn formatModel(\n\t\t\t\t(reasoning.length > 0 ? reasoning : candidates).sort(\n\t\t\t\t\t(a, b) => b.contextWindow + b.maxTokens - (a.contextWindow + a.maxTokens),\n\t\t\t\t)[0],\n\t\t\t);\n\t\t}\n\t\tif (hint === \"strongest\") {\n\t\t\treturn formatModel(\n\t\t\t\t[...candidates].sort(\n\t\t\t\t\t(a, b) =>\n\t\t\t\t\t\tNumber(b.reasoning) - Number(a.reasoning) ||\n\t\t\t\t\t\tb.contextWindow + b.maxTokens - (a.contextWindow + a.maxTokens),\n\t\t\t\t)[0],\n\t\t\t);\n\t\t}\n\t\tconst reasoningByCost = byCost.find((model) => model.reasoning);\n\t\treturn formatModel(reasoningByCost ?? byCost[0]);\n\t}\n\n\tprivate _refreshModelDependentRuntime(): void {\n\t\tif (!this._baseToolsOverride && this._baseToolDefinitions.has(\"subagent\")) {\n\t\t\tthis._baseToolDefinitions.set(\n\t\t\t\t\"subagent\",\n\t\t\t\tcreateSubagentToolDefinition({\n\t\t\t\t\trunner: this._subagentRunner,\n\t\t\t\t\tmodelCatalog: formatSubagentModelCatalog(this.model, this._getSubagentModels()),\n\t\t\t\t\tresolveModelHint: (hint) => this._resolveSubagentModelHint(hint),\n\t\t\t\t\ttrustProjectAgents: this._trustProjectAgents,\n\t\t\t\t\trunRegistry: this._subagentRunRegistry,\n\t\t\t\t\tconfigRegistry: this._subagentConfigRegistry,\n\t\t\t\t}) as unknown as ToolDefinition,\n\t\t\t);\n\t\t\tthis._refreshToolRegistry({ activeToolNames: this.getActiveToolNames() });\n\t\t\treturn;\n\t\t}\n\t\tthis._refreshBaseSystemPrompt();\n\t}\n\n\t// =========================================================================\n\t// Prompting\n\t// =========================================================================\n\n\tprivate async _continueAgentIfReady(): Promise<boolean> {\n\t\tif (this._sandboxToolOperations?.isDisconnected()) return false;\n\t\tconst lastMessage = this.agent.state.messages.at(-1);\n\t\tif (lastMessage?.role === \"assistant\" && !this.agent.hasQueuedMessages()) {\n\t\t\treturn false;\n\t\t}\n\t\tawait this.agent.continue();\n\t\treturn true;\n\t}\n\n\tprivate _runAgentPrompt(messages: AgentMessage | AgentMessage[]): Promise<void> {\n\t\tif (this._sandboxToolOperations?.isDisconnected()) {\n\t\t\tthrow new Error(\"Sandbox connection lost; reconnect before prompting\");\n\t\t}\n\t\treturn this._runActivity(\"agent run\", async () => {\n\t\t\ttry {\n\t\t\t\tawait this.agent.prompt(messages);\n\t\t\t\twhile (!this._sandboxToolOperations?.isDisconnected() && (await this._handlePostAgentRun())) {\n\t\t\t\t\tif (!(await this._continueAgentIfReady())) break;\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tthis._systemPromptOverride = undefined;\n\t\t\t\tthis._flushPendingBashMessages();\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate _finishFailedRetry(message: AssistantMessage): void {\n\t\tif (message.stopReason !== \"error\" || this._retryAttempt <= 0) return;\n\t\tthis._emit({\n\t\t\ttype: \"auto_retry_end\",\n\t\t\tsuccess: false,\n\t\t\tattempt: this._retryAttempt,\n\t\t\tfinalError: message.errorMessage,\n\t\t});\n\t\tthis._retryAttempt = 0;\n\t}\n\tprivate _resetStopHookContinuationState(): void {\n\t\tthis._stopHookContinuations = 0;\n\t\tthis._stopHookProgress = undefined;\n\t}\n\n\tprivate _recordStopHookProgress(progress: number | undefined): boolean {\n\t\tif (progress === undefined) {\n\t\t\tthis._stopHookProgress = undefined;\n\t\t\treturn false;\n\t\t}\n\t\tif (!this._stopHookProgress || progress < this._stopHookProgress.best) {\n\t\t\tthis._stopHookProgress = { best: progress, consecutiveNonImprovingCalls: 0 };\n\t\t\treturn true;\n\t\t}\n\t\tthis._stopHookProgress.consecutiveNonImprovingCalls++;\n\t\treturn this._stopHookProgress.consecutiveNonImprovingCalls <= STOP_HOOK_PROGRESS_REGRESSION_TOLERANCE;\n\t}\n\n\tprivate async _runStopFailureHook(message: AssistantMessage): Promise<void> {\n\t\tconst details = message.errorMessage ?? \"Assistant response failed\";\n\t\tawait this._runHook({\n\t\t\t...this._hookCommon(\"StopFailure\"),\n\t\t\thook_event_name: \"StopFailure\",\n\t\t\terror: classifyStopFailure(details),\n\t\t\terror_details: details,\n\t\t\tlast_assistant_message: getAssistantText(message),\n\t\t});\n\t\tthis._resetStopHookContinuationState();\n\t}\n\n\tprivate async _runStopHook(message: AssistantMessage): Promise<boolean> {\n\t\tconst stopResult = await this._runHook({\n\t\t\t...this._hookCommon(\"Stop\"),\n\t\t\thook_event_name: \"Stop\",\n\t\t\tstop_hook_active: this._stopHookContinuations > 0,\n\t\t\tlast_assistant_message: getAssistantText(message),\n\t\t});\n\t\tif (!stopResult.continue) {\n\t\t\t// Aggregate termination is not a request for another model turn.\n\t\t\tthis._resetStopHookContinuationState();\n\t\t\treturn false;\n\t\t}\n\t\tif (!stopResult.blocked) {\n\t\t\tthis._resetStopHookContinuationState();\n\t\t\treturn false;\n\t\t}\n\t\tconst progressAllowsExtendedContinuation = this._recordStopHookProgress(stopResult.stopContinuationProgress);\n\t\tif (this._stopHookContinuations >= DEFAULT_STOP_HOOK_CONTINUATION_LIMIT && !progressAllowsExtendedContinuation) {\n\t\t\tthis._reportHookDiagnostic({\n\t\t\t\tlevel: \"warning\",\n\t\t\t\tcode: \"continuation-cap\",\n\t\t\t\tevent: \"Stop\",\n\t\t\t\tmessage: `Stop hook continuation limit (${DEFAULT_STOP_HOOK_CONTINUATION_LIMIT}) reached without recent progress; settling the session`,\n\t\t\t});\n\t\t\tthis._resetStopHookContinuationState();\n\t\t\treturn false;\n\t\t}\n\t\tconst values = [stopResult.reason, ...stopResult.additionalContext].filter((value): value is string =>\n\t\t\tBoolean(value),\n\t\t);\n\t\tconst feedback =\n\t\t\tcapModelVisibleHookField([...new Set(values)].join(\"\\n\\n\")) || \"A Stop hook requested another model turn.\";\n\t\tthis._stopHookContinuations++;\n\t\tthis.agent.followUp({\n\t\t\trole: \"custom\",\n\t\t\tcustomType: \"claude-stop-hook-feedback\",\n\t\t\tcontent: [{ type: \"text\", text: feedback }],\n\t\t\tdisplay: false,\n\t\t\ttimestamp: Date.now(),\n\t\t});\n\t\treturn true;\n\t}\n\n\tprivate async _handlePostAgentRun(): Promise<boolean> {\n\t\tconst message = this._lastAssistantMessage;\n\t\tthis._lastAssistantMessage = undefined;\n\t\tif (!message) return false;\n\n\t\t// A user abort is not an assistant stop/failure lifecycle event.\n\t\tif (message.stopReason === \"aborted\") {\n\t\t\tthis._resetStopHookContinuationState();\n\t\t\treturn this.agent.hasQueuedMessages();\n\t\t}\n\t\tif (this._isRetryableError(message) && (await this._prepareRetry(message))) return true;\n\t\tthis._finishFailedRetry(message);\n\t\tif (await this._checkCompaction(message)) return true;\n\t\tif (message.stopReason === \"error\") await this._runStopFailureHook(message);\n\t\telse if (await this._runStopHook(message)) return true;\n\n\t\t// The agent loop drains both queues before emitting agent_end. Any messages\n\t\t// here were queued by agent_end extension handlers and need a continuation.\n\t\treturn this.agent.hasQueuedMessages();\n\t}\n\n\tgetStructuredResponse<TSchemaValue extends TSchema>(\n\t\toptions: StructuredResponseOptions<TSchemaValue>,\n\t): Promise<StructuredResponse<Static<TSchemaValue>>> {\n\t\treturn this._runActivity(\"structured response\", () => this._getStructuredResponse(options));\n\t}\n\tprivate _requireStructuredResponseModel(): Model<Api> {\n\t\tif (this._isAgentRunActive) {\n\t\t\tthrow new Error(\"Agent is already processing. Wait for completion before requesting structured output.\");\n\t\t}\n\t\tconst model = this.model;\n\t\tif (!model) throw new Error(formatNoModelSelectedMessage());\n\t\tif (!this._modelRegistry.hasConfiguredAuth(model)) {\n\t\t\tthrow new Error(formatNoApiKeyFoundMessage(model.provider));\n\t\t}\n\t\treturn model;\n\t}\n\tprivate async _getStructuredResponse<TSchemaValue extends TSchema>(\n\t\toptions: StructuredResponseOptions<TSchemaValue>,\n\t): Promise<StructuredResponse<Static<TSchemaValue>>> {\n\t\tconst model = this._requireStructuredResponseModel();\n\n\t\tconst schemaName = options.name ?? DEFAULT_STRUCTURED_RESPONSE_TOOL_NAME;\n\t\tconst tool: Tool<TSchemaValue> = {\n\t\t\tname: schemaName,\n\t\t\tdescription:\n\t\t\t\toptions.description ??\n\t\t\t\t\"Return the requested structured response. Call this tool exactly once with arguments matching the schema.\",\n\t\t\tparameters: options.schema,\n\t\t};\n\t\tconst lastAssistant = this._findLastAssistantMessage();\n\t\tif (!lastAssistant) {\n\t\t\tthrow new Error(\"No assistant response is available to structure.\");\n\t\t}\n\n\t\tconst direct = this._tryParseStructuredAssistantText(tool, lastAssistant);\n\t\tif (direct.ok) {\n\t\t\tthis._appendStructuredInternalEntry(\n\t\t\t\t\"result\",\n\t\t\t\tschemaName,\n\t\t\t\t0,\n\t\t\t\t\"Validated structured response from assistant JSON.\",\n\t\t\t\t{\n\t\t\t\t\tstage: \"result\",\n\t\t\t\t\tschemaName,\n\t\t\t\t\tattempt: 0,\n\t\t\t\t\tsource: \"json\",\n\t\t\t\t},\n\t\t\t);\n\t\t\treturn { output: direct.output, attempts: 0, source: \"json\", message: lastAssistant };\n\t\t}\n\n\t\tconst maxCorrections = options.maxCorrections ?? DEFAULT_STRUCTURED_RESPONSE_CORRECTIONS;\n\t\tconst maxAttempts = maxCorrections + 1;\n\t\tconst { apiKey, headers } = await this._getRequiredRequestAuth(model);\n\t\tconst messages = this._buildStructuredResponseMessages(options.scope ?? \"latest\", lastAssistant, schemaName);\n\t\tlet lastError = direct.error;\n\n\t\tfor (let attempt = 1; attempt <= maxAttempts; attempt++) {\n\t\t\tconst requestText =\n\t\t\t\tattempt === 1\n\t\t\t\t\t? `Extract the structured response by calling ${schemaName} exactly once.`\n\t\t\t\t\t: `Correct the structured response by calling ${schemaName} exactly once.\\n\\nValidation error:\\n${lastError}`;\n\t\t\tconst userMessage: Message = {\n\t\t\t\trole: \"user\",\n\t\t\t\tcontent: [{ type: \"text\", text: requestText }],\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t};\n\t\t\tmessages.push(userMessage);\n\t\t\tthis._appendStructuredInternalEntry(\"request\", schemaName, attempt, requestText, {\n\t\t\t\tstage: \"request\",\n\t\t\t\tschemaName,\n\t\t\t\tattempt,\n\t\t\t\tvalidationError: attempt === 1 ? undefined : lastError,\n\t\t\t});\n\n\t\t\tconst responseStream = await this.agent.streamFn(\n\t\t\t\tmodel,\n\t\t\t\t{\n\t\t\t\t\tsystemPrompt:\n\t\t\t\t\t\t\"You are a structured data extraction assistant. Do not answer in prose. Use the provided tool to return the structured data.\",\n\t\t\t\t\tmessages,\n\t\t\t\t\ttools: [tool],\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tapiKey,\n\t\t\t\t\theaders,\n\t\t\t\t\tsessionId: this.agent.sessionId,\n\t\t\t\t\ttransport: this.agent.transport,\n\t\t\t\t\tthinkingBudgets: this.agent.thinkingBudgets,\n\t\t\t\t\tmaxRetryDelayMs: this.agent.maxRetryDelayMs,\n\t\t\t\t},\n\t\t\t);\n\t\t\tconst assistantMessage = await responseStream.result();\n\t\t\tmessages.push(assistantMessage);\n\t\t\tthis._appendStructuredInternalEntry(\n\t\t\t\t\"assistant\",\n\t\t\t\tschemaName,\n\t\t\t\tattempt,\n\t\t\t\tthis._formatStructuredAssistantLog(assistantMessage),\n\t\t\t\t{\n\t\t\t\t\tstage: \"assistant\",\n\t\t\t\t\tschemaName,\n\t\t\t\t\tattempt,\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tconst attemptResult = this._evaluateStructuredResponseAttempt({\n\t\t\t\ttool,\n\t\t\t\tassistantMessage,\n\t\t\t\tmessages,\n\t\t\t\tschemaName,\n\t\t\t\tattempt,\n\t\t\t});\n\t\t\tif (attemptResult.ok) return attemptResult.response;\n\t\t\tlastError = attemptResult.error;\n\t\t}\n\n\t\tthrow new Error(`Structured response validation failed after ${maxAttempts} attempt(s):\\n${lastError}`);\n\t}\n\tprivate _evaluateStructuredResponseAttempt<TSchemaValue extends TSchema>({\n\t\ttool,\n\t\tassistantMessage,\n\t\tmessages,\n\t\tschemaName,\n\t\tattempt,\n\t}: StructuredAttemptContext<TSchemaValue>): StructuredAttemptResult<Static<TSchemaValue>> {\n\t\tconst toolCall = assistantMessage.content.find(\n\t\t\t(block): block is ToolCall => block.type === \"toolCall\" && block.name === schemaName,\n\t\t);\n\t\tif (toolCall) {\n\t\t\tconst validation = this._validateStructuredArguments(tool, toolCall.arguments);\n\t\t\tif (validation.ok) {\n\t\t\t\tthis._appendStructuredInternalEntry(\"result\", schemaName, attempt, \"Validated structured tool response.\", {\n\t\t\t\t\tstage: \"result\",\n\t\t\t\t\tschemaName,\n\t\t\t\t\tattempt,\n\t\t\t\t\tsource: \"tool\",\n\t\t\t\t});\n\t\t\t\treturn {\n\t\t\t\t\tok: true,\n\t\t\t\t\tresponse: { output: validation.output, attempts: attempt, source: \"tool\", message: assistantMessage },\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst toolResult: ToolResultMessage = {\n\t\t\t\trole: \"toolResult\",\n\t\t\t\ttoolCallId: toolCall.id,\n\t\t\t\ttoolName: toolCall.name,\n\t\t\t\tcontent: [{ type: \"text\", text: validation.error }],\n\t\t\t\tisError: true,\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t};\n\t\t\tmessages.push(toolResult);\n\t\t\tthis._appendStructuredInternalEntry(\"tool_result\", schemaName, attempt, validation.error, {\n\t\t\t\tstage: \"tool_result\",\n\t\t\t\tschemaName,\n\t\t\t\tattempt,\n\t\t\t\tvalidationError: validation.error,\n\t\t\t});\n\t\t\treturn { ok: false, error: validation.error };\n\t\t}\n\n\t\tconst textValidation = this._tryParseStructuredAssistantText(tool, assistantMessage);\n\t\tif (!textValidation.ok) return textValidation;\n\t\tthis._appendStructuredInternalEntry(\"result\", schemaName, attempt, \"Validated structured JSON response.\", {\n\t\t\tstage: \"result\",\n\t\t\tschemaName,\n\t\t\tattempt,\n\t\t\tsource: \"json\",\n\t\t});\n\t\treturn {\n\t\t\tok: true,\n\t\t\tresponse: { output: textValidation.output, attempts: attempt, source: \"json\", message: assistantMessage },\n\t\t};\n\t}\n\n\tprivate _buildStructuredResponseMessages(\n\t\tscope: StructuredResponseScope,\n\t\tlastAssistant: AssistantMessage,\n\t\tschemaName: string,\n\t): Message[] {\n\t\tif (scope === \"conversation\") {\n\t\t\treturn this.agent.state.messages.filter(\n\t\t\t\t(message): message is Message =>\n\t\t\t\t\tmessage.role === \"user\" || message.role === \"assistant\" || message.role === \"toolResult\",\n\t\t\t);\n\t\t}\n\n\t\treturn [\n\t\t\t{\n\t\t\t\trole: \"user\",\n\t\t\t\tcontent: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\ttext:\n\t\t\t\t\t\t\t`Extract structured data from the latest assistant response below. Call ${schemaName} exactly once.\\n\\n` +\n\t\t\t\t\t\t\t`<assistant_response>\\n${getAssistantText(lastAssistant)}\\n</assistant_response>`,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t},\n\t\t];\n\t}\n\n\tprivate _tryParseStructuredAssistantText<TSchemaValue extends TSchema>(\n\t\ttool: Tool<TSchemaValue>,\n\t\tmessage: AssistantMessage,\n\t): StructuredValidationResult<Static<TSchemaValue>> {\n\t\tconst text = getAssistantText(message);\n\t\tfor (const candidate of extractJsonCandidates(text)) {\n\t\t\tif (!isRecord(candidate)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst validation = this._validateStructuredArguments(tool, candidate);\n\t\t\tif (validation.ok) {\n\t\t\t\treturn validation;\n\t\t\t}\n\t\t}\n\t\treturn { ok: false, error: \"Assistant response did not contain valid JSON matching the requested schema.\" };\n\t}\n\n\tprivate _validateStructuredArguments<TSchemaValue extends TSchema>(\n\t\ttool: Tool<TSchemaValue>,\n\t\targuments_: Record<string, unknown>,\n\t): StructuredValidationResult<Static<TSchemaValue>> {\n\t\ttry {\n\t\t\tconst output = validateToolArguments(tool, {\n\t\t\t\ttype: \"toolCall\",\n\t\t\t\tid: \"structured-response-validation\",\n\t\t\t\tname: tool.name,\n\t\t\t\targuments: arguments_,\n\t\t\t}) as Static<TSchemaValue>;\n\t\t\treturn { ok: true, output };\n\t\t} catch (error) {\n\t\t\treturn { ok: false, error: error instanceof Error ? error.message : String(error) };\n\t\t}\n\t}\n\n\tprivate _appendStructuredInternalEntry(\n\t\tstage: StructuredInternalStage,\n\t\tschemaName: string,\n\t\tattempt: number,\n\t\tcontent: string,\n\t\tdetails: StructuredInternalDetails,\n\t): void {\n\t\tthis.session.appendCustomMessageEntry(STRUCTURED_RESPONSE_INTERNAL_CUSTOM_TYPE, content, false, {\n\t\t\t...details,\n\t\t\tstage,\n\t\t\tschemaName,\n\t\t\tattempt,\n\t\t});\n\t}\n\n\tprivate _formatStructuredAssistantLog(message: AssistantMessage): string {\n\t\tconst parts = message.content.map((block) => {\n\t\t\tif (block.type === \"text\") {\n\t\t\t\treturn block.text;\n\t\t\t}\n\t\t\tif (block.type === \"thinking\") {\n\t\t\t\treturn \"[thinking omitted]\";\n\t\t\t}\n\t\t\treturn `tool:${block.name} ${JSON.stringify(block.arguments)}`;\n\t\t});\n\t\treturn parts.join(\"\\n\");\n\t}\n\n\tprivate async _runPromptAdmission(state: PromptPreparationState): Promise<boolean> {\n\t\tif (this._disposed) throw new Error(\"Agent session is disposed\");\n\t\t// Reject every prompt submission, including extension commands, before hooks or message mutation.\n\t\t// _compactionInFlight is set synchronously, before manual compaction aborts an active agent run.\n\t\tif (this._compactionInFlight !== undefined) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Cannot submit a prompt while compaction is in progress. Wait for compaction to finish and retry.\",\n\t\t\t);\n\t\t}\n\t\tstate.queueIntoActiveRun = this._hasActiveAgentRun;\n\t\tif (!state.queueIntoActiveRun && this._hasPromptAdmission()) {\n\t\t\tthrow new Error(\"Agent is already processing. Wait for prompt admission to complete.\");\n\t\t}\n\n\t\t// Admission is synchronous and precedes hooks so concurrent first prompts cannot race SessionStart.\n\t\tstate.promptActivity = this._beginActivity(\"prompt admission\");\n\t\tthis._resetStopHookContinuationState();\n\t\tstate.sessionContext = await this._ensureHookSessionStarted(state.promptId);\n\t\tif (state.sessionContext) state.hookContextMessages.push(state.sessionContext);\n\t\tconst promptHook = await this._runHook({\n\t\t\t...this._hookCommon(\"UserPromptSubmit\", state.promptId),\n\t\t\thook_event_name: \"UserPromptSubmit\",\n\t\t\tprompt: state.text,\n\t\t});\n\t\tif (promptHook.blocked || !promptHook.continue) {\n\t\t\tthrow new Error(promptHook.reason ?? \"Prompt submission blocked by hook\");\n\t\t}\n\t\tconst promptContext = this._hookContextMessage(promptHook.additionalContext);\n\t\tif (promptContext) state.hookContextMessages.push(promptContext);\n\n\t\t// Hooks run before extension commands, input interception, and template expansion.\n\t\tif (state.expandPromptTemplates && state.text.startsWith(\"/\")) {\n\t\t\tconst handled = await this._tryExecuteExtensionCommand(state.text, () => {\n\t\t\t\t// Session-replacing extension commands must not wait on their own admission token.\n\t\t\t\tif (state.promptActivity) this._endActivity(state.promptActivity);\n\t\t\t\tstate.promptActivity = undefined;\n\t\t\t});\n\t\t\tif (handled) {\n\t\t\t\tstate.options?.preflightResult?.(true);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\tif (this._disposed) throw new Error(\"Agent session is disposed\");\n\t\treturn false;\n\t}\n\n\tprivate async _interceptPromptInput(state: PromptPreparationState): Promise<boolean> {\n\t\tif (!this._extensionRunner.hasHandlers(\"input\")) return false;\n\t\tconst inputResult = await this._runAsyncBarrierCallback(\"extension input handler\", () =>\n\t\t\tthis._extensionRunner.emitInput(\n\t\t\t\tstate.currentText,\n\t\t\t\tstate.currentImages,\n\t\t\t\tstate.options?.source ?? \"interactive\",\n\t\t\t\tstate.queueIntoActiveRun ? state.options?.streamingBehavior : undefined,\n\t\t\t),\n\t\t);\n\t\tif (inputResult.action === \"handled\") {\n\t\t\tstate.options?.preflightResult?.(true);\n\t\t\treturn true;\n\t\t}\n\t\tif (inputResult.action === \"transform\") {\n\t\t\tstate.currentText = inputResult.text;\n\t\t\tstate.currentImages = inputResult.images ?? state.currentImages;\n\t\t}\n\t\treturn false;\n\t}\n\n\tprivate _expandPromptInput(state: PromptPreparationState): void {\n\t\tstate.queueIntoActiveRun = this._hasActiveAgentRun;\n\t\tif (!state.queueIntoActiveRun && this._hasPromptAdmission(state.promptActivity)) {\n\t\t\tthrow new Error(\"Agent is already processing. Wait for prompt admission to complete.\");\n\t\t}\n\t\tstate.expandedText = state.currentText;\n\t\tif (!state.expandPromptTemplates) return;\n\t\tstate.expandedText = this._expandSkillCommand(state.expandedText);\n\t\tstate.expandedText = this._expandRuleCommand(state.expandedText);\n\t\tstate.expandedText = expandPromptTemplate(state.expandedText, [...this.promptTemplates]);\n\t}\n\n\tprivate async _queuePromptIntoActiveRun(state: PromptPreparationState): Promise<boolean> {\n\t\tif (!state.queueIntoActiveRun) return false;\n\t\tconst streamingBehavior = state.options?.streamingBehavior;\n\t\tif (!streamingBehavior) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Agent is already processing. Specify streamingBehavior ('steer' or 'followUp') to queue the message.\",\n\t\t\t);\n\t\t}\n\t\tif (streamingBehavior === \"followUp\") {\n\t\t\tawait this._queueFollowUp(state.expandedText, state.currentImages, state.hookContextMessages, state.promptId);\n\t\t} else {\n\t\t\tawait this._queueSteer(state.expandedText, state.currentImages, state.hookContextMessages, state.promptId);\n\t\t}\n\t\tif (state.sessionContext) this._pendingHookSessionStartContext = undefined;\n\t\tstate.options?.preflightResult?.(true);\n\t\treturn true;\n\t}\n\n\tprivate _validatePromptModelAuthentication(): void {\n\t\tconst model = this.model;\n\t\tif (!model) throw new Error(formatNoModelSelectedMessage());\n\t\tif (this._modelRegistry.hasConfiguredAuth(model)) return;\n\t\tif (this._modelRegistry.isUsingOAuth(model)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Authentication failed for \"${model.provider}\". ` +\n\t\t\t\t\t`Credentials may have expired or network is unavailable. ` +\n\t\t\t\t\t`Run '/login ${model.provider}' to re-authenticate.`,\n\t\t\t);\n\t\t}\n\t\tthrow new Error(formatNoApiKeyFoundMessage(model.provider));\n\t}\n\n\tprivate async _resumeAfterPromptCompaction(): Promise<void> {\n\t\tconst lastAssistant = this._findLastAssistantMessage();\n\t\tif (!lastAssistant || !(await this._checkCompaction(lastAssistant, false))) return;\n\t\ttry {\n\t\t\tif (!(await this._continueAgentIfReady())) return;\n\t\t\twhile (await this._handlePostAgentRun()) {\n\t\t\t\tif (!(await this._continueAgentIfReady())) break;\n\t\t\t}\n\t\t} finally {\n\t\t\tthis._flushPendingBashMessages();\n\t\t}\n\t}\n\n\tprivate async _buildPromptMessages(state: PromptPreparationState): Promise<AgentMessage[]> {\n\t\tconst userContent: (TextContent | ImageContent)[] = [{ type: \"text\", text: state.expandedText }];\n\t\tif (state.currentImages) userContent.push(...state.currentImages);\n\t\tconst messages: AgentMessage[] = [\n\t\t\t{ role: \"user\", content: userContent, timestamp: Date.now() },\n\t\t\t...state.hookContextMessages,\n\t\t\t...this._pendingNextTurnMessages,\n\t\t];\n\t\tif (state.sessionContext) this._pendingHookSessionStartContext = undefined;\n\t\tthis._pendingNextTurnMessages = [];\n\n\t\tconst result = await this._runAsyncBarrierCallback(\"extension before_agent_start handler\", () =>\n\t\t\tthis._extensionRunner.emitBeforeAgentStart(\n\t\t\t\tstate.expandedText,\n\t\t\t\tstate.currentImages,\n\t\t\t\tthis._baseSystemPrompt,\n\t\t\t\tthis._baseSystemPromptOptions,\n\t\t\t),\n\t\t);\n\t\tif (result?.messages) {\n\t\t\tfor (const msg of result.messages) {\n\t\t\t\tmessages.push({\n\t\t\t\t\trole: \"custom\",\n\t\t\t\t\tcustomType: msg.customType,\n\t\t\t\t\tcontent: msg.content ?? [],\n\t\t\t\t\tdisplay: msg.display,\n\t\t\t\t\tdetails: msg.details,\n\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tif (result?.systemPrompt !== undefined) {\n\t\t\tthis._systemPromptOverride = result.systemPrompt;\n\t\t\tthis.agent.state.systemPrompt = result.systemPrompt;\n\t\t} else {\n\t\t\tthis._systemPromptOverride = undefined;\n\t\t\tthis.agent.state.systemPrompt = this._baseSystemPrompt;\n\t\t}\n\t\treturn messages;\n\t}\n\n\tprivate async _preparePrompt(state: PromptPreparationState): Promise<AgentMessage[] | undefined> {\n\t\tif (await this._runPromptAdmission(state)) return undefined;\n\t\tif (await this._interceptPromptInput(state)) return undefined;\n\t\tthis._expandPromptInput(state);\n\t\tif (await this._queuePromptIntoActiveRun(state)) return undefined;\n\t\tthis._flushPendingBashMessages();\n\t\tthis._validatePromptModelAuthentication();\n\t\tawait this._resumeAfterPromptCompaction();\n\t\treturn this._buildPromptMessages(state);\n\t}\n\n\t/**\n\t * Send a prompt to the agent.\n\t * - Handles extension commands (registered via the extension API's registerCommand) immediately, even during streaming, when no compaction is active\n\t * - Expands file-based prompt templates by default\n\t * - During streaming, queues via steer() or followUp() based on streamingBehavior option\n\t * - Validates model and API key before sending (when not streaming)\n\t * @throws Error if streaming and no streamingBehavior specified\n\t * @throws Error if no model selected or no API key available (when not streaming)\n\t * @throws Error if manual or extension-requested compaction is in progress\n\t */\n\tasync prompt(text: string, options?: PromptOptions): Promise<void> {\n\t\tif (this._sandboxToolOperations?.isDisconnected()) {\n\t\t\tthrow new Error(\"Sandbox connection lost; reconnect before prompting\");\n\t\t}\n\t\tconst promptId = randomUUID();\n\t\tconst state: PromptPreparationState = {\n\t\t\ttext,\n\t\t\toptions,\n\t\t\tpromptId,\n\t\t\texpandPromptTemplates: options?.expandPromptTemplates ?? true,\n\t\t\thookContextMessages: [],\n\t\t\tpromptActivity: undefined,\n\t\t\tqueueIntoActiveRun: false,\n\t\t\tsessionContext: undefined,\n\t\t\tcurrentText: text,\n\t\t\tcurrentImages: options?.images,\n\t\t\texpandedText: text,\n\t\t};\n\t\ttry {\n\t\t\tlet messages: AgentMessage[] | undefined;\n\t\t\ttry {\n\t\t\t\tmessages = await this._preparePrompt(state);\n\t\t\t} catch (error) {\n\t\t\t\toptions?.preflightResult?.(false);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tif (!messages) return;\n\t\t\toptions?.preflightResult?.(true);\n\t\t\tthis._activePromptId = promptId;\n\t\t\tawait this._runAgentPrompt(messages);\n\t\t} finally {\n\t\t\tif (this._activePromptId === promptId) this._activePromptId = undefined;\n\t\t\tif (state.promptActivity) this._endActivity(state.promptActivity);\n\t\t}\n\t}\n\n\t/**\n\t * Try to execute an extension command. Returns true if command was found and executed.\n\t */\n\tprivate async _tryExecuteExtensionCommand(text: string, onFound?: () => void): Promise<boolean> {\n\t\t// Parse command name and args\n\t\tconst spaceIndex = text.indexOf(\" \");\n\t\tconst commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);\n\t\tconst args = spaceIndex === -1 ? \"\" : text.slice(spaceIndex + 1);\n\n\t\tconst command = this._extensionRunner.getCommand(commandName);\n\t\tif (!command) return false;\n\t\tonFound?.();\n\n\t\t// Get command context from extension runner (includes session control methods)\n\t\tconst ctx = this._extensionRunner.createCommandContext();\n\n\t\ttry {\n\t\t\tawait command.handler(args, ctx);\n\t\t\treturn true;\n\t\t} catch (err) {\n\t\t\t// Emit error via extension runner\n\t\t\tthis._extensionRunner.emitError({\n\t\t\t\textensionPath: `command:${commandName}`,\n\t\t\t\tevent: \"command\",\n\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t});\n\t\t\treturn true;\n\t\t}\n\t}\n\n\t/**\n\t * Expand skill commands (/skill:name args) to their full content.\n\t * Returns the expanded text, or the original text if not a skill command or skill not found.\n\t * Emits errors via extension runner if file read fails.\n\t */\n\tprivate _expandSkillCommand(text: string): string {\n\t\tif (!text.startsWith(\"/skill:\")) return text;\n\n\t\tconst spaceIndex = text.indexOf(\" \");\n\t\tconst skillName = spaceIndex === -1 ? text.slice(7) : text.slice(7, spaceIndex);\n\t\tconst args = spaceIndex === -1 ? \"\" : text.slice(spaceIndex + 1).trim();\n\n\t\tconst skill = this.getRegisteredSkills().find((s) => s.name === skillName);\n\t\tif (!skill) return text; // Unknown skill, pass through\n\n\t\ttry {\n\t\t\tthis._loadAssociatedInstructionTools(skill);\n\t\t\tconst content = skill.content ?? readFileSync(skill.filePath, \"utf-8\");\n\t\t\tconst body = stripFrontmatter(content).trim();\n\t\t\tconst skillBlock = `<skill name=\"${skill.name}\" location=\"${skill.filePath}\">\\nReferences are relative to ${skill.baseDir}.\\n\\n${body}\\n</skill>`;\n\t\t\treturn args ? `${skillBlock}\\n\\n${args}` : skillBlock;\n\t\t} catch (err) {\n\t\t\t// Emit error like extension commands do\n\t\t\tthis._extensionRunner.emitError({\n\t\t\t\textensionPath: skill.filePath,\n\t\t\t\tevent: \"skill_expansion\",\n\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t});\n\t\t\treturn text; // Return original on error\n\t\t}\n\t}\n\n\tprivate _expandRuleCommand(text: string): string {\n\t\tif (!text.startsWith(\"/rule:\")) return text;\n\n\t\tconst spaceIndex = text.indexOf(\" \");\n\t\tconst ruleName = spaceIndex === -1 ? text.slice(6) : text.slice(6, spaceIndex);\n\t\tconst args = spaceIndex === -1 ? \"\" : text.slice(spaceIndex + 1).trim();\n\n\t\tconst rule = this.getRegisteredRules().find((r) => r.name === ruleName);\n\t\tif (!rule) return text; // Unknown rule, pass through\n\n\t\ttry {\n\t\t\tthis._loadAssociatedInstructionTools(rule);\n\t\t\tconst content = rule.content ?? readFileSync(rule.filePath, \"utf-8\");\n\t\t\tconst body = stripFrontmatter(content).trim();\n\t\t\tconst ruleBlock = `<rule name=\"${rule.name}\" location=\"${rule.filePath}\">\\nReferences are relative to ${rule.baseDir}.\\n\\n${body}\\n</rule>`;\n\t\t\treturn args ? `${ruleBlock}\\n\\n${args}` : ruleBlock;\n\t\t} catch (err) {\n\t\t\tthis._extensionRunner.emitError({\n\t\t\t\textensionPath: rule.filePath,\n\t\t\t\tevent: \"rule_expansion\",\n\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t});\n\t\t\treturn text;\n\t\t}\n\t}\n\n\t/**\n\t * Queue a steering message while the agent is running.\n\t * Delivered after the current assistant turn finishes executing its tool calls,\n\t * before the next LLM call.\n\t * Expands skill/rule commands and prompt templates. Errors on extension commands.\n\t * @param images Optional image attachments to include with the message\n\t * @throws Error if text is an extension command\n\t */\n\tasync steer(text: string, images?: ImageContent[]): Promise<void> {\n\t\t// Check for extension commands (cannot be queued)\n\t\tif (text.startsWith(\"/\")) {\n\t\t\tthis._throwIfExtensionCommand(text);\n\t\t}\n\t\tif (!this._hasActiveAgentRun) throw new Error(\"Cannot steer while the agent is idle\");\n\n\t\t// Expand skill/rule commands and prompt templates\n\t\tlet expandedText = this._expandSkillCommand(text);\n\t\texpandedText = this._expandRuleCommand(expandedText);\n\t\texpandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);\n\n\t\tawait this._queueSteer(expandedText, images);\n\t}\n\n\t/**\n\t * Queue a follow-up message to be processed after the agent finishes.\n\t * Delivered only when agent has no more tool calls or steering messages.\n\t * Expands skill/rule commands and prompt templates. Errors on extension commands.\n\t * @param images Optional image attachments to include with the message\n\t * @throws Error if text is an extension command\n\t */\n\tasync followUp(text: string, images?: ImageContent[]): Promise<void> {\n\t\t// Check for extension commands (cannot be queued)\n\t\tif (text.startsWith(\"/\")) {\n\t\t\tthis._throwIfExtensionCommand(text);\n\t\t}\n\t\tif (!this._hasActiveAgentRun) throw new Error(\"Cannot queue a follow-up while the agent is idle\");\n\n\t\t// Expand skill/rule commands and prompt templates\n\t\tlet expandedText = this._expandSkillCommand(text);\n\t\texpandedText = this._expandRuleCommand(expandedText);\n\t\texpandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);\n\n\t\tawait this._queueFollowUp(expandedText, images);\n\t}\n\n\t/**\n\t * Internal: Queue a steering message (already expanded, no extension command check).\n\t */\n\tprivate async _queueSteer(\n\t\ttext: string,\n\t\timages?: ImageContent[],\n\t\thookContexts: CustomMessage[] = [],\n\t\tpromptId?: string,\n\t): Promise<void> {\n\t\tif (this._disposed || !this._hasActiveAgentRun) throw new Error(\"Cannot steer without an active agent run\");\n\t\tthis._steeringMessages.push(text);\n\t\tthis._emitQueueUpdate();\n\t\tconst content: (TextContent | ImageContent)[] = [{ type: \"text\", text }];\n\t\tif (images) {\n\t\t\tcontent.push(...images);\n\t\t}\n\t\tthis.agent.steer({\n\t\t\trole: \"user\",\n\t\t\tcontent,\n\t\t\ttimestamp: Date.now(),\n\t\t});\n\t\tfor (const context of hookContexts) this.agent.steer(context);\n\t\tif (promptId) {\n\t\t\tconst ids = this._queuedPromptIds.get(text) ?? [];\n\t\t\tids.push(promptId);\n\t\t\tthis._queuedPromptIds.set(text, ids);\n\t\t}\n\t}\n\n\t/**\n\t * Internal: Queue a follow-up message (already expanded, no extension command check).\n\t */\n\tprivate async _queueFollowUp(\n\t\ttext: string,\n\t\timages?: ImageContent[],\n\t\thookContexts: CustomMessage[] = [],\n\t\tpromptId?: string,\n\t): Promise<void> {\n\t\tif (this._disposed || !this._hasActiveAgentRun) {\n\t\t\tthrow new Error(\"Cannot queue a follow-up without an active agent run\");\n\t\t}\n\t\tthis._followUpMessages.push(text);\n\t\tthis._emitQueueUpdate();\n\t\tconst content: (TextContent | ImageContent)[] = [{ type: \"text\", text }];\n\t\tif (images) {\n\t\t\tcontent.push(...images);\n\t\t}\n\t\tthis.agent.followUp({\n\t\t\trole: \"user\",\n\t\t\tcontent,\n\t\t\ttimestamp: Date.now(),\n\t\t});\n\t\tfor (const context of hookContexts) this.agent.followUp(context);\n\t\tif (promptId) {\n\t\t\tconst ids = this._queuedPromptIds.get(text) ?? [];\n\t\t\tids.push(promptId);\n\t\t\tthis._queuedPromptIds.set(text, ids);\n\t\t}\n\t}\n\n\t/**\n\t * Throw an error if the text is an extension command.\n\t */\n\tprivate _throwIfExtensionCommand(text: string): void {\n\t\tconst spaceIndex = text.indexOf(\" \");\n\t\tconst commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);\n\t\tconst command = this._extensionRunner.getCommand(commandName);\n\n\t\tif (command) {\n\t\t\tthrow new Error(\n\t\t\t\t`Extension command \"/${commandName}\" cannot be queued. Use prompt() or execute the command when not streaming.`,\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Send a custom message to the session. Creates a CustomMessageEntry.\n\t *\n\t * Handles three cases:\n\t * - Streaming: queues message, processed when loop pulls from queue\n\t * - Not streaming + triggerTurn: appends to state/session, starts new turn\n\t * - Not streaming + no trigger: appends to state/session, no turn\n\t *\n\t * @param message Custom message with customType, content, display, details\n\t * @param options.triggerTurn If true and not streaming, triggers a new LLM turn\n\t * @param options.deliverAs Delivery mode: \"steer\", \"followUp\", or \"nextTurn\"\n\t */\n\tsendCustomMessage<T = unknown>(\n\t\tmessage: Pick<CustomMessage<T>, \"customType\" | \"content\" | \"display\" | \"details\">,\n\t\toptions?: SendCustomMessageOptions,\n\t): Promise<void> {\n\t\treturn this._runActivity(\"custom message\", () => this._sendCustomMessage(message, options));\n\t}\n\n\tprivate async _sendCustomMessage<T = unknown>(\n\t\tmessage: Pick<CustomMessage<T>, \"customType\" | \"content\" | \"display\" | \"details\">,\n\t\toptions?: SendCustomMessageOptions,\n\t): Promise<void> {\n\t\tconst appMessage = {\n\t\t\trole: \"custom\" as const,\n\t\t\tcustomType: message.customType,\n\t\t\tcontent: message.content ?? [],\n\t\t\tdisplay: message.display,\n\t\t\tdetails: message.details,\n\t\t\ttimestamp: Date.now(),\n\t\t} satisfies CustomMessage<T>;\n\t\tif (options?.deliverAs === \"nextTurn\") {\n\t\t\tthis._pendingNextTurnMessages.push(appMessage);\n\t\t} else if (this._isAgentRunActive) {\n\t\t\tif (options?.deliverAs === \"followUp\") {\n\t\t\t\tthis.agent.followUp(appMessage);\n\t\t\t} else {\n\t\t\t\tthis.agent.steer(appMessage);\n\t\t\t}\n\t\t} else if (options?.triggerTurn) {\n\t\t\tawait this._runAgentPrompt(appMessage);\n\t\t} else {\n\t\t\tthis.agent.state.messages.push(appMessage);\n\t\t\tthis.session.appendCustomMessageEntry(\n\t\t\t\tappMessage.customType,\n\t\t\t\tappMessage.content,\n\t\t\t\tappMessage.display,\n\t\t\t\tappMessage.details,\n\t\t\t);\n\t\t\tawait this._emit({ type: \"message_start\", message: appMessage });\n\t\t\tawait this._emit({ type: \"message_end\", message: appMessage });\n\t\t}\n\t}\n\n\t/**\n\t * Send a user message to the agent. Always triggers a turn.\n\t * When the agent is streaming, use deliverAs to specify how to queue the message.\n\t *\n\t * @param content User message content (string or content array)\n\t * @param options.deliverAs Delivery mode when streaming: \"steer\" or \"followUp\"\n\t */\n\tasync sendUserMessage(\n\t\tcontent: string | (TextContent | ImageContent)[],\n\t\toptions?: SendUserMessageOptions,\n\t): Promise<void> {\n\t\t// Normalize content to text string + optional images\n\t\tlet text: string;\n\t\tlet images: ImageContent[] | undefined;\n\n\t\tif (typeof content === \"string\") {\n\t\t\ttext = content;\n\t\t} else {\n\t\t\tconst textParts: string[] = [];\n\t\t\timages = [];\n\t\t\tfor (const part of content) {\n\t\t\t\tif (part.type === \"text\") {\n\t\t\t\t\ttextParts.push(part.text);\n\t\t\t\t} else {\n\t\t\t\t\timages.push(part);\n\t\t\t\t}\n\t\t\t}\n\t\t\ttext = textParts.join(\"\\n\");\n\t\t\tif (images.length === 0) images = undefined;\n\t\t}\n\n\t\t// Use prompt() with expandPromptTemplates: false to skip command handling and template expansion\n\t\tawait this.prompt(text, {\n\t\t\texpandPromptTemplates: false,\n\t\t\tstreamingBehavior: options?.deliverAs,\n\t\t\timages,\n\t\t\tsource: \"extension\",\n\t\t});\n\t}\n\n\t/**\n\t * Clear all queued messages and return them.\n\t * Useful for restoring to editor when user aborts.\n\t * @returns Object with steering and followUp arrays\n\t */\n\tclearQueue(): QueuedMessages {\n\t\tconst steering = [...this._steeringMessages];\n\t\tconst followUp = [...this._followUpMessages];\n\t\tthis._steeringMessages = [];\n\t\tthis._followUpMessages = [];\n\t\tthis.agent.clearAllQueues();\n\t\tthis._emitQueueUpdate();\n\t\tthis._activityGeneration++;\n\t\tthis._scheduleSettlementCheck();\n\t\treturn { steering, followUp };\n\t}\n\n\t/** Number of pending messages (includes both steering and follow-up) */\n\tget pendingMessageCount(): number {\n\t\treturn this._steeringMessages.length + this._followUpMessages.length;\n\t}\n\n\t/** Get pending steering messages (read-only) */\n\tgetSteeringMessages(): readonly string[] {\n\t\treturn this._steeringMessages;\n\t}\n\n\t/** Get pending follow-up messages (read-only) */\n\tgetFollowUpMessages(): readonly string[] {\n\t\treturn this._followUpMessages;\n\t}\n\n\tget resourceLoader(): ResourceLoader {\n\t\treturn this._resourceLoader;\n\t}\n\n\t/**\n\t * Abort current operation and wait for agent to become idle.\n\t */\n\tasync abort(): Promise<void> {\n\t\tthis.abortRetry();\n\t\tthis.agent.abort();\n\t\tif (this._barrierOwnerContext.getStore()?.active) return;\n\t\tawait this.waitForIdle();\n\t}\n\n\tprivate async _abortAgentAndDrain(): Promise<void> {\n\t\tthis.abortRetry();\n\t\tthis.agent.abort();\n\t\tawait this.agent.waitForIdle();\n\t}\n\n\t/** Wait for the full AgentSession lifecycle to settle. */\n\twaitForIdle(): Promise<void> {\n\t\tconst owner = this._barrierOwnerContext.getStore();\n\t\tif (owner?.active) {\n\t\t\tthrow new Error(\n\t\t\t\t`AgentSession.waitForIdle() cannot be awaited from ${owner.label} because it is part of the same settlement barrier`,\n\t\t\t);\n\t\t}\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\tthis._idleWaiters.add({ resolve, reject });\n\t\t\tthis._scheduleSettlementCheck();\n\t\t});\n\t}\n\n\t// =========================================================================\n\t// Model Management\n\t// =========================================================================\n\n\tprivate async _emitModelSelect(\n\t\tnextModel: Model<any>,\n\t\tpreviousModel: Model<any> | undefined,\n\t\tsource: ModelSelectSource,\n\t): Promise<void> {\n\t\tif (modelsAreEqual(previousModel, nextModel)) return;\n\t\tawait this._extensionRunner.emit({\n\t\t\ttype: \"model_select\",\n\t\t\tmodel: nextModel,\n\t\t\tpreviousModel,\n\t\t\tsource,\n\t\t});\n\t}\n\n\t/** Whether agent-initiated context compression is active in this session. */\n\tget contextCompressionEnabled(): boolean {\n\t\treturn this.getActiveToolNames().includes(\"compress_context\");\n\t}\n\n\t/** Toggle compression for this session without changing global settings. */\n\tsetContextCompressionEnabled(enabled: boolean): void {\n\t\tif (enabled && !this._toolRegistry.has(\"compress_context\")) {\n\t\t\tthrow new Error(\"Context compression is unavailable under this session's tool restrictions.\");\n\t\t}\n\t\tthis._contextCompressionEnabledOverride = enabled;\n\t\tconst tools = this.getActiveToolNames().filter((name) => name !== \"compress_context\");\n\t\tif (enabled) tools.push(\"compress_context\");\n\t\tthis.setActiveToolsByName(tools);\n\t}\n\n\t/** Detection requires both its session opt-in and an active compression tool. */\n\tget compressionDetectionEnabled(): boolean {\n\t\treturn this._compressionDetectionEnabled && this.contextCompressionEnabled;\n\t}\n\n\tsetCompressionDetectionEnabled(enabled: boolean): void {\n\t\tif (enabled) {\n\t\t\tif (!this.settingsManager.getCompressionDetectionModel()) {\n\t\t\t\tthrow new Error(\"Choose a detection model with /compress-detection-model first.\");\n\t\t\t}\n\t\t\tthis.setContextCompressionEnabled(true);\n\t\t}\n\t\tthis._compressionDetectionEnabled = enabled;\n\t\tthis._compressionDetector.reset();\n\t}\n\n\t/** Save the detector model and opt in for this session only, without changing the primary model. */\n\tsetCompressionDetectionModel(reference: string | undefined): void {\n\t\tif (reference) this.setContextCompressionEnabled(true);\n\t\tthis.settingsManager.setCompressionDetectionModel(reference);\n\t\tthis.setCompressionDetectionEnabled(!!reference);\n\t}\n\n\t/**\n\t * Set model directly.\n\t * Validates that auth is configured, saves to session and settings.\n\t * @throws Error if no auth is configured for the model\n\t */\n\tasync setModel(model: Model<any>): Promise<void> {\n\t\tif (!this._modelRegistry.hasConfiguredAuth(model)) {\n\t\t\tthrow new Error(`No API key for ${model.provider}/${model.id}`);\n\t\t}\n\n\t\tconst previousModel = this.model;\n\t\tconst thinkingLevel = this._getThinkingLevelForModelSwitch();\n\t\tthis.agent.state.model = model;\n\t\tthis.session.appendModelChange(model.provider, model.id);\n\t\tthis.settingsManager.setDefaultModelAndProvider(model.provider, model.id);\n\n\t\t// Re-clamp thinking level for new model's capabilities\n\t\tthis.setThinkingLevel(thinkingLevel);\n\t\tthis._refreshModelDependentRuntime();\n\n\t\tawait this._emitModelSelect(model, previousModel, \"set\");\n\t}\n\n\t/**\n\t * Cycle to next/previous model.\n\t * Uses scoped models (from --models flag) if available, otherwise all available models.\n\t * @param direction - \"forward\" (default) or \"backward\"\n\t * @returns The new model info, or undefined if only one model available\n\t */\n\tasync cycleModel(direction: ModelCycleDirection = \"forward\"): Promise<ModelCycleResult | undefined> {\n\t\tif (this._scopedModels.length > 0) {\n\t\t\treturn this._cycleScopedModel(direction);\n\t\t}\n\t\treturn this._cycleAvailableModel(direction);\n\t}\n\n\tprivate async _cycleScopedModel(direction: ModelCycleDirection): Promise<ModelCycleResult | undefined> {\n\t\tconst scopedModels = this._scopedModels.filter((scoped) => this._modelRegistry.hasConfiguredAuth(scoped.model));\n\t\tif (scopedModels.length <= 1) return undefined;\n\n\t\tconst currentModel = this.model;\n\t\tlet currentIndex = scopedModels.findIndex((sm) => modelsAreEqual(sm.model, currentModel));\n\n\t\tif (currentIndex === -1) currentIndex = 0;\n\t\tconst len = scopedModels.length;\n\t\tconst nextIndex = direction === \"forward\" ? (currentIndex + 1) % len : (currentIndex - 1 + len) % len;\n\t\tconst next = scopedModels[nextIndex];\n\t\tconst thinkingLevel = this._getThinkingLevelForModelSwitch(next.thinkingLevel);\n\n\t\t// Apply model\n\t\tthis.agent.state.model = next.model;\n\t\tthis.session.appendModelChange(next.model.provider, next.model.id);\n\t\tthis.settingsManager.setDefaultModelAndProvider(next.model.provider, next.model.id);\n\n\t\t// Apply thinking level.\n\t\t// - Explicit scoped model thinking level overrides current session level\n\t\t// - Undefined scoped model thinking level inherits the current session preference\n\t\t// setThinkingLevel clamps to model capabilities.\n\t\tthis.setThinkingLevel(thinkingLevel);\n\t\tthis._refreshModelDependentRuntime();\n\n\t\tawait this._emitModelSelect(next.model, currentModel, \"cycle\");\n\n\t\treturn { model: next.model, thinkingLevel: this.thinkingLevel, isScoped: true };\n\t}\n\n\tprivate async _cycleAvailableModel(direction: ModelCycleDirection): Promise<ModelCycleResult | undefined> {\n\t\tconst availableModels = await this._modelRegistry.getAvailable();\n\t\tif (availableModels.length <= 1) return undefined;\n\n\t\tconst currentModel = this.model;\n\t\tlet currentIndex = availableModels.findIndex((m) => modelsAreEqual(m, currentModel));\n\n\t\tif (currentIndex === -1) currentIndex = 0;\n\t\tconst len = availableModels.length;\n\t\tconst nextIndex = direction === \"forward\" ? (currentIndex + 1) % len : (currentIndex - 1 + len) % len;\n\t\tconst nextModel = availableModels[nextIndex];\n\n\t\tconst thinkingLevel = this._getThinkingLevelForModelSwitch();\n\t\tthis.agent.state.model = nextModel;\n\t\tthis.session.appendModelChange(nextModel.provider, nextModel.id);\n\t\tthis.settingsManager.setDefaultModelAndProvider(nextModel.provider, nextModel.id);\n\n\t\t// Re-clamp thinking level for new model's capabilities\n\t\tthis.setThinkingLevel(thinkingLevel);\n\t\tthis._refreshModelDependentRuntime();\n\n\t\tawait this._emitModelSelect(nextModel, currentModel, \"cycle\");\n\n\t\treturn { model: nextModel, thinkingLevel: this.thinkingLevel, isScoped: false };\n\t}\n\n\t// =========================================================================\n\t// Thinking Level Management\n\t// =========================================================================\n\n\t/**\n\t * Set thinking level.\n\t * Clamps to model capabilities based on available thinking levels.\n\t * Saves to session and settings only if the level actually changes.\n\t */\n\tsetThinkingLevel(level: ThinkingLevel): void {\n\t\tconst availableLevels = this.getAvailableThinkingLevels();\n\t\tconst effectiveLevel = availableLevels.includes(level) ? level : this._clampThinkingLevel(level, availableLevels);\n\n\t\t// Only persist if actually changing\n\t\tconst previousLevel = this.agent.state.thinkingLevel;\n\t\tconst isChanging = effectiveLevel !== previousLevel;\n\n\t\tthis.agent.state.thinkingLevel = effectiveLevel;\n\n\t\tif (isChanging) {\n\t\t\tthis.session.appendThinkingLevelChange(effectiveLevel);\n\t\t\tif (this.supportsThinking() || effectiveLevel !== \"off\") {\n\t\t\t\tthis.settingsManager.setDefaultThinkingLevel(effectiveLevel);\n\t\t\t}\n\t\t\tthis._emit({ type: \"thinking_level_changed\", level: effectiveLevel });\n\t\t\tvoid this._extensionRunner.emit({\n\t\t\t\ttype: \"thinking_level_select\",\n\t\t\t\tlevel: effectiveLevel,\n\t\t\t\tpreviousLevel,\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Cycle to next thinking level.\n\t * @returns New level, or undefined if model doesn't support thinking\n\t */\n\tcycleThinkingLevel(): ThinkingLevel | undefined {\n\t\tif (!this.supportsThinking()) return undefined;\n\n\t\tconst levels = this.getAvailableThinkingLevels();\n\t\tconst currentIndex = levels.indexOf(this.thinkingLevel);\n\t\tconst nextIndex = (currentIndex + 1) % levels.length;\n\t\tconst nextLevel = levels[nextIndex];\n\n\t\tthis.setThinkingLevel(nextLevel);\n\t\treturn nextLevel;\n\t}\n\n\t/**\n\t * Get available thinking levels for current model.\n\t * The provider will clamp to what the specific model supports internally.\n\t */\n\tgetAvailableThinkingLevels(): ThinkingLevel[] {\n\t\tif (!this.model) return THINKING_LEVELS;\n\t\treturn getSupportedThinkingLevels(this.model) as ThinkingLevel[];\n\t}\n\n\t/**\n\t * Check if current model supports thinking/reasoning.\n\t */\n\tsupportsThinking(): boolean {\n\t\treturn !!this.model?.reasoning;\n\t}\n\n\tprivate _getThinkingLevelForModelSwitch(explicitLevel?: ThinkingLevel): ThinkingLevel {\n\t\tif (explicitLevel !== undefined) {\n\t\t\treturn explicitLevel;\n\t\t}\n\t\tif (!this.supportsThinking()) {\n\t\t\treturn this.settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL;\n\t\t}\n\t\treturn this.thinkingLevel;\n\t}\n\n\tprivate _clampThinkingLevel(level: ThinkingLevel, _availableLevels: ThinkingLevel[]): ThinkingLevel {\n\t\treturn this.model ? (clampThinkingLevel(this.model, level) as ThinkingLevel) : \"off\";\n\t}\n\n\t// =========================================================================\n\t// Queue Mode Management\n\t// =========================================================================\n\n\t/**\n\t * Set steering message mode.\n\t * Saves to settings.\n\t */\n\tsetSteeringMode(mode: QueueMode): void {\n\t\tthis.agent.steeringMode = mode;\n\t\tthis.settingsManager.setSteeringMode(mode);\n\t}\n\n\t/**\n\t * Set follow-up message mode.\n\t * Saves to settings.\n\t */\n\tsetFollowUpMode(mode: QueueMode): void {\n\t\tthis.agent.followUpMode = mode;\n\t\tthis.settingsManager.setFollowUpMode(mode);\n\t}\n\n\t// =========================================================================\n\t// Compaction\n\t// =========================================================================\n\n\t/** Queue extension-triggered compaction outside the currently active agent turn. */\n\tprivate _requestExtensionCompaction(options?: CompactOptions): void {\n\t\tthis._extensionCompactionActivity ??= this._beginActivity(\"extension compaction request\");\n\t\tthis._extensionCompactionQueue.push(options ?? {});\n\t\tif (this._isAgentRunActive) return;\n\t\tthis._scheduleExtensionCompactions();\n\t}\n\n\tprivate _scheduleExtensionCompactions(): void {\n\t\tconst alreadyScheduled = this._extensionCompactionTimer !== undefined;\n\t\tif (this._extensionCompactionRunning || alreadyScheduled || this._extensionCompactionQueue.length === 0) {\n\t\t\treturn;\n\t\t}\n\t\tthis._extensionCompactionTimer = setTimeout(() => {\n\t\t\tthis._extensionCompactionTimer = undefined;\n\t\t\tvoid this._drainExtensionCompactionQueue();\n\t\t}, 0);\n\t}\n\n\tprivate async _drainExtensionCompactionQueue(mode: ExtensionCompactionQueueMode = \"idle\"): Promise<boolean> {\n\t\tif (this._extensionCompactionRunning) {\n\t\t\treturn false;\n\t\t}\n\t\tthis._extensionCompactionRunning = true;\n\t\tlet compacted = false;\n\t\ttry {\n\t\t\twhile (mode === \"between-turns\" || !this._isAgentRunActive) {\n\t\t\t\tconst options = this._extensionCompactionQueue.shift();\n\t\t\t\tif (!options) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcompacted = (await this._runRequestedExtensionCompaction(options, mode === \"idle\")) || compacted;\n\t\t\t}\n\t\t} finally {\n\t\t\tthis._extensionCompactionRunning = false;\n\t\t}\n\t\tif (mode === \"idle\" && !this._isAgentRunActive) this._scheduleExtensionCompactions();\n\t\tif (\n\t\t\tthis._extensionCompactionQueue.length === 0 &&\n\t\t\tthis._extensionCompactionTimer === undefined &&\n\t\t\t!this._extensionCompactionRunning &&\n\t\t\tthis._extensionCompactionActivity\n\t\t) {\n\t\t\tthis._endActivity(this._extensionCompactionActivity);\n\t\t\tthis._extensionCompactionActivity = undefined;\n\t\t}\n\t\treturn compacted;\n\t}\n\n\tprivate async _runRequestedExtensionCompaction(options: CompactOptions, abortActiveRun: boolean): Promise<boolean> {\n\t\ttry {\n\t\t\tconst result = await this._compactSession(options.customInstructions, { abortActiveRun });\n\t\t\toptions.onComplete?.(result);\n\t\t\treturn true;\n\t\t} catch (error) {\n\t\t\tconst err = error instanceof Error ? error : new Error(String(error));\n\t\t\toptions.onError?.(err);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Manually compact the session context.\n\t * Aborts current agent operation first.\n\t * @param customInstructions Optional instructions for the compaction summary\n\t */\n\tasync compact(customInstructions?: string): Promise<CompactionResult> {\n\t\treturn this._compactSession(customInstructions, { abortActiveRun: true });\n\t}\n\n\tprivate async _runWithCompactionGuard<T>(operation: () => Promise<T>): Promise<T> {\n\t\tif (this._disposed) {\n\t\t\tthrow new Error(\"Agent session is disposed\");\n\t\t}\n\t\tif (this._compactionInFlight) {\n\t\t\tthrow new Error(\"Compaction already in progress\");\n\t\t}\n\t\tconst inFlight = this._runActivity(\"compaction\", async () => {\n\t\t\tawait Promise.resolve();\n\t\t\tif (this._disposed) throw new Error(\"Agent session is disposed\");\n\t\t\treturn operation();\n\t\t});\n\t\tthis._compactionInFlight = inFlight;\n\t\ttry {\n\t\t\treturn await inFlight;\n\t\t} finally {\n\t\t\tif (this._compactionInFlight === inFlight) {\n\t\t\t\tthis._compactionInFlight = undefined;\n\t\t\t\tthis._scheduleSettlementCheck();\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate _compactSession(\n\t\tcustomInstructions: string | undefined,\n\t\toptions: CompactionExecutionOptions,\n\t): Promise<CompactionResult> {\n\t\treturn this._runWithCompactionGuard(() => this._compactSessionUnlocked(customInstructions, options));\n\t}\n\n\tprivate async _prepareForManualCompaction(options: CompactionExecutionOptions): Promise<void> {\n\t\tif (!options.abortActiveRun) return;\n\t\tawait this._abortAgentAndDrain();\n\t\tif (this._disposed) throw new Error(\"Agent session is disposed\");\n\t}\n\n\tprivate async _buildManualCompactionPlan(): Promise<CompactionPlan> {\n\t\tconst model = this.model;\n\t\tif (!model) throw new Error(formatNoModelSelectedMessage());\n\t\tconst auth = await this._getCompactionRequestAuth(model);\n\t\tconst pathEntries = this.session.getBranch();\n\t\tconst preparation = prepareCompaction(pathEntries, this.settingsManager.getCompactionSettings());\n\t\tif (!preparation) {\n\t\t\tconst lastEntry = pathEntries[pathEntries.length - 1];\n\t\t\tif (lastEntry?.type === \"compaction\") throw new Error(\"Already compacted\");\n\t\t\tthrow new Error(\"Nothing to compact (session too small)\");\n\t\t}\n\t\treturn { ...auth, model, preparation, pathEntries };\n\t}\n\n\tprivate async _runManualCompactionInterceptors(\n\t\tplan: CompactionPlan,\n\t\tcustomInstructions: string | undefined,\n\t\tsignal: AbortSignal,\n\t): Promise<CompactionResult | undefined> {\n\t\tconst preHook = await this._runHook({\n\t\t\t...this._hookCommon(\"PreCompact\"),\n\t\t\thook_event_name: \"PreCompact\",\n\t\t\ttrigger: \"manual\",\n\t\t\tcustom_instructions: customInstructions ?? \"\",\n\t\t});\n\t\tif (preHook.blocked || !preHook.continue) throw new Error(preHook.reason ?? \"Compaction blocked by hook\");\n\t\tif (!this._extensionRunner.hasHandlers(\"session_before_compact\")) return undefined;\n\t\tconst result = (await this._extensionRunner.emit({\n\t\t\ttype: \"session_before_compact\",\n\t\t\tpreparation: plan.preparation,\n\t\t\tbranchEntries: plan.pathEntries,\n\t\t\tcustomInstructions,\n\t\t\tsignal,\n\t\t})) as SessionBeforeCompactResult | undefined;\n\t\tif (result?.cancel) throw new Error(\"Compaction cancelled\");\n\t\treturn result?.compaction;\n\t}\n\n\tprivate async _resolveManualCompaction(\n\t\tplan: CompactionPlan,\n\t\tcustomInstructions: string | undefined,\n\t\textensionCompaction: CompactionResult | undefined,\n\t\tsignal: AbortSignal,\n\t): Promise<CompactionResult> {\n\t\tif (extensionCompaction) return extensionCompaction;\n\t\treturn compact(\n\t\t\tplan.preparation,\n\t\t\tplan.model,\n\t\t\tplan.apiKey,\n\t\t\tplan.headers,\n\t\t\tcustomInstructions,\n\t\t\tsignal,\n\t\t\tthis.thinkingLevel,\n\t\t\tthis.agent.streamFn,\n\t\t\tthis.settingsManager.getRetrySettings(),\n\t\t\tthis._summarizationRetryCallbacks({ source: \"compaction\", reason: \"manual\" }),\n\t\t);\n\t}\n\n\tprivate async _commitManualCompaction(result: CompactionResult, fromExtension: boolean): Promise<void> {\n\t\tthis.session.appendCompaction(\n\t\t\tresult.summary,\n\t\t\tresult.firstKeptEntryId,\n\t\t\tresult.tokensBefore,\n\t\t\tresult.details,\n\t\t\tfromExtension,\n\t\t);\n\t\tconst newEntries = this.session.getEntries();\n\t\tthis.agent.state.messages = this.session.buildSessionContext().messages;\n\t\tthis._compressionDetector.reset(this.agent.state.messages, this.getContextUsage()?.percent ?? null);\n\t\tconst savedCompactionEntry = newEntries.find(\n\t\t\t(entry): entry is CompactionEntry => entry.type === \"compaction\" && entry.summary === result.summary,\n\t\t);\n\t\tif (savedCompactionEntry) {\n\t\t\tawait this._extensionRunner.emit({\n\t\t\t\ttype: \"session_compact\",\n\t\t\t\tcompactionEntry: savedCompactionEntry,\n\t\t\t\tfromExtension,\n\t\t\t});\n\t\t}\n\t\tawait this._runHook({\n\t\t\t...this._hookCommon(\"PostCompact\"),\n\t\t\thook_event_name: \"PostCompact\",\n\t\t\ttrigger: \"manual\",\n\t\t\tcompact_summary: result.summary,\n\t\t});\n\t}\n\n\tprivate async _compactSessionUnlocked(\n\t\tcustomInstructions: string | undefined,\n\t\toptions: CompactionExecutionOptions,\n\t): Promise<CompactionResult> {\n\t\tawait this._prepareForManualCompaction(options);\n\t\tconst abortController = new AbortController();\n\t\tthis._compactionAbortController = abortController;\n\t\tthis._emit({ type: \"compaction_start\", reason: \"manual\" });\n\t\ttry {\n\t\t\tconst plan = await this._buildManualCompactionPlan();\n\t\t\tconst extensionCompaction = await this._runManualCompactionInterceptors(\n\t\t\t\tplan,\n\t\t\t\tcustomInstructions,\n\t\t\t\tabortController.signal,\n\t\t\t);\n\t\t\tconst result = await this._resolveManualCompaction(\n\t\t\t\tplan,\n\t\t\t\tcustomInstructions,\n\t\t\t\textensionCompaction,\n\t\t\t\tabortController.signal,\n\t\t\t);\n\t\t\tif (abortController.signal.aborted) throw new Error(\"Compaction cancelled\");\n\t\t\tconst fromExtension = extensionCompaction !== undefined;\n\t\t\tawait this._commitManualCompaction(result, fromExtension);\n\t\t\tconst compactionResult: CompactionResult = {\n\t\t\t\tsummary: result.summary,\n\t\t\t\tfirstKeptEntryId: result.firstKeptEntryId,\n\t\t\t\ttokensBefore: result.tokensBefore,\n\t\t\t\tdetails: result.details,\n\t\t\t};\n\t\t\t// compaction_end listeners may submit queued prompts, so expose idle state before notifying them.\n\t\t\tthis._compactionAbortController = undefined;\n\t\t\tthis._compactionInFlight = undefined;\n\t\t\tthis._emit({\n\t\t\t\ttype: \"compaction_end\",\n\t\t\t\treason: \"manual\",\n\t\t\t\tresult: compactionResult,\n\t\t\t\taborted: false,\n\t\t\t\twillRetry: false,\n\t\t\t});\n\t\t\treturn compactionResult;\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tconst aborted = message === \"Compaction cancelled\" || (error instanceof Error && error.name === \"AbortError\");\n\t\t\tthis._compactionAbortController = undefined;\n\t\t\tthis._compactionInFlight = undefined;\n\t\t\tthis._emit({\n\t\t\t\ttype: \"compaction_end\",\n\t\t\t\treason: \"manual\",\n\t\t\t\tresult: undefined,\n\t\t\t\taborted,\n\t\t\t\twillRetry: false,\n\t\t\t\terrorMessage: aborted ? undefined : `Compaction failed: ${message}`,\n\t\t\t});\n\t\t\tthrow error;\n\t\t} finally {\n\t\t\tthis._compactionAbortController = undefined;\n\t\t}\n\t}\n\n\t/**\n\t * Cancel in-progress compaction (manual or auto).\n\t */\n\tabortCompaction(): void {\n\t\tthis._compactionAbortController?.abort();\n\t\tthis._autoCompactionAbortController?.abort();\n\t}\n\n\t/**\n\t * Cancel in-progress branch summarization.\n\t */\n\tabortBranchSummary(): void {\n\t\tthis._branchSummaryAbortController?.abort();\n\t}\n\n\tprivate _isAtOrBeforeCompactionBoundary(timestamp: number, compactionEntry: CompactionEntry | null): boolean {\n\t\treturn compactionEntry !== null && timestamp <= new Date(compactionEntry.timestamp).getTime();\n\t}\n\n\tprivate async _runOverflowCompactionRecovery(): Promise<boolean> {\n\t\tif (this._overflowRecoveryAttempted) {\n\t\t\tthis._emit({\n\t\t\t\ttype: \"compaction_end\",\n\t\t\t\treason: \"overflow\",\n\t\t\t\tresult: undefined,\n\t\t\t\taborted: false,\n\t\t\t\twillRetry: false,\n\t\t\t\terrorMessage:\n\t\t\t\t\t\"Context overflow recovery failed after one compact-and-retry attempt. Try reducing context or switching to a larger-context model.\",\n\t\t\t});\n\t\t\treturn false;\n\t\t}\n\t\tthis._overflowRecoveryAttempted = true;\n\t\t// Keep the persisted error in session history, but remove it from retry context.\n\t\tconst messages = this.agent.state.messages;\n\t\tif (messages.length > 0 && messages[messages.length - 1].role === \"assistant\") {\n\t\t\tthis.agent.state.messages = messages.slice(0, -1);\n\t\t}\n\t\treturn await this._runAutoCompaction(\"overflow\", true);\n\t}\n\n\tprivate _resolveCompactionThresholdTokens(\n\t\tassistantMessage: AssistantMessage,\n\t\tcompactionEntry: CompactionEntry | null,\n\t): number | undefined {\n\t\tif (assistantMessage.stopReason !== \"error\") return calculateContextTokens(assistantMessage.usage);\n\t\tconst messages = this.agent.state.messages;\n\t\tconst estimate = estimateContextTokens(messages);\n\t\tif (estimate.lastUsageIndex === null) return undefined;\n\t\t// Kept pre-compaction usage reflects the old context and must not trigger another compaction.\n\t\tconst usageMessage = messages[estimate.lastUsageIndex];\n\t\tif (\n\t\t\tusageMessage.role === \"assistant\" &&\n\t\t\tthis._isAtOrBeforeCompactionBoundary((usageMessage as AssistantMessage).timestamp, compactionEntry)\n\t\t) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn estimate.tokens;\n\t}\n\n\t/**\n\t * Check if compaction is needed and run it.\n\t * Called after agent_end and before prompt submission.\n\t *\n\t * Two cases:\n\t * 1. Overflow: LLM returned context overflow error, remove error message from agent state, compact, auto-retry\n\t * 2. Threshold: Context over threshold, compact, NO auto-retry (user continues manually)\n\t *\n\t * @param assistantMessage The assistant message to check\n\t * @param skipAbortedCheck If false, include aborted messages (for pre-prompt check). Default: true\n\t */\n\tprivate async _checkCompaction(assistantMessage: AssistantMessage, skipAbortedCheck = true): Promise<boolean> {\n\t\tconst settings = this.settingsManager.getCompactionSettings();\n\t\tif (!settings.enabled) return false;\n\t\tif (skipAbortedCheck && assistantMessage.stopReason === \"aborted\") return false;\n\t\tconst contextWindow = this.model?.contextWindow ?? 0;\n\t\tconst sameModel =\n\t\t\tthis.model && assistantMessage.provider === this.model.provider && assistantMessage.model === this.model.id;\n\t\tconst compactionEntry = getLatestCompactionEntry(this.session.getBranch());\n\t\tif (this._isAtOrBeforeCompactionBoundary(assistantMessage.timestamp, compactionEntry)) return false;\n\t\tif (sameModel && isContextOverflow(assistantMessage, contextWindow)) {\n\t\t\treturn await this._runOverflowCompactionRecovery();\n\t\t}\n\t\tconst contextTokens = this._resolveCompactionThresholdTokens(assistantMessage, compactionEntry);\n\t\tif (contextTokens === undefined || !shouldCompact(contextTokens, contextWindow, settings)) return false;\n\t\treturn await this._runAutoCompaction(\"threshold\", false);\n\t}\n\n\t/**\n\t * Internal: Run auto-compaction with events.\n\t */\n\tprivate _runAutoCompaction(reason: AutoCompactionReason, willRetry: boolean): Promise<boolean> {\n\t\tif (this._compactionInFlight || this._disposed) return Promise.resolve(false);\n\t\treturn this._runWithCompactionGuard(() => this._runAutoCompactionUnlocked(reason, willRetry));\n\t}\n\n\tprivate _emitAutoCompactionWithoutResult(\n\t\treason: AutoCompactionReason,\n\t\taborted: boolean,\n\t\terrorMessage?: string,\n\t): void {\n\t\tthis._emit({\n\t\t\ttype: \"compaction_end\",\n\t\t\treason,\n\t\t\tresult: undefined,\n\t\t\taborted,\n\t\t\twillRetry: false,\n\t\t\t...(errorMessage === undefined ? {} : { errorMessage }),\n\t\t});\n\t}\n\n\tprivate async _buildAutoCompactionPlan(\n\t\treason: AutoCompactionReason,\n\t\tsettings: CompactionSettings,\n\t): Promise<CompactionPlan | undefined> {\n\t\tconst model = this.model;\n\t\tif (!model) {\n\t\t\tthis._emitAutoCompactionWithoutResult(reason, false);\n\t\t\treturn undefined;\n\t\t}\n\t\tlet auth: CompactionRequestAuth;\n\t\tif (this.agent.streamFn === streamSimple) {\n\t\t\tconst authResult = await this._modelRegistry.getApiKeyAndHeaders(model);\n\t\t\tif (!authResult.ok || !authResult.apiKey) {\n\t\t\t\tthis._emitAutoCompactionWithoutResult(reason, false);\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\tauth = { apiKey: authResult.apiKey, headers: authResult.headers };\n\t\t} else {\n\t\t\tauth = await this._getCompactionRequestAuth(model);\n\t\t}\n\t\tconst pathEntries = this.session.getBranch();\n\t\tconst preparation = prepareCompaction(pathEntries, settings);\n\t\tif (!preparation) {\n\t\t\tthis._emitAutoCompactionWithoutResult(reason, false);\n\t\t\treturn undefined;\n\t\t}\n\t\treturn { ...auth, model, preparation, pathEntries };\n\t}\n\n\tprivate async _runAutoCompactionInterceptors(\n\t\tplan: CompactionPlan,\n\t\tsignal: AbortSignal,\n\t): Promise<AutoCompactionInterception> {\n\t\tconst preHook = await this._runHook({\n\t\t\t...this._hookCommon(\"PreCompact\"),\n\t\t\thook_event_name: \"PreCompact\",\n\t\t\ttrigger: \"auto\",\n\t\t\tcustom_instructions: \"\",\n\t\t});\n\t\tif (preHook.blocked || !preHook.continue) return { cancelled: true };\n\t\tif (!this._extensionRunner.hasHandlers(\"session_before_compact\")) return { cancelled: false };\n\t\tconst extensionResult = (await this._extensionRunner.emit({\n\t\t\ttype: \"session_before_compact\",\n\t\t\tpreparation: plan.preparation,\n\t\t\tbranchEntries: plan.pathEntries,\n\t\t\tcustomInstructions: undefined,\n\t\t\tsignal,\n\t\t})) as SessionBeforeCompactResult | undefined;\n\t\tif (extensionResult?.cancel) return { cancelled: true };\n\t\treturn { cancelled: false, compaction: extensionResult?.compaction };\n\t}\n\n\tprivate async _resolveAutoCompaction(\n\t\tplan: CompactionPlan,\n\t\tinterception: AutoCompactionInterception,\n\t\treason: AutoCompactionReason,\n\t\tsignal: AbortSignal,\n\t): Promise<ResolvedAutoCompaction> {\n\t\tif (interception.compaction) return { result: interception.compaction, fromExtension: true };\n\t\tconst result = await compact(\n\t\t\tplan.preparation,\n\t\t\tplan.model,\n\t\t\tplan.apiKey,\n\t\t\tplan.headers,\n\t\t\tundefined,\n\t\t\tsignal,\n\t\t\tthis.thinkingLevel,\n\t\t\tthis.agent.streamFn,\n\t\t\tthis.settingsManager.getRetrySettings(),\n\t\t\tthis._summarizationRetryCallbacks({ source: \"compaction\", reason }),\n\t\t);\n\t\treturn { result, fromExtension: false };\n\t}\n\n\tprivate async _commitAutoCompaction(compaction: ResolvedAutoCompaction): Promise<void> {\n\t\tconst { result, fromExtension } = compaction;\n\t\tthis.session.appendCompaction(\n\t\t\tresult.summary,\n\t\t\tresult.firstKeptEntryId,\n\t\t\tresult.tokensBefore,\n\t\t\tresult.details,\n\t\t\tfromExtension,\n\t\t);\n\t\tconst newEntries = this.session.getEntries();\n\t\tthis.agent.state.messages = this.session.buildSessionContext().messages;\n\t\tthis._compressionDetector.reset(this.agent.state.messages, this.getContextUsage()?.percent ?? null);\n\t\tconst savedCompactionEntry = newEntries.find(\n\t\t\t(entry): entry is CompactionEntry => entry.type === \"compaction\" && entry.summary === result.summary,\n\t\t);\n\t\tif (savedCompactionEntry) {\n\t\t\tawait this._extensionRunner.emit({\n\t\t\t\ttype: \"session_compact\",\n\t\t\t\tcompactionEntry: savedCompactionEntry,\n\t\t\t\tfromExtension,\n\t\t\t});\n\t\t}\n\t\tawait this._runHook({\n\t\t\t...this._hookCommon(\"PostCompact\"),\n\t\t\thook_event_name: \"PostCompact\",\n\t\t\ttrigger: \"auto\",\n\t\t\tcompact_summary: result.summary,\n\t\t});\n\t}\n\n\tprivate async _runAutoCompactionUnlocked(reason: AutoCompactionReason, willRetry: boolean): Promise<boolean> {\n\t\tconst settings = this.settingsManager.getCompactionSettings();\n\t\tthis._emit({ type: \"compaction_start\", reason });\n\t\tconst abortController = new AbortController();\n\t\tthis._autoCompactionAbortController = abortController;\n\n\t\ttry {\n\t\t\tconst plan = await this._buildAutoCompactionPlan(reason, settings);\n\t\t\tif (!plan) return false;\n\t\t\tconst interception = await this._runAutoCompactionInterceptors(plan, abortController.signal);\n\t\t\tif (interception.cancelled) {\n\t\t\t\tthis._emitAutoCompactionWithoutResult(reason, true);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tconst compaction = await this._resolveAutoCompaction(plan, interception, reason, abortController.signal);\n\t\t\tif (abortController.signal.aborted) {\n\t\t\t\tthis._emitAutoCompactionWithoutResult(reason, true);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tawait this._commitAutoCompaction(compaction);\n\t\t\tthis._emit({ type: \"compaction_end\", reason, result: compaction.result, aborted: false, willRetry });\n\n\t\t\tif (willRetry) {\n\t\t\t\tconst messages = this.agent.state.messages;\n\t\t\t\tconst lastMessage = messages[messages.length - 1];\n\t\t\t\tif (lastMessage?.role === \"assistant\" && (lastMessage as AssistantMessage).stopReason === \"error\") {\n\t\t\t\t\tthis.agent.state.messages = messages.slice(0, -1);\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\t// Auto-compaction can complete while follow-up/steering/custom messages are waiting.\n\t\t\t// Continue once so queued messages are delivered.\n\t\t\treturn this.agent.hasQueuedMessages();\n\t\t} catch (error) {\n\t\t\tconst errorMessage = error instanceof Error ? error.message : \"compaction failed\";\n\t\t\tthis._emitAutoCompactionWithoutResult(\n\t\t\t\treason,\n\t\t\t\tfalse,\n\t\t\t\treason === \"overflow\"\n\t\t\t\t\t? `Context overflow recovery failed: ${errorMessage}`\n\t\t\t\t\t: `Auto-compaction failed: ${errorMessage}`,\n\t\t\t);\n\t\t\treturn false;\n\t\t} finally {\n\t\t\tthis._autoCompactionAbortController = undefined;\n\t\t}\n\t}\n\n\t/**\n\t * Toggle auto-compaction setting.\n\t */\n\tsetAutoCompactionEnabled(enabled: boolean): void {\n\t\tthis.settingsManager.setCompactionEnabled(enabled);\n\t}\n\n\t/** Whether auto-compaction is enabled */\n\tget autoCompactionEnabled(): boolean {\n\t\treturn this.settingsManager.getCompactionEnabled();\n\t}\n\n\t/** Whether built-in subagent tools are enabled by default. */\n\tget subagentsEnabled(): boolean {\n\t\treturn this.settingsManager.getEnableSubagents();\n\t}\n\n\t/** Persist and immediately apply built-in subagent tool activation. */\n\tsetSubagentsEnabled(enabled: boolean): void {\n\t\tthis.settingsManager.setEnableSubagents(enabled);\n\t\tthis._refreshToolRegistry({ activeToolNames: this._withCurrentDefaultTools(this.getActiveToolNames()) });\n\t}\n\n\tbindExtensions(bindings: ExtensionBindings): Promise<void> {\n\t\treturn this._runActivity(\"extension binding\", () => this._bindExtensions(bindings));\n\t}\n\n\tprivate async _bindExtensions(bindings: ExtensionBindings): Promise<void> {\n\t\tif (bindings.uiContext !== undefined) {\n\t\t\tthis._extensionUIContext = bindings.uiContext;\n\t\t}\n\t\tif (bindings.commandContextActions !== undefined) {\n\t\t\tthis._extensionCommandContextActions = bindings.commandContextActions;\n\t\t}\n\t\tif (bindings.abortHandler !== undefined) {\n\t\t\tthis._extensionAbortHandler = bindings.abortHandler;\n\t\t}\n\t\tif (bindings.shutdownHandler !== undefined) {\n\t\t\tthis._extensionShutdownHandler = bindings.shutdownHandler;\n\t\t}\n\t\tif (bindings.onError !== undefined) {\n\t\t\tthis._extensionErrorListener = bindings.onError;\n\t\t}\n\n\t\tthis._applyExtensionBindings(this._extensionRunner);\n\t\tawait this._extensionRunner.emit(this._sessionStartEvent);\n\t\tawait this.extendResourcesFromExtensions(this._sessionStartEvent.reason === \"reload\" ? \"reload\" : \"startup\");\n\t}\n\n\tprivate async extendResourcesFromExtensions(reason: ResourcesDiscoverReason): Promise<void> {\n\t\tif (!this._extensionRunner.hasHandlers(\"resources_discover\")) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst { skillPaths, rulePaths, promptPaths, themePaths } = await this._extensionRunner.emitResourcesDiscover(\n\t\t\tthis._cwd,\n\t\t\treason,\n\t\t);\n\n\t\tif (skillPaths.length === 0 && rulePaths.length === 0 && promptPaths.length === 0 && themePaths.length === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst extensionPaths: ResourceExtensionPaths = {\n\t\t\tskillPaths: this.buildExtensionResourcePaths(skillPaths),\n\t\t\trulePaths: this.buildExtensionResourcePaths(rulePaths),\n\t\t\tpromptPaths: this.buildExtensionResourcePaths(promptPaths),\n\t\t\tthemePaths: this.buildExtensionResourcePaths(themePaths),\n\t\t};\n\n\t\tthis._resourceLoader.extendResources(extensionPaths);\n\t\tthis._baseSystemPrompt = this._rebuildSystemPrompt(this.getActiveToolNames());\n\t\tthis.agent.state.systemPrompt = this._systemPromptOverride ?? this._baseSystemPrompt;\n\t}\n\n\tprivate buildExtensionResourcePaths(entries: ExtensionResourcePath[]): ResourcePathEntry[] {\n\t\treturn entries.map((entry) => {\n\t\t\tconst source = this.getExtensionSourceLabel(entry.extensionPath);\n\t\t\tconst baseDir = entry.extensionPath.startsWith(\"<\") ? undefined : dirname(entry.extensionPath);\n\t\t\treturn {\n\t\t\t\tpath: entry.path,\n\t\t\t\tmetadata: {\n\t\t\t\t\tsource,\n\t\t\t\t\tscope: \"temporary\",\n\t\t\t\t\torigin: \"top-level\",\n\t\t\t\t\tbaseDir,\n\t\t\t\t},\n\t\t\t};\n\t\t});\n\t}\n\n\tprivate getExtensionSourceLabel(extensionPath: string): string {\n\t\tif (extensionPath.startsWith(\"<\")) {\n\t\t\treturn `extension:${extensionPath.replace(/[<>]/g, \"\")}`;\n\t\t}\n\t\tconst base = basename(extensionPath);\n\t\tconst name = base.replace(/\\.(ts|js)$/, \"\");\n\t\treturn `extension:${name}`;\n\t}\n\n\tprivate _applyExtensionBindings(runner: ExtensionRunner): void {\n\t\trunner.setUIContext(this._extensionUIContext);\n\t\trunner.bindCommandContext(this._extensionCommandContextActions);\n\n\t\tthis._extensionErrorUnsubscriber?.();\n\t\tthis._extensionErrorUnsubscriber = this._extensionErrorListener\n\t\t\t? runner.onError(this._extensionErrorListener)\n\t\t\t: undefined;\n\t}\n\n\tprivate _refreshCurrentModelFromRegistry(): void {\n\t\tconst currentModel = this.model;\n\t\tif (!currentModel) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst refreshedModel = this._modelRegistry.find(currentModel.provider, currentModel.id);\n\t\tif (refreshedModel && refreshedModel !== currentModel) {\n\t\t\tthis.agent.state.model = refreshedModel;\n\t\t}\n\t\tthis._refreshModelDependentRuntime();\n\t}\n\n\tprivate _updateCurrentRunnerLspOwnership(): void {\n\t\tconst ownership = this._extensionLspOwnership.get(this._extensionRunner);\n\t\tif (!ownership) return;\n\t\townership.configuration = structuredClone(this._lspConfiguration);\n\t\townership.runtimeState = this._lspRuntimeState;\n\t}\n\n\tprivate _bindExtensionCore(runner: ExtensionRunner): void {\n\t\tconst getToolOperations = (): ToolOperations => this.getToolOperations();\n\t\tconst boundLspOwnership: ExtensionLspOwnership = {\n\t\t\truntimeState: this._lspRuntimeState,\n\t\t\tconfiguration: structuredClone(this._lspConfiguration),\n\t\t};\n\t\tthis._extensionLspOwnership.set(runner, boundLspOwnership);\n\t\tconst getBoundLspStatus = (): LspSessionStatus =>\n\t\t\trunner === this._extensionRunner\n\t\t\t\t? this.getLspStatus()\n\t\t\t\t: {\n\t\t\t\t\t\towner: \"agent-session\",\n\t\t\t\t\t\tenabled: boundLspOwnership.configuration.enabled,\n\t\t\t\t\t\tconfiguration: structuredClone(boundLspOwnership.configuration),\n\t\t\t\t\t\tservers: boundLspOwnership.runtimeState?.manager.getStatus() ?? [],\n\t\t\t\t\t};\n\n\t\tconst getCommands = (): SlashCommandInfo[] => {\n\t\t\tconst extensionCommands: SlashCommandInfo[] = runner\n\t\t\t\t.getRegisteredCommands()\n\t\t\t\t.filter((command) => !HIDDEN_BUILTIN_SLASH_COMMAND_NAMES.has(command.name))\n\t\t\t\t.map((command) => ({\n\t\t\t\t\tname: command.invocationName,\n\t\t\t\t\tdescription: command.description,\n\t\t\t\t\tsource: \"extension\",\n\t\t\t\t\tsourceInfo: command.sourceInfo,\n\t\t\t\t}));\n\n\t\t\tconst templates: SlashCommandInfo[] = this.promptTemplates.map((template) => ({\n\t\t\t\tname: template.name,\n\t\t\t\tdescription: template.description,\n\t\t\t\tsource: \"prompt\",\n\t\t\t\tsourceInfo: template.sourceInfo,\n\t\t\t}));\n\n\t\t\tconst skills: SlashCommandInfo[] = this.getRegisteredSkills().map((skill) => ({\n\t\t\t\tname: `skill:${skill.name}`,\n\t\t\t\tdescription: skill.description,\n\t\t\t\tsource: \"skill\",\n\t\t\t\tsourceInfo: skill.sourceInfo,\n\t\t\t}));\n\n\t\t\tconst rules: SlashCommandInfo[] = this.getRegisteredRules().map((rule) => ({\n\t\t\t\tname: `rule:${rule.name}`,\n\t\t\t\tdescription: rule.description,\n\t\t\t\tsource: \"rule\",\n\t\t\t\tsourceInfo: rule.sourceInfo,\n\t\t\t}));\n\n\t\t\treturn [...extensionCommands, ...templates, ...skills, ...rules];\n\t\t};\n\n\t\trunner.bindCore(\n\t\t\t{\n\t\t\t\tsendMessage: (message, options) => {\n\t\t\t\t\tthis.sendCustomMessage(message, options).catch((err) => {\n\t\t\t\t\t\trunner.emitError({\n\t\t\t\t\t\t\textensionPath: \"<runtime>\",\n\t\t\t\t\t\t\tevent: \"send_message\",\n\t\t\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tsendUserMessage: (content, options) => {\n\t\t\t\t\tthis.sendUserMessage(content, options).catch((err) => {\n\t\t\t\t\t\trunner.emitError({\n\t\t\t\t\t\t\textensionPath: \"<runtime>\",\n\t\t\t\t\t\t\tevent: \"send_user_message\",\n\t\t\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tappendEntry: (customType, data) => {\n\t\t\t\t\tthis.session.appendCustomEntry(customType, data);\n\t\t\t\t},\n\t\t\t\tsetSessionName: (name) => {\n\t\t\t\t\tthis.setSessionName(name);\n\t\t\t\t},\n\t\t\t\tgetSessionName: () => {\n\t\t\t\t\treturn this.session.getSessionName();\n\t\t\t\t},\n\t\t\t\tsetLabel: (entryId, label) => {\n\t\t\t\t\tthis.session.appendLabelChange(entryId, label);\n\t\t\t\t},\n\t\t\t\tgetActiveTools: () => this.getActiveToolNames(),\n\t\t\t\tgetAllTools: () => this.getAllTools(),\n\t\t\t\tgetAvailableTools: () => this.getAvailableSessionTools(),\n\t\t\t\tsetActiveTools: (toolNames) => this.setActiveToolsByName(toolNames),\n\t\t\t\tunregisterTool: (name) => this.unregisterSessionTool(name),\n\t\t\t\tloadTool: (name) => this.loadSessionTool(name),\n\t\t\t\tunloadTool: (name) => this.unloadSessionTool(name),\n\t\t\t\trefreshTools: () => this._refreshToolRegistry(),\n\t\t\t\tgetCommands,\n\t\t\t\tsetModel: async (model) => {\n\t\t\t\t\tif (!this.modelRegistry.hasConfiguredAuth(model)) return false;\n\t\t\t\t\tawait this.setModel(model);\n\t\t\t\t\treturn true;\n\t\t\t\t},\n\t\t\t\tgetThinkingLevel: () => this.thinkingLevel,\n\t\t\t\tsetThinkingLevel: (level) => this.setThinkingLevel(level),\n\t\t\t},\n\t\t\t{\n\t\t\t\tgetModel: () => this.model,\n\t\t\t\tisIdle: () => this.isIdle,\n\t\t\t\tgetSignal: () => this.agent.signal,\n\t\t\t\tabort: () => {\n\t\t\t\t\tif (this._extensionAbortHandler) {\n\t\t\t\t\t\tthis._extensionAbortHandler();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tvoid this.abort();\n\t\t\t\t},\n\t\t\t\thasPendingMessages: () => this.pendingMessageCount > 0,\n\t\t\t\tshutdown: () => {\n\t\t\t\t\tthis._extensionShutdownHandler?.();\n\t\t\t\t},\n\t\t\t\tgetContextUsage: () => this.getContextUsage(),\n\t\t\t\tcompact: (options) => {\n\t\t\t\t\tthis._requestExtensionCompaction(options);\n\t\t\t\t},\n\t\t\t\tgetSystemPrompt: () => this.systemPrompt,\n\t\t\t\tgetToolOperations,\n\t\t\t\tgetToolBackendInfo: () => this.getToolBackendInfo(),\n\t\t\t\tgetLspStatus: getBoundLspStatus,\n\t\t\t\tconfigureLsp: (configuration) => {\n\t\t\t\t\tif (runner !== this._extensionRunner) throw new Error(STALE_EXTENSION_CONTEXT_MESSAGE);\n\t\t\t\t\treturn this.configureLsp(configuration);\n\t\t\t\t},\n\t\t\t\texecToolBackend: (command, options) =>\n\t\t\t\t\tthis._runActivity(\"extension backend command\", async () => {\n\t\t\t\t\t\tconst operations = getToolOperations();\n\t\t\t\t\t\tconst prefix = this.settingsManager.getShellCommandPrefix();\n\t\t\t\t\t\tconst resolvedCommand = prefix ? `${prefix}\\n${command}` : command;\n\t\t\t\t\t\treturn executeBashWithOperations(\n\t\t\t\t\t\t\tresolvedCommand,\n\t\t\t\t\t\t\toptions?.cwd ?? operations.cwd,\n\t\t\t\t\t\t\toperations,\n\t\t\t\t\t\t\toptions,\n\t\t\t\t\t\t);\n\t\t\t\t\t}),\n\t\t\t},\n\t\t\t{\n\t\t\t\tregisterProvider: (name, config) => {\n\t\t\t\t\tthis._modelRegistry.registerProvider(name, config);\n\t\t\t\t\tthis._refreshCurrentModelFromRegistry();\n\t\t\t\t},\n\t\t\t\tunregisterProvider: (name) => {\n\t\t\t\t\tthis._modelRegistry.unregisterProvider(name);\n\t\t\t\t\tthis._refreshCurrentModelFromRegistry();\n\t\t\t\t},\n\t\t\t},\n\t\t);\n\t}\n\n\tprivate _createLoadToolDefinition(): ToolDefinition {\n\t\tconst parameters = Type.Object({\n\t\t\tname: Type.Union([Type.String(), Type.Array(Type.String())], {\n\t\t\t\tdescription:\n\t\t\t\t\t\"Name of the available session-scoped tool to load, or an array of names to load several at once\",\n\t\t\t}),\n\t\t});\n\t\treturn {\n\t\t\tname: \"load_tool\",\n\t\t\tlabel: \"Load tool\",\n\t\t\tdescription:\n\t\t\t\t\"Load one or more session-scoped available tools into the active tool context for subsequent turns. Pass a single name or an array of names to load several in one call.\",\n\t\t\tpromptSnippet:\n\t\t\t\t\"Use load_tool to load session-scoped available tools listed in <available-tools> before calling them; pass an array of names to load several at once.\",\n\t\t\tparameters,\n\t\t\trenderCall: (args: unknown) => {\n\t\t\t\treturn new Text(\n\t\t\t\t\tformatSessionToolLifecycleCall(\"load\", getToolNameArgs(args as SessionToolNameArgs | undefined)),\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t);\n\t\t\t},\n\t\t\trenderResult: (result, _options, _theme, context) => {\n\t\t\t\tconst details = result.details as { loaded?: string[]; notFound?: string[] } | undefined;\n\t\t\t\tconst loaded = details?.loaded ?? getToolNameArgs(context.args as SessionToolNameArgs | undefined);\n\t\t\t\treturn new Text(formatSessionToolLifecycleResult(\"loaded\", loaded, details?.notFound ?? []), 0, 0);\n\t\t\t},\n\t\t\texecutionMode: \"sequential\",\n\t\t\texecute: async (_toolCallId, params: SessionToolNameParameters) => {\n\t\t\t\tconst names = normalizeSessionToolNames(params.name);\n\t\t\t\tconst loaded: string[] = [];\n\t\t\t\tconst notFound: string[] = [];\n\t\t\t\tfor (const name of names) {\n\t\t\t\t\tif (this.loadSessionTool(name)) loaded.push(name);\n\t\t\t\t\telse notFound.push(name);\n\t\t\t\t}\n\t\t\t\tconst parts: string[] = [];\n\t\t\t\tif (loaded.length > 0) {\n\t\t\t\t\tparts.push(\n\t\t\t\t\t\t`Loaded tool${loaded.length > 1 ? \"s\" : \"\"}: ${loaded.join(\", \")}. ${loaded.length > 1 ? \"They are\" : \"It is\"} now active and can be called.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (notFound.length > 0) {\n\t\t\t\t\tparts.push(`Tool${notFound.length > 1 ? \"s\" : \"\"} not found in this session: ${notFound.join(\", \")}.`);\n\t\t\t\t}\n\t\t\t\tif (parts.length === 0) {\n\t\t\t\t\tparts.push(\"No tool names provided.\");\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: parts.join(\" \") }],\n\t\t\t\t\tdetails: { names, loaded, notFound },\n\t\t\t\t};\n\t\t\t},\n\t\t};\n\t}\n\n\tprivate _createUnloadToolDefinition(): ToolDefinition {\n\t\tconst parameters = Type.Object({\n\t\t\tname: Type.Union([Type.String(), Type.Array(Type.String())], {\n\t\t\t\tdescription: \"Name of the session-scoped tool to unload, or an array of names to unload several at once\",\n\t\t\t}),\n\t\t});\n\t\treturn {\n\t\t\tname: \"unload_tool\",\n\t\t\tlabel: \"Unload tool\",\n\t\t\tdescription:\n\t\t\t\t\"Unload one or more session-scoped tools from the active tool context while keeping them available in this session. Pass a single name or an array of names.\",\n\t\t\tpromptSnippet:\n\t\t\t\t\"Use unload_tool to remove session-scoped loaded tools from active context when no longer needed; pass an array of names to unload several at once.\",\n\t\t\tparameters,\n\t\t\trenderCall: (args: unknown) => {\n\t\t\t\treturn new Text(\n\t\t\t\t\tformatSessionToolLifecycleCall(\"unload\", getToolNameArgs(args as SessionToolNameArgs | undefined)),\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t);\n\t\t\t},\n\t\t\trenderResult: (result, _options, _theme, context) => {\n\t\t\t\tconst details = result.details as { unloaded?: string[]; notFound?: string[] } | undefined;\n\t\t\t\tconst unloaded = details?.unloaded ?? getToolNameArgs(context.args as SessionToolNameArgs | undefined);\n\t\t\t\treturn new Text(formatSessionToolLifecycleResult(\"unloaded\", unloaded, details?.notFound ?? []), 0, 0);\n\t\t\t},\n\t\t\texecutionMode: \"sequential\",\n\t\t\texecute: async (_toolCallId, params: SessionToolNameParameters) => {\n\t\t\t\tconst names = normalizeSessionToolNames(params.name);\n\t\t\t\tconst unloaded: string[] = [];\n\t\t\t\tconst notFound: string[] = [];\n\t\t\t\tfor (const name of names) {\n\t\t\t\t\tif (this.unloadSessionTool(name)) unloaded.push(name);\n\t\t\t\t\telse notFound.push(name);\n\t\t\t\t}\n\t\t\t\tconst parts: string[] = [];\n\t\t\t\tif (unloaded.length > 0) {\n\t\t\t\t\tparts.push(`Unloaded tool${unloaded.length > 1 ? \"s\" : \"\"}: ${unloaded.join(\", \")}.`);\n\t\t\t\t}\n\t\t\t\tif (notFound.length > 0) {\n\t\t\t\t\tparts.push(`Tool${notFound.length > 1 ? \"s\" : \"\"} not found in this session: ${notFound.join(\", \")}.`);\n\t\t\t\t}\n\t\t\t\tif (parts.length === 0) {\n\t\t\t\t\tparts.push(\"No tool names provided.\");\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: parts.join(\" \") }],\n\t\t\t\t\tdetails: { names, unloaded, notFound },\n\t\t\t\t};\n\t\t\t},\n\t\t};\n\t}\n\n\tprivate _resolveRefreshedActiveToolNames(\n\t\toptions: ToolRegistryRefreshOptions | undefined,\n\t\tpreviousRegistryNames: Set<string>,\n\t\tpreviousActiveToolNames: string[],\n\t\twrappedExtensionTools: AgentTool[],\n\t\thasAvailableSessionTools: boolean,\n\t): string[] {\n\t\tconst nextActiveToolNames = (\n\t\t\toptions?.activeToolNames ? [...options.activeToolNames] : [...previousActiveToolNames]\n\t\t).filter((name) => this._isToolPermitted(name));\n\t\tif (this._allowedToolNames) {\n\t\t\tnextActiveToolNames.push(\n\t\t\t\t...Array.from(this._toolRegistry.keys()).filter((toolName) => this._allowedToolNames?.has(toolName)),\n\t\t\t);\n\t\t} else if (options?.includeAllExtensionTools) {\n\t\t\tnextActiveToolNames.push(...wrappedExtensionTools.map((tool) => tool.name));\n\t\t} else if (!options?.activeToolNames) {\n\t\t\tnextActiveToolNames.push(\n\t\t\t\t...Array.from(this._toolRegistry.keys()).filter((toolName) => !previousRegistryNames.has(toolName)),\n\t\t\t);\n\t\t}\n\t\tif (hasAvailableSessionTools) nextActiveToolNames.push(\"load_tool\", \"unload_tool\");\n\t\treturn nextActiveToolNames;\n\t}\n\n\tprivate _refreshToolRegistry(options?: ToolRegistryRefreshOptions): void {\n\t\tconst previousRegistryNames = new Set(this._toolRegistry.keys());\n\t\tconst previousActiveToolNames = this.getActiveToolNames();\n\t\tconst isAllowedTool = (name: string): boolean => this._isToolPermitted(name);\n\t\tconst lifecycleToolNames = new Set([\"load_tool\", \"unload_tool\"]);\n\t\tconst hasAvailableSessionTools = this.getAvailableSessionTools().length > 0;\n\t\tconst isVisibleBaseTool = (name: string): boolean =>\n\t\t\tisAllowedTool(name) && (hasAvailableSessionTools || !lifecycleToolNames.has(name));\n\t\tconst registeredTools = this._extensionRunner\n\t\t\t.getAllRegisteredTools()\n\t\t\t.filter((tool) => tool.lazy !== true || tool.loaded === true);\n\t\tconst activeSessionTools = Array.from(this._sessionTools.values()).filter((tool) => !tool.lazy || tool.loaded);\n\t\tconst allCustomTools = [\n\t\t\t...registeredTools,\n\t\t\t...this._customTools.map((definition) => ({\n\t\t\t\tdefinition,\n\t\t\t\tsourceInfo: createSyntheticSourceInfo(`<sdk:${definition.name}>`, { source: \"sdk\" }),\n\t\t\t})),\n\t\t\t...activeSessionTools,\n\t\t].filter((tool) => isAllowedTool(tool.definition.name));\n\t\tconst definitionRegistry = new Map<string, ToolDefinitionEntry>(\n\t\t\tArray.from(this._baseToolDefinitions.entries())\n\t\t\t\t.filter(([name]) => isVisibleBaseTool(name))\n\t\t\t\t.map(([name, definition]) => [\n\t\t\t\t\tname,\n\t\t\t\t\t{\n\t\t\t\t\t\tdefinition,\n\t\t\t\t\t\tsourceInfo: createSyntheticSourceInfo(`<builtin:${name}>`, { source: \"builtin\" }),\n\t\t\t\t\t},\n\t\t\t\t]),\n\t\t);\n\t\tfor (const tool of allCustomTools) {\n\t\t\tdefinitionRegistry.set(tool.definition.name, {\n\t\t\t\tdefinition: tool.definition,\n\t\t\t\tsourceInfo: tool.sourceInfo,\n\t\t\t});\n\t\t}\n\t\tthis._toolDefinitions = definitionRegistry;\n\t\tthis._toolPromptSnippets = new Map(\n\t\t\tArray.from(definitionRegistry.values())\n\t\t\t\t.map(({ definition }) => {\n\t\t\t\t\tconst snippet = this._normalizePromptSnippet(definition.promptSnippet);\n\t\t\t\t\treturn snippet ? ([definition.name, snippet] as const) : undefined;\n\t\t\t\t})\n\t\t\t\t.filter((entry): entry is readonly [string, string] => entry !== undefined),\n\t\t);\n\t\tthis._toolPromptGuidelines = new Map(\n\t\t\tArray.from(definitionRegistry.values())\n\t\t\t\t.map(({ definition }) => {\n\t\t\t\t\tconst guidelines = this._normalizePromptGuidelines(definition.promptGuidelines);\n\t\t\t\t\treturn guidelines.length > 0 ? ([definition.name, guidelines] as const) : undefined;\n\t\t\t\t})\n\t\t\t\t.filter((entry): entry is readonly [string, string[]] => entry !== undefined),\n\t\t);\n\t\tconst runner = this._extensionRunner;\n\t\tconst wrappedExtensionTools = wrapRegisteredTools(allCustomTools, runner);\n\t\tconst wrappedBuiltInTools = wrapRegisteredTools(\n\t\t\tArray.from(this._baseToolDefinitions.values())\n\t\t\t\t.filter((definition) => isVisibleBaseTool(definition.name))\n\t\t\t\t.map((definition) => ({\n\t\t\t\t\tdefinition,\n\t\t\t\t\tsourceInfo: createSyntheticSourceInfo(`<builtin:${definition.name}>`, { source: \"builtin\" }),\n\t\t\t\t})),\n\t\t\trunner,\n\t\t);\n\n\t\tconst toolRegistry = new Map(wrappedBuiltInTools.map((tool) => [tool.name, tool]));\n\t\tfor (const tool of wrappedExtensionTools as AgentTool[]) {\n\t\t\ttoolRegistry.set(tool.name, tool);\n\t\t}\n\t\tthis._toolRegistry = toolRegistry;\n\n\t\tconst nextActiveToolNames = this._resolveRefreshedActiveToolNames(\n\t\t\toptions,\n\t\t\tpreviousRegistryNames,\n\t\t\tpreviousActiveToolNames,\n\t\t\twrappedExtensionTools,\n\t\t\thasAvailableSessionTools,\n\t\t);\n\t\tthis.setActiveToolsByName([...new Set(nextActiveToolNames)]);\n\t}\n\n\tprivate _registerLspToolDefinitions(getState: () => LspRuntimeState, getOperations?: () => ToolOperations): void {\n\t\tfor (const definition of createLspToolDefinitions(getState, getOperations)) {\n\t\t\tthis._baseToolDefinitions.set(definition.name, definition as unknown as ToolDefinition);\n\t\t}\n\t}\n\n\tprivate async _replaceLspRuntime(configuration: ResolvedLspConfiguration): Promise<void> {\n\t\tconst nextConfiguration = structuredClone(configuration);\n\t\tconst previous = this._lspRuntimeState;\n\t\tif (previous && nextConfiguration.enabled && !this._baseToolsOverride) {\n\t\t\tawait previous.manager.setConfiguration(nextConfiguration);\n\t\t\tthis._lspConfiguration = nextConfiguration;\n\t\t\tthis._updateCurrentRunnerLspOwnership();\n\t\t\treturn;\n\t\t}\n\t\tconst operations = this.getToolOperations();\n\t\tconst next =\n\t\t\tnextConfiguration.enabled && !this._baseToolsOverride\n\t\t\t\t? createLspRuntimeState(operations.cwd, {\n\t\t\t\t\t\tconfiguration: nextConfiguration,\n\t\t\t\t\t\tconnectionFactories: this._lspConnectionFactories,\n\t\t\t\t\t\tgetToolBackendInfo: () => operations.getBackendInfo?.() ?? { type: \"local\", cwd: operations.cwd },\n\t\t\t\t\t\tgetToolOperations: () => operations,\n\t\t\t\t\t})\n\t\t\t\t: undefined;\n\t\tthis._lspConfiguration = nextConfiguration;\n\t\tthis._lspRuntimeState = next;\n\t\tthis._updateCurrentRunnerLspOwnership();\n\t\tfor (const name of LSP_TOOL_NAMES) this._baseToolDefinitions.delete(name);\n\t\tif (next) this._registerLspToolDefinitions(() => next);\n\t\tthis._refreshToolRegistry({ activeToolNames: this._withCurrentDefaultTools(this.getActiveToolNames()) });\n\t\tawait previous?.manager.shutdownAll();\n\t}\n\tprivate _createWorkspaceToolHost(operations: ToolOperations): WorkspaceToolHost {\n\t\tconst shellPath = this.settingsManager.getShellPath();\n\t\tconst daemonOwnedLsp = operations.getBackendInfo?.().type === \"remote\";\n\t\treturn new WorkspaceToolHost({\n\t\t\tcwd: operations.cwd,\n\t\t\toperations,\n\t\t\ttools: {\n\t\t\t\tread: {\n\t\t\t\t\tautoResizeImages: this.settingsManager.getImageAutoResize(),\n\t\t\t\t\toperationsForPath: (path) => this._getReadOperationsForPath(path, shellPath),\n\t\t\t\t},\n\t\t\t\tbash: { commandPrefix: this.settingsManager.getShellCommandPrefix() },\n\t\t\t},\n\t\t\tadditionalDefinitions: daemonOwnedLsp\n\t\t\t\t? createLspToolDefinitions(\n\t\t\t\t\t\t() => {\n\t\t\t\t\t\t\tthrow new Error(\"Remote LSP proxy attempted local execution\");\n\t\t\t\t\t\t},\n\t\t\t\t\t\t() => operations,\n\t\t\t\t\t)\n\t\t\t\t: undefined,\n\t\t\tonCatalogChanged: () => this._refreshRemoteWorkspaceToolCatalog(),\n\t\t});\n\t}\n\n\tprivate _refreshRemoteWorkspaceToolCatalog(): Promise<void> {\n\t\tif (this._disposed || this._baseToolsOverride) return Promise.resolve();\n\t\treturn this._enqueueRuntimeLifecycle(async () => {\n\t\t\tif (this._disposed || this._baseToolsOverride) return;\n\t\t\tconst previous = this._workspaceToolHost;\n\t\t\tconst next = this._createWorkspaceToolHost(this.getToolOperations());\n\t\t\tconst activeToolNames = this.getActiveToolNames();\n\t\t\tfor (const name of [...WORKSPACE_TOOL_NAMES, ...LSP_TOOL_NAMES]) this._baseToolDefinitions.delete(name);\n\t\t\tfor (const [name, definition] of next.getDefinitions()) {\n\t\t\t\tthis._baseToolDefinitions.set(name, definition as unknown as ToolDefinition);\n\t\t\t}\n\t\t\tthis._workspaceToolHost = next;\n\t\t\tthis._refreshToolRegistry({ activeToolNames: this._withCurrentDefaultTools(activeToolNames) });\n\t\t\tif (previous) {\n\t\t\t\tprevious.detachCatalogListener();\n\t\t\t\tthis._retiredWorkspaceToolHosts.push(previous);\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate _captureRuntimeBuildSnapshot(): RuntimeBuildSnapshot {\n\t\treturn {\n\t\t\tbaseToolDefinitions: this._baseToolDefinitions,\n\t\t\tlspRuntimeState: this._lspRuntimeState,\n\t\t\textensionRunner: this._extensionRunner as ExtensionRunner | undefined,\n\t\t\ttoolRegistry: this._toolRegistry,\n\t\t\ttoolDefinitions: this._toolDefinitions,\n\t\t\ttoolPromptSnippets: this._toolPromptSnippets,\n\t\t\ttoolPromptGuidelines: this._toolPromptGuidelines,\n\t\t\tbaseSystemPrompt: this._baseSystemPrompt,\n\t\t\tbaseSystemPromptOptions: this._baseSystemPromptOptions as BuildSystemPromptOptions | undefined,\n\t\t\tagentTools: this.agent.state.tools,\n\t\t\tagentSystemPrompt: this.agent.state.systemPrompt,\n\t\t\textensionRunnerRef: this._extensionRunnerRef?.current,\n\t\t\tworkspaceToolHost: this._workspaceToolHost,\n\t\t};\n\t}\n\n\tprivate _createRuntimeBaseToolDefinitions(\n\t\tworkspaceToolHost: WorkspaceToolHost | undefined,\n\t): Record<string, ToolDefinition> {\n\t\tif (this._baseToolsOverride) {\n\t\t\treturn Object.fromEntries(\n\t\t\t\tObject.entries(this._baseToolsOverride).map(([name, tool]) => [\n\t\t\t\t\tname,\n\t\t\t\t\tcreateToolDefinitionFromAgentTool(tool),\n\t\t\t\t]),\n\t\t\t);\n\t\t}\n\t\treturn {\n\t\t\t...Object.fromEntries(workspaceToolHost?.getDefinitions() ?? []),\n\t\t\tsession_search: createSessionSearchToolDefinition(this.session) as unknown as ToolDefinition,\n\t\t\tsession_entry_get: createSessionEntryGetToolDefinition(this.session) as unknown as ToolDefinition,\n\t\t\tcompress_context: createCompressContextToolDefinition(this.session, (request) => {\n\t\t\t\tthis._pendingContextCompression = request;\n\t\t\t}) as unknown as ToolDefinition,\n\t\t\twebsearch: createWebsearchToolDefinition(\n\t\t\t\tparseWebsearchToolOptions(this.settingsManager.getToolSettings(\"websearch\")),\n\t\t\t) as unknown as ToolDefinition,\n\t\t\tsubagent: createSubagentToolDefinition({\n\t\t\t\trunner: this._subagentRunner,\n\t\t\t\tmodelCatalog: formatSubagentModelCatalog(this.model, this._getSubagentModels()),\n\t\t\t\tresolveModelHint: (hint) => this._resolveSubagentModelHint(hint),\n\t\t\t\ttrustProjectAgents: this._trustProjectAgents,\n\t\t\t\trunRegistry: this._subagentRunRegistry,\n\t\t\t\tconfigRegistry: this._subagentConfigRegistry,\n\t\t\t}) as unknown as ToolDefinition,\n\t\t\tsubagent_runs: createSubagentRunsToolDefinition(this._subagentRunRegistry) as unknown as ToolDefinition,\n\t\t\tcreate_subagent: createCreateSubagentToolDefinition(this._subagentConfigRegistry) as unknown as ToolDefinition,\n\t\t};\n\t}\n\n\tprivate _initializeRuntimeLsp(operations: ToolOperations): void {\n\t\tconst daemonOwnedLsp = operations.getBackendInfo?.().type === \"remote\";\n\t\tif (daemonOwnedLsp || this._baseToolsOverride || !this._lspConfiguration.enabled) {\n\t\t\tthis._lspRuntimeState = undefined;\n\t\t\treturn;\n\t\t}\n\t\tthis._lspRuntimeState = createLspRuntimeState(operations.cwd, {\n\t\t\tconfiguration: this._lspConfiguration,\n\t\t\tconnectionFactories: this._lspConnectionFactories,\n\t\t\tgetToolBackendInfo: () => operations.getBackendInfo?.() ?? { type: \"local\", cwd: operations.cwd },\n\t\t\tgetToolOperations: () => operations,\n\t\t});\n\t\tthis._registerLspToolDefinitions(() => {\n\t\t\tif (!this._lspRuntimeState) throw new Error(\"LSP was disabled while a tool call was in progress\");\n\t\t\treturn this._lspRuntimeState;\n\t\t});\n\t}\n\n\tprivate _restoreRuntimeBuildSnapshot(\n\t\tsnapshot: RuntimeBuildSnapshot,\n\t\tworkspaceToolHost: WorkspaceToolHost | undefined,\n\t): void {\n\t\tconst failedExtensionRunner = this._extensionRunner as ExtensionRunner | undefined;\n\t\tconst failedLspRuntimeState = this._lspRuntimeState;\n\t\tthis._workspaceToolHost = snapshot.workspaceToolHost;\n\t\tthis._baseToolDefinitions = snapshot.baseToolDefinitions;\n\t\tthis._lspRuntimeState = snapshot.lspRuntimeState;\n\t\tthis._toolRegistry = snapshot.toolRegistry;\n\t\tthis._toolDefinitions = snapshot.toolDefinitions;\n\t\tthis._toolPromptSnippets = snapshot.toolPromptSnippets;\n\t\tthis._toolPromptGuidelines = snapshot.toolPromptGuidelines;\n\t\tthis._baseSystemPrompt = snapshot.baseSystemPrompt;\n\t\tif (snapshot.baseSystemPromptOptions) this._baseSystemPromptOptions = snapshot.baseSystemPromptOptions;\n\t\tthis.agent.state.tools = snapshot.agentTools;\n\t\tthis.agent.state.systemPrompt = snapshot.agentSystemPrompt;\n\t\tif (failedExtensionRunner !== snapshot.extensionRunner) {\n\t\t\tfailedExtensionRunner?.invalidate(STALE_EXTENSION_CONTEXT_MESSAGE);\n\t\t\tthis._extensionErrorUnsubscriber?.();\n\t\t\tthis._extensionErrorUnsubscriber = undefined;\n\t\t\tthis._extensionRunner = snapshot.extensionRunner as ExtensionRunner;\n\t\t\tif (snapshot.extensionRunner) this._applyExtensionBindings(snapshot.extensionRunner);\n\t\t}\n\t\tif (this._extensionRunnerRef) this._extensionRunnerRef.current = snapshot.extensionRunnerRef;\n\t\tif (failedLspRuntimeState && failedLspRuntimeState !== snapshot.lspRuntimeState) {\n\t\t\tvoid failedLspRuntimeState.manager.shutdownAll();\n\t\t}\n\t\tvoid workspaceToolHost?.dispose();\n\t}\n\n\tprivate _buildRuntime(options: BuildRuntimeOptions): void {\n\t\tconst operations = this.getToolOperations();\n\t\tconst snapshot = this._captureRuntimeBuildSnapshot();\n\t\tconst workspaceToolHost = this._baseToolsOverride ? undefined : this._createWorkspaceToolHost(operations);\n\t\ttry {\n\t\t\tconst baseToolDefinitions = this._createRuntimeBaseToolDefinitions(workspaceToolHost);\n\t\t\tthis._baseToolDefinitions = new Map(Object.entries(baseToolDefinitions));\n\t\t\tthis._initializeRuntimeLsp(operations);\n\n\t\t\tthis._baseToolDefinitions.set(\"load_tool\", this._createLoadToolDefinition());\n\t\t\tthis._baseToolDefinitions.set(\"unload_tool\", this._createUnloadToolDefinition());\n\n\t\t\tconst extensionsResult = this._resourceLoader.getExtensions();\n\t\t\tif (options.flagValues) {\n\t\t\t\tfor (const [name, value] of options.flagValues) {\n\t\t\t\t\textensionsResult.runtime.flagValues.set(name, value);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis._extensionRunner = new ExtensionRunner(\n\t\t\t\textensionsResult.extensions,\n\t\t\t\textensionsResult.runtime,\n\t\t\t\tthis._cwd,\n\t\t\t\tthis.session,\n\t\t\t\tthis._modelRegistry,\n\t\t\t);\n\t\t\tif (this._extensionRunnerRef) {\n\t\t\t\tthis._extensionRunnerRef.current = this._extensionRunner;\n\t\t\t}\n\t\t\tthis._bindExtensionCore(this._extensionRunner);\n\t\t\tthis._applyExtensionBindings(this._extensionRunner);\n\t\t\tthis._workspaceToolHost = workspaceToolHost;\n\n\t\t\tconst defaultActiveToolNames = this._baseToolsOverride\n\t\t\t\t? Object.keys(this._baseToolsOverride)\n\t\t\t\t: getDefaultActiveToolNames(\n\t\t\t\t\t\tthis.settingsManager.getEnableSubagents(),\n\t\t\t\t\t\tthis._enableLspTools ?? this.settingsManager.getEnableLspTools(),\n\t\t\t\t\t);\n\t\t\tconst baseActiveToolNames = this._withCurrentDefaultTools(options.activeToolNames ?? defaultActiveToolNames);\n\t\t\tthis._refreshToolRegistry({\n\t\t\t\tactiveToolNames: baseActiveToolNames,\n\t\t\t\tincludeAllExtensionTools: options.includeAllExtensionTools,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthis._restoreRuntimeBuildSnapshot(snapshot, workspaceToolHost);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprivate _withCurrentDefaultTools(activeToolNames: string[]): string[] {\n\t\tif (this._baseToolsOverride || this._allowedToolNames) {\n\t\t\treturn activeToolNames.filter((name) => !this._excludedToolNames.has(name));\n\t\t}\n\t\tconst enableLspTools = this._enableLspTools ?? this.settingsManager.getEnableLspTools();\n\t\tconst configuredDefaults = getDefaultActiveToolNames(this.settingsManager.getEnableSubagents(), enableLspTools);\n\t\tconst subagentToolNames = new Set<string>(SUBAGENT_TOOL_NAMES);\n\t\tconst lspToolNames = new Set<string>(LSP_TOOL_NAMES);\n\t\tconst retainedToolNames = activeToolNames.filter(\n\t\t\t(name) =>\n\t\t\t\t(enableLspTools || !lspToolNames.has(name)) &&\n\t\t\t\t(this.settingsManager.getEnableSubagents() || !subagentToolNames.has(name)),\n\t\t);\n\t\tconst active = new Set(retainedToolNames);\n\t\tconst usesDefaultCoreTools = CORE_DEFAULT_TOOL_NAMES.every((toolName) => active.has(toolName));\n\t\tconst expanded = usesDefaultCoreTools\n\t\t\t? [...retainedToolNames, ...configuredDefaults.filter((toolName) => !active.has(toolName))]\n\t\t\t: retainedToolNames;\n\t\treturn expanded.filter((toolName) => !this._excludedToolNames.has(toolName));\n\t}\n\tprivate _isToolPermitted(name: string): boolean {\n\t\treturn !this._excludedToolNames.has(name) && (!this._allowedToolNames || this._allowedToolNames.has(name));\n\t}\n\n\treload(): Promise<AgentSessionReloadResult> {\n\t\tif (this._disposed) return Promise.reject(new Error(\"Cannot reload after AgentSession disposal\"));\n\t\treturn this._enqueueRuntimeLifecycle(() => this._reload());\n\t}\n\n\tprivate async _reload(): Promise<AgentSessionReloadResult> {\n\t\tconst previousRunner = this._extensionRunner;\n\t\tconst previousLspRuntimeState = this._lspRuntimeState;\n\t\tconst previousWorkspaceToolHost = this._workspaceToolHost;\n\t\tconst previousRetiredWorkspaceToolHosts = [...this._retiredWorkspaceToolHosts];\n\t\tconst previousFlagValues = previousRunner.getFlagValues();\n\t\tawait this.settingsManager.reload();\n\t\tlet lspResult: LoadLspConfigurationResult | undefined;\n\t\tif (this._resolveLspConfiguration) {\n\t\t\tlspResult = await this._resolveLspConfiguration();\n\t\t\tthis._lspConfiguration = structuredClone(lspResult.configuration);\n\t\t\tthis._onLspConfigurationDiagnostics?.(structuredClone(lspResult.diagnostics));\n\t\t}\n\t\tif (this._resolveHooks) this._loadedHooks = await this._resolveHooks();\n\t\tawait this._resourceLoader.reload();\n\t\tresetApiProviders();\n\t\tconst activeToolNames = this._withCurrentDefaultTools(this.getActiveToolNames());\n\t\tthis._buildRuntime({\n\t\t\tactiveToolNames,\n\t\t\tflagValues: previousFlagValues,\n\t\t\tincludeAllExtensionTools: true,\n\t\t});\n\t\tthis._retiredWorkspaceToolHosts = [];\n\t\ttry {\n\t\t\tawait emitSessionShutdownEvent(previousRunner, { type: \"session_shutdown\", reason: \"reload\" });\n\t\t} finally {\n\t\t\tpreviousRunner.invalidate(STALE_EXTENSION_CONTEXT_MESSAGE);\n\t\t\tawait Promise.all([\n\t\t\t\tpreviousLspRuntimeState?.manager.shutdownAll(),\n\t\t\t\tpreviousWorkspaceToolHost?.dispose(),\n\t\t\t\t...previousRetiredWorkspaceToolHosts.map((host) => host.dispose()),\n\t\t\t]);\n\t\t}\n\n\t\tconst hasBindings =\n\t\t\tthis._extensionUIContext ||\n\t\t\tthis._extensionCommandContextActions ||\n\t\t\tthis._extensionShutdownHandler ||\n\t\t\tthis._extensionErrorListener;\n\t\tif (hasBindings) {\n\t\t\tawait this._extensionRunner.emit({ type: \"session_start\", reason: \"reload\" });\n\t\t\tawait this.extendResourcesFromExtensions(\"reload\");\n\t\t}\n\t\treturn lspResult ? { lsp: structuredClone(lspResult) } : {};\n\t}\n\n\t// =========================================================================\n\t// Auto-Retry\n\t// =========================================================================\n\n\t/**\n\t * Check if an error is retryable (overloaded, rate limit, server errors).\n\t * Context overflow errors are NOT retryable (handled by compaction instead).\n\t */\n\tprivate _isRetryableError(message: AssistantMessage): boolean {\n\t\t// Context overflow is handled by compaction, not retry.\n\t\tif (isContextOverflow(message, this.model?.contextWindow ?? 0)) return false;\n\t\treturn isRetryableAssistantError(message);\n\t}\n\n\tprivate _summarizationRetryCallbacks(source: SummarizationRetrySource): RetryCallbacks {\n\t\treturn {\n\t\t\tonRetryScheduled: (attempt, maxAttempts, delayMs, errorMessage) => {\n\t\t\t\tthis._emit({\n\t\t\t\t\ttype: \"summarization_retry_scheduled\",\n\t\t\t\t\tattempt,\n\t\t\t\t\tmaxAttempts,\n\t\t\t\t\tdelayMs,\n\t\t\t\t\terrorMessage,\n\t\t\t\t});\n\t\t\t},\n\t\t\tonRetryAttemptStart: () => {\n\t\t\t\tthis._emit({ type: \"summarization_retry_attempt_start\", ...source });\n\t\t\t},\n\t\t\tonRetryFinished: () => {\n\t\t\t\tthis._emit({ type: \"summarization_retry_finished\" });\n\t\t\t},\n\t\t};\n\t}\n\n\t/**\n\t * Prepare a retryable error for continuation with exponential backoff.\n\t * @returns true if the caller should continue the agent, false otherwise\n\t */\n\tprivate async _prepareRetry(message: AssistantMessage): Promise<boolean> {\n\t\tconst settings = this.settingsManager.getRetrySettings();\n\t\tif (!settings.enabled) {\n\t\t\treturn false;\n\t\t}\n\n\t\tthis._retryAttempt++;\n\n\t\tif (this._retryAttempt > settings.maxRetries) {\n\t\t\t// Preserve the completed attempt count so post-run handling can emit the final failure.\n\t\t\tthis._retryAttempt--;\n\t\t\treturn false;\n\t\t}\n\n\t\tconst delayMs = settings.baseDelayMs * 2 ** (this._retryAttempt - 1);\n\n\t\tthis._emit({\n\t\t\ttype: \"auto_retry_start\",\n\t\t\tattempt: this._retryAttempt,\n\t\t\tmaxAttempts: settings.maxRetries,\n\t\t\tdelayMs,\n\t\t\terrorMessage: message.errorMessage || \"Unknown error\",\n\t\t});\n\n\t\t// Remove error message from agent state (keep in session for history)\n\t\tconst messages = this.agent.state.messages;\n\t\tif (messages.length > 0 && messages[messages.length - 1].role === \"assistant\") {\n\t\t\tthis.agent.state.messages = messages.slice(0, -1);\n\t\t}\n\n\t\t// Wait with exponential backoff (abortable)\n\t\tthis._retryAbortController = new AbortController();\n\t\ttry {\n\t\t\tawait sleep(delayMs, this._retryAbortController.signal);\n\t\t} catch {\n\t\t\t// Aborted during sleep - emit end event so UI can clean up\n\t\t\tconst attempt = this._retryAttempt;\n\t\t\tthis._retryAttempt = 0;\n\t\t\tthis._emit({\n\t\t\t\ttype: \"auto_retry_end\",\n\t\t\t\tsuccess: false,\n\t\t\t\tattempt,\n\t\t\t\tfinalError: \"Retry cancelled\",\n\t\t\t});\n\t\t\treturn false;\n\t\t} finally {\n\t\t\tthis._retryAbortController = undefined;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * Cancel in-progress retry.\n\t */\n\tabortRetry(): void {\n\t\tthis._retryAbortController?.abort();\n\t}\n\n\t/** Whether auto-retry is currently in progress */\n\tget isRetrying(): boolean {\n\t\treturn this._retryAbortController !== undefined;\n\t}\n\n\t/** Whether auto-retry is enabled */\n\tget autoRetryEnabled(): boolean {\n\t\treturn this.settingsManager.getRetryEnabled();\n\t}\n\n\t/**\n\t * Toggle auto-retry setting.\n\t */\n\tsetAutoRetryEnabled(enabled: boolean): void {\n\t\tthis.settingsManager.setRetryEnabled(enabled);\n\t}\n\n\t// =========================================================================\n\t// Bash Execution\n\t// =========================================================================\n\n\t/**\n\t * Execute a bash command.\n\t * Adds result to agent context and session unless options.record is false.\n\t * @param command The bash command to execute\n\t * @param onChunk Optional streaming callback for output\n\t * @param options.excludeFromContext If true, command output won't be sent to LLM (!! prefix)\n\t * @param options.operations Custom ToolOperations for remote execution\n\t * @param options.record If false, result won't be stored in agent state or session history\n\t * @param options.truncate If false, returned output won't be truncated\n\t */\n\tasync executeBash(\n\t\tcommand: string,\n\t\tonChunk?: (chunk: string) => void,\n\t\toptions?: ExecuteBashOptions,\n\t): Promise<BashResult> {\n\t\treturn this._runActivity(\"bash execution\", async () => {\n\t\t\tconst abortController = new AbortController();\n\t\t\tthis._bashAbortControllers.add(abortController);\n\n\t\t\t// Apply command prefix if configured (e.g., \"shopt -s expand_aliases\" for alias support)\n\t\t\tconst prefix = this.settingsManager.getShellCommandPrefix();\n\t\t\tconst shellPath = this.settingsManager.getShellPath();\n\t\t\tconst resolvedCommand = prefix ? `${prefix}\\n${command}` : command;\n\n\t\t\ttry {\n\t\t\t\tconst operations =\n\t\t\t\t\toptions?.operations ??\n\t\t\t\t\tthis._toolOperations ??\n\t\t\t\t\tcreateLocalBashOperations({ cwd: this.session.getCwd(), shellPath });\n\t\t\t\tconst result = await executeBashWithOperations(resolvedCommand, operations.cwd, operations, {\n\t\t\t\t\tonChunk,\n\t\t\t\t\tsignal: abortController.signal,\n\t\t\t\t\ttruncate: options?.truncate,\n\t\t\t\t});\n\n\t\t\t\tif (options?.record !== false) this._recordBashResult(command, result, options);\n\t\t\t\treturn result;\n\t\t\t} finally {\n\t\t\t\tthis._bashAbortControllers.delete(abortController);\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Record a bash execution result in session history.\n\t * Used by executeBash and by extensions that handle bash execution themselves.\n\t */\n\trecordBashResult(command: string, result: BashResult, options?: RecordBashResultOptions): void {\n\t\tif (this._disposed) throw new Error(\"Agent session is disposed\");\n\t\tthis._runSynchronousActivity(\"bash result recording\", () => this._recordBashResult(command, result, options));\n\t}\n\n\tprivate _recordBashResult(command: string, result: BashResult, options?: RecordBashResultOptions): void {\n\t\tconst bashMessage: BashExecutionMessage = {\n\t\t\trole: \"bashExecution\",\n\t\t\tcommand,\n\t\t\toutput: result.output,\n\t\t\texitCode: result.exitCode,\n\t\t\tcancelled: result.cancelled,\n\t\t\ttruncated: result.truncated,\n\t\t\tfullOutputPath: result.fullOutputPath,\n\t\t\ttimestamp: Date.now(),\n\t\t\texcludeFromContext: options?.excludeFromContext,\n\t\t};\n\n\t\t// If agent is streaming, defer adding to avoid breaking tool_use/tool_result ordering\n\t\tif (this._isAgentRunActive) {\n\t\t\t// Queue for later - will be flushed on agent_end\n\t\t\tthis._pendingBashMessages.push(bashMessage);\n\t\t} else {\n\t\t\t// Add to agent state immediately\n\t\t\tthis.agent.state.messages.push(bashMessage);\n\n\t\t\t// Save to session\n\t\t\tthis.session.appendMessage(bashMessage);\n\t\t}\n\t}\n\n\t/**\n\t * Cancel running bash command.\n\t */\n\tabortBash(): void {\n\t\tfor (const controller of this._bashAbortControllers) controller.abort();\n\t}\n\n\t/** Whether a bash command is currently running */\n\tget isBashRunning(): boolean {\n\t\treturn this._bashAbortControllers.size > 0;\n\t}\n\n\t/** Whether there are pending bash messages waiting to be flushed */\n\tget hasPendingBashMessages(): boolean {\n\t\treturn this._pendingBashMessages.length > 0;\n\t}\n\n\t/**\n\t * Flush pending bash messages to agent state and session.\n\t * Called after agent turn completes to maintain proper message ordering.\n\t */\n\tprivate _flushPendingBashMessages(): void {\n\t\tif (this._pendingBashMessages.length === 0) return;\n\n\t\tfor (const bashMessage of this._pendingBashMessages) {\n\t\t\t// Add to agent state\n\t\t\tthis.agent.state.messages.push(bashMessage);\n\n\t\t\t// Save to session\n\t\t\tthis.session.appendMessage(bashMessage);\n\t\t}\n\n\t\tthis._pendingBashMessages = [];\n\t}\n\n\tprivate async _prepareTreeNavigation(\n\t\tpreparation: TreePreparation,\n\t\tsettings: TreeNavigationSettings,\n\t\tshouldSummarize: boolean,\n\t\tsignal: AbortSignal,\n\t): Promise<TreeExtensionPreparationResult> {\n\t\tif (!this._extensionRunner.hasHandlers(\"session_before_tree\")) {\n\t\t\treturn { ...settings, cancelled: false, fromExtension: false };\n\t\t}\n\t\tconst result = (await this._extensionRunner.emit({\n\t\t\ttype: \"session_before_tree\",\n\t\t\tpreparation,\n\t\t\tsignal,\n\t\t})) as SessionBeforeTreeResult | undefined;\n\t\tif (result?.cancel) return { ...settings, cancelled: true, fromExtension: false };\n\t\treturn {\n\t\t\tcustomInstructions: result?.customInstructions ?? settings.customInstructions,\n\t\t\treplaceInstructions: result?.replaceInstructions ?? settings.replaceInstructions,\n\t\t\tlabel: result?.label ?? settings.label,\n\t\t\tcancelled: false,\n\t\t\textensionSummary: shouldSummarize ? result?.summary : undefined,\n\t\t\tfromExtension: shouldSummarize && result?.summary !== undefined,\n\t\t};\n\t}\n\n\tprivate async _resolveTreeSummary(\n\t\tentriesToSummarize: SessionEntry[],\n\t\tshouldSummarize: boolean,\n\t\tpreparation: TreeExtensionPreparationResult,\n\t\tsignal: AbortSignal,\n\t): Promise<TreeSummaryResolution> {\n\t\tif (preparation.extensionSummary) {\n\t\t\treturn {\n\t\t\t\tcancelled: false,\n\t\t\t\tsummaryText: preparation.extensionSummary.summary,\n\t\t\t\tsummaryDetails: preparation.extensionSummary.details,\n\t\t\t\tfromExtension: true,\n\t\t\t};\n\t\t}\n\t\tif (!shouldSummarize || entriesToSummarize.length === 0) {\n\t\t\treturn { cancelled: false, fromExtension: false };\n\t\t}\n\t\tconst model = this.model!;\n\t\tconst { apiKey, headers } = await this._getCompactionRequestAuth(model);\n\t\tconst branchSummarySettings = this.settingsManager.getBranchSummarySettings();\n\t\tconst result = await generateBranchSummary(entriesToSummarize, {\n\t\t\tmodel,\n\t\t\tapiKey,\n\t\t\theaders,\n\t\t\tsignal,\n\t\t\tcustomInstructions: preparation.customInstructions,\n\t\t\treplaceInstructions: preparation.replaceInstructions,\n\t\t\treserveTokens: branchSummarySettings.reserveTokens,\n\t\t\tstreamFn: this.agent.streamFn,\n\t\t\tretry: this.settingsManager.getRetrySettings(),\n\t\t\tcallbacks: this._summarizationRetryCallbacks({ source: \"branchSummary\" }),\n\t\t});\n\t\tif (result.aborted) return { cancelled: true, aborted: true, fromExtension: false };\n\t\tif (result.error) throw new Error(result.error);\n\t\treturn {\n\t\t\tcancelled: false,\n\t\t\tsummaryText: result.summary,\n\t\t\tsummaryDetails: {\n\t\t\t\treadFiles: result.readFiles || [],\n\t\t\t\tmodifiedFiles: result.modifiedFiles || [],\n\t\t\t},\n\t\t\tfromExtension: false,\n\t\t};\n\t}\n\n\tprivate _resolveTreeTarget(targetEntry: SessionEntry, targetId: string): TreeTargetResolution {\n\t\tif (targetEntry.type === \"message\" && targetEntry.message.role === \"user\") {\n\t\t\treturn {\n\t\t\t\tnewLeafId: targetEntry.parentId,\n\t\t\t\teditorText: this._extractUserMessageText(targetEntry.message),\n\t\t\t};\n\t\t}\n\t\tif (targetEntry.type === \"custom_message\") {\n\t\t\tconst editorText =\n\t\t\t\ttypeof targetEntry.content === \"string\"\n\t\t\t\t\t? targetEntry.content\n\t\t\t\t\t: targetEntry.content\n\t\t\t\t\t\t\t.filter((content): content is TextContent => content.type === \"text\")\n\t\t\t\t\t\t\t.map((content) => content.text)\n\t\t\t\t\t\t\t.join(\"\");\n\t\t\treturn { newLeafId: targetEntry.parentId, editorText };\n\t\t}\n\t\treturn { newLeafId: targetId };\n\t}\n\n\tprivate _switchTreeLeaf(\n\t\ttargetId: string,\n\t\tnewLeafId: string | null,\n\t\tsummary: TreeSummaryResolution,\n\t\tlabel: string | undefined,\n\t): BranchSummaryEntry | undefined {\n\t\tlet summaryEntry: BranchSummaryEntry | undefined;\n\t\tif (summary.summaryText) {\n\t\t\tconst summaryId = this.session.branchWithSummary(\n\t\t\t\tnewLeafId,\n\t\t\t\tsummary.summaryText,\n\t\t\t\tsummary.summaryDetails,\n\t\t\t\tsummary.fromExtension,\n\t\t\t);\n\t\t\tsummaryEntry = this.session.getEntry(summaryId) as BranchSummaryEntry;\n\t\t\tif (label) this.session.appendLabelChange(summaryId, label);\n\t\t} else if (newLeafId === null) {\n\t\t\tthis.session.resetLeaf();\n\t\t} else {\n\t\t\tthis.session.branch(newLeafId);\n\t\t}\n\t\tif (label && !summary.summaryText) this.session.appendLabelChange(targetId, label);\n\t\treturn summaryEntry;\n\t}\n\n\t// =========================================================================\n\t// Session Management\n\t// =========================================================================\n\n\t/**\n\t * Set a display name for the current session.\n\t */\n\tsetSessionName(name: string): void {\n\t\tthis.session.appendSessionInfo(name);\n\t\tconst event = { type: \"session_info_changed\", name: this.session.getSessionName() } as const;\n\t\tvoid this._emit(event);\n\t\tvoid this._extensionRunner.emit(event);\n\t}\n\n\t// =========================================================================\n\t// Tree Navigation\n\t// =========================================================================\n\n\t/**\n\t * Navigate to a different node in the session tree.\n\t * Unlike fork() which creates a new session file, this stays in the same file.\n\t *\n\t * @param targetId The entry ID to navigate to\n\t * @param options.summarize Whether user wants to summarize abandoned branch\n\t * @param options.customInstructions Custom instructions for summarizer\n\t * @param options.replaceInstructions If true, customInstructions replaces the default prompt\n\t * @param options.label Label to attach to the branch summary entry\n\t * @returns Result with editorText (if user message) and cancelled status\n\t */\n\tnavigateTree(targetId: string, options: NavigateTreeOptions = {}): Promise<NavigateTreeResult> {\n\t\treturn this._runActivity(\"tree navigation\", () => this._navigateTree(targetId, options));\n\t}\n\n\tprivate async _navigateTree(targetId: string, options: NavigateTreeOptions = {}): Promise<NavigateTreeResult> {\n\t\tconst oldLeafId = this.session.getLeafId();\n\n\t\t// No-op if already at target\n\t\tif (targetId === oldLeafId) {\n\t\t\treturn { cancelled: false };\n\t\t}\n\n\t\t// Model required for summarization\n\t\tif (options.summarize && !this.model) {\n\t\t\tthrow new Error(\"No model available for summarization\");\n\t\t}\n\n\t\tconst targetEntry = this.session.getEntry(targetId);\n\t\tif (!targetEntry) {\n\t\t\tthrow new Error(`Entry ${targetId} not found`);\n\t\t}\n\n\t\t// Collect entries to summarize (from old leaf to common ancestor)\n\t\tconst { entries: entriesToSummarize, commonAncestorId } = collectEntriesForBranchSummary(\n\t\t\tthis.session,\n\t\t\toldLeafId,\n\t\t\ttargetId,\n\t\t);\n\n\t\t// Prepare event data - mutable so extensions can override\n\t\tconst navigationSettings: TreeNavigationSettings = {\n\t\t\tcustomInstructions: options.customInstructions,\n\t\t\treplaceInstructions: options.replaceInstructions,\n\t\t\tlabel: options.label,\n\t\t};\n\n\t\tconst preparation: TreePreparation = {\n\t\t\ttargetId,\n\t\t\toldLeafId,\n\t\t\tcommonAncestorId,\n\t\t\tentriesToSummarize,\n\t\t\tuserWantsSummary: options.summarize ?? false,\n\t\t\tcustomInstructions: navigationSettings.customInstructions,\n\t\t\treplaceInstructions: navigationSettings.replaceInstructions,\n\t\t\tlabel: navigationSettings.label,\n\t\t};\n\n\t\t// Set up abort controller for summarization\n\t\tthis._branchSummaryAbortController = new AbortController();\n\n\t\ttry {\n\t\t\tconst extensionPreparation = await this._prepareTreeNavigation(\n\t\t\t\tpreparation,\n\t\t\t\tnavigationSettings,\n\t\t\t\toptions.summarize ?? false,\n\t\t\t\tthis._branchSummaryAbortController.signal,\n\t\t\t);\n\t\t\tif (extensionPreparation.cancelled) return { cancelled: true };\n\t\t\tconst summary = await this._resolveTreeSummary(\n\t\t\t\tentriesToSummarize,\n\t\t\t\toptions.summarize ?? false,\n\t\t\t\textensionPreparation,\n\t\t\t\tthis._branchSummaryAbortController.signal,\n\t\t\t);\n\t\t\tif (summary.cancelled) return { cancelled: true, aborted: summary.aborted };\n\t\t\tconst { newLeafId, editorText } = this._resolveTreeTarget(targetEntry, targetId);\n\t\t\tconst summaryEntry = this._switchTreeLeaf(targetId, newLeafId, summary, extensionPreparation.label);\n\n\t\t\t// Update agent state\n\t\t\tconst sessionContext = this.session.buildSessionContext();\n\t\t\tthis.agent.state.messages = sessionContext.messages;\n\n\t\t\t// Emit session_tree event\n\t\t\tawait this._extensionRunner.emit({\n\t\t\t\ttype: \"session_tree\",\n\t\t\t\tnewLeafId: this.session.getLeafId(),\n\t\t\t\toldLeafId,\n\t\t\t\tsummaryEntry,\n\t\t\t\tfromExtension: summary.summaryText ? summary.fromExtension : undefined,\n\t\t\t});\n\n\t\t\t// Emit to custom tools\n\n\t\t\treturn { editorText, cancelled: false, summaryEntry };\n\t\t} finally {\n\t\t\tthis._branchSummaryAbortController = undefined;\n\t\t}\n\t}\n\n\t/**\n\t * Get all user messages from session for fork selector.\n\t */\n\tgetUserMessagesForForking(): ForkableUserMessage[] {\n\t\tconst entries = this.session.getEntries();\n\t\tconst result: ForkableUserMessage[] = [];\n\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.type !== \"message\") continue;\n\t\t\tif (entry.message.role !== \"user\") continue;\n\n\t\t\tconst text = this._extractUserMessageText(entry.message);\n\t\t\tif (text) {\n\t\t\t\tresult.push({ entryId: entry.id, text });\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tprivate _extractUserMessageText(message: UserMessage): string {\n\t\tconst { content } = message;\n\t\tif (typeof content === \"string\") return content;\n\t\tif (Array.isArray(content)) {\n\t\t\treturn content\n\t\t\t\t.filter((c): c is TextContent => c.type === \"text\")\n\t\t\t\t.map((c) => c.text)\n\t\t\t\t.join(\"\");\n\t\t}\n\t\treturn \"\";\n\t}\n\n\t/**\n\t * Get session statistics.\n\t */\n\tgetSessionStats(): SessionStats {\n\t\tconst state = this.state;\n\t\tconst userMessages = state.messages.filter((m) => m.role === \"user\").length;\n\t\tconst assistantMessages = state.messages.filter((m) => m.role === \"assistant\").length;\n\t\tconst toolResults = state.messages.filter((m) => m.role === \"toolResult\").length;\n\n\t\tlet toolCalls = 0;\n\t\tfor (const message of state.messages) {\n\t\t\tif (message.role === \"assistant\") toolCalls += message.content.filter((c) => c.type === \"toolCall\").length;\n\t\t}\n\t\tconst usage = readUsageLedger(this.session.getEntries());\n\t\treturn {\n\t\t\tsessionFile: this.sessionFile,\n\t\t\tsessionId: this.sessionId,\n\t\t\tuserMessages,\n\t\t\tassistantMessages,\n\t\t\ttoolCalls,\n\t\t\ttoolResults,\n\t\t\ttotalMessages: state.messages.length,\n\t\t\ttokens: {\n\t\t\t\tinput: usage.input,\n\t\t\t\toutput: usage.output,\n\t\t\t\tcacheRead: usage.cacheRead,\n\t\t\t\tcacheWrite: usage.cacheWrite,\n\t\t\t\ttotal: usage.input + usage.output + usage.cacheRead + usage.cacheWrite,\n\t\t\t},\n\t\t\tcost: usage.catalogCost,\n\t\t\tcontextUsage: this.getContextUsage(),\n\t\t};\n\t}\n\n\tprivate _hasUsableAssistantUsageAfterBoundary(branchEntries: SessionEntry[], boundary: SessionEntry): boolean {\n\t\tconst compactionIndex = branchEntries.lastIndexOf(boundary);\n\t\tfor (let index = branchEntries.length - 1; index > compactionIndex; index--) {\n\t\t\tconst entry = branchEntries[index];\n\t\t\tif (entry.type !== \"message\" || entry.message.role !== \"assistant\") continue;\n\t\t\tconst assistant = entry.message;\n\t\t\tif (assistant.stopReason === \"aborted\" || assistant.stopReason === \"error\") continue;\n\t\t\treturn calculateContextTokens(assistant.usage) > 0;\n\t\t}\n\t\treturn false;\n\t}\n\n\tgetCompressionDetectionCounts(): CompressionDetectionCounts {\n\t\tconst { keep, compress } = readCompressionDetectionLedger(this.session.getEntries());\n\t\treturn { keep, compress };\n\t}\n\n\tgetCompressionDetectionCost(): number {\n\t\treturn readCompressionDetectionLedger(this.session.getEntries()).cost;\n\t}\n\n\tgetContextUsage(): ContextUsage | undefined {\n\t\tconst model = this.model;\n\t\tif (!model) return undefined;\n\n\t\tconst contextWindow = model.contextWindow ?? 0;\n\t\tif (contextWindow <= 0) return undefined;\n\n\t\t// After compaction, the last assistant usage reflects pre-compaction context size.\n\t\t// We can only trust usage from an assistant that responded after the latest compaction.\n\t\t// If no such assistant exists, context token count is unknown until the next LLM response.\n\t\tconst branchEntries = this.session.getBranch();\n\t\tconst latestCompaction = getLatestCompactionEntry(branchEntries);\n\t\tconst latestCompression = [...branchEntries]\n\t\t\t.reverse()\n\t\t\t.find(\n\t\t\t\t(entry) =>\n\t\t\t\t\tentry.type === \"custom_message\" &&\n\t\t\t\t\tentry.customType === \"compress_context\" &&\n\t\t\t\t\t!!entry.details &&\n\t\t\t\t\ttypeof entry.details === \"object\" &&\n\t\t\t\t\t\"replacementVersion\" in entry.details &&\n\t\t\t\t\tentry.details.replacementVersion === 1,\n\t\t\t);\n\t\tconst boundary =\n\t\t\tlatestCompression &&\n\t\t\t(!latestCompaction || branchEntries.indexOf(latestCompression) > branchEntries.indexOf(latestCompaction))\n\t\t\t\t? latestCompression\n\t\t\t\t: latestCompaction;\n\t\tif (boundary && !this._hasUsableAssistantUsageAfterBoundary(branchEntries, boundary)) {\n\t\t\treturn { tokens: null, contextWindow, percent: null };\n\t\t}\n\n\t\tconst estimate = estimateContextTokens(this.messages);\n\t\tconst percent = (estimate.tokens / contextWindow) * 100;\n\n\t\treturn {\n\t\t\ttokens: estimate.tokens,\n\t\t\tcontextWindow,\n\t\t\tpercent,\n\t\t};\n\t}\n\n\t/**\n\t * Export session to HTML.\n\t * @param outputPath Optional output path (defaults to session directory)\n\t * @returns Path to exported file\n\t */\n\tasync exportToHtml(outputPath?: string): Promise<string> {\n\t\tconst themeName = this.settingsManager.getTheme();\n\n\t\t// Create tool renderer if we have an extension runner (for custom tool HTML rendering)\n\t\tconst toolRenderer: ToolHtmlRenderer = createToolHtmlRenderer({\n\t\t\tgetToolDefinition: (name) => this.getToolDefinition(name),\n\t\t\ttheme,\n\t\t\tcwd: this.session.getCwd(),\n\t\t});\n\n\t\treturn await exportSessionToHtml(this.session, this.state, {\n\t\t\toutputPath,\n\t\t\tthemeName,\n\t\t\ttoolRenderer,\n\t\t});\n\t}\n\n\t/**\n\t * Export the current session branch to a JSONL file.\n\t * Writes the session header followed by all entries on the current branch path.\n\t * @param outputPath Target file path. If omitted, generates a timestamped file in cwd.\n\t * @returns The resolved output file path.\n\t */\n\texportToJsonl(outputPath?: string): string {\n\t\tconst filePath = resolvePath(\n\t\t\toutputPath ?? `session-${new Date().toISOString().replace(/[:.]/g, \"-\")}.jsonl`,\n\t\t\tprocess.cwd(),\n\t\t);\n\t\tconst dir = dirname(filePath);\n\t\tif (!existsSync(dir)) {\n\t\t\tmkdirSync(dir, { recursive: true });\n\t\t}\n\n\t\tconst header: SessionHeader = {\n\t\t\ttype: \"session\",\n\t\t\tversion: CURRENT_SESSION_VERSION,\n\t\t\tid: this.session.getSessionId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tcwd: this.session.getCwd(),\n\t\t};\n\n\t\tconst branchEntries = this.session.getBranch();\n\t\tconst lines = [JSON.stringify(header)];\n\n\t\t// Re-chain parentIds to form a linear sequence\n\t\tlet prevId: string | null = null;\n\t\tfor (const entry of branchEntries) {\n\t\t\tconst linear = { ...entry, parentId: prevId };\n\t\t\tlines.push(JSON.stringify(linear));\n\t\t\tprevId = entry.id;\n\t\t}\n\n\t\twriteFileSync(filePath, `${lines.join(\"\\n\")}\\n`);\n\t\treturn filePath;\n\t}\n\n\t// =========================================================================\n\t// Utilities\n\t// =========================================================================\n\n\t/**\n\t * Get text content of last assistant message.\n\t * Useful for /copy command.\n\t * @returns Text content, or undefined if no assistant message exists\n\t */\n\tgetLastAssistantText(): string | undefined {\n\t\tconst lastAssistant = this.messages\n\t\t\t.slice()\n\t\t\t.reverse()\n\t\t\t.find((m) => {\n\t\t\t\tif (m.role !== \"assistant\") return false;\n\t\t\t\tconst msg = m as AssistantMessage;\n\t\t\t\t// Skip aborted messages with no content\n\t\t\t\tif (msg.stopReason === \"aborted\" && msg.content.length === 0) return false;\n\t\t\t\treturn true;\n\t\t\t});\n\n\t\tif (!lastAssistant) return undefined;\n\n\t\tlet text = \"\";\n\t\tfor (const content of (lastAssistant as AssistantMessage).content) {\n\t\t\tif (content.type === \"text\") {\n\t\t\t\ttext += content.text;\n\t\t\t}\n\t\t}\n\n\t\treturn text.trim() || undefined;\n\t}\n\n\t// =========================================================================\n\t// Extension System\n\t// =========================================================================\n\n\tcreateReplacedSessionContext(): ReplacedSessionContext {\n\t\tconst context = Object.defineProperties(\n\t\t\t{},\n\t\t\tObject.getOwnPropertyDescriptors(this._extensionRunner.createCommandContext()),\n\t\t) as ReplacedSessionContext;\n\t\tcontext.sendMessage = (message, options) => this.sendCustomMessage(message, options);\n\t\tcontext.sendUserMessage = (content, options) => this.sendUserMessage(content, options);\n\t\treturn context;\n\t}\n\n\t/**\n\t * Check if extensions have handlers for a specific event type.\n\t */\n\thasExtensionHandlers(eventType: string): boolean {\n\t\treturn this._extensionRunner.hasHandlers(eventType);\n\t}\n\n\t/**\n\t * Get the extension runner (for setting UI context and error handlers).\n\t */\n\tget extensionRunner(): ExtensionRunner {\n\t\treturn this._extensionRunner;\n\t}\n}\n"]}
|