@caupulican/pi-adaptative 0.81.29 → 0.81.31

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.
Files changed (147) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/bundled-resources/skills/harness-self-adaptation/SKILL.md +327 -0
  3. package/dist/bundled-resources/skills/harness-self-adaptation/references/adaptation-layers.md +89 -0
  4. package/dist/core/agent-session.d.ts +11 -1
  5. package/dist/core/agent-session.d.ts.map +1 -1
  6. package/dist/core/agent-session.js +16 -14
  7. package/dist/core/agent-session.js.map +1 -1
  8. package/dist/core/autonomy/approval-gate.d.ts +1 -1
  9. package/dist/core/autonomy/approval-gate.d.ts.map +1 -1
  10. package/dist/core/autonomy/approval-gate.js +5 -1
  11. package/dist/core/autonomy/approval-gate.js.map +1 -1
  12. package/dist/core/autonomy/lane-tool-surface.d.ts +4 -0
  13. package/dist/core/autonomy/lane-tool-surface.d.ts.map +1 -1
  14. package/dist/core/autonomy/lane-tool-surface.js +31 -4
  15. package/dist/core/autonomy/lane-tool-surface.js.map +1 -1
  16. package/dist/core/autonomy/lane-tracker.d.ts +1 -0
  17. package/dist/core/autonomy/lane-tracker.d.ts.map +1 -1
  18. package/dist/core/autonomy/lane-tracker.js +11 -0
  19. package/dist/core/autonomy/lane-tracker.js.map +1 -1
  20. package/dist/core/autonomy/risk-assessment.d.ts.map +1 -1
  21. package/dist/core/autonomy/risk-assessment.js +1 -1
  22. package/dist/core/autonomy/risk-assessment.js.map +1 -1
  23. package/dist/core/background-lane-controller.d.ts +12 -0
  24. package/dist/core/background-lane-controller.d.ts.map +1 -1
  25. package/dist/core/background-lane-controller.js +114 -19
  26. package/dist/core/background-lane-controller.js.map +1 -1
  27. package/dist/core/context/context-artifacts.d.ts.map +1 -1
  28. package/dist/core/context/context-artifacts.js +7 -6
  29. package/dist/core/context/context-artifacts.js.map +1 -1
  30. package/dist/core/context/context-store-retention.d.ts +12 -0
  31. package/dist/core/context/context-store-retention.d.ts.map +1 -0
  32. package/dist/core/context/context-store-retention.js +161 -0
  33. package/dist/core/context/context-store-retention.js.map +1 -0
  34. package/dist/core/context-gc.d.ts.map +1 -1
  35. package/dist/core/context-gc.js +19 -4
  36. package/dist/core/context-gc.js.map +1 -1
  37. package/dist/core/context-pipeline.d.ts +13 -12
  38. package/dist/core/context-pipeline.d.ts.map +1 -1
  39. package/dist/core/context-pipeline.js +128 -33
  40. package/dist/core/context-pipeline.js.map +1 -1
  41. package/dist/core/cost/cost-summary.d.ts +7 -1
  42. package/dist/core/cost/cost-summary.d.ts.map +1 -1
  43. package/dist/core/cost/cost-summary.js +28 -17
  44. package/dist/core/cost/cost-summary.js.map +1 -1
  45. package/dist/core/cost/daily-usage.d.ts +7 -3
  46. package/dist/core/cost/daily-usage.d.ts.map +1 -1
  47. package/dist/core/cost/daily-usage.js +10 -5
  48. package/dist/core/cost/daily-usage.js.map +1 -1
  49. package/dist/core/delegation/worker-runner.d.ts.map +1 -1
  50. package/dist/core/delegation/worker-runner.js +25 -9
  51. package/dist/core/delegation/worker-runner.js.map +1 -1
  52. package/dist/core/export-html/template.js +2 -1
  53. package/dist/core/extensions/loader.d.ts.map +1 -1
  54. package/dist/core/extensions/loader.js +52 -2
  55. package/dist/core/extensions/loader.js.map +1 -1
  56. package/dist/core/extensions/runner.d.ts.map +1 -1
  57. package/dist/core/extensions/runner.js +21 -19
  58. package/dist/core/extensions/runner.js.map +1 -1
  59. package/dist/core/extensions/types.d.ts +5 -0
  60. package/dist/core/extensions/types.d.ts.map +1 -1
  61. package/dist/core/extensions/types.js.map +1 -1
  62. package/dist/core/failure-corpus.d.ts.map +1 -1
  63. package/dist/core/failure-corpus.js +36 -13
  64. package/dist/core/failure-corpus.js.map +1 -1
  65. package/dist/core/local-runtime-controller.d.ts.map +1 -1
  66. package/dist/core/local-runtime-controller.js +20 -2
  67. package/dist/core/local-runtime-controller.js.map +1 -1
  68. package/dist/core/memory-controller.d.ts +2 -0
  69. package/dist/core/memory-controller.d.ts.map +1 -1
  70. package/dist/core/memory-controller.js +18 -0
  71. package/dist/core/memory-controller.js.map +1 -1
  72. package/dist/core/models/local-runtime.d.ts.map +1 -1
  73. package/dist/core/models/local-runtime.js +15 -10
  74. package/dist/core/models/local-runtime.js.map +1 -1
  75. package/dist/core/reflection-controller.d.ts +3 -1
  76. package/dist/core/reflection-controller.d.ts.map +1 -1
  77. package/dist/core/reflection-controller.js +8 -5
  78. package/dist/core/reflection-controller.js.map +1 -1
  79. package/dist/core/runtime-builder.d.ts +2 -0
  80. package/dist/core/runtime-builder.d.ts.map +1 -1
  81. package/dist/core/runtime-builder.js.map +1 -1
  82. package/dist/core/sdk.d.ts.map +1 -1
  83. package/dist/core/sdk.js +1 -1
  84. package/dist/core/sdk.js.map +1 -1
  85. package/dist/core/session-analytics.d.ts +13 -7
  86. package/dist/core/session-analytics.d.ts.map +1 -1
  87. package/dist/core/session-analytics.js +155 -112
  88. package/dist/core/session-analytics.js.map +1 -1
  89. package/dist/core/tool-recovery-log-records.d.ts.map +1 -1
  90. package/dist/core/tool-recovery-log-records.js +54 -9
  91. package/dist/core/tool-recovery-log-records.js.map +1 -1
  92. package/dist/core/tool-recovery-logger.d.ts +2 -0
  93. package/dist/core/tool-recovery-logger.d.ts.map +1 -1
  94. package/dist/core/tool-recovery-logger.js +32 -13
  95. package/dist/core/tool-recovery-logger.js.map +1 -1
  96. package/dist/core/tool-recovery-stats.d.ts +15 -0
  97. package/dist/core/tool-recovery-stats.d.ts.map +1 -0
  98. package/dist/core/tool-recovery-stats.js +206 -0
  99. package/dist/core/tool-recovery-stats.js.map +1 -0
  100. package/dist/core/tools/delegate-status.d.ts.map +1 -1
  101. package/dist/core/tools/delegate-status.js +15 -6
  102. package/dist/core/tools/delegate-status.js.map +1 -1
  103. package/dist/core/tools/delegate.d.ts +3 -0
  104. package/dist/core/tools/delegate.d.ts.map +1 -1
  105. package/dist/core/tools/delegate.js +7 -2
  106. package/dist/core/tools/delegate.js.map +1 -1
  107. package/dist/main.d.ts.map +1 -1
  108. package/dist/main.js +1 -1
  109. package/dist/main.js.map +1 -1
  110. package/dist/modes/interactive/components/bash-execution.d.ts +2 -1
  111. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  112. package/dist/modes/interactive/components/bash-execution.js +12 -1
  113. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  114. package/dist/modes/interactive/components/footer.d.ts +1 -0
  115. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  116. package/dist/modes/interactive/components/footer.js +19 -9
  117. package/dist/modes/interactive/components/footer.js.map +1 -1
  118. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  119. package/dist/modes/interactive/components/tool-execution.js +12 -2
  120. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  121. package/dist/modes/interactive/components/tool-panel-registry.d.ts +1 -0
  122. package/dist/modes/interactive/components/tool-panel-registry.d.ts.map +1 -1
  123. package/dist/modes/interactive/components/tool-panel-registry.js +18 -2
  124. package/dist/modes/interactive/components/tool-panel-registry.js.map +1 -1
  125. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  126. package/dist/modes/interactive/interactive-mode.js +9 -3
  127. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  128. package/dist/modes/rpc/jsonl.d.ts.map +1 -1
  129. package/dist/modes/rpc/jsonl.js +9 -28
  130. package/dist/modes/rpc/jsonl.js.map +1 -1
  131. package/dist/utils/streaming-lines.d.ts +20 -0
  132. package/dist/utils/streaming-lines.d.ts.map +1 -0
  133. package/dist/utils/streaming-lines.js +79 -0
  134. package/dist/utils/streaming-lines.js.map +1 -0
  135. package/docs/extensions.md +2 -0
  136. package/docs/index.md +1 -1
  137. package/docs/self-adaptation.md +7 -0
  138. package/docs/settings.md +5 -1
  139. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  140. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  141. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  142. package/examples/extensions/sandbox/package-lock.json +2 -2
  143. package/examples/extensions/sandbox/package.json +1 -1
  144. package/examples/extensions/with-deps/package-lock.json +2 -2
  145. package/examples/extensions/with-deps/package.json +1 -1
  146. package/npm-shrinkwrap.json +12 -12
  147. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"interactive-mode.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/interactive-mode.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Interactive mode for the coding agent.\n * Handles TUI rendering and user interaction, delegating business logic to AgentSession.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { AgentMessage, ToolCallRepairInfo } from \"@caupulican/pi-agent-core\";\nimport { createCompactionSummaryMessage } from \"@caupulican/pi-agent-core\";\nimport type { SessionContext, SessionManager, TruncationResult } from \"@caupulican/pi-agent-core/node\";\nimport type { AssistantMessage, ImageContent, Message, Model, ToolCall } from \"@caupulican/pi-ai\";\nimport type { AutocompleteProvider, EditorComponent, Keybinding, MarkdownTheme, SelectItem } from \"@caupulican/pi-tui\";\nimport {\n\ttype Component,\n\tContainer,\n\tLoader,\n\ttype LoaderIndicatorOptions,\n\tProcessTerminal,\n\tSpacer,\n\tsetKeybindings,\n\tText,\n\tTruncatedText,\n\tTUI,\n} from \"@caupulican/pi-tui\";\nimport { APP_NAME, APP_TITLE, VERSION } from \"../../config.ts\";\nimport { type AgentSession, type AgentSessionEvent, parseSkillBlock } from \"../../core/agent-session.ts\";\nimport type { AgentSessionRuntime } from \"../../core/agent-session-runtime.ts\";\nimport type {\n\tAutocompleteProviderFactory,\n\tExtensionCommandContext,\n\tExtensionRunner,\n} from \"../../core/extensions/index.ts\";\nimport { FooterDataProvider } from \"../../core/footer-data-provider.ts\";\nimport {\n\tDEFAULT_GOAL_CONTINUE_MAX_STALL_TURNS,\n\tDEFAULT_GOAL_CONTINUE_MAX_TURNS,\n\tDEFAULT_GOAL_CONTINUE_MAX_WALL_CLOCK_MINUTES,\n\tMAX_GOAL_CONTINUE_MAX_STALL_TURNS,\n\tMAX_GOAL_CONTINUE_MAX_TURNS,\n\tMAX_GOAL_CONTINUE_MAX_WALL_CLOCK_MINUTES,\n} from \"../../core/goals/goal-continuation-defaults.ts\";\nimport { configureHttpDispatcher } from \"../../core/http-dispatcher.ts\";\nimport { type AppKeybinding, KeybindingsManager } from \"../../core/keybindings.ts\";\nimport type { OllamaRuntime, TransformersRuntime } from \"../../core/models/local-runtime.ts\";\nimport type { ResourceDiagnostic } from \"../../core/resource-loader.ts\";\nimport { formatMissingSessionCwdPrompt, type MissingSessionCwdError } from \"../../core/session-cwd.ts\";\nimport type {\n\tAutoLearnSettings,\n\tAutonomyMode,\n\tSelfModificationSettings,\n\tSettingsScope,\n} from \"../../core/settings-manager.ts\";\nimport { BUILTIN_SLASH_COMMANDS } from \"../../core/slash-commands.ts\";\nimport type { SourceInfo } from \"../../core/source-info.ts\";\nimport { getCwdRelativePath, resolvePath } from \"../../utils/paths.ts\";\nimport { ensureTool } from \"../../utils/tools-manager.ts\";\nimport { checkForNewPiVersion, type LatestPiRelease } from \"../../utils/version-check.ts\";\nimport { AuthDialogsController } from \"./auth-dialogs-controller.ts\";\nimport { AutoLearnController, type AutoLearnState } from \"./auto-learn-controller.ts\";\nimport * as autocompleteProvider from \"./autocomplete-provider.ts\";\nimport * as autonomyCommands from \"./autonomy-commands.ts\";\nimport * as clipboardInput from \"./clipboard-input.ts\";\nimport * as compactionQueue from \"./compaction-queue.ts\";\nimport { AssistantMessageComponent } from \"./components/assistant-message.ts\";\nimport { BashExecutionComponent } from \"./components/bash-execution.ts\";\nimport { BranchSummaryMessageComponent } from \"./components/branch-summary-message.ts\";\nimport { CompactionSummaryMessageComponent } from \"./components/compaction-summary-message.ts\";\nimport { CountdownTimer } from \"./components/countdown-timer.ts\";\nimport { CustomEditor } from \"./components/custom-editor.ts\";\nimport { CustomMessageComponent } from \"./components/custom-message.ts\";\nimport { DynamicBorder } from \"./components/dynamic-border.ts\";\nimport type { FitnessRole } from \"./components/fitness-role-selector.ts\";\nimport { FooterComponent } from \"./components/footer.ts\";\nimport { formatKeyText, keyDisplayText, keyHint, keyText, rawKeyHint } from \"./components/keybinding-hints.ts\";\nimport { SkillInvocationMessageComponent } from \"./components/skill-invocation-message.ts\";\nimport { ToolExecutionComponent } from \"./components/tool-execution.ts\";\nimport { ToolGroupComponent } from \"./components/tool-group.ts\";\nimport {\n\tgetToolPanelActionKey,\n\tgetToolPanelResultActionKeys,\n\tshouldReuseToolPanelInPlace,\n\tToolPanelRegistry,\n} from \"./components/tool-panel-registry.ts\";\nimport { UserMessageComponent } from \"./components/user-message.ts\";\nimport * as configBackup from \"./config-backup.ts\";\nimport { EditorOverlayHost } from \"./editor-overlay-host.ts\";\nimport { ExtensionUiHost } from \"./extension-ui-host.ts\";\nimport { openEditorForPath, openExternalEditor } from \"./external-editor.ts\";\nimport * as historyReloadMath from \"./history-reload-math.ts\";\nimport * as keyHandlers from \"./key-handlers.ts\";\nimport * as localModelCommands from \"./local-model-commands.ts\";\nimport { ProfileMenuController } from \"./profile-menu-controller.ts\";\nimport * as reportCommands from \"./report-commands.ts\";\nimport * as resourceDisplay from \"./resource-display.ts\";\nimport * as resourceShellCommands from \"./resource-shell-commands.ts\";\nimport * as sessionFlows from \"./session-flow-commands.ts\";\nimport * as sessionIoCommands from \"./session-io-commands.ts\";\nimport * as settingsSelectorFlow from \"./settings-selector-flow.ts\";\nimport * as signalLifecycle from \"./signal-lifecycle.ts\";\nimport * as startupChecks from \"./startup-checks.ts\";\nimport {\n\tgetEditorTheme,\n\tgetMarkdownTheme,\n\tinitTheme,\n\tonThemeChange,\n\tsetRegisteredThemes,\n\tsetTheme,\n\tstopThemeWatcher,\n\ttype ThemeColor,\n\ttheme,\n} from \"./theme/theme.ts\";\n\nconst TUI_HISTORY_RELOAD_CHUNK_SIZE = 20;\nconst TUI_LIVE_HISTORY_MAX_COMPONENTS = 260;\nconst TUI_LIVE_HISTORY_TRIM_TO_COMPONENTS = 220;\nconst STREAMING_UI_UPDATE_INTERVAL_MS = 80;\n\n/** Interface for components that can be expanded/collapsed */\ninterface Expandable {\n\tsetExpanded(expanded: boolean): void;\n}\n\nfunction isExpandable(obj: unknown): obj is Expandable {\n\treturn typeof obj === \"object\" && obj !== null && \"setExpanded\" in obj && typeof obj.setExpanded === \"function\";\n}\n\nclass ExpandableText extends Text implements Expandable {\n\tprivate readonly getCollapsedText: () => string;\n\tprivate readonly getExpandedText: () => string;\n\n\tconstructor(\n\t\tgetCollapsedText: () => string,\n\t\tgetExpandedText: () => string,\n\t\texpanded = false,\n\t\tpaddingX = 0,\n\t\tpaddingY = 0,\n\t) {\n\t\tsuper(expanded ? getExpandedText() : getCollapsedText(), paddingX, paddingY);\n\t\tthis.getCollapsedText = getCollapsedText;\n\t\tthis.getExpandedText = getExpandedText;\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tthis.setText(expanded ? this.getExpandedText() : this.getCollapsedText());\n\t}\n}\n\ntype UserInputSubmission = {\n\ttext: string;\n\timages?: ImageContent[];\n};\n\ntype PendingClipboardImage = {\n\tlabel: string;\n\tcontent: ImageContent;\n};\n\ntype CompactionQueuedMessage = {\n\ttext: string;\n\tmode: \"steer\" | \"followUp\";\n\timages?: ImageContent[];\n};\n\nconst ANTHROPIC_SUBSCRIPTION_AUTH_WARNING =\n\t\"Anthropic subscription auth is active. Third-party harness usage draws from extra usage and is billed per token, not your Claude plan limits. Manage extra usage at https://claude.ai/settings/usage.\";\n\nfunction isAnthropicSubscriptionAuthKey(apiKey: string | undefined): boolean {\n\treturn typeof apiKey === \"string\" && apiKey.startsWith(\"sk-ant-oat\");\n}\n\nfunction quoteIfNeeded(value: string): string {\n\tif (value.length > 0 && !/[^a-zA-Z0-9_\\-./~:@]/.test(value)) {\n\t\treturn value;\n\t}\n\treturn `'${value.replace(/'/g, `'\\\\''`)}'`;\n}\n\nexport function formatResumeCommand(sessionManager: SessionManager): string | undefined {\n\tif (!process.stdout.isTTY) return undefined;\n\tif (!sessionManager.isPersisted()) return undefined;\n\n\tconst sessionFile = sessionManager.getSessionFile();\n\tif (!sessionFile || !fs.existsSync(sessionFile)) return undefined;\n\n\tconst args = [APP_NAME];\n\tif (!sessionManager.usesDefaultSessionDir()) {\n\t\targs.push(\"--session-dir\", quoteIfNeeded(sessionManager.getSessionDir()));\n\t}\n\targs.push(\"--session\", sessionManager.getSessionId());\n\treturn args.join(\" \");\n}\n\n/**\n * Options for InteractiveMode initialization.\n */\nexport interface InteractiveModeOptions {\n\t/** Providers that were migrated to auth.json (shows warning) */\n\tmigratedProviders?: string[];\n\t/** Warning message if session model couldn't be restored */\n\tmodelFallbackMessage?: string;\n\t/** Initial message to send on startup (can include @file content) */\n\tinitialMessage?: string;\n\t/** Images to attach to the initial message */\n\tinitialImages?: ImageContent[];\n\t/** Additional messages to send after the initial message */\n\tinitialMessages?: string[];\n\t/** Force verbose startup (overrides quietStartup setting) */\n\tverbose?: boolean;\n}\n\nexport class InteractiveMode {\n\tprivate runtimeHost: AgentSessionRuntime;\n\tprivate ui: TUI;\n\tprivate chatContainer: Container;\n\tprivate pendingMessagesContainer: Container;\n\tprivate statusContainer: Container;\n\tprivate defaultEditor: CustomEditor;\n\tprivate editor: EditorComponent;\n\tprivate autocompleteProvider: AutocompleteProvider | undefined;\n\tprivate autocompleteProviderWrappers: AutocompleteProviderFactory[] = [];\n\tprivate fdPath: string | undefined;\n\tprivate editorContainer: Container;\n\tprivate overlayHost: EditorOverlayHost;\n\tprivate footer: FooterComponent;\n\tprivate footerDataProvider: FooterDataProvider;\n\tprivate autoLearnController: AutoLearnController;\n\tprivate profileMenu: ProfileMenuController;\n\tprivate authDialogs: AuthDialogsController;\n\tprivate extensionUiHost: ExtensionUiHost;\n\t// Stored so the same manager can be injected into custom editors, selectors, and extension UI.\n\tprivate keybindings: KeybindingsManager;\n\tprivate version: string;\n\tprivate isInitialized = false;\n\tprivate onInputCallback?: (submission: UserInputSubmission) => void;\n\tprivate pendingUserInputs: UserInputSubmission[] = [];\n\tprivate pendingClipboardImages: PendingClipboardImage[] = [];\n\tprivate clipboardImageCounter = 0;\n\tprivate loadingAnimation: Loader | undefined = undefined;\n\tprivate workingMessage: string | undefined = undefined;\n\tprivate workingVisible = true;\n\tprivate workingIndicatorOptions: LoaderIndicatorOptions | undefined = undefined;\n\tprivate readonly defaultWorkingMessage = \"Working...\";\n\tprivate readonly defaultHiddenThinkingLabel = \"Thinking...\";\n\tprivate hiddenThinkingLabel = this.defaultHiddenThinkingLabel;\n\n\tprivate lastSigintTime = 0;\n\tprivate lastEscapeTime = 0;\n\tprivate changelogMarkdown: string | undefined = undefined;\n\tprivate startupNoticesShown = false;\n\tprivate anthropicSubscriptionWarningShown = false;\n\n\t// Status line tracking (for mutating immediately-sequential status updates)\n\tprivate lastStatusSpacer: Spacer | undefined = undefined;\n\tprivate lastStatusText: Text | undefined = undefined;\n\n\t// Live TUI history cap. Full session history remains in SessionManager/model state.\n\tprivate liveHistoryHiddenNotice: Text | undefined = undefined;\n\tprivate liveHistoryHiddenComponents = 0;\n\tprivate tuiHistoryLoaded = false;\n\tprivate tuiHistoryLoadInProgress = false;\n\n\t// Streaming message tracking\n\tprivate streamingComponent: AssistantMessageComponent | undefined = undefined;\n\tprivate streamingMessage: AssistantMessage | undefined = undefined;\n\tprivate streamingUiUpdateTimer: ReturnType<typeof setTimeout> | undefined = undefined;\n\tprivate lastStreamingUiUpdateAt = 0;\n\n\t// Tool execution tracking and session-scoped reusable panels\n\tprivate toolPanels = new ToolPanelRegistry();\n\n\t// Tool output expansion state\n\tprivate toolOutputExpanded = false;\n\n\t// Thinking block visibility state\n\tprivate hideThinkingBlock = false;\n\n\t// Skill commands: command name -> skill file path\n\tprivate skillCommands = new Map<string, string>();\n\n\t// Agent subscription unsubscribe function\n\tprivate unsubscribe?: () => void;\n\tprivate unsubscribeExtensionsChanged?: () => void;\n\tprivate signalCleanupHandlers: Array<() => void> = [];\n\n\t// Track if editor is in bash mode (text starts with !)\n\tprivate isBashMode = false;\n\n\t// Track current bash execution component\n\tprivate bashComponent: BashExecutionComponent | undefined = undefined;\n\n\t// Track pending bash components (shown in pending area, moved to chat on submit)\n\tprivate pendingBashComponents: BashExecutionComponent[] = [];\n\n\t// Auto-compaction state\n\tprivate autoCompactionLoader: Loader | undefined = undefined;\n\tprivate autoCompactionEscapeHandler?: () => void;\n\n\t// Auto-retry state\n\tprivate retryLoader: Loader | undefined = undefined;\n\tprivate retryCountdown: CountdownTimer | undefined = undefined;\n\tprivate retryEscapeHandler?: () => void;\n\n\t// Messages queued while compaction is running\n\tprivate compactionQueuedMessages: CompactionQueuedMessage[] = [];\n\n\t// Shutdown state\n\tprivate shutdownRequested = false;\n\n\t// Extension widget containers (hold components rendered above/below the editor)\n\tprivate widgetContainerAbove!: Container;\n\tprivate widgetContainerBelow!: Container;\n\n\t// Header container that holds the built-in or custom header\n\tprivate headerContainer: Container;\n\n\t// Built-in header (logo + keybinding hints + changelog)\n\tprivate builtInHeader: Component | undefined = undefined;\n\n\tprivate options: InteractiveModeOptions;\n\n\t// Convenience accessors\n\tprivate get session(): AgentSession {\n\t\treturn this.runtimeHost.session;\n\t}\n\tprivate get agent() {\n\t\treturn this.session.agent;\n\t}\n\tprivate get sessionManager() {\n\t\treturn this.session.sessionManager;\n\t}\n\tprivate get settingsManager() {\n\t\treturn this.session.settingsManager;\n\t}\n\n\tconstructor(runtimeHost: AgentSessionRuntime, options: InteractiveModeOptions = {}) {\n\t\tthis.runtimeHost = runtimeHost;\n\t\tthis.options = options;\n\t\tthis.runtimeHost.setBeforeSessionInvalidate(() => {\n\t\t\tthis.extensionUiHost.resetExtensionUI();\n\t\t});\n\t\tthis.runtimeHost.setRebindSession(async () => {\n\t\t\tawait this.rebindCurrentSession();\n\t\t});\n\t\tthis.version = VERSION;\n\t\tthis.ui = new TUI(new ProcessTerminal(), this.settingsManager.getShowHardwareCursor());\n\t\tthis.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());\n\t\tthis.headerContainer = new Container();\n\t\tthis.chatContainer = new Container();\n\t\tthis.pendingMessagesContainer = new Container();\n\t\tthis.statusContainer = new Container();\n\t\tthis.widgetContainerAbove = new Container();\n\t\tthis.widgetContainerBelow = new Container();\n\t\tthis.keybindings = KeybindingsManager.create();\n\t\tsetKeybindings(this.keybindings);\n\t\tconst editorPaddingX = this.settingsManager.getEditorPaddingX();\n\t\tconst autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();\n\t\tthis.defaultEditor = new CustomEditor(this.ui, getEditorTheme(), this.keybindings, {\n\t\t\tpaddingX: editorPaddingX,\n\t\t\tautocompleteMaxVisible,\n\t\t});\n\t\tthis.editor = this.defaultEditor;\n\t\tthis.editorContainer = new Container();\n\t\tthis.editorContainer.addChild(this.editor as Component);\n\t\tthis.overlayHost = new EditorOverlayHost(this.editorContainer, this.ui);\n\t\tthis.footerDataProvider = new FooterDataProvider(this.sessionManager.getCwd());\n\t\tthis.footer = new FooterComponent(this.session, this.footerDataProvider);\n\t\tthis.autoLearnController = new AutoLearnController({\n\t\t\tgetSession: () => this.runtimeHost.session,\n\t\t\tresolveSelfModificationSource: (settings) => this.resolveSelfModificationSource(settings),\n\t\t\tui: {\n\t\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\t\tfooterDataProvider: this.footerDataProvider,\n\t\t\t\tinvalidateFooter: () => this.footer.invalidate(),\n\t\t\t\trequestRender: () => this.ui.requestRender(),\n\t\t\t},\n\t\t});\n\t\tthis.profileMenu = new ProfileMenuController({\n\t\t\tgetSession: () => this.runtimeHost.session,\n\t\t\tui: {\n\t\t\t\tshowSelector: (create) => this.showSelector(create),\n\t\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\t\tshowError: (message) => this.showError(message),\n\t\t\t\tshowWarning: (message) => this.showWarning(message),\n\t\t\t\trequestRender: () => this.ui.requestRender(),\n\t\t\t\ttui: this.ui,\n\t\t\t\tfooterDataProvider: this.footerDataProvider,\n\t\t\t\tinvalidateFooter: () => this.footer.invalidate(),\n\t\t\t\tupdateEditorBorderColor: () => this.updateEditorBorderColor(),\n\t\t\t\topenEditorForPath: (filePath) => this.openEditorForPath(filePath),\n\t\t\t\thandleReloadCommand: () => this.handleReloadCommandWithResult(),\n\t\t\t\tmaybeWarnAboutAnthropicSubscriptionAuth: (model) =>\n\t\t\t\t\tvoid this.maybeWarnAboutAnthropicSubscriptionAuth(model),\n\t\t\t\tcheckDaxnutsEasterEgg: (model) => this.checkDaxnutsEasterEgg(model),\n\t\t\t\tshowSettingsSelector: () => this.showSettingsSelector(),\n\t\t\t\tgetAutoLearnModelOptions: () => this.getAutoLearnModelOptions(),\n\t\t\t},\n\t\t});\n\t\tthis.authDialogs = new AuthDialogsController({\n\t\t\tgetSession: () => this.runtimeHost.session,\n\t\t\tui: {\n\t\t\t\tshowSelector: (create) => this.showSelector(create),\n\t\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\t\tshowError: (message) => this.showError(message),\n\t\t\t\trequestRender: () => this.ui.requestRender(),\n\t\t\t\ttui: this.ui,\n\t\t\t\toverlayHost: this.overlayHost,\n\t\t\t\tgetEditor: () => this.editor,\n\t\t\t\tupdateAvailableProviderCount: () => this.updateAvailableProviderCount(),\n\t\t\t\tinvalidateFooter: () => this.footer.invalidate(),\n\t\t\t\tupdateEditorBorderColor: () => this.updateEditorBorderColor(),\n\t\t\t\tmaybeWarnAboutAnthropicSubscriptionAuth: (model) =>\n\t\t\t\t\tvoid this.maybeWarnAboutAnthropicSubscriptionAuth(model),\n\t\t\t\tcheckDaxnutsEasterEgg: (model) => this.checkDaxnutsEasterEgg(model),\n\t\t\t},\n\t\t});\n\t\tthis.extensionUiHost = new ExtensionUiHost({\n\t\t\tgetSession: () => this.runtimeHost.session,\n\t\t\tui: {\n\t\t\t\ttui: this.ui,\n\t\t\t\toverlayHost: this.overlayHost,\n\t\t\t\tkeybindings: this.keybindings,\n\t\t\t\tfooter: this.footer,\n\t\t\t\tfooterDataProvider: this.footerDataProvider,\n\t\t\t\theaderContainer: this.headerContainer,\n\t\t\t\tchatContainer: this.chatContainer,\n\t\t\t\teditorContainer: this.editorContainer,\n\t\t\t\tdefaultEditor: this.defaultEditor,\n\t\t\t\twidgetContainerAbove: this.widgetContainerAbove,\n\t\t\t\twidgetContainerBelow: this.widgetContainerBelow,\n\t\t\t\tgetEditor: () => this.editor,\n\t\t\t\tsetEditor: (editor) => {\n\t\t\t\t\tconst previousEditor = this.editor;\n\t\t\t\t\tthis.editor = editor;\n\t\t\t\t\tif (previousEditor !== editor && previousEditor !== this.defaultEditor) {\n\t\t\t\t\t\tconst disposableEditor = previousEditor as typeof previousEditor & { dispose?: () => void };\n\t\t\t\t\t\tdisposableEditor.dispose?.();\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tgetBuiltInHeader: () => this.builtInHeader,\n\t\t\t\tgetAutocompleteProvider: () => this.autocompleteProvider,\n\t\t\t\tgetToolsExpanded: () => this.toolOutputExpanded,\n\t\t\t\tpushAutocompleteProviderWrapper: (factory) => {\n\t\t\t\t\tthis.autocompleteProviderWrappers.push(factory);\n\t\t\t\t},\n\t\t\t\tresetAutocompleteProviderWrappers: () => {\n\t\t\t\t\tthis.autocompleteProviderWrappers = [];\n\t\t\t\t},\n\t\t\t\tsetupAutocompleteProvider: () => this.setupAutocompleteProvider(),\n\t\t\t\tsetWorkingVisible: (visible) => this.setWorkingVisible(visible),\n\t\t\t\tsetWorkingIndicator: (options) => this.setWorkingIndicator(options),\n\t\t\t\tsetHiddenThinkingLabel: (label) => this.setHiddenThinkingLabel(label),\n\t\t\t\tsetWorkingMessage: (message) => this.setWorkingMessage(message),\n\t\t\t\tresetWorkingIndicators: () => this.resetWorkingIndicators(),\n\t\t\t\tsetToolsExpanded: (expanded) => this.setToolsExpanded(expanded),\n\t\t\t\ttoggleToolsExpanded: () => this.toggleToolOutputExpansion(),\n\t\t\t\tupdateTerminalTitle: () => this.updateTerminalTitle(),\n\t\t\t\tmarkShutdownRequested: () => {\n\t\t\t\t\tthis.shutdownRequested = true;\n\t\t\t\t},\n\t\t\t\tabort: () => {\n\t\t\t\t\tthis.restoreQueuedMessagesToEditor({ abort: true });\n\t\t\t\t},\n\t\t\t\treload: () => this.handleReloadCommand(),\n\t\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\t\tshowWarning: (message) => this.showWarning(message),\n\t\t\t\tshowError: (message) => this.showError(message),\n\t\t\t},\n\t\t});\n\t\tthis.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);\n\n\t\t// Load hide thinking block setting\n\t\tthis.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();\n\n\t\t// Register themes from resource loader and initialize\n\t\tsetRegisteredThemes(this.session.resourceLoader.getThemes().themes);\n\t\tinitTheme(this.settingsManager.getTheme(), true);\n\t}\n\n\tprivate getBuiltInCommandConflictDiagnostics(extensionRunner: ExtensionRunner): ResourceDiagnostic[] {\n\t\tconst builtinNames = new Set(BUILTIN_SLASH_COMMANDS.map((command) => command.name));\n\t\treturn extensionRunner\n\t\t\t.getRegisteredCommands()\n\t\t\t.filter((command) => builtinNames.has(command.name))\n\t\t\t.map((command) => ({\n\t\t\t\ttype: \"warning\" as const,\n\t\t\t\tmessage:\n\t\t\t\t\tcommand.invocationName === command.name\n\t\t\t\t\t\t? `Extension command '/${command.name}' conflicts with built-in interactive command. Skipping in autocomplete.`\n\t\t\t\t\t\t: `Extension command '/${command.name}' conflicts with built-in interactive command. Available as '/${command.invocationName}'.`,\n\t\t\t\tpath: command.sourceInfo.path,\n\t\t\t}));\n\t}\n\n\t// Thin delegate retained so setupAutocompleteProvider's base-provider seam\n\t// (stubbed via prototype in interactive-mode-status.test.ts) keeps working.\n\tprivate createBaseAutocompleteProvider(): AutocompleteProvider {\n\t\treturn autocompleteProvider.createBaseAutocompleteProvider({\n\t\t\tsession: this.session,\n\t\t\tsettingsManager: this.settingsManager,\n\t\t\tsessionManager: this.sessionManager,\n\t\t\tfdPath: this.fdPath,\n\t\t\tskillCommands: this.skillCommands,\n\t\t});\n\t}\n\n\tprivate setupAutocompleteProvider(): void {\n\t\tlet provider = this.createBaseAutocompleteProvider();\n\t\tfor (const wrapProvider of this.autocompleteProviderWrappers) {\n\t\t\tprovider = wrapProvider(provider);\n\t\t}\n\n\t\tthis.autocompleteProvider = provider;\n\t\tthis.defaultEditor.setAutocompleteProvider(provider);\n\t\tif (this.editor !== this.defaultEditor) {\n\t\t\tthis.editor.setAutocompleteProvider?.(provider);\n\t\t}\n\t}\n\n\tprivate startupChecksHost(): startupChecks.StartupChecksHost {\n\t\tconst self = this;\n\t\treturn {\n\t\t\tsessionManager: this.sessionManager,\n\t\t\tsettingsManager: this.settingsManager,\n\t\t\tsession: this.session,\n\t\t\tchatContainer: this.chatContainer,\n\t\t\tui: this.ui,\n\t\t\tversion: this.version,\n\t\t\tget changelogMarkdown() {\n\t\t\t\treturn self.changelogMarkdown;\n\t\t\t},\n\t\t\tget startupNoticesShown() {\n\t\t\t\treturn self.startupNoticesShown;\n\t\t\t},\n\t\t\tset startupNoticesShown(value) {\n\t\t\t\tself.startupNoticesShown = value;\n\t\t\t},\n\t\t\tgetMarkdownThemeWithSettings: () => this.getMarkdownThemeWithSettings(),\n\t\t};\n\t}\n\n\tprivate showStartupNoticesIfNeeded(): void {\n\t\tstartupChecks.showStartupNoticesIfNeeded(this.startupChecksHost());\n\t}\n\n\tasync init(): Promise<void> {\n\t\tif (this.isInitialized) return;\n\n\t\tthis.registerSignalHandlers();\n\n\t\t// Load changelog (only show new entries, skip for resumed sessions)\n\t\tthis.changelogMarkdown = this.getChangelogForDisplay();\n\n\t\t// Ensure fd and rg are available (downloads if missing, adds to PATH via getBinDir)\n\t\t// Both are needed: fd for autocomplete, rg for grep tool and bash commands\n\t\tconst [fdPath] = await Promise.all([ensureTool(\"fd\"), ensureTool(\"rg\")]);\n\t\tthis.fdPath = fdPath;\n\n\t\tif (this.session.scopedModels.length > 0 && (this.options.verbose || !this.settingsManager.getQuietStartup())) {\n\t\t\tconst modelList = this.session.scopedModels\n\t\t\t\t.map((sm) => {\n\t\t\t\t\tconst thinkingStr = sm.thinkingLevel ? `:${sm.thinkingLevel}` : \"\";\n\t\t\t\t\treturn `${sm.model.id}${thinkingStr}`;\n\t\t\t\t})\n\t\t\t\t.join(\", \");\n\t\t\tconst cycleKeys = this.keybindings.getKeys(\"app.model.cycleForward\");\n\t\t\tconst cycleHint =\n\t\t\t\tcycleKeys.length > 0\n\t\t\t\t\t? theme.fg(\"muted\", ` (${formatKeyText(cycleKeys.join(\"/\"), { capitalize: true })} to cycle)`)\n\t\t\t\t\t: \"\";\n\t\t\tconsole.log(theme.fg(\"dim\", `Model scope: ${modelList}${cycleHint}`));\n\t\t}\n\n\t\t// Add header container as first child\n\t\tthis.ui.addChild(this.headerContainer);\n\n\t\t// Add header with keybindings from config (unless silenced)\n\t\tif (this.options.verbose || !this.settingsManager.getQuietStartup()) {\n\t\t\tconst logo = theme.bold(theme.fg(\"accent\", APP_NAME)) + theme.fg(\"dim\", ` v${this.version}`);\n\n\t\t\t// Build startup instructions using keybinding hint helpers\n\t\t\tconst hint = (keybinding: AppKeybinding, description: string) => keyHint(keybinding, description);\n\n\t\t\tconst expandedInstructions = [\n\t\t\t\thint(\"app.interrupt\", \"to interrupt\"),\n\t\t\t\thint(\"app.clear\", \"to clear\"),\n\t\t\t\trawKeyHint(`${keyText(\"app.clear\")} twice`, \"to exit\"),\n\t\t\t\thint(\"app.exit\", \"to exit (empty)\"),\n\t\t\t\thint(\"app.suspend\", \"to suspend\"),\n\t\t\t\tkeyHint(\"tui.editor.deleteToLineEnd\", \"to delete to end\"),\n\t\t\t\thint(\"app.thinking.cycle\", \"to cycle thinking level\"),\n\t\t\t\trawKeyHint(`${keyText(\"app.model.cycleForward\")}/${keyText(\"app.model.cycleBackward\")}`, \"to cycle models\"),\n\t\t\t\thint(\"app.model.select\", \"to select model\"),\n\t\t\t\thint(\"app.tools.expand\", \"to load history / expand tools\"),\n\t\t\t\thint(\"app.thinking.toggle\", \"to expand thinking\"),\n\t\t\t\thint(\"app.editor.external\", \"for external editor\"),\n\t\t\t\trawKeyHint(\"/\", \"for commands\"),\n\t\t\t\trawKeyHint(\"!\", \"to run bash\"),\n\t\t\t\trawKeyHint(\"!!\", \"to run bash (no context)\"),\n\t\t\t\thint(\"app.message.followUp\", \"to queue follow-up\"),\n\t\t\t\trawKeyHint(\">>\", \"prefix to queue follow-up\"),\n\t\t\t\thint(\"app.message.dequeue\", \"to edit all queued messages\"),\n\t\t\t\trawKeyHint(\"↑\", \"(empty editor) to recall queued messages\"),\n\t\t\t\thint(\"app.clipboard.pasteImage\", \"to paste image\"),\n\t\t\t\trawKeyHint(\"drop files\", \"to attach\"),\n\t\t\t].join(\"\\n\");\n\t\t\tconst compactInstructions = [\n\t\t\t\thint(\"app.interrupt\", \"interrupt\"),\n\t\t\t\trawKeyHint(`${keyText(\"app.clear\")}/${keyText(\"app.exit\")}`, \"clear/exit\"),\n\t\t\t\trawKeyHint(\"/\", \"commands\"),\n\t\t\t\trawKeyHint(\"!\", \"bash\"),\n\t\t\t\thint(\"app.tools.expand\", \"history/more\"),\n\t\t\t].join(theme.fg(\"muted\", \" · \"));\n\t\t\tconst compactOnboarding = theme.fg(\n\t\t\t\t\"dim\",\n\t\t\t\t`Press ${keyText(\"app.tools.expand\")} to load session history or show full startup help and loaded resources.`,\n\t\t\t);\n\t\t\tconst onboarding = theme.fg(\n\t\t\t\t\"dim\",\n\t\t\t\t`Pi can explain its own features and look up its docs. Ask it how to use or extend Pi.`,\n\t\t\t);\n\t\t\tthis.builtInHeader = new ExpandableText(\n\t\t\t\t() => `${logo}\\n${compactInstructions}\\n${compactOnboarding}\\n\\n${onboarding}`,\n\t\t\t\t() => `${logo}\\n${expandedInstructions}\\n\\n${onboarding}`,\n\t\t\t\tthis.getStartupExpansionState(),\n\t\t\t\t1,\n\t\t\t\t0,\n\t\t\t);\n\n\t\t\t// Setup UI layout\n\t\t\tthis.headerContainer.addChild(new Spacer(1));\n\t\t\tthis.headerContainer.addChild(this.builtInHeader);\n\t\t\tthis.headerContainer.addChild(new Spacer(1));\n\t\t} else {\n\t\t\t// Minimal header when silenced\n\t\t\tthis.builtInHeader = new Text(\"\", 0, 0);\n\t\t\tthis.headerContainer.addChild(this.builtInHeader);\n\t\t}\n\n\t\tthis.ui.addChild(this.chatContainer);\n\t\tthis.ui.addChild(this.pendingMessagesContainer);\n\t\tthis.ui.addChild(this.statusContainer);\n\t\tthis.extensionUiHost.renderWidgets(); // Initialize with default spacer\n\t\tthis.ui.addChild(this.widgetContainerAbove);\n\t\tthis.ui.addChild(this.editorContainer);\n\t\tthis.ui.addChild(this.widgetContainerBelow);\n\t\tthis.ui.addChild(this.footer);\n\t\tthis.ui.setFocus(this.editor);\n\n\t\tthis.setupKeyHandlers();\n\t\tthis.setupEditorSubmitHandler();\n\n\t\t// Start the UI before initializing extensions so session_start handlers can use interactive dialogs\n\t\tthis.ui.start();\n\t\tthis.isInitialized = true;\n\n\t\t// Initialize extensions first so resources are shown before messages\n\t\tawait this.rebindCurrentSession();\n\n\t\t// Register extensions-changed listener for live reload UI refresh\n\t\tthis.subscribeToExtensionsChanged();\n\n\t\t// Render initial messages AFTER showing loaded resources\n\t\tawait this.renderInitialMessages();\n\t\tthis.renderProjectTrustWarningIfNeeded();\n\n\t\t// Set up theme file watcher\n\t\tonThemeChange(() => {\n\t\t\tthis.ui.invalidate();\n\t\t\tthis.updateEditorBorderColor();\n\t\t\tthis.ui.requestRender();\n\t\t});\n\n\t\t// Set up git branch watcher (uses provider instead of footer)\n\t\tthis.footerDataProvider.onBranchChange(() => {\n\t\t\tthis.ui.requestRender();\n\t\t});\n\n\t\t// Initialize available provider count and Auto Learn status for footer display\n\t\tawait this.updateAvailableProviderCount();\n\t\tthis.updateAutoLearnFooter();\n\t}\n\n\tprivate renderProjectTrustWarningIfNeeded(): void {\n\t\tstartupChecks.renderProjectTrustWarningIfNeeded(this.startupChecksHost());\n\t}\n\n\t/**\n\t * Update terminal title with session name and cwd.\n\t */\n\tprivate updateTerminalTitle(): void {\n\t\tconst cwdBasename = path.basename(this.sessionManager.getCwd());\n\t\tconst sessionName = this.sessionManager.getSessionName();\n\t\tif (sessionName) {\n\t\t\tthis.ui.terminal.setTitle(`${APP_TITLE} - ${sessionName} - ${cwdBasename}`);\n\t\t} else {\n\t\t\tthis.ui.terminal.setTitle(`${APP_TITLE} - ${cwdBasename}`);\n\t\t}\n\t}\n\n\t/**\n\t * Run the interactive mode. This is the main entry point.\n\t * Initializes the UI, shows warnings, processes initial messages, and starts the interactive loop.\n\t */\n\tasync run(): Promise<void> {\n\t\tawait this.init();\n\n\t\t// Start version check asynchronously\n\t\tcheckForNewPiVersion(this.version).then((newRelease) => {\n\t\t\tif (newRelease) {\n\t\t\t\tthis.showNewVersionNotification(newRelease);\n\t\t\t}\n\t\t});\n\n\t\t// Start package update check asynchronously\n\t\tthis.checkForPackageUpdates().then((updates) => {\n\t\t\tif (updates.length > 0) {\n\t\t\t\tthis.showPackageUpdateNotification(updates);\n\t\t\t}\n\t\t});\n\n\t\t// Check tmux keyboard setup asynchronously\n\t\tthis.checkTmuxKeyboardSetup().then((warning) => {\n\t\t\tif (warning) {\n\t\t\t\tthis.showWarning(warning);\n\t\t\t}\n\t\t});\n\n\t\t// Skill curator (#32): auto-archive stale reflection-promoted skills at startup, and ANNOUNCE it\n\t\t// (never silent — the user can restore any of them with `/curate restore <name>`).\n\t\tthis.session\n\t\t\t.runStartupSkillCuration()\n\t\t\t.then((archived) => {\n\t\t\t\tif (archived.length > 0) {\n\t\t\t\t\tthis.showStatus(\n\t\t\t\t\t\t`Curator: auto-archived ${archived.length} stale skill(s) — ${archived.join(\", \")}. Restore with /curate restore <name>.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t})\n\t\t\t.catch(() => {\n\t\t\t\t// curation is best-effort; never disrupt startup\n\t\t\t});\n\n\t\t// Show startup warnings\n\t\tconst { migratedProviders, modelFallbackMessage, initialMessage, initialImages, initialMessages } = this.options;\n\n\t\tif (migratedProviders && migratedProviders.length > 0) {\n\t\t\tthis.showWarning(`Migrated credentials to auth.json: ${migratedProviders.join(\", \")}`);\n\t\t}\n\n\t\tconst modelsJsonError = this.session.modelRegistry.getError();\n\t\tif (modelsJsonError) {\n\t\t\tthis.showError(`models.json error: ${modelsJsonError}`);\n\t\t}\n\n\t\tif (modelFallbackMessage) {\n\t\t\tthis.showWarning(modelFallbackMessage);\n\t\t}\n\n\t\tvoid this.maybeWarnAboutAnthropicSubscriptionAuth();\n\n\t\t// Process initial messages\n\t\tif (initialMessage) {\n\t\t\ttry {\n\t\t\t\tawait this.session.prompt(initialMessage, { images: initialImages });\n\t\t\t} catch (error: unknown) {\n\t\t\t\tconst errorMessage = error instanceof Error ? error.message : \"Unknown error occurred\";\n\t\t\t\tthis.showError(errorMessage);\n\t\t\t} finally {\n\t\t\t\tthis.refreshAutonomyFooterStatus();\n\t\t\t}\n\t\t}\n\n\t\tif (initialMessages) {\n\t\t\tfor (const message of initialMessages) {\n\t\t\t\ttry {\n\t\t\t\t\tawait this.session.prompt(message);\n\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\tconst errorMessage = error instanceof Error ? error.message : \"Unknown error occurred\";\n\t\t\t\t\tthis.showError(errorMessage);\n\t\t\t\t} finally {\n\t\t\t\t\tthis.refreshAutonomyFooterStatus();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Main interactive loop\n\t\twhile (true) {\n\t\t\tconst userInput = await this.getUserInput();\n\t\t\ttry {\n\t\t\t\tawait this.session.prompt(userInput.text, { images: userInput.images });\n\t\t\t} catch (error: unknown) {\n\t\t\t\tconst errorMessage = error instanceof Error ? error.message : \"Unknown error occurred\";\n\t\t\t\tthis.showError(errorMessage);\n\t\t\t} finally {\n\t\t\t\tthis.refreshAutonomyFooterStatus();\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate refreshAutonomyFooterStatus(): void {\n\t\tthis.footerDataProvider.setAutonomyStatusSnapshot(this.session.getAutonomyStatusSnapshot());\n\t\tthis.footer.invalidate();\n\t}\n\n\tprivate checkForPackageUpdates(): Promise<string[]> {\n\t\treturn startupChecks.checkForPackageUpdates(this.startupChecksHost());\n\t}\n\n\tprivate checkTmuxKeyboardSetup(): Promise<string | undefined> {\n\t\treturn startupChecks.checkTmuxKeyboardSetup();\n\t}\n\n\t/**\n\t * Get changelog entries to display on startup.\n\t * Only shows new entries since last seen version, skips for resumed sessions.\n\t */\n\tprivate getChangelogForDisplay(): string | undefined {\n\t\treturn startupChecks.getChangelogForDisplay(this.startupChecksHost());\n\t}\n\n\tprivate getMarkdownThemeWithSettings(): MarkdownTheme {\n\t\treturn {\n\t\t\t...getMarkdownTheme(),\n\t\t\tcodeBlockIndent: this.settingsManager.getCodeBlockIndent(),\n\t\t};\n\t}\n\n\t// =========================================================================\n\t// Extension System\n\t// =========================================================================\n\n\t// The resource-display formatters below are pure and live in ./resource-display.ts.\n\t// These thin `this.`-delegates are retained so showLoadedResources' formatter\n\t// injection seam (exercised via prototype stubs in interactive-mode-status.test.ts)\n\t// keeps working unchanged.\n\tprivate formatDisplayPath(p: string): string {\n\t\treturn resourceDisplay.formatDisplayPath(p);\n\t}\n\n\tprivate formatExtensionDisplayPath(path: string): string {\n\t\treturn resourceDisplay.formatExtensionDisplayPath(path);\n\t}\n\n\tprivate formatContextPath(p: string): string {\n\t\tconst cwd = path.resolve(this.sessionManager.getCwd());\n\t\tconst absolutePath = path.isAbsolute(p) ? path.resolve(p) : path.resolve(cwd, p);\n\t\tconst relativePath = getCwdRelativePath(absolutePath, cwd);\n\t\tif (relativePath !== undefined) {\n\t\t\treturn relativePath;\n\t\t}\n\n\t\treturn this.formatDisplayPath(absolutePath);\n\t}\n\n\tprivate getStartupExpansionState(): boolean {\n\t\treturn this.options.verbose || this.toolOutputExpanded;\n\t}\n\n\tprivate getShortPath(fullPath: string, sourceInfo?: SourceInfo): string {\n\t\treturn resourceDisplay.getShortPath(fullPath, sourceInfo);\n\t}\n\n\tprivate getCompactPathLabel(resourcePath: string, sourceInfo?: SourceInfo): string {\n\t\treturn resourceDisplay.getCompactPathLabel(resourcePath, sourceInfo);\n\t}\n\n\tprivate getCompactPackageSourceLabel(sourceInfo?: SourceInfo): string {\n\t\treturn resourceDisplay.getCompactPackageSourceLabel(sourceInfo);\n\t}\n\n\tprivate getCompactExtensionLabel(resourcePath: string, sourceInfo?: SourceInfo): string {\n\t\treturn resourceDisplay.getCompactExtensionLabel(resourcePath, sourceInfo);\n\t}\n\n\tprivate getCompactDisplayPathSegments(resourcePath: string): string[] {\n\t\treturn resourceDisplay.getCompactDisplayPathSegments(resourcePath);\n\t}\n\n\tprivate getCompactNonPackageExtensionLabel(\n\t\tresourcePath: string,\n\t\tindex: number,\n\t\tallPaths: Array<{ path: string; segments: string[] }>,\n\t): string {\n\t\treturn resourceDisplay.getCompactNonPackageExtensionLabel(resourcePath, index, allPaths);\n\t}\n\n\tprivate getCompactExtensionLabels(extensions: Array<{ path: string; sourceInfo?: SourceInfo }>): string[] {\n\t\treturn resourceDisplay.getCompactExtensionLabels(extensions);\n\t}\n\n\tprivate getScopeGroup(sourceInfo?: SourceInfo): \"user\" | \"project\" | \"path\" {\n\t\treturn resourceDisplay.getScopeGroup(sourceInfo);\n\t}\n\n\tprivate isPackageSource(sourceInfo?: SourceInfo): boolean {\n\t\treturn resourceDisplay.isPackageSource(sourceInfo);\n\t}\n\n\tprivate buildScopeGroups(items: Array<{ path: string; sourceInfo?: SourceInfo }>): Array<{\n\t\tscope: \"user\" | \"project\" | \"path\";\n\t\tpaths: Array<{ path: string; sourceInfo?: SourceInfo }>;\n\t\tpackages: Map<string, Array<{ path: string; sourceInfo?: SourceInfo }>>;\n\t}> {\n\t\treturn resourceDisplay.buildScopeGroups(items);\n\t}\n\n\tprivate formatScopeGroups(\n\t\tgroups: Array<{\n\t\t\tscope: \"user\" | \"project\" | \"path\";\n\t\t\tpaths: Array<{ path: string; sourceInfo?: SourceInfo }>;\n\t\t\tpackages: Map<string, Array<{ path: string; sourceInfo?: SourceInfo }>>;\n\t\t}>,\n\t\toptions: {\n\t\t\tformatPath: (item: { path: string; sourceInfo?: SourceInfo }) => string;\n\t\t\tformatPackagePath: (item: { path: string; sourceInfo?: SourceInfo }, source: string) => string;\n\t\t},\n\t): string {\n\t\treturn resourceDisplay.formatScopeGroups(groups, options);\n\t}\n\n\tprivate formatDiagnostics(diagnostics: readonly ResourceDiagnostic[], sourceInfos: Map<string, SourceInfo>): string {\n\t\treturn resourceDisplay.formatDiagnostics(diagnostics, sourceInfos);\n\t}\n\n\tprivate showLoadedResources(options?: {\n\t\textensions?: Array<{ path: string; sourceInfo?: SourceInfo }>;\n\t\tforce?: boolean;\n\t\tshowDiagnosticsWhenQuiet?: boolean;\n\t}): void {\n\t\tconst showListing = options?.force || this.options.verbose || !this.settingsManager.getQuietStartup();\n\t\tconst showDiagnostics = showListing || options?.showDiagnosticsWhenQuiet === true;\n\t\tif (!showListing && !showDiagnostics) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst sectionHeader = (name: string, color: ThemeColor = \"mdHeading\") => theme.fg(color, `[${name}]`);\n\t\tconst formatCompactList = (items: string[], options?: { sort?: boolean }): string => {\n\t\t\tconst labels = items.map((item) => item.trim()).filter((item) => item.length > 0);\n\t\t\tif (options?.sort !== false) {\n\t\t\t\tlabels.sort((a, b) => a.localeCompare(b));\n\t\t\t}\n\t\t\treturn theme.fg(\"dim\", ` ${labels.join(\", \")}`);\n\t\t};\n\t\tconst addLoadedSection = (\n\t\t\tname: string,\n\t\t\tcollapsedBody: string,\n\t\t\texpandedBody = collapsedBody,\n\t\t\tcolor: ThemeColor = \"mdHeading\",\n\t\t): void => {\n\t\t\tconst section = new ExpandableText(\n\t\t\t\t() => `${sectionHeader(name, color)}\\n${collapsedBody}`,\n\t\t\t\t() => `${sectionHeader(name, color)}\\n${expandedBody}`,\n\t\t\t\tthis.getStartupExpansionState(),\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t);\n\t\t\tthis.chatContainer.addChild(section);\n\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t};\n\n\t\tconst skillsResult = this.session.resourceLoader.getSkills();\n\t\tconst promptsResult = this.session.resourceLoader.getPrompts();\n\t\tconst themesResult = this.session.resourceLoader.getThemes();\n\t\tconst extensions =\n\t\t\toptions?.extensions ??\n\t\t\tthis.session.resourceLoader.getExtensions().extensions.map((extension) => ({\n\t\t\t\tpath: extension.path,\n\t\t\t\tsourceInfo: extension.sourceInfo,\n\t\t\t}));\n\t\tconst sourceInfos = new Map<string, SourceInfo>();\n\t\tfor (const extension of extensions) {\n\t\t\tif (extension.sourceInfo) {\n\t\t\t\tsourceInfos.set(extension.path, extension.sourceInfo);\n\t\t\t}\n\t\t}\n\t\tfor (const skill of skillsResult.skills) {\n\t\t\tif (skill.sourceInfo) {\n\t\t\t\tsourceInfos.set(skill.filePath, skill.sourceInfo);\n\t\t\t}\n\t\t}\n\t\tfor (const prompt of promptsResult.prompts) {\n\t\t\tif (prompt.sourceInfo) {\n\t\t\t\tsourceInfos.set(prompt.filePath, prompt.sourceInfo);\n\t\t\t}\n\t\t}\n\t\tfor (const loadedTheme of themesResult.themes) {\n\t\t\tif (loadedTheme.sourcePath && loadedTheme.sourceInfo) {\n\t\t\t\tsourceInfos.set(loadedTheme.sourcePath, loadedTheme.sourceInfo);\n\t\t\t}\n\t\t}\n\n\t\tif (showListing) {\n\t\t\tconst contextFiles = this.session.resourceLoader.getAgentsFiles().agentsFiles;\n\t\t\tif (contextFiles.length > 0) {\n\t\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t\t\tconst contextList = contextFiles\n\t\t\t\t\t.map((f) => theme.fg(\"dim\", ` ${this.formatDisplayPath(f.path)}`))\n\t\t\t\t\t.join(\"\\n\");\n\t\t\t\tconst contextCompactList = formatCompactList(\n\t\t\t\t\tcontextFiles.map((contextFile) => this.formatContextPath(contextFile.path)),\n\t\t\t\t\t{ sort: false },\n\t\t\t\t);\n\t\t\t\taddLoadedSection(\"Context\", contextCompactList, contextList);\n\t\t\t}\n\n\t\t\tconst skills = this.session.resourceLoader.getActiveSkills();\n\t\t\tif (skills.length > 0) {\n\t\t\t\tconst groups = this.buildScopeGroups(\n\t\t\t\t\tskills.map((skill) => ({ path: skill.filePath, sourceInfo: skill.sourceInfo })),\n\t\t\t\t);\n\t\t\t\tconst skillList = this.formatScopeGroups(groups, {\n\t\t\t\t\tformatPath: (item) => this.formatDisplayPath(item.path),\n\t\t\t\t\tformatPackagePath: (item) => this.getShortPath(item.path, item.sourceInfo),\n\t\t\t\t});\n\t\t\t\tconst skillCompactList = formatCompactList(skills.map((skill) => skill.name));\n\t\t\t\taddLoadedSection(\"Skills\", skillCompactList, skillList);\n\t\t\t}\n\n\t\t\tconst templates = this.session.promptTemplates;\n\t\t\tif (templates.length > 0) {\n\t\t\t\tconst groups = this.buildScopeGroups(\n\t\t\t\t\ttemplates.map((template) => ({ path: template.filePath, sourceInfo: template.sourceInfo })),\n\t\t\t\t);\n\t\t\t\tconst templateByPath = new Map(templates.map((t) => [t.filePath, t]));\n\t\t\t\tconst templateList = this.formatScopeGroups(groups, {\n\t\t\t\t\tformatPath: (item) => {\n\t\t\t\t\t\tconst template = templateByPath.get(item.path);\n\t\t\t\t\t\treturn template ? `/${template.name}` : this.formatDisplayPath(item.path);\n\t\t\t\t\t},\n\t\t\t\t\tformatPackagePath: (item) => {\n\t\t\t\t\t\tconst template = templateByPath.get(item.path);\n\t\t\t\t\t\treturn template ? `/${template.name}` : this.formatDisplayPath(item.path);\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t\tconst promptCompactList = formatCompactList(templates.map((template) => `/${template.name}`));\n\t\t\t\taddLoadedSection(\"Prompts\", promptCompactList, templateList);\n\t\t\t}\n\n\t\t\tif (extensions.length > 0) {\n\t\t\t\tconst groups = this.buildScopeGroups(extensions);\n\t\t\t\tconst extList = this.formatScopeGroups(groups, {\n\t\t\t\t\tformatPath: (item) => this.formatExtensionDisplayPath(item.path),\n\t\t\t\t\tformatPackagePath: (item) =>\n\t\t\t\t\t\tthis.formatExtensionDisplayPath(this.getShortPath(item.path, item.sourceInfo)),\n\t\t\t\t});\n\t\t\t\tconst extensionCompactList = formatCompactList(this.getCompactExtensionLabels(extensions));\n\t\t\t\taddLoadedSection(\"Extensions\", extensionCompactList, extList, \"mdHeading\");\n\t\t\t}\n\n\t\t\t// Show loaded themes (excluding built-in)\n\t\t\tconst loadedThemes = themesResult.themes;\n\t\t\tconst customThemes = loadedThemes.filter((t) => t.sourcePath);\n\t\t\tif (customThemes.length > 0) {\n\t\t\t\tconst groups = this.buildScopeGroups(\n\t\t\t\t\tcustomThemes.map((loadedTheme) => ({\n\t\t\t\t\t\tpath: loadedTheme.sourcePath!,\n\t\t\t\t\t\tsourceInfo: loadedTheme.sourceInfo,\n\t\t\t\t\t})),\n\t\t\t\t);\n\t\t\t\tconst themeList = this.formatScopeGroups(groups, {\n\t\t\t\t\tformatPath: (item) => this.formatDisplayPath(item.path),\n\t\t\t\t\tformatPackagePath: (item) => this.getShortPath(item.path, item.sourceInfo),\n\t\t\t\t});\n\t\t\t\tconst themeCompactList = formatCompactList(\n\t\t\t\t\tcustomThemes.map(\n\t\t\t\t\t\t(loadedTheme) =>\n\t\t\t\t\t\t\tloadedTheme.name ?? this.getCompactPathLabel(loadedTheme.sourcePath!, loadedTheme.sourceInfo),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\taddLoadedSection(\"Themes\", themeCompactList, themeList);\n\t\t\t}\n\t\t}\n\n\t\tif (showDiagnostics) {\n\t\t\tconst skillDiagnostics = skillsResult.diagnostics;\n\t\t\tif (skillDiagnostics.length > 0) {\n\t\t\t\tconst warningLines = this.formatDiagnostics(skillDiagnostics, sourceInfos);\n\t\t\t\tthis.chatContainer.addChild(new Text(`${theme.fg(\"warning\", \"[Skill conflicts]\")}\\n${warningLines}`, 0, 0));\n\t\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t\t}\n\n\t\t\tconst promptDiagnostics = promptsResult.diagnostics;\n\t\t\tif (promptDiagnostics.length > 0) {\n\t\t\t\tconst warningLines = this.formatDiagnostics(promptDiagnostics, sourceInfos);\n\t\t\t\tthis.chatContainer.addChild(\n\t\t\t\t\tnew Text(`${theme.fg(\"warning\", \"[Prompt conflicts]\")}\\n${warningLines}`, 0, 0),\n\t\t\t\t);\n\t\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t\t}\n\n\t\t\tconst extensionDiagnostics: ResourceDiagnostic[] = [];\n\t\t\tconst extensionErrors = this.session.resourceLoader.getExtensions().errors;\n\t\t\tif (extensionErrors.length > 0) {\n\t\t\t\tfor (const error of extensionErrors) {\n\t\t\t\t\textensionDiagnostics.push({ type: \"error\", message: error.error, path: error.path });\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst commandDiagnostics = this.session.extensionRunner.getCommandDiagnostics();\n\t\t\textensionDiagnostics.push(...commandDiagnostics);\n\t\t\textensionDiagnostics.push(...this.getBuiltInCommandConflictDiagnostics(this.session.extensionRunner));\n\n\t\t\tconst shortcutDiagnostics = this.session.extensionRunner.getShortcutDiagnostics();\n\t\t\textensionDiagnostics.push(...shortcutDiagnostics);\n\n\t\t\tif (extensionDiagnostics.length > 0) {\n\t\t\t\tconst warningLines = this.formatDiagnostics(extensionDiagnostics, sourceInfos);\n\t\t\t\tthis.chatContainer.addChild(\n\t\t\t\t\tnew Text(`${theme.fg(\"warning\", \"[Extension issues]\")}\\n${warningLines}`, 0, 0),\n\t\t\t\t);\n\t\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t\t}\n\n\t\t\tconst themeDiagnostics = themesResult.diagnostics;\n\t\t\tif (themeDiagnostics.length > 0) {\n\t\t\t\tconst warningLines = this.formatDiagnostics(themeDiagnostics, sourceInfos);\n\t\t\t\tthis.chatContainer.addChild(new Text(`${theme.fg(\"warning\", \"[Theme conflicts]\")}\\n${warningLines}`, 0, 0));\n\t\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Initialize the extension system with TUI-based UI context.\n\t */\n\tprivate async bindCurrentSessionExtensions(): Promise<void> {\n\t\tconst uiContext = this.extensionUiHost.createExtensionUIContext();\n\t\tawait this.session.bindExtensions({\n\t\t\tuiContext,\n\t\t\tmode: \"tui\",\n\t\t\tabortHandler: () => {\n\t\t\t\tthis.restoreQueuedMessagesToEditor({ abort: true });\n\t\t\t},\n\t\t\tcommandContextActions: {\n\t\t\t\twaitForIdle: () => this.session.agent.waitForIdle(),\n\t\t\t\tnewSession: async (options) => {\n\t\t\t\t\tif (this.loadingAnimation) {\n\t\t\t\t\t\tthis.loadingAnimation.stop();\n\t\t\t\t\t\tthis.loadingAnimation = undefined;\n\t\t\t\t\t}\n\t\t\t\t\tthis.statusContainer.clear();\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst result = await this.runtimeHost.newSession(options);\n\t\t\t\t\t\tif (!result.cancelled) {\n\t\t\t\t\t\t\tthis.renderCurrentSessionState();\n\t\t\t\t\t\t\tthis.ui.requestRender();\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn result;\n\t\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\t\treturn this.handleFatalRuntimeError(\"Failed to create session\", error);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tfork: async (entryId, options) => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst result = await this.runtimeHost.fork(entryId, options);\n\t\t\t\t\t\tif (!result.cancelled) {\n\t\t\t\t\t\t\tthis.renderCurrentSessionState();\n\t\t\t\t\t\t\tthis.editor.setText(result.selectedText ?? \"\");\n\t\t\t\t\t\t\tthis.showStatus(\"Forked to new session\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn { cancelled: result.cancelled };\n\t\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\t\treturn this.handleFatalRuntimeError(\"Failed to fork session\", error);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tnavigateTree: async (targetId, options) => {\n\t\t\t\t\tconst result = await this.session.navigateTree(targetId, {\n\t\t\t\t\t\tsummarize: options?.summarize,\n\t\t\t\t\t\tcustomInstructions: options?.customInstructions,\n\t\t\t\t\t\treplaceInstructions: options?.replaceInstructions,\n\t\t\t\t\t\tlabel: options?.label,\n\t\t\t\t\t});\n\t\t\t\t\tif (result.cancelled) {\n\t\t\t\t\t\treturn { cancelled: true };\n\t\t\t\t\t}\n\n\t\t\t\t\tawait this.renderInitialMessages();\n\t\t\t\t\tif (result.editorText && !this.editor.getText().trim()) {\n\t\t\t\t\t\tthis.editor.setText(result.editorText);\n\t\t\t\t\t}\n\t\t\t\t\tthis.showStatus(\"Navigated to selected point\");\n\t\t\t\t\tvoid this.flushCompactionQueue({ willRetry: false });\n\t\t\t\t\treturn { cancelled: false };\n\t\t\t\t},\n\t\t\t\tswitchSession: async (sessionPath, options) => {\n\t\t\t\t\treturn this.handleResumeSession(sessionPath, options);\n\t\t\t\t},\n\t\t\t\treload: async () => {\n\t\t\t\t\tawait this.handleReloadCommand();\n\t\t\t\t},\n\t\t\t},\n\t\t\tshutdownHandler: () => {\n\t\t\t\tthis.shutdownRequested = true;\n\t\t\t\tif (!this.session.isStreaming) {\n\t\t\t\t\tvoid this.shutdown();\n\t\t\t\t}\n\t\t\t},\n\t\t\tonError: (error) => {\n\t\t\t\tthis.extensionUiHost.showExtensionError(error.extensionPath, error.error, error.stack);\n\t\t\t},\n\t\t});\n\n\t\tsetRegisteredThemes(this.session.resourceLoader.getThemes().themes);\n\t\tthis.setupAutocompleteProvider();\n\n\t\tconst extensionRunner = this.session.extensionRunner;\n\t\tthis.extensionUiHost.setupExtensionShortcuts(extensionRunner);\n\t\tthis.showLoadedResources({ force: false, showDiagnosticsWhenQuiet: true });\n\t\tthis.showStartupNoticesIfNeeded();\n\t}\n\n\tprivate applyRuntimeSettings(): void {\n\t\tconfigureHttpDispatcher(this.settingsManager.getHttpIdleTimeoutMs());\n\t\tthis.footer.setSession(this.session);\n\t\tthis.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);\n\t\tthis.footerDataProvider.setCwd(this.sessionManager.getCwd());\n\t\tthis.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();\n\t\tthis.ui.setShowHardwareCursor(this.settingsManager.getShowHardwareCursor());\n\t\tthis.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());\n\t\tconst editorPaddingX = this.settingsManager.getEditorPaddingX();\n\t\tconst autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();\n\t\tthis.defaultEditor.setPaddingX(editorPaddingX);\n\t\tthis.defaultEditor.setAutocompleteMaxVisible(autocompleteMaxVisible);\n\t\tif (this.editor !== this.defaultEditor) {\n\t\t\tthis.editor.setPaddingX?.(editorPaddingX);\n\t\t\tthis.editor.setAutocompleteMaxVisible?.(autocompleteMaxVisible);\n\t\t}\n\t}\n\n\tprivate async rebindCurrentSession(): Promise<void> {\n\t\tthis.unsubscribe?.();\n\t\tthis.unsubscribe = undefined;\n\t\tthis.applyRuntimeSettings();\n\t\tawait this.bindCurrentSessionExtensions();\n\t\tthis.subscribeToAgent();\n\t\tthis.subscribeToExtensionsChanged();\n\t\tawait this.updateAvailableProviderCount();\n\t\tthis.updateEditorBorderColor();\n\t\tthis.updateTerminalTitle();\n\t}\n\n\tprivate subscribeToExtensionsChanged(): void {\n\t\tthis.unsubscribeExtensionsChanged?.();\n\t\tthis.unsubscribeExtensionsChanged = this.session.onExtensionsChanged(() => {\n\t\t\tthis.refreshUIAfterExtensionsChanged();\n\t\t});\n\t}\n\n\tprivate async handleFatalRuntimeError(prefix: string, error: unknown): Promise<never> {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tthis.showError(`${prefix}: ${message}`);\n\t\tstopThemeWatcher();\n\t\tthis.stop();\n\t\tprocess.exit(1);\n\t}\n\n\tprivate renderCurrentSessionState(): void {\n\t\tthis.pendingMessagesContainer.clear();\n\t\tthis.compactionQueuedMessages = [];\n\t\tthis.streamingComponent = undefined;\n\t\tthis.streamingMessage = undefined;\n\t\tthis.clearRenderedToolPanelState();\n\t\tvoid this.renderInitialMessages();\n\t}\n\n\t/**\n\t * Get a registered tool definition by name (for custom rendering).\n\t */\n\tprivate getRegisteredToolDefinition(toolName: string) {\n\t\treturn this.session.getToolDefinition(toolName);\n\t}\n\n\tprivate getToolPanelScope() {\n\t\treturn {\n\t\t\tsessionId: this.sessionManager.getSessionId?.(),\n\t\t\tsessionFile: this.sessionManager.getSessionFile?.(),\n\t\t\tcwd: this.sessionManager.getCwd(),\n\t\t};\n\t}\n\n\tprivate appendToolExecutionComponent(component: ToolExecutionComponent, allowGrouping: boolean): void {\n\t\tconst toolGroup = allowGrouping ? component.toolGroup?.trim() : undefined;\n\t\tif (!toolGroup) {\n\t\t\tthis.chatContainer.addChild(component);\n\t\t\tthis.trimLiveTuiHistory();\n\t\t\treturn;\n\t\t}\n\n\t\tconst children = this.chatContainer.children;\n\t\tconst lastChild = children[children.length - 1];\n\t\tif (lastChild instanceof ToolGroupComponent && lastChild.toolGroup === toolGroup) {\n\t\t\tlastChild.addTool(component);\n\t\t\tthis.trimLiveTuiHistory();\n\t\t\treturn;\n\t\t}\n\t\tif (lastChild instanceof ToolExecutionComponent && lastChild.toolGroup?.trim() === toolGroup) {\n\t\t\tconst group = new ToolGroupComponent(toolGroup, [lastChild, component]);\n\t\t\tgroup.setExpanded(this.toolOutputExpanded);\n\t\t\tchildren[children.length - 1] = group;\n\t\t\tthis.trimLiveTuiHistory();\n\t\t\treturn;\n\t\t}\n\t\tthis.chatContainer.addChild(component);\n\t\tthis.trimLiveTuiHistory();\n\t}\n\n\tprivate detachToolExecutionComponent(component: ToolExecutionComponent): void {\n\t\tconst children = this.chatContainer.children;\n\t\tconst directIndex = children.indexOf(component);\n\t\tif (directIndex !== -1) {\n\t\t\tchildren.splice(directIndex, 1);\n\t\t\treturn;\n\t\t}\n\t\tfor (let i = 0; i < children.length; i++) {\n\t\t\tconst child = children[i];\n\t\t\tif (!(child instanceof ToolGroupComponent) || !child.removeTool(component)) continue;\n\t\t\tconst remaining = child.getToolCount();\n\t\t\tif (remaining === 0) {\n\t\t\t\tchildren.splice(i, 1);\n\t\t\t} else if (remaining === 1) {\n\t\t\t\tconst onlyTool = child.getOnlyTool();\n\t\t\t\tif (onlyTool) children[i] = onlyTool;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t}\n\n\tprivate attachToolExecutionComponent(\n\t\ttoolName: string,\n\t\ttoolCallId: string,\n\t\targs: any,\n\t\trepair?: ToolCallRepairInfo,\n\t): ToolExecutionComponent {\n\t\tconst actionKey = getToolPanelActionKey(this.getToolPanelScope(), toolName, args);\n\t\tconst toolDefinition = this.getRegisteredToolDefinition(toolName);\n\t\tconst reuseInPlace = shouldReuseToolPanelInPlace(toolName, args);\n\t\tconst existing = this.toolPanels.getReusable(actionKey, { allowActive: reuseInPlace });\n\t\tif (existing) {\n\t\t\tif (reuseInPlace && actionKey) {\n\t\t\t\texisting.resetInvocation(toolName, toolCallId, args, toolDefinition, repair);\n\t\t\t\texisting.setExpanded(this.toolOutputExpanded);\n\t\t\t\tthis.toolPanels.replaceActiveForAction(toolCallId, existing, actionKey);\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\treturn existing;\n\t\t\t}\n\t\t\tthis.detachToolExecutionComponent(existing);\n\t\t\texisting.resetInvocation(toolName, toolCallId, args, toolDefinition, repair);\n\t\t\texisting.setExpanded(this.toolOutputExpanded);\n\t\t\tthis.appendToolExecutionComponent(existing, true);\n\t\t\tthis.toolPanels.register(toolCallId, existing, actionKey);\n\t\t\treturn existing;\n\t\t}\n\t\tconst component = new ToolExecutionComponent(\n\t\t\ttoolName,\n\t\t\ttoolCallId,\n\t\t\targs,\n\t\t\t{\n\t\t\t\tshowImages: this.settingsManager.getShowImages(),\n\t\t\t\timageWidthCells: this.settingsManager.getImageWidthCells(),\n\t\t\t\trepair,\n\t\t\t},\n\t\t\ttoolDefinition,\n\t\t\tthis.ui,\n\t\t\tthis.sessionManager.getCwd(),\n\t\t);\n\t\tcomponent.setExpanded(this.toolOutputExpanded);\n\t\tthis.appendToolExecutionComponent(component, true);\n\t\tthis.toolPanels.register(toolCallId, component, actionKey);\n\t\treturn component;\n\t}\n\n\tprivate clearActiveToolCalls(): void {\n\t\tthis.toolPanels.clearActive();\n\t}\n\n\tprivate clearRenderedToolPanelState(): void {\n\t\tthis.toolPanels.clearAll();\n\t}\n\n\tprivate getWorkingLoaderMessage(): string {\n\t\treturn this.workingMessage ?? this.defaultWorkingMessage;\n\t}\n\n\tprivate createWorkingLoader(): Loader {\n\t\treturn new Loader(\n\t\t\tthis.ui,\n\t\t\t(spinner) => theme.fg(\"accent\", spinner),\n\t\t\t(text) => theme.fg(\"muted\", text),\n\t\t\tthis.getWorkingLoaderMessage(),\n\t\t\tthis.workingIndicatorOptions,\n\t\t);\n\t}\n\n\tprivate stopWorkingLoader(): void {\n\t\tif (this.loadingAnimation) {\n\t\t\tthis.loadingAnimation.stop();\n\t\t\tthis.loadingAnimation = undefined;\n\t\t}\n\t\tthis.statusContainer.clear();\n\t}\n\n\tprivate setWorkingVisible(visible: boolean): void {\n\t\tthis.workingVisible = visible;\n\t\tif (!visible) {\n\t\t\tthis.stopWorkingLoader();\n\t\t\tthis.ui.requestRender();\n\t\t\treturn;\n\t\t}\n\t\tif (this.session.isStreaming && !this.loadingAnimation) {\n\t\t\tthis.statusContainer.clear();\n\t\t\tthis.loadingAnimation = this.createWorkingLoader();\n\t\t\tthis.statusContainer.addChild(this.loadingAnimation);\n\t\t}\n\t\tthis.ui.requestRender();\n\t}\n\n\tprivate setWorkingIndicator(options?: LoaderIndicatorOptions): void {\n\t\tthis.workingIndicatorOptions = options;\n\t\tthis.loadingAnimation?.setIndicator(options);\n\t\tthis.ui.requestRender();\n\t}\n\n\tprivate setHiddenThinkingLabel(label?: string): void {\n\t\tthis.hiddenThinkingLabel = label ?? this.defaultHiddenThinkingLabel;\n\t\tfor (const child of this.chatContainer.children) {\n\t\t\tif (child instanceof AssistantMessageComponent) {\n\t\t\t\tchild.setHiddenThinkingLabel(this.hiddenThinkingLabel);\n\t\t\t}\n\t\t}\n\t\tif (this.streamingComponent) {\n\t\t\tthis.streamingComponent.setHiddenThinkingLabel(this.hiddenThinkingLabel);\n\t\t}\n\t\tthis.ui.requestRender();\n\t}\n\n\t/**\n\t * Set the extension-provided working-loader message (undefined restores the default).\n\t */\n\tprivate setWorkingMessage(message: string | undefined): void {\n\t\tthis.workingMessage = message;\n\t\tif (this.loadingAnimation) {\n\t\t\tthis.loadingAnimation.setMessage(message ?? this.defaultWorkingMessage);\n\t\t}\n\t}\n\n\t/**\n\t * Reset the working indicator and hidden-thinking label to their built-in defaults.\n\t */\n\tprivate resetWorkingIndicators(): void {\n\t\tthis.workingMessage = undefined;\n\t\tthis.workingVisible = true;\n\t\tthis.setWorkingIndicator();\n\t\tif (this.loadingAnimation) {\n\t\t\tthis.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${keyText(\"app.interrupt\")} to interrupt)`);\n\t\t}\n\t\tthis.setHiddenThinkingLabel();\n\t}\n\n\tprivate async promptForMissingSessionCwd(error: MissingSessionCwdError): Promise<string | undefined> {\n\t\tconst confirmed = await this.extensionUiHost.showExtensionConfirm(\n\t\t\t\"Session cwd not found\",\n\t\t\tformatMissingSessionCwdPrompt(error.issue),\n\t\t);\n\t\treturn confirmed ? error.issue.fallbackCwd : undefined;\n\t}\n\n\t// =========================================================================\n\t// Key Handlers\n\t// =========================================================================\n\n\tprivate setupKeyHandlers(): void {\n\t\tkeyHandlers.setupKeyHandlers(this.keyHandlersHost());\n\t}\n\n\tprivate keyHandlersHost(): keyHandlers.KeyHandlersHost {\n\t\tconst self = this;\n\t\treturn {\n\t\t\tdefaultEditor: this.defaultEditor,\n\t\t\tui: this.ui,\n\t\t\tget editor() {\n\t\t\t\treturn self.editor;\n\t\t\t},\n\t\t\tget session() {\n\t\t\t\treturn self.session;\n\t\t\t},\n\t\t\tget settingsManager() {\n\t\t\t\treturn self.settingsManager;\n\t\t\t},\n\t\t\tget isBashMode() {\n\t\t\t\treturn self.isBashMode;\n\t\t\t},\n\t\t\tset isBashMode(value) {\n\t\t\t\tself.isBashMode = value;\n\t\t\t},\n\t\t\tget lastEscapeTime() {\n\t\t\t\treturn self.lastEscapeTime;\n\t\t\t},\n\t\t\tset lastEscapeTime(value) {\n\t\t\t\tself.lastEscapeTime = value;\n\t\t\t},\n\t\t\trestoreQueuedMessagesToEditor: (options) => this.restoreQueuedMessagesToEditor(options),\n\t\t\tupdateEditorBorderColor: () => this.updateEditorBorderColor(),\n\t\t\tshowTreeSelector: (id) => this.showTreeSelector(id),\n\t\t\tshowUserMessageSelector: (name) => this.showUserMessageSelector(name),\n\t\t\thandleCtrlC: () => this.handleCtrlC(),\n\t\t\thandleCtrlD: () => this.handleCtrlD(),\n\t\t\thandleCtrlZ: () => this.handleCtrlZ(),\n\t\t\tcycleThinkingLevel: () => this.cycleThinkingLevel(),\n\t\t\tcycleModel: (direction) => this.cycleModel(direction),\n\t\t\thandleDebugCommand: () => this.handleDebugCommand(),\n\t\t\tshowModelSelector: (input) => this.showModelSelector(input),\n\t\t\tloadTuiHistoryOnDemand: () => this.loadTuiHistoryOnDemand(),\n\t\t\ttoggleThinkingBlockVisibility: () => this.toggleThinkingBlockVisibility(),\n\t\t\topenExternalEditor: () => this.openExternalEditor(),\n\t\t\thandleFollowUp: () => this.handleFollowUp(),\n\t\t\thandleDequeue: () => this.handleDequeue(),\n\t\t\thandleClearCommand: (name) => this.handleClearCommand(name),\n\t\t\tshowSessionSelector: () => this.showSessionSelector(),\n\t\t\thandleClipboardImagePaste: () => this.handleClipboardImagePaste(),\n\t\t};\n\t}\n\n\tprivate handleClipboardImagePaste(): Promise<void> {\n\t\tconst self = this;\n\t\treturn clipboardInput.handleClipboardImagePaste({\n\t\t\tget pendingClipboardImages() {\n\t\t\t\treturn self.pendingClipboardImages;\n\t\t\t},\n\t\t\tset pendingClipboardImages(value) {\n\t\t\t\tself.pendingClipboardImages = value;\n\t\t\t},\n\t\t\tget clipboardImageCounter() {\n\t\t\t\treturn self.clipboardImageCounter;\n\t\t\t},\n\t\t\tset clipboardImageCounter(value) {\n\t\t\t\tself.clipboardImageCounter = value;\n\t\t\t},\n\t\t\teditor: this.editor,\n\t\t\tui: this.ui,\n\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\tshowWarning: (message) => this.showWarning(message),\n\t\t});\n\t}\n\n\tprivate takeClipboardImagesForText(text: string): ImageContent[] | undefined {\n\t\tconst self = this;\n\t\treturn clipboardInput.takeClipboardImagesForText(\n\t\t\t{\n\t\t\t\tget pendingClipboardImages() {\n\t\t\t\t\treturn self.pendingClipboardImages;\n\t\t\t\t},\n\t\t\t\tset pendingClipboardImages(value) {\n\t\t\t\t\tself.pendingClipboardImages = value;\n\t\t\t\t},\n\t\t\t\tget clipboardImageCounter() {\n\t\t\t\t\treturn self.clipboardImageCounter;\n\t\t\t\t},\n\t\t\t\tset clipboardImageCounter(value) {\n\t\t\t\t\tself.clipboardImageCounter = value;\n\t\t\t\t},\n\t\t\t},\n\t\t\ttext,\n\t\t);\n\t}\n\n\tprivate buildUserInputSubmission(text: string): UserInputSubmission {\n\t\treturn clipboardInput.buildUserInputSubmission(\n\t\t\t{ takeClipboardImagesForText: (t) => this.takeClipboardImagesForText(t) },\n\t\t\ttext,\n\t\t);\n\t}\n\n\tprivate setupEditorSubmitHandler(): void {\n\t\tthis.defaultEditor.onSubmit = async (text: string) => {\n\t\t\ttext = text.trim();\n\t\t\tif (!text) return;\n\n\t\t\tif (text === \"/quit\" || text === \"/exit\") {\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\tawait this.shutdown();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// A \">>\" prefix queues the message as a follow-up (delivered after the\n\t\t\t// current work finishes, starting the next round) instead of steering.\n\t\t\t// This is the chord-free alternative to app.message.followUp, which many\n\t\t\t// terminals swallow (e.g. Windows Terminal claims alt+enter).\n\t\t\tconst queueAsFollowUp = text.startsWith(\">>\");\n\t\t\tif (queueAsFollowUp) {\n\t\t\t\ttext = text.slice(2).trim();\n\t\t\t\tif (!text) return;\n\t\t\t}\n\n\t\t\t// User input submitted while work is active is always steering. Treat\n\t\t\t// slash/bang text as user steering text instead of executing commands that\n\t\t\t// would interrupt the current stream or compaction.\n\t\t\tif (this.session.isCompacting) {\n\t\t\t\tconst images = this.takeClipboardImagesForText(text);\n\t\t\t\tthis.queueCompactionMessage(text, queueAsFollowUp ? \"followUp\" : \"steer\", images);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (this.session.isStreaming || this.session.isRetrying) {\n\t\t\t\tconst images = this.takeClipboardImagesForText(text);\n\t\t\t\tthis.editor.addToHistory?.(text);\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\ttry {\n\t\t\t\t\tawait this.session.prompt(text, {\n\t\t\t\t\t\tstreamingBehavior: queueAsFollowUp ? \"followUp\" : \"steer\",\n\t\t\t\t\t\timages,\n\t\t\t\t\t\tprocessSlashCommands: false,\n\t\t\t\t\t});\n\t\t\t\t} finally {\n\t\t\t\t\tthis.refreshAutonomyFooterStatus();\n\t\t\t\t}\n\t\t\t\tthis.updatePendingMessagesDisplay();\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Handle commands\n\t\t\tif (text === \"/settings\") {\n\t\t\t\tthis.showSettingsSelector();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/auto-learn\" || text.startsWith(\"/auto-learn \")) {\n\t\t\t\tthis.handleAutoLearnCommand(text);\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/autonomy\" || text.startsWith(\"/autonomy \")) {\n\t\t\t\tthis.handleAutonomyCommand(text);\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/fitness\" || text.startsWith(\"/fitness \")) {\n\t\t\t\tconst fitnessArgs = text.slice(\"/fitness\".length).trim();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\tif (fitnessArgs.length === 0) {\n\t\t\t\t\t// No args: open the model picker, probe the selection, then offer role assignment.\n\t\t\t\t\tthis.showFitnessModelSelector();\n\t\t\t\t} else {\n\t\t\t\t\t// Explicit ref: same handler as /autonomy fitness.\n\t\t\t\t\tthis.handleAutonomyCommand(`/autonomy fitness ${fitnessArgs}`);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/models\" || text.startsWith(\"/models \")) {\n\t\t\t\tvoid this.handleModelsCommand(text.slice(\"/models\".length).trim());\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/context\") {\n\t\t\t\tthis.showStatus(this.session.formatContextCompositionDashboard());\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/toolhealth\") {\n\t\t\t\tthis.showStatus(this.session.formatToolRepairHealthReport());\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/toolprobe\" || text.startsWith(\"/toolprobe \")) {\n\t\t\t\tconst target = text.slice(\"/toolprobe\".length).trim();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\tthis.showStatus(\"Running tool probe...\");\n\t\t\t\ttry {\n\t\t\t\t\tconst report = await this.session.probeToolCalling(target || undefined);\n\t\t\t\t\tthis.showStatus(report.table);\n\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\tthis.showWarning(error instanceof Error ? error.message : String(error));\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text.startsWith(\"/toolrule-remove\")) {\n\t\t\t\tconst [model, mode] = text.slice(\"/toolrule-remove\".length).trim().split(/\\s+/, 2);\n\t\t\t\tconst message =\n\t\t\t\t\tmodel && mode\n\t\t\t\t\t\t? this.session.removeToolRepairRule(model, mode)\n\t\t\t\t\t\t\t? `Removed tool repair rule ${mode} for ${model}.`\n\t\t\t\t\t\t\t: `No tool repair rule ${mode} found for ${model}.`\n\t\t\t\t\t\t: \"Usage: /toolrule-remove <provider/model> <mode>\";\n\t\t\t\tthis.showStatus(message);\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text.startsWith(\"/toolprotocol-reset\")) {\n\t\t\t\tconst model = text.slice(\"/toolprotocol-reset\".length).trim();\n\t\t\t\tconst message = model\n\t\t\t\t\t? this.session.resetToolProtocolCalibration(model)\n\t\t\t\t\t\t? `Reset text tool protocol calibration for ${model}.`\n\t\t\t\t\t\t: `No text tool protocol calibration found for ${model}.`\n\t\t\t\t\t: \"Usage: /toolprotocol-reset <provider/model>\";\n\t\t\t\tthis.showStatus(message);\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/scoped-models\") {\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\tawait this.showModelsSelector();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/model\" || text.startsWith(\"/model \")) {\n\t\t\t\tconst searchTerm = text.startsWith(\"/model \") ? text.slice(7).trim() : undefined;\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\tawait this.handleModelCommand(searchTerm);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/profiles\" || text.startsWith(\"/profiles \")) {\n\t\t\t\tconst rawProfileName = text.startsWith(\"/profiles \") ? text.slice(10).trim() : undefined;\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\tawait this.handleProfilesCommand(rawProfileName?.length ? rawProfileName : undefined);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/export\" || text.startsWith(\"/export \")) {\n\t\t\t\tawait this.handleExportCommand(text);\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/import\" || text.startsWith(\"/import \")) {\n\t\t\t\tawait this.handleImportCommand(text);\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/share\") {\n\t\t\t\tawait this.handleShareCommand();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/copy\") {\n\t\t\t\tawait this.handleCopyCommand();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/name\" || text.startsWith(\"/name \")) {\n\t\t\t\tthis.handleNameCommand(text);\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/session\") {\n\t\t\t\tthis.handleSessionCommand();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/usage\" || text === \"/cost\") {\n\t\t\t\tthis.handleUsageCommand();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/goal\" || text.startsWith(\"/goal \")) {\n\t\t\t\tawait this.handleGoalCommand(text);\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/goal-continue\" || text.startsWith(\"/goal-continue \")) {\n\t\t\t\tawait this.handleGoalContinueCommand(text);\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/changelog\") {\n\t\t\t\tthis.handleChangelogCommand();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/hotkeys\") {\n\t\t\t\tthis.handleHotkeysCommand();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/fork\" || text.startsWith(\"/fork \")) {\n\t\t\t\tthis.showUserMessageSelector(text.slice(\"/fork\".length).trim() || undefined);\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/clone\" || text.startsWith(\"/clone \")) {\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\tawait this.handleCloneCommand(text.slice(\"/clone\".length).trim() || undefined);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/tree\") {\n\t\t\t\tthis.showTreeSelector();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/trust\") {\n\t\t\t\tthis.showTrustSelector();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/login\" || text.startsWith(\"/login \")) {\n\t\t\t\tawait this.authDialogs.showOAuthSelector(\"login\", text.slice(\"/login\".length).trim() || undefined);\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/logout\" || text.startsWith(\"/logout \")) {\n\t\t\t\tawait this.authDialogs.showOAuthSelector(\"logout\", text.slice(\"/logout\".length).trim() || undefined);\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/new\" || text.startsWith(\"/new \")) {\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\tawait this.handleClearCommand(text.slice(\"/new\".length).trim() || undefined);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/compact\" || text.startsWith(\"/compact \")) {\n\t\t\t\tconst customInstructions = text.startsWith(\"/compact \") ? text.slice(9).trim() : undefined;\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\tawait this.handleCompactCommand(customInstructions);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/reload\") {\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\tawait this.handleReloadCommand();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/curate\" || text.startsWith(\"/curate \")) {\n\t\t\t\tconst args = text.slice(\"/curate\".length).trim();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\tthis.handleCurateCommand(args);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/install-resources\" || text.startsWith(\"/install-resources \")) {\n\t\t\t\tconst args = text.slice(\"/install-resources\".length).trim();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\tawait this.handleInstallResourcesCommand(args);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/config-backup\" || text.startsWith(\"/config-backup \")) {\n\t\t\t\tconst file = text.slice(\"/config-backup\".length).trim() || undefined;\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\tawait this.handleConfigBackupCommand(file);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/config-restore\" || text.startsWith(\"/config-restore \")) {\n\t\t\t\tconst file = text.slice(\"/config-restore\".length).trim();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\tawait this.handleConfigRestoreCommand(file);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/debug\") {\n\t\t\t\tthis.handleDebugCommand();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/arminsayshi\") {\n\t\t\t\tthis.handleArminSaysHi();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/dementedelves\") {\n\t\t\t\tthis.handleDementedDelves();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/resume\") {\n\t\t\t\tthis.showSessionSelector();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Handle bash command (! for normal, !! for excluded from context)\n\t\t\tif (text.startsWith(\"!\")) {\n\t\t\t\tconst isExcluded = text.startsWith(\"!!\");\n\t\t\t\tconst command = isExcluded ? text.slice(2).trim() : text.slice(1).trim();\n\t\t\t\tif (command) {\n\t\t\t\t\tif (this.session.isBashRunning) {\n\t\t\t\t\t\tthis.showWarning(\"A bash command is already running. Press Esc to cancel it first.\");\n\t\t\t\t\t\tthis.editor.setText(text);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tthis.editor.addToHistory?.(text);\n\t\t\t\t\tawait this.handleBashCommand(command, isExcluded);\n\t\t\t\t\tthis.isBashMode = false;\n\t\t\t\t\tthis.updateEditorBorderColor();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Normal message submission\n\t\t\t// First, move any pending bash components to chat\n\t\t\tthis.flushPendingBashComponents();\n\n\t\t\tconst submission = this.buildUserInputSubmission(text);\n\t\t\tif (this.onInputCallback) {\n\t\t\t\tthis.onInputCallback(submission);\n\t\t\t} else {\n\t\t\t\tthis.pendingUserInputs.push(submission);\n\t\t\t}\n\t\t\tthis.editor.addToHistory?.(text);\n\t\t};\n\t}\n\n\tprivate subscribeToAgent(): void {\n\t\tthis.unsubscribe = this.session.subscribe(async (event) => {\n\t\t\tawait this.handleEvent(event);\n\t\t});\n\t}\n\n\tprivate async handleEvent(event: AgentSessionEvent): Promise<void> {\n\t\tif (!this.isInitialized) {\n\t\t\tawait this.init();\n\t\t}\n\n\t\tthis.footer.invalidate();\n\n\t\tswitch (event.type) {\n\t\t\t// Part B: general \"the system is processing\" feedback for the routing/prep gap before a\n\t\t\t// turn starts streaming (the judge is a real bounded LLM call, not instant) — independent\n\t\t\t// of thinking level, since this isn't model-thinking. Reuses the same working loader\n\t\t\t// agent_start below uses, so the hand-off into real streaming is the same\n\t\t\t// stop-then-recreate it already does — no distinct spinner, no double-render.\n\t\t\tcase \"routing_start\":\n\t\t\t\tif (!this.session.isStreaming && !this.loadingAnimation && this.workingVisible) {\n\t\t\t\t\tthis.loadingAnimation = this.createWorkingLoader();\n\t\t\t\t\tthis.statusContainer.addChild(this.loadingAnimation);\n\t\t\t\t\tthis.ui.requestRender();\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase \"routing_end\":\n\t\t\t\t// Unconditional: covers both a clean hand-off into agent_start (which immediately\n\t\t\t\t// stops-and-recreates its own loader anyway) and a turn that failed before ever\n\t\t\t\t// starting, which must not leave the indicator spinning forever.\n\t\t\t\tthis.stopWorkingLoader();\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\tbreak;\n\n\t\t\tcase \"agent_start\":\n\t\t\t\tthis.clearActiveToolCalls();\n\t\t\t\tif (this.settingsManager.getShowTerminalProgress()) {\n\t\t\t\t\tthis.ui.terminal.setProgress(true);\n\t\t\t\t}\n\t\t\t\t// Restore main escape handler if retry handler is still active\n\t\t\t\t// (retry success event fires later, but we need main handler now)\n\t\t\t\tif (this.retryEscapeHandler) {\n\t\t\t\t\tthis.defaultEditor.onEscape = this.retryEscapeHandler;\n\t\t\t\t\tthis.retryEscapeHandler = undefined;\n\t\t\t\t}\n\t\t\t\tif (this.retryCountdown) {\n\t\t\t\t\tthis.retryCountdown.dispose();\n\t\t\t\t\tthis.retryCountdown = undefined;\n\t\t\t\t}\n\t\t\t\tif (this.retryLoader) {\n\t\t\t\t\tthis.retryLoader.stop();\n\t\t\t\t\tthis.retryLoader = undefined;\n\t\t\t\t}\n\t\t\t\tthis.stopWorkingLoader();\n\t\t\t\tif (this.workingVisible) {\n\t\t\t\t\tthis.loadingAnimation = this.createWorkingLoader();\n\t\t\t\t\tthis.statusContainer.addChild(this.loadingAnimation);\n\t\t\t\t}\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\tbreak;\n\n\t\t\tcase \"queue_update\":\n\t\t\t\tthis.updatePendingMessagesDisplay();\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\tbreak;\n\n\t\t\tcase \"session_info_changed\":\n\t\t\t\tthis.updateTerminalTitle();\n\t\t\t\tthis.footer.invalidate();\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\tbreak;\n\n\t\t\tcase \"thinking_level_changed\":\n\t\t\t\tthis.footer.invalidate();\n\t\t\t\tthis.updateEditorBorderColor();\n\t\t\t\tbreak;\n\n\t\t\tcase \"warning\":\n\t\t\t\tthis.showWarning(event.message);\n\t\t\t\tbreak;\n\n\t\t\tcase \"delegate_workers\": {\n\t\t\t\tconst status =\n\t\t\t\t\tevent.active > 0\n\t\t\t\t\t\t? `${event.active} running`\n\t\t\t\t\t\t: event.failedSinceFlush > 0\n\t\t\t\t\t\t\t? `${event.failedSinceFlush} failed`\n\t\t\t\t\t\t\t: event.completedSinceFlush > 0\n\t\t\t\t\t\t\t\t? \"done\"\n\t\t\t\t\t\t\t\t: undefined;\n\t\t\t\tthis.footerDataProvider.setExtensionStatus(\"delegate\", status ? `delegate: ${status}` : undefined);\n\t\t\t\tthis.footer.invalidate();\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"message_start\":\n\t\t\t\tif (event.message.role === \"custom\") {\n\t\t\t\t\tthis.addMessageToChat(event.message);\n\t\t\t\t\tthis.ui.requestRender();\n\t\t\t\t} else if (event.message.role === \"user\") {\n\t\t\t\t\tthis.addMessageToChat(event.message);\n\t\t\t\t\tthis.updatePendingMessagesDisplay();\n\t\t\t\t\tthis.ui.requestRender();\n\t\t\t\t} else if (event.message.role === \"assistant\") {\n\t\t\t\t\tthis.clearPendingStreamingUiUpdate();\n\t\t\t\t\tthis.lastStreamingUiUpdateAt = 0;\n\t\t\t\t\tthis.streamingComponent = new AssistantMessageComponent(\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\tthis.hideThinkingBlock,\n\t\t\t\t\t\tthis.getMarkdownThemeWithSettings(),\n\t\t\t\t\t\tthis.hiddenThinkingLabel,\n\t\t\t\t\t);\n\t\t\t\t\tthis.streamingMessage = event.message;\n\t\t\t\t\tthis.chatContainer.addChild(this.streamingComponent);\n\t\t\t\t\tthis.applyStreamingMessageUpdate(this.streamingMessage, { force: true });\n\t\t\t\t\tthis.trimLiveTuiHistory();\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase \"message_update\":\n\t\t\t\tif (this.streamingComponent && event.message.role === \"assistant\") {\n\t\t\t\t\tthis.applyStreamingMessageUpdate(event.message);\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase \"message_end\":\n\t\t\t\tif (event.message.role === \"user\") break;\n\t\t\t\tif (this.streamingComponent && event.message.role === \"assistant\") {\n\t\t\t\t\tthis.streamingMessage = event.message;\n\t\t\t\t\tlet errorMessage: string | undefined;\n\t\t\t\t\tif (this.streamingMessage.stopReason === \"aborted\") {\n\t\t\t\t\t\tconst retryAttempt = this.session.retryAttempt;\n\t\t\t\t\t\terrorMessage =\n\t\t\t\t\t\t\tretryAttempt > 0\n\t\t\t\t\t\t\t\t? `Aborted after ${retryAttempt} retry attempt${retryAttempt > 1 ? \"s\" : \"\"}`\n\t\t\t\t\t\t\t\t: \"Operation aborted\";\n\t\t\t\t\t\tthis.streamingMessage.errorMessage = errorMessage;\n\t\t\t\t\t}\n\t\t\t\t\tthis.applyStreamingMessageUpdate(this.streamingMessage, { force: true });\n\n\t\t\t\t\tif (this.streamingMessage.stopReason === \"aborted\" || this.streamingMessage.stopReason === \"error\") {\n\t\t\t\t\t\tif (!errorMessage) {\n\t\t\t\t\t\t\terrorMessage = this.streamingMessage.errorMessage || \"Error\";\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor (const [, component] of this.toolPanels.activeEntries()) {\n\t\t\t\t\t\t\tcomponent.updateResult({\n\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: errorMessage }],\n\t\t\t\t\t\t\t\tisError: true,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.clearActiveToolCalls();\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Args are now complete - trigger diff computation for edit tools\n\t\t\t\t\t\tfor (const [, component] of this.toolPanels.activeEntries()) {\n\t\t\t\t\t\t\tcomponent.setArgsComplete();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tthis.streamingComponent = undefined;\n\t\t\t\t\tthis.streamingMessage = undefined;\n\t\t\t\t\tthis.footer.invalidate();\n\t\t\t\t}\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\tbreak;\n\n\t\t\tcase \"tool_execution_start\": {\n\t\t\t\tlet component = this.toolPanels.getActive(event.toolCallId);\n\t\t\t\tif (!component)\n\t\t\t\t\tcomponent = this.attachToolExecutionComponent(\n\t\t\t\t\t\tevent.toolName,\n\t\t\t\t\t\tevent.toolCallId,\n\t\t\t\t\t\tevent.args,\n\t\t\t\t\t\tevent.repair,\n\t\t\t\t\t);\n\t\t\t\telse component.updateArgs(event.args, event.repair);\n\t\t\t\tcomponent.markExecutionStarted(event.repair);\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"tool_execution_update\": {\n\t\t\t\tconst component = this.toolPanels.getActive(event.toolCallId);\n\t\t\t\tif (component) {\n\t\t\t\t\tcomponent.updateArgs(event.args, event.repair);\n\t\t\t\t\tcomponent.updateResult({ ...event.partialResult, isError: false }, true);\n\t\t\t\t\tthis.ui.requestRender();\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"tool_execution_end\": {\n\t\t\t\tconst component = this.toolPanels.getActive(event.toolCallId);\n\t\t\t\tif (component) {\n\t\t\t\t\tcomponent.updateResult({ ...event.result, isError: event.isError });\n\t\t\t\t\tthis.toolPanels.registerAliases(\n\t\t\t\t\t\tcomponent,\n\t\t\t\t\t\tgetToolPanelResultActionKeys(this.getToolPanelScope(), event.toolName, event.result),\n\t\t\t\t\t);\n\t\t\t\t\tthis.toolPanels.finish(event.toolCallId);\n\t\t\t\t\tthis.ui.requestRender();\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"agent_end\":\n\t\t\t\t// Native in-process reflection fully replaces the subprocess learning paths\n\t\t\t\t// (continuous-learning AND autonomy-review) when enabled; otherwise fall back to legacy.\n\t\t\t\tif (this.isNativeReflectionEnabled()) {\n\t\t\t\t\tthis.maybeRunNativeReflection(event.messages);\n\t\t\t\t} else if (!this.maybeStartAutoLearn()) {\n\t\t\t\t\tthis.maybeStartAutonomyReview(event.messages);\n\t\t\t\t}\n\t\t\t\tif (this.settingsManager.getShowTerminalProgress()) {\n\t\t\t\t\tthis.ui.terminal.setProgress(false);\n\t\t\t\t}\n\t\t\t\tif (this.loadingAnimation) {\n\t\t\t\t\tthis.loadingAnimation.stop();\n\t\t\t\t\tthis.loadingAnimation = undefined;\n\t\t\t\t\tthis.statusContainer.clear();\n\t\t\t\t}\n\t\t\t\tif (this.streamingComponent) {\n\t\t\t\t\tthis.chatContainer.removeChild(this.streamingComponent);\n\t\t\t\t\tthis.streamingComponent = undefined;\n\t\t\t\t\tthis.streamingMessage = undefined;\n\t\t\t\t}\n\t\t\t\tthis.clearActiveToolCalls();\n\n\t\t\t\tawait this.checkShutdownRequested();\n\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\tbreak;\n\n\t\t\tcase \"compaction_start\": {\n\t\t\t\tif (this.settingsManager.getShowTerminalProgress()) {\n\t\t\t\t\tthis.ui.terminal.setProgress(true);\n\t\t\t\t}\n\t\t\t\t// Keep editor active; submissions are queued during compaction.\n\t\t\t\tthis.autoCompactionEscapeHandler = this.defaultEditor.onEscape;\n\t\t\t\tthis.defaultEditor.onEscape = () => {\n\t\t\t\t\tthis.session.abortCompaction();\n\t\t\t\t};\n\t\t\t\tthis.statusContainer.clear();\n\t\t\t\tconst cancelHint = `(${keyText(\"app.interrupt\")} to cancel)`;\n\t\t\t\tconst label =\n\t\t\t\t\tevent.reason === \"manual\"\n\t\t\t\t\t\t? `Compacting context... ${cancelHint}`\n\t\t\t\t\t\t: `${event.reason === \"overflow\" ? \"Context overflow detected, \" : \"\"}Auto-compacting... ${cancelHint}`;\n\t\t\t\tthis.autoCompactionLoader = new Loader(\n\t\t\t\t\tthis.ui,\n\t\t\t\t\t(spinner) => theme.fg(\"accent\", spinner),\n\t\t\t\t\t(text) => theme.fg(\"muted\", text),\n\t\t\t\t\tlabel,\n\t\t\t\t);\n\t\t\t\tthis.statusContainer.addChild(this.autoCompactionLoader);\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"compaction_end\": {\n\t\t\t\tif (this.settingsManager.getShowTerminalProgress()) {\n\t\t\t\t\tthis.ui.terminal.setProgress(false);\n\t\t\t\t}\n\t\t\t\tif (this.autoCompactionEscapeHandler) {\n\t\t\t\t\tthis.defaultEditor.onEscape = this.autoCompactionEscapeHandler;\n\t\t\t\t\tthis.autoCompactionEscapeHandler = undefined;\n\t\t\t\t}\n\t\t\t\tif (this.autoCompactionLoader) {\n\t\t\t\t\tthis.autoCompactionLoader.stop();\n\t\t\t\t\tthis.autoCompactionLoader = undefined;\n\t\t\t\t\tthis.statusContainer.clear();\n\t\t\t\t}\n\t\t\t\tif (event.aborted) {\n\t\t\t\t\tif (event.reason === \"manual\") {\n\t\t\t\t\t\tthis.showError(\"Compaction cancelled\");\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.showStatus(\"Auto-compaction cancelled\");\n\t\t\t\t\t}\n\t\t\t\t} else if (event.result) {\n\t\t\t\t\tawait this.rebuildChatFromMessages();\n\t\t\t\t\tthis.addMessageToChat(\n\t\t\t\t\t\tcreateCompactionSummaryMessage(\n\t\t\t\t\t\t\tevent.result.summary,\n\t\t\t\t\t\t\tevent.result.tokensBefore,\n\t\t\t\t\t\t\tnew Date().toISOString(),\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tthis.footer.invalidate();\n\t\t\t\t} else if (event.errorMessage) {\n\t\t\t\t\tif (event.reason === \"manual\") {\n\t\t\t\t\t\tthis.showError(event.errorMessage);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t\t\t\t\tthis.chatContainer.addChild(new Text(theme.fg(\"error\", event.errorMessage), 1, 0));\n\t\t\t\t\t}\n\t\t\t\t} else if (event.skipReason) {\n\t\t\t\t\t// Benign auto-compaction no-op: still tell the user WHY nothing changed —\n\t\t\t\t\t// an invisible skip is indistinguishable from broken compaction.\n\t\t\t\t\tthis.showStatus(`Auto-compaction skipped: ${event.skipReason}`);\n\t\t\t\t}\n\t\t\t\tvoid this.flushCompactionQueue({ willRetry: event.willRetry });\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"auto_retry_start\": {\n\t\t\t\t// Set up escape to abort retry\n\t\t\t\tthis.retryEscapeHandler = this.defaultEditor.onEscape;\n\t\t\t\tthis.defaultEditor.onEscape = () => {\n\t\t\t\t\tthis.session.abortRetry();\n\t\t\t\t};\n\t\t\t\t// Show retry indicator\n\t\t\t\tthis.statusContainer.clear();\n\t\t\t\tthis.retryCountdown?.dispose();\n\t\t\t\tconst retryMessage = (seconds: number) =>\n\t\t\t\t\t`Retrying (${event.attempt}/${event.maxAttempts}) in ${seconds}s... (${keyText(\"app.interrupt\")} to cancel)`;\n\t\t\t\tthis.retryLoader = new Loader(\n\t\t\t\t\tthis.ui,\n\t\t\t\t\t(spinner) => theme.fg(\"warning\", spinner),\n\t\t\t\t\t(text) => theme.fg(\"muted\", text),\n\t\t\t\t\tretryMessage(Math.ceil(event.delayMs / 1000)),\n\t\t\t\t);\n\t\t\t\tthis.retryCountdown = new CountdownTimer(\n\t\t\t\t\tevent.delayMs,\n\t\t\t\t\tthis.ui,\n\t\t\t\t\t(seconds) => {\n\t\t\t\t\t\tthis.retryLoader?.setMessage(retryMessage(seconds));\n\t\t\t\t\t},\n\t\t\t\t\t() => {\n\t\t\t\t\t\tthis.retryCountdown = undefined;\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\tthis.statusContainer.addChild(this.retryLoader);\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"auto_retry_end\": {\n\t\t\t\t// Restore escape handler\n\t\t\t\tif (this.retryEscapeHandler) {\n\t\t\t\t\tthis.defaultEditor.onEscape = this.retryEscapeHandler;\n\t\t\t\t\tthis.retryEscapeHandler = undefined;\n\t\t\t\t}\n\t\t\t\tif (this.retryCountdown) {\n\t\t\t\t\tthis.retryCountdown.dispose();\n\t\t\t\t\tthis.retryCountdown = undefined;\n\t\t\t\t}\n\t\t\t\t// Stop loader\n\t\t\t\tif (this.retryLoader) {\n\t\t\t\t\tthis.retryLoader.stop();\n\t\t\t\t\tthis.retryLoader = undefined;\n\t\t\t\t\tthis.statusContainer.clear();\n\t\t\t\t}\n\t\t\t\t// Show error only on final failure (success shows normal response)\n\t\t\t\tif (!event.success) {\n\t\t\t\t\tthis.showError(`Retry failed after ${event.attempt} attempts: ${event.finalError || \"Unknown error\"}`);\n\t\t\t\t}\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Extract text content from a user message */\n\t// Thin `this.`-delegate to the pure formatter in ./history-reload-math.ts; kept so\n\t// addMessageToChat and the prototype-based interactive-mode tests resolve it via `this`.\n\tprivate getUserMessageText(message: Message): string {\n\t\treturn historyReloadMath.getUserMessageText(message);\n\t}\n\n\tprivate setEditorInputHistory(history: readonly string[]): void {\n\t\tif (this.editor.setHistory) {\n\t\t\tthis.editor.setHistory(history);\n\t\t\treturn;\n\t\t}\n\t\tfor (const text of history) {\n\t\t\tthis.editor.addToHistory?.(text);\n\t\t}\n\t}\n\n\tprivate populateEditorInputHistoryFromMessages(messages: readonly AgentMessage[]): void {\n\t\tconst history = messages\n\t\t\t.filter((message): message is Message => message.role === \"user\")\n\t\t\t.map((message) => this.getUserMessageText(message).trim())\n\t\t\t.filter((text) => text.length > 0)\n\t\t\t.slice(-100);\n\t\tthis.setEditorInputHistory(history);\n\t}\n\n\tprivate populateEditorInputHistoryFromSession(): void {\n\t\tconst sessionManager = this.sessionManager as { getRecentUserInputHistory?: (limit?: number) => string[] };\n\t\tconst history = sessionManager.getRecentUserInputHistory?.(100);\n\t\tif (history) this.setEditorInputHistory(history);\n\t}\n\n\tprivate resetLiveTuiHistoryTrim(): void {\n\t\tthis.liveHistoryHiddenNotice = undefined;\n\t\tthis.liveHistoryHiddenComponents = 0;\n\t}\n\n\tprivate clearPendingStreamingUiUpdate(): void {\n\t\tif (!this.streamingUiUpdateTimer) return;\n\t\tclearTimeout(this.streamingUiUpdateTimer);\n\t\tthis.streamingUiUpdateTimer = undefined;\n\t}\n\n\tprivate getSessionEntryCount(): number {\n\t\tconst manager = this.sessionManager as typeof this.sessionManager & { getEntryCount?: () => number };\n\t\treturn manager.getEntryCount?.() ?? manager.getEntries().length;\n\t}\n\n\tprivate showDeferredHistoryPlaceholder(options: { requestRender?: boolean } = {}): void {\n\t\tthis.chatContainer.children = [];\n\t\tthis.resetLiveTuiHistoryTrim();\n\t\tthis.clearRenderedToolPanelState();\n\n\t\tconst entryCount = this.getSessionEntryCount();\n\t\tif (entryCount > 0) {\n\t\t\tthis.chatContainer.addChild(\n\t\t\t\tnew Text(\n\t\t\t\t\ttheme.fg(\n\t\t\t\t\t\t\"dim\",\n\t\t\t\t\t\t`History hidden for typing performance (${entryCount} entries). Press ${keyText(\"app.tools.expand\")} to load session history on demand.`,\n\t\t\t\t\t),\n\t\t\t\t\t1,\n\t\t\t\t\t0,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\n\t\tif (options.requestRender ?? true) this.ui.requestRender();\n\t}\n\n\tprivate loadTuiHistoryOnDemand(): void {\n\t\tif (this.tuiHistoryLoadInProgress) return;\n\t\tif (this.tuiHistoryLoaded || this.getSessionEntryCount() === 0) {\n\t\t\tthis.toggleToolOutputExpansion();\n\t\t\treturn;\n\t\t}\n\n\t\tthis.tuiHistoryLoadInProgress = true;\n\t\tvoid (async () => {\n\t\t\ttry {\n\t\t\t\tawait this.renderInitialMessages({ forceHistoryLoad: true });\n\t\t\t} catch (error) {\n\t\t\t\tthis.showError(`Failed to load TUI history: ${error instanceof Error ? error.message : String(error)}`);\n\t\t\t} finally {\n\t\t\t\tthis.tuiHistoryLoadInProgress = false;\n\t\t\t}\n\t\t})();\n\t}\n\n\tprivate getToolCallRepairInfo(toolCall: ToolCall): ToolCallRepairInfo | undefined {\n\t\tif (!toolCall.rawArguments && !toolCall.repairNotes?.length) return undefined;\n\t\treturn {\n\t\t\trepaired: true,\n\t\t\t...(toolCall.rawArguments ? { rawArguments: toolCall.rawArguments } : {}),\n\t\t\t...(toolCall.repairNotes?.length ? { notes: toolCall.repairNotes } : {}),\n\t\t};\n\t}\n\n\tprivate attachStreamingToolPanels(message: AssistantMessage): void {\n\t\tfor (const content of message.content) {\n\t\t\tif (content.type !== \"toolCall\") continue;\n\t\t\tconst repair = this.getToolCallRepairInfo(content);\n\t\t\tif (!this.toolPanels.hasActive(content.id)) {\n\t\t\t\tthis.attachToolExecutionComponent(content.name, content.id, content.arguments, repair);\n\t\t\t} else {\n\t\t\t\tconst component = this.toolPanels.getActive(content.id);\n\t\t\t\tif (component) {\n\t\t\t\t\tcomponent.updateArgs(content.arguments, repair);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate applyStreamingMessageUpdate(message: AssistantMessage, options: { force?: boolean } = {}): void {\n\t\tthis.streamingMessage = message;\n\t\tif (!this.streamingComponent) return;\n\n\t\tconst now = performance.now();\n\t\tconst elapsed = now - this.lastStreamingUiUpdateAt;\n\t\tconst hasToolCall = message.content.some((content) => content.type === \"toolCall\");\n\t\tconst shouldUpdateNow = options.force || hasToolCall || elapsed >= STREAMING_UI_UPDATE_INTERVAL_MS;\n\n\t\tconst update = () => {\n\t\t\tif (!this.streamingComponent || !this.streamingMessage) return;\n\t\t\tthis.streamingComponent.updateContent(this.streamingMessage);\n\t\t\tthis.attachStreamingToolPanels(this.streamingMessage);\n\t\t\tthis.lastStreamingUiUpdateAt = performance.now();\n\t\t\tthis.ui.requestRender();\n\t\t};\n\n\t\tif (shouldUpdateNow) {\n\t\t\tthis.clearPendingStreamingUiUpdate();\n\t\t\tupdate();\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.streamingUiUpdateTimer) return;\n\t\tthis.streamingUiUpdateTimer = setTimeout(\n\t\t\t() => {\n\t\t\t\tthis.streamingUiUpdateTimer = undefined;\n\t\t\t\tupdate();\n\t\t\t},\n\t\t\tMath.max(0, STREAMING_UI_UPDATE_INTERVAL_MS - elapsed),\n\t\t);\n\t}\n\n\tprivate trimLiveTuiHistory(): void {\n\t\tconst children = this.chatContainer.children;\n\t\tif (children.length <= TUI_LIVE_HISTORY_MAX_COMPONENTS) return;\n\n\t\tlet protectedStart = children.length;\n\t\tconst protect = (component: Component | undefined) => {\n\t\t\tif (!component) return;\n\t\t\tconst index = children.indexOf(component);\n\t\t\tif (index !== -1 && index < protectedStart) protectedStart = index;\n\t\t};\n\n\t\tprotect(this.streamingComponent);\n\t\tprotect(this.lastStatusSpacer);\n\t\tprotect(this.lastStatusText);\n\t\tfor (const [, component] of this.toolPanels.activeEntries()) {\n\t\t\tprotect(component);\n\t\t}\n\n\t\tconst trimStart = children[0] === this.liveHistoryHiddenNotice ? 1 : 0;\n\t\tconst targetTrimEnd = children.length - TUI_LIVE_HISTORY_TRIM_TO_COMPONENTS;\n\t\tconst trimEnd = Math.min(targetTrimEnd, protectedStart);\n\t\tif (trimEnd <= trimStart) return;\n\n\t\tconst removed = children.splice(trimStart, trimEnd - trimStart);\n\t\tthis.liveHistoryHiddenComponents += removed.length;\n\t\tif (removed.includes(this.lastStatusSpacer as Component)) this.lastStatusSpacer = undefined;\n\t\tif (removed.includes(this.lastStatusText as Component)) this.lastStatusText = undefined;\n\n\t\tconst noticeText = theme.fg(\n\t\t\t\"dim\",\n\t\t\t`Older TUI history hidden to preserve FPS (${this.liveHistoryHiddenComponents} components). Full session remains available to the model.`,\n\t\t);\n\t\tif (children[0] === this.liveHistoryHiddenNotice) {\n\t\t\tthis.liveHistoryHiddenNotice?.setText(noticeText);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.liveHistoryHiddenNotice = new Text(noticeText, 1, 0);\n\t\tchildren.unshift(this.liveHistoryHiddenNotice);\n\t}\n\n\tprivate appendStatusToChat(message: string, options: { requestRender?: boolean } = {}): void {\n\t\tconst children = this.chatContainer.children;\n\t\tconst last = children.length > 0 ? children[children.length - 1] : undefined;\n\t\tconst secondLast = children.length > 1 ? children[children.length - 2] : undefined;\n\n\t\tif (last && secondLast && last === this.lastStatusText && secondLast === this.lastStatusSpacer) {\n\t\t\tthis.lastStatusText.setText(theme.fg(\"dim\", message));\n\t\t\tif (options.requestRender ?? true) this.ui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tconst spacer = new Spacer(1);\n\t\tconst text = new Text(theme.fg(\"dim\", message), 1, 0);\n\t\tthis.chatContainer.addChild(spacer);\n\t\tthis.chatContainer.addChild(text);\n\t\tthis.lastStatusSpacer = spacer;\n\t\tthis.lastStatusText = text;\n\t\tthis.trimLiveTuiHistory();\n\t\tif (options.requestRender ?? true) this.ui.requestRender();\n\t}\n\n\t/**\n\t * Show a status message in the chat.\n\t *\n\t * If multiple status messages are emitted back-to-back (without anything else being added to the chat),\n\t * we update the previous status line instead of appending new ones to avoid log spam.\n\t */\n\tprivate showStatus(message: string): void {\n\t\tthis.appendStatusToChat(message);\n\t}\n\n\tprivate addMessageToChat(message: AgentMessage, options?: { populateHistory?: boolean }): void {\n\t\tswitch (message.role) {\n\t\t\tcase \"bashExecution\": {\n\t\t\t\tconst component = new BashExecutionComponent(message.command, this.ui, message.excludeFromContext);\n\t\t\t\tif (message.output) {\n\t\t\t\t\tcomponent.appendOutput(message.output);\n\t\t\t\t}\n\t\t\t\tcomponent.setComplete(\n\t\t\t\t\tmessage.exitCode,\n\t\t\t\t\tmessage.cancelled,\n\t\t\t\t\tmessage.truncated ? ({ truncated: true } as TruncationResult) : undefined,\n\t\t\t\t\tmessage.fullOutputPath,\n\t\t\t\t);\n\t\t\t\tthis.chatContainer.addChild(component);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"custom\": {\n\t\t\t\tif (message.display) {\n\t\t\t\t\tconst renderer = this.session.extensionRunner.getMessageRenderer(message.customType);\n\t\t\t\t\tconst component = new CustomMessageComponent(message, renderer, this.getMarkdownThemeWithSettings());\n\t\t\t\t\tcomponent.setExpanded(this.toolOutputExpanded);\n\t\t\t\t\tthis.chatContainer.addChild(component);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"compactionSummary\": {\n\t\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t\t\tconst component = new CompactionSummaryMessageComponent(message, this.getMarkdownThemeWithSettings());\n\t\t\t\tcomponent.setExpanded(this.toolOutputExpanded);\n\t\t\t\tthis.chatContainer.addChild(component);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"branchSummary\": {\n\t\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t\t\tconst component = new BranchSummaryMessageComponent(message, this.getMarkdownThemeWithSettings());\n\t\t\t\tcomponent.setExpanded(this.toolOutputExpanded);\n\t\t\t\tthis.chatContainer.addChild(component);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"user\": {\n\t\t\t\tconst textContent = this.getUserMessageText(message);\n\t\t\t\tif (textContent) {\n\t\t\t\t\tif (this.chatContainer.children.length > 0) {\n\t\t\t\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t\t\t\t}\n\t\t\t\t\tconst skillBlock = parseSkillBlock(textContent);\n\t\t\t\t\tif (skillBlock) {\n\t\t\t\t\t\t// Render skill block (collapsible)\n\t\t\t\t\t\tconst component = new SkillInvocationMessageComponent(\n\t\t\t\t\t\t\tskillBlock,\n\t\t\t\t\t\t\tthis.getMarkdownThemeWithSettings(),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tcomponent.setExpanded(this.toolOutputExpanded);\n\t\t\t\t\t\tthis.chatContainer.addChild(component);\n\t\t\t\t\t\t// Render user message separately if present\n\t\t\t\t\t\tif (skillBlock.userMessage) {\n\t\t\t\t\t\t\tconst userComponent = new UserMessageComponent(\n\t\t\t\t\t\t\t\tskillBlock.userMessage,\n\t\t\t\t\t\t\t\tthis.getMarkdownThemeWithSettings(),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tthis.chatContainer.addChild(userComponent);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst userComponent = new UserMessageComponent(textContent, this.getMarkdownThemeWithSettings());\n\t\t\t\t\t\tthis.chatContainer.addChild(userComponent);\n\t\t\t\t\t}\n\t\t\t\t\tif (options?.populateHistory) {\n\t\t\t\t\t\tthis.editor.addToHistory?.(textContent);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"assistant\": {\n\t\t\t\tconst assistantComponent = new AssistantMessageComponent(\n\t\t\t\t\tmessage,\n\t\t\t\t\tthis.hideThinkingBlock,\n\t\t\t\t\tthis.getMarkdownThemeWithSettings(),\n\t\t\t\t\tthis.hiddenThinkingLabel,\n\t\t\t\t);\n\t\t\t\tthis.chatContainer.addChild(assistantComponent);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"toolResult\": {\n\t\t\t\t// Tool results are rendered inline with tool calls, handled separately\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault: {\n\t\t\t\tconst _exhaustive: never = message;\n\t\t\t}\n\t\t}\n\t\tthis.trimLiveTuiHistory();\n\t}\n\n\t// Thin `this.`-delegate to the pure reload-window math in ./history-reload-math.ts;\n\t// kept so renderSessionContext and the prototype-based history tests resolve it via `this`.\n\tprivate messagesForTuiHistoryReload(messages: AgentMessage[]): {\n\t\tmessages: AgentMessage[];\n\t\tomittedMessages: number;\n\t\testimatedLines: number;\n\t} {\n\t\treturn historyReloadMath.messagesForTuiHistoryReload(messages);\n\t}\n\n\t/**\n\t * Render session context to chat. Used for initial load and rebuild after compaction.\n\t * @param sessionContext Session context to render\n\t * @param options.updateFooter Update footer state\n\t * @param options.populateHistory Add user messages to editor history\n\t */\n\tprivate renderGeneration = 0;\n\tprivate renderQueue?: Promise<void>;\n\n\tprivate async renderSessionContext(\n\t\tsessionContext: SessionContext,\n\t\toptions: { updateFooter?: boolean; populateHistory?: boolean } = {},\n\t): Promise<void> {\n\t\tconst run = async () => {\n\t\t\t// Build long history offscreen, then atomically swap it into the visible\n\t\t\t// chat container. This keeps the TUI responsive without flashing blank or\n\t\t\t// partial transcript frames during resume/reload/compaction rebuilds.\n\t\t\tthis.renderGeneration = (this.renderGeneration ?? 0) + 1;\n\t\t\tconst generation = this.renderGeneration;\n\t\t\tlet processed = 0;\n\t\t\tlet committed = false;\n\n\t\t\tconst visibleChatContainer = this.chatContainer;\n\t\t\tconst previousLiveHistoryHiddenNotice = this.liveHistoryHiddenNotice;\n\t\t\tconst previousLiveHistoryHiddenComponents = this.liveHistoryHiddenComponents;\n\t\t\tconst previousLastStatusSpacer = this.lastStatusSpacer;\n\t\t\tconst previousLastStatusText = this.lastStatusText;\n\t\t\tconst stagingChatContainer = new Container();\n\n\t\t\tthis.chatContainer = stagingChatContainer;\n\t\t\tthis.resetLiveTuiHistoryTrim();\n\t\t\tthis.clearRenderedToolPanelState();\n\t\t\tconst renderedPendingTools = new Map<string, ToolExecutionComponent>();\n\n\t\t\ttry {\n\t\t\t\tif (options.updateFooter) {\n\t\t\t\t\tthis.footer.invalidate();\n\t\t\t\t\tthis.updateEditorBorderColor();\n\t\t\t\t}\n\n\t\t\t\tconst tuiHistory = this.messagesForTuiHistoryReload(sessionContext.messages);\n\t\t\t\tif (tuiHistory.omittedMessages > 0) {\n\t\t\t\t\tthis.appendStatusToChat(\n\t\t\t\t\t\t`Showing last ~${historyReloadMath.TUI_HISTORY_RELOAD_MAX_LINES} TUI history lines; omitted ${tuiHistory.omittedMessages} older message${tuiHistory.omittedMessages === 1 ? \"\" : \"s\"}. Full session remains available to the model.`,\n\t\t\t\t\t\t{ requestRender: false },\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tfor (const message of tuiHistory.messages) {\n\t\t\t\t\tif (processed > 0 && processed % TUI_HISTORY_RELOAD_CHUNK_SIZE === 0) {\n\t\t\t\t\t\tawait new Promise((resolve) => setImmediate(resolve));\n\t\t\t\t\t\tif (generation !== this.renderGeneration) return;\n\t\t\t\t\t}\n\t\t\t\t\tprocessed++;\n\t\t\t\t\t// Assistant messages need special handling for tool calls\n\t\t\t\t\tif (message.role === \"assistant\") {\n\t\t\t\t\t\tthis.addMessageToChat(message);\n\t\t\t\t\t\t// Render tool call components\n\t\t\t\t\t\tfor (const content of message.content) {\n\t\t\t\t\t\t\tif (content.type === \"toolCall\") {\n\t\t\t\t\t\t\t\tconst component = this.attachToolExecutionComponent(\n\t\t\t\t\t\t\t\t\tcontent.name,\n\t\t\t\t\t\t\t\t\tcontent.id,\n\t\t\t\t\t\t\t\t\tcontent.arguments,\n\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\tif (message.stopReason === \"aborted\" || message.stopReason === \"error\") {\n\t\t\t\t\t\t\t\t\tlet errorMessage: string;\n\t\t\t\t\t\t\t\t\tif (message.stopReason === \"aborted\") {\n\t\t\t\t\t\t\t\t\t\tconst retryAttempt = this.session.retryAttempt;\n\t\t\t\t\t\t\t\t\t\terrorMessage =\n\t\t\t\t\t\t\t\t\t\t\tretryAttempt > 0\n\t\t\t\t\t\t\t\t\t\t\t\t? `Aborted after ${retryAttempt} retry attempt${retryAttempt > 1 ? \"s\" : \"\"}`\n\t\t\t\t\t\t\t\t\t\t\t\t: \"Operation aborted\";\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\terrorMessage = message.errorMessage || \"Error\";\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tcomponent.updateResult({ content: [{ type: \"text\", text: errorMessage }], isError: true });\n\t\t\t\t\t\t\t\t\tthis.toolPanels.finish(content.id);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\trenderedPendingTools.set(content.id, component);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (message.role === \"toolResult\") {\n\t\t\t\t\t\t// Match tool results to pending tool components\n\t\t\t\t\t\tconst component = renderedPendingTools.get(message.toolCallId);\n\t\t\t\t\t\tif (component) {\n\t\t\t\t\t\t\tcomponent.updateResult(message);\n\t\t\t\t\t\t\trenderedPendingTools.delete(message.toolCallId);\n\t\t\t\t\t\t\tthis.toolPanels.finish(message.toolCallId);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// All other messages use standard rendering\n\t\t\t\t\t\tthis.addMessageToChat(message, options);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (generation !== this.renderGeneration) return;\n\t\t\t\tvisibleChatContainer.children = stagingChatContainer.children;\n\t\t\t\tcommitted = true;\n\t\t\t} finally {\n\t\t\t\tconst stagedLiveHistoryHiddenNotice = this.liveHistoryHiddenNotice;\n\t\t\t\tconst stagedLiveHistoryHiddenComponents = this.liveHistoryHiddenComponents;\n\t\t\t\tconst stagedLastStatusSpacer = this.lastStatusSpacer;\n\t\t\t\tconst stagedLastStatusText = this.lastStatusText;\n\n\t\t\t\tthis.chatContainer = visibleChatContainer;\n\t\t\t\tif (committed) {\n\t\t\t\t\tthis.liveHistoryHiddenNotice = stagedLiveHistoryHiddenNotice;\n\t\t\t\t\tthis.liveHistoryHiddenComponents = stagedLiveHistoryHiddenComponents;\n\t\t\t\t\tthis.lastStatusSpacer = stagedLastStatusSpacer;\n\t\t\t\t\tthis.lastStatusText = stagedLastStatusText;\n\t\t\t\t} else {\n\t\t\t\t\tthis.liveHistoryHiddenNotice = previousLiveHistoryHiddenNotice;\n\t\t\t\t\tthis.liveHistoryHiddenComponents = previousLiveHistoryHiddenComponents;\n\t\t\t\t\tthis.lastStatusSpacer = previousLastStatusSpacer;\n\t\t\t\t\tthis.lastStatusText = previousLastStatusText;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (committed) this.ui.requestRender();\n\t\t};\n\n\t\tthis.renderQueue = (this.renderQueue ?? Promise.resolve()).then(run, run);\n\t\treturn this.renderQueue;\n\t}\n\n\tasync renderInitialMessages(options: { forceHistoryLoad?: boolean } = {}): Promise<void> {\n\t\tif (!options.forceHistoryLoad) {\n\t\t\tthis.populateEditorInputHistoryFromSession();\n\t\t\tthis.tuiHistoryLoaded = false;\n\t\t\tthis.showDeferredHistoryPlaceholder({ requestRender: true });\n\t\t\tthis.footer.invalidate();\n\t\t\tthis.updateEditorBorderColor();\n\t\t\treturn;\n\t\t}\n\n\t\t// Get aligned messages and entries from session context only when the user\n\t\t// explicitly requests TUI history. The model/session state is already loaded.\n\t\tconst context = this.sessionManager.buildSessionContext();\n\t\tthis.populateEditorInputHistoryFromMessages(context.messages);\n\t\tawait this.renderSessionContext(context, {\n\t\t\tupdateFooter: true,\n\t\t});\n\t\tthis.tuiHistoryLoaded = true;\n\n\t\t// Show compaction info if session was compacted\n\t\tconst allEntries = this.sessionManager.getEntries();\n\t\tconst compactionCount = allEntries.filter((e) => e.type === \"compaction\").length;\n\t\tif (compactionCount > 0) {\n\t\t\tconst times = compactionCount === 1 ? \"1 time\" : `${compactionCount} times`;\n\t\t\tthis.showStatus(`Session compacted ${times}`);\n\t\t}\n\t}\n\n\tasync getUserInput(): Promise<UserInputSubmission> {\n\t\tconst queuedInput = this.pendingUserInputs.shift();\n\t\tif (queuedInput !== undefined) {\n\t\t\treturn queuedInput;\n\t\t}\n\n\t\treturn new Promise((resolve) => {\n\t\t\tthis.onInputCallback = (submission: UserInputSubmission) => {\n\t\t\t\tthis.onInputCallback = undefined;\n\t\t\t\tresolve(submission);\n\t\t\t};\n\t\t});\n\t}\n\n\tprivate async rebuildChatFromMessages(): Promise<void> {\n\t\tif (!this.tuiHistoryLoaded) {\n\t\t\tthis.showDeferredHistoryPlaceholder({ requestRender: true });\n\t\t\treturn;\n\t\t}\n\t\tconst context = this.sessionManager.buildSessionContext();\n\t\tawait this.renderSessionContext(context);\n\t}\n\n\t// =========================================================================\n\t// Key handlers\n\t// =========================================================================\n\n\tprivate handleCtrlC(): void {\n\t\tconst now = Date.now();\n\t\tif (now - this.lastSigintTime < 500) {\n\t\t\tvoid this.shutdown();\n\t\t} else {\n\t\t\tthis.clearEditor();\n\t\t\tthis.lastSigintTime = now;\n\t\t}\n\t}\n\n\tprivate handleCtrlD(): void {\n\t\t// Only called when editor is empty (enforced by CustomEditor)\n\t\tvoid this.shutdown();\n\t}\n\n\t/**\n\t * Gracefully shutdown the agent.\n\t * Stops the TUI before emitting shutdown events so extension UI cleanup cannot\n\t * repaint the final frame while the process is exiting.\n\t */\n\tprivate isShuttingDown = false;\n\n\tprivate shutdown(options?: { fromSignal?: boolean }): Promise<void> {\n\t\tconst self = this;\n\t\treturn signalLifecycle.shutdown(\n\t\t\t{\n\t\t\t\tget isShuttingDown() {\n\t\t\t\t\treturn self.isShuttingDown;\n\t\t\t\t},\n\t\t\t\tset isShuttingDown(value) {\n\t\t\t\t\tself.isShuttingDown = value;\n\t\t\t\t},\n\t\t\t\tget signalCleanupHandlers() {\n\t\t\t\t\treturn self.signalCleanupHandlers;\n\t\t\t\t},\n\t\t\t\tset signalCleanupHandlers(value) {\n\t\t\t\t\tself.signalCleanupHandlers = value;\n\t\t\t\t},\n\t\t\t\tget shutdownRequested() {\n\t\t\t\t\treturn self.shutdownRequested;\n\t\t\t\t},\n\t\t\t\truntimeHost: this.runtimeHost,\n\t\t\t\tui: this.ui,\n\t\t\t\tstop: () => this.stop(),\n\t\t\t\tformatResumeCommand: () => formatResumeCommand(this.sessionManager),\n\t\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\t\tshutdown: (opts) => this.shutdown(opts),\n\t\t\t\tunregisterSignalHandlers: () => this.unregisterSignalHandlers(),\n\t\t\t\temergencyTerminalExit: () => this.emergencyTerminalExit(),\n\t\t\t\tuncaughtCrash: (error) => this.uncaughtCrash(error),\n\t\t\t},\n\t\t\toptions,\n\t\t);\n\t}\n\n\tprivate emergencyTerminalExit(): never {\n\t\treturn signalLifecycle.emergencyTerminalExit(this as unknown as signalLifecycle.SignalLifecycleHost);\n\t}\n\n\tprivate uncaughtCrash(error: Error): never {\n\t\treturn signalLifecycle.uncaughtCrash(this as unknown as signalLifecycle.SignalLifecycleHost, error);\n\t}\n\n\tprivate checkShutdownRequested(): Promise<void> {\n\t\treturn signalLifecycle.checkShutdownRequested(this as unknown as signalLifecycle.SignalLifecycleHost);\n\t}\n\n\tprivate registerSignalHandlers(): void {\n\t\tsignalLifecycle.registerSignalHandlers(this as unknown as signalLifecycle.SignalLifecycleHost);\n\t}\n\n\tprivate unregisterSignalHandlers(): void {\n\t\tsignalLifecycle.unregisterSignalHandlers(this as unknown as signalLifecycle.SignalLifecycleHost);\n\t}\n\n\tprivate handleCtrlZ(): void {\n\t\tsignalLifecycle.handleCtrlZ({ ui: this.ui, showStatus: (message) => this.showStatus(message) });\n\t}\n\n\tprivate async handleFollowUp(): Promise<void> {\n\t\tconst text = (this.editor.getExpandedText?.() ?? this.editor.getText()).trim();\n\t\tif (!text) return;\n\n\t\t// Queue input during compaction (extension commands execute immediately)\n\t\tif (this.session.isCompacting) {\n\t\t\tif (this.isExtensionCommand(text)) {\n\t\t\t\tthis.editor.addToHistory?.(text);\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\ttry {\n\t\t\t\t\tawait this.session.prompt(text);\n\t\t\t\t} finally {\n\t\t\t\t\tthis.refreshAutonomyFooterStatus();\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconst images = this.takeClipboardImagesForText(text);\n\t\t\t\tthis.queueCompactionMessage(text, \"followUp\", images);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Alt+Enter queues a follow-up message (waits until agent finishes)\n\t\t// This handles extension commands (execute immediately), prompt template expansion, and queueing\n\t\tif (this.session.isStreaming) {\n\t\t\tconst images = this.takeClipboardImagesForText(text);\n\t\t\tthis.editor.addToHistory?.(text);\n\t\t\tthis.editor.setText(\"\");\n\t\t\ttry {\n\t\t\t\tawait this.session.prompt(text, { streamingBehavior: \"followUp\", images });\n\t\t\t} finally {\n\t\t\t\tthis.refreshAutonomyFooterStatus();\n\t\t\t}\n\t\t\tthis.updatePendingMessagesDisplay();\n\t\t\tthis.ui.requestRender();\n\t\t}\n\t\t// If not streaming, Alt+Enter acts like regular Enter (trigger onSubmit)\n\t\telse if (this.editor.onSubmit) {\n\t\t\tawait this.editor.onSubmit(text);\n\t\t\tthis.editor.setText(\"\");\n\t\t}\n\t}\n\n\tprivate handleDequeue(): void {\n\t\tconst restored = this.restoreQueuedMessagesToEditor();\n\t\tif (restored === 0) {\n\t\t\tthis.showStatus(\"No queued messages to restore\");\n\t\t} else {\n\t\t\tthis.showStatus(`Restored ${restored} queued message${restored > 1 ? \"s\" : \"\"} to editor`);\n\t\t}\n\t}\n\n\tprivate updateEditorBorderColor(): void {\n\t\tif (this.isBashMode) {\n\t\t\tthis.editor.borderColor = theme.getBashModeBorderColor();\n\t\t} else {\n\t\t\tconst level = this.session.thinkingLevel || \"off\";\n\t\t\tthis.editor.borderColor = theme.getThinkingBorderColor(level);\n\t\t}\n\t\tthis.ui.requestRender();\n\t}\n\n\tprivate cycleThinkingLevel(): void {\n\t\tconst newLevel = this.session.cycleThinkingLevel();\n\t\tif (newLevel === undefined) {\n\t\t\tthis.showStatus(\"Current model does not support thinking\");\n\t\t} else {\n\t\t\tthis.footer.invalidate();\n\t\t\tthis.updateEditorBorderColor();\n\t\t\tthis.showStatus(`Thinking level: ${newLevel}`);\n\t\t}\n\t}\n\n\tprivate async cycleModel(direction: \"forward\" | \"backward\"): Promise<void> {\n\t\ttry {\n\t\t\tconst result = await this.session.cycleModel(direction);\n\t\t\tif (result === undefined) {\n\t\t\t\tconst msg = this.session.scopedModels.length > 0 ? \"Only one model in scope\" : \"Only one model available\";\n\t\t\t\tthis.showStatus(msg);\n\t\t\t} else {\n\t\t\t\tthis.footer.invalidate();\n\t\t\t\tthis.updateEditorBorderColor();\n\t\t\t\tconst thinkingStr =\n\t\t\t\t\tresult.model.reasoning && result.thinkingLevel !== \"off\" ? ` (thinking: ${result.thinkingLevel})` : \"\";\n\t\t\t\tthis.showStatus(`Switched to ${result.model.name || result.model.id}${thinkingStr}`);\n\t\t\t\tvoid this.maybeWarnAboutAnthropicSubscriptionAuth(result.model);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tthis.showError(error instanceof Error ? error.message : String(error));\n\t\t}\n\t}\n\n\tprivate toggleToolOutputExpansion(): void {\n\t\tthis.setToolsExpanded(!this.toolOutputExpanded);\n\t}\n\n\tprivate setToolsExpanded(expanded: boolean): void {\n\t\tthis.toolOutputExpanded = expanded;\n\t\tconst activeHeader = this.extensionUiHost.getCustomHeader() ?? this.builtInHeader;\n\t\tif (isExpandable(activeHeader)) {\n\t\t\tactiveHeader.setExpanded(expanded);\n\t\t}\n\t\tfor (const child of this.chatContainer.children) {\n\t\t\tif (isExpandable(child)) {\n\t\t\t\tchild.setExpanded(expanded);\n\t\t\t}\n\t\t}\n\t\tthis.ui.requestRender();\n\t}\n\n\tprivate async toggleThinkingBlockVisibility(): Promise<void> {\n\t\tthis.hideThinkingBlock = !this.hideThinkingBlock;\n\t\tthis.settingsManager.setHideThinkingBlock(this.hideThinkingBlock);\n\n\t\t// Rebuild chat from session messages\n\t\tawait this.rebuildChatFromMessages();\n\n\t\t// If streaming, re-add the streaming component with updated visibility and re-render\n\t\tif (this.streamingComponent && this.streamingMessage) {\n\t\t\tthis.streamingComponent.setHideThinkingBlock(this.hideThinkingBlock);\n\t\t\tthis.streamingComponent.updateContent(this.streamingMessage);\n\t\t\tthis.chatContainer.addChild(this.streamingComponent);\n\t\t}\n\n\t\tthis.showStatus(`Thinking blocks: ${this.hideThinkingBlock ? \"hidden\" : \"visible\"}`);\n\t}\n\n\tprivate openExternalEditor(): Promise<void> {\n\t\treturn openExternalEditor({\n\t\t\teditor: this.editor,\n\t\t\tui: this.ui,\n\t\t\tshowWarning: (message) => this.showWarning(message),\n\t\t});\n\t}\n\n\tprivate openEditorForPath(filePath: string): Promise<boolean> {\n\t\treturn openEditorForPath(\n\t\t\t{\n\t\t\t\teditor: this.editor,\n\t\t\t\tui: this.ui,\n\t\t\t\tshowWarning: (message) => this.showWarning(message),\n\t\t\t},\n\t\t\tfilePath,\n\t\t);\n\t}\n\n\t// =========================================================================\n\t// UI helpers\n\t// =========================================================================\n\n\tclearEditor(): void {\n\t\tthis.editor.setText(\"\");\n\t\tthis.ui.requestRender();\n\t}\n\n\tshowError(errorMessage: string): void {\n\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\tthis.chatContainer.addChild(new Text(theme.fg(\"error\", `Error: ${errorMessage}`), 1, 0));\n\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\tthis.ui.requestRender();\n\t}\n\n\tshowWarning(warningMessage: string): void {\n\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\tthis.chatContainer.addChild(new Text(theme.fg(\"warning\", `Warning: ${warningMessage}`), 1, 0));\n\t\tthis.ui.requestRender();\n\t}\n\n\tshowNewVersionNotification(release: LatestPiRelease): void {\n\t\tstartupChecks.showNewVersionNotification(this.startupChecksHost(), release);\n\t}\n\n\tshowPackageUpdateNotification(packages: string[]): void {\n\t\tstartupChecks.showPackageUpdateNotification(this.startupChecksHost(), packages);\n\t}\n\n\t/**\n\t * Get all queued messages (read-only).\n\t * Combines session queue and compaction queue.\n\t */\n\tprivate getAllQueuedMessages(): { steering: string[]; followUp: string[] } {\n\t\treturn {\n\t\t\tsteering: [\n\t\t\t\t...this.session.getSteeringMessages(),\n\t\t\t\t...this.compactionQueuedMessages.filter((msg) => msg.mode === \"steer\").map((msg) => msg.text),\n\t\t\t],\n\t\t\tfollowUp: [\n\t\t\t\t...this.session.getFollowUpMessages(),\n\t\t\t\t...this.session.getQueuedExtensionCommands(),\n\t\t\t\t...this.compactionQueuedMessages.filter((msg) => msg.mode === \"followUp\").map((msg) => msg.text),\n\t\t\t],\n\t\t};\n\t}\n\n\t/**\n\t * Clear all queued messages and return their contents.\n\t * Clears both session queue and compaction queue.\n\t */\n\tprivate clearAllQueues(): { steering: string[]; followUp: string[] } {\n\t\tconst { steering, followUp, commands } = this.session.clearQueue();\n\t\tconst compactionSteering = this.compactionQueuedMessages\n\t\t\t.filter((msg) => msg.mode === \"steer\")\n\t\t\t.map((msg) => msg.text);\n\t\tconst compactionFollowUp = this.compactionQueuedMessages\n\t\t\t.filter((msg) => msg.mode === \"followUp\")\n\t\t\t.map((msg) => msg.text);\n\t\tthis.compactionQueuedMessages = [];\n\t\treturn {\n\t\t\tsteering: [...steering, ...compactionSteering],\n\t\t\tfollowUp: [...followUp, ...commands, ...compactionFollowUp],\n\t\t};\n\t}\n\n\tprivate updatePendingMessagesDisplay(): void {\n\t\tthis.pendingMessagesContainer.clear();\n\t\tconst { steering: steeringMessages, followUp: followUpMessages } = this.getAllQueuedMessages();\n\t\tif (steeringMessages.length > 0 || followUpMessages.length > 0) {\n\t\t\tthis.pendingMessagesContainer.addChild(new Spacer(1));\n\t\t\tfor (const message of steeringMessages) {\n\t\t\t\tconst text = theme.fg(\"dim\", `Steering: ${message}`);\n\t\t\t\tthis.pendingMessagesContainer.addChild(new TruncatedText(text, 1, 0));\n\t\t\t}\n\t\t\tfor (const message of followUpMessages) {\n\t\t\t\tconst text = theme.fg(\"dim\", `Follow-up: ${message}`);\n\t\t\t\tthis.pendingMessagesContainer.addChild(new TruncatedText(text, 1, 0));\n\t\t\t}\n\t\t\tconst dequeueHint = this.getAppKeyDisplay(\"app.message.dequeue\");\n\t\t\tconst hintText = theme.fg(\"dim\", `↳ ${dequeueHint} to edit all queued messages`);\n\t\t\tthis.pendingMessagesContainer.addChild(new TruncatedText(hintText, 1, 0));\n\t\t}\n\t}\n\n\tprivate restoreQueuedMessagesToEditor(options?: { abort?: boolean; currentText?: string }): number {\n\t\tconst { steering, followUp } = this.clearAllQueues();\n\t\tconst allQueued = [...steering, ...followUp];\n\t\tif (allQueued.length === 0) {\n\t\t\tthis.updatePendingMessagesDisplay();\n\t\t\tif (options?.abort) {\n\t\t\t\tthis.agent.abort();\n\t\t\t}\n\t\t\treturn 0;\n\t\t}\n\t\tconst queuedText = allQueued.join(\"\\n\\n\");\n\t\tconst currentText = options?.currentText ?? this.editor.getText();\n\t\tconst combinedText = [queuedText, currentText].filter((t) => t.trim()).join(\"\\n\\n\");\n\t\tthis.editor.setText(combinedText);\n\t\tthis.updatePendingMessagesDisplay();\n\t\tif (options?.abort) {\n\t\t\tthis.agent.abort();\n\t\t}\n\t\treturn allQueued.length;\n\t}\n\n\tprivate queueCompactionMessage(text: string, mode: \"steer\" | \"followUp\", images?: ImageContent[]): void {\n\t\tthis.compactionQueuedMessages.push({ text, mode, images });\n\t\tthis.editor.addToHistory?.(text);\n\t\tthis.editor.setText(\"\");\n\t\tthis.updatePendingMessagesDisplay();\n\t\tthis.showStatus(\"Queued message for after compaction\");\n\t}\n\n\tprivate isExtensionCommand(text: string): boolean {\n\t\tif (!text.startsWith(\"/\")) return false;\n\n\t\tconst extensionRunner = this.session.extensionRunner;\n\n\t\tconst spaceIndex = text.indexOf(\" \");\n\t\tconst commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);\n\t\treturn !!extensionRunner.getCommand(commandName);\n\t}\n\n\tprivate flushCompactionQueue(options?: { willRetry?: boolean }): Promise<void> {\n\t\tconst self = this;\n\t\treturn compactionQueue.flushCompactionQueue(\n\t\t\t{\n\t\t\t\tget compactionQueuedMessages() {\n\t\t\t\t\treturn self.compactionQueuedMessages;\n\t\t\t\t},\n\t\t\t\tset compactionQueuedMessages(value) {\n\t\t\t\t\tself.compactionQueuedMessages = value;\n\t\t\t\t},\n\t\t\t\tsession: this.session,\n\t\t\t\tupdatePendingMessagesDisplay: () => this.updatePendingMessagesDisplay(),\n\t\t\t\tshowError: (message) => this.showError(message),\n\t\t\t\tisExtensionCommand: (text) => this.isExtensionCommand(text),\n\t\t\t\trefreshAutonomyFooterStatus: () => this.refreshAutonomyFooterStatus(),\n\t\t\t},\n\t\t\toptions,\n\t\t);\n\t}\n\n\t/** Move pending bash components from pending area to chat */\n\tprivate flushPendingBashComponents(): void {\n\t\tfor (const component of this.pendingBashComponents) {\n\t\t\tthis.pendingMessagesContainer.removeChild(component);\n\t\t\tthis.chatContainer.addChild(component);\n\t\t}\n\t\tthis.pendingBashComponents = [];\n\t}\n\n\t// =========================================================================\n\t// Selectors\n\t// =========================================================================\n\n\t/**\n\t * Shows a selector component in place of the editor.\n\t * @param create Factory that receives a `done` callback and returns the component and focus target\n\t */\n\tprivate showSelector(create: (done: () => void) => { component: Component; focus: Component }): void {\n\t\tconst done = () => {\n\t\t\tthis.overlayHost.swap(this.editor, { focusMode: \"restore\", render: \"none\" });\n\t\t};\n\t\tconst { component, focus } = create(done);\n\t\tthis.overlayHost.swap(component, { focus });\n\t}\n\n\t/** Narrow seam shared by the session-picker/tree/fork and model-selector flows. */\n\tprivate sessionFlowHost(): sessionFlows.SessionFlowHost {\n\t\tconst self = this;\n\t\treturn {\n\t\t\tsession: this.session,\n\t\t\tsessionManager: this.sessionManager,\n\t\t\tsettingsManager: this.settingsManager,\n\t\t\truntimeHost: this.runtimeHost,\n\t\t\tui: this.ui,\n\t\t\tchatContainer: this.chatContainer,\n\t\t\tstatusContainer: this.statusContainer,\n\t\t\teditor: this.editor,\n\t\t\tdefaultEditor: this.defaultEditor,\n\t\t\tfooter: this.footer,\n\t\t\textensionUiHost: this.extensionUiHost,\n\t\t\tkeybindings: this.keybindings,\n\t\t\tget loadingAnimation() {\n\t\t\t\treturn self.loadingAnimation;\n\t\t\t},\n\t\t\tset loadingAnimation(value) {\n\t\t\t\tself.loadingAnimation = value;\n\t\t\t},\n\t\t\tshowSelector: (create) => this.showSelector(create),\n\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\tshowError: (message) => this.showError(message),\n\t\t\trenderCurrentSessionState: () => this.renderCurrentSessionState(),\n\t\t\trenderInitialMessages: (options) => this.renderInitialMessages(options),\n\t\t\tflushCompactionQueue: (options) => this.flushCompactionQueue(options),\n\t\t\thandleFatalRuntimeError: (prefix, error) => this.handleFatalRuntimeError(prefix, error),\n\t\t\tpromptForMissingSessionCwd: (error) => this.promptForMissingSessionCwd(error),\n\t\t\tupdateEditorBorderColor: () => this.updateEditorBorderColor(),\n\t\t\tupdateAvailableProviderCount: () => this.updateAvailableProviderCount(),\n\t\t\tmaybeWarnAboutAnthropicSubscriptionAuth: (model) => this.maybeWarnAboutAnthropicSubscriptionAuth(model),\n\t\t\tcheckDaxnutsEasterEgg: (model) => this.checkDaxnutsEasterEgg(model),\n\t\t\tgetModelCandidates: () => this.getModelCandidates(),\n\t\t\tshutdown: (options) => this.shutdown(options),\n\t\t};\n\t}\n\n\tprivate getAutoLearnModelOptions(): SelectItem[] {\n\t\treturn this.autoLearnController.getAutoLearnModelOptions();\n\t}\n\n\tprivate getAutoLearnDataDir(): string {\n\t\treturn this.autoLearnController.getAutoLearnDataDir();\n\t}\n\n\tprivate getPrunedAutoLearnState(): AutoLearnState {\n\t\treturn this.autoLearnController.getPrunedAutoLearnState();\n\t}\n\n\tprivate getAutoLearnPresetForAutonomyMode(\n\t\tmode: AutonomyMode,\n\t\tcurrent: AutoLearnSettings = {},\n\t): Required<AutoLearnSettings> {\n\t\treturn this.autoLearnController.getAutoLearnPresetForAutonomyMode(mode, current);\n\t}\n\n\tprivate getEffectiveAutoLearnSettings(): Required<AutoLearnSettings> {\n\t\treturn this.autoLearnController.getEffectiveAutoLearnSettings();\n\t}\n\n\tprivate getCurrentAutoLearnSettings(): Required<AutoLearnSettings> {\n\t\treturn this.autoLearnController.getCurrentAutoLearnSettings();\n\t}\n\n\tprivate getAutoLearnTenantKey(): string {\n\t\treturn this.autoLearnController.getAutoLearnTenantKey();\n\t}\n\n\tprivate getAutoLearnTenantDataDir(): string {\n\t\treturn this.autoLearnController.getAutoLearnTenantDataDir();\n\t}\n\n\tprivate validateAutoLearnModelValue(value: string | undefined): string | undefined {\n\t\treturn this.autoLearnController.validateAutoLearnModelValue(value);\n\t}\n\n\tprivate collectSelfModificationCandidates(settings: { sourcePath?: string; sourcePaths?: string[] }): string[] {\n\t\treturn [\n\t\t\t...(Array.isArray(settings.sourcePaths) ? settings.sourcePaths : []),\n\t\t\t...(settings.sourcePath ? [settings.sourcePath] : []),\n\t\t]\n\t\t\t.map((candidate) => candidate?.trim())\n\t\t\t.filter((candidate): candidate is string => Boolean(candidate));\n\t}\n\n\tprivate getCurrentCwdForSettings(): string {\n\t\treturn this.runtimeHost?.session?.sessionManager?.getCwd?.() || process.cwd();\n\t}\n\n\tprivate resolveSelfModificationSource(settings: {\n\t\tsourcePath?: string;\n\t\tsourcePaths?: string[];\n\t}): string | undefined {\n\t\tconst cwd = this.getCurrentCwdForSettings();\n\t\tconst resolved = this.collectSelfModificationCandidates(settings).map((candidate) =>\n\t\t\tresolvePath(candidate, cwd, { trim: true }),\n\t\t);\n\t\tif (resolved.length === 0) return undefined;\n\t\treturn (\n\t\t\tresolved.find(\n\t\t\t\t(candidate) => fs.existsSync(candidate) && fs.existsSync(path.join(candidate, \"package.json\")),\n\t\t\t) ?? resolved[0]\n\t\t);\n\t}\n\n\tprivate validateSelfModificationSource(settings: SelfModificationSettings): string | undefined {\n\t\tif (!settings.enabled) return undefined;\n\t\tconst cwd = this.getCurrentCwdForSettings();\n\t\tconst resolved = this.collectSelfModificationCandidates(settings).map((candidate) =>\n\t\t\tresolvePath(candidate, cwd, { trim: true }),\n\t\t);\n\t\tif (resolved.length === 0) return \"Self modification is enabled, but no pi-adaptative source path is set.\";\n\t\tconst valid = resolved.find(\n\t\t\t(candidate) =>\n\t\t\t\tfs.existsSync(candidate) &&\n\t\t\t\tfs.existsSync(path.join(candidate, \"package.json\")) &&\n\t\t\t\tfs.existsSync(path.join(candidate, \"packages\", \"coding-agent\")),\n\t\t);\n\t\tif (valid) return undefined;\n\t\tconst first = resolved[0];\n\t\tif (!fs.existsSync(first)) return `Self modification source path does not exist: ${first}`;\n\t\tif (!fs.existsSync(path.join(first, \"package.json\"))) {\n\t\t\treturn `Self modification source path has no package.json: ${first}`;\n\t\t}\n\t\treturn `Self modification source path does not look like pi-adaptative (missing packages/coding-agent): ${first}`;\n\t}\n\n\tprivate launchAutoLearn(\n\t\treason: string,\n\t\tforce = false,\n\t\toptions: {\n\t\t\tcooldownKind?: \"auto\" | \"reflection\";\n\t\t\tpromptKind?: \"auto\" | \"reflection\";\n\t\t\tturnDigest?: string;\n\t\t\tbypassReflectionCooldown?: boolean;\n\t\t} = {},\n\t): string {\n\t\treturn this.autoLearnController.launchAutoLearn(reason, force, options);\n\t}\n\n\tprivate isNativeReflectionEnabled(): boolean {\n\t\treturn this.autoLearnController.isNativeReflectionEnabled();\n\t}\n\n\tprivate maybeRunNativeReflection(messages: AgentMessage[]): void {\n\t\tthis.autoLearnController.maybeRunNativeReflection(messages);\n\t}\n\n\tprivate maybeStartAutoLearn(): boolean {\n\t\treturn this.autoLearnController.maybeStartAutoLearn();\n\t}\n\n\tprivate maybeStartAutonomyReview(messages: AgentMessage[]): boolean {\n\t\treturn this.autoLearnController.maybeStartAutonomyReview(messages);\n\t}\n\n\tprivate updateAutoLearnFooter(): void {\n\t\tthis.autoLearnController.updateAutoLearnFooter();\n\t}\n\n\tprivate formatAutoLearnStatus(): string {\n\t\treturn this.autoLearnController.formatAutoLearnStatus();\n\t}\n\n\tprivate applyAutonomyMode(mode: AutonomyMode, scope: SettingsScope = \"global\"): void {\n\t\tconst currentAutoLearn = this.settingsManager.getAutoLearnSettings();\n\t\tconst preset = this.getAutoLearnPresetForAutonomyMode(mode, currentAutoLearn);\n\t\tthis.settingsManager.setAutonomySettings({ ...this.settingsManager.getAutonomySettings(), mode }, scope);\n\t\tthis.settingsManager.setAutoLearnSettings(preset, scope);\n\t\tthis.updateAutoLearnFooter();\n\t}\n\n\t/**\n\t * Delegates to the session rather than keeping its own instance (#27): the model router boots a\n\t * local server through AgentSession.getLocalRuntime() before a routed turn, and `/models`\n\t * commands need to see and be able to stop that SAME pi-managed process, not an unrelated one\n\t * tracked separately here.\n\t */\n\tprivate get localRuntime(): OllamaRuntime {\n\t\treturn this.session.getLocalRuntime();\n\t}\n\n\tprivate getTransformersRuntime(modelId: string, baseUrl?: string): TransformersRuntime {\n\t\treturn this.session.getTransformersRuntime(modelId, baseUrl);\n\t}\n\n\t/** Narrow seam shared by the /models and /fitness flows. */\n\tprivate localModelHost(): localModelCommands.LocalModelHost {\n\t\treturn {\n\t\t\tlocalRuntime: this.localRuntime,\n\t\t\tsession: this.session,\n\t\t\tsettingsManager: this.settingsManager,\n\t\t\tui: this.ui,\n\t\t\tchatContainer: this.chatContainer,\n\t\t\tgetTransformersRuntime: (modelId, baseUrl) => this.getTransformersRuntime(modelId, baseUrl),\n\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\tshowError: (message) => this.showError(message),\n\t\t\tshowSelector: (create) => this.showSelector(create),\n\t\t};\n\t}\n\n\tprivate handleModelsCommand(argsText: string): Promise<void> {\n\t\treturn localModelCommands.handleModelsCommand(this.localModelHost(), argsText);\n\t}\n\n\tprivate showFitnessModelSelector(): void {\n\t\tlocalModelCommands.showFitnessModelSelector(this.localModelHost());\n\t}\n\n\tprivate runFitnessAndAssign(modelRef: string, preselectRole?: FitnessRole): Promise<void> {\n\t\treturn localModelCommands.runFitnessAndAssign(\n\t\t\t{\n\t\t\t\tsession: this.session,\n\t\t\t\tsettingsManager: this.settingsManager,\n\t\t\t\tchatContainer: this.chatContainer,\n\t\t\t\tui: this.ui,\n\t\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\t\tshowError: (message) => this.showError(message),\n\t\t\t\tshowSelector: (create) => this.showSelector(create),\n\t\t\t},\n\t\t\tmodelRef,\n\t\t\tpreselectRole,\n\t\t);\n\t}\n\n\tprivate assignFitnessRole(modelRef: string, role: FitnessRole): void {\n\t\tlocalModelCommands.assignFitnessRole(\n\t\t\t{\n\t\t\t\tsettingsManager: this.settingsManager,\n\t\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\t},\n\t\t\tmodelRef,\n\t\t\trole,\n\t\t);\n\t}\n\n\t/** Narrow seam for the /autonomy and /auto-learn command bodies. */\n\tprivate autonomyHost(): autonomyCommands.AutonomyHost {\n\t\treturn {\n\t\t\tsession: this.session,\n\t\t\tsettingsManager: this.settingsManager,\n\t\t\tchatContainer: this.chatContainer,\n\t\t\tui: this.ui,\n\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\tapplyAutonomyMode: (mode, scope) => this.applyAutonomyMode(mode, scope),\n\t\t\tlaunchAutoLearn: (reason, force, options) => this.launchAutoLearn(reason, force, options),\n\t\t\tformatAutoLearnStatus: () => this.formatAutoLearnStatus(),\n\t\t\tgetEffectiveAutoLearnSettings: () => this.getEffectiveAutoLearnSettings(),\n\t\t\tgetPrunedAutoLearnState: () => this.getPrunedAutoLearnState(),\n\t\t\tgetAutoLearnTenantKey: () => this.getAutoLearnTenantKey(),\n\t\t\tgetAutoLearnDataDir: () => this.getAutoLearnDataDir(),\n\t\t\tgetAutoLearnTenantDataDir: () => this.getAutoLearnTenantDataDir(),\n\t\t};\n\t}\n\n\tprivate handleAutonomyCommand(text: string): void {\n\t\tautonomyCommands.handleAutonomyCommand(this.autonomyHost(), text);\n\t}\n\n\tprivate handleAutoLearnCommand(text: string): void {\n\t\tautonomyCommands.handleAutoLearnCommand(this.autonomyHost(), text);\n\t}\n\n\t/** Wide seam for the /settings selector; the hideThinkingBlock field is read+written here. */\n\tprivate settingsSelectorHost(): settingsSelectorFlow.SettingsSelectorHost {\n\t\tconst self = this;\n\t\treturn {\n\t\t\tsession: this.session,\n\t\t\tsettingsManager: this.settingsManager,\n\t\t\tfooter: this.footer,\n\t\t\tchatContainer: this.chatContainer,\n\t\t\tui: this.ui,\n\t\t\tdefaultEditor: this.defaultEditor,\n\t\t\teditor: this.editor,\n\t\t\tget hideThinkingBlock() {\n\t\t\t\treturn self.hideThinkingBlock;\n\t\t\t},\n\t\t\tset hideThinkingBlock(value) {\n\t\t\t\tself.hideThinkingBlock = value;\n\t\t\t},\n\t\t\tshowSelector: (create) => this.showSelector(create),\n\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\tshowWarning: (message) => this.showWarning(message),\n\t\t\tshowError: (message) => this.showError(message),\n\t\t\tgetAutoLearnModelOptions: () => this.getAutoLearnModelOptions(),\n\t\t\tsetupAutocompleteProvider: () => this.setupAutocompleteProvider(),\n\t\t\tupdateEditorBorderColor: () => this.updateEditorBorderColor(),\n\t\t\trebuildChatFromMessages: () => this.rebuildChatFromMessages(),\n\t\t\tvalidateSelfModificationSource: (settings) => this.validateSelfModificationSource(settings),\n\t\t\tapplyAutonomyMode: (mode, scope) => this.applyAutonomyMode(mode, scope),\n\t\t\tvalidateAutoLearnModelValue: (value) => this.validateAutoLearnModelValue(value),\n\t\t\tupdateAutoLearnFooter: () => this.updateAutoLearnFooter(),\n\t\t\thandleResourcesHubAction: (action) => this.handleResourcesHubAction(action),\n\t\t};\n\t}\n\n\tprivate showSettingsSelector(): void {\n\t\tsettingsSelectorFlow.showSettingsSelector(this.settingsSelectorHost());\n\t}\n\n\tprivate handleResourcesHubAction(action: string): Promise<void> {\n\t\treturn this.profileMenu.handleResourcesHubAction(action);\n\t}\n\n\tprivate handleProfilesCommand(profileName?: string): Promise<void> {\n\t\treturn this.profileMenu.handleProfilesCommand(profileName);\n\t}\n\n\tprivate refreshAfterProfileMutation(profileName: string): Promise<void> {\n\t\treturn this.profileMenu.refreshAfterProfileMutation(profileName);\n\t}\n\n\tprivate async handleModelCommand(searchTerm?: string): Promise<void> {\n\t\tawait sessionFlows.handleModelCommand(this.sessionFlowHost(), searchTerm);\n\t}\n\n\tprivate async getModelCandidates(): Promise<Model<any>[]> {\n\t\tif (this.session.scopedModels.length > 0) {\n\t\t\treturn this.session.scopedModels.map((scoped) => scoped.model);\n\t\t}\n\n\t\tthis.session.modelRegistry.refresh();\n\t\ttry {\n\t\t\treturn await this.session.modelRegistry.getAvailable();\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t}\n\n\t/** Update the footer's available provider count from current model candidates */\n\tprivate async updateAvailableProviderCount(): Promise<void> {\n\t\tconst models = await this.getModelCandidates();\n\t\tconst uniqueProviders = new Set(models.map((m) => m.provider));\n\t\tthis.footerDataProvider.setAvailableProviderCount(uniqueProviders.size);\n\t}\n\n\tprivate async maybeWarnAboutAnthropicSubscriptionAuth(\n\t\tmodel: Model<any> | undefined = this.session.model,\n\t): Promise<void> {\n\t\tif (this.settingsManager.getWarnings().anthropicExtraUsage === false) {\n\t\t\treturn;\n\t\t}\n\t\tif (this.anthropicSubscriptionWarningShown) {\n\t\t\treturn;\n\t\t}\n\t\tif (!model || model.provider !== \"anthropic\") {\n\t\t\treturn;\n\t\t}\n\n\t\tconst storedCredential = this.session.modelRegistry.authStorage.get(\"anthropic\");\n\t\tif (storedCredential?.type === \"oauth\") {\n\t\t\tthis.anthropicSubscriptionWarningShown = true;\n\t\t\tthis.showWarning(ANTHROPIC_SUBSCRIPTION_AUTH_WARNING);\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tconst apiKey = await this.session.modelRegistry.getApiKeyForProvider(model.provider);\n\t\t\tif (!isAnthropicSubscriptionAuthKey(apiKey)) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.anthropicSubscriptionWarningShown = true;\n\t\t\tthis.showWarning(ANTHROPIC_SUBSCRIPTION_AUTH_WARNING);\n\t\t} catch {\n\t\t\t// Ignore auth lookup failures for warning-only checks.\n\t\t}\n\t}\n\n\tprivate async showModelSelector(initialSearchInput?: string): Promise<void> {\n\t\tawait sessionFlows.showModelSelector(this.sessionFlowHost(), initialSearchInput);\n\t}\n\n\tprivate async showModelsSelector(): Promise<void> {\n\t\tawait sessionFlows.showModelsSelector(this.sessionFlowHost());\n\t}\n\n\tprivate showUserMessageSelector(newSessionName?: string): void {\n\t\tsessionFlows.showUserMessageSelector(this.sessionFlowHost(), newSessionName);\n\t}\n\n\tprivate async handleCloneCommand(newSessionName?: string): Promise<void> {\n\t\tawait sessionFlows.handleCloneCommand(\n\t\t\t{\n\t\t\t\tsessionManager: this.sessionManager,\n\t\t\t\truntimeHost: this.runtimeHost,\n\t\t\t\trenderCurrentSessionState: () => this.renderCurrentSessionState(),\n\t\t\t\teditor: this.editor,\n\t\t\t\tsession: this.session,\n\t\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\t\tshowError: (message) => this.showError(message),\n\t\t\t\tui: this.ui,\n\t\t\t},\n\t\t\tnewSessionName,\n\t\t);\n\t}\n\n\tprivate showTreeSelector(initialSelectedId?: string): void {\n\t\tsessionFlows.showTreeSelector(this.sessionFlowHost(), initialSelectedId);\n\t}\n\n\tprivate showTrustSelector(): void {\n\t\tsessionFlows.showTrustSelector(this.sessionFlowHost());\n\t}\n\n\tprivate showSessionSelector(): void {\n\t\tsessionFlows.showSessionSelector(this.sessionFlowHost());\n\t}\n\n\tprivate handleResumeSession(\n\t\tsessionPath: string,\n\t\toptions?: Parameters<ExtensionCommandContext[\"switchSession\"]>[1],\n\t): Promise<{ cancelled: boolean }> {\n\t\treturn sessionFlows.handleResumeSession(this.sessionFlowHost(), sessionPath, options);\n\t}\n\n\t// =========================================================================\n\t// Command handlers\n\t// =========================================================================\n\n\tprivate async handleReloadCommand(): Promise<void> {\n\t\tawait this.handleReloadCommandWithResult();\n\t}\n\n\t/** Reload and report whether the previous runtime was replaced successfully. */\n\tprivate async handleReloadCommandWithResult(): Promise<boolean> {\n\t\tif (this.session.isStreaming) {\n\t\t\tthis.showWarning(\"Wait for the current response to finish before reloading.\");\n\t\t\treturn false;\n\t\t}\n\t\tif (this.session.isCompacting) {\n\t\t\tthis.showWarning(\"Wait for compaction to finish before reloading.\");\n\t\t\treturn false;\n\t\t}\n\n\t\tthis.extensionUiHost.resetExtensionUI();\n\n\t\tconst reloadBox = new Container();\n\t\tconst borderColor = (s: string) => theme.fg(\"border\", s);\n\t\treloadBox.addChild(new DynamicBorder(borderColor));\n\t\treloadBox.addChild(new Spacer(1));\n\t\treloadBox.addChild(\n\t\t\tnew Text(theme.fg(\"muted\", \"Reloading keybindings, extensions, skills, prompts, themes...\"), 1, 0),\n\t\t);\n\t\treloadBox.addChild(new Spacer(1));\n\t\treloadBox.addChild(new DynamicBorder(borderColor));\n\n\t\tconst previousEditor = this.editor;\n\t\tthis.overlayHost.swap(reloadBox, { render: \"sync\" });\n\t\t// Let the terminal paint the reload notice before CPU-heavy extension/theme\n\t\t// work begins. process.nextTick runs before I/O and can still make reloads\n\t\t// appear frozen.\n\t\tawait new Promise((resolve) => setImmediate(resolve));\n\n\t\tconst dismissReloadBox = (editor: Component) => {\n\t\t\tthis.overlayHost.swap(editor);\n\t\t};\n\n\t\ttry {\n\t\t\tawait this.session.reload();\n\t\t\tconfigureHttpDispatcher(this.settingsManager.getHttpIdleTimeoutMs());\n\t\t\tthis.keybindings.reload();\n\t\t\tconst activeHeader = this.extensionUiHost.getCustomHeader() ?? this.builtInHeader;\n\t\t\tif (isExpandable(activeHeader)) {\n\t\t\t\tactiveHeader.setExpanded(this.toolOutputExpanded);\n\t\t\t}\n\t\t\tsetRegisteredThemes(this.session.resourceLoader.getThemes().themes);\n\t\t\tthis.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();\n\t\t\tconst themeName = this.settingsManager.getTheme();\n\t\t\tconst themeResult = themeName ? setTheme(themeName, true) : { success: true };\n\t\t\tif (!themeResult.success) {\n\t\t\t\tthis.showError(`Failed to load theme \"${themeName}\": ${themeResult.error}\\nFell back to dark theme.`);\n\t\t\t}\n\t\t\tconst editorPaddingX = this.settingsManager.getEditorPaddingX();\n\t\t\tconst autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();\n\t\t\tthis.defaultEditor.setPaddingX(editorPaddingX);\n\t\t\tthis.defaultEditor.setAutocompleteMaxVisible(autocompleteMaxVisible);\n\t\t\tif (this.editor !== this.defaultEditor) {\n\t\t\t\tthis.editor.setPaddingX?.(editorPaddingX);\n\t\t\t\tthis.editor.setAutocompleteMaxVisible?.(autocompleteMaxVisible);\n\t\t\t}\n\t\t\tthis.ui.setShowHardwareCursor(this.settingsManager.getShowHardwareCursor());\n\t\t\tthis.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());\n\t\t\tthis.setupAutocompleteProvider();\n\t\t\tconst runner = this.session.extensionRunner;\n\t\t\tthis.extensionUiHost.setupExtensionShortcuts(runner);\n\t\t\tawait this.rebuildChatFromMessages();\n\t\t\tdismissReloadBox(this.editor as Component);\n\t\t\tthis.showLoadedResources({\n\t\t\t\tforce: false,\n\t\t\t\tshowDiagnosticsWhenQuiet: true,\n\t\t\t});\n\t\t\tconst modelsJsonError = this.session.modelRegistry.getError();\n\t\t\tif (modelsJsonError) {\n\t\t\t\tthis.showError(`models.json error: ${modelsJsonError}`);\n\t\t\t}\n\t\t\tthis.showStatus(\"Reloaded keybindings, extensions, skills, prompts, themes\");\n\t\t\treturn true;\n\t\t} catch (error) {\n\t\t\tdismissReloadBox(previousEditor as Component);\n\t\t\tthis.showError(`Reload failed: ${error instanceof Error ? error.message : String(error)}`);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Refresh UI after extensions are loaded/unloaded live.\n\t * Performs the same refresh calls as handleReloadCommand but without the full reload.\n\t */\n\tprivate async refreshUIAfterExtensionsChanged(): Promise<void> {\n\t\ttry {\n\t\t\t// Refresh keybindings and autocomplete\n\t\t\tthis.keybindings.reload();\n\t\t\tthis.setupAutocompleteProvider();\n\n\t\t\t// Refresh themes\n\t\t\tconst activeHeader = this.extensionUiHost.getCustomHeader() ?? this.builtInHeader;\n\t\t\tif (isExpandable(activeHeader)) {\n\t\t\t\tactiveHeader.setExpanded(this.toolOutputExpanded);\n\t\t\t}\n\t\t\tsetRegisteredThemes(this.session.resourceLoader.getThemes().themes);\n\t\t\tthis.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();\n\t\t\tconst themeName = this.settingsManager.getTheme();\n\t\t\tconst themeResult = themeName ? setTheme(themeName, true) : { success: true };\n\t\t\tif (!themeResult.success) {\n\t\t\t\tthis.showError(`Failed to load theme \"${themeName}\": ${themeResult.error}\\nFell back to dark theme.`);\n\t\t\t}\n\n\t\t\t// Refresh editor settings\n\t\t\tconst editorPaddingX = this.settingsManager.getEditorPaddingX();\n\t\t\tconst autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();\n\t\t\tthis.defaultEditor.setPaddingX(editorPaddingX);\n\t\t\tthis.defaultEditor.setAutocompleteMaxVisible(autocompleteMaxVisible);\n\t\t\tif (this.editor !== this.defaultEditor) {\n\t\t\t\tthis.editor.setPaddingX?.(editorPaddingX);\n\t\t\t\tthis.editor.setAutocompleteMaxVisible?.(autocompleteMaxVisible);\n\t\t\t}\n\n\t\t\t// Refresh extension shortcuts\n\t\t\tconst runner = this.session.extensionRunner;\n\t\t\tthis.extensionUiHost.setupExtensionShortcuts(runner);\n\n\t\t\t// Refresh chat and UI\n\t\t\tawait this.rebuildChatFromMessages();\n\t\t\tthis.footer.invalidate();\n\t\t\tthis.ui.requestRender();\n\t\t} catch (error) {\n\t\t\tthis.showError(`Extension refresh failed: ${error instanceof Error ? error.message : String(error)}`);\n\t\t}\n\t}\n\n\tprivate handleExportCommand(text: string): Promise<void> {\n\t\treturn sessionIoCommands.handleExportCommand(\n\t\t\t{\n\t\t\t\tsession: this.session,\n\t\t\t\tgetPathCommandArgument: (t, command) => this.getPathCommandArgument(t, command),\n\t\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\t\tshowError: (message) => this.showError(message),\n\t\t\t},\n\t\t\ttext,\n\t\t);\n\t}\n\n\tprivate getPathCommandArgument(text: string, command: \"/export\" | \"/import\"): string | undefined {\n\t\tif (text === command) {\n\t\t\treturn undefined;\n\t\t}\n\t\tif (!text.startsWith(`${command} `)) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst argsString = text.slice(command.length + 1).trimStart();\n\t\tif (!argsString) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst firstChar = argsString[0];\n\t\tif (firstChar === '\"' || firstChar === \"'\") {\n\t\t\tconst closingQuoteIndex = argsString.indexOf(firstChar, 1);\n\t\t\tif (closingQuoteIndex < 0) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\treturn argsString.slice(1, closingQuoteIndex);\n\t\t}\n\n\t\tconst firstWhitespaceIndex = argsString.search(/\\s/);\n\t\tif (firstWhitespaceIndex < 0) {\n\t\t\treturn argsString;\n\t\t}\n\t\treturn argsString.slice(0, firstWhitespaceIndex);\n\t}\n\n\tprivate handleImportCommand(text: string): Promise<void> {\n\t\tconst self = this;\n\t\treturn sessionIoCommands.handleImportCommand(\n\t\t\t{\n\t\t\t\tgetPathCommandArgument: (t, command) => this.getPathCommandArgument(t, command),\n\t\t\t\tshowError: (message) => this.showError(message),\n\t\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\t\textensionUiHost: this.extensionUiHost,\n\t\t\t\tget loadingAnimation() {\n\t\t\t\t\treturn self.loadingAnimation;\n\t\t\t\t},\n\t\t\t\tset loadingAnimation(value) {\n\t\t\t\t\tself.loadingAnimation = value;\n\t\t\t\t},\n\t\t\t\tstatusContainer: this.statusContainer,\n\t\t\t\truntimeHost: this.runtimeHost,\n\t\t\t\trenderCurrentSessionState: () => this.renderCurrentSessionState(),\n\t\t\t\tpromptForMissingSessionCwd: (error) => this.promptForMissingSessionCwd(error),\n\t\t\t\thandleFatalRuntimeError: (prefix, error) => this.handleFatalRuntimeError(prefix, error),\n\t\t\t},\n\t\t\ttext,\n\t\t);\n\t}\n\n\tprivate handleShareCommand(): Promise<void> {\n\t\treturn sessionIoCommands.handleShareCommand({\n\t\t\tshowError: (message) => this.showError(message),\n\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\tsession: this.session,\n\t\t\tui: this.ui,\n\t\t\toverlayHost: this.overlayHost,\n\t\t\teditor: this.editor,\n\t\t});\n\t}\n\n\tprivate handleCopyCommand(): Promise<void> {\n\t\treturn sessionIoCommands.handleCopyCommand({\n\t\t\tsession: this.session,\n\t\t\tshowError: (message) => this.showError(message),\n\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t});\n\t}\n\n\tprivate handleNameCommand(text: string): void {\n\t\tsessionIoCommands.handleNameCommand(\n\t\t\t{\n\t\t\t\tsessionManager: this.sessionManager,\n\t\t\t\tchatContainer: this.chatContainer,\n\t\t\t\tshowWarning: (message) => this.showWarning(message),\n\t\t\t\tsession: this.session,\n\t\t\t\tui: this.ui,\n\t\t\t},\n\t\t\ttext,\n\t\t);\n\t}\n\n\tprivate parseGoalContinueCommand(\n\t\ttext: string,\n\t):\n\t\t| { ok: true; maxTurns: number; maxStallTurns: number; maxWallClockMinutes: number }\n\t\t| { ok: false; error: string } {\n\t\tconst usage = \"Usage: /goal-continue [maxTurns 1-20] [maxStallTurns 0-100] [maxMinutes 0-1440]\";\n\t\tconst parts = text.trim().split(/\\s+/).slice(1);\n\t\tif (parts.length > 3) {\n\t\t\treturn { ok: false, error: usage };\n\t\t}\n\n\t\tconst parseBoundedInteger = (\n\t\t\tvalue: string | undefined,\n\t\t\tfallback: number,\n\t\t\tmin: number,\n\t\t\tmax: number,\n\t\t): number | undefined => {\n\t\t\tif (value === undefined || value.length === 0) return fallback;\n\t\t\tif (!/^\\d+$/.test(value)) return undefined;\n\t\t\tconst parsed = Number.parseInt(value, 10);\n\t\t\tif (!Number.isSafeInteger(parsed) || parsed < min || parsed > max) return undefined;\n\t\t\treturn parsed;\n\t\t};\n\n\t\tconst maxTurns = parseBoundedInteger(parts[0], DEFAULT_GOAL_CONTINUE_MAX_TURNS, 1, MAX_GOAL_CONTINUE_MAX_TURNS);\n\t\tconst maxStallTurns = parseBoundedInteger(\n\t\t\tparts[1],\n\t\t\tDEFAULT_GOAL_CONTINUE_MAX_STALL_TURNS,\n\t\t\t0,\n\t\t\tMAX_GOAL_CONTINUE_MAX_STALL_TURNS,\n\t\t);\n\t\tconst maxWallClockMinutes = parseBoundedInteger(\n\t\t\tparts[2],\n\t\t\tDEFAULT_GOAL_CONTINUE_MAX_WALL_CLOCK_MINUTES,\n\t\t\t0,\n\t\t\tMAX_GOAL_CONTINUE_MAX_WALL_CLOCK_MINUTES,\n\t\t);\n\t\tif (maxTurns === undefined || maxStallTurns === undefined || maxWallClockMinutes === undefined) {\n\t\t\treturn { ok: false, error: usage };\n\t\t}\n\t\treturn { ok: true, maxTurns, maxStallTurns, maxWallClockMinutes };\n\t}\n\n\tprivate async handleGoalCommand(text: string): Promise<void> {\n\t\tawait sessionFlows.handleGoalCommand(\n\t\t\t{\n\t\t\t\tsession: this.session,\n\t\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\t\tshowError: (message) => this.showError(message),\n\t\t\t\trefreshAutonomyFooterStatus: () => this.refreshAutonomyFooterStatus(),\n\t\t\t},\n\t\t\ttext,\n\t\t);\n\t}\n\n\tprivate async handleGoalContinueCommand(text: string): Promise<void> {\n\t\tawait sessionFlows.handleGoalContinueCommand(\n\t\t\t{\n\t\t\t\tsession: this.session,\n\t\t\t\tparseGoalContinueCommand: (t) => this.parseGoalContinueCommand(t),\n\t\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\t\tshowError: (message) => this.showError(message),\n\t\t\t\trefreshAutonomyFooterStatus: () => this.refreshAutonomyFooterStatus(),\n\t\t\t},\n\t\t\ttext,\n\t\t);\n\t}\n\n\tprivate handleSessionCommand(): void {\n\t\tsessionFlows.handleSessionCommand({\n\t\t\tsession: this.session,\n\t\t\tsessionManager: this.sessionManager,\n\t\t\tchatContainer: this.chatContainer,\n\t\t\tui: this.ui,\n\t\t});\n\t}\n\n\tprivate handleUsageCommand(): void {\n\t\treportCommands.handleUsageCommand({\n\t\t\tsession: this.session,\n\t\t\tchatContainer: this.chatContainer,\n\t\t\tui: this.ui,\n\t\t\tgetCurrentAutoLearnSettings: () => this.getCurrentAutoLearnSettings(),\n\t\t});\n\t}\n\n\tprivate handleChangelogCommand(): void {\n\t\treportCommands.handleChangelogCommand({\n\t\t\tchatContainer: this.chatContainer,\n\t\t\tui: this.ui,\n\t\t\tgetMarkdownThemeWithSettings: () => this.getMarkdownThemeWithSettings(),\n\t\t});\n\t}\n\n\t/**\n\t * Get capitalized display string for an app keybinding action.\n\t */\n\tprivate getAppKeyDisplay(action: AppKeybinding): string {\n\t\treturn keyDisplayText(action);\n\t}\n\n\t/**\n\t * Get capitalized display string for an editor keybinding action.\n\t */\n\tprivate getEditorKeyDisplay(action: Keybinding): string {\n\t\treturn keyDisplayText(action);\n\t}\n\n\tprivate handleHotkeysCommand(): void {\n\t\treportCommands.handleHotkeysCommand({\n\t\t\tsession: this.session,\n\t\t\tkeybindings: this.keybindings,\n\t\t\tchatContainer: this.chatContainer,\n\t\t\tui: this.ui,\n\t\t\tgetMarkdownThemeWithSettings: () => this.getMarkdownThemeWithSettings(),\n\t\t\tgetAppKeyDisplay: (action) => this.getAppKeyDisplay(action),\n\t\t\tgetEditorKeyDisplay: (action) => this.getEditorKeyDisplay(action),\n\t\t});\n\t}\n\n\tprivate async handleClearCommand(newSessionName?: string): Promise<void> {\n\t\tif (this.loadingAnimation) {\n\t\t\tthis.loadingAnimation.stop();\n\t\t\tthis.loadingAnimation = undefined;\n\t\t}\n\t\tthis.statusContainer.clear();\n\t\ttry {\n\t\t\tconst result = await this.runtimeHost.newSession();\n\t\t\tif (result.cancelled) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.renderCurrentSessionState();\n\t\t\tif (newSessionName) {\n\t\t\t\tthis.session.setSessionName(newSessionName);\n\t\t\t}\n\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t\tconst label = newSessionName ? `✓ New session started: ${newSessionName}` : \"✓ New session started\";\n\t\t\tthis.chatContainer.addChild(new Text(`${theme.fg(\"accent\", label)}`, 1, 1));\n\t\t\tthis.ui.requestRender();\n\t\t} catch (error: unknown) {\n\t\t\tawait this.handleFatalRuntimeError(\"Failed to create session\", error);\n\t\t}\n\t}\n\n\tprivate copyResourcesRecursively(\n\t\tsrc: string,\n\t\tdest: string,\n\t\tforce: boolean,\n\t\tstats: { installed: string[]; skipped: string[] },\n\t): void {\n\t\tif (!fs.existsSync(src)) return;\n\n\t\tconst entries = fs.readdirSync(src, { withFileTypes: true });\n\t\tfs.mkdirSync(dest, { recursive: true });\n\n\t\tfor (const entry of entries) {\n\t\t\tconst srcPath = path.join(src, entry.name);\n\t\t\tconst destPath = path.join(dest, entry.name);\n\n\t\t\tif (entry.isDirectory()) {\n\t\t\t\tthis.copyResourcesRecursively(srcPath, destPath, force, stats);\n\t\t\t} else if (entry.isFile()) {\n\t\t\t\tif (fs.existsSync(destPath) && !force) {\n\t\t\t\t\tstats.skipped.push(destPath);\n\t\t\t\t} else {\n\t\t\t\t\tfs.copyFileSync(srcPath, destPath);\n\t\t\t\t\tstats.installed.push(destPath);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate handleInstallResourcesCommand(argsString: string): Promise<void> {\n\t\treturn resourceShellCommands.handleInstallResourcesCommand(\n\t\t\t{\n\t\t\t\tsettingsManager: this.settingsManager,\n\t\t\t\tshowError: (message) => this.showError(message),\n\t\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\t\tshowSelector: (create) => this.showSelector(create),\n\t\t\t\thandleReloadCommand: () => this.handleReloadCommand(),\n\t\t\t\tcopyResourcesRecursively: (src, dest, force, stats) =>\n\t\t\t\t\tthis.copyResourcesRecursively(src, dest, force, stats),\n\t\t\t},\n\t\t\targsString,\n\t\t);\n\t}\n\n\t/**\n\t * `/curate` — skill curator (#32). With no args, lists reflection-promoted skills proposed for\n\t * archival (stale/unused) and pairs proposed for consolidation (overlapping). PROPOSE-ONLY: the user\n\t * applies actions explicitly via `/curate archive <name>` / `/curate restore <name>`. Never touches\n\t * hand-authored skills; archival is restorable.\n\t */\n\tprivate handleCurateCommand(args: string): void {\n\t\tresourceShellCommands.handleCurateCommand(\n\t\t\t{\n\t\t\t\tsession: this.session,\n\t\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\t},\n\t\t\targs,\n\t\t);\n\t}\n\n\tprivate async handleConfigBackupCommand(fileArg?: string): Promise<void> {\n\t\tawait configBackup.handleConfigBackupCommand(\n\t\t\t{\n\t\t\t\tsettingsManager: this.settingsManager,\n\t\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\t\tshowError: (message) => this.showError(message),\n\t\t\t},\n\t\t\tfileArg,\n\t\t);\n\t}\n\n\tprivate async handleConfigRestoreCommand(fileArg: string): Promise<void> {\n\t\tawait configBackup.handleConfigRestoreCommand(\n\t\t\t{\n\t\t\t\tsettingsManager: this.settingsManager,\n\t\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\t\tshowError: (message) => this.showError(message),\n\t\t\t\tshowSelector: (create) => this.showSelector(create),\n\t\t\t\thandleReloadCommand: () => this.handleReloadCommandWithResult(),\n\t\t\t},\n\t\t\tfileArg,\n\t\t);\n\t}\n\n\tprivate handleDebugCommand(): void {\n\t\treportCommands.handleDebugCommand({\n\t\t\tsession: this.session,\n\t\t\tchatContainer: this.chatContainer,\n\t\t\tui: this.ui,\n\t\t});\n\t}\n\n\tprivate handleArminSaysHi(): void {\n\t\treportCommands.handleArminSaysHi({ chatContainer: this.chatContainer, ui: this.ui });\n\t}\n\n\tprivate handleDementedDelves(): void {\n\t\treportCommands.handleDementedDelves({ chatContainer: this.chatContainer, ui: this.ui });\n\t}\n\n\tprivate checkDaxnutsEasterEgg(model: { provider: string; id: string }): void {\n\t\treportCommands.checkDaxnutsEasterEgg({ chatContainer: this.chatContainer, ui: this.ui }, model);\n\t}\n\n\tprivate handleBashCommand(command: string, excludeFromContext = false): Promise<void> {\n\t\tconst self = this;\n\t\treturn resourceShellCommands.handleBashCommand(\n\t\t\t{\n\t\t\t\tsession: this.session,\n\t\t\t\tsessionManager: this.sessionManager,\n\t\t\t\tui: this.ui,\n\t\t\t\tchatContainer: this.chatContainer,\n\t\t\t\tpendingMessagesContainer: this.pendingMessagesContainer,\n\t\t\t\tpendingBashComponents: this.pendingBashComponents,\n\t\t\t\tget bashComponent() {\n\t\t\t\t\treturn self.bashComponent;\n\t\t\t\t},\n\t\t\t\tset bashComponent(value) {\n\t\t\t\t\tself.bashComponent = value;\n\t\t\t\t},\n\t\t\t\tshowError: (message) => this.showError(message),\n\t\t\t},\n\t\t\tcommand,\n\t\t\texcludeFromContext,\n\t\t);\n\t}\n\n\tprivate handleCompactCommand(customInstructions?: string): Promise<void> {\n\t\tconst self = this;\n\t\treturn resourceShellCommands.handleCompactCommand(\n\t\t\t{\n\t\t\t\tsessionManager: this.sessionManager,\n\t\t\t\tshowWarning: (message) => this.showWarning(message),\n\t\t\t\tget loadingAnimation() {\n\t\t\t\t\treturn self.loadingAnimation;\n\t\t\t\t},\n\t\t\t\tset loadingAnimation(value) {\n\t\t\t\t\tself.loadingAnimation = value;\n\t\t\t\t},\n\t\t\t\tstatusContainer: this.statusContainer,\n\t\t\t\tsession: this.session,\n\t\t\t},\n\t\t\tcustomInstructions,\n\t\t);\n\t}\n\n\tstop(): void {\n\t\tthis.unregisterSignalHandlers();\n\t\tif (this.settingsManager.getShowTerminalProgress()) {\n\t\t\tthis.ui.terminal.setProgress(false);\n\t\t}\n\t\tif (this.loadingAnimation) {\n\t\t\tthis.loadingAnimation.stop();\n\t\t\tthis.loadingAnimation = undefined;\n\t\t}\n\t\tthis.extensionUiHost.clearExtensionTerminalInputListeners();\n\t\tthis.footer.dispose();\n\t\tthis.footerDataProvider.dispose();\n\t\tif (this.unsubscribe) {\n\t\t\tthis.unsubscribe();\n\t\t}\n\t\tif (this.unsubscribeExtensionsChanged) {\n\t\t\tthis.unsubscribeExtensionsChanged();\n\t\t}\n\t\tif (this.isInitialized) {\n\t\t\tthis.ui.stop();\n\t\t\tthis.isInitialized = false;\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"interactive-mode.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/interactive-mode.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Interactive mode for the coding agent.\n * Handles TUI rendering and user interaction, delegating business logic to AgentSession.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { AgentMessage, ToolCallRepairInfo } from \"@caupulican/pi-agent-core\";\nimport { createCompactionSummaryMessage } from \"@caupulican/pi-agent-core\";\nimport type { SessionContext, SessionManager, TruncationResult } from \"@caupulican/pi-agent-core/node\";\nimport type { AssistantMessage, ImageContent, Message, Model, ToolCall } from \"@caupulican/pi-ai\";\nimport type { AutocompleteProvider, EditorComponent, Keybinding, MarkdownTheme, SelectItem } from \"@caupulican/pi-tui\";\nimport {\n\ttype Component,\n\tContainer,\n\tLoader,\n\ttype LoaderIndicatorOptions,\n\tProcessTerminal,\n\tSpacer,\n\tsetKeybindings,\n\tText,\n\tTruncatedText,\n\tTUI,\n} from \"@caupulican/pi-tui\";\nimport { APP_NAME, APP_TITLE, VERSION } from \"../../config.ts\";\nimport { type AgentSession, type AgentSessionEvent, parseSkillBlock } from \"../../core/agent-session.ts\";\nimport type { AgentSessionRuntime } from \"../../core/agent-session-runtime.ts\";\nimport type {\n\tAutocompleteProviderFactory,\n\tExtensionCommandContext,\n\tExtensionRunner,\n} from \"../../core/extensions/index.ts\";\nimport { FooterDataProvider } from \"../../core/footer-data-provider.ts\";\nimport {\n\tDEFAULT_GOAL_CONTINUE_MAX_STALL_TURNS,\n\tDEFAULT_GOAL_CONTINUE_MAX_TURNS,\n\tDEFAULT_GOAL_CONTINUE_MAX_WALL_CLOCK_MINUTES,\n\tMAX_GOAL_CONTINUE_MAX_STALL_TURNS,\n\tMAX_GOAL_CONTINUE_MAX_TURNS,\n\tMAX_GOAL_CONTINUE_MAX_WALL_CLOCK_MINUTES,\n} from \"../../core/goals/goal-continuation-defaults.ts\";\nimport { configureHttpDispatcher } from \"../../core/http-dispatcher.ts\";\nimport { type AppKeybinding, KeybindingsManager } from \"../../core/keybindings.ts\";\nimport type { OllamaRuntime, TransformersRuntime } from \"../../core/models/local-runtime.ts\";\nimport type { ResourceDiagnostic } from \"../../core/resource-loader.ts\";\nimport { formatMissingSessionCwdPrompt, type MissingSessionCwdError } from \"../../core/session-cwd.ts\";\nimport type {\n\tAutoLearnSettings,\n\tAutonomyMode,\n\tSelfModificationSettings,\n\tSettingsScope,\n} from \"../../core/settings-manager.ts\";\nimport { BUILTIN_SLASH_COMMANDS } from \"../../core/slash-commands.ts\";\nimport type { SourceInfo } from \"../../core/source-info.ts\";\nimport { getCwdRelativePath, resolvePath } from \"../../utils/paths.ts\";\nimport { ensureTool } from \"../../utils/tools-manager.ts\";\nimport { checkForNewPiVersion, type LatestPiRelease } from \"../../utils/version-check.ts\";\nimport { AuthDialogsController } from \"./auth-dialogs-controller.ts\";\nimport { AutoLearnController, type AutoLearnState } from \"./auto-learn-controller.ts\";\nimport * as autocompleteProvider from \"./autocomplete-provider.ts\";\nimport * as autonomyCommands from \"./autonomy-commands.ts\";\nimport * as clipboardInput from \"./clipboard-input.ts\";\nimport * as compactionQueue from \"./compaction-queue.ts\";\nimport { AssistantMessageComponent } from \"./components/assistant-message.ts\";\nimport { BashExecutionComponent } from \"./components/bash-execution.ts\";\nimport { BranchSummaryMessageComponent } from \"./components/branch-summary-message.ts\";\nimport { CompactionSummaryMessageComponent } from \"./components/compaction-summary-message.ts\";\nimport { CountdownTimer } from \"./components/countdown-timer.ts\";\nimport { CustomEditor } from \"./components/custom-editor.ts\";\nimport { CustomMessageComponent } from \"./components/custom-message.ts\";\nimport { DynamicBorder } from \"./components/dynamic-border.ts\";\nimport type { FitnessRole } from \"./components/fitness-role-selector.ts\";\nimport { FooterComponent } from \"./components/footer.ts\";\nimport { formatKeyText, keyDisplayText, keyHint, keyText, rawKeyHint } from \"./components/keybinding-hints.ts\";\nimport { SkillInvocationMessageComponent } from \"./components/skill-invocation-message.ts\";\nimport { ToolExecutionComponent } from \"./components/tool-execution.ts\";\nimport { ToolGroupComponent } from \"./components/tool-group.ts\";\nimport {\n\tgetToolPanelActionKey,\n\tgetToolPanelResultActionKeys,\n\tshouldReuseToolPanelInPlace,\n\tToolPanelRegistry,\n} from \"./components/tool-panel-registry.ts\";\nimport { UserMessageComponent } from \"./components/user-message.ts\";\nimport * as configBackup from \"./config-backup.ts\";\nimport { EditorOverlayHost } from \"./editor-overlay-host.ts\";\nimport { ExtensionUiHost } from \"./extension-ui-host.ts\";\nimport { openEditorForPath, openExternalEditor } from \"./external-editor.ts\";\nimport * as historyReloadMath from \"./history-reload-math.ts\";\nimport * as keyHandlers from \"./key-handlers.ts\";\nimport * as localModelCommands from \"./local-model-commands.ts\";\nimport { ProfileMenuController } from \"./profile-menu-controller.ts\";\nimport * as reportCommands from \"./report-commands.ts\";\nimport * as resourceDisplay from \"./resource-display.ts\";\nimport * as resourceShellCommands from \"./resource-shell-commands.ts\";\nimport * as sessionFlows from \"./session-flow-commands.ts\";\nimport * as sessionIoCommands from \"./session-io-commands.ts\";\nimport * as settingsSelectorFlow from \"./settings-selector-flow.ts\";\nimport * as signalLifecycle from \"./signal-lifecycle.ts\";\nimport * as startupChecks from \"./startup-checks.ts\";\nimport {\n\tgetEditorTheme,\n\tgetMarkdownTheme,\n\tinitTheme,\n\tonThemeChange,\n\tsetRegisteredThemes,\n\tsetTheme,\n\tstopThemeWatcher,\n\ttype ThemeColor,\n\ttheme,\n} from \"./theme/theme.ts\";\n\nconst TUI_HISTORY_RELOAD_CHUNK_SIZE = 20;\nconst TUI_LIVE_HISTORY_MAX_COMPONENTS = 260;\nconst TUI_LIVE_HISTORY_TRIM_TO_COMPONENTS = 220;\nconst STREAMING_UI_UPDATE_INTERVAL_MS = 80;\n\n/** Interface for components that can be expanded/collapsed */\ninterface Expandable {\n\tsetExpanded(expanded: boolean): void;\n}\n\nfunction isExpandable(obj: unknown): obj is Expandable {\n\treturn typeof obj === \"object\" && obj !== null && \"setExpanded\" in obj && typeof obj.setExpanded === \"function\";\n}\n\nclass ExpandableText extends Text implements Expandable {\n\tprivate readonly getCollapsedText: () => string;\n\tprivate readonly getExpandedText: () => string;\n\n\tconstructor(\n\t\tgetCollapsedText: () => string,\n\t\tgetExpandedText: () => string,\n\t\texpanded = false,\n\t\tpaddingX = 0,\n\t\tpaddingY = 0,\n\t) {\n\t\tsuper(expanded ? getExpandedText() : getCollapsedText(), paddingX, paddingY);\n\t\tthis.getCollapsedText = getCollapsedText;\n\t\tthis.getExpandedText = getExpandedText;\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tthis.setText(expanded ? this.getExpandedText() : this.getCollapsedText());\n\t}\n}\n\ntype UserInputSubmission = {\n\ttext: string;\n\timages?: ImageContent[];\n};\n\ntype PendingClipboardImage = {\n\tlabel: string;\n\tcontent: ImageContent;\n};\n\ntype CompactionQueuedMessage = {\n\ttext: string;\n\tmode: \"steer\" | \"followUp\";\n\timages?: ImageContent[];\n};\n\nconst ANTHROPIC_SUBSCRIPTION_AUTH_WARNING =\n\t\"Anthropic subscription auth is active. Third-party harness usage draws from extra usage and is billed per token, not your Claude plan limits. Manage extra usage at https://claude.ai/settings/usage.\";\n\nfunction isAnthropicSubscriptionAuthKey(apiKey: string | undefined): boolean {\n\treturn typeof apiKey === \"string\" && apiKey.startsWith(\"sk-ant-oat\");\n}\n\nfunction quoteIfNeeded(value: string): string {\n\tif (value.length > 0 && !/[^a-zA-Z0-9_\\-./~:@]/.test(value)) {\n\t\treturn value;\n\t}\n\treturn `'${value.replace(/'/g, `'\\\\''`)}'`;\n}\n\nexport function formatResumeCommand(sessionManager: SessionManager): string | undefined {\n\tif (!process.stdout.isTTY) return undefined;\n\tif (!sessionManager.isPersisted()) return undefined;\n\n\tconst sessionFile = sessionManager.getSessionFile();\n\tif (!sessionFile || !fs.existsSync(sessionFile)) return undefined;\n\n\tconst args = [APP_NAME];\n\tif (!sessionManager.usesDefaultSessionDir()) {\n\t\targs.push(\"--session-dir\", quoteIfNeeded(sessionManager.getSessionDir()));\n\t}\n\targs.push(\"--session\", sessionManager.getSessionId());\n\treturn args.join(\" \");\n}\n\n/**\n * Options for InteractiveMode initialization.\n */\nexport interface InteractiveModeOptions {\n\t/** Providers that were migrated to auth.json (shows warning) */\n\tmigratedProviders?: string[];\n\t/** Warning message if session model couldn't be restored */\n\tmodelFallbackMessage?: string;\n\t/** Initial message to send on startup (can include @file content) */\n\tinitialMessage?: string;\n\t/** Images to attach to the initial message */\n\tinitialImages?: ImageContent[];\n\t/** Additional messages to send after the initial message */\n\tinitialMessages?: string[];\n\t/** Force verbose startup (overrides quietStartup setting) */\n\tverbose?: boolean;\n}\n\nexport class InteractiveMode {\n\tprivate runtimeHost: AgentSessionRuntime;\n\tprivate ui: TUI;\n\tprivate chatContainer: Container;\n\tprivate pendingMessagesContainer: Container;\n\tprivate statusContainer: Container;\n\tprivate defaultEditor: CustomEditor;\n\tprivate editor: EditorComponent;\n\tprivate autocompleteProvider: AutocompleteProvider | undefined;\n\tprivate autocompleteProviderWrappers: AutocompleteProviderFactory[] = [];\n\tprivate fdPath: string | undefined;\n\tprivate editorContainer: Container;\n\tprivate overlayHost: EditorOverlayHost;\n\tprivate footer: FooterComponent;\n\tprivate footerDataProvider: FooterDataProvider;\n\tprivate autoLearnController: AutoLearnController;\n\tprivate profileMenu: ProfileMenuController;\n\tprivate authDialogs: AuthDialogsController;\n\tprivate extensionUiHost: ExtensionUiHost;\n\t// Stored so the same manager can be injected into custom editors, selectors, and extension UI.\n\tprivate keybindings: KeybindingsManager;\n\tprivate version: string;\n\tprivate isInitialized = false;\n\tprivate onInputCallback?: (submission: UserInputSubmission) => void;\n\tprivate pendingUserInputs: UserInputSubmission[] = [];\n\tprivate pendingClipboardImages: PendingClipboardImage[] = [];\n\tprivate clipboardImageCounter = 0;\n\tprivate loadingAnimation: Loader | undefined = undefined;\n\tprivate workingMessage: string | undefined = undefined;\n\tprivate workingVisible = true;\n\tprivate workingIndicatorOptions: LoaderIndicatorOptions | undefined = undefined;\n\tprivate readonly defaultWorkingMessage = \"Working...\";\n\tprivate readonly defaultHiddenThinkingLabel = \"Thinking...\";\n\tprivate hiddenThinkingLabel = this.defaultHiddenThinkingLabel;\n\n\tprivate lastSigintTime = 0;\n\tprivate lastEscapeTime = 0;\n\tprivate changelogMarkdown: string | undefined = undefined;\n\tprivate startupNoticesShown = false;\n\tprivate anthropicSubscriptionWarningShown = false;\n\n\t// Status line tracking (for mutating immediately-sequential status updates)\n\tprivate lastStatusSpacer: Spacer | undefined = undefined;\n\tprivate lastStatusText: Text | undefined = undefined;\n\n\t// Live TUI history cap. Full session history remains in SessionManager/model state.\n\tprivate liveHistoryHiddenNotice: Text | undefined = undefined;\n\tprivate liveHistoryHiddenComponents = 0;\n\tprivate tuiHistoryLoaded = false;\n\tprivate tuiHistoryLoadInProgress = false;\n\n\t// Streaming message tracking\n\tprivate streamingComponent: AssistantMessageComponent | undefined = undefined;\n\tprivate streamingMessage: AssistantMessage | undefined = undefined;\n\tprivate streamingUiUpdateTimer: ReturnType<typeof setTimeout> | undefined = undefined;\n\tprivate lastStreamingUiUpdateAt = 0;\n\n\t// Tool execution tracking and session-scoped reusable panels\n\tprivate toolPanels = new ToolPanelRegistry();\n\n\t// Tool output expansion state\n\tprivate toolOutputExpanded = false;\n\n\t// Thinking block visibility state\n\tprivate hideThinkingBlock = false;\n\n\t// Skill commands: command name -> skill file path\n\tprivate skillCommands = new Map<string, string>();\n\n\t// Agent subscription unsubscribe function\n\tprivate unsubscribe?: () => void;\n\tprivate unsubscribeExtensionsChanged?: () => void;\n\tprivate signalCleanupHandlers: Array<() => void> = [];\n\n\t// Track if editor is in bash mode (text starts with !)\n\tprivate isBashMode = false;\n\n\t// Track current bash execution component\n\tprivate bashComponent: BashExecutionComponent | undefined = undefined;\n\n\t// Track pending bash components (shown in pending area, moved to chat on submit)\n\tprivate pendingBashComponents: BashExecutionComponent[] = [];\n\n\t// Auto-compaction state\n\tprivate autoCompactionLoader: Loader | undefined = undefined;\n\tprivate autoCompactionEscapeHandler?: () => void;\n\n\t// Auto-retry state\n\tprivate retryLoader: Loader | undefined = undefined;\n\tprivate retryCountdown: CountdownTimer | undefined = undefined;\n\tprivate retryEscapeHandler?: () => void;\n\n\t// Messages queued while compaction is running\n\tprivate compactionQueuedMessages: CompactionQueuedMessage[] = [];\n\n\t// Shutdown state\n\tprivate shutdownRequested = false;\n\n\t// Extension widget containers (hold components rendered above/below the editor)\n\tprivate widgetContainerAbove!: Container;\n\tprivate widgetContainerBelow!: Container;\n\n\t// Header container that holds the built-in or custom header\n\tprivate headerContainer: Container;\n\n\t// Built-in header (logo + keybinding hints + changelog)\n\tprivate builtInHeader: Component | undefined = undefined;\n\n\tprivate options: InteractiveModeOptions;\n\n\t// Convenience accessors\n\tprivate get session(): AgentSession {\n\t\treturn this.runtimeHost.session;\n\t}\n\tprivate get agent() {\n\t\treturn this.session.agent;\n\t}\n\tprivate get sessionManager() {\n\t\treturn this.session.sessionManager;\n\t}\n\tprivate get settingsManager() {\n\t\treturn this.session.settingsManager;\n\t}\n\n\tconstructor(runtimeHost: AgentSessionRuntime, options: InteractiveModeOptions = {}) {\n\t\tthis.runtimeHost = runtimeHost;\n\t\tthis.options = options;\n\t\tthis.runtimeHost.setBeforeSessionInvalidate(() => {\n\t\t\tthis.extensionUiHost.resetExtensionUI();\n\t\t});\n\t\tthis.runtimeHost.setRebindSession(async () => {\n\t\t\tawait this.rebindCurrentSession();\n\t\t});\n\t\tthis.version = VERSION;\n\t\tthis.ui = new TUI(new ProcessTerminal(), this.settingsManager.getShowHardwareCursor());\n\t\tthis.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());\n\t\tthis.headerContainer = new Container();\n\t\tthis.chatContainer = new Container();\n\t\tthis.pendingMessagesContainer = new Container();\n\t\tthis.statusContainer = new Container();\n\t\tthis.widgetContainerAbove = new Container();\n\t\tthis.widgetContainerBelow = new Container();\n\t\tthis.keybindings = KeybindingsManager.create();\n\t\tsetKeybindings(this.keybindings);\n\t\tconst editorPaddingX = this.settingsManager.getEditorPaddingX();\n\t\tconst autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();\n\t\tthis.defaultEditor = new CustomEditor(this.ui, getEditorTheme(), this.keybindings, {\n\t\t\tpaddingX: editorPaddingX,\n\t\t\tautocompleteMaxVisible,\n\t\t});\n\t\tthis.editor = this.defaultEditor;\n\t\tthis.editorContainer = new Container();\n\t\tthis.editorContainer.addChild(this.editor as Component);\n\t\tthis.overlayHost = new EditorOverlayHost(this.editorContainer, this.ui);\n\t\tthis.footerDataProvider = new FooterDataProvider(this.sessionManager.getCwd());\n\t\tthis.footer = new FooterComponent(this.session, this.footerDataProvider);\n\t\tthis.autoLearnController = new AutoLearnController({\n\t\t\tgetSession: () => this.runtimeHost.session,\n\t\t\tresolveSelfModificationSource: (settings) => this.resolveSelfModificationSource(settings),\n\t\t\tui: {\n\t\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\t\tfooterDataProvider: this.footerDataProvider,\n\t\t\t\tinvalidateFooter: () => this.footer.invalidate(),\n\t\t\t\trequestRender: () => this.ui.requestRender(),\n\t\t\t},\n\t\t});\n\t\tthis.profileMenu = new ProfileMenuController({\n\t\t\tgetSession: () => this.runtimeHost.session,\n\t\t\tui: {\n\t\t\t\tshowSelector: (create) => this.showSelector(create),\n\t\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\t\tshowError: (message) => this.showError(message),\n\t\t\t\tshowWarning: (message) => this.showWarning(message),\n\t\t\t\trequestRender: () => this.ui.requestRender(),\n\t\t\t\ttui: this.ui,\n\t\t\t\tfooterDataProvider: this.footerDataProvider,\n\t\t\t\tinvalidateFooter: () => this.footer.invalidate(),\n\t\t\t\tupdateEditorBorderColor: () => this.updateEditorBorderColor(),\n\t\t\t\topenEditorForPath: (filePath) => this.openEditorForPath(filePath),\n\t\t\t\thandleReloadCommand: () => this.handleReloadCommandWithResult(),\n\t\t\t\tmaybeWarnAboutAnthropicSubscriptionAuth: (model) =>\n\t\t\t\t\tvoid this.maybeWarnAboutAnthropicSubscriptionAuth(model),\n\t\t\t\tcheckDaxnutsEasterEgg: (model) => this.checkDaxnutsEasterEgg(model),\n\t\t\t\tshowSettingsSelector: () => this.showSettingsSelector(),\n\t\t\t\tgetAutoLearnModelOptions: () => this.getAutoLearnModelOptions(),\n\t\t\t},\n\t\t});\n\t\tthis.authDialogs = new AuthDialogsController({\n\t\t\tgetSession: () => this.runtimeHost.session,\n\t\t\tui: {\n\t\t\t\tshowSelector: (create) => this.showSelector(create),\n\t\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\t\tshowError: (message) => this.showError(message),\n\t\t\t\trequestRender: () => this.ui.requestRender(),\n\t\t\t\ttui: this.ui,\n\t\t\t\toverlayHost: this.overlayHost,\n\t\t\t\tgetEditor: () => this.editor,\n\t\t\t\tupdateAvailableProviderCount: () => this.updateAvailableProviderCount(),\n\t\t\t\tinvalidateFooter: () => this.footer.invalidate(),\n\t\t\t\tupdateEditorBorderColor: () => this.updateEditorBorderColor(),\n\t\t\t\tmaybeWarnAboutAnthropicSubscriptionAuth: (model) =>\n\t\t\t\t\tvoid this.maybeWarnAboutAnthropicSubscriptionAuth(model),\n\t\t\t\tcheckDaxnutsEasterEgg: (model) => this.checkDaxnutsEasterEgg(model),\n\t\t\t},\n\t\t});\n\t\tthis.extensionUiHost = new ExtensionUiHost({\n\t\t\tgetSession: () => this.runtimeHost.session,\n\t\t\tui: {\n\t\t\t\ttui: this.ui,\n\t\t\t\toverlayHost: this.overlayHost,\n\t\t\t\tkeybindings: this.keybindings,\n\t\t\t\tfooter: this.footer,\n\t\t\t\tfooterDataProvider: this.footerDataProvider,\n\t\t\t\theaderContainer: this.headerContainer,\n\t\t\t\tchatContainer: this.chatContainer,\n\t\t\t\teditorContainer: this.editorContainer,\n\t\t\t\tdefaultEditor: this.defaultEditor,\n\t\t\t\twidgetContainerAbove: this.widgetContainerAbove,\n\t\t\t\twidgetContainerBelow: this.widgetContainerBelow,\n\t\t\t\tgetEditor: () => this.editor,\n\t\t\t\tsetEditor: (editor) => {\n\t\t\t\t\tconst previousEditor = this.editor;\n\t\t\t\t\tthis.editor = editor;\n\t\t\t\t\tif (previousEditor !== editor && previousEditor !== this.defaultEditor) {\n\t\t\t\t\t\tconst disposableEditor = previousEditor as typeof previousEditor & { dispose?: () => void };\n\t\t\t\t\t\tdisposableEditor.dispose?.();\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tgetBuiltInHeader: () => this.builtInHeader,\n\t\t\t\tgetAutocompleteProvider: () => this.autocompleteProvider,\n\t\t\t\tgetToolsExpanded: () => this.toolOutputExpanded,\n\t\t\t\tpushAutocompleteProviderWrapper: (factory) => {\n\t\t\t\t\tthis.autocompleteProviderWrappers.push(factory);\n\t\t\t\t},\n\t\t\t\tresetAutocompleteProviderWrappers: () => {\n\t\t\t\t\tthis.autocompleteProviderWrappers = [];\n\t\t\t\t},\n\t\t\t\tsetupAutocompleteProvider: () => this.setupAutocompleteProvider(),\n\t\t\t\tsetWorkingVisible: (visible) => this.setWorkingVisible(visible),\n\t\t\t\tsetWorkingIndicator: (options) => this.setWorkingIndicator(options),\n\t\t\t\tsetHiddenThinkingLabel: (label) => this.setHiddenThinkingLabel(label),\n\t\t\t\tsetWorkingMessage: (message) => this.setWorkingMessage(message),\n\t\t\t\tresetWorkingIndicators: () => this.resetWorkingIndicators(),\n\t\t\t\tsetToolsExpanded: (expanded) => this.setToolsExpanded(expanded),\n\t\t\t\ttoggleToolsExpanded: () => this.toggleToolOutputExpansion(),\n\t\t\t\tupdateTerminalTitle: () => this.updateTerminalTitle(),\n\t\t\t\tmarkShutdownRequested: () => {\n\t\t\t\t\tthis.shutdownRequested = true;\n\t\t\t\t},\n\t\t\t\tabort: () => {\n\t\t\t\t\tthis.restoreQueuedMessagesToEditor({ abort: true });\n\t\t\t\t},\n\t\t\t\treload: () => this.handleReloadCommand(),\n\t\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\t\tshowWarning: (message) => this.showWarning(message),\n\t\t\t\tshowError: (message) => this.showError(message),\n\t\t\t},\n\t\t});\n\t\tthis.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);\n\n\t\t// Load hide thinking block setting\n\t\tthis.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();\n\n\t\t// Register themes from resource loader and initialize\n\t\tsetRegisteredThemes(this.session.resourceLoader.getThemes().themes);\n\t\tinitTheme(this.settingsManager.getTheme(), true);\n\t}\n\n\tprivate getBuiltInCommandConflictDiagnostics(extensionRunner: ExtensionRunner): ResourceDiagnostic[] {\n\t\tconst builtinNames = new Set(BUILTIN_SLASH_COMMANDS.map((command) => command.name));\n\t\treturn extensionRunner\n\t\t\t.getRegisteredCommands()\n\t\t\t.filter((command) => builtinNames.has(command.name))\n\t\t\t.map((command) => ({\n\t\t\t\ttype: \"warning\" as const,\n\t\t\t\tmessage:\n\t\t\t\t\tcommand.invocationName === command.name\n\t\t\t\t\t\t? `Extension command '/${command.name}' conflicts with built-in interactive command. Skipping in autocomplete.`\n\t\t\t\t\t\t: `Extension command '/${command.name}' conflicts with built-in interactive command. Available as '/${command.invocationName}'.`,\n\t\t\t\tpath: command.sourceInfo.path,\n\t\t\t}));\n\t}\n\n\t// Thin delegate retained so setupAutocompleteProvider's base-provider seam\n\t// (stubbed via prototype in interactive-mode-status.test.ts) keeps working.\n\tprivate createBaseAutocompleteProvider(): AutocompleteProvider {\n\t\treturn autocompleteProvider.createBaseAutocompleteProvider({\n\t\t\tsession: this.session,\n\t\t\tsettingsManager: this.settingsManager,\n\t\t\tsessionManager: this.sessionManager,\n\t\t\tfdPath: this.fdPath,\n\t\t\tskillCommands: this.skillCommands,\n\t\t});\n\t}\n\n\tprivate setupAutocompleteProvider(): void {\n\t\tlet provider = this.createBaseAutocompleteProvider();\n\t\tfor (const wrapProvider of this.autocompleteProviderWrappers) {\n\t\t\tprovider = wrapProvider(provider);\n\t\t}\n\n\t\tthis.autocompleteProvider = provider;\n\t\tthis.defaultEditor.setAutocompleteProvider(provider);\n\t\tif (this.editor !== this.defaultEditor) {\n\t\t\tthis.editor.setAutocompleteProvider?.(provider);\n\t\t}\n\t}\n\n\tprivate startupChecksHost(): startupChecks.StartupChecksHost {\n\t\tconst self = this;\n\t\treturn {\n\t\t\tsessionManager: this.sessionManager,\n\t\t\tsettingsManager: this.settingsManager,\n\t\t\tsession: this.session,\n\t\t\tchatContainer: this.chatContainer,\n\t\t\tui: this.ui,\n\t\t\tversion: this.version,\n\t\t\tget changelogMarkdown() {\n\t\t\t\treturn self.changelogMarkdown;\n\t\t\t},\n\t\t\tget startupNoticesShown() {\n\t\t\t\treturn self.startupNoticesShown;\n\t\t\t},\n\t\t\tset startupNoticesShown(value) {\n\t\t\t\tself.startupNoticesShown = value;\n\t\t\t},\n\t\t\tgetMarkdownThemeWithSettings: () => this.getMarkdownThemeWithSettings(),\n\t\t};\n\t}\n\n\tprivate showStartupNoticesIfNeeded(): void {\n\t\tstartupChecks.showStartupNoticesIfNeeded(this.startupChecksHost());\n\t}\n\n\tasync init(): Promise<void> {\n\t\tif (this.isInitialized) return;\n\n\t\tthis.registerSignalHandlers();\n\n\t\t// Load changelog (only show new entries, skip for resumed sessions)\n\t\tthis.changelogMarkdown = this.getChangelogForDisplay();\n\n\t\t// Ensure fd and rg are available (downloads if missing, adds to PATH via getBinDir)\n\t\t// Both are needed: fd for autocomplete, rg for grep tool and bash commands\n\t\tconst [fdPath] = await Promise.all([ensureTool(\"fd\"), ensureTool(\"rg\")]);\n\t\tthis.fdPath = fdPath;\n\n\t\tif (this.session.scopedModels.length > 0 && (this.options.verbose || !this.settingsManager.getQuietStartup())) {\n\t\t\tconst modelList = this.session.scopedModels\n\t\t\t\t.map((sm) => {\n\t\t\t\t\tconst thinkingStr = sm.thinkingLevel ? `:${sm.thinkingLevel}` : \"\";\n\t\t\t\t\treturn `${sm.model.id}${thinkingStr}`;\n\t\t\t\t})\n\t\t\t\t.join(\", \");\n\t\t\tconst cycleKeys = this.keybindings.getKeys(\"app.model.cycleForward\");\n\t\t\tconst cycleHint =\n\t\t\t\tcycleKeys.length > 0\n\t\t\t\t\t? theme.fg(\"muted\", ` (${formatKeyText(cycleKeys.join(\"/\"), { capitalize: true })} to cycle)`)\n\t\t\t\t\t: \"\";\n\t\t\tconsole.log(theme.fg(\"dim\", `Model scope: ${modelList}${cycleHint}`));\n\t\t}\n\n\t\t// Add header container as first child\n\t\tthis.ui.addChild(this.headerContainer);\n\n\t\t// Add header with keybindings from config (unless silenced)\n\t\tif (this.options.verbose || !this.settingsManager.getQuietStartup()) {\n\t\t\tconst logo = theme.bold(theme.fg(\"accent\", APP_NAME)) + theme.fg(\"dim\", ` v${this.version}`);\n\n\t\t\t// Build startup instructions using keybinding hint helpers\n\t\t\tconst hint = (keybinding: AppKeybinding, description: string) => keyHint(keybinding, description);\n\n\t\t\tconst expandedInstructions = [\n\t\t\t\thint(\"app.interrupt\", \"to interrupt\"),\n\t\t\t\thint(\"app.clear\", \"to clear\"),\n\t\t\t\trawKeyHint(`${keyText(\"app.clear\")} twice`, \"to exit\"),\n\t\t\t\thint(\"app.exit\", \"to exit (empty)\"),\n\t\t\t\thint(\"app.suspend\", \"to suspend\"),\n\t\t\t\tkeyHint(\"tui.editor.deleteToLineEnd\", \"to delete to end\"),\n\t\t\t\thint(\"app.thinking.cycle\", \"to cycle thinking level\"),\n\t\t\t\trawKeyHint(`${keyText(\"app.model.cycleForward\")}/${keyText(\"app.model.cycleBackward\")}`, \"to cycle models\"),\n\t\t\t\thint(\"app.model.select\", \"to select model\"),\n\t\t\t\thint(\"app.tools.expand\", \"to load history / expand tools\"),\n\t\t\t\thint(\"app.thinking.toggle\", \"to expand thinking\"),\n\t\t\t\thint(\"app.editor.external\", \"for external editor\"),\n\t\t\t\trawKeyHint(\"/\", \"for commands\"),\n\t\t\t\trawKeyHint(\"!\", \"to run bash\"),\n\t\t\t\trawKeyHint(\"!!\", \"to run bash (no context)\"),\n\t\t\t\thint(\"app.message.followUp\", \"to queue follow-up\"),\n\t\t\t\trawKeyHint(\">>\", \"prefix to queue follow-up\"),\n\t\t\t\thint(\"app.message.dequeue\", \"to edit all queued messages\"),\n\t\t\t\trawKeyHint(\"↑\", \"(empty editor) to recall queued messages\"),\n\t\t\t\thint(\"app.clipboard.pasteImage\", \"to paste image\"),\n\t\t\t\trawKeyHint(\"drop files\", \"to attach\"),\n\t\t\t].join(\"\\n\");\n\t\t\tconst compactInstructions = [\n\t\t\t\thint(\"app.interrupt\", \"interrupt\"),\n\t\t\t\trawKeyHint(`${keyText(\"app.clear\")}/${keyText(\"app.exit\")}`, \"clear/exit\"),\n\t\t\t\trawKeyHint(\"/\", \"commands\"),\n\t\t\t\trawKeyHint(\"!\", \"bash\"),\n\t\t\t\thint(\"app.tools.expand\", \"history/more\"),\n\t\t\t].join(theme.fg(\"muted\", \" · \"));\n\t\t\tconst compactOnboarding = theme.fg(\n\t\t\t\t\"dim\",\n\t\t\t\t`Press ${keyText(\"app.tools.expand\")} to load session history or show full startup help and loaded resources.`,\n\t\t\t);\n\t\t\tconst onboarding = theme.fg(\n\t\t\t\t\"dim\",\n\t\t\t\t`Pi can explain its own features and look up its docs. Ask it how to use or extend Pi.`,\n\t\t\t);\n\t\t\tthis.builtInHeader = new ExpandableText(\n\t\t\t\t() => `${logo}\\n${compactInstructions}\\n${compactOnboarding}\\n\\n${onboarding}`,\n\t\t\t\t() => `${logo}\\n${expandedInstructions}\\n\\n${onboarding}`,\n\t\t\t\tthis.getStartupExpansionState(),\n\t\t\t\t1,\n\t\t\t\t0,\n\t\t\t);\n\n\t\t\t// Setup UI layout\n\t\t\tthis.headerContainer.addChild(new Spacer(1));\n\t\t\tthis.headerContainer.addChild(this.builtInHeader);\n\t\t\tthis.headerContainer.addChild(new Spacer(1));\n\t\t} else {\n\t\t\t// Minimal header when silenced\n\t\t\tthis.builtInHeader = new Text(\"\", 0, 0);\n\t\t\tthis.headerContainer.addChild(this.builtInHeader);\n\t\t}\n\n\t\tthis.ui.addChild(this.chatContainer);\n\t\tthis.ui.addChild(this.pendingMessagesContainer);\n\t\tthis.ui.addChild(this.statusContainer);\n\t\tthis.extensionUiHost.renderWidgets(); // Initialize with default spacer\n\t\tthis.ui.addChild(this.widgetContainerAbove);\n\t\tthis.ui.addChild(this.editorContainer);\n\t\tthis.ui.addChild(this.widgetContainerBelow);\n\t\tthis.ui.addChild(this.footer);\n\t\tthis.ui.setFocus(this.editor);\n\n\t\tthis.setupKeyHandlers();\n\t\tthis.setupEditorSubmitHandler();\n\n\t\t// Start the UI before initializing extensions so session_start handlers can use interactive dialogs\n\t\tthis.ui.start();\n\t\tthis.isInitialized = true;\n\n\t\t// Initialize extensions first so resources are shown before messages\n\t\tawait this.rebindCurrentSession();\n\n\t\t// Register extensions-changed listener for live reload UI refresh\n\t\tthis.subscribeToExtensionsChanged();\n\n\t\t// Render initial messages AFTER showing loaded resources\n\t\tawait this.renderInitialMessages();\n\t\tthis.renderProjectTrustWarningIfNeeded();\n\n\t\t// Set up theme file watcher\n\t\tonThemeChange(() => {\n\t\t\tthis.ui.invalidate();\n\t\t\tthis.updateEditorBorderColor();\n\t\t\tthis.ui.requestRender();\n\t\t});\n\n\t\t// Set up git branch watcher (uses provider instead of footer)\n\t\tthis.footerDataProvider.onBranchChange(() => {\n\t\t\tthis.ui.requestRender();\n\t\t});\n\n\t\t// Initialize available provider count and Auto Learn status for footer display\n\t\tawait this.updateAvailableProviderCount();\n\t\tthis.updateAutoLearnFooter();\n\t}\n\n\tprivate renderProjectTrustWarningIfNeeded(): void {\n\t\tstartupChecks.renderProjectTrustWarningIfNeeded(this.startupChecksHost());\n\t}\n\n\t/**\n\t * Update terminal title with session name and cwd.\n\t */\n\tprivate updateTerminalTitle(): void {\n\t\tconst cwdBasename = path.basename(this.sessionManager.getCwd());\n\t\tconst sessionName = this.sessionManager.getSessionName();\n\t\tif (sessionName) {\n\t\t\tthis.ui.terminal.setTitle(`${APP_TITLE} - ${sessionName} - ${cwdBasename}`);\n\t\t} else {\n\t\t\tthis.ui.terminal.setTitle(`${APP_TITLE} - ${cwdBasename}`);\n\t\t}\n\t}\n\n\t/**\n\t * Run the interactive mode. This is the main entry point.\n\t * Initializes the UI, shows warnings, processes initial messages, and starts the interactive loop.\n\t */\n\tasync run(): Promise<void> {\n\t\tawait this.init();\n\n\t\t// Start version check asynchronously\n\t\tcheckForNewPiVersion(this.version).then((newRelease) => {\n\t\t\tif (newRelease) {\n\t\t\t\tthis.showNewVersionNotification(newRelease);\n\t\t\t}\n\t\t});\n\n\t\t// Start package update check asynchronously\n\t\tthis.checkForPackageUpdates().then((updates) => {\n\t\t\tif (updates.length > 0) {\n\t\t\t\tthis.showPackageUpdateNotification(updates);\n\t\t\t}\n\t\t});\n\n\t\t// Check tmux keyboard setup asynchronously\n\t\tthis.checkTmuxKeyboardSetup().then((warning) => {\n\t\t\tif (warning) {\n\t\t\t\tthis.showWarning(warning);\n\t\t\t}\n\t\t});\n\n\t\t// Skill curator (#32): auto-archive stale reflection-promoted skills at startup, and ANNOUNCE it\n\t\t// (never silent — the user can restore any of them with `/curate restore <name>`).\n\t\tthis.session\n\t\t\t.runStartupSkillCuration()\n\t\t\t.then((archived) => {\n\t\t\t\tif (archived.length > 0) {\n\t\t\t\t\tthis.showStatus(\n\t\t\t\t\t\t`Curator: auto-archived ${archived.length} stale skill(s) — ${archived.join(\", \")}. Restore with /curate restore <name>.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t})\n\t\t\t.catch(() => {\n\t\t\t\t// curation is best-effort; never disrupt startup\n\t\t\t});\n\n\t\t// Show startup warnings\n\t\tconst { migratedProviders, modelFallbackMessage, initialMessage, initialImages, initialMessages } = this.options;\n\n\t\tif (migratedProviders && migratedProviders.length > 0) {\n\t\t\tthis.showWarning(`Migrated credentials to auth.json: ${migratedProviders.join(\", \")}`);\n\t\t}\n\n\t\tconst modelsJsonError = this.session.modelRegistry.getError();\n\t\tif (modelsJsonError) {\n\t\t\tthis.showError(`models.json error: ${modelsJsonError}`);\n\t\t}\n\n\t\tif (modelFallbackMessage) {\n\t\t\tthis.showWarning(modelFallbackMessage);\n\t\t}\n\n\t\tvoid this.maybeWarnAboutAnthropicSubscriptionAuth();\n\n\t\t// Process initial messages\n\t\tif (initialMessage) {\n\t\t\ttry {\n\t\t\t\tawait this.session.prompt(initialMessage, { images: initialImages });\n\t\t\t} catch (error: unknown) {\n\t\t\t\tconst errorMessage = error instanceof Error ? error.message : \"Unknown error occurred\";\n\t\t\t\tthis.showError(errorMessage);\n\t\t\t} finally {\n\t\t\t\tthis.refreshAutonomyFooterStatus();\n\t\t\t}\n\t\t}\n\n\t\tif (initialMessages) {\n\t\t\tfor (const message of initialMessages) {\n\t\t\t\ttry {\n\t\t\t\t\tawait this.session.prompt(message);\n\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\tconst errorMessage = error instanceof Error ? error.message : \"Unknown error occurred\";\n\t\t\t\t\tthis.showError(errorMessage);\n\t\t\t\t} finally {\n\t\t\t\t\tthis.refreshAutonomyFooterStatus();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Main interactive loop\n\t\twhile (true) {\n\t\t\tconst userInput = await this.getUserInput();\n\t\t\ttry {\n\t\t\t\tawait this.session.prompt(userInput.text, { images: userInput.images });\n\t\t\t} catch (error: unknown) {\n\t\t\t\tconst errorMessage = error instanceof Error ? error.message : \"Unknown error occurred\";\n\t\t\t\tthis.showError(errorMessage);\n\t\t\t} finally {\n\t\t\t\tthis.refreshAutonomyFooterStatus();\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate refreshAutonomyFooterStatus(): void {\n\t\tthis.footerDataProvider.setAutonomyStatusSnapshot(this.session.getAutonomyStatusSnapshot());\n\t\tthis.footer.invalidate();\n\t}\n\n\tprivate checkForPackageUpdates(): Promise<string[]> {\n\t\treturn startupChecks.checkForPackageUpdates(this.startupChecksHost());\n\t}\n\n\tprivate checkTmuxKeyboardSetup(): Promise<string | undefined> {\n\t\treturn startupChecks.checkTmuxKeyboardSetup();\n\t}\n\n\t/**\n\t * Get changelog entries to display on startup.\n\t * Only shows new entries since last seen version, skips for resumed sessions.\n\t */\n\tprivate getChangelogForDisplay(): string | undefined {\n\t\treturn startupChecks.getChangelogForDisplay(this.startupChecksHost());\n\t}\n\n\tprivate getMarkdownThemeWithSettings(): MarkdownTheme {\n\t\treturn {\n\t\t\t...getMarkdownTheme(),\n\t\t\tcodeBlockIndent: this.settingsManager.getCodeBlockIndent(),\n\t\t};\n\t}\n\n\t// =========================================================================\n\t// Extension System\n\t// =========================================================================\n\n\t// The resource-display formatters below are pure and live in ./resource-display.ts.\n\t// These thin `this.`-delegates are retained so showLoadedResources' formatter\n\t// injection seam (exercised via prototype stubs in interactive-mode-status.test.ts)\n\t// keeps working unchanged.\n\tprivate formatDisplayPath(p: string): string {\n\t\treturn resourceDisplay.formatDisplayPath(p);\n\t}\n\n\tprivate formatExtensionDisplayPath(path: string): string {\n\t\treturn resourceDisplay.formatExtensionDisplayPath(path);\n\t}\n\n\tprivate formatContextPath(p: string): string {\n\t\tconst cwd = path.resolve(this.sessionManager.getCwd());\n\t\tconst absolutePath = path.isAbsolute(p) ? path.resolve(p) : path.resolve(cwd, p);\n\t\tconst relativePath = getCwdRelativePath(absolutePath, cwd);\n\t\tif (relativePath !== undefined) {\n\t\t\treturn relativePath;\n\t\t}\n\n\t\treturn this.formatDisplayPath(absolutePath);\n\t}\n\n\tprivate getStartupExpansionState(): boolean {\n\t\treturn this.options.verbose || this.toolOutputExpanded;\n\t}\n\n\tprivate getShortPath(fullPath: string, sourceInfo?: SourceInfo): string {\n\t\treturn resourceDisplay.getShortPath(fullPath, sourceInfo);\n\t}\n\n\tprivate getCompactPathLabel(resourcePath: string, sourceInfo?: SourceInfo): string {\n\t\treturn resourceDisplay.getCompactPathLabel(resourcePath, sourceInfo);\n\t}\n\n\tprivate getCompactPackageSourceLabel(sourceInfo?: SourceInfo): string {\n\t\treturn resourceDisplay.getCompactPackageSourceLabel(sourceInfo);\n\t}\n\n\tprivate getCompactExtensionLabel(resourcePath: string, sourceInfo?: SourceInfo): string {\n\t\treturn resourceDisplay.getCompactExtensionLabel(resourcePath, sourceInfo);\n\t}\n\n\tprivate getCompactDisplayPathSegments(resourcePath: string): string[] {\n\t\treturn resourceDisplay.getCompactDisplayPathSegments(resourcePath);\n\t}\n\n\tprivate getCompactNonPackageExtensionLabel(\n\t\tresourcePath: string,\n\t\tindex: number,\n\t\tallPaths: Array<{ path: string; segments: string[] }>,\n\t): string {\n\t\treturn resourceDisplay.getCompactNonPackageExtensionLabel(resourcePath, index, allPaths);\n\t}\n\n\tprivate getCompactExtensionLabels(extensions: Array<{ path: string; sourceInfo?: SourceInfo }>): string[] {\n\t\treturn resourceDisplay.getCompactExtensionLabels(extensions);\n\t}\n\n\tprivate getScopeGroup(sourceInfo?: SourceInfo): \"user\" | \"project\" | \"path\" {\n\t\treturn resourceDisplay.getScopeGroup(sourceInfo);\n\t}\n\n\tprivate isPackageSource(sourceInfo?: SourceInfo): boolean {\n\t\treturn resourceDisplay.isPackageSource(sourceInfo);\n\t}\n\n\tprivate buildScopeGroups(items: Array<{ path: string; sourceInfo?: SourceInfo }>): Array<{\n\t\tscope: \"user\" | \"project\" | \"path\";\n\t\tpaths: Array<{ path: string; sourceInfo?: SourceInfo }>;\n\t\tpackages: Map<string, Array<{ path: string; sourceInfo?: SourceInfo }>>;\n\t}> {\n\t\treturn resourceDisplay.buildScopeGroups(items);\n\t}\n\n\tprivate formatScopeGroups(\n\t\tgroups: Array<{\n\t\t\tscope: \"user\" | \"project\" | \"path\";\n\t\t\tpaths: Array<{ path: string; sourceInfo?: SourceInfo }>;\n\t\t\tpackages: Map<string, Array<{ path: string; sourceInfo?: SourceInfo }>>;\n\t\t}>,\n\t\toptions: {\n\t\t\tformatPath: (item: { path: string; sourceInfo?: SourceInfo }) => string;\n\t\t\tformatPackagePath: (item: { path: string; sourceInfo?: SourceInfo }, source: string) => string;\n\t\t},\n\t): string {\n\t\treturn resourceDisplay.formatScopeGroups(groups, options);\n\t}\n\n\tprivate formatDiagnostics(diagnostics: readonly ResourceDiagnostic[], sourceInfos: Map<string, SourceInfo>): string {\n\t\treturn resourceDisplay.formatDiagnostics(diagnostics, sourceInfos);\n\t}\n\n\tprivate showLoadedResources(options?: {\n\t\textensions?: Array<{ path: string; sourceInfo?: SourceInfo }>;\n\t\tforce?: boolean;\n\t\tshowDiagnosticsWhenQuiet?: boolean;\n\t}): void {\n\t\tconst showListing = options?.force || this.options.verbose || !this.settingsManager.getQuietStartup();\n\t\tconst showDiagnostics = showListing || options?.showDiagnosticsWhenQuiet === true;\n\t\tif (!showListing && !showDiagnostics) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst sectionHeader = (name: string, color: ThemeColor = \"mdHeading\") => theme.fg(color, `[${name}]`);\n\t\tconst formatCompactList = (items: string[], options?: { sort?: boolean }): string => {\n\t\t\tconst labels = items.map((item) => item.trim()).filter((item) => item.length > 0);\n\t\t\tif (options?.sort !== false) {\n\t\t\t\tlabels.sort((a, b) => a.localeCompare(b));\n\t\t\t}\n\t\t\treturn theme.fg(\"dim\", ` ${labels.join(\", \")}`);\n\t\t};\n\t\tconst addLoadedSection = (\n\t\t\tname: string,\n\t\t\tcollapsedBody: string,\n\t\t\texpandedBody = collapsedBody,\n\t\t\tcolor: ThemeColor = \"mdHeading\",\n\t\t): void => {\n\t\t\tconst section = new ExpandableText(\n\t\t\t\t() => `${sectionHeader(name, color)}\\n${collapsedBody}`,\n\t\t\t\t() => `${sectionHeader(name, color)}\\n${expandedBody}`,\n\t\t\t\tthis.getStartupExpansionState(),\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t);\n\t\t\tthis.chatContainer.addChild(section);\n\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t};\n\n\t\tconst skillsResult = this.session.resourceLoader.getSkills();\n\t\tconst promptsResult = this.session.resourceLoader.getPrompts();\n\t\tconst themesResult = this.session.resourceLoader.getThemes();\n\t\tconst extensions =\n\t\t\toptions?.extensions ??\n\t\t\tthis.session.resourceLoader.getExtensions().extensions.map((extension) => ({\n\t\t\t\tpath: extension.path,\n\t\t\t\tsourceInfo: extension.sourceInfo,\n\t\t\t}));\n\t\tconst sourceInfos = new Map<string, SourceInfo>();\n\t\tfor (const extension of extensions) {\n\t\t\tif (extension.sourceInfo) {\n\t\t\t\tsourceInfos.set(extension.path, extension.sourceInfo);\n\t\t\t}\n\t\t}\n\t\tfor (const skill of skillsResult.skills) {\n\t\t\tif (skill.sourceInfo) {\n\t\t\t\tsourceInfos.set(skill.filePath, skill.sourceInfo);\n\t\t\t}\n\t\t}\n\t\tfor (const prompt of promptsResult.prompts) {\n\t\t\tif (prompt.sourceInfo) {\n\t\t\t\tsourceInfos.set(prompt.filePath, prompt.sourceInfo);\n\t\t\t}\n\t\t}\n\t\tfor (const loadedTheme of themesResult.themes) {\n\t\t\tif (loadedTheme.sourcePath && loadedTheme.sourceInfo) {\n\t\t\t\tsourceInfos.set(loadedTheme.sourcePath, loadedTheme.sourceInfo);\n\t\t\t}\n\t\t}\n\n\t\tif (showListing) {\n\t\t\tconst contextFiles = this.session.resourceLoader.getAgentsFiles().agentsFiles;\n\t\t\tif (contextFiles.length > 0) {\n\t\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t\t\tconst contextList = contextFiles\n\t\t\t\t\t.map((f) => theme.fg(\"dim\", ` ${this.formatDisplayPath(f.path)}`))\n\t\t\t\t\t.join(\"\\n\");\n\t\t\t\tconst contextCompactList = formatCompactList(\n\t\t\t\t\tcontextFiles.map((contextFile) => this.formatContextPath(contextFile.path)),\n\t\t\t\t\t{ sort: false },\n\t\t\t\t);\n\t\t\t\taddLoadedSection(\"Context\", contextCompactList, contextList);\n\t\t\t}\n\n\t\t\tconst skills = this.session.resourceLoader.getActiveSkills();\n\t\t\tif (skills.length > 0) {\n\t\t\t\tconst groups = this.buildScopeGroups(\n\t\t\t\t\tskills.map((skill) => ({ path: skill.filePath, sourceInfo: skill.sourceInfo })),\n\t\t\t\t);\n\t\t\t\tconst skillList = this.formatScopeGroups(groups, {\n\t\t\t\t\tformatPath: (item) => this.formatDisplayPath(item.path),\n\t\t\t\t\tformatPackagePath: (item) => this.getShortPath(item.path, item.sourceInfo),\n\t\t\t\t});\n\t\t\t\tconst skillCompactList = formatCompactList(skills.map((skill) => skill.name));\n\t\t\t\taddLoadedSection(\"Skills\", skillCompactList, skillList);\n\t\t\t}\n\n\t\t\tconst templates = this.session.promptTemplates;\n\t\t\tif (templates.length > 0) {\n\t\t\t\tconst groups = this.buildScopeGroups(\n\t\t\t\t\ttemplates.map((template) => ({ path: template.filePath, sourceInfo: template.sourceInfo })),\n\t\t\t\t);\n\t\t\t\tconst templateByPath = new Map(templates.map((t) => [t.filePath, t]));\n\t\t\t\tconst templateList = this.formatScopeGroups(groups, {\n\t\t\t\t\tformatPath: (item) => {\n\t\t\t\t\t\tconst template = templateByPath.get(item.path);\n\t\t\t\t\t\treturn template ? `/${template.name}` : this.formatDisplayPath(item.path);\n\t\t\t\t\t},\n\t\t\t\t\tformatPackagePath: (item) => {\n\t\t\t\t\t\tconst template = templateByPath.get(item.path);\n\t\t\t\t\t\treturn template ? `/${template.name}` : this.formatDisplayPath(item.path);\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t\tconst promptCompactList = formatCompactList(templates.map((template) => `/${template.name}`));\n\t\t\t\taddLoadedSection(\"Prompts\", promptCompactList, templateList);\n\t\t\t}\n\n\t\t\tif (extensions.length > 0) {\n\t\t\t\tconst groups = this.buildScopeGroups(extensions);\n\t\t\t\tconst extList = this.formatScopeGroups(groups, {\n\t\t\t\t\tformatPath: (item) => this.formatExtensionDisplayPath(item.path),\n\t\t\t\t\tformatPackagePath: (item) =>\n\t\t\t\t\t\tthis.formatExtensionDisplayPath(this.getShortPath(item.path, item.sourceInfo)),\n\t\t\t\t});\n\t\t\t\tconst extensionCompactList = formatCompactList(this.getCompactExtensionLabels(extensions));\n\t\t\t\taddLoadedSection(\"Extensions\", extensionCompactList, extList, \"mdHeading\");\n\t\t\t}\n\n\t\t\t// Show loaded themes (excluding built-in)\n\t\t\tconst loadedThemes = themesResult.themes;\n\t\t\tconst customThemes = loadedThemes.filter((t) => t.sourcePath);\n\t\t\tif (customThemes.length > 0) {\n\t\t\t\tconst groups = this.buildScopeGroups(\n\t\t\t\t\tcustomThemes.map((loadedTheme) => ({\n\t\t\t\t\t\tpath: loadedTheme.sourcePath!,\n\t\t\t\t\t\tsourceInfo: loadedTheme.sourceInfo,\n\t\t\t\t\t})),\n\t\t\t\t);\n\t\t\t\tconst themeList = this.formatScopeGroups(groups, {\n\t\t\t\t\tformatPath: (item) => this.formatDisplayPath(item.path),\n\t\t\t\t\tformatPackagePath: (item) => this.getShortPath(item.path, item.sourceInfo),\n\t\t\t\t});\n\t\t\t\tconst themeCompactList = formatCompactList(\n\t\t\t\t\tcustomThemes.map(\n\t\t\t\t\t\t(loadedTheme) =>\n\t\t\t\t\t\t\tloadedTheme.name ?? this.getCompactPathLabel(loadedTheme.sourcePath!, loadedTheme.sourceInfo),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\taddLoadedSection(\"Themes\", themeCompactList, themeList);\n\t\t\t}\n\t\t}\n\n\t\tif (showDiagnostics) {\n\t\t\tconst skillDiagnostics = skillsResult.diagnostics;\n\t\t\tif (skillDiagnostics.length > 0) {\n\t\t\t\tconst warningLines = this.formatDiagnostics(skillDiagnostics, sourceInfos);\n\t\t\t\tthis.chatContainer.addChild(new Text(`${theme.fg(\"warning\", \"[Skill conflicts]\")}\\n${warningLines}`, 0, 0));\n\t\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t\t}\n\n\t\t\tconst promptDiagnostics = promptsResult.diagnostics;\n\t\t\tif (promptDiagnostics.length > 0) {\n\t\t\t\tconst warningLines = this.formatDiagnostics(promptDiagnostics, sourceInfos);\n\t\t\t\tthis.chatContainer.addChild(\n\t\t\t\t\tnew Text(`${theme.fg(\"warning\", \"[Prompt conflicts]\")}\\n${warningLines}`, 0, 0),\n\t\t\t\t);\n\t\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t\t}\n\n\t\t\tconst extensionDiagnostics: ResourceDiagnostic[] = [];\n\t\t\tconst extensionErrors = this.session.resourceLoader.getExtensions().errors;\n\t\t\tif (extensionErrors.length > 0) {\n\t\t\t\tfor (const error of extensionErrors) {\n\t\t\t\t\textensionDiagnostics.push({ type: \"error\", message: error.error, path: error.path });\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst commandDiagnostics = this.session.extensionRunner.getCommandDiagnostics();\n\t\t\textensionDiagnostics.push(...commandDiagnostics);\n\t\t\textensionDiagnostics.push(...this.getBuiltInCommandConflictDiagnostics(this.session.extensionRunner));\n\n\t\t\tconst shortcutDiagnostics = this.session.extensionRunner.getShortcutDiagnostics();\n\t\t\textensionDiagnostics.push(...shortcutDiagnostics);\n\n\t\t\tif (extensionDiagnostics.length > 0) {\n\t\t\t\tconst warningLines = this.formatDiagnostics(extensionDiagnostics, sourceInfos);\n\t\t\t\tthis.chatContainer.addChild(\n\t\t\t\t\tnew Text(`${theme.fg(\"warning\", \"[Extension issues]\")}\\n${warningLines}`, 0, 0),\n\t\t\t\t);\n\t\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t\t}\n\n\t\t\tconst themeDiagnostics = themesResult.diagnostics;\n\t\t\tif (themeDiagnostics.length > 0) {\n\t\t\t\tconst warningLines = this.formatDiagnostics(themeDiagnostics, sourceInfos);\n\t\t\t\tthis.chatContainer.addChild(new Text(`${theme.fg(\"warning\", \"[Theme conflicts]\")}\\n${warningLines}`, 0, 0));\n\t\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Initialize the extension system with TUI-based UI context.\n\t */\n\tprivate async bindCurrentSessionExtensions(): Promise<void> {\n\t\tconst uiContext = this.extensionUiHost.createExtensionUIContext();\n\t\tawait this.session.bindExtensions({\n\t\t\tuiContext,\n\t\t\tmode: \"tui\",\n\t\t\tabortHandler: () => {\n\t\t\t\tthis.restoreQueuedMessagesToEditor({ abort: true });\n\t\t\t},\n\t\t\tcommandContextActions: {\n\t\t\t\twaitForIdle: () => this.session.agent.waitForIdle(),\n\t\t\t\tnewSession: async (options) => {\n\t\t\t\t\tif (this.loadingAnimation) {\n\t\t\t\t\t\tthis.loadingAnimation.stop();\n\t\t\t\t\t\tthis.loadingAnimation = undefined;\n\t\t\t\t\t}\n\t\t\t\t\tthis.statusContainer.clear();\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst result = await this.runtimeHost.newSession(options);\n\t\t\t\t\t\tif (!result.cancelled) {\n\t\t\t\t\t\t\tthis.renderCurrentSessionState();\n\t\t\t\t\t\t\tthis.ui.requestRender();\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn result;\n\t\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\t\treturn this.handleFatalRuntimeError(\"Failed to create session\", error);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tfork: async (entryId, options) => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst result = await this.runtimeHost.fork(entryId, options);\n\t\t\t\t\t\tif (!result.cancelled) {\n\t\t\t\t\t\t\tthis.renderCurrentSessionState();\n\t\t\t\t\t\t\tthis.editor.setText(result.selectedText ?? \"\");\n\t\t\t\t\t\t\tthis.showStatus(\"Forked to new session\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn { cancelled: result.cancelled };\n\t\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\t\treturn this.handleFatalRuntimeError(\"Failed to fork session\", error);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tnavigateTree: async (targetId, options) => {\n\t\t\t\t\tconst result = await this.session.navigateTree(targetId, {\n\t\t\t\t\t\tsummarize: options?.summarize,\n\t\t\t\t\t\tcustomInstructions: options?.customInstructions,\n\t\t\t\t\t\treplaceInstructions: options?.replaceInstructions,\n\t\t\t\t\t\tlabel: options?.label,\n\t\t\t\t\t});\n\t\t\t\t\tif (result.cancelled) {\n\t\t\t\t\t\treturn { cancelled: true };\n\t\t\t\t\t}\n\n\t\t\t\t\tawait this.renderInitialMessages();\n\t\t\t\t\tif (result.editorText && !this.editor.getText().trim()) {\n\t\t\t\t\t\tthis.editor.setText(result.editorText);\n\t\t\t\t\t}\n\t\t\t\t\tthis.showStatus(\"Navigated to selected point\");\n\t\t\t\t\tvoid this.flushCompactionQueue({ willRetry: false });\n\t\t\t\t\treturn { cancelled: false };\n\t\t\t\t},\n\t\t\t\tswitchSession: async (sessionPath, options) => {\n\t\t\t\t\treturn this.handleResumeSession(sessionPath, options);\n\t\t\t\t},\n\t\t\t\treload: async () => {\n\t\t\t\t\tawait this.handleReloadCommand();\n\t\t\t\t},\n\t\t\t},\n\t\t\tshutdownHandler: () => {\n\t\t\t\tthis.shutdownRequested = true;\n\t\t\t\tif (!this.session.isStreaming) {\n\t\t\t\t\tvoid this.shutdown();\n\t\t\t\t}\n\t\t\t},\n\t\t\tonError: (error) => {\n\t\t\t\tthis.extensionUiHost.showExtensionError(error.extensionPath, error.error, error.stack);\n\t\t\t},\n\t\t});\n\n\t\tsetRegisteredThemes(this.session.resourceLoader.getThemes().themes);\n\t\tthis.setupAutocompleteProvider();\n\n\t\tconst extensionRunner = this.session.extensionRunner;\n\t\tthis.extensionUiHost.setupExtensionShortcuts(extensionRunner);\n\t\tthis.showLoadedResources({ force: false, showDiagnosticsWhenQuiet: true });\n\t\tthis.showStartupNoticesIfNeeded();\n\t}\n\n\tprivate applyRuntimeSettings(): void {\n\t\tconfigureHttpDispatcher(this.settingsManager.getHttpIdleTimeoutMs());\n\t\tthis.footer.setSession(this.session);\n\t\tthis.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);\n\t\tthis.footerDataProvider.setCwd(this.sessionManager.getCwd());\n\t\tthis.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();\n\t\tthis.ui.setShowHardwareCursor(this.settingsManager.getShowHardwareCursor());\n\t\tthis.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());\n\t\tconst editorPaddingX = this.settingsManager.getEditorPaddingX();\n\t\tconst autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();\n\t\tthis.defaultEditor.setPaddingX(editorPaddingX);\n\t\tthis.defaultEditor.setAutocompleteMaxVisible(autocompleteMaxVisible);\n\t\tif (this.editor !== this.defaultEditor) {\n\t\t\tthis.editor.setPaddingX?.(editorPaddingX);\n\t\t\tthis.editor.setAutocompleteMaxVisible?.(autocompleteMaxVisible);\n\t\t}\n\t}\n\n\tprivate async rebindCurrentSession(): Promise<void> {\n\t\tthis.unsubscribe?.();\n\t\tthis.unsubscribe = undefined;\n\t\tthis.applyRuntimeSettings();\n\t\tawait this.bindCurrentSessionExtensions();\n\t\tthis.subscribeToAgent();\n\t\tthis.subscribeToExtensionsChanged();\n\t\tawait this.updateAvailableProviderCount();\n\t\tthis.updateEditorBorderColor();\n\t\tthis.updateTerminalTitle();\n\t}\n\n\tprivate subscribeToExtensionsChanged(): void {\n\t\tthis.unsubscribeExtensionsChanged?.();\n\t\tthis.unsubscribeExtensionsChanged = this.session.onExtensionsChanged(() => {\n\t\t\tthis.refreshUIAfterExtensionsChanged();\n\t\t});\n\t}\n\n\tprivate async handleFatalRuntimeError(prefix: string, error: unknown): Promise<never> {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tthis.showError(`${prefix}: ${message}`);\n\t\tstopThemeWatcher();\n\t\tthis.stop();\n\t\tprocess.exit(1);\n\t}\n\n\tprivate renderCurrentSessionState(): void {\n\t\tthis.pendingMessagesContainer.clear();\n\t\tthis.compactionQueuedMessages = [];\n\t\tthis.streamingComponent = undefined;\n\t\tthis.streamingMessage = undefined;\n\t\tthis.clearRenderedToolPanelState();\n\t\tvoid this.renderInitialMessages();\n\t}\n\n\t/**\n\t * Get a registered tool definition by name (for custom rendering).\n\t */\n\tprivate getRegisteredToolDefinition(toolName: string) {\n\t\treturn this.session.getToolDefinition(toolName);\n\t}\n\n\tprivate getToolPanelScope() {\n\t\treturn {\n\t\t\tsessionId: this.sessionManager.getSessionId?.(),\n\t\t\tsessionFile: this.sessionManager.getSessionFile?.(),\n\t\t\tcwd: this.sessionManager.getCwd(),\n\t\t};\n\t}\n\n\tprivate appendToolExecutionComponent(component: ToolExecutionComponent, allowGrouping: boolean): void {\n\t\tconst toolGroup = allowGrouping ? component.toolGroup?.trim() : undefined;\n\t\tif (!toolGroup) {\n\t\t\tthis.chatContainer.addChild(component);\n\t\t\tthis.trimLiveTuiHistory();\n\t\t\treturn;\n\t\t}\n\n\t\tconst children = this.chatContainer.children;\n\t\tconst lastChild = children[children.length - 1];\n\t\tif (lastChild instanceof ToolGroupComponent && lastChild.toolGroup === toolGroup) {\n\t\t\tlastChild.addTool(component);\n\t\t\tthis.trimLiveTuiHistory();\n\t\t\treturn;\n\t\t}\n\t\tif (lastChild instanceof ToolExecutionComponent && lastChild.toolGroup?.trim() === toolGroup) {\n\t\t\tconst group = new ToolGroupComponent(toolGroup, [lastChild, component]);\n\t\t\tgroup.setExpanded(this.toolOutputExpanded);\n\t\t\tchildren[children.length - 1] = group;\n\t\t\tthis.trimLiveTuiHistory();\n\t\t\treturn;\n\t\t}\n\t\tthis.chatContainer.addChild(component);\n\t\tthis.trimLiveTuiHistory();\n\t}\n\n\tprivate detachToolExecutionComponent(component: ToolExecutionComponent): void {\n\t\tconst children = this.chatContainer.children;\n\t\tconst directIndex = children.indexOf(component);\n\t\tif (directIndex !== -1) {\n\t\t\tchildren.splice(directIndex, 1);\n\t\t\treturn;\n\t\t}\n\t\tfor (let i = 0; i < children.length; i++) {\n\t\t\tconst child = children[i];\n\t\t\tif (!(child instanceof ToolGroupComponent) || !child.removeTool(component)) continue;\n\t\t\tconst remaining = child.getToolCount();\n\t\t\tif (remaining === 0) {\n\t\t\t\tchildren.splice(i, 1);\n\t\t\t} else if (remaining === 1) {\n\t\t\t\tconst onlyTool = child.getOnlyTool();\n\t\t\t\tif (onlyTool) children[i] = onlyTool;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t}\n\n\tprivate attachToolExecutionComponent(\n\t\ttoolName: string,\n\t\ttoolCallId: string,\n\t\targs: any,\n\t\trepair?: ToolCallRepairInfo,\n\t): ToolExecutionComponent {\n\t\tconst actionKey = getToolPanelActionKey(this.getToolPanelScope(), toolName, args);\n\t\tconst toolDefinition = this.getRegisteredToolDefinition(toolName);\n\t\tconst reuseInPlace = shouldReuseToolPanelInPlace(toolName, args);\n\t\tconst existing = this.toolPanels.getReusable(actionKey, { allowActive: reuseInPlace });\n\t\tif (existing) {\n\t\t\tif (reuseInPlace && actionKey) {\n\t\t\t\texisting.resetInvocation(toolName, toolCallId, args, toolDefinition, repair);\n\t\t\t\texisting.setExpanded(this.toolOutputExpanded);\n\t\t\t\tthis.toolPanels.replaceActiveForAction(toolCallId, existing, actionKey);\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\treturn existing;\n\t\t\t}\n\t\t\tthis.detachToolExecutionComponent(existing);\n\t\t\texisting.resetInvocation(toolName, toolCallId, args, toolDefinition, repair);\n\t\t\texisting.setExpanded(this.toolOutputExpanded);\n\t\t\tthis.appendToolExecutionComponent(existing, true);\n\t\t\tthis.toolPanels.register(toolCallId, existing, actionKey);\n\t\t\treturn existing;\n\t\t}\n\t\tconst component = new ToolExecutionComponent(\n\t\t\ttoolName,\n\t\t\ttoolCallId,\n\t\t\targs,\n\t\t\t{\n\t\t\t\tshowImages: this.settingsManager.getShowImages(),\n\t\t\t\timageWidthCells: this.settingsManager.getImageWidthCells(),\n\t\t\t\trepair,\n\t\t\t},\n\t\t\ttoolDefinition,\n\t\t\tthis.ui,\n\t\t\tthis.sessionManager.getCwd(),\n\t\t);\n\t\tcomponent.setExpanded(this.toolOutputExpanded);\n\t\tthis.appendToolExecutionComponent(component, true);\n\t\tthis.toolPanels.register(toolCallId, component, actionKey);\n\t\treturn component;\n\t}\n\n\tprivate clearActiveToolCalls(): void {\n\t\tthis.toolPanels.clearActive();\n\t}\n\n\tprivate clearRenderedToolPanelState(): void {\n\t\tthis.toolPanels.clearAll();\n\t}\n\n\tprivate getWorkingLoaderMessage(): string {\n\t\treturn this.workingMessage ?? this.defaultWorkingMessage;\n\t}\n\n\tprivate createWorkingLoader(): Loader {\n\t\treturn new Loader(\n\t\t\tthis.ui,\n\t\t\t(spinner) => theme.fg(\"accent\", spinner),\n\t\t\t(text) => theme.fg(\"muted\", text),\n\t\t\tthis.getWorkingLoaderMessage(),\n\t\t\tthis.workingIndicatorOptions,\n\t\t);\n\t}\n\n\tprivate stopWorkingLoader(): void {\n\t\tif (this.loadingAnimation) {\n\t\t\tthis.loadingAnimation.stop();\n\t\t\tthis.loadingAnimation = undefined;\n\t\t}\n\t\tthis.statusContainer.clear();\n\t}\n\n\tprivate setWorkingVisible(visible: boolean): void {\n\t\tthis.workingVisible = visible;\n\t\tif (!visible) {\n\t\t\tthis.stopWorkingLoader();\n\t\t\tthis.ui.requestRender();\n\t\t\treturn;\n\t\t}\n\t\tif (this.session.isStreaming && !this.loadingAnimation) {\n\t\t\tthis.statusContainer.clear();\n\t\t\tthis.loadingAnimation = this.createWorkingLoader();\n\t\t\tthis.statusContainer.addChild(this.loadingAnimation);\n\t\t}\n\t\tthis.ui.requestRender();\n\t}\n\n\tprivate setWorkingIndicator(options?: LoaderIndicatorOptions): void {\n\t\tthis.workingIndicatorOptions = options;\n\t\tthis.loadingAnimation?.setIndicator(options);\n\t\tthis.ui.requestRender();\n\t}\n\n\tprivate setHiddenThinkingLabel(label?: string): void {\n\t\tthis.hiddenThinkingLabel = label ?? this.defaultHiddenThinkingLabel;\n\t\tfor (const child of this.chatContainer.children) {\n\t\t\tif (child instanceof AssistantMessageComponent) {\n\t\t\t\tchild.setHiddenThinkingLabel(this.hiddenThinkingLabel);\n\t\t\t}\n\t\t}\n\t\tif (this.streamingComponent) {\n\t\t\tthis.streamingComponent.setHiddenThinkingLabel(this.hiddenThinkingLabel);\n\t\t}\n\t\tthis.ui.requestRender();\n\t}\n\n\t/**\n\t * Set the extension-provided working-loader message (undefined restores the default).\n\t */\n\tprivate setWorkingMessage(message: string | undefined): void {\n\t\tthis.workingMessage = message;\n\t\tif (this.loadingAnimation) {\n\t\t\tthis.loadingAnimation.setMessage(message ?? this.defaultWorkingMessage);\n\t\t}\n\t}\n\n\t/**\n\t * Reset the working indicator and hidden-thinking label to their built-in defaults.\n\t */\n\tprivate resetWorkingIndicators(): void {\n\t\tthis.workingMessage = undefined;\n\t\tthis.workingVisible = true;\n\t\tthis.setWorkingIndicator();\n\t\tif (this.loadingAnimation) {\n\t\t\tthis.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${keyText(\"app.interrupt\")} to interrupt)`);\n\t\t}\n\t\tthis.setHiddenThinkingLabel();\n\t}\n\n\tprivate async promptForMissingSessionCwd(error: MissingSessionCwdError): Promise<string | undefined> {\n\t\tconst confirmed = await this.extensionUiHost.showExtensionConfirm(\n\t\t\t\"Session cwd not found\",\n\t\t\tformatMissingSessionCwdPrompt(error.issue),\n\t\t);\n\t\treturn confirmed ? error.issue.fallbackCwd : undefined;\n\t}\n\n\t// =========================================================================\n\t// Key Handlers\n\t// =========================================================================\n\n\tprivate setupKeyHandlers(): void {\n\t\tkeyHandlers.setupKeyHandlers(this.keyHandlersHost());\n\t}\n\n\tprivate keyHandlersHost(): keyHandlers.KeyHandlersHost {\n\t\tconst self = this;\n\t\treturn {\n\t\t\tdefaultEditor: this.defaultEditor,\n\t\t\tui: this.ui,\n\t\t\tget editor() {\n\t\t\t\treturn self.editor;\n\t\t\t},\n\t\t\tget session() {\n\t\t\t\treturn self.session;\n\t\t\t},\n\t\t\tget settingsManager() {\n\t\t\t\treturn self.settingsManager;\n\t\t\t},\n\t\t\tget isBashMode() {\n\t\t\t\treturn self.isBashMode;\n\t\t\t},\n\t\t\tset isBashMode(value) {\n\t\t\t\tself.isBashMode = value;\n\t\t\t},\n\t\t\tget lastEscapeTime() {\n\t\t\t\treturn self.lastEscapeTime;\n\t\t\t},\n\t\t\tset lastEscapeTime(value) {\n\t\t\t\tself.lastEscapeTime = value;\n\t\t\t},\n\t\t\trestoreQueuedMessagesToEditor: (options) => this.restoreQueuedMessagesToEditor(options),\n\t\t\tupdateEditorBorderColor: () => this.updateEditorBorderColor(),\n\t\t\tshowTreeSelector: (id) => this.showTreeSelector(id),\n\t\t\tshowUserMessageSelector: (name) => this.showUserMessageSelector(name),\n\t\t\thandleCtrlC: () => this.handleCtrlC(),\n\t\t\thandleCtrlD: () => this.handleCtrlD(),\n\t\t\thandleCtrlZ: () => this.handleCtrlZ(),\n\t\t\tcycleThinkingLevel: () => this.cycleThinkingLevel(),\n\t\t\tcycleModel: (direction) => this.cycleModel(direction),\n\t\t\thandleDebugCommand: () => this.handleDebugCommand(),\n\t\t\tshowModelSelector: (input) => this.showModelSelector(input),\n\t\t\tloadTuiHistoryOnDemand: () => this.loadTuiHistoryOnDemand(),\n\t\t\ttoggleThinkingBlockVisibility: () => this.toggleThinkingBlockVisibility(),\n\t\t\topenExternalEditor: () => this.openExternalEditor(),\n\t\t\thandleFollowUp: () => this.handleFollowUp(),\n\t\t\thandleDequeue: () => this.handleDequeue(),\n\t\t\thandleClearCommand: (name) => this.handleClearCommand(name),\n\t\t\tshowSessionSelector: () => this.showSessionSelector(),\n\t\t\thandleClipboardImagePaste: () => this.handleClipboardImagePaste(),\n\t\t};\n\t}\n\n\tprivate handleClipboardImagePaste(): Promise<void> {\n\t\tconst self = this;\n\t\treturn clipboardInput.handleClipboardImagePaste({\n\t\t\tget pendingClipboardImages() {\n\t\t\t\treturn self.pendingClipboardImages;\n\t\t\t},\n\t\t\tset pendingClipboardImages(value) {\n\t\t\t\tself.pendingClipboardImages = value;\n\t\t\t},\n\t\t\tget clipboardImageCounter() {\n\t\t\t\treturn self.clipboardImageCounter;\n\t\t\t},\n\t\t\tset clipboardImageCounter(value) {\n\t\t\t\tself.clipboardImageCounter = value;\n\t\t\t},\n\t\t\teditor: this.editor,\n\t\t\tui: this.ui,\n\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\tshowWarning: (message) => this.showWarning(message),\n\t\t});\n\t}\n\n\tprivate takeClipboardImagesForText(text: string): ImageContent[] | undefined {\n\t\tconst self = this;\n\t\treturn clipboardInput.takeClipboardImagesForText(\n\t\t\t{\n\t\t\t\tget pendingClipboardImages() {\n\t\t\t\t\treturn self.pendingClipboardImages;\n\t\t\t\t},\n\t\t\t\tset pendingClipboardImages(value) {\n\t\t\t\t\tself.pendingClipboardImages = value;\n\t\t\t\t},\n\t\t\t\tget clipboardImageCounter() {\n\t\t\t\t\treturn self.clipboardImageCounter;\n\t\t\t\t},\n\t\t\t\tset clipboardImageCounter(value) {\n\t\t\t\t\tself.clipboardImageCounter = value;\n\t\t\t\t},\n\t\t\t},\n\t\t\ttext,\n\t\t);\n\t}\n\n\tprivate buildUserInputSubmission(text: string): UserInputSubmission {\n\t\treturn clipboardInput.buildUserInputSubmission(\n\t\t\t{ takeClipboardImagesForText: (t) => this.takeClipboardImagesForText(t) },\n\t\t\ttext,\n\t\t);\n\t}\n\n\tprivate setupEditorSubmitHandler(): void {\n\t\tthis.defaultEditor.onSubmit = async (text: string) => {\n\t\t\ttext = text.trim();\n\t\t\tif (!text) return;\n\n\t\t\tif (text === \"/quit\" || text === \"/exit\") {\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\tawait this.shutdown();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// A \">>\" prefix queues the message as a follow-up (delivered after the\n\t\t\t// current work finishes, starting the next round) instead of steering.\n\t\t\t// This is the chord-free alternative to app.message.followUp, which many\n\t\t\t// terminals swallow (e.g. Windows Terminal claims alt+enter).\n\t\t\tconst queueAsFollowUp = text.startsWith(\">>\");\n\t\t\tif (queueAsFollowUp) {\n\t\t\t\ttext = text.slice(2).trim();\n\t\t\t\tif (!text) return;\n\t\t\t}\n\n\t\t\t// User input submitted while work is active is always steering. Treat\n\t\t\t// slash/bang text as user steering text instead of executing commands that\n\t\t\t// would interrupt the current stream or compaction.\n\t\t\tif (this.session.isCompacting) {\n\t\t\t\tconst images = this.takeClipboardImagesForText(text);\n\t\t\t\tthis.queueCompactionMessage(text, queueAsFollowUp ? \"followUp\" : \"steer\", images);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (this.session.isStreaming || this.session.isRetrying) {\n\t\t\t\tconst images = this.takeClipboardImagesForText(text);\n\t\t\t\tthis.editor.addToHistory?.(text);\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\ttry {\n\t\t\t\t\tawait this.session.prompt(text, {\n\t\t\t\t\t\tstreamingBehavior: queueAsFollowUp ? \"followUp\" : \"steer\",\n\t\t\t\t\t\timages,\n\t\t\t\t\t\tprocessSlashCommands: false,\n\t\t\t\t\t});\n\t\t\t\t} finally {\n\t\t\t\t\tthis.refreshAutonomyFooterStatus();\n\t\t\t\t}\n\t\t\t\tthis.updatePendingMessagesDisplay();\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Handle commands\n\t\t\tif (text === \"/settings\") {\n\t\t\t\tthis.showSettingsSelector();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/auto-learn\" || text.startsWith(\"/auto-learn \")) {\n\t\t\t\tthis.handleAutoLearnCommand(text);\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/autonomy\" || text.startsWith(\"/autonomy \")) {\n\t\t\t\tthis.handleAutonomyCommand(text);\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/fitness\" || text.startsWith(\"/fitness \")) {\n\t\t\t\tconst fitnessArgs = text.slice(\"/fitness\".length).trim();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\tif (fitnessArgs.length === 0) {\n\t\t\t\t\t// No args: open the model picker, probe the selection, then offer role assignment.\n\t\t\t\t\tthis.showFitnessModelSelector();\n\t\t\t\t} else {\n\t\t\t\t\t// Explicit ref: same handler as /autonomy fitness.\n\t\t\t\t\tthis.handleAutonomyCommand(`/autonomy fitness ${fitnessArgs}`);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/models\" || text.startsWith(\"/models \")) {\n\t\t\t\tvoid this.handleModelsCommand(text.slice(\"/models\".length).trim());\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/context\") {\n\t\t\t\tthis.showStatus(this.session.formatContextCompositionDashboard());\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/toolhealth\") {\n\t\t\t\tthis.showStatus(this.session.formatToolRepairHealthReport());\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/toolprobe\" || text.startsWith(\"/toolprobe \")) {\n\t\t\t\tconst target = text.slice(\"/toolprobe\".length).trim();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\tthis.showStatus(\"Running tool probe...\");\n\t\t\t\ttry {\n\t\t\t\t\tconst report = await this.session.probeToolCalling(target || undefined);\n\t\t\t\t\tthis.showStatus(report.table);\n\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\tthis.showWarning(error instanceof Error ? error.message : String(error));\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text.startsWith(\"/toolrule-remove\")) {\n\t\t\t\tconst [model, mode] = text.slice(\"/toolrule-remove\".length).trim().split(/\\s+/, 2);\n\t\t\t\tconst message =\n\t\t\t\t\tmodel && mode\n\t\t\t\t\t\t? this.session.removeToolRepairRule(model, mode)\n\t\t\t\t\t\t\t? `Removed tool repair rule ${mode} for ${model}.`\n\t\t\t\t\t\t\t: `No tool repair rule ${mode} found for ${model}.`\n\t\t\t\t\t\t: \"Usage: /toolrule-remove <provider/model> <mode>\";\n\t\t\t\tthis.showStatus(message);\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text.startsWith(\"/toolprotocol-reset\")) {\n\t\t\t\tconst model = text.slice(\"/toolprotocol-reset\".length).trim();\n\t\t\t\tconst message = model\n\t\t\t\t\t? this.session.resetToolProtocolCalibration(model)\n\t\t\t\t\t\t? `Reset text tool protocol calibration for ${model}.`\n\t\t\t\t\t\t: `No text tool protocol calibration found for ${model}.`\n\t\t\t\t\t: \"Usage: /toolprotocol-reset <provider/model>\";\n\t\t\t\tthis.showStatus(message);\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/scoped-models\") {\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\tawait this.showModelsSelector();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/model\" || text.startsWith(\"/model \")) {\n\t\t\t\tconst searchTerm = text.startsWith(\"/model \") ? text.slice(7).trim() : undefined;\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\tawait this.handleModelCommand(searchTerm);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/profiles\" || text.startsWith(\"/profiles \")) {\n\t\t\t\tconst rawProfileName = text.startsWith(\"/profiles \") ? text.slice(10).trim() : undefined;\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\tawait this.handleProfilesCommand(rawProfileName?.length ? rawProfileName : undefined);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/export\" || text.startsWith(\"/export \")) {\n\t\t\t\tawait this.handleExportCommand(text);\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/import\" || text.startsWith(\"/import \")) {\n\t\t\t\tawait this.handleImportCommand(text);\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/share\") {\n\t\t\t\tawait this.handleShareCommand();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/copy\") {\n\t\t\t\tawait this.handleCopyCommand();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/name\" || text.startsWith(\"/name \")) {\n\t\t\t\tthis.handleNameCommand(text);\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/session\") {\n\t\t\t\tthis.handleSessionCommand();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/usage\" || text === \"/cost\") {\n\t\t\t\tthis.handleUsageCommand();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/goal\" || text.startsWith(\"/goal \")) {\n\t\t\t\tawait this.handleGoalCommand(text);\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/goal-continue\" || text.startsWith(\"/goal-continue \")) {\n\t\t\t\tawait this.handleGoalContinueCommand(text);\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/changelog\") {\n\t\t\t\tthis.handleChangelogCommand();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/hotkeys\") {\n\t\t\t\tthis.handleHotkeysCommand();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/fork\" || text.startsWith(\"/fork \")) {\n\t\t\t\tthis.showUserMessageSelector(text.slice(\"/fork\".length).trim() || undefined);\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/clone\" || text.startsWith(\"/clone \")) {\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\tawait this.handleCloneCommand(text.slice(\"/clone\".length).trim() || undefined);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/tree\") {\n\t\t\t\tthis.showTreeSelector();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/trust\") {\n\t\t\t\tthis.showTrustSelector();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/login\" || text.startsWith(\"/login \")) {\n\t\t\t\tawait this.authDialogs.showOAuthSelector(\"login\", text.slice(\"/login\".length).trim() || undefined);\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/logout\" || text.startsWith(\"/logout \")) {\n\t\t\t\tawait this.authDialogs.showOAuthSelector(\"logout\", text.slice(\"/logout\".length).trim() || undefined);\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/new\" || text.startsWith(\"/new \")) {\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\tawait this.handleClearCommand(text.slice(\"/new\".length).trim() || undefined);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/compact\" || text.startsWith(\"/compact \")) {\n\t\t\t\tconst customInstructions = text.startsWith(\"/compact \") ? text.slice(9).trim() : undefined;\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\tawait this.handleCompactCommand(customInstructions);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/reload\") {\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\tawait this.handleReloadCommand();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/curate\" || text.startsWith(\"/curate \")) {\n\t\t\t\tconst args = text.slice(\"/curate\".length).trim();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\tthis.handleCurateCommand(args);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/install-resources\" || text.startsWith(\"/install-resources \")) {\n\t\t\t\tconst args = text.slice(\"/install-resources\".length).trim();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\tawait this.handleInstallResourcesCommand(args);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/config-backup\" || text.startsWith(\"/config-backup \")) {\n\t\t\t\tconst file = text.slice(\"/config-backup\".length).trim() || undefined;\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\tawait this.handleConfigBackupCommand(file);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/config-restore\" || text.startsWith(\"/config-restore \")) {\n\t\t\t\tconst file = text.slice(\"/config-restore\".length).trim();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\tawait this.handleConfigRestoreCommand(file);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/debug\") {\n\t\t\t\tthis.handleDebugCommand();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/arminsayshi\") {\n\t\t\t\tthis.handleArminSaysHi();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/dementedelves\") {\n\t\t\t\tthis.handleDementedDelves();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (text === \"/resume\") {\n\t\t\t\tthis.showSessionSelector();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Handle bash command (! for normal, !! for excluded from context)\n\t\t\tif (text.startsWith(\"!\")) {\n\t\t\t\tconst isExcluded = text.startsWith(\"!!\");\n\t\t\t\tconst command = isExcluded ? text.slice(2).trim() : text.slice(1).trim();\n\t\t\t\tif (command) {\n\t\t\t\t\tif (this.session.isBashRunning) {\n\t\t\t\t\t\tthis.showWarning(\"A bash command is already running. Press Esc to cancel it first.\");\n\t\t\t\t\t\tthis.editor.setText(text);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tthis.editor.addToHistory?.(text);\n\t\t\t\t\tawait this.handleBashCommand(command, isExcluded);\n\t\t\t\t\tthis.isBashMode = false;\n\t\t\t\t\tthis.updateEditorBorderColor();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Normal message submission\n\t\t\t// First, move any pending bash components to chat\n\t\t\tthis.flushPendingBashComponents();\n\n\t\t\tconst submission = this.buildUserInputSubmission(text);\n\t\t\tif (this.onInputCallback) {\n\t\t\t\tthis.onInputCallback(submission);\n\t\t\t} else {\n\t\t\t\tthis.pendingUserInputs.push(submission);\n\t\t\t}\n\t\t\tthis.editor.addToHistory?.(text);\n\t\t};\n\t}\n\n\tprivate subscribeToAgent(): void {\n\t\tthis.unsubscribe = this.session.subscribe(async (event) => {\n\t\t\tawait this.handleEvent(event);\n\t\t});\n\t}\n\n\tprivate async handleEvent(event: AgentSessionEvent): Promise<void> {\n\t\tif (!this.isInitialized) {\n\t\t\tawait this.init();\n\t\t}\n\n\t\tthis.footer.invalidate();\n\n\t\tswitch (event.type) {\n\t\t\t// Part B: general \"the system is processing\" feedback for the routing/prep gap before a\n\t\t\t// turn starts streaming (the judge is a real bounded LLM call, not instant) — independent\n\t\t\t// of thinking level, since this isn't model-thinking. Reuses the same working loader\n\t\t\t// agent_start below uses, so the hand-off into real streaming is the same\n\t\t\t// stop-then-recreate it already does — no distinct spinner, no double-render.\n\t\t\tcase \"routing_start\":\n\t\t\t\tif (!this.session.isStreaming && !this.loadingAnimation && this.workingVisible) {\n\t\t\t\t\tthis.loadingAnimation = this.createWorkingLoader();\n\t\t\t\t\tthis.statusContainer.addChild(this.loadingAnimation);\n\t\t\t\t\tthis.ui.requestRender();\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase \"routing_end\":\n\t\t\t\t// Unconditional: covers both a clean hand-off into agent_start (which immediately\n\t\t\t\t// stops-and-recreates its own loader anyway) and a turn that failed before ever\n\t\t\t\t// starting, which must not leave the indicator spinning forever.\n\t\t\t\tthis.stopWorkingLoader();\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\tbreak;\n\n\t\t\tcase \"agent_start\":\n\t\t\t\tthis.clearActiveToolCalls();\n\t\t\t\tif (this.settingsManager.getShowTerminalProgress()) {\n\t\t\t\t\tthis.ui.terminal.setProgress(true);\n\t\t\t\t}\n\t\t\t\t// Restore main escape handler if retry handler is still active\n\t\t\t\t// (retry success event fires later, but we need main handler now)\n\t\t\t\tif (this.retryEscapeHandler) {\n\t\t\t\t\tthis.defaultEditor.onEscape = this.retryEscapeHandler;\n\t\t\t\t\tthis.retryEscapeHandler = undefined;\n\t\t\t\t}\n\t\t\t\tif (this.retryCountdown) {\n\t\t\t\t\tthis.retryCountdown.dispose();\n\t\t\t\t\tthis.retryCountdown = undefined;\n\t\t\t\t}\n\t\t\t\tif (this.retryLoader) {\n\t\t\t\t\tthis.retryLoader.stop();\n\t\t\t\t\tthis.retryLoader = undefined;\n\t\t\t\t}\n\t\t\t\tthis.stopWorkingLoader();\n\t\t\t\tif (this.workingVisible) {\n\t\t\t\t\tthis.loadingAnimation = this.createWorkingLoader();\n\t\t\t\t\tthis.statusContainer.addChild(this.loadingAnimation);\n\t\t\t\t}\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\tbreak;\n\n\t\t\tcase \"queue_update\":\n\t\t\t\tthis.updatePendingMessagesDisplay();\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\tbreak;\n\n\t\t\tcase \"session_info_changed\":\n\t\t\t\tthis.updateTerminalTitle();\n\t\t\t\tthis.footer.invalidate();\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\tbreak;\n\n\t\t\tcase \"thinking_level_changed\":\n\t\t\t\tthis.footer.invalidate();\n\t\t\t\tthis.updateEditorBorderColor();\n\t\t\t\tbreak;\n\n\t\t\tcase \"warning\":\n\t\t\t\tthis.showWarning(event.message);\n\t\t\t\tbreak;\n\n\t\t\tcase \"delegate_workers\": {\n\t\t\t\tconst activeParts = [\n\t\t\t\t\tevent.running > 0 ? `${event.running} running` : undefined,\n\t\t\t\t\tevent.queued > 0 ? `${event.queued} queued` : undefined,\n\t\t\t\t].filter((part): part is string => part !== undefined);\n\t\t\t\tconst latestTerminal = event.terminalSinceFlush.at(-1);\n\t\t\t\tconst terminalStatus =\n\t\t\t\t\tevent.terminalSinceFlush.length === 1 && latestTerminal\n\t\t\t\t\t\t? `${latestTerminal.laneId} ${latestTerminal.status}`\n\t\t\t\t\t\t: event.failedSinceFlush > 0\n\t\t\t\t\t\t\t? `${event.failedSinceFlush} failed`\n\t\t\t\t\t\t\t: event.completedSinceFlush > 0\n\t\t\t\t\t\t\t\t? `${event.completedSinceFlush} completed`\n\t\t\t\t\t\t\t\t: undefined;\n\t\t\t\tconst status = activeParts.length > 0 ? activeParts.join(\", \") : terminalStatus;\n\t\t\t\tthis.footerDataProvider.setExtensionStatus(\"delegate\", status ? `delegate: ${status}` : undefined);\n\t\t\t\tthis.footer.invalidate();\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"message_start\":\n\t\t\t\tif (event.message.role === \"custom\") {\n\t\t\t\t\tthis.addMessageToChat(event.message);\n\t\t\t\t\tthis.ui.requestRender();\n\t\t\t\t} else if (event.message.role === \"user\") {\n\t\t\t\t\tthis.addMessageToChat(event.message);\n\t\t\t\t\tthis.updatePendingMessagesDisplay();\n\t\t\t\t\tthis.ui.requestRender();\n\t\t\t\t} else if (event.message.role === \"assistant\") {\n\t\t\t\t\tthis.clearPendingStreamingUiUpdate();\n\t\t\t\t\tthis.lastStreamingUiUpdateAt = 0;\n\t\t\t\t\tthis.streamingComponent = new AssistantMessageComponent(\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\tthis.hideThinkingBlock,\n\t\t\t\t\t\tthis.getMarkdownThemeWithSettings(),\n\t\t\t\t\t\tthis.hiddenThinkingLabel,\n\t\t\t\t\t);\n\t\t\t\t\tthis.streamingMessage = event.message;\n\t\t\t\t\tthis.chatContainer.addChild(this.streamingComponent);\n\t\t\t\t\tthis.applyStreamingMessageUpdate(this.streamingMessage, { force: true });\n\t\t\t\t\tthis.trimLiveTuiHistory();\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase \"message_update\":\n\t\t\t\tif (this.streamingComponent && event.message.role === \"assistant\") {\n\t\t\t\t\tthis.applyStreamingMessageUpdate(event.message);\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase \"message_end\":\n\t\t\t\tif (event.message.role === \"user\") break;\n\t\t\t\tif (this.streamingComponent && event.message.role === \"assistant\") {\n\t\t\t\t\tthis.streamingMessage = event.message;\n\t\t\t\t\tlet errorMessage: string | undefined;\n\t\t\t\t\tif (this.streamingMessage.stopReason === \"aborted\") {\n\t\t\t\t\t\tconst retryAttempt = this.session.retryAttempt;\n\t\t\t\t\t\terrorMessage =\n\t\t\t\t\t\t\tretryAttempt > 0\n\t\t\t\t\t\t\t\t? `Aborted after ${retryAttempt} retry attempt${retryAttempt > 1 ? \"s\" : \"\"}`\n\t\t\t\t\t\t\t\t: \"Operation aborted\";\n\t\t\t\t\t\tthis.streamingMessage.errorMessage = errorMessage;\n\t\t\t\t\t}\n\t\t\t\t\tthis.applyStreamingMessageUpdate(this.streamingMessage, { force: true });\n\n\t\t\t\t\tif (this.streamingMessage.stopReason === \"aborted\" || this.streamingMessage.stopReason === \"error\") {\n\t\t\t\t\t\tif (!errorMessage) {\n\t\t\t\t\t\t\terrorMessage = this.streamingMessage.errorMessage || \"Error\";\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor (const [, component] of this.toolPanels.activeEntries()) {\n\t\t\t\t\t\t\tcomponent.updateResult({\n\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: errorMessage }],\n\t\t\t\t\t\t\t\tisError: true,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.clearActiveToolCalls();\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Args are now complete - trigger diff computation for edit tools\n\t\t\t\t\t\tfor (const [, component] of this.toolPanels.activeEntries()) {\n\t\t\t\t\t\t\tcomponent.setArgsComplete();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tthis.streamingComponent = undefined;\n\t\t\t\t\tthis.streamingMessage = undefined;\n\t\t\t\t\tthis.footer.invalidate();\n\t\t\t\t}\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\tbreak;\n\n\t\t\tcase \"tool_execution_start\": {\n\t\t\t\tlet component = this.toolPanels.getActive(event.toolCallId);\n\t\t\t\tif (!component)\n\t\t\t\t\tcomponent = this.attachToolExecutionComponent(\n\t\t\t\t\t\tevent.toolName,\n\t\t\t\t\t\tevent.toolCallId,\n\t\t\t\t\t\tevent.args,\n\t\t\t\t\t\tevent.repair,\n\t\t\t\t\t);\n\t\t\t\telse component.updateArgs(event.args, event.repair);\n\t\t\t\tcomponent.markExecutionStarted(event.repair);\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"tool_execution_update\": {\n\t\t\t\tconst component = this.toolPanels.getActive(event.toolCallId);\n\t\t\t\tif (component) {\n\t\t\t\t\tcomponent.updateArgs(event.args, event.repair);\n\t\t\t\t\tcomponent.updateResult({ ...event.partialResult, isError: false }, true);\n\t\t\t\t\tthis.ui.requestRender();\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"tool_execution_end\": {\n\t\t\t\tconst component = this.toolPanels.getActive(event.toolCallId);\n\t\t\t\tif (component) {\n\t\t\t\t\tcomponent.updateResult({ ...event.result, isError: event.isError });\n\t\t\t\t\tthis.toolPanels.registerAliases(\n\t\t\t\t\t\tcomponent,\n\t\t\t\t\t\tgetToolPanelResultActionKeys(this.getToolPanelScope(), event.toolName, event.result),\n\t\t\t\t\t);\n\t\t\t\t\tthis.toolPanels.finish(event.toolCallId);\n\t\t\t\t\tthis.ui.requestRender();\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"agent_end\":\n\t\t\t\t// Native in-process reflection fully replaces the subprocess learning paths\n\t\t\t\t// (continuous-learning AND autonomy-review) when enabled; otherwise fall back to legacy.\n\t\t\t\tif (this.isNativeReflectionEnabled()) {\n\t\t\t\t\tthis.maybeRunNativeReflection(event.messages);\n\t\t\t\t} else if (!this.maybeStartAutoLearn()) {\n\t\t\t\t\tthis.maybeStartAutonomyReview(event.messages);\n\t\t\t\t}\n\t\t\t\tif (this.settingsManager.getShowTerminalProgress()) {\n\t\t\t\t\tthis.ui.terminal.setProgress(false);\n\t\t\t\t}\n\t\t\t\tif (this.loadingAnimation) {\n\t\t\t\t\tthis.loadingAnimation.stop();\n\t\t\t\t\tthis.loadingAnimation = undefined;\n\t\t\t\t\tthis.statusContainer.clear();\n\t\t\t\t}\n\t\t\t\tif (this.streamingComponent) {\n\t\t\t\t\tthis.chatContainer.removeChild(this.streamingComponent);\n\t\t\t\t\tthis.streamingComponent = undefined;\n\t\t\t\t\tthis.streamingMessage = undefined;\n\t\t\t\t}\n\t\t\t\tthis.clearActiveToolCalls();\n\n\t\t\t\tawait this.checkShutdownRequested();\n\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\tbreak;\n\n\t\t\tcase \"compaction_start\": {\n\t\t\t\tif (this.settingsManager.getShowTerminalProgress()) {\n\t\t\t\t\tthis.ui.terminal.setProgress(true);\n\t\t\t\t}\n\t\t\t\t// Keep editor active; submissions are queued during compaction.\n\t\t\t\tthis.autoCompactionEscapeHandler = this.defaultEditor.onEscape;\n\t\t\t\tthis.defaultEditor.onEscape = () => {\n\t\t\t\t\tthis.session.abortCompaction();\n\t\t\t\t};\n\t\t\t\tthis.statusContainer.clear();\n\t\t\t\tconst cancelHint = `(${keyText(\"app.interrupt\")} to cancel)`;\n\t\t\t\tconst label =\n\t\t\t\t\tevent.reason === \"manual\"\n\t\t\t\t\t\t? `Compacting context... ${cancelHint}`\n\t\t\t\t\t\t: `${event.reason === \"overflow\" ? \"Context overflow detected, \" : \"\"}Auto-compacting... ${cancelHint}`;\n\t\t\t\tthis.autoCompactionLoader = new Loader(\n\t\t\t\t\tthis.ui,\n\t\t\t\t\t(spinner) => theme.fg(\"accent\", spinner),\n\t\t\t\t\t(text) => theme.fg(\"muted\", text),\n\t\t\t\t\tlabel,\n\t\t\t\t);\n\t\t\t\tthis.statusContainer.addChild(this.autoCompactionLoader);\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"compaction_end\": {\n\t\t\t\tif (this.settingsManager.getShowTerminalProgress()) {\n\t\t\t\t\tthis.ui.terminal.setProgress(false);\n\t\t\t\t}\n\t\t\t\tif (this.autoCompactionEscapeHandler) {\n\t\t\t\t\tthis.defaultEditor.onEscape = this.autoCompactionEscapeHandler;\n\t\t\t\t\tthis.autoCompactionEscapeHandler = undefined;\n\t\t\t\t}\n\t\t\t\tif (this.autoCompactionLoader) {\n\t\t\t\t\tthis.autoCompactionLoader.stop();\n\t\t\t\t\tthis.autoCompactionLoader = undefined;\n\t\t\t\t\tthis.statusContainer.clear();\n\t\t\t\t}\n\t\t\t\tif (event.aborted) {\n\t\t\t\t\tif (event.reason === \"manual\") {\n\t\t\t\t\t\tthis.showError(\"Compaction cancelled\");\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.showStatus(\"Auto-compaction cancelled\");\n\t\t\t\t\t}\n\t\t\t\t} else if (event.result) {\n\t\t\t\t\tawait this.rebuildChatFromMessages();\n\t\t\t\t\tthis.addMessageToChat(\n\t\t\t\t\t\tcreateCompactionSummaryMessage(\n\t\t\t\t\t\t\tevent.result.summary,\n\t\t\t\t\t\t\tevent.result.tokensBefore,\n\t\t\t\t\t\t\tnew Date().toISOString(),\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tthis.footer.invalidate();\n\t\t\t\t} else if (event.errorMessage) {\n\t\t\t\t\tif (event.reason === \"manual\") {\n\t\t\t\t\t\tthis.showError(event.errorMessage);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t\t\t\t\tthis.chatContainer.addChild(new Text(theme.fg(\"error\", event.errorMessage), 1, 0));\n\t\t\t\t\t}\n\t\t\t\t} else if (event.skipReason) {\n\t\t\t\t\t// Benign auto-compaction no-op: still tell the user WHY nothing changed —\n\t\t\t\t\t// an invisible skip is indistinguishable from broken compaction.\n\t\t\t\t\tthis.showStatus(`Auto-compaction skipped: ${event.skipReason}`);\n\t\t\t\t}\n\t\t\t\tvoid this.flushCompactionQueue({ willRetry: event.willRetry });\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"auto_retry_start\": {\n\t\t\t\t// Set up escape to abort retry\n\t\t\t\tthis.retryEscapeHandler = this.defaultEditor.onEscape;\n\t\t\t\tthis.defaultEditor.onEscape = () => {\n\t\t\t\t\tthis.session.abortRetry();\n\t\t\t\t};\n\t\t\t\t// Show retry indicator\n\t\t\t\tthis.statusContainer.clear();\n\t\t\t\tthis.retryCountdown?.dispose();\n\t\t\t\tconst retryMessage = (seconds: number) =>\n\t\t\t\t\t`Retrying (${event.attempt}/${event.maxAttempts}) in ${seconds}s... (${keyText(\"app.interrupt\")} to cancel)`;\n\t\t\t\tthis.retryLoader = new Loader(\n\t\t\t\t\tthis.ui,\n\t\t\t\t\t(spinner) => theme.fg(\"warning\", spinner),\n\t\t\t\t\t(text) => theme.fg(\"muted\", text),\n\t\t\t\t\tretryMessage(Math.ceil(event.delayMs / 1000)),\n\t\t\t\t);\n\t\t\t\tthis.retryCountdown = new CountdownTimer(\n\t\t\t\t\tevent.delayMs,\n\t\t\t\t\tthis.ui,\n\t\t\t\t\t(seconds) => {\n\t\t\t\t\t\tthis.retryLoader?.setMessage(retryMessage(seconds));\n\t\t\t\t\t},\n\t\t\t\t\t() => {\n\t\t\t\t\t\tthis.retryCountdown = undefined;\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\tthis.statusContainer.addChild(this.retryLoader);\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"auto_retry_end\": {\n\t\t\t\t// Restore escape handler\n\t\t\t\tif (this.retryEscapeHandler) {\n\t\t\t\t\tthis.defaultEditor.onEscape = this.retryEscapeHandler;\n\t\t\t\t\tthis.retryEscapeHandler = undefined;\n\t\t\t\t}\n\t\t\t\tif (this.retryCountdown) {\n\t\t\t\t\tthis.retryCountdown.dispose();\n\t\t\t\t\tthis.retryCountdown = undefined;\n\t\t\t\t}\n\t\t\t\t// Stop loader\n\t\t\t\tif (this.retryLoader) {\n\t\t\t\t\tthis.retryLoader.stop();\n\t\t\t\t\tthis.retryLoader = undefined;\n\t\t\t\t\tthis.statusContainer.clear();\n\t\t\t\t}\n\t\t\t\t// Show error only on final failure (success shows normal response)\n\t\t\t\tif (!event.success) {\n\t\t\t\t\tthis.showError(`Retry failed after ${event.attempt} attempts: ${event.finalError || \"Unknown error\"}`);\n\t\t\t\t}\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Extract text content from a user message */\n\t// Thin `this.`-delegate to the pure formatter in ./history-reload-math.ts; kept so\n\t// addMessageToChat and the prototype-based interactive-mode tests resolve it via `this`.\n\tprivate getUserMessageText(message: Message): string {\n\t\treturn historyReloadMath.getUserMessageText(message);\n\t}\n\n\tprivate setEditorInputHistory(history: readonly string[]): void {\n\t\tif (this.editor.setHistory) {\n\t\t\tthis.editor.setHistory(history);\n\t\t\treturn;\n\t\t}\n\t\tfor (const text of history) {\n\t\t\tthis.editor.addToHistory?.(text);\n\t\t}\n\t}\n\n\tprivate populateEditorInputHistoryFromMessages(messages: readonly AgentMessage[]): void {\n\t\tconst history = messages\n\t\t\t.filter((message): message is Message => message.role === \"user\")\n\t\t\t.map((message) => this.getUserMessageText(message).trim())\n\t\t\t.filter((text) => text.length > 0)\n\t\t\t.slice(-100);\n\t\tthis.setEditorInputHistory(history);\n\t}\n\n\tprivate populateEditorInputHistoryFromSession(): void {\n\t\tconst sessionManager = this.sessionManager as { getRecentUserInputHistory?: (limit?: number) => string[] };\n\t\tconst history = sessionManager.getRecentUserInputHistory?.(100);\n\t\tif (history) this.setEditorInputHistory(history);\n\t}\n\n\tprivate resetLiveTuiHistoryTrim(): void {\n\t\tthis.liveHistoryHiddenNotice = undefined;\n\t\tthis.liveHistoryHiddenComponents = 0;\n\t}\n\n\tprivate clearPendingStreamingUiUpdate(): void {\n\t\tif (!this.streamingUiUpdateTimer) return;\n\t\tclearTimeout(this.streamingUiUpdateTimer);\n\t\tthis.streamingUiUpdateTimer = undefined;\n\t}\n\n\tprivate getSessionEntryCount(): number {\n\t\tconst manager = this.sessionManager as typeof this.sessionManager & { getEntryCount?: () => number };\n\t\treturn manager.getEntryCount?.() ?? manager.getEntries().length;\n\t}\n\n\tprivate showDeferredHistoryPlaceholder(options: { requestRender?: boolean } = {}): void {\n\t\tthis.chatContainer.children = [];\n\t\tthis.resetLiveTuiHistoryTrim();\n\t\tthis.clearRenderedToolPanelState();\n\n\t\tconst entryCount = this.getSessionEntryCount();\n\t\tif (entryCount > 0) {\n\t\t\tthis.chatContainer.addChild(\n\t\t\t\tnew Text(\n\t\t\t\t\ttheme.fg(\n\t\t\t\t\t\t\"dim\",\n\t\t\t\t\t\t`History hidden for typing performance (${entryCount} entries). Press ${keyText(\"app.tools.expand\")} to load session history on demand.`,\n\t\t\t\t\t),\n\t\t\t\t\t1,\n\t\t\t\t\t0,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\n\t\tif (options.requestRender ?? true) this.ui.requestRender();\n\t}\n\n\tprivate loadTuiHistoryOnDemand(): void {\n\t\tif (this.tuiHistoryLoadInProgress) return;\n\t\tif (this.tuiHistoryLoaded || this.getSessionEntryCount() === 0) {\n\t\t\tthis.toggleToolOutputExpansion();\n\t\t\treturn;\n\t\t}\n\n\t\tthis.tuiHistoryLoadInProgress = true;\n\t\tvoid (async () => {\n\t\t\ttry {\n\t\t\t\tawait this.renderInitialMessages({ forceHistoryLoad: true });\n\t\t\t} catch (error) {\n\t\t\t\tthis.showError(`Failed to load TUI history: ${error instanceof Error ? error.message : String(error)}`);\n\t\t\t} finally {\n\t\t\t\tthis.tuiHistoryLoadInProgress = false;\n\t\t\t}\n\t\t})();\n\t}\n\n\tprivate getToolCallRepairInfo(toolCall: ToolCall): ToolCallRepairInfo | undefined {\n\t\tif (!toolCall.rawArguments && !toolCall.repairNotes?.length) return undefined;\n\t\treturn {\n\t\t\trepaired: true,\n\t\t\t...(toolCall.rawArguments ? { rawArguments: toolCall.rawArguments } : {}),\n\t\t\t...(toolCall.repairNotes?.length ? { notes: toolCall.repairNotes } : {}),\n\t\t};\n\t}\n\n\tprivate attachStreamingToolPanels(message: AssistantMessage): void {\n\t\tfor (const content of message.content) {\n\t\t\tif (content.type !== \"toolCall\") continue;\n\t\t\tconst repair = this.getToolCallRepairInfo(content);\n\t\t\tif (!this.toolPanels.hasActive(content.id)) {\n\t\t\t\tthis.attachToolExecutionComponent(content.name, content.id, content.arguments, repair);\n\t\t\t} else {\n\t\t\t\tconst component = this.toolPanels.getActive(content.id);\n\t\t\t\tif (component) {\n\t\t\t\t\tcomponent.updateArgs(content.arguments, repair);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate applyStreamingMessageUpdate(message: AssistantMessage, options: { force?: boolean } = {}): void {\n\t\tthis.streamingMessage = message;\n\t\tif (!this.streamingComponent) return;\n\n\t\tconst now = performance.now();\n\t\tconst elapsed = now - this.lastStreamingUiUpdateAt;\n\t\tconst hasToolCall = message.content.some((content) => content.type === \"toolCall\");\n\t\tconst shouldUpdateNow = options.force || hasToolCall || elapsed >= STREAMING_UI_UPDATE_INTERVAL_MS;\n\n\t\tconst update = () => {\n\t\t\tif (!this.streamingComponent || !this.streamingMessage) return;\n\t\t\tthis.streamingComponent.updateContent(this.streamingMessage);\n\t\t\tthis.attachStreamingToolPanels(this.streamingMessage);\n\t\t\tthis.lastStreamingUiUpdateAt = performance.now();\n\t\t\tthis.ui.requestRender();\n\t\t};\n\n\t\tif (shouldUpdateNow) {\n\t\t\tthis.clearPendingStreamingUiUpdate();\n\t\t\tupdate();\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.streamingUiUpdateTimer) return;\n\t\tthis.streamingUiUpdateTimer = setTimeout(\n\t\t\t() => {\n\t\t\t\tthis.streamingUiUpdateTimer = undefined;\n\t\t\t\tupdate();\n\t\t\t},\n\t\t\tMath.max(0, STREAMING_UI_UPDATE_INTERVAL_MS - elapsed),\n\t\t);\n\t}\n\n\tprivate trimLiveTuiHistory(): void {\n\t\tconst children = this.chatContainer.children;\n\t\tif (children.length <= TUI_LIVE_HISTORY_MAX_COMPONENTS) return;\n\n\t\tlet protectedStart = children.length;\n\t\tconst protect = (component: Component | undefined) => {\n\t\t\tif (!component) return;\n\t\t\tconst index = children.indexOf(component);\n\t\t\tif (index !== -1 && index < protectedStart) protectedStart = index;\n\t\t};\n\n\t\tprotect(this.streamingComponent);\n\t\tprotect(this.lastStatusSpacer);\n\t\tprotect(this.lastStatusText);\n\t\tfor (const [, component] of this.toolPanels.activeEntries()) {\n\t\t\tprotect(component);\n\t\t}\n\n\t\tconst trimStart = children[0] === this.liveHistoryHiddenNotice ? 1 : 0;\n\t\tconst targetTrimEnd = children.length - TUI_LIVE_HISTORY_TRIM_TO_COMPONENTS;\n\t\tconst trimEnd = Math.min(targetTrimEnd, protectedStart);\n\t\tif (trimEnd <= trimStart) return;\n\n\t\tconst removed = children.splice(trimStart, trimEnd - trimStart);\n\t\tthis.liveHistoryHiddenComponents += removed.length;\n\t\tif (removed.includes(this.lastStatusSpacer as Component)) this.lastStatusSpacer = undefined;\n\t\tif (removed.includes(this.lastStatusText as Component)) this.lastStatusText = undefined;\n\n\t\tconst noticeText = theme.fg(\n\t\t\t\"dim\",\n\t\t\t`Older TUI history hidden to preserve FPS (${this.liveHistoryHiddenComponents} components). Full session remains available to the model.`,\n\t\t);\n\t\tif (children[0] === this.liveHistoryHiddenNotice) {\n\t\t\tthis.liveHistoryHiddenNotice?.setText(noticeText);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.liveHistoryHiddenNotice = new Text(noticeText, 1, 0);\n\t\tchildren.unshift(this.liveHistoryHiddenNotice);\n\t}\n\n\tprivate appendStatusToChat(message: string, options: { requestRender?: boolean } = {}): void {\n\t\tconst children = this.chatContainer.children;\n\t\tconst last = children.length > 0 ? children[children.length - 1] : undefined;\n\t\tconst secondLast = children.length > 1 ? children[children.length - 2] : undefined;\n\n\t\tif (last && secondLast && last === this.lastStatusText && secondLast === this.lastStatusSpacer) {\n\t\t\tthis.lastStatusText.setText(theme.fg(\"dim\", message));\n\t\t\tif (options.requestRender ?? true) this.ui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tconst spacer = new Spacer(1);\n\t\tconst text = new Text(theme.fg(\"dim\", message), 1, 0);\n\t\tthis.chatContainer.addChild(spacer);\n\t\tthis.chatContainer.addChild(text);\n\t\tthis.lastStatusSpacer = spacer;\n\t\tthis.lastStatusText = text;\n\t\tthis.trimLiveTuiHistory();\n\t\tif (options.requestRender ?? true) this.ui.requestRender();\n\t}\n\n\t/**\n\t * Show a status message in the chat.\n\t *\n\t * If multiple status messages are emitted back-to-back (without anything else being added to the chat),\n\t * we update the previous status line instead of appending new ones to avoid log spam.\n\t */\n\tprivate showStatus(message: string): void {\n\t\tthis.appendStatusToChat(message);\n\t}\n\n\tprivate addMessageToChat(message: AgentMessage, options?: { populateHistory?: boolean }): void {\n\t\tswitch (message.role) {\n\t\t\tcase \"bashExecution\": {\n\t\t\t\tconst component = new BashExecutionComponent(message.command, this.ui, message.excludeFromContext);\n\t\t\t\tif (message.output) {\n\t\t\t\t\tcomponent.appendOutput(message.output);\n\t\t\t\t}\n\t\t\t\tcomponent.setComplete(\n\t\t\t\t\tmessage.exitCode,\n\t\t\t\t\tmessage.cancelled,\n\t\t\t\t\tmessage.truncated ? ({ truncated: true } as TruncationResult) : undefined,\n\t\t\t\t\tmessage.fullOutputPath,\n\t\t\t\t);\n\t\t\t\tthis.chatContainer.addChild(component);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"custom\": {\n\t\t\t\tif (message.display) {\n\t\t\t\t\tconst renderer = this.session.extensionRunner.getMessageRenderer(message.customType);\n\t\t\t\t\tconst component = new CustomMessageComponent(message, renderer, this.getMarkdownThemeWithSettings());\n\t\t\t\t\tcomponent.setExpanded(this.toolOutputExpanded);\n\t\t\t\t\tthis.chatContainer.addChild(component);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"compactionSummary\": {\n\t\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t\t\tconst component = new CompactionSummaryMessageComponent(message, this.getMarkdownThemeWithSettings());\n\t\t\t\tcomponent.setExpanded(this.toolOutputExpanded);\n\t\t\t\tthis.chatContainer.addChild(component);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"branchSummary\": {\n\t\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t\t\tconst component = new BranchSummaryMessageComponent(message, this.getMarkdownThemeWithSettings());\n\t\t\t\tcomponent.setExpanded(this.toolOutputExpanded);\n\t\t\t\tthis.chatContainer.addChild(component);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"user\": {\n\t\t\t\tconst textContent = this.getUserMessageText(message);\n\t\t\t\tif (textContent) {\n\t\t\t\t\tif (this.chatContainer.children.length > 0) {\n\t\t\t\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t\t\t\t}\n\t\t\t\t\tconst skillBlock = parseSkillBlock(textContent);\n\t\t\t\t\tif (skillBlock) {\n\t\t\t\t\t\t// Render skill block (collapsible)\n\t\t\t\t\t\tconst component = new SkillInvocationMessageComponent(\n\t\t\t\t\t\t\tskillBlock,\n\t\t\t\t\t\t\tthis.getMarkdownThemeWithSettings(),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tcomponent.setExpanded(this.toolOutputExpanded);\n\t\t\t\t\t\tthis.chatContainer.addChild(component);\n\t\t\t\t\t\t// Render user message separately if present\n\t\t\t\t\t\tif (skillBlock.userMessage) {\n\t\t\t\t\t\t\tconst userComponent = new UserMessageComponent(\n\t\t\t\t\t\t\t\tskillBlock.userMessage,\n\t\t\t\t\t\t\t\tthis.getMarkdownThemeWithSettings(),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tthis.chatContainer.addChild(userComponent);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst userComponent = new UserMessageComponent(textContent, this.getMarkdownThemeWithSettings());\n\t\t\t\t\t\tthis.chatContainer.addChild(userComponent);\n\t\t\t\t\t}\n\t\t\t\t\tif (options?.populateHistory) {\n\t\t\t\t\t\tthis.editor.addToHistory?.(textContent);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"assistant\": {\n\t\t\t\tconst assistantComponent = new AssistantMessageComponent(\n\t\t\t\t\tmessage,\n\t\t\t\t\tthis.hideThinkingBlock,\n\t\t\t\t\tthis.getMarkdownThemeWithSettings(),\n\t\t\t\t\tthis.hiddenThinkingLabel,\n\t\t\t\t);\n\t\t\t\tthis.chatContainer.addChild(assistantComponent);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"toolResult\": {\n\t\t\t\t// Tool results are rendered inline with tool calls, handled separately\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault: {\n\t\t\t\tconst _exhaustive: never = message;\n\t\t\t}\n\t\t}\n\t\tthis.trimLiveTuiHistory();\n\t}\n\n\t// Thin `this.`-delegate to the pure reload-window math in ./history-reload-math.ts;\n\t// kept so renderSessionContext and the prototype-based history tests resolve it via `this`.\n\tprivate messagesForTuiHistoryReload(messages: AgentMessage[]): {\n\t\tmessages: AgentMessage[];\n\t\tomittedMessages: number;\n\t\testimatedLines: number;\n\t} {\n\t\treturn historyReloadMath.messagesForTuiHistoryReload(messages);\n\t}\n\n\t/**\n\t * Render session context to chat. Used for initial load and rebuild after compaction.\n\t * @param sessionContext Session context to render\n\t * @param options.updateFooter Update footer state\n\t * @param options.populateHistory Add user messages to editor history\n\t */\n\tprivate renderGeneration = 0;\n\tprivate renderQueue?: Promise<void>;\n\n\tprivate async renderSessionContext(\n\t\tsessionContext: SessionContext,\n\t\toptions: { updateFooter?: boolean; populateHistory?: boolean } = {},\n\t): Promise<void> {\n\t\tconst run = async () => {\n\t\t\t// Build long history offscreen, then atomically swap it into the visible\n\t\t\t// chat container. This keeps the TUI responsive without flashing blank or\n\t\t\t// partial transcript frames during resume/reload/compaction rebuilds.\n\t\t\tthis.renderGeneration = (this.renderGeneration ?? 0) + 1;\n\t\t\tconst generation = this.renderGeneration;\n\t\t\tlet processed = 0;\n\t\t\tlet committed = false;\n\n\t\t\tconst visibleChatContainer = this.chatContainer;\n\t\t\tconst previousLiveHistoryHiddenNotice = this.liveHistoryHiddenNotice;\n\t\t\tconst previousLiveHistoryHiddenComponents = this.liveHistoryHiddenComponents;\n\t\t\tconst previousLastStatusSpacer = this.lastStatusSpacer;\n\t\t\tconst previousLastStatusText = this.lastStatusText;\n\t\t\tconst stagingChatContainer = new Container();\n\n\t\t\tthis.chatContainer = stagingChatContainer;\n\t\t\tthis.resetLiveTuiHistoryTrim();\n\t\t\tthis.clearRenderedToolPanelState();\n\t\t\tconst renderedPendingTools = new Map<string, ToolExecutionComponent>();\n\n\t\t\ttry {\n\t\t\t\tif (options.updateFooter) {\n\t\t\t\t\tthis.footer.invalidate();\n\t\t\t\t\tthis.updateEditorBorderColor();\n\t\t\t\t}\n\n\t\t\t\tconst tuiHistory = this.messagesForTuiHistoryReload(sessionContext.messages);\n\t\t\t\tif (tuiHistory.omittedMessages > 0) {\n\t\t\t\t\tthis.appendStatusToChat(\n\t\t\t\t\t\t`Showing last ~${historyReloadMath.TUI_HISTORY_RELOAD_MAX_LINES} TUI history lines; omitted ${tuiHistory.omittedMessages} older message${tuiHistory.omittedMessages === 1 ? \"\" : \"s\"}. Full session remains available to the model.`,\n\t\t\t\t\t\t{ requestRender: false },\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tfor (const message of tuiHistory.messages) {\n\t\t\t\t\tif (processed > 0 && processed % TUI_HISTORY_RELOAD_CHUNK_SIZE === 0) {\n\t\t\t\t\t\tawait new Promise((resolve) => setImmediate(resolve));\n\t\t\t\t\t\tif (generation !== this.renderGeneration) return;\n\t\t\t\t\t}\n\t\t\t\t\tprocessed++;\n\t\t\t\t\t// Assistant messages need special handling for tool calls\n\t\t\t\t\tif (message.role === \"assistant\") {\n\t\t\t\t\t\tthis.addMessageToChat(message);\n\t\t\t\t\t\t// Render tool call components\n\t\t\t\t\t\tfor (const content of message.content) {\n\t\t\t\t\t\t\tif (content.type === \"toolCall\") {\n\t\t\t\t\t\t\t\tconst component = this.attachToolExecutionComponent(\n\t\t\t\t\t\t\t\t\tcontent.name,\n\t\t\t\t\t\t\t\t\tcontent.id,\n\t\t\t\t\t\t\t\t\tcontent.arguments,\n\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\tif (message.stopReason === \"aborted\" || message.stopReason === \"error\") {\n\t\t\t\t\t\t\t\t\tlet errorMessage: string;\n\t\t\t\t\t\t\t\t\tif (message.stopReason === \"aborted\") {\n\t\t\t\t\t\t\t\t\t\tconst retryAttempt = this.session.retryAttempt;\n\t\t\t\t\t\t\t\t\t\terrorMessage =\n\t\t\t\t\t\t\t\t\t\t\tretryAttempt > 0\n\t\t\t\t\t\t\t\t\t\t\t\t? `Aborted after ${retryAttempt} retry attempt${retryAttempt > 1 ? \"s\" : \"\"}`\n\t\t\t\t\t\t\t\t\t\t\t\t: \"Operation aborted\";\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\terrorMessage = message.errorMessage || \"Error\";\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tcomponent.updateResult({ content: [{ type: \"text\", text: errorMessage }], isError: true });\n\t\t\t\t\t\t\t\t\tthis.toolPanels.finish(content.id);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\trenderedPendingTools.set(content.id, component);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (message.role === \"toolResult\") {\n\t\t\t\t\t\t// Match tool results to pending tool components\n\t\t\t\t\t\tconst component = renderedPendingTools.get(message.toolCallId);\n\t\t\t\t\t\tif (component) {\n\t\t\t\t\t\t\tcomponent.updateResult(message);\n\t\t\t\t\t\t\trenderedPendingTools.delete(message.toolCallId);\n\t\t\t\t\t\t\tthis.toolPanels.finish(message.toolCallId);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// All other messages use standard rendering\n\t\t\t\t\t\tthis.addMessageToChat(message, options);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (generation !== this.renderGeneration) return;\n\t\t\t\tvisibleChatContainer.children = stagingChatContainer.children;\n\t\t\t\tcommitted = true;\n\t\t\t} finally {\n\t\t\t\tconst stagedLiveHistoryHiddenNotice = this.liveHistoryHiddenNotice;\n\t\t\t\tconst stagedLiveHistoryHiddenComponents = this.liveHistoryHiddenComponents;\n\t\t\t\tconst stagedLastStatusSpacer = this.lastStatusSpacer;\n\t\t\t\tconst stagedLastStatusText = this.lastStatusText;\n\n\t\t\t\tthis.chatContainer = visibleChatContainer;\n\t\t\t\tif (committed) {\n\t\t\t\t\tthis.liveHistoryHiddenNotice = stagedLiveHistoryHiddenNotice;\n\t\t\t\t\tthis.liveHistoryHiddenComponents = stagedLiveHistoryHiddenComponents;\n\t\t\t\t\tthis.lastStatusSpacer = stagedLastStatusSpacer;\n\t\t\t\t\tthis.lastStatusText = stagedLastStatusText;\n\t\t\t\t} else {\n\t\t\t\t\tthis.liveHistoryHiddenNotice = previousLiveHistoryHiddenNotice;\n\t\t\t\t\tthis.liveHistoryHiddenComponents = previousLiveHistoryHiddenComponents;\n\t\t\t\t\tthis.lastStatusSpacer = previousLastStatusSpacer;\n\t\t\t\t\tthis.lastStatusText = previousLastStatusText;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (committed) this.ui.requestRender();\n\t\t};\n\n\t\tthis.renderQueue = (this.renderQueue ?? Promise.resolve()).then(run, run);\n\t\treturn this.renderQueue;\n\t}\n\n\tasync renderInitialMessages(options: { forceHistoryLoad?: boolean } = {}): Promise<void> {\n\t\tif (!options.forceHistoryLoad) {\n\t\t\tthis.populateEditorInputHistoryFromSession();\n\t\t\tthis.tuiHistoryLoaded = false;\n\t\t\tthis.showDeferredHistoryPlaceholder({ requestRender: true });\n\t\t\tthis.footer.invalidate();\n\t\t\tthis.updateEditorBorderColor();\n\t\t\treturn;\n\t\t}\n\n\t\t// Get aligned messages and entries from session context only when the user\n\t\t// explicitly requests TUI history. The model/session state is already loaded.\n\t\tconst context = this.sessionManager.buildSessionContext();\n\t\tthis.populateEditorInputHistoryFromMessages(context.messages);\n\t\tawait this.renderSessionContext(context, {\n\t\t\tupdateFooter: true,\n\t\t});\n\t\tthis.tuiHistoryLoaded = true;\n\n\t\t// Show compaction info if session was compacted\n\t\tconst allEntries = this.sessionManager.getEntries();\n\t\tconst compactionCount = allEntries.filter((e) => e.type === \"compaction\").length;\n\t\tif (compactionCount > 0) {\n\t\t\tconst times = compactionCount === 1 ? \"1 time\" : `${compactionCount} times`;\n\t\t\tthis.showStatus(`Session compacted ${times}`);\n\t\t}\n\t}\n\n\tasync getUserInput(): Promise<UserInputSubmission> {\n\t\tconst queuedInput = this.pendingUserInputs.shift();\n\t\tif (queuedInput !== undefined) {\n\t\t\treturn queuedInput;\n\t\t}\n\n\t\treturn new Promise((resolve) => {\n\t\t\tthis.onInputCallback = (submission: UserInputSubmission) => {\n\t\t\t\tthis.onInputCallback = undefined;\n\t\t\t\tresolve(submission);\n\t\t\t};\n\t\t});\n\t}\n\n\tprivate async rebuildChatFromMessages(): Promise<void> {\n\t\tif (!this.tuiHistoryLoaded) {\n\t\t\tthis.showDeferredHistoryPlaceholder({ requestRender: true });\n\t\t\treturn;\n\t\t}\n\t\tconst context = this.sessionManager.buildSessionContext();\n\t\tawait this.renderSessionContext(context);\n\t}\n\n\t// =========================================================================\n\t// Key handlers\n\t// =========================================================================\n\n\tprivate handleCtrlC(): void {\n\t\tconst now = Date.now();\n\t\tif (now - this.lastSigintTime < 500) {\n\t\t\tvoid this.shutdown();\n\t\t} else {\n\t\t\tthis.clearEditor();\n\t\t\tthis.lastSigintTime = now;\n\t\t}\n\t}\n\n\tprivate handleCtrlD(): void {\n\t\t// Only called when editor is empty (enforced by CustomEditor)\n\t\tvoid this.shutdown();\n\t}\n\n\t/**\n\t * Gracefully shutdown the agent.\n\t * Stops the TUI before emitting shutdown events so extension UI cleanup cannot\n\t * repaint the final frame while the process is exiting.\n\t */\n\tprivate isShuttingDown = false;\n\n\tprivate shutdown(options?: { fromSignal?: boolean }): Promise<void> {\n\t\tconst self = this;\n\t\treturn signalLifecycle.shutdown(\n\t\t\t{\n\t\t\t\tget isShuttingDown() {\n\t\t\t\t\treturn self.isShuttingDown;\n\t\t\t\t},\n\t\t\t\tset isShuttingDown(value) {\n\t\t\t\t\tself.isShuttingDown = value;\n\t\t\t\t},\n\t\t\t\tget signalCleanupHandlers() {\n\t\t\t\t\treturn self.signalCleanupHandlers;\n\t\t\t\t},\n\t\t\t\tset signalCleanupHandlers(value) {\n\t\t\t\t\tself.signalCleanupHandlers = value;\n\t\t\t\t},\n\t\t\t\tget shutdownRequested() {\n\t\t\t\t\treturn self.shutdownRequested;\n\t\t\t\t},\n\t\t\t\truntimeHost: this.runtimeHost,\n\t\t\t\tui: this.ui,\n\t\t\t\tstop: () => this.stop(),\n\t\t\t\tformatResumeCommand: () => formatResumeCommand(this.sessionManager),\n\t\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\t\tshutdown: (opts) => this.shutdown(opts),\n\t\t\t\tunregisterSignalHandlers: () => this.unregisterSignalHandlers(),\n\t\t\t\temergencyTerminalExit: () => this.emergencyTerminalExit(),\n\t\t\t\tuncaughtCrash: (error) => this.uncaughtCrash(error),\n\t\t\t},\n\t\t\toptions,\n\t\t);\n\t}\n\n\tprivate emergencyTerminalExit(): never {\n\t\treturn signalLifecycle.emergencyTerminalExit(this as unknown as signalLifecycle.SignalLifecycleHost);\n\t}\n\n\tprivate uncaughtCrash(error: Error): never {\n\t\treturn signalLifecycle.uncaughtCrash(this as unknown as signalLifecycle.SignalLifecycleHost, error);\n\t}\n\n\tprivate checkShutdownRequested(): Promise<void> {\n\t\treturn signalLifecycle.checkShutdownRequested(this as unknown as signalLifecycle.SignalLifecycleHost);\n\t}\n\n\tprivate registerSignalHandlers(): void {\n\t\tsignalLifecycle.registerSignalHandlers(this as unknown as signalLifecycle.SignalLifecycleHost);\n\t}\n\n\tprivate unregisterSignalHandlers(): void {\n\t\tsignalLifecycle.unregisterSignalHandlers(this as unknown as signalLifecycle.SignalLifecycleHost);\n\t}\n\n\tprivate handleCtrlZ(): void {\n\t\tsignalLifecycle.handleCtrlZ({ ui: this.ui, showStatus: (message) => this.showStatus(message) });\n\t}\n\n\tprivate async handleFollowUp(): Promise<void> {\n\t\tconst text = (this.editor.getExpandedText?.() ?? this.editor.getText()).trim();\n\t\tif (!text) return;\n\n\t\t// Queue input during compaction (extension commands execute immediately)\n\t\tif (this.session.isCompacting) {\n\t\t\tif (this.isExtensionCommand(text)) {\n\t\t\t\tthis.editor.addToHistory?.(text);\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\ttry {\n\t\t\t\t\tawait this.session.prompt(text);\n\t\t\t\t} finally {\n\t\t\t\t\tthis.refreshAutonomyFooterStatus();\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconst images = this.takeClipboardImagesForText(text);\n\t\t\t\tthis.queueCompactionMessage(text, \"followUp\", images);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Alt+Enter queues a follow-up message (waits until agent finishes)\n\t\t// This handles extension commands (execute immediately), prompt template expansion, and queueing\n\t\tif (this.session.isStreaming) {\n\t\t\tconst images = this.takeClipboardImagesForText(text);\n\t\t\tthis.editor.addToHistory?.(text);\n\t\t\tthis.editor.setText(\"\");\n\t\t\ttry {\n\t\t\t\tawait this.session.prompt(text, { streamingBehavior: \"followUp\", images });\n\t\t\t} finally {\n\t\t\t\tthis.refreshAutonomyFooterStatus();\n\t\t\t}\n\t\t\tthis.updatePendingMessagesDisplay();\n\t\t\tthis.ui.requestRender();\n\t\t}\n\t\t// If not streaming, Alt+Enter acts like regular Enter (trigger onSubmit)\n\t\telse if (this.editor.onSubmit) {\n\t\t\tawait this.editor.onSubmit(text);\n\t\t\tthis.editor.setText(\"\");\n\t\t}\n\t}\n\n\tprivate handleDequeue(): void {\n\t\tconst restored = this.restoreQueuedMessagesToEditor();\n\t\tif (restored === 0) {\n\t\t\tthis.showStatus(\"No queued messages to restore\");\n\t\t} else {\n\t\t\tthis.showStatus(`Restored ${restored} queued message${restored > 1 ? \"s\" : \"\"} to editor`);\n\t\t}\n\t}\n\n\tprivate updateEditorBorderColor(): void {\n\t\tif (this.isBashMode) {\n\t\t\tthis.editor.borderColor = theme.getBashModeBorderColor();\n\t\t} else {\n\t\t\tconst level = this.session.thinkingLevel || \"off\";\n\t\t\tthis.editor.borderColor = theme.getThinkingBorderColor(level);\n\t\t}\n\t\tthis.ui.requestRender();\n\t}\n\n\tprivate cycleThinkingLevel(): void {\n\t\tconst newLevel = this.session.cycleThinkingLevel();\n\t\tif (newLevel === undefined) {\n\t\t\tthis.showStatus(\"Current model does not support thinking\");\n\t\t} else {\n\t\t\tthis.footer.invalidate();\n\t\t\tthis.updateEditorBorderColor();\n\t\t\tthis.showStatus(`Thinking level: ${newLevel}`);\n\t\t}\n\t}\n\n\tprivate async cycleModel(direction: \"forward\" | \"backward\"): Promise<void> {\n\t\ttry {\n\t\t\tconst result = await this.session.cycleModel(direction);\n\t\t\tif (result === undefined) {\n\t\t\t\tconst msg = this.session.scopedModels.length > 0 ? \"Only one model in scope\" : \"Only one model available\";\n\t\t\t\tthis.showStatus(msg);\n\t\t\t} else {\n\t\t\t\tthis.footer.invalidate();\n\t\t\t\tthis.updateEditorBorderColor();\n\t\t\t\tconst thinkingStr =\n\t\t\t\t\tresult.model.reasoning && result.thinkingLevel !== \"off\" ? ` (thinking: ${result.thinkingLevel})` : \"\";\n\t\t\t\tthis.showStatus(`Switched to ${result.model.name || result.model.id}${thinkingStr}`);\n\t\t\t\tvoid this.maybeWarnAboutAnthropicSubscriptionAuth(result.model);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tthis.showError(error instanceof Error ? error.message : String(error));\n\t\t}\n\t}\n\n\tprivate toggleToolOutputExpansion(): void {\n\t\tthis.setToolsExpanded(!this.toolOutputExpanded);\n\t}\n\n\tprivate setToolsExpanded(expanded: boolean): void {\n\t\tthis.toolOutputExpanded = expanded;\n\t\tconst activeHeader = this.extensionUiHost.getCustomHeader() ?? this.builtInHeader;\n\t\tif (isExpandable(activeHeader)) {\n\t\t\tactiveHeader.setExpanded(expanded);\n\t\t}\n\t\tfor (const child of this.chatContainer.children) {\n\t\t\tif (isExpandable(child)) {\n\t\t\t\tchild.setExpanded(expanded);\n\t\t\t}\n\t\t}\n\t\tthis.ui.requestRender();\n\t}\n\n\tprivate async toggleThinkingBlockVisibility(): Promise<void> {\n\t\tthis.hideThinkingBlock = !this.hideThinkingBlock;\n\t\tthis.settingsManager.setHideThinkingBlock(this.hideThinkingBlock);\n\n\t\t// Rebuild chat from session messages\n\t\tawait this.rebuildChatFromMessages();\n\n\t\t// If streaming, re-add the streaming component with updated visibility and re-render\n\t\tif (this.streamingComponent && this.streamingMessage) {\n\t\t\tthis.streamingComponent.setHideThinkingBlock(this.hideThinkingBlock);\n\t\t\tthis.streamingComponent.updateContent(this.streamingMessage);\n\t\t\tthis.chatContainer.addChild(this.streamingComponent);\n\t\t}\n\n\t\tthis.showStatus(`Thinking blocks: ${this.hideThinkingBlock ? \"hidden\" : \"visible\"}`);\n\t}\n\n\tprivate openExternalEditor(): Promise<void> {\n\t\treturn openExternalEditor({\n\t\t\teditor: this.editor,\n\t\t\tui: this.ui,\n\t\t\tshowWarning: (message) => this.showWarning(message),\n\t\t});\n\t}\n\n\tprivate openEditorForPath(filePath: string): Promise<boolean> {\n\t\treturn openEditorForPath(\n\t\t\t{\n\t\t\t\teditor: this.editor,\n\t\t\t\tui: this.ui,\n\t\t\t\tshowWarning: (message) => this.showWarning(message),\n\t\t\t},\n\t\t\tfilePath,\n\t\t);\n\t}\n\n\t// =========================================================================\n\t// UI helpers\n\t// =========================================================================\n\n\tclearEditor(): void {\n\t\tthis.editor.setText(\"\");\n\t\tthis.ui.requestRender();\n\t}\n\n\tshowError(errorMessage: string): void {\n\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\tthis.chatContainer.addChild(new Text(theme.fg(\"error\", `Error: ${errorMessage}`), 1, 0));\n\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\tthis.ui.requestRender();\n\t}\n\n\tshowWarning(warningMessage: string): void {\n\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\tthis.chatContainer.addChild(new Text(theme.fg(\"warning\", `Warning: ${warningMessage}`), 1, 0));\n\t\tthis.ui.requestRender();\n\t}\n\n\tshowNewVersionNotification(release: LatestPiRelease): void {\n\t\tstartupChecks.showNewVersionNotification(this.startupChecksHost(), release);\n\t}\n\n\tshowPackageUpdateNotification(packages: string[]): void {\n\t\tstartupChecks.showPackageUpdateNotification(this.startupChecksHost(), packages);\n\t}\n\n\t/**\n\t * Get all queued messages (read-only).\n\t * Combines session queue and compaction queue.\n\t */\n\tprivate getAllQueuedMessages(): { steering: string[]; followUp: string[] } {\n\t\treturn {\n\t\t\tsteering: [\n\t\t\t\t...this.session.getSteeringMessages(),\n\t\t\t\t...this.compactionQueuedMessages.filter((msg) => msg.mode === \"steer\").map((msg) => msg.text),\n\t\t\t],\n\t\t\tfollowUp: [\n\t\t\t\t...this.session.getFollowUpMessages(),\n\t\t\t\t...this.session.getQueuedExtensionCommands(),\n\t\t\t\t...this.compactionQueuedMessages.filter((msg) => msg.mode === \"followUp\").map((msg) => msg.text),\n\t\t\t],\n\t\t};\n\t}\n\n\t/**\n\t * Clear all queued messages and return their contents.\n\t * Clears both session queue and compaction queue.\n\t */\n\tprivate clearAllQueues(): { steering: string[]; followUp: string[] } {\n\t\tconst { steering, followUp, commands } = this.session.clearQueue();\n\t\tconst compactionSteering = this.compactionQueuedMessages\n\t\t\t.filter((msg) => msg.mode === \"steer\")\n\t\t\t.map((msg) => msg.text);\n\t\tconst compactionFollowUp = this.compactionQueuedMessages\n\t\t\t.filter((msg) => msg.mode === \"followUp\")\n\t\t\t.map((msg) => msg.text);\n\t\tthis.compactionQueuedMessages = [];\n\t\treturn {\n\t\t\tsteering: [...steering, ...compactionSteering],\n\t\t\tfollowUp: [...followUp, ...commands, ...compactionFollowUp],\n\t\t};\n\t}\n\n\tprivate updatePendingMessagesDisplay(): void {\n\t\tthis.pendingMessagesContainer.clear();\n\t\tconst { steering: steeringMessages, followUp: followUpMessages } = this.getAllQueuedMessages();\n\t\tif (steeringMessages.length > 0 || followUpMessages.length > 0) {\n\t\t\tthis.pendingMessagesContainer.addChild(new Spacer(1));\n\t\t\tfor (const message of steeringMessages) {\n\t\t\t\tconst text = theme.fg(\"dim\", `Steering: ${message}`);\n\t\t\t\tthis.pendingMessagesContainer.addChild(new TruncatedText(text, 1, 0));\n\t\t\t}\n\t\t\tfor (const message of followUpMessages) {\n\t\t\t\tconst text = theme.fg(\"dim\", `Follow-up: ${message}`);\n\t\t\t\tthis.pendingMessagesContainer.addChild(new TruncatedText(text, 1, 0));\n\t\t\t}\n\t\t\tconst dequeueHint = this.getAppKeyDisplay(\"app.message.dequeue\");\n\t\t\tconst hintText = theme.fg(\"dim\", `↳ ${dequeueHint} to edit all queued messages`);\n\t\t\tthis.pendingMessagesContainer.addChild(new TruncatedText(hintText, 1, 0));\n\t\t}\n\t}\n\n\tprivate restoreQueuedMessagesToEditor(options?: { abort?: boolean; currentText?: string }): number {\n\t\tconst { steering, followUp } = this.clearAllQueues();\n\t\tconst allQueued = [...steering, ...followUp];\n\t\tif (allQueued.length === 0) {\n\t\t\tthis.updatePendingMessagesDisplay();\n\t\t\tif (options?.abort) {\n\t\t\t\tthis.agent.abort();\n\t\t\t}\n\t\t\treturn 0;\n\t\t}\n\t\tconst queuedText = allQueued.join(\"\\n\\n\");\n\t\tconst currentText = options?.currentText ?? this.editor.getText();\n\t\tconst combinedText = [queuedText, currentText].filter((t) => t.trim()).join(\"\\n\\n\");\n\t\tthis.editor.setText(combinedText);\n\t\tthis.updatePendingMessagesDisplay();\n\t\tif (options?.abort) {\n\t\t\tthis.agent.abort();\n\t\t}\n\t\treturn allQueued.length;\n\t}\n\n\tprivate queueCompactionMessage(text: string, mode: \"steer\" | \"followUp\", images?: ImageContent[]): void {\n\t\tthis.compactionQueuedMessages.push({ text, mode, images });\n\t\tthis.editor.addToHistory?.(text);\n\t\tthis.editor.setText(\"\");\n\t\tthis.updatePendingMessagesDisplay();\n\t\tthis.showStatus(\"Queued message for after compaction\");\n\t}\n\n\tprivate isExtensionCommand(text: string): boolean {\n\t\tif (!text.startsWith(\"/\")) return false;\n\n\t\tconst extensionRunner = this.session.extensionRunner;\n\n\t\tconst spaceIndex = text.indexOf(\" \");\n\t\tconst commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);\n\t\treturn !!extensionRunner.getCommand(commandName);\n\t}\n\n\tprivate flushCompactionQueue(options?: { willRetry?: boolean }): Promise<void> {\n\t\tconst self = this;\n\t\treturn compactionQueue.flushCompactionQueue(\n\t\t\t{\n\t\t\t\tget compactionQueuedMessages() {\n\t\t\t\t\treturn self.compactionQueuedMessages;\n\t\t\t\t},\n\t\t\t\tset compactionQueuedMessages(value) {\n\t\t\t\t\tself.compactionQueuedMessages = value;\n\t\t\t\t},\n\t\t\t\tsession: this.session,\n\t\t\t\tupdatePendingMessagesDisplay: () => this.updatePendingMessagesDisplay(),\n\t\t\t\tshowError: (message) => this.showError(message),\n\t\t\t\tisExtensionCommand: (text) => this.isExtensionCommand(text),\n\t\t\t\trefreshAutonomyFooterStatus: () => this.refreshAutonomyFooterStatus(),\n\t\t\t},\n\t\t\toptions,\n\t\t);\n\t}\n\n\t/** Move pending bash components from pending area to chat */\n\tprivate flushPendingBashComponents(): void {\n\t\tfor (const component of this.pendingBashComponents) {\n\t\t\tthis.pendingMessagesContainer.removeChild(component);\n\t\t\tthis.chatContainer.addChild(component);\n\t\t}\n\t\tthis.pendingBashComponents = [];\n\t}\n\n\t// =========================================================================\n\t// Selectors\n\t// =========================================================================\n\n\t/**\n\t * Shows a selector component in place of the editor.\n\t * @param create Factory that receives a `done` callback and returns the component and focus target\n\t */\n\tprivate showSelector(create: (done: () => void) => { component: Component; focus: Component }): void {\n\t\tconst done = () => {\n\t\t\tthis.overlayHost.swap(this.editor, { focusMode: \"restore\", render: \"none\" });\n\t\t};\n\t\tconst { component, focus } = create(done);\n\t\tthis.overlayHost.swap(component, { focus });\n\t}\n\n\t/** Narrow seam shared by the session-picker/tree/fork and model-selector flows. */\n\tprivate sessionFlowHost(): sessionFlows.SessionFlowHost {\n\t\tconst self = this;\n\t\treturn {\n\t\t\tsession: this.session,\n\t\t\tsessionManager: this.sessionManager,\n\t\t\tsettingsManager: this.settingsManager,\n\t\t\truntimeHost: this.runtimeHost,\n\t\t\tui: this.ui,\n\t\t\tchatContainer: this.chatContainer,\n\t\t\tstatusContainer: this.statusContainer,\n\t\t\teditor: this.editor,\n\t\t\tdefaultEditor: this.defaultEditor,\n\t\t\tfooter: this.footer,\n\t\t\textensionUiHost: this.extensionUiHost,\n\t\t\tkeybindings: this.keybindings,\n\t\t\tget loadingAnimation() {\n\t\t\t\treturn self.loadingAnimation;\n\t\t\t},\n\t\t\tset loadingAnimation(value) {\n\t\t\t\tself.loadingAnimation = value;\n\t\t\t},\n\t\t\tshowSelector: (create) => this.showSelector(create),\n\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\tshowError: (message) => this.showError(message),\n\t\t\trenderCurrentSessionState: () => this.renderCurrentSessionState(),\n\t\t\trenderInitialMessages: (options) => this.renderInitialMessages(options),\n\t\t\tflushCompactionQueue: (options) => this.flushCompactionQueue(options),\n\t\t\thandleFatalRuntimeError: (prefix, error) => this.handleFatalRuntimeError(prefix, error),\n\t\t\tpromptForMissingSessionCwd: (error) => this.promptForMissingSessionCwd(error),\n\t\t\tupdateEditorBorderColor: () => this.updateEditorBorderColor(),\n\t\t\tupdateAvailableProviderCount: () => this.updateAvailableProviderCount(),\n\t\t\tmaybeWarnAboutAnthropicSubscriptionAuth: (model) => this.maybeWarnAboutAnthropicSubscriptionAuth(model),\n\t\t\tcheckDaxnutsEasterEgg: (model) => this.checkDaxnutsEasterEgg(model),\n\t\t\tgetModelCandidates: () => this.getModelCandidates(),\n\t\t\tshutdown: (options) => this.shutdown(options),\n\t\t};\n\t}\n\n\tprivate getAutoLearnModelOptions(): SelectItem[] {\n\t\treturn this.autoLearnController.getAutoLearnModelOptions();\n\t}\n\n\tprivate getAutoLearnDataDir(): string {\n\t\treturn this.autoLearnController.getAutoLearnDataDir();\n\t}\n\n\tprivate getPrunedAutoLearnState(): AutoLearnState {\n\t\treturn this.autoLearnController.getPrunedAutoLearnState();\n\t}\n\n\tprivate getAutoLearnPresetForAutonomyMode(\n\t\tmode: AutonomyMode,\n\t\tcurrent: AutoLearnSettings = {},\n\t): Required<AutoLearnSettings> {\n\t\treturn this.autoLearnController.getAutoLearnPresetForAutonomyMode(mode, current);\n\t}\n\n\tprivate getEffectiveAutoLearnSettings(): Required<AutoLearnSettings> {\n\t\treturn this.autoLearnController.getEffectiveAutoLearnSettings();\n\t}\n\n\tprivate getCurrentAutoLearnSettings(): Required<AutoLearnSettings> {\n\t\treturn this.autoLearnController.getCurrentAutoLearnSettings();\n\t}\n\n\tprivate getAutoLearnTenantKey(): string {\n\t\treturn this.autoLearnController.getAutoLearnTenantKey();\n\t}\n\n\tprivate getAutoLearnTenantDataDir(): string {\n\t\treturn this.autoLearnController.getAutoLearnTenantDataDir();\n\t}\n\n\tprivate validateAutoLearnModelValue(value: string | undefined): string | undefined {\n\t\treturn this.autoLearnController.validateAutoLearnModelValue(value);\n\t}\n\n\tprivate collectSelfModificationCandidates(settings: { sourcePath?: string; sourcePaths?: string[] }): string[] {\n\t\treturn [\n\t\t\t...(Array.isArray(settings.sourcePaths) ? settings.sourcePaths : []),\n\t\t\t...(settings.sourcePath ? [settings.sourcePath] : []),\n\t\t]\n\t\t\t.map((candidate) => candidate?.trim())\n\t\t\t.filter((candidate): candidate is string => Boolean(candidate));\n\t}\n\n\tprivate getCurrentCwdForSettings(): string {\n\t\treturn this.runtimeHost?.session?.sessionManager?.getCwd?.() || process.cwd();\n\t}\n\n\tprivate resolveSelfModificationSource(settings: {\n\t\tsourcePath?: string;\n\t\tsourcePaths?: string[];\n\t}): string | undefined {\n\t\tconst cwd = this.getCurrentCwdForSettings();\n\t\tconst resolved = this.collectSelfModificationCandidates(settings).map((candidate) =>\n\t\t\tresolvePath(candidate, cwd, { trim: true }),\n\t\t);\n\t\tif (resolved.length === 0) return undefined;\n\t\treturn (\n\t\t\tresolved.find(\n\t\t\t\t(candidate) => fs.existsSync(candidate) && fs.existsSync(path.join(candidate, \"package.json\")),\n\t\t\t) ?? resolved[0]\n\t\t);\n\t}\n\n\tprivate validateSelfModificationSource(settings: SelfModificationSettings): string | undefined {\n\t\tif (!settings.enabled) return undefined;\n\t\tconst cwd = this.getCurrentCwdForSettings();\n\t\tconst resolved = this.collectSelfModificationCandidates(settings).map((candidate) =>\n\t\t\tresolvePath(candidate, cwd, { trim: true }),\n\t\t);\n\t\tif (resolved.length === 0) return \"Self modification is enabled, but no pi-adaptative source path is set.\";\n\t\tconst valid = resolved.find(\n\t\t\t(candidate) =>\n\t\t\t\tfs.existsSync(candidate) &&\n\t\t\t\tfs.existsSync(path.join(candidate, \"package.json\")) &&\n\t\t\t\tfs.existsSync(path.join(candidate, \"packages\", \"coding-agent\")),\n\t\t);\n\t\tif (valid) return undefined;\n\t\tconst first = resolved[0];\n\t\tif (!fs.existsSync(first)) return `Self modification source path does not exist: ${first}`;\n\t\tif (!fs.existsSync(path.join(first, \"package.json\"))) {\n\t\t\treturn `Self modification source path has no package.json: ${first}`;\n\t\t}\n\t\treturn `Self modification source path does not look like pi-adaptative (missing packages/coding-agent): ${first}`;\n\t}\n\n\tprivate launchAutoLearn(\n\t\treason: string,\n\t\tforce = false,\n\t\toptions: {\n\t\t\tcooldownKind?: \"auto\" | \"reflection\";\n\t\t\tpromptKind?: \"auto\" | \"reflection\";\n\t\t\tturnDigest?: string;\n\t\t\tbypassReflectionCooldown?: boolean;\n\t\t} = {},\n\t): string {\n\t\treturn this.autoLearnController.launchAutoLearn(reason, force, options);\n\t}\n\n\tprivate isNativeReflectionEnabled(): boolean {\n\t\treturn this.autoLearnController.isNativeReflectionEnabled();\n\t}\n\n\tprivate maybeRunNativeReflection(messages: AgentMessage[]): void {\n\t\tthis.autoLearnController.maybeRunNativeReflection(messages);\n\t}\n\n\tprivate maybeStartAutoLearn(): boolean {\n\t\treturn this.autoLearnController.maybeStartAutoLearn();\n\t}\n\n\tprivate maybeStartAutonomyReview(messages: AgentMessage[]): boolean {\n\t\treturn this.autoLearnController.maybeStartAutonomyReview(messages);\n\t}\n\n\tprivate updateAutoLearnFooter(): void {\n\t\tthis.autoLearnController.updateAutoLearnFooter();\n\t}\n\n\tprivate formatAutoLearnStatus(): string {\n\t\treturn this.autoLearnController.formatAutoLearnStatus();\n\t}\n\n\tprivate applyAutonomyMode(mode: AutonomyMode, scope: SettingsScope = \"global\"): void {\n\t\tconst currentAutoLearn = this.settingsManager.getAutoLearnSettings();\n\t\tconst preset = this.getAutoLearnPresetForAutonomyMode(mode, currentAutoLearn);\n\t\tthis.settingsManager.setAutonomySettings({ ...this.settingsManager.getAutonomySettings(), mode }, scope);\n\t\tthis.settingsManager.setAutoLearnSettings(preset, scope);\n\t\tthis.updateAutoLearnFooter();\n\t}\n\n\t/**\n\t * Delegates to the session rather than keeping its own instance (#27): the model router boots a\n\t * local server through AgentSession.getLocalRuntime() before a routed turn, and `/models`\n\t * commands need to see and be able to stop that SAME pi-managed process, not an unrelated one\n\t * tracked separately here.\n\t */\n\tprivate get localRuntime(): OllamaRuntime {\n\t\treturn this.session.getLocalRuntime();\n\t}\n\n\tprivate getTransformersRuntime(modelId: string, baseUrl?: string): TransformersRuntime {\n\t\treturn this.session.getTransformersRuntime(modelId, baseUrl);\n\t}\n\n\t/** Narrow seam shared by the /models and /fitness flows. */\n\tprivate localModelHost(): localModelCommands.LocalModelHost {\n\t\treturn {\n\t\t\tlocalRuntime: this.localRuntime,\n\t\t\tsession: this.session,\n\t\t\tsettingsManager: this.settingsManager,\n\t\t\tui: this.ui,\n\t\t\tchatContainer: this.chatContainer,\n\t\t\tgetTransformersRuntime: (modelId, baseUrl) => this.getTransformersRuntime(modelId, baseUrl),\n\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\tshowError: (message) => this.showError(message),\n\t\t\tshowSelector: (create) => this.showSelector(create),\n\t\t};\n\t}\n\n\tprivate handleModelsCommand(argsText: string): Promise<void> {\n\t\treturn localModelCommands.handleModelsCommand(this.localModelHost(), argsText);\n\t}\n\n\tprivate showFitnessModelSelector(): void {\n\t\tlocalModelCommands.showFitnessModelSelector(this.localModelHost());\n\t}\n\n\tprivate runFitnessAndAssign(modelRef: string, preselectRole?: FitnessRole): Promise<void> {\n\t\treturn localModelCommands.runFitnessAndAssign(\n\t\t\t{\n\t\t\t\tsession: this.session,\n\t\t\t\tsettingsManager: this.settingsManager,\n\t\t\t\tchatContainer: this.chatContainer,\n\t\t\t\tui: this.ui,\n\t\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\t\tshowError: (message) => this.showError(message),\n\t\t\t\tshowSelector: (create) => this.showSelector(create),\n\t\t\t},\n\t\t\tmodelRef,\n\t\t\tpreselectRole,\n\t\t);\n\t}\n\n\tprivate assignFitnessRole(modelRef: string, role: FitnessRole): void {\n\t\tlocalModelCommands.assignFitnessRole(\n\t\t\t{\n\t\t\t\tsettingsManager: this.settingsManager,\n\t\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\t},\n\t\t\tmodelRef,\n\t\t\trole,\n\t\t);\n\t}\n\n\t/** Narrow seam for the /autonomy and /auto-learn command bodies. */\n\tprivate autonomyHost(): autonomyCommands.AutonomyHost {\n\t\treturn {\n\t\t\tsession: this.session,\n\t\t\tsettingsManager: this.settingsManager,\n\t\t\tchatContainer: this.chatContainer,\n\t\t\tui: this.ui,\n\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\tapplyAutonomyMode: (mode, scope) => this.applyAutonomyMode(mode, scope),\n\t\t\tlaunchAutoLearn: (reason, force, options) => this.launchAutoLearn(reason, force, options),\n\t\t\tformatAutoLearnStatus: () => this.formatAutoLearnStatus(),\n\t\t\tgetEffectiveAutoLearnSettings: () => this.getEffectiveAutoLearnSettings(),\n\t\t\tgetPrunedAutoLearnState: () => this.getPrunedAutoLearnState(),\n\t\t\tgetAutoLearnTenantKey: () => this.getAutoLearnTenantKey(),\n\t\t\tgetAutoLearnDataDir: () => this.getAutoLearnDataDir(),\n\t\t\tgetAutoLearnTenantDataDir: () => this.getAutoLearnTenantDataDir(),\n\t\t};\n\t}\n\n\tprivate handleAutonomyCommand(text: string): void {\n\t\tautonomyCommands.handleAutonomyCommand(this.autonomyHost(), text);\n\t}\n\n\tprivate handleAutoLearnCommand(text: string): void {\n\t\tautonomyCommands.handleAutoLearnCommand(this.autonomyHost(), text);\n\t}\n\n\t/** Wide seam for the /settings selector; the hideThinkingBlock field is read+written here. */\n\tprivate settingsSelectorHost(): settingsSelectorFlow.SettingsSelectorHost {\n\t\tconst self = this;\n\t\treturn {\n\t\t\tsession: this.session,\n\t\t\tsettingsManager: this.settingsManager,\n\t\t\tfooter: this.footer,\n\t\t\tchatContainer: this.chatContainer,\n\t\t\tui: this.ui,\n\t\t\tdefaultEditor: this.defaultEditor,\n\t\t\teditor: this.editor,\n\t\t\tget hideThinkingBlock() {\n\t\t\t\treturn self.hideThinkingBlock;\n\t\t\t},\n\t\t\tset hideThinkingBlock(value) {\n\t\t\t\tself.hideThinkingBlock = value;\n\t\t\t},\n\t\t\tshowSelector: (create) => this.showSelector(create),\n\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\tshowWarning: (message) => this.showWarning(message),\n\t\t\tshowError: (message) => this.showError(message),\n\t\t\tgetAutoLearnModelOptions: () => this.getAutoLearnModelOptions(),\n\t\t\tsetupAutocompleteProvider: () => this.setupAutocompleteProvider(),\n\t\t\tupdateEditorBorderColor: () => this.updateEditorBorderColor(),\n\t\t\trebuildChatFromMessages: () => this.rebuildChatFromMessages(),\n\t\t\tvalidateSelfModificationSource: (settings) => this.validateSelfModificationSource(settings),\n\t\t\tapplyAutonomyMode: (mode, scope) => this.applyAutonomyMode(mode, scope),\n\t\t\tvalidateAutoLearnModelValue: (value) => this.validateAutoLearnModelValue(value),\n\t\t\tupdateAutoLearnFooter: () => this.updateAutoLearnFooter(),\n\t\t\thandleResourcesHubAction: (action) => this.handleResourcesHubAction(action),\n\t\t};\n\t}\n\n\tprivate showSettingsSelector(): void {\n\t\tsettingsSelectorFlow.showSettingsSelector(this.settingsSelectorHost());\n\t}\n\n\tprivate handleResourcesHubAction(action: string): Promise<void> {\n\t\treturn this.profileMenu.handleResourcesHubAction(action);\n\t}\n\n\tprivate handleProfilesCommand(profileName?: string): Promise<void> {\n\t\treturn this.profileMenu.handleProfilesCommand(profileName);\n\t}\n\n\tprivate refreshAfterProfileMutation(profileName: string): Promise<void> {\n\t\treturn this.profileMenu.refreshAfterProfileMutation(profileName);\n\t}\n\n\tprivate async handleModelCommand(searchTerm?: string): Promise<void> {\n\t\tawait sessionFlows.handleModelCommand(this.sessionFlowHost(), searchTerm);\n\t}\n\n\tprivate async getModelCandidates(): Promise<Model<any>[]> {\n\t\tif (this.session.scopedModels.length > 0) {\n\t\t\treturn this.session.scopedModels.map((scoped) => scoped.model);\n\t\t}\n\n\t\tthis.session.modelRegistry.refresh();\n\t\ttry {\n\t\t\treturn await this.session.modelRegistry.getAvailable();\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t}\n\n\t/** Update the footer's available provider count from current model candidates */\n\tprivate async updateAvailableProviderCount(): Promise<void> {\n\t\tconst models = await this.getModelCandidates();\n\t\tconst uniqueProviders = new Set(models.map((m) => m.provider));\n\t\tthis.footerDataProvider.setAvailableProviderCount(uniqueProviders.size);\n\t}\n\n\tprivate async maybeWarnAboutAnthropicSubscriptionAuth(\n\t\tmodel: Model<any> | undefined = this.session.model,\n\t): Promise<void> {\n\t\tif (this.settingsManager.getWarnings().anthropicExtraUsage === false) {\n\t\t\treturn;\n\t\t}\n\t\tif (this.anthropicSubscriptionWarningShown) {\n\t\t\treturn;\n\t\t}\n\t\tif (!model || model.provider !== \"anthropic\") {\n\t\t\treturn;\n\t\t}\n\n\t\tconst storedCredential = this.session.modelRegistry.authStorage.get(\"anthropic\");\n\t\tif (storedCredential?.type === \"oauth\") {\n\t\t\tthis.anthropicSubscriptionWarningShown = true;\n\t\t\tthis.showWarning(ANTHROPIC_SUBSCRIPTION_AUTH_WARNING);\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tconst apiKey = await this.session.modelRegistry.getApiKeyForProvider(model.provider);\n\t\t\tif (!isAnthropicSubscriptionAuthKey(apiKey)) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.anthropicSubscriptionWarningShown = true;\n\t\t\tthis.showWarning(ANTHROPIC_SUBSCRIPTION_AUTH_WARNING);\n\t\t} catch {\n\t\t\t// Ignore auth lookup failures for warning-only checks.\n\t\t}\n\t}\n\n\tprivate async showModelSelector(initialSearchInput?: string): Promise<void> {\n\t\tawait sessionFlows.showModelSelector(this.sessionFlowHost(), initialSearchInput);\n\t}\n\n\tprivate async showModelsSelector(): Promise<void> {\n\t\tawait sessionFlows.showModelsSelector(this.sessionFlowHost());\n\t}\n\n\tprivate showUserMessageSelector(newSessionName?: string): void {\n\t\tsessionFlows.showUserMessageSelector(this.sessionFlowHost(), newSessionName);\n\t}\n\n\tprivate async handleCloneCommand(newSessionName?: string): Promise<void> {\n\t\tawait sessionFlows.handleCloneCommand(\n\t\t\t{\n\t\t\t\tsessionManager: this.sessionManager,\n\t\t\t\truntimeHost: this.runtimeHost,\n\t\t\t\trenderCurrentSessionState: () => this.renderCurrentSessionState(),\n\t\t\t\teditor: this.editor,\n\t\t\t\tsession: this.session,\n\t\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\t\tshowError: (message) => this.showError(message),\n\t\t\t\tui: this.ui,\n\t\t\t},\n\t\t\tnewSessionName,\n\t\t);\n\t}\n\n\tprivate showTreeSelector(initialSelectedId?: string): void {\n\t\tsessionFlows.showTreeSelector(this.sessionFlowHost(), initialSelectedId);\n\t}\n\n\tprivate showTrustSelector(): void {\n\t\tsessionFlows.showTrustSelector(this.sessionFlowHost());\n\t}\n\n\tprivate showSessionSelector(): void {\n\t\tsessionFlows.showSessionSelector(this.sessionFlowHost());\n\t}\n\n\tprivate handleResumeSession(\n\t\tsessionPath: string,\n\t\toptions?: Parameters<ExtensionCommandContext[\"switchSession\"]>[1],\n\t): Promise<{ cancelled: boolean }> {\n\t\treturn sessionFlows.handleResumeSession(this.sessionFlowHost(), sessionPath, options);\n\t}\n\n\t// =========================================================================\n\t// Command handlers\n\t// =========================================================================\n\n\tprivate async handleReloadCommand(): Promise<void> {\n\t\tawait this.handleReloadCommandWithResult();\n\t}\n\n\t/** Reload and report whether the previous runtime was replaced successfully. */\n\tprivate async handleReloadCommandWithResult(): Promise<boolean> {\n\t\tif (this.session.isStreaming) {\n\t\t\tthis.showWarning(\"Wait for the current response to finish before reloading.\");\n\t\t\treturn false;\n\t\t}\n\t\tif (this.session.isCompacting) {\n\t\t\tthis.showWarning(\"Wait for compaction to finish before reloading.\");\n\t\t\treturn false;\n\t\t}\n\n\t\tthis.extensionUiHost.resetExtensionUI();\n\n\t\tconst reloadBox = new Container();\n\t\tconst borderColor = (s: string) => theme.fg(\"border\", s);\n\t\treloadBox.addChild(new DynamicBorder(borderColor));\n\t\treloadBox.addChild(new Spacer(1));\n\t\treloadBox.addChild(\n\t\t\tnew Text(theme.fg(\"muted\", \"Reloading keybindings, extensions, skills, prompts, themes...\"), 1, 0),\n\t\t);\n\t\treloadBox.addChild(new Spacer(1));\n\t\treloadBox.addChild(new DynamicBorder(borderColor));\n\n\t\tconst previousEditor = this.editor;\n\t\tthis.overlayHost.swap(reloadBox, { render: \"sync\" });\n\t\t// Let the terminal paint the reload notice before CPU-heavy extension/theme\n\t\t// work begins. process.nextTick runs before I/O and can still make reloads\n\t\t// appear frozen.\n\t\tawait new Promise((resolve) => setImmediate(resolve));\n\n\t\tconst dismissReloadBox = (editor: Component) => {\n\t\t\tthis.overlayHost.swap(editor);\n\t\t};\n\n\t\ttry {\n\t\t\tawait this.session.reload();\n\t\t\tconfigureHttpDispatcher(this.settingsManager.getHttpIdleTimeoutMs());\n\t\t\tthis.keybindings.reload();\n\t\t\tconst activeHeader = this.extensionUiHost.getCustomHeader() ?? this.builtInHeader;\n\t\t\tif (isExpandable(activeHeader)) {\n\t\t\t\tactiveHeader.setExpanded(this.toolOutputExpanded);\n\t\t\t}\n\t\t\tsetRegisteredThemes(this.session.resourceLoader.getThemes().themes);\n\t\t\tthis.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();\n\t\t\tconst themeName = this.settingsManager.getTheme();\n\t\t\tconst themeResult = themeName ? setTheme(themeName, true) : { success: true };\n\t\t\tif (!themeResult.success) {\n\t\t\t\tthis.showError(`Failed to load theme \"${themeName}\": ${themeResult.error}\\nFell back to dark theme.`);\n\t\t\t}\n\t\t\tconst editorPaddingX = this.settingsManager.getEditorPaddingX();\n\t\t\tconst autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();\n\t\t\tthis.defaultEditor.setPaddingX(editorPaddingX);\n\t\t\tthis.defaultEditor.setAutocompleteMaxVisible(autocompleteMaxVisible);\n\t\t\tif (this.editor !== this.defaultEditor) {\n\t\t\t\tthis.editor.setPaddingX?.(editorPaddingX);\n\t\t\t\tthis.editor.setAutocompleteMaxVisible?.(autocompleteMaxVisible);\n\t\t\t}\n\t\t\tthis.ui.setShowHardwareCursor(this.settingsManager.getShowHardwareCursor());\n\t\t\tthis.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());\n\t\t\tthis.setupAutocompleteProvider();\n\t\t\tconst runner = this.session.extensionRunner;\n\t\t\tthis.extensionUiHost.setupExtensionShortcuts(runner);\n\t\t\tawait this.rebuildChatFromMessages();\n\t\t\tdismissReloadBox(this.editor as Component);\n\t\t\tthis.showLoadedResources({\n\t\t\t\tforce: false,\n\t\t\t\tshowDiagnosticsWhenQuiet: true,\n\t\t\t});\n\t\t\tconst modelsJsonError = this.session.modelRegistry.getError();\n\t\t\tif (modelsJsonError) {\n\t\t\t\tthis.showError(`models.json error: ${modelsJsonError}`);\n\t\t\t}\n\t\t\tthis.showStatus(\"Reloaded keybindings, extensions, skills, prompts, themes\");\n\t\t\treturn true;\n\t\t} catch (error) {\n\t\t\tdismissReloadBox(previousEditor as Component);\n\t\t\tthis.showError(`Reload failed: ${error instanceof Error ? error.message : String(error)}`);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Refresh UI after extensions are loaded/unloaded live.\n\t * Performs the same refresh calls as handleReloadCommand but without the full reload.\n\t */\n\tprivate async refreshUIAfterExtensionsChanged(): Promise<void> {\n\t\ttry {\n\t\t\t// Refresh keybindings and autocomplete\n\t\t\tthis.keybindings.reload();\n\t\t\tthis.setupAutocompleteProvider();\n\n\t\t\t// Refresh themes\n\t\t\tconst activeHeader = this.extensionUiHost.getCustomHeader() ?? this.builtInHeader;\n\t\t\tif (isExpandable(activeHeader)) {\n\t\t\t\tactiveHeader.setExpanded(this.toolOutputExpanded);\n\t\t\t}\n\t\t\tsetRegisteredThemes(this.session.resourceLoader.getThemes().themes);\n\t\t\tthis.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();\n\t\t\tconst themeName = this.settingsManager.getTheme();\n\t\t\tconst themeResult = themeName ? setTheme(themeName, true) : { success: true };\n\t\t\tif (!themeResult.success) {\n\t\t\t\tthis.showError(`Failed to load theme \"${themeName}\": ${themeResult.error}\\nFell back to dark theme.`);\n\t\t\t}\n\n\t\t\t// Refresh editor settings\n\t\t\tconst editorPaddingX = this.settingsManager.getEditorPaddingX();\n\t\t\tconst autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();\n\t\t\tthis.defaultEditor.setPaddingX(editorPaddingX);\n\t\t\tthis.defaultEditor.setAutocompleteMaxVisible(autocompleteMaxVisible);\n\t\t\tif (this.editor !== this.defaultEditor) {\n\t\t\t\tthis.editor.setPaddingX?.(editorPaddingX);\n\t\t\t\tthis.editor.setAutocompleteMaxVisible?.(autocompleteMaxVisible);\n\t\t\t}\n\n\t\t\t// Refresh extension shortcuts\n\t\t\tconst runner = this.session.extensionRunner;\n\t\t\tthis.extensionUiHost.setupExtensionShortcuts(runner);\n\n\t\t\t// Refresh chat and UI\n\t\t\tawait this.rebuildChatFromMessages();\n\t\t\tthis.footer.invalidate();\n\t\t\tthis.ui.requestRender();\n\t\t} catch (error) {\n\t\t\tthis.showError(`Extension refresh failed: ${error instanceof Error ? error.message : String(error)}`);\n\t\t}\n\t}\n\n\tprivate handleExportCommand(text: string): Promise<void> {\n\t\treturn sessionIoCommands.handleExportCommand(\n\t\t\t{\n\t\t\t\tsession: this.session,\n\t\t\t\tgetPathCommandArgument: (t, command) => this.getPathCommandArgument(t, command),\n\t\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\t\tshowError: (message) => this.showError(message),\n\t\t\t},\n\t\t\ttext,\n\t\t);\n\t}\n\n\tprivate getPathCommandArgument(text: string, command: \"/export\" | \"/import\"): string | undefined {\n\t\tif (text === command) {\n\t\t\treturn undefined;\n\t\t}\n\t\tif (!text.startsWith(`${command} `)) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst argsString = text.slice(command.length + 1).trimStart();\n\t\tif (!argsString) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst firstChar = argsString[0];\n\t\tif (firstChar === '\"' || firstChar === \"'\") {\n\t\t\tconst closingQuoteIndex = argsString.indexOf(firstChar, 1);\n\t\t\tif (closingQuoteIndex < 0) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\treturn argsString.slice(1, closingQuoteIndex);\n\t\t}\n\n\t\tconst firstWhitespaceIndex = argsString.search(/\\s/);\n\t\tif (firstWhitespaceIndex < 0) {\n\t\t\treturn argsString;\n\t\t}\n\t\treturn argsString.slice(0, firstWhitespaceIndex);\n\t}\n\n\tprivate handleImportCommand(text: string): Promise<void> {\n\t\tconst self = this;\n\t\treturn sessionIoCommands.handleImportCommand(\n\t\t\t{\n\t\t\t\tgetPathCommandArgument: (t, command) => this.getPathCommandArgument(t, command),\n\t\t\t\tshowError: (message) => this.showError(message),\n\t\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\t\textensionUiHost: this.extensionUiHost,\n\t\t\t\tget loadingAnimation() {\n\t\t\t\t\treturn self.loadingAnimation;\n\t\t\t\t},\n\t\t\t\tset loadingAnimation(value) {\n\t\t\t\t\tself.loadingAnimation = value;\n\t\t\t\t},\n\t\t\t\tstatusContainer: this.statusContainer,\n\t\t\t\truntimeHost: this.runtimeHost,\n\t\t\t\trenderCurrentSessionState: () => this.renderCurrentSessionState(),\n\t\t\t\tpromptForMissingSessionCwd: (error) => this.promptForMissingSessionCwd(error),\n\t\t\t\thandleFatalRuntimeError: (prefix, error) => this.handleFatalRuntimeError(prefix, error),\n\t\t\t},\n\t\t\ttext,\n\t\t);\n\t}\n\n\tprivate handleShareCommand(): Promise<void> {\n\t\treturn sessionIoCommands.handleShareCommand({\n\t\t\tshowError: (message) => this.showError(message),\n\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\tsession: this.session,\n\t\t\tui: this.ui,\n\t\t\toverlayHost: this.overlayHost,\n\t\t\teditor: this.editor,\n\t\t});\n\t}\n\n\tprivate handleCopyCommand(): Promise<void> {\n\t\treturn sessionIoCommands.handleCopyCommand({\n\t\t\tsession: this.session,\n\t\t\tshowError: (message) => this.showError(message),\n\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t});\n\t}\n\n\tprivate handleNameCommand(text: string): void {\n\t\tsessionIoCommands.handleNameCommand(\n\t\t\t{\n\t\t\t\tsessionManager: this.sessionManager,\n\t\t\t\tchatContainer: this.chatContainer,\n\t\t\t\tshowWarning: (message) => this.showWarning(message),\n\t\t\t\tsession: this.session,\n\t\t\t\tui: this.ui,\n\t\t\t},\n\t\t\ttext,\n\t\t);\n\t}\n\n\tprivate parseGoalContinueCommand(\n\t\ttext: string,\n\t):\n\t\t| { ok: true; maxTurns: number; maxStallTurns: number; maxWallClockMinutes: number }\n\t\t| { ok: false; error: string } {\n\t\tconst usage = \"Usage: /goal-continue [maxTurns 1-20] [maxStallTurns 0-100] [maxMinutes 0-1440]\";\n\t\tconst parts = text.trim().split(/\\s+/).slice(1);\n\t\tif (parts.length > 3) {\n\t\t\treturn { ok: false, error: usage };\n\t\t}\n\n\t\tconst parseBoundedInteger = (\n\t\t\tvalue: string | undefined,\n\t\t\tfallback: number,\n\t\t\tmin: number,\n\t\t\tmax: number,\n\t\t): number | undefined => {\n\t\t\tif (value === undefined || value.length === 0) return fallback;\n\t\t\tif (!/^\\d+$/.test(value)) return undefined;\n\t\t\tconst parsed = Number.parseInt(value, 10);\n\t\t\tif (!Number.isSafeInteger(parsed) || parsed < min || parsed > max) return undefined;\n\t\t\treturn parsed;\n\t\t};\n\n\t\tconst maxTurns = parseBoundedInteger(parts[0], DEFAULT_GOAL_CONTINUE_MAX_TURNS, 1, MAX_GOAL_CONTINUE_MAX_TURNS);\n\t\tconst maxStallTurns = parseBoundedInteger(\n\t\t\tparts[1],\n\t\t\tDEFAULT_GOAL_CONTINUE_MAX_STALL_TURNS,\n\t\t\t0,\n\t\t\tMAX_GOAL_CONTINUE_MAX_STALL_TURNS,\n\t\t);\n\t\tconst maxWallClockMinutes = parseBoundedInteger(\n\t\t\tparts[2],\n\t\t\tDEFAULT_GOAL_CONTINUE_MAX_WALL_CLOCK_MINUTES,\n\t\t\t0,\n\t\t\tMAX_GOAL_CONTINUE_MAX_WALL_CLOCK_MINUTES,\n\t\t);\n\t\tif (maxTurns === undefined || maxStallTurns === undefined || maxWallClockMinutes === undefined) {\n\t\t\treturn { ok: false, error: usage };\n\t\t}\n\t\treturn { ok: true, maxTurns, maxStallTurns, maxWallClockMinutes };\n\t}\n\n\tprivate async handleGoalCommand(text: string): Promise<void> {\n\t\tawait sessionFlows.handleGoalCommand(\n\t\t\t{\n\t\t\t\tsession: this.session,\n\t\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\t\tshowError: (message) => this.showError(message),\n\t\t\t\trefreshAutonomyFooterStatus: () => this.refreshAutonomyFooterStatus(),\n\t\t\t},\n\t\t\ttext,\n\t\t);\n\t}\n\n\tprivate async handleGoalContinueCommand(text: string): Promise<void> {\n\t\tawait sessionFlows.handleGoalContinueCommand(\n\t\t\t{\n\t\t\t\tsession: this.session,\n\t\t\t\tparseGoalContinueCommand: (t) => this.parseGoalContinueCommand(t),\n\t\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\t\tshowError: (message) => this.showError(message),\n\t\t\t\trefreshAutonomyFooterStatus: () => this.refreshAutonomyFooterStatus(),\n\t\t\t},\n\t\t\ttext,\n\t\t);\n\t}\n\n\tprivate handleSessionCommand(): void {\n\t\tsessionFlows.handleSessionCommand({\n\t\t\tsession: this.session,\n\t\t\tsessionManager: this.sessionManager,\n\t\t\tchatContainer: this.chatContainer,\n\t\t\tui: this.ui,\n\t\t});\n\t}\n\n\tprivate handleUsageCommand(): void {\n\t\treportCommands.handleUsageCommand({\n\t\t\tsession: this.session,\n\t\t\tchatContainer: this.chatContainer,\n\t\t\tui: this.ui,\n\t\t\tgetCurrentAutoLearnSettings: () => this.getCurrentAutoLearnSettings(),\n\t\t});\n\t}\n\n\tprivate handleChangelogCommand(): void {\n\t\treportCommands.handleChangelogCommand({\n\t\t\tchatContainer: this.chatContainer,\n\t\t\tui: this.ui,\n\t\t\tgetMarkdownThemeWithSettings: () => this.getMarkdownThemeWithSettings(),\n\t\t});\n\t}\n\n\t/**\n\t * Get capitalized display string for an app keybinding action.\n\t */\n\tprivate getAppKeyDisplay(action: AppKeybinding): string {\n\t\treturn keyDisplayText(action);\n\t}\n\n\t/**\n\t * Get capitalized display string for an editor keybinding action.\n\t */\n\tprivate getEditorKeyDisplay(action: Keybinding): string {\n\t\treturn keyDisplayText(action);\n\t}\n\n\tprivate handleHotkeysCommand(): void {\n\t\treportCommands.handleHotkeysCommand({\n\t\t\tsession: this.session,\n\t\t\tkeybindings: this.keybindings,\n\t\t\tchatContainer: this.chatContainer,\n\t\t\tui: this.ui,\n\t\t\tgetMarkdownThemeWithSettings: () => this.getMarkdownThemeWithSettings(),\n\t\t\tgetAppKeyDisplay: (action) => this.getAppKeyDisplay(action),\n\t\t\tgetEditorKeyDisplay: (action) => this.getEditorKeyDisplay(action),\n\t\t});\n\t}\n\n\tprivate async handleClearCommand(newSessionName?: string): Promise<void> {\n\t\tif (this.loadingAnimation) {\n\t\t\tthis.loadingAnimation.stop();\n\t\t\tthis.loadingAnimation = undefined;\n\t\t}\n\t\tthis.statusContainer.clear();\n\t\ttry {\n\t\t\tconst result = await this.runtimeHost.newSession();\n\t\t\tif (result.cancelled) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.renderCurrentSessionState();\n\t\t\tif (newSessionName) {\n\t\t\t\tthis.session.setSessionName(newSessionName);\n\t\t\t}\n\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t\tconst label = newSessionName ? `✓ New session started: ${newSessionName}` : \"✓ New session started\";\n\t\t\tthis.chatContainer.addChild(new Text(`${theme.fg(\"accent\", label)}`, 1, 1));\n\t\t\tthis.ui.requestRender();\n\t\t} catch (error: unknown) {\n\t\t\tawait this.handleFatalRuntimeError(\"Failed to create session\", error);\n\t\t}\n\t}\n\n\tprivate copyResourcesRecursively(\n\t\tsrc: string,\n\t\tdest: string,\n\t\tforce: boolean,\n\t\tstats: { installed: string[]; skipped: string[] },\n\t): void {\n\t\tif (!fs.existsSync(src)) return;\n\n\t\tconst entries = fs.readdirSync(src, { withFileTypes: true });\n\t\tfs.mkdirSync(dest, { recursive: true });\n\n\t\tfor (const entry of entries) {\n\t\t\tconst srcPath = path.join(src, entry.name);\n\t\t\tconst destPath = path.join(dest, entry.name);\n\n\t\t\tif (entry.isDirectory()) {\n\t\t\t\tthis.copyResourcesRecursively(srcPath, destPath, force, stats);\n\t\t\t} else if (entry.isFile()) {\n\t\t\t\tif (fs.existsSync(destPath) && !force) {\n\t\t\t\t\tstats.skipped.push(destPath);\n\t\t\t\t} else {\n\t\t\t\t\tfs.copyFileSync(srcPath, destPath);\n\t\t\t\t\tstats.installed.push(destPath);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate handleInstallResourcesCommand(argsString: string): Promise<void> {\n\t\treturn resourceShellCommands.handleInstallResourcesCommand(\n\t\t\t{\n\t\t\t\tsettingsManager: this.settingsManager,\n\t\t\t\tshowError: (message) => this.showError(message),\n\t\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\t\tshowSelector: (create) => this.showSelector(create),\n\t\t\t\thandleReloadCommand: () => this.handleReloadCommand(),\n\t\t\t\tcopyResourcesRecursively: (src, dest, force, stats) =>\n\t\t\t\t\tthis.copyResourcesRecursively(src, dest, force, stats),\n\t\t\t},\n\t\t\targsString,\n\t\t);\n\t}\n\n\t/**\n\t * `/curate` — skill curator (#32). With no args, lists reflection-promoted skills proposed for\n\t * archival (stale/unused) and pairs proposed for consolidation (overlapping). PROPOSE-ONLY: the user\n\t * applies actions explicitly via `/curate archive <name>` / `/curate restore <name>`. Never touches\n\t * hand-authored skills; archival is restorable.\n\t */\n\tprivate handleCurateCommand(args: string): void {\n\t\tresourceShellCommands.handleCurateCommand(\n\t\t\t{\n\t\t\t\tsession: this.session,\n\t\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\t},\n\t\t\targs,\n\t\t);\n\t}\n\n\tprivate async handleConfigBackupCommand(fileArg?: string): Promise<void> {\n\t\tawait configBackup.handleConfigBackupCommand(\n\t\t\t{\n\t\t\t\tsettingsManager: this.settingsManager,\n\t\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\t\tshowError: (message) => this.showError(message),\n\t\t\t},\n\t\t\tfileArg,\n\t\t);\n\t}\n\n\tprivate async handleConfigRestoreCommand(fileArg: string): Promise<void> {\n\t\tawait configBackup.handleConfigRestoreCommand(\n\t\t\t{\n\t\t\t\tsettingsManager: this.settingsManager,\n\t\t\t\tshowStatus: (message) => this.showStatus(message),\n\t\t\t\tshowError: (message) => this.showError(message),\n\t\t\t\tshowSelector: (create) => this.showSelector(create),\n\t\t\t\thandleReloadCommand: () => this.handleReloadCommandWithResult(),\n\t\t\t},\n\t\t\tfileArg,\n\t\t);\n\t}\n\n\tprivate handleDebugCommand(): void {\n\t\treportCommands.handleDebugCommand({\n\t\t\tsession: this.session,\n\t\t\tchatContainer: this.chatContainer,\n\t\t\tui: this.ui,\n\t\t});\n\t}\n\n\tprivate handleArminSaysHi(): void {\n\t\treportCommands.handleArminSaysHi({ chatContainer: this.chatContainer, ui: this.ui });\n\t}\n\n\tprivate handleDementedDelves(): void {\n\t\treportCommands.handleDementedDelves({ chatContainer: this.chatContainer, ui: this.ui });\n\t}\n\n\tprivate checkDaxnutsEasterEgg(model: { provider: string; id: string }): void {\n\t\treportCommands.checkDaxnutsEasterEgg({ chatContainer: this.chatContainer, ui: this.ui }, model);\n\t}\n\n\tprivate handleBashCommand(command: string, excludeFromContext = false): Promise<void> {\n\t\tconst self = this;\n\t\treturn resourceShellCommands.handleBashCommand(\n\t\t\t{\n\t\t\t\tsession: this.session,\n\t\t\t\tsessionManager: this.sessionManager,\n\t\t\t\tui: this.ui,\n\t\t\t\tchatContainer: this.chatContainer,\n\t\t\t\tpendingMessagesContainer: this.pendingMessagesContainer,\n\t\t\t\tpendingBashComponents: this.pendingBashComponents,\n\t\t\t\tget bashComponent() {\n\t\t\t\t\treturn self.bashComponent;\n\t\t\t\t},\n\t\t\t\tset bashComponent(value) {\n\t\t\t\t\tself.bashComponent = value;\n\t\t\t\t},\n\t\t\t\tshowError: (message) => this.showError(message),\n\t\t\t},\n\t\t\tcommand,\n\t\t\texcludeFromContext,\n\t\t);\n\t}\n\n\tprivate handleCompactCommand(customInstructions?: string): Promise<void> {\n\t\tconst self = this;\n\t\treturn resourceShellCommands.handleCompactCommand(\n\t\t\t{\n\t\t\t\tsessionManager: this.sessionManager,\n\t\t\t\tshowWarning: (message) => this.showWarning(message),\n\t\t\t\tget loadingAnimation() {\n\t\t\t\t\treturn self.loadingAnimation;\n\t\t\t\t},\n\t\t\t\tset loadingAnimation(value) {\n\t\t\t\t\tself.loadingAnimation = value;\n\t\t\t\t},\n\t\t\t\tstatusContainer: this.statusContainer,\n\t\t\t\tsession: this.session,\n\t\t\t},\n\t\t\tcustomInstructions,\n\t\t);\n\t}\n\n\tstop(): void {\n\t\tthis.unregisterSignalHandlers();\n\t\tif (this.settingsManager.getShowTerminalProgress()) {\n\t\t\tthis.ui.terminal.setProgress(false);\n\t\t}\n\t\tif (this.loadingAnimation) {\n\t\t\tthis.loadingAnimation.stop();\n\t\t\tthis.loadingAnimation = undefined;\n\t\t}\n\t\tthis.extensionUiHost.clearExtensionTerminalInputListeners();\n\t\tthis.footer.dispose();\n\t\tthis.footerDataProvider.dispose();\n\t\tif (this.unsubscribe) {\n\t\t\tthis.unsubscribe();\n\t\t}\n\t\tif (this.unsubscribeExtensionsChanged) {\n\t\t\tthis.unsubscribeExtensionsChanged();\n\t\t}\n\t\tif (this.isInitialized) {\n\t\t\tthis.ui.stop();\n\t\t\tthis.isInitialized = false;\n\t\t}\n\t}\n}\n"]}