@caupulican/pi-adaptative 0.80.102 → 0.81.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +131 -0
- package/dist/cli/session-picker.d.ts +1 -1
- package/dist/cli/session-picker.d.ts.map +1 -1
- package/dist/cli/session-picker.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +7 -1
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +11 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +17 -3
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session-runtime.d.ts +1 -1
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +6 -6
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session-services.d.ts +1 -1
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +152 -512
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +635 -4590
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/autonomy/session-lane-record.d.ts +1 -1
- package/dist/core/autonomy/session-lane-record.d.ts.map +1 -1
- package/dist/core/autonomy/session-lane-record.js.map +1 -1
- package/dist/core/autonomy/status.d.ts +1 -0
- package/dist/core/autonomy/status.d.ts.map +1 -1
- package/dist/core/autonomy/status.js +1 -0
- package/dist/core/autonomy/status.js.map +1 -1
- package/dist/core/autonomy-telemetry.d.ts +80 -0
- package/dist/core/autonomy-telemetry.d.ts.map +1 -0
- package/dist/core/autonomy-telemetry.js +266 -0
- package/dist/core/autonomy-telemetry.js.map +1 -0
- package/dist/core/background-lane-controller.d.ts +186 -0
- package/dist/core/background-lane-controller.d.ts.map +1 -0
- package/dist/core/background-lane-controller.js +691 -0
- package/dist/core/background-lane-controller.js.map +1 -0
- package/dist/core/bash-execution-controller.d.ts +51 -0
- package/dist/core/bash-execution-controller.d.ts.map +1 -0
- package/dist/core/bash-execution-controller.js +96 -0
- package/dist/core/bash-execution-controller.js.map +1 -0
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +9 -2
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/compaction-support.d.ts +56 -0
- package/dist/core/compaction-support.d.ts.map +1 -0
- package/dist/core/compaction-support.js +99 -0
- package/dist/core/compaction-support.js.map +1 -0
- package/dist/core/context/artifact-retrieval.d.ts +1 -1
- package/dist/core/context/artifact-retrieval.d.ts.map +1 -1
- package/dist/core/context/artifact-retrieval.js +1 -1
- package/dist/core/context/artifact-retrieval.js.map +1 -1
- package/dist/core/context/context-composition.d.ts.map +1 -1
- package/dist/core/context/context-composition.js +1 -1
- package/dist/core/context/context-composition.js.map +1 -1
- package/dist/core/context/tool-output-packer.d.ts +1 -1
- package/dist/core/context/tool-output-packer.d.ts.map +1 -1
- package/dist/core/context/tool-output-packer.js +1 -1
- package/dist/core/context/tool-output-packer.js.map +1 -1
- package/dist/core/context-gc.d.ts.map +1 -1
- package/dist/core/context-gc.js +1 -1
- package/dist/core/context-gc.js.map +1 -1
- package/dist/core/context-pipeline.d.ts +223 -0
- package/dist/core/context-pipeline.d.ts.map +1 -0
- package/dist/core/context-pipeline.js +594 -0
- package/dist/core/context-pipeline.js.map +1 -0
- package/dist/core/cost/daily-usage.d.ts +1 -1
- package/dist/core/cost/daily-usage.d.ts.map +1 -1
- package/dist/core/cost/daily-usage.js +1 -1
- package/dist/core/cost/daily-usage.js.map +1 -1
- package/dist/core/cost/session-usage.d.ts +1 -1
- package/dist/core/cost/session-usage.d.ts.map +1 -1
- package/dist/core/cost/session-usage.js +1 -1
- package/dist/core/cost/session-usage.js.map +1 -1
- package/dist/core/delegation/session-worker-result.d.ts +1 -1
- package/dist/core/delegation/session-worker-result.d.ts.map +1 -1
- package/dist/core/delegation/session-worker-result.js.map +1 -1
- package/dist/core/exec.d.ts +2 -0
- package/dist/core/exec.d.ts.map +1 -1
- package/dist/core/exec.js +15 -9
- package/dist/core/exec.js.map +1 -1
- package/dist/core/export-html/index.d.ts +1 -1
- package/dist/core/export-html/index.d.ts.map +1 -1
- package/dist/core/export-html/index.js +2 -2
- package/dist/core/export-html/index.js.map +1 -1
- package/dist/core/extensions/builtin.d.ts.map +1 -1
- package/dist/core/extensions/builtin.js +2 -2
- package/dist/core/extensions/builtin.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +1 -1
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +2 -4
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/goal-loop-controller.d.ts +24 -0
- package/dist/core/goal-loop-controller.d.ts.map +1 -0
- package/dist/core/goal-loop-controller.js +83 -0
- package/dist/core/goal-loop-controller.js.map +1 -0
- package/dist/core/goals/goal-runtime-snapshot.d.ts +1 -1
- package/dist/core/goals/goal-runtime-snapshot.d.ts.map +1 -1
- package/dist/core/goals/goal-runtime-snapshot.js.map +1 -1
- package/dist/core/goals/session-goal-state.d.ts +1 -1
- package/dist/core/goals/session-goal-state.d.ts.map +1 -1
- package/dist/core/goals/session-goal-state.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/learning/learning-audit.d.ts +1 -1
- package/dist/core/learning/learning-audit.d.ts.map +1 -1
- package/dist/core/learning/learning-audit.js.map +1 -1
- package/dist/core/learning/session-learning-decision.d.ts +1 -1
- package/dist/core/learning/session-learning-decision.d.ts.map +1 -1
- package/dist/core/learning/session-learning-decision.js.map +1 -1
- package/dist/core/local-runtime-controller.d.ts +99 -0
- package/dist/core/local-runtime-controller.d.ts.map +1 -0
- package/dist/core/local-runtime-controller.js +214 -0
- package/dist/core/local-runtime-controller.js.map +1 -0
- package/dist/core/memory/providers/transcript-recall.d.ts.map +1 -1
- package/dist/core/memory/providers/transcript-recall.js +1 -1
- package/dist/core/memory/providers/transcript-recall.js.map +1 -1
- package/dist/core/memory-controller.d.ts +139 -0
- package/dist/core/memory-controller.d.ts.map +1 -0
- package/dist/core/memory-controller.js +323 -0
- package/dist/core/memory-controller.js.map +1 -0
- package/dist/core/model-router/status.d.ts +1 -1
- package/dist/core/model-router/status.d.ts.map +1 -1
- package/dist/core/model-router/status.js.map +1 -1
- package/dist/core/model-router-controller.d.ts +140 -0
- package/dist/core/model-router-controller.d.ts.map +1 -0
- package/dist/core/model-router-controller.js +623 -0
- package/dist/core/model-router-controller.js.map +1 -0
- package/dist/core/model-selection-controller.d.ts +95 -0
- package/dist/core/model-selection-controller.d.ts.map +1 -0
- package/dist/core/model-selection-controller.js +252 -0
- package/dist/core/model-selection-controller.js.map +1 -0
- package/dist/core/models/model-ref.d.ts +13 -0
- package/dist/core/models/model-ref.d.ts.map +1 -1
- package/dist/core/models/model-ref.js +22 -0
- package/dist/core/models/model-ref.js.map +1 -1
- package/dist/core/profile-filter-controller.d.ts +77 -0
- package/dist/core/profile-filter-controller.d.ts.map +1 -0
- package/dist/core/profile-filter-controller.js +159 -0
- package/dist/core/profile-filter-controller.js.map +1 -0
- package/dist/core/reflection-controller.d.ts +111 -0
- package/dist/core/reflection-controller.d.ts.map +1 -0
- package/dist/core/reflection-controller.js +412 -0
- package/dist/core/reflection-controller.js.map +1 -0
- package/dist/core/research/model-fitness.d.ts +12 -0
- package/dist/core/research/model-fitness.d.ts.map +1 -1
- package/dist/core/research/model-fitness.js +21 -0
- package/dist/core/research/model-fitness.js.map +1 -1
- package/dist/core/research/session-evidence-bundle.d.ts +1 -1
- package/dist/core/research/session-evidence-bundle.d.ts.map +1 -1
- package/dist/core/research/session-evidence-bundle.js.map +1 -1
- package/dist/core/runtime-builder.d.ts +222 -0
- package/dist/core/runtime-builder.d.ts.map +1 -0
- package/dist/core/runtime-builder.js +640 -0
- package/dist/core/runtime-builder.js.map +1 -0
- package/dist/core/sdk.d.ts +2 -2
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +3 -4
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-analytics.d.ts +96 -0
- package/dist/core/session-analytics.d.ts.map +1 -0
- package/dist/core/session-analytics.js +331 -0
- package/dist/core/session-analytics.js.map +1 -0
- package/dist/core/session-manager-factory.d.ts +18 -0
- package/dist/core/session-manager-factory.d.ts.map +1 -0
- package/dist/core/session-manager-factory.js +32 -0
- package/dist/core/session-manager-factory.js.map +1 -0
- package/dist/core/session-tree-navigator.d.ts +68 -0
- package/dist/core/session-tree-navigator.d.ts.map +1 -0
- package/dist/core/session-tree-navigator.js +217 -0
- package/dist/core/session-tree-navigator.js.map +1 -0
- package/dist/core/system-prompt-builder.d.ts +63 -0
- package/dist/core/system-prompt-builder.d.ts.map +1 -0
- package/dist/core/system-prompt-builder.js +181 -0
- package/dist/core/system-prompt-builder.js.map +1 -0
- package/dist/core/tool-gate-controller.d.ts +36 -0
- package/dist/core/tool-gate-controller.d.ts.map +1 -0
- package/dist/core/tool-gate-controller.js +94 -0
- package/dist/core/tool-gate-controller.js.map +1 -0
- package/dist/core/tools/artifact-retrieve.d.ts.map +1 -1
- package/dist/core/tools/artifact-retrieve.js +1 -1
- package/dist/core/tools/artifact-retrieve.js.map +1 -1
- package/dist/core/tools/bash.d.ts +4 -2
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +49 -7
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/file-mutation-queue.d.ts +6 -0
- package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
- package/dist/core/tools/file-mutation-queue.js +61 -1
- package/dist/core/tools/file-mutation-queue.js.map +1 -1
- package/dist/core/tools/find.d.ts +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +1 -1
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +1 -1
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/index.d.ts +1 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +1 -1
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/ls.d.ts +1 -1
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +1 -1
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/output-accumulator.d.ts +1 -1
- package/dist/core/tools/output-accumulator.d.ts.map +1 -1
- package/dist/core/tools/output-accumulator.js +1 -1
- package/dist/core/tools/output-accumulator.js.map +1 -1
- package/dist/core/tools/read.d.ts +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +1 -1
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/render-utils.d.ts.map +1 -1
- package/dist/core/tools/render-utils.js +1 -1
- package/dist/core/tools/render-utils.js.map +1 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -4
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +13 -12
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/auth-dialogs-controller.d.ts +59 -0
- package/dist/modes/interactive/auth-dialogs-controller.d.ts.map +1 -0
- package/dist/modes/interactive/auth-dialogs-controller.js +398 -0
- package/dist/modes/interactive/auth-dialogs-controller.js.map +1 -0
- package/dist/modes/interactive/auto-learn-controller.d.ts +168 -0
- package/dist/modes/interactive/auto-learn-controller.d.ts.map +1 -0
- package/dist/modes/interactive/auto-learn-controller.js +1239 -0
- package/dist/modes/interactive/auto-learn-controller.js.map +1 -0
- package/dist/modes/interactive/autocomplete-provider.d.ts +26 -0
- package/dist/modes/interactive/autocomplete-provider.d.ts.map +1 -0
- package/dist/modes/interactive/autocomplete-provider.js +103 -0
- package/dist/modes/interactive/autocomplete-provider.js.map +1 -0
- package/dist/modes/interactive/autonomy-commands.d.ts +38 -0
- package/dist/modes/interactive/autonomy-commands.d.ts.map +1 -0
- package/dist/modes/interactive/autonomy-commands.js +142 -0
- package/dist/modes/interactive/autonomy-commands.js.map +1 -0
- package/dist/modes/interactive/clipboard-input.d.ts +37 -0
- package/dist/modes/interactive/clipboard-input.d.ts.map +1 -0
- package/dist/modes/interactive/clipboard-input.js +59 -0
- package/dist/modes/interactive/clipboard-input.js.map +1 -0
- package/dist/modes/interactive/compaction-queue.d.ts +30 -0
- package/dist/modes/interactive/compaction-queue.d.ts.map +1 -0
- package/dist/modes/interactive/compaction-queue.js +91 -0
- package/dist/modes/interactive/compaction-queue.js.map +1 -0
- package/dist/modes/interactive/components/bash-execution.d.ts +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/bash-execution.js +1 -1
- package/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts +1 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/custom-message.d.ts +1 -1
- package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-message.js.map +1 -1
- package/dist/modes/interactive/components/session-selector-search.d.ts +1 -1
- package/dist/modes/interactive/components/session-selector-search.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector-search.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +2 -2
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tree-selector.d.ts +1 -1
- package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-selector.js.map +1 -1
- package/dist/modes/interactive/config-backup.d.ts +27 -0
- package/dist/modes/interactive/config-backup.d.ts.map +1 -0
- package/dist/modes/interactive/config-backup.js +146 -0
- package/dist/modes/interactive/config-backup.js.map +1 -0
- package/dist/modes/interactive/editor-overlay-host.d.ts +48 -0
- package/dist/modes/interactive/editor-overlay-host.d.ts.map +1 -0
- package/dist/modes/interactive/editor-overlay-host.js +43 -0
- package/dist/modes/interactive/editor-overlay-host.js.map +1 -0
- package/dist/modes/interactive/extension-ui-host.d.ts +165 -0
- package/dist/modes/interactive/extension-ui-host.d.ts.map +1 -0
- package/dist/modes/interactive/extension-ui-host.js +610 -0
- package/dist/modes/interactive/extension-ui-host.js.map +1 -0
- package/dist/modes/interactive/external-editor.d.ts +18 -0
- package/dist/modes/interactive/external-editor.d.ts.map +1 -0
- package/dist/modes/interactive/external-editor.js +107 -0
- package/dist/modes/interactive/external-editor.js.map +1 -0
- package/dist/modes/interactive/history-reload-math.d.ts +24 -0
- package/dist/modes/interactive/history-reload-math.d.ts.map +1 -0
- package/dist/modes/interactive/history-reload-math.js +129 -0
- package/dist/modes/interactive/history-reload-math.js.map +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts +22 -238
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +714 -5910
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/key-handlers.d.ts +46 -0
- package/dist/modes/interactive/key-handlers.d.ts.map +1 -0
- package/dist/modes/interactive/key-handlers.js +80 -0
- package/dist/modes/interactive/key-handlers.js.map +1 -0
- package/dist/modes/interactive/local-model-commands.d.ts +68 -0
- package/dist/modes/interactive/local-model-commands.d.ts.map +1 -0
- package/dist/modes/interactive/local-model-commands.js +288 -0
- package/dist/modes/interactive/local-model-commands.js.map +1 -0
- package/dist/modes/interactive/profile-menu-controller.d.ts +77 -0
- package/dist/modes/interactive/profile-menu-controller.d.ts.map +1 -0
- package/dist/modes/interactive/profile-menu-controller.js +917 -0
- package/dist/modes/interactive/profile-menu-controller.js.map +1 -0
- package/dist/modes/interactive/report-commands.d.ts +46 -0
- package/dist/modes/interactive/report-commands.d.ts.map +1 -0
- package/dist/modes/interactive/report-commands.js +255 -0
- package/dist/modes/interactive/report-commands.js.map +1 -0
- package/dist/modes/interactive/resource-display.d.ts +73 -0
- package/dist/modes/interactive/resource-display.d.ts.map +1 -0
- package/dist/modes/interactive/resource-display.js +278 -0
- package/dist/modes/interactive/resource-display.js.map +1 -0
- package/dist/modes/interactive/resource-shell-commands.d.ts +64 -0
- package/dist/modes/interactive/resource-shell-commands.d.ts.map +1 -0
- package/dist/modes/interactive/resource-shell-commands.js +202 -0
- package/dist/modes/interactive/resource-shell-commands.js.map +1 -0
- package/dist/modes/interactive/session-flow-commands.d.ts +139 -0
- package/dist/modes/interactive/session-flow-commands.d.ts.map +1 -0
- package/dist/modes/interactive/session-flow-commands.js +458 -0
- package/dist/modes/interactive/session-flow-commands.js.map +1 -0
- package/dist/modes/interactive/session-io-commands.d.ts +73 -0
- package/dist/modes/interactive/session-io-commands.d.ts.map +1 -0
- package/dist/modes/interactive/session-io-commands.js +202 -0
- package/dist/modes/interactive/session-io-commands.js.map +1 -0
- package/dist/modes/interactive/settings-selector-flow.d.ts +42 -0
- package/dist/modes/interactive/settings-selector-flow.d.ts.map +1 -0
- package/dist/modes/interactive/settings-selector-flow.js +287 -0
- package/dist/modes/interactive/settings-selector-flow.js.map +1 -0
- package/dist/modes/interactive/signal-lifecycle.d.ts +55 -0
- package/dist/modes/interactive/signal-lifecycle.d.ts.map +1 -0
- package/dist/modes/interactive/signal-lifecycle.js +177 -0
- package/dist/modes/interactive/signal-lifecycle.js.map +1 -0
- package/dist/modes/interactive/startup-checks.d.ts +38 -0
- package/dist/modes/interactive/startup-checks.d.ts.map +1 -0
- package/dist/modes/interactive/startup-checks.js +189 -0
- package/dist/modes/interactive/startup-checks.js.map +1 -0
- package/dist/modes/rpc/rpc-client.d.ts +1 -1
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +1 -1
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/utils/paths.d.ts +2 -14
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +5 -30
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/process-memory.d.ts +8 -0
- package/dist/utils/process-memory.d.ts.map +1 -0
- package/dist/utils/process-memory.js +11 -0
- package/dist/utils/process-memory.js.map +1 -0
- package/dist/utils/shell.d.ts +0 -9
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +0 -37
- package/dist/utils/shell.js.map +1 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/sdk/11-sessions.ts +8 -8
- package/examples/sdk/13-session-runtime.ts +1 -1
- package/npm-shrinkwrap.json +12 -12
- package/package.json +4 -4
- package/dist/core/compaction/branch-summarization.d.ts +0 -88
- package/dist/core/compaction/branch-summarization.d.ts.map +0 -1
- package/dist/core/compaction/branch-summarization.js +0 -243
- package/dist/core/compaction/branch-summarization.js.map +0 -1
- package/dist/core/compaction/compaction.d.ts +0 -143
- package/dist/core/compaction/compaction.d.ts.map +0 -1
- package/dist/core/compaction/compaction.js +0 -659
- package/dist/core/compaction/compaction.js.map +0 -1
- package/dist/core/compaction/index.d.ts +0 -7
- package/dist/core/compaction/index.d.ts.map +0 -1
- package/dist/core/compaction/index.js +0 -7
- package/dist/core/compaction/index.js.map +0 -1
- package/dist/core/compaction/utils.d.ts +0 -38
- package/dist/core/compaction/utils.d.ts.map +0 -1
- package/dist/core/compaction/utils.js +0 -153
- package/dist/core/compaction/utils.js.map +0 -1
- package/dist/core/message-retention.d.ts +0 -26
- package/dist/core/message-retention.d.ts.map +0 -1
- package/dist/core/message-retention.js +0 -95
- package/dist/core/message-retention.js.map +0 -1
- package/dist/core/messages.d.ts +0 -77
- package/dist/core/messages.d.ts.map +0 -1
- package/dist/core/messages.js +0 -123
- package/dist/core/messages.js.map +0 -1
- package/dist/core/session-manager.d.ts +0 -337
- package/dist/core/session-manager.d.ts.map +0 -1
- package/dist/core/session-manager.js +0 -1328
- package/dist/core/session-manager.js.map +0 -1
- package/dist/core/tools/truncate.d.ts +0 -70
- package/dist/core/tools/truncate.d.ts.map +0 -1
- package/dist/core/tools/truncate.js +0 -215
- package/dist/core/tools/truncate.js.map +0 -1
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config backup/restore commands extracted from interactive-mode.
|
|
3
|
+
*
|
|
4
|
+
* `handleConfigBackupCommand` snapshots the reusable-file profiles and the
|
|
5
|
+
* resource-relevant settings to a JSON bundle; `handleConfigRestoreCommand`
|
|
6
|
+
* reads such a bundle back, confirming with the user before clobbering local
|
|
7
|
+
* state. Both operate through a narrow `host` seam (settings manager + the
|
|
8
|
+
* status/error/selector callbacks) so the file IO and validation live outside
|
|
9
|
+
* the god file while interactive-mode keeps only thin delegating wrappers.
|
|
10
|
+
*/
|
|
11
|
+
import * as fs from "node:fs";
|
|
12
|
+
import * as path from "node:path";
|
|
13
|
+
import { getAgentDir } from "../../config.js";
|
|
14
|
+
import { SelectSubmenu } from "./components/settings-selector.js";
|
|
15
|
+
export async function handleConfigBackupCommand(host, fileArg) {
|
|
16
|
+
try {
|
|
17
|
+
const profilesDir = path.join(getAgentDir(), "profiles");
|
|
18
|
+
const profiles = {};
|
|
19
|
+
if (fs.existsSync(profilesDir)) {
|
|
20
|
+
const entries = fs.readdirSync(profilesDir);
|
|
21
|
+
for (const entry of entries) {
|
|
22
|
+
if (entry.endsWith(".json")) {
|
|
23
|
+
const pPath = path.join(profilesDir, entry);
|
|
24
|
+
try {
|
|
25
|
+
const content = fs.readFileSync(pPath, "utf-8");
|
|
26
|
+
profiles[entry] = JSON.parse(content);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// skip
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const backupData = {
|
|
35
|
+
profiles,
|
|
36
|
+
settings: {
|
|
37
|
+
resourceProfiles: host.settingsManager.settings.resourceProfiles,
|
|
38
|
+
activeResourceProfile: host.settingsManager.settings.activeResourceProfile,
|
|
39
|
+
externalResourceRoots: host.settingsManager.settings.externalResourceRoots,
|
|
40
|
+
trustedResourceRoots: host.settingsManager.settings.trustedResourceRoots,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
let targetFile = fileArg;
|
|
44
|
+
if (!targetFile) {
|
|
45
|
+
const backupsDir = path.join(getAgentDir(), "backups");
|
|
46
|
+
fs.mkdirSync(backupsDir, { recursive: true });
|
|
47
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
48
|
+
targetFile = path.join(backupsDir, `config-${timestamp}.json`);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
const resolved = host.settingsManager.canonicalizePath(targetFile);
|
|
52
|
+
if (resolved) {
|
|
53
|
+
targetFile = resolved;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
fs.mkdirSync(path.dirname(targetFile), { recursive: true });
|
|
57
|
+
fs.writeFileSync(targetFile, JSON.stringify(backupData, null, 2), "utf-8");
|
|
58
|
+
host.showStatus(`Configuration backup saved to ${targetFile}`);
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
host.showError(error instanceof Error ? error.message : String(error));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export async function handleConfigRestoreCommand(host, fileArg) {
|
|
65
|
+
try {
|
|
66
|
+
const trimmed = fileArg.trim();
|
|
67
|
+
if (!trimmed) {
|
|
68
|
+
host.showError("Usage: /config-restore <file>");
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const resolved = host.settingsManager.canonicalizePath(trimmed);
|
|
72
|
+
if (!resolved || !fs.existsSync(resolved)) {
|
|
73
|
+
host.showError(`Backup file does not exist: ${trimmed}`);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
let bundle;
|
|
77
|
+
try {
|
|
78
|
+
const content = fs.readFileSync(resolved, "utf-8");
|
|
79
|
+
bundle = JSON.parse(content);
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
host.showError(`Failed to parse backup file: ${error instanceof Error ? error.message : String(error)}`);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (!bundle || typeof bundle !== "object") {
|
|
86
|
+
host.showError("Invalid backup file: must be a JSON object");
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
// Confirm before clobbering
|
|
90
|
+
const confirm = await new Promise((resolve) => {
|
|
91
|
+
host.showSelector((done) => {
|
|
92
|
+
const submenu = new SelectSubmenu("Restore configuration?", "This will overwrite existing local profiles and settings with the backup values. Do you want to continue?", [
|
|
93
|
+
{ value: "yes", label: "Yes", description: "Proceed with restoration." },
|
|
94
|
+
{ value: "no", label: "No", description: "Cancel and abort." },
|
|
95
|
+
], "no", (value) => {
|
|
96
|
+
done();
|
|
97
|
+
resolve(value === "yes");
|
|
98
|
+
}, () => {
|
|
99
|
+
done();
|
|
100
|
+
resolve(false);
|
|
101
|
+
});
|
|
102
|
+
return { component: submenu, focus: submenu.getSelectList() };
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
if (!confirm) {
|
|
106
|
+
host.showStatus("Restore aborted.");
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
// 1. Restore profile files (reusable-file scope)
|
|
110
|
+
if (bundle.profiles && typeof bundle.profiles === "object") {
|
|
111
|
+
const profilesDir = path.join(getAgentDir(), "profiles");
|
|
112
|
+
fs.mkdirSync(profilesDir, { recursive: true });
|
|
113
|
+
for (const [filename, content] of Object.entries(bundle.profiles)) {
|
|
114
|
+
const targetPath = path.join(profilesDir, filename);
|
|
115
|
+
fs.writeFileSync(targetPath, JSON.stringify(content, null, 2), "utf-8");
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// 2. Restore settings
|
|
119
|
+
if (bundle.settings && typeof bundle.settings === "object") {
|
|
120
|
+
const bs = bundle.settings;
|
|
121
|
+
// Global profiles definitions
|
|
122
|
+
if (bs.resourceProfiles && typeof bs.resourceProfiles === "object") {
|
|
123
|
+
for (const [name, definition] of Object.entries(bs.resourceProfiles)) {
|
|
124
|
+
host.settingsManager.setProfileDefinition(name, definition, "global");
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// Active profile selection
|
|
128
|
+
if (bs.activeResourceProfile) {
|
|
129
|
+
host.settingsManager.setActiveProfile(bs.activeResourceProfile, "global");
|
|
130
|
+
}
|
|
131
|
+
// External roots (trustedRoots are NOT restored, as per SECURITY requirement)
|
|
132
|
+
if (Array.isArray(bs.externalResourceRoots)) {
|
|
133
|
+
host.settingsManager.setExternalResourceRoots(bs.externalResourceRoots, "global");
|
|
134
|
+
const currentTrusted = host.settingsManager.getTrustedResourceRoots();
|
|
135
|
+
const newTrusted = currentTrusted.filter((r) => !bs.externalResourceRoots.includes(r));
|
|
136
|
+
host.settingsManager.setTrustedResourceRoots(newTrusted, "global");
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
host.showStatus("Configuration restored successfully.");
|
|
140
|
+
await host.handleReloadCommand();
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
host.showError(error instanceof Error ? error.message : String(error));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=config-backup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-backup.js","sourceRoot":"","sources":["../../../src/modes/interactive/config-backup.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Config backup/restore commands extracted from interactive-mode.\n *\n * `handleConfigBackupCommand` snapshots the reusable-file profiles and the\n * resource-relevant settings to a JSON bundle; `handleConfigRestoreCommand`\n * reads such a bundle back, confirming with the user before clobbering local\n * state. Both operate through a narrow `host` seam (settings manager + the\n * status/error/selector callbacks) so the file IO and validation live outside\n * the god file while interactive-mode keeps only thin delegating wrappers.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { Component } from \"@caupulican/pi-tui\";\nimport { getAgentDir } from \"../../config.ts\";\nimport type { SettingsManager } from \"../../core/settings-manager.ts\";\nimport { SelectSubmenu } from \"./components/settings-selector.ts\";\n\nexport interface ConfigBackupHost {\n\treadonly settingsManager: SettingsManager;\n\tshowStatus(message: string): void;\n\tshowError(errorMessage: string): void;\n}\n\nexport interface ConfigRestoreHost extends ConfigBackupHost {\n\tshowSelector(create: (done: () => void) => { component: Component; focus: Component }): void;\n\thandleReloadCommand(): Promise<void>;\n}\n\nexport async function handleConfigBackupCommand(host: ConfigBackupHost, fileArg?: string): Promise<void> {\n\ttry {\n\t\tconst profilesDir = path.join(getAgentDir(), \"profiles\");\n\t\tconst profiles: Record<string, any> = {};\n\t\tif (fs.existsSync(profilesDir)) {\n\t\t\tconst entries = fs.readdirSync(profilesDir);\n\t\t\tfor (const entry of entries) {\n\t\t\t\tif (entry.endsWith(\".json\")) {\n\t\t\t\t\tconst pPath = path.join(profilesDir, entry);\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst content = fs.readFileSync(pPath, \"utf-8\");\n\t\t\t\t\t\tprofiles[entry] = JSON.parse(content);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// skip\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst backupData = {\n\t\t\tprofiles,\n\t\t\tsettings: {\n\t\t\t\tresourceProfiles: host.settingsManager.settings.resourceProfiles,\n\t\t\t\tactiveResourceProfile: host.settingsManager.settings.activeResourceProfile,\n\t\t\t\texternalResourceRoots: host.settingsManager.settings.externalResourceRoots,\n\t\t\t\ttrustedResourceRoots: host.settingsManager.settings.trustedResourceRoots,\n\t\t\t},\n\t\t};\n\n\t\tlet targetFile = fileArg;\n\t\tif (!targetFile) {\n\t\t\tconst backupsDir = path.join(getAgentDir(), \"backups\");\n\t\t\tfs.mkdirSync(backupsDir, { recursive: true });\n\t\t\tconst timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n\t\t\ttargetFile = path.join(backupsDir, `config-${timestamp}.json`);\n\t\t} else {\n\t\t\tconst resolved = host.settingsManager.canonicalizePath(targetFile);\n\t\t\tif (resolved) {\n\t\t\t\ttargetFile = resolved;\n\t\t\t}\n\t\t}\n\n\t\tfs.mkdirSync(path.dirname(targetFile), { recursive: true });\n\t\tfs.writeFileSync(targetFile, JSON.stringify(backupData, null, 2), \"utf-8\");\n\t\thost.showStatus(`Configuration backup saved to ${targetFile}`);\n\t} catch (error) {\n\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t}\n}\n\nexport async function handleConfigRestoreCommand(host: ConfigRestoreHost, fileArg: string): Promise<void> {\n\ttry {\n\t\tconst trimmed = fileArg.trim();\n\t\tif (!trimmed) {\n\t\t\thost.showError(\"Usage: /config-restore <file>\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst resolved = host.settingsManager.canonicalizePath(trimmed);\n\t\tif (!resolved || !fs.existsSync(resolved)) {\n\t\t\thost.showError(`Backup file does not exist: ${trimmed}`);\n\t\t\treturn;\n\t\t}\n\n\t\tlet bundle: any;\n\t\ttry {\n\t\t\tconst content = fs.readFileSync(resolved, \"utf-8\");\n\t\t\tbundle = JSON.parse(content);\n\t\t} catch (error) {\n\t\t\thost.showError(`Failed to parse backup file: ${error instanceof Error ? error.message : String(error)}`);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!bundle || typeof bundle !== \"object\") {\n\t\t\thost.showError(\"Invalid backup file: must be a JSON object\");\n\t\t\treturn;\n\t\t}\n\n\t\t// Confirm before clobbering\n\t\tconst confirm = await new Promise<boolean>((resolve) => {\n\t\t\thost.showSelector((done) => {\n\t\t\t\tconst submenu = new SelectSubmenu(\n\t\t\t\t\t\"Restore configuration?\",\n\t\t\t\t\t\"This will overwrite existing local profiles and settings with the backup values. Do you want to continue?\",\n\t\t\t\t\t[\n\t\t\t\t\t\t{ value: \"yes\", label: \"Yes\", description: \"Proceed with restoration.\" },\n\t\t\t\t\t\t{ value: \"no\", label: \"No\", description: \"Cancel and abort.\" },\n\t\t\t\t\t],\n\t\t\t\t\t\"no\",\n\t\t\t\t\t(value) => {\n\t\t\t\t\t\tdone();\n\t\t\t\t\t\tresolve(value === \"yes\");\n\t\t\t\t\t},\n\t\t\t\t\t() => {\n\t\t\t\t\t\tdone();\n\t\t\t\t\t\tresolve(false);\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\treturn { component: submenu, focus: submenu.getSelectList() };\n\t\t\t});\n\t\t});\n\n\t\tif (!confirm) {\n\t\t\thost.showStatus(\"Restore aborted.\");\n\t\t\treturn;\n\t\t}\n\n\t\t// 1. Restore profile files (reusable-file scope)\n\t\tif (bundle.profiles && typeof bundle.profiles === \"object\") {\n\t\t\tconst profilesDir = path.join(getAgentDir(), \"profiles\");\n\t\t\tfs.mkdirSync(profilesDir, { recursive: true });\n\t\t\tfor (const [filename, content] of Object.entries(bundle.profiles)) {\n\t\t\t\tconst targetPath = path.join(profilesDir, filename);\n\t\t\t\tfs.writeFileSync(targetPath, JSON.stringify(content, null, 2), \"utf-8\");\n\t\t\t}\n\t\t}\n\n\t\t// 2. Restore settings\n\t\tif (bundle.settings && typeof bundle.settings === \"object\") {\n\t\t\tconst bs = bundle.settings;\n\n\t\t\t// Global profiles definitions\n\t\t\tif (bs.resourceProfiles && typeof bs.resourceProfiles === \"object\") {\n\t\t\t\tfor (const [name, definition] of Object.entries(bs.resourceProfiles)) {\n\t\t\t\t\thost.settingsManager.setProfileDefinition(name, definition as any, \"global\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Active profile selection\n\t\t\tif (bs.activeResourceProfile) {\n\t\t\t\thost.settingsManager.setActiveProfile(bs.activeResourceProfile, \"global\");\n\t\t\t}\n\n\t\t\t// External roots (trustedRoots are NOT restored, as per SECURITY requirement)\n\t\t\tif (Array.isArray(bs.externalResourceRoots)) {\n\t\t\t\thost.settingsManager.setExternalResourceRoots(bs.externalResourceRoots, \"global\");\n\n\t\t\t\tconst currentTrusted = host.settingsManager.getTrustedResourceRoots();\n\t\t\t\tconst newTrusted = currentTrusted.filter((r) => !bs.externalResourceRoots.includes(r));\n\t\t\t\thost.settingsManager.setTrustedResourceRoots(newTrusted, \"global\");\n\t\t\t}\n\t\t}\n\n\t\thost.showStatus(\"Configuration restored successfully.\");\n\t\tawait host.handleReloadCommand();\n\t} catch (error) {\n\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t}\n}\n"]}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Component, Container } from "@caupulican/pi-tui";
|
|
2
|
+
/** Focus operation the host performs after mounting a component. */
|
|
3
|
+
export type EditorOverlayFocusMode = "set" | "restore";
|
|
4
|
+
/** How the host requests a render after swapping the mounted component. */
|
|
5
|
+
export type EditorOverlayRenderMode = "default" | "sync" | "none";
|
|
6
|
+
/** The subset of the TUI the host drives. */
|
|
7
|
+
export interface EditorOverlayUi {
|
|
8
|
+
setFocus(component: Component | null): void;
|
|
9
|
+
restoreFocus(component: Component | null): void;
|
|
10
|
+
requestRender(force?: boolean): void;
|
|
11
|
+
}
|
|
12
|
+
export interface EditorOverlaySwapOptions {
|
|
13
|
+
/** Component to focus after mounting. Defaults to the mounted component. */
|
|
14
|
+
focus?: Component;
|
|
15
|
+
/**
|
|
16
|
+
* Whether to `setFocus` the target (default) or `restoreFocus` it. The latter
|
|
17
|
+
* prefers the topmost visible overlay and is used when returning to the editor
|
|
18
|
+
* from a selector.
|
|
19
|
+
*/
|
|
20
|
+
focusMode?: EditorOverlayFocusMode;
|
|
21
|
+
/**
|
|
22
|
+
* Render request issued after the swap:
|
|
23
|
+
* - `"default"` (default): `requestRender()`
|
|
24
|
+
* - `"sync"`: `requestRender(true)` — force an immediate paint
|
|
25
|
+
* - `"none"`: leave rendering to the caller
|
|
26
|
+
*/
|
|
27
|
+
render?: EditorOverlayRenderMode;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Owns the single-slot editor container and performs the recurring
|
|
31
|
+
* "clear → mount one component → focus a target → request a render" swap that
|
|
32
|
+
* interactive mode uses to show overlays (selectors, dialogs, loaders) in place
|
|
33
|
+
* of the editor and to restore the editor afterwards.
|
|
34
|
+
*
|
|
35
|
+
* The host is stateless beyond its container/ui references: every swap names the
|
|
36
|
+
* component to mount, so a changing active editor never has to be tracked here.
|
|
37
|
+
*/
|
|
38
|
+
export declare class EditorOverlayHost {
|
|
39
|
+
private readonly container;
|
|
40
|
+
private readonly ui;
|
|
41
|
+
constructor(container: Container, ui: EditorOverlayUi);
|
|
42
|
+
/**
|
|
43
|
+
* Replace whatever the editor container currently holds with `component`,
|
|
44
|
+
* focus a target, and (by default) request a render.
|
|
45
|
+
*/
|
|
46
|
+
swap(component: Component, options?: EditorOverlaySwapOptions): void;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=editor-overlay-host.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"editor-overlay-host.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/editor-overlay-host.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/D,oEAAoE;AACpE,MAAM,MAAM,sBAAsB,GAAG,KAAK,GAAG,SAAS,CAAC;AAEvD,2EAA2E;AAC3E,MAAM,MAAM,uBAAuB,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;AAElE,6CAA6C;AAC7C,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,GAAG,IAAI,CAAC;IAC5C,YAAY,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,GAAG,IAAI,CAAC;IAChD,aAAa,CAAC,KAAK,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CACrC;AAED,MAAM,WAAW,wBAAwB;IACxC,4EAA4E;IAC5E,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;;;;OAIG;IACH,SAAS,CAAC,EAAE,sBAAsB,CAAC;IACnC;;;;;OAKG;IACH,MAAM,CAAC,EAAE,uBAAuB,CAAC;CACjC;AAED;;;;;;;;GAQG;AACH,qBAAa,iBAAiB;IAC7B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAkB;IAErC,YAAY,SAAS,EAAE,SAAS,EAAE,EAAE,EAAE,eAAe,EAGpD;IAED;;;OAGG;IACH,IAAI,CAAC,SAAS,EAAE,SAAS,EAAE,OAAO,GAAE,wBAA6B,GAAG,IAAI,CAmBvE;CACD","sourcesContent":["import type { Component, Container } from \"@caupulican/pi-tui\";\n\n/** Focus operation the host performs after mounting a component. */\nexport type EditorOverlayFocusMode = \"set\" | \"restore\";\n\n/** How the host requests a render after swapping the mounted component. */\nexport type EditorOverlayRenderMode = \"default\" | \"sync\" | \"none\";\n\n/** The subset of the TUI the host drives. */\nexport interface EditorOverlayUi {\n\tsetFocus(component: Component | null): void;\n\trestoreFocus(component: Component | null): void;\n\trequestRender(force?: boolean): void;\n}\n\nexport interface EditorOverlaySwapOptions {\n\t/** Component to focus after mounting. Defaults to the mounted component. */\n\tfocus?: Component;\n\t/**\n\t * Whether to `setFocus` the target (default) or `restoreFocus` it. The latter\n\t * prefers the topmost visible overlay and is used when returning to the editor\n\t * from a selector.\n\t */\n\tfocusMode?: EditorOverlayFocusMode;\n\t/**\n\t * Render request issued after the swap:\n\t * - `\"default\"` (default): `requestRender()`\n\t * - `\"sync\"`: `requestRender(true)` — force an immediate paint\n\t * - `\"none\"`: leave rendering to the caller\n\t */\n\trender?: EditorOverlayRenderMode;\n}\n\n/**\n * Owns the single-slot editor container and performs the recurring\n * \"clear → mount one component → focus a target → request a render\" swap that\n * interactive mode uses to show overlays (selectors, dialogs, loaders) in place\n * of the editor and to restore the editor afterwards.\n *\n * The host is stateless beyond its container/ui references: every swap names the\n * component to mount, so a changing active editor never has to be tracked here.\n */\nexport class EditorOverlayHost {\n\tprivate readonly container: Container;\n\tprivate readonly ui: EditorOverlayUi;\n\n\tconstructor(container: Container, ui: EditorOverlayUi) {\n\t\tthis.container = container;\n\t\tthis.ui = ui;\n\t}\n\n\t/**\n\t * Replace whatever the editor container currently holds with `component`,\n\t * focus a target, and (by default) request a render.\n\t */\n\tswap(component: Component, options: EditorOverlaySwapOptions = {}): void {\n\t\tthis.container.clear();\n\t\tthis.container.addChild(component);\n\t\tconst target = options.focus ?? component;\n\t\tif (options.focusMode === \"restore\") {\n\t\t\tthis.ui.restoreFocus(target);\n\t\t} else {\n\t\t\tthis.ui.setFocus(target);\n\t\t}\n\t\tswitch (options.render ?? \"default\") {\n\t\t\tcase \"default\":\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\tbreak;\n\t\t\tcase \"sync\":\n\t\t\t\tthis.ui.requestRender(true);\n\t\t\t\tbreak;\n\t\t\tcase \"none\":\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n"]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Owns the single-slot editor container and performs the recurring
|
|
3
|
+
* "clear → mount one component → focus a target → request a render" swap that
|
|
4
|
+
* interactive mode uses to show overlays (selectors, dialogs, loaders) in place
|
|
5
|
+
* of the editor and to restore the editor afterwards.
|
|
6
|
+
*
|
|
7
|
+
* The host is stateless beyond its container/ui references: every swap names the
|
|
8
|
+
* component to mount, so a changing active editor never has to be tracked here.
|
|
9
|
+
*/
|
|
10
|
+
export class EditorOverlayHost {
|
|
11
|
+
container;
|
|
12
|
+
ui;
|
|
13
|
+
constructor(container, ui) {
|
|
14
|
+
this.container = container;
|
|
15
|
+
this.ui = ui;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Replace whatever the editor container currently holds with `component`,
|
|
19
|
+
* focus a target, and (by default) request a render.
|
|
20
|
+
*/
|
|
21
|
+
swap(component, options = {}) {
|
|
22
|
+
this.container.clear();
|
|
23
|
+
this.container.addChild(component);
|
|
24
|
+
const target = options.focus ?? component;
|
|
25
|
+
if (options.focusMode === "restore") {
|
|
26
|
+
this.ui.restoreFocus(target);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
this.ui.setFocus(target);
|
|
30
|
+
}
|
|
31
|
+
switch (options.render ?? "default") {
|
|
32
|
+
case "default":
|
|
33
|
+
this.ui.requestRender();
|
|
34
|
+
break;
|
|
35
|
+
case "sync":
|
|
36
|
+
this.ui.requestRender(true);
|
|
37
|
+
break;
|
|
38
|
+
case "none":
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=editor-overlay-host.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"editor-overlay-host.js","sourceRoot":"","sources":["../../../src/modes/interactive/editor-overlay-host.ts"],"names":[],"mappings":"AAiCA;;;;;;;;GAQG;AACH,MAAM,OAAO,iBAAiB;IACZ,SAAS,CAAY;IACrB,EAAE,CAAkB;IAErC,YAAY,SAAoB,EAAE,EAAmB,EAAE;QACtD,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;IAAA,CACb;IAED;;;OAGG;IACH,IAAI,CAAC,SAAoB,EAAE,OAAO,GAA6B,EAAE,EAAQ;QACxE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;QACvB,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QACnC,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,IAAI,SAAS,CAAC;QAC1C,IAAI,OAAO,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;YACrC,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QAC9B,CAAC;aAAM,CAAC;YACP,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC1B,CAAC;QACD,QAAQ,OAAO,CAAC,MAAM,IAAI,SAAS,EAAE,CAAC;YACrC,KAAK,SAAS;gBACb,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;gBACxB,MAAM;YACP,KAAK,MAAM;gBACV,IAAI,CAAC,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;gBAC5B,MAAM;YACP,KAAK,MAAM;gBACV,MAAM;QACR,CAAC;IAAA,CACD;CACD","sourcesContent":["import type { Component, Container } from \"@caupulican/pi-tui\";\n\n/** Focus operation the host performs after mounting a component. */\nexport type EditorOverlayFocusMode = \"set\" | \"restore\";\n\n/** How the host requests a render after swapping the mounted component. */\nexport type EditorOverlayRenderMode = \"default\" | \"sync\" | \"none\";\n\n/** The subset of the TUI the host drives. */\nexport interface EditorOverlayUi {\n\tsetFocus(component: Component | null): void;\n\trestoreFocus(component: Component | null): void;\n\trequestRender(force?: boolean): void;\n}\n\nexport interface EditorOverlaySwapOptions {\n\t/** Component to focus after mounting. Defaults to the mounted component. */\n\tfocus?: Component;\n\t/**\n\t * Whether to `setFocus` the target (default) or `restoreFocus` it. The latter\n\t * prefers the topmost visible overlay and is used when returning to the editor\n\t * from a selector.\n\t */\n\tfocusMode?: EditorOverlayFocusMode;\n\t/**\n\t * Render request issued after the swap:\n\t * - `\"default\"` (default): `requestRender()`\n\t * - `\"sync\"`: `requestRender(true)` — force an immediate paint\n\t * - `\"none\"`: leave rendering to the caller\n\t */\n\trender?: EditorOverlayRenderMode;\n}\n\n/**\n * Owns the single-slot editor container and performs the recurring\n * \"clear → mount one component → focus a target → request a render\" swap that\n * interactive mode uses to show overlays (selectors, dialogs, loaders) in place\n * of the editor and to restore the editor afterwards.\n *\n * The host is stateless beyond its container/ui references: every swap names the\n * component to mount, so a changing active editor never has to be tracked here.\n */\nexport class EditorOverlayHost {\n\tprivate readonly container: Container;\n\tprivate readonly ui: EditorOverlayUi;\n\n\tconstructor(container: Container, ui: EditorOverlayUi) {\n\t\tthis.container = container;\n\t\tthis.ui = ui;\n\t}\n\n\t/**\n\t * Replace whatever the editor container currently holds with `component`,\n\t * focus a target, and (by default) request a render.\n\t */\n\tswap(component: Component, options: EditorOverlaySwapOptions = {}): void {\n\t\tthis.container.clear();\n\t\tthis.container.addChild(component);\n\t\tconst target = options.focus ?? component;\n\t\tif (options.focusMode === \"restore\") {\n\t\t\tthis.ui.restoreFocus(target);\n\t\t} else {\n\t\t\tthis.ui.setFocus(target);\n\t\t}\n\t\tswitch (options.render ?? \"default\") {\n\t\t\tcase \"default\":\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\tbreak;\n\t\t\tcase \"sync\":\n\t\t\t\tthis.ui.requestRender(true);\n\t\t\t\tbreak;\n\t\t\tcase \"none\":\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n"]}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extension UI host.
|
|
3
|
+
*
|
|
4
|
+
* Extracted verbatim from interactive-mode.ts (god-file decomposition). Owns the interactive-mode
|
|
5
|
+
* surface of the extension UI API: extension-provided dialogs (selector / confirm / input / multi-line
|
|
6
|
+
* editor / custom component), widget mounting above and below the editor, extension status text, custom
|
|
7
|
+
* footer and header swaps, the custom editor-component swap, keyboard-shortcut wiring, terminal-input
|
|
8
|
+
* listeners, and the `ExtensionUIContext` handed to extensions. It owns exactly the state this cluster
|
|
9
|
+
* mutates (the live dialog components, the widget maps, the terminal-input unsubscribers, the custom
|
|
10
|
+
* footer/header, and the current custom-editor factory) and reaches everything else — the mutable
|
|
11
|
+
* `editor`, the render-core containers, the working-indicator, autocomplete wiring, theme/tools state —
|
|
12
|
+
* through a narrow deps/ui surface rather than the whole InteractiveMode instance. Extensions must see
|
|
13
|
+
* identical behavior, so the render-core operations (working indicator, hidden-thinking label,
|
|
14
|
+
* autocomplete rebuild) stay host-side and are invoked here as delegations.
|
|
15
|
+
*/
|
|
16
|
+
import type { AutocompleteProvider, Component, EditorComponent, LoaderIndicatorOptions } from "@caupulican/pi-tui";
|
|
17
|
+
import { Container, type TUI } from "@caupulican/pi-tui";
|
|
18
|
+
import type { AgentSession } from "../../core/agent-session.ts";
|
|
19
|
+
import type { AutocompleteProviderFactory, ExtensionRunner, ExtensionUIContext, ExtensionUIDialogOptions } from "../../core/extensions/index.ts";
|
|
20
|
+
import type { FooterDataProvider } from "../../core/footer-data-provider.ts";
|
|
21
|
+
import type { KeybindingsManager } from "../../core/keybindings.ts";
|
|
22
|
+
import type { CustomEditor } from "./components/custom-editor.ts";
|
|
23
|
+
import type { FooterComponent } from "./components/footer.ts";
|
|
24
|
+
import type { EditorOverlayHost } from "./editor-overlay-host.ts";
|
|
25
|
+
/**
|
|
26
|
+
* The narrow slice of InteractiveMode the extension UI host drives. Stable containers and the TUI are
|
|
27
|
+
* passed directly; mutable render-core state (the active editor, built-in header, loading animation,
|
|
28
|
+
* autocomplete wrappers, tools-expansion) is read/written through accessors so InteractiveMode keeps
|
|
29
|
+
* ownership; and render-core / status operations stay host-side and are exposed here as callbacks.
|
|
30
|
+
*/
|
|
31
|
+
export interface ExtensionUiHostUi {
|
|
32
|
+
readonly tui: TUI;
|
|
33
|
+
readonly overlayHost: EditorOverlayHost;
|
|
34
|
+
readonly keybindings: KeybindingsManager;
|
|
35
|
+
readonly footer: FooterComponent;
|
|
36
|
+
readonly footerDataProvider: FooterDataProvider;
|
|
37
|
+
readonly headerContainer: Container;
|
|
38
|
+
readonly chatContainer: Container;
|
|
39
|
+
readonly editorContainer: Container;
|
|
40
|
+
readonly defaultEditor: CustomEditor;
|
|
41
|
+
readonly widgetContainerAbove: Container;
|
|
42
|
+
readonly widgetContainerBelow: Container;
|
|
43
|
+
getEditor(): EditorComponent;
|
|
44
|
+
setEditor(editor: EditorComponent): void;
|
|
45
|
+
getBuiltInHeader(): Component | undefined;
|
|
46
|
+
getAutocompleteProvider(): AutocompleteProvider | undefined;
|
|
47
|
+
getToolsExpanded(): boolean;
|
|
48
|
+
pushAutocompleteProviderWrapper(factory: AutocompleteProviderFactory): void;
|
|
49
|
+
resetAutocompleteProviderWrappers(): void;
|
|
50
|
+
setupAutocompleteProvider(): void;
|
|
51
|
+
setWorkingVisible(visible: boolean): void;
|
|
52
|
+
setWorkingIndicator(options?: LoaderIndicatorOptions): void;
|
|
53
|
+
setHiddenThinkingLabel(label?: string): void;
|
|
54
|
+
setWorkingMessage(message: string | undefined): void;
|
|
55
|
+
resetWorkingIndicators(): void;
|
|
56
|
+
setToolsExpanded(expanded: boolean): void;
|
|
57
|
+
toggleToolsExpanded(): void;
|
|
58
|
+
updateTerminalTitle(): void;
|
|
59
|
+
markShutdownRequested(): void;
|
|
60
|
+
abort(): void;
|
|
61
|
+
reload(): Promise<void>;
|
|
62
|
+
showStatus(message: string): void;
|
|
63
|
+
showWarning(message: string): void;
|
|
64
|
+
showError(message: string): void;
|
|
65
|
+
}
|
|
66
|
+
export interface ExtensionUiHostDeps {
|
|
67
|
+
getSession(): AgentSession;
|
|
68
|
+
ui: ExtensionUiHostUi;
|
|
69
|
+
}
|
|
70
|
+
export declare class ExtensionUiHost {
|
|
71
|
+
private readonly deps;
|
|
72
|
+
private extensionSelector;
|
|
73
|
+
private extensionInput;
|
|
74
|
+
private extensionEditor;
|
|
75
|
+
private extensionTerminalInputUnsubscribers;
|
|
76
|
+
private extensionWidgetsAbove;
|
|
77
|
+
private extensionWidgetsBelow;
|
|
78
|
+
private customFooter;
|
|
79
|
+
private customHeader;
|
|
80
|
+
private editorComponentFactory;
|
|
81
|
+
constructor(deps: ExtensionUiHostDeps);
|
|
82
|
+
private get session();
|
|
83
|
+
private get ui();
|
|
84
|
+
/** The active custom header, if an extension has installed one. */
|
|
85
|
+
getCustomHeader(): (Component & {
|
|
86
|
+
dispose?(): void;
|
|
87
|
+
}) | undefined;
|
|
88
|
+
/**
|
|
89
|
+
* Set up keyboard shortcuts registered by extensions.
|
|
90
|
+
*/
|
|
91
|
+
setupExtensionShortcuts(extensionRunner: ExtensionRunner): void;
|
|
92
|
+
/**
|
|
93
|
+
* Set extension status text in the footer.
|
|
94
|
+
*/
|
|
95
|
+
private setExtensionStatus;
|
|
96
|
+
private static readonly MAX_WIDGET_LINES;
|
|
97
|
+
/**
|
|
98
|
+
* Set an extension widget (string array or custom component).
|
|
99
|
+
*/
|
|
100
|
+
private setExtensionWidget;
|
|
101
|
+
private clearExtensionWidgets;
|
|
102
|
+
resetExtensionUI(): void;
|
|
103
|
+
/**
|
|
104
|
+
* Render all extension widgets to the widget container.
|
|
105
|
+
*/
|
|
106
|
+
renderWidgets(): void;
|
|
107
|
+
private renderWidgetContainer;
|
|
108
|
+
/**
|
|
109
|
+
* Set a custom footer component, or restore the built-in footer.
|
|
110
|
+
*/
|
|
111
|
+
private setExtensionFooter;
|
|
112
|
+
/**
|
|
113
|
+
* Set a custom header component, or restore the built-in header.
|
|
114
|
+
*/
|
|
115
|
+
private setExtensionHeader;
|
|
116
|
+
private addExtensionTerminalInputListener;
|
|
117
|
+
clearExtensionTerminalInputListeners(): void;
|
|
118
|
+
/**
|
|
119
|
+
* Create the ExtensionUIContext for extensions.
|
|
120
|
+
*/
|
|
121
|
+
createExtensionUIContext(): ExtensionUIContext;
|
|
122
|
+
/**
|
|
123
|
+
* Show a selector for extensions.
|
|
124
|
+
*/
|
|
125
|
+
showExtensionSelector(title: string, options: string[], opts?: ExtensionUIDialogOptions): Promise<string | undefined>;
|
|
126
|
+
/**
|
|
127
|
+
* Hide the extension selector.
|
|
128
|
+
*/
|
|
129
|
+
private hideExtensionSelector;
|
|
130
|
+
/**
|
|
131
|
+
* Show a confirmation dialog for extensions.
|
|
132
|
+
*/
|
|
133
|
+
showExtensionConfirm(title: string, message: string, opts?: ExtensionUIDialogOptions): Promise<boolean>;
|
|
134
|
+
/**
|
|
135
|
+
* Show a text input for extensions.
|
|
136
|
+
*/
|
|
137
|
+
private showExtensionInput;
|
|
138
|
+
/**
|
|
139
|
+
* Hide the extension input.
|
|
140
|
+
*/
|
|
141
|
+
private hideExtensionInput;
|
|
142
|
+
/**
|
|
143
|
+
* Show a multi-line editor for extensions (with Ctrl+G support).
|
|
144
|
+
*/
|
|
145
|
+
showExtensionEditor(title: string, prefill?: string): Promise<string | undefined>;
|
|
146
|
+
/**
|
|
147
|
+
* Hide the extension editor.
|
|
148
|
+
*/
|
|
149
|
+
private hideExtensionEditor;
|
|
150
|
+
/**
|
|
151
|
+
* Set a custom editor component from an extension.
|
|
152
|
+
* Pass undefined to restore the default editor.
|
|
153
|
+
*/
|
|
154
|
+
private setCustomEditorComponent;
|
|
155
|
+
/**
|
|
156
|
+
* Show a notification for extensions.
|
|
157
|
+
*/
|
|
158
|
+
private showExtensionNotify;
|
|
159
|
+
private showExtensionCustom;
|
|
160
|
+
/**
|
|
161
|
+
* Show an extension error in the UI.
|
|
162
|
+
*/
|
|
163
|
+
showExtensionError(extensionPath: string, error: string, stack?: string): void;
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=extension-ui-host.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extension-ui-host.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/extension-ui-host.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Extension UI host.\n *\n * Extracted verbatim from interactive-mode.ts (god-file decomposition). Owns the interactive-mode\n * surface of the extension UI API: extension-provided dialogs (selector / confirm / input / multi-line\n * editor / custom component), widget mounting above and below the editor, extension status text, custom\n * footer and header swaps, the custom editor-component swap, keyboard-shortcut wiring, terminal-input\n * listeners, and the `ExtensionUIContext` handed to extensions. It owns exactly the state this cluster\n * mutates (the live dialog components, the widget maps, the terminal-input unsubscribers, the custom\n * footer/header, and the current custom-editor factory) and reaches everything else — the mutable\n * `editor`, the render-core containers, the working-indicator, autocomplete wiring, theme/tools state —\n * through a narrow deps/ui surface rather than the whole InteractiveMode instance. Extensions must see\n * identical behavior, so the render-core operations (working indicator, hidden-thinking label,\n * autocomplete rebuild) stay host-side and are invoked here as delegations.\n */\n\nimport type {\n\tAutocompleteProvider,\n\tComponent,\n\tEditorComponent,\n\tKeyId,\n\tLoaderIndicatorOptions,\n\tOverlayHandle,\n\tOverlayOptions,\n} from \"@caupulican/pi-tui\";\nimport { Container, matchesKey, Spacer, Text, type TUI } from \"@caupulican/pi-tui\";\nimport type { AgentSession } from \"../../core/agent-session.ts\";\nimport type {\n\tAutocompleteProviderFactory,\n\tEditorFactory,\n\tExtensionContext,\n\tExtensionRunner,\n\tExtensionUIContext,\n\tExtensionUIDialogOptions,\n\tExtensionWidgetOptions,\n} from \"../../core/extensions/index.ts\";\nimport type { FooterDataProvider, ReadonlyFooterDataProvider } from \"../../core/footer-data-provider.ts\";\nimport type { KeybindingsManager } from \"../../core/keybindings.ts\";\nimport type { CustomEditor } from \"./components/custom-editor.ts\";\nimport { ExtensionEditorComponent } from \"./components/extension-editor.ts\";\nimport { ExtensionInputComponent } from \"./components/extension-input.ts\";\nimport { ExtensionSelectorComponent } from \"./components/extension-selector.ts\";\nimport type { FooterComponent } from \"./components/footer.ts\";\nimport type { EditorOverlayHost } from \"./editor-overlay-host.ts\";\nimport {\n\tgetAvailableThemesWithPaths,\n\tgetEditorTheme,\n\tgetThemeByName,\n\tsetTheme,\n\tsetThemeInstance,\n\tTheme,\n\ttheme,\n} from \"./theme/theme.ts\";\n\n/** Components that can be expanded/collapsed (custom headers may opt in). */\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\n/**\n * The narrow slice of InteractiveMode the extension UI host drives. Stable containers and the TUI are\n * passed directly; mutable render-core state (the active editor, built-in header, loading animation,\n * autocomplete wrappers, tools-expansion) is read/written through accessors so InteractiveMode keeps\n * ownership; and render-core / status operations stay host-side and are exposed here as callbacks.\n */\nexport interface ExtensionUiHostUi {\n\treadonly tui: TUI;\n\treadonly overlayHost: EditorOverlayHost;\n\treadonly keybindings: KeybindingsManager;\n\treadonly footer: FooterComponent;\n\treadonly footerDataProvider: FooterDataProvider;\n\treadonly headerContainer: Container;\n\treadonly chatContainer: Container;\n\treadonly editorContainer: Container;\n\treadonly defaultEditor: CustomEditor;\n\treadonly widgetContainerAbove: Container;\n\treadonly widgetContainerBelow: Container;\n\n\tgetEditor(): EditorComponent;\n\tsetEditor(editor: EditorComponent): void;\n\tgetBuiltInHeader(): Component | undefined;\n\tgetAutocompleteProvider(): AutocompleteProvider | undefined;\n\tgetToolsExpanded(): boolean;\n\tpushAutocompleteProviderWrapper(factory: AutocompleteProviderFactory): void;\n\tresetAutocompleteProviderWrappers(): void;\n\n\tsetupAutocompleteProvider(): void;\n\tsetWorkingVisible(visible: boolean): void;\n\tsetWorkingIndicator(options?: LoaderIndicatorOptions): void;\n\tsetHiddenThinkingLabel(label?: string): void;\n\tsetWorkingMessage(message: string | undefined): void;\n\tresetWorkingIndicators(): void;\n\tsetToolsExpanded(expanded: boolean): void;\n\ttoggleToolsExpanded(): void;\n\tupdateTerminalTitle(): void;\n\tmarkShutdownRequested(): void;\n\tabort(): void;\n\treload(): Promise<void>;\n\tshowStatus(message: string): void;\n\tshowWarning(message: string): void;\n\tshowError(message: string): void;\n}\n\nexport interface ExtensionUiHostDeps {\n\tgetSession(): AgentSession;\n\tui: ExtensionUiHostUi;\n}\n\nexport class ExtensionUiHost {\n\tprivate readonly deps: ExtensionUiHostDeps;\n\n\t// Extension UI state\n\tprivate extensionSelector: ExtensionSelectorComponent | undefined = undefined;\n\tprivate extensionInput: ExtensionInputComponent | undefined = undefined;\n\tprivate extensionEditor: ExtensionEditorComponent | undefined = undefined;\n\tprivate extensionTerminalInputUnsubscribers = new Set<() => void>();\n\n\t// Extension widgets (components rendered above/below the editor)\n\tprivate extensionWidgetsAbove = new Map<string, Component & { dispose?(): void }>();\n\tprivate extensionWidgetsBelow = new Map<string, Component & { dispose?(): void }>();\n\n\t// Custom footer from extension (undefined = use built-in footer)\n\tprivate customFooter: (Component & { dispose?(): void }) | undefined = undefined;\n\n\t// Custom header from extension (undefined = use built-in header)\n\tprivate customHeader: (Component & { dispose?(): void }) | undefined = undefined;\n\n\t// Current custom editor-component factory (undefined = default editor)\n\tprivate editorComponentFactory: EditorFactory | undefined;\n\n\tconstructor(deps: ExtensionUiHostDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\tprivate get session(): AgentSession {\n\t\treturn this.deps.getSession();\n\t}\n\tprivate get ui(): ExtensionUiHostUi {\n\t\treturn this.deps.ui;\n\t}\n\n\t/** The active custom header, if an extension has installed one. */\n\tgetCustomHeader(): (Component & { dispose?(): void }) | undefined {\n\t\treturn this.customHeader;\n\t}\n\n\t/**\n\t * Set up keyboard shortcuts registered by extensions.\n\t */\n\tsetupExtensionShortcuts(extensionRunner: ExtensionRunner): void {\n\t\tconst shortcuts = extensionRunner.getShortcuts(this.ui.keybindings.getEffectiveConfig());\n\t\tif (shortcuts.size === 0) return;\n\n\t\t// Create a context for shortcut handlers\n\t\tconst createContext = (): ExtensionContext => ({\n\t\t\tui: this.createExtensionUIContext(),\n\t\t\thasUI: true,\n\t\t\tmode: \"tui\",\n\t\t\tcwd: this.session.sessionManager.getCwd(),\n\t\t\tsessionManager: this.session.sessionManager,\n\t\t\tmodelRegistry: this.session.modelRegistry,\n\t\t\tmodel: this.session.model,\n\t\t\tisIdle: () => !this.session.isStreaming,\n\t\t\tsignal: this.session.agent.signal,\n\t\t\tabort: () => {\n\t\t\t\tthis.ui.abort();\n\t\t\t},\n\t\t\thasPendingMessages: () => this.session.pendingMessageCount > 0,\n\t\t\tshutdown: () => {\n\t\t\t\tthis.ui.markShutdownRequested();\n\t\t\t},\n\t\t\tgetContextUsage: () => this.session.getContextUsage(),\n\t\t\tcompact: (options) => {\n\t\t\t\tvoid (async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst result = await this.session.compact(options?.customInstructions);\n\t\t\t\t\t\toptions?.onComplete?.(result);\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tconst err = error instanceof Error ? error : new Error(String(error));\n\t\t\t\t\t\toptions?.onError?.(err);\n\t\t\t\t\t}\n\t\t\t\t})();\n\t\t\t},\n\t\t\treload: async () => {\n\t\t\t\tawait this.ui.reload();\n\t\t\t},\n\t\t\tgetSystemPrompt: () => this.session.systemPrompt,\n\t\t});\n\n\t\t// Set up the extension shortcut handler on the default editor\n\t\tthis.ui.defaultEditor.onExtensionShortcut = (data: string) => {\n\t\t\tfor (const [shortcutStr, shortcut] of shortcuts) {\n\t\t\t\t// Cast to KeyId - extension shortcuts use the same format\n\t\t\t\tif (matchesKey(data, shortcutStr as KeyId)) {\n\t\t\t\t\t// Run handler async, don't block input\n\t\t\t\t\tPromise.resolve(shortcut.handler(createContext())).catch((err) => {\n\t\t\t\t\t\tthis.ui.showError(`Shortcut handler error: ${err instanceof Error ? err.message : String(err)}`);\n\t\t\t\t\t});\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn false;\n\t\t};\n\t}\n\n\t/**\n\t * Set extension status text in the footer.\n\t */\n\tprivate setExtensionStatus(key: string, text: string | undefined): void {\n\t\tthis.ui.footerDataProvider.setExtensionStatus(key, text);\n\t\tthis.ui.tui.requestRender();\n\t}\n\n\t// Maximum total widget lines to prevent viewport overflow\n\tprivate static readonly MAX_WIDGET_LINES = 10;\n\n\t/**\n\t * Set an extension widget (string array or custom component).\n\t */\n\tprivate setExtensionWidget(\n\t\tkey: string,\n\t\tcontent: string[] | ((tui: TUI, thm: Theme) => Component & { dispose?(): void }) | undefined,\n\t\toptions?: ExtensionWidgetOptions,\n\t): void {\n\t\tconst placement = options?.placement ?? \"aboveEditor\";\n\t\tconst removeExisting = (map: Map<string, Component & { dispose?(): void }>) => {\n\t\t\tconst existing = map.get(key);\n\t\t\tif (existing?.dispose) existing.dispose();\n\t\t\tmap.delete(key);\n\t\t};\n\n\t\tremoveExisting(this.extensionWidgetsAbove);\n\t\tremoveExisting(this.extensionWidgetsBelow);\n\n\t\tif (content === undefined) {\n\t\t\tthis.renderWidgets();\n\t\t\treturn;\n\t\t}\n\n\t\tlet component: Component & { dispose?(): void };\n\n\t\tif (Array.isArray(content)) {\n\t\t\t// Wrap string array in a Container with Text components\n\t\t\tconst container = new Container();\n\t\t\tfor (const line of content.slice(0, ExtensionUiHost.MAX_WIDGET_LINES)) {\n\t\t\t\tcontainer.addChild(new Text(line, 1, 0));\n\t\t\t}\n\t\t\tif (content.length > ExtensionUiHost.MAX_WIDGET_LINES) {\n\t\t\t\tcontainer.addChild(new Text(theme.fg(\"muted\", \"... (widget truncated)\"), 1, 0));\n\t\t\t}\n\t\t\tcomponent = container;\n\t\t} else {\n\t\t\t// Factory function - create component\n\t\t\tcomponent = content(this.ui.tui, theme);\n\t\t}\n\n\t\tconst targetMap = placement === \"belowEditor\" ? this.extensionWidgetsBelow : this.extensionWidgetsAbove;\n\t\ttargetMap.set(key, component);\n\t\tthis.renderWidgets();\n\t}\n\n\tprivate clearExtensionWidgets(): void {\n\t\tfor (const widget of this.extensionWidgetsAbove.values()) {\n\t\t\twidget.dispose?.();\n\t\t}\n\t\tfor (const widget of this.extensionWidgetsBelow.values()) {\n\t\t\twidget.dispose?.();\n\t\t}\n\t\tthis.extensionWidgetsAbove.clear();\n\t\tthis.extensionWidgetsBelow.clear();\n\t\tthis.renderWidgets();\n\t}\n\n\tresetExtensionUI(): void {\n\t\tif (this.extensionSelector) {\n\t\t\tthis.hideExtensionSelector();\n\t\t}\n\t\tif (this.extensionInput) {\n\t\t\tthis.hideExtensionInput();\n\t\t}\n\t\tif (this.extensionEditor) {\n\t\t\tthis.hideExtensionEditor();\n\t\t}\n\t\tthis.ui.tui.hideOverlay();\n\t\tthis.clearExtensionTerminalInputListeners();\n\t\tthis.setExtensionFooter(undefined);\n\t\tthis.setExtensionHeader(undefined);\n\t\tthis.clearExtensionWidgets();\n\t\tthis.ui.footerDataProvider.clearExtensionStatuses();\n\t\tthis.ui.footer.invalidate();\n\t\tthis.ui.resetAutocompleteProviderWrappers();\n\t\tthis.setCustomEditorComponent(undefined);\n\t\tthis.ui.setupAutocompleteProvider();\n\t\tthis.ui.defaultEditor.onExtensionShortcut = undefined;\n\t\tthis.ui.updateTerminalTitle();\n\t\tthis.ui.resetWorkingIndicators();\n\t}\n\n\t/**\n\t * Render all extension widgets to the widget container.\n\t */\n\trenderWidgets(): void {\n\t\tif (!this.ui.widgetContainerAbove || !this.ui.widgetContainerBelow) return;\n\t\tthis.renderWidgetContainer(this.ui.widgetContainerAbove, this.extensionWidgetsAbove, true, true);\n\t\tthis.renderWidgetContainer(this.ui.widgetContainerBelow, this.extensionWidgetsBelow, false, false);\n\t\tthis.ui.tui.requestRender();\n\t}\n\n\tprivate renderWidgetContainer(\n\t\tcontainer: Container,\n\t\twidgets: Map<string, Component & { dispose?(): void }>,\n\t\tspacerWhenEmpty: boolean,\n\t\tleadingSpacer: boolean,\n\t): void {\n\t\tcontainer.clear();\n\n\t\tif (widgets.size === 0) {\n\t\t\tif (spacerWhenEmpty) {\n\t\t\t\tcontainer.addChild(new Spacer(1));\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (leadingSpacer) {\n\t\t\tcontainer.addChild(new Spacer(1));\n\t\t}\n\t\tfor (const component of widgets.values()) {\n\t\t\tcontainer.addChild(component);\n\t\t}\n\t}\n\n\t/**\n\t * Set a custom footer component, or restore the built-in footer.\n\t */\n\tprivate setExtensionFooter(\n\t\tfactory:\n\t\t\t| ((tui: TUI, thm: Theme, footerData: ReadonlyFooterDataProvider) => Component & { dispose?(): void })\n\t\t\t| undefined,\n\t): void {\n\t\t// Dispose existing custom footer\n\t\tif (this.customFooter?.dispose) {\n\t\t\tthis.customFooter.dispose();\n\t\t}\n\n\t\t// Remove current footer from UI\n\t\tif (this.customFooter) {\n\t\t\tthis.ui.tui.removeChild(this.customFooter);\n\t\t} else {\n\t\t\tthis.ui.tui.removeChild(this.ui.footer);\n\t\t}\n\n\t\tif (factory) {\n\t\t\t// Create and add custom footer, passing the data provider\n\t\t\tthis.customFooter = factory(this.ui.tui, theme, this.ui.footerDataProvider);\n\t\t\tthis.ui.tui.addChild(this.customFooter);\n\t\t} else {\n\t\t\t// Restore built-in footer\n\t\t\tthis.customFooter = undefined;\n\t\t\tthis.ui.tui.addChild(this.ui.footer);\n\t\t}\n\n\t\tthis.ui.tui.requestRender();\n\t}\n\n\t/**\n\t * Set a custom header component, or restore the built-in header.\n\t */\n\tprivate setExtensionHeader(factory: ((tui: TUI, thm: Theme) => Component & { dispose?(): void }) | undefined): void {\n\t\t// Header may not be initialized yet if called during early initialization\n\t\tconst builtInHeader = this.ui.getBuiltInHeader();\n\t\tif (!builtInHeader) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Dispose existing custom header\n\t\tif (this.customHeader?.dispose) {\n\t\t\tthis.customHeader.dispose();\n\t\t}\n\n\t\t// Find the index of the current header in the header container\n\t\tconst currentHeader = this.customHeader || builtInHeader;\n\t\tconst index = this.ui.headerContainer.children.indexOf(currentHeader);\n\n\t\tif (factory) {\n\t\t\t// Create and add custom header\n\t\t\tthis.customHeader = factory(this.ui.tui, theme);\n\t\t\tif (isExpandable(this.customHeader)) {\n\t\t\t\tthis.customHeader.setExpanded(this.ui.getToolsExpanded());\n\t\t\t}\n\t\t\tif (index !== -1) {\n\t\t\t\tthis.ui.headerContainer.children[index] = this.customHeader;\n\t\t\t} else {\n\t\t\t\t// If not found (e.g. builtInHeader was never added), add at the top\n\t\t\t\tthis.ui.headerContainer.children.unshift(this.customHeader);\n\t\t\t}\n\t\t} else {\n\t\t\t// Restore built-in header\n\t\t\tthis.customHeader = undefined;\n\t\t\tif (isExpandable(builtInHeader)) {\n\t\t\t\tbuiltInHeader.setExpanded(this.ui.getToolsExpanded());\n\t\t\t}\n\t\t\tif (index !== -1) {\n\t\t\t\tthis.ui.headerContainer.children[index] = builtInHeader;\n\t\t\t}\n\t\t}\n\n\t\tthis.ui.tui.requestRender();\n\t}\n\n\tprivate addExtensionTerminalInputListener(\n\t\thandler: (data: string) => { consume?: boolean; data?: string } | undefined,\n\t): () => void {\n\t\tconst unsubscribe = this.ui.tui.addInputListener(handler);\n\t\tthis.extensionTerminalInputUnsubscribers.add(unsubscribe);\n\t\treturn () => {\n\t\t\tunsubscribe();\n\t\t\tthis.extensionTerminalInputUnsubscribers.delete(unsubscribe);\n\t\t};\n\t}\n\n\tclearExtensionTerminalInputListeners(): void {\n\t\tfor (const unsubscribe of this.extensionTerminalInputUnsubscribers) {\n\t\t\tunsubscribe();\n\t\t}\n\t\tthis.extensionTerminalInputUnsubscribers.clear();\n\t}\n\n\t/**\n\t * Create the ExtensionUIContext for extensions.\n\t */\n\tcreateExtensionUIContext(): ExtensionUIContext {\n\t\treturn {\n\t\t\tselect: (title, options, opts) => this.showExtensionSelector(title, options, opts),\n\t\t\tconfirm: (title, message, opts) => this.showExtensionConfirm(title, message, opts),\n\t\t\tinput: (title, placeholder, opts) => this.showExtensionInput(title, placeholder, opts),\n\t\t\tnotify: (message, type) => this.showExtensionNotify(message, type),\n\t\t\tonTerminalInput: (handler) => this.addExtensionTerminalInputListener(handler),\n\t\t\tsetStatus: (key, text) => this.setExtensionStatus(key, text),\n\t\t\tsetWorkingMessage: (message) => this.ui.setWorkingMessage(message),\n\t\t\tsetWorkingVisible: (visible) => this.ui.setWorkingVisible(visible),\n\t\t\tsetWorkingIndicator: (options) => this.ui.setWorkingIndicator(options),\n\t\t\tsetHiddenThinkingLabel: (label) => this.ui.setHiddenThinkingLabel(label),\n\t\t\tsetWidget: (key, content, options) => this.setExtensionWidget(key, content, options),\n\t\t\tsetFooter: (factory) => this.setExtensionFooter(factory),\n\t\t\tsetHeader: (factory) => this.setExtensionHeader(factory),\n\t\t\tsetTitle: (title) => this.ui.tui.terminal.setTitle(title),\n\t\t\tcustom: (factory, options) => this.showExtensionCustom(factory, options),\n\t\t\tpasteToEditor: (text) => this.ui.getEditor().handleInput(`\\x1b[200~${text}\\x1b[201~`),\n\t\t\tsetEditorText: (text) => this.ui.getEditor().setText(text),\n\t\t\tgetEditorText: () => this.ui.getEditor().getExpandedText?.() ?? this.ui.getEditor().getText(),\n\t\t\teditor: (title, prefill) => this.showExtensionEditor(title, prefill),\n\t\t\taddAutocompleteProvider: (factory) => {\n\t\t\t\tthis.ui.pushAutocompleteProviderWrapper(factory);\n\t\t\t\tthis.ui.setupAutocompleteProvider();\n\t\t\t},\n\t\t\tsetEditorComponent: (factory) => this.setCustomEditorComponent(factory),\n\t\t\tgetEditorComponent: () => this.editorComponentFactory,\n\t\t\tget theme() {\n\t\t\t\treturn theme;\n\t\t\t},\n\t\t\tgetAllThemes: () => getAvailableThemesWithPaths(),\n\t\t\tgetTheme: (name) => getThemeByName(name),\n\t\t\tsetTheme: (themeOrName) => {\n\t\t\t\tif (themeOrName instanceof Theme) {\n\t\t\t\t\tsetThemeInstance(themeOrName);\n\t\t\t\t\tthis.ui.tui.requestRender();\n\t\t\t\t\treturn { success: true };\n\t\t\t\t}\n\t\t\t\tconst result = setTheme(themeOrName, true);\n\t\t\t\tif (result.success) {\n\t\t\t\t\tif (this.session.settingsManager.getTheme() !== themeOrName) {\n\t\t\t\t\t\tthis.session.settingsManager.setTheme(themeOrName);\n\t\t\t\t\t}\n\t\t\t\t\tthis.ui.tui.requestRender();\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t\t},\n\t\t\tgetToolsExpanded: () => this.ui.getToolsExpanded(),\n\t\t\tsetToolsExpanded: (expanded) => this.ui.setToolsExpanded(expanded),\n\t\t};\n\t}\n\n\t/**\n\t * Show a selector for extensions.\n\t */\n\tshowExtensionSelector(\n\t\ttitle: string,\n\t\toptions: string[],\n\t\topts?: ExtensionUIDialogOptions,\n\t): Promise<string | undefined> {\n\t\treturn new Promise((resolve) => {\n\t\t\tif (opts?.signal?.aborted) {\n\t\t\t\tresolve(undefined);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst onAbort = () => {\n\t\t\t\tthis.hideExtensionSelector();\n\t\t\t\tresolve(undefined);\n\t\t\t};\n\t\t\topts?.signal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\t\t\tthis.extensionSelector = new ExtensionSelectorComponent(\n\t\t\t\ttitle,\n\t\t\t\toptions,\n\t\t\t\t(option) => {\n\t\t\t\t\topts?.signal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\tthis.hideExtensionSelector();\n\t\t\t\t\tresolve(option);\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\topts?.signal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\tthis.hideExtensionSelector();\n\t\t\t\t\tresolve(undefined);\n\t\t\t\t},\n\t\t\t\t{ tui: this.ui.tui, timeout: opts?.timeout, onToggleToolsExpanded: () => this.ui.toggleToolsExpanded() },\n\t\t\t);\n\n\t\t\tthis.ui.overlayHost.swap(this.extensionSelector);\n\t\t});\n\t}\n\n\t/**\n\t * Hide the extension selector.\n\t */\n\tprivate hideExtensionSelector(): void {\n\t\tthis.extensionSelector?.dispose();\n\t\tthis.extensionSelector = undefined;\n\t\tthis.ui.overlayHost.swap(this.ui.getEditor());\n\t}\n\n\t/**\n\t * Show a confirmation dialog for extensions.\n\t */\n\tasync showExtensionConfirm(title: string, message: string, opts?: ExtensionUIDialogOptions): Promise<boolean> {\n\t\tconst result = await this.showExtensionSelector(`${title}\\n${message}`, [\"Yes\", \"No\"], opts);\n\t\treturn result === \"Yes\";\n\t}\n\n\t/**\n\t * Show a text input for extensions.\n\t */\n\tprivate showExtensionInput(\n\t\ttitle: string,\n\t\tplaceholder?: string,\n\t\topts?: ExtensionUIDialogOptions,\n\t): Promise<string | undefined> {\n\t\treturn new Promise((resolve) => {\n\t\t\tif (opts?.signal?.aborted) {\n\t\t\t\tresolve(undefined);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst onAbort = () => {\n\t\t\t\tthis.hideExtensionInput();\n\t\t\t\tresolve(undefined);\n\t\t\t};\n\t\t\topts?.signal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\t\t\tthis.extensionInput = new ExtensionInputComponent(\n\t\t\t\ttitle,\n\t\t\t\tplaceholder,\n\t\t\t\t(value) => {\n\t\t\t\t\topts?.signal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\tthis.hideExtensionInput();\n\t\t\t\t\tresolve(value);\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\topts?.signal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\tthis.hideExtensionInput();\n\t\t\t\t\tresolve(undefined);\n\t\t\t\t},\n\t\t\t\t{ tui: this.ui.tui, timeout: opts?.timeout },\n\t\t\t);\n\n\t\t\tthis.ui.overlayHost.swap(this.extensionInput);\n\t\t});\n\t}\n\n\t/**\n\t * Hide the extension input.\n\t */\n\tprivate hideExtensionInput(): void {\n\t\tthis.extensionInput?.dispose();\n\t\tthis.extensionInput = undefined;\n\t\tthis.ui.overlayHost.swap(this.ui.getEditor());\n\t}\n\n\t/**\n\t * Show a multi-line editor for extensions (with Ctrl+G support).\n\t */\n\tshowExtensionEditor(title: string, prefill?: string): Promise<string | undefined> {\n\t\treturn new Promise((resolve) => {\n\t\t\tthis.extensionEditor = new ExtensionEditorComponent(\n\t\t\t\tthis.ui.tui,\n\t\t\t\tthis.ui.keybindings,\n\t\t\t\ttitle,\n\t\t\t\tprefill,\n\t\t\t\t(value) => {\n\t\t\t\t\tthis.hideExtensionEditor();\n\t\t\t\t\tresolve(value);\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\tthis.hideExtensionEditor();\n\t\t\t\t\tresolve(undefined);\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tthis.ui.overlayHost.swap(this.extensionEditor);\n\t\t});\n\t}\n\n\t/**\n\t * Hide the extension editor.\n\t */\n\tprivate hideExtensionEditor(): void {\n\t\tthis.extensionEditor = undefined;\n\t\tthis.ui.overlayHost.swap(this.ui.getEditor());\n\t}\n\n\t/**\n\t * Set a custom editor component from an extension.\n\t * Pass undefined to restore the default editor.\n\t */\n\tprivate setCustomEditorComponent(factory: EditorFactory | undefined): void {\n\t\tthis.editorComponentFactory = factory;\n\n\t\t// Save text from current editor before switching\n\t\tconst currentText = this.ui.getEditor().getText();\n\n\t\tthis.ui.editorContainer.clear();\n\n\t\tif (factory) {\n\t\t\t// Create the custom editor with tui, theme, and keybindings\n\t\t\tconst newEditor = factory(this.ui.tui, getEditorTheme(), this.ui.keybindings);\n\n\t\t\t// Wire up callbacks from the default editor\n\t\t\tnewEditor.onSubmit = this.ui.defaultEditor.onSubmit;\n\t\t\tnewEditor.onChange = this.ui.defaultEditor.onChange;\n\n\t\t\t// Copy text from previous editor\n\t\t\tnewEditor.setText(currentText);\n\n\t\t\t// Copy appearance settings if supported\n\t\t\tif (newEditor.borderColor !== undefined) {\n\t\t\t\tnewEditor.borderColor = this.ui.defaultEditor.borderColor;\n\t\t\t}\n\t\t\tif (newEditor.setPaddingX !== undefined) {\n\t\t\t\tnewEditor.setPaddingX(this.ui.defaultEditor.getPaddingX());\n\t\t\t}\n\n\t\t\t// Set autocomplete if supported\n\t\t\tconst autocompleteProvider = this.ui.getAutocompleteProvider();\n\t\t\tif (newEditor.setAutocompleteProvider && autocompleteProvider) {\n\t\t\t\tnewEditor.setAutocompleteProvider(autocompleteProvider);\n\t\t\t}\n\n\t\t\t// If extending CustomEditor, copy app-level handlers\n\t\t\t// Use duck typing since instanceof fails across jiti module boundaries\n\t\t\tconst customEditor = newEditor as unknown as Record<string, unknown>;\n\t\t\tif (\"actionHandlers\" in customEditor && customEditor.actionHandlers instanceof Map) {\n\t\t\t\tif (!customEditor.onEscape) {\n\t\t\t\t\tcustomEditor.onEscape = () => this.ui.defaultEditor.onEscape?.();\n\t\t\t\t}\n\t\t\t\tif (!customEditor.onCtrlD) {\n\t\t\t\t\tcustomEditor.onCtrlD = () => this.ui.defaultEditor.onCtrlD?.();\n\t\t\t\t}\n\t\t\t\tif (!customEditor.onPasteImage) {\n\t\t\t\t\tcustomEditor.onPasteImage = () => this.ui.defaultEditor.onPasteImage?.();\n\t\t\t\t}\n\t\t\t\tif (!customEditor.onExtensionShortcut) {\n\t\t\t\t\tcustomEditor.onExtensionShortcut = (data: string) => this.ui.defaultEditor.onExtensionShortcut?.(data);\n\t\t\t\t}\n\t\t\t\t// Copy action handlers (clear, suspend, model switching, etc.)\n\t\t\t\tfor (const [action, handler] of this.ui.defaultEditor.actionHandlers) {\n\t\t\t\t\t(customEditor.actionHandlers as Map<string, () => void>).set(action, handler);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.ui.setEditor(newEditor);\n\t\t} else {\n\t\t\t// Restore default editor with text from custom editor\n\t\t\tthis.ui.defaultEditor.setText(currentText);\n\t\t\tthis.ui.setEditor(this.ui.defaultEditor);\n\t\t}\n\n\t\tthis.ui.editorContainer.addChild(this.ui.getEditor() as Component);\n\t\tthis.ui.tui.setFocus(this.ui.getEditor() as Component);\n\t\tthis.ui.tui.requestRender();\n\t}\n\n\t/**\n\t * Show a notification for extensions.\n\t */\n\tprivate showExtensionNotify(message: string, type?: \"info\" | \"warning\" | \"error\"): void {\n\t\tif (type === \"error\") {\n\t\t\tthis.ui.showError(message);\n\t\t} else if (type === \"warning\") {\n\t\t\tthis.ui.showWarning(message);\n\t\t} else {\n\t\t\tthis.ui.showStatus(message);\n\t\t}\n\t}\n\n\t/** Show a custom component with keyboard focus. Overlay mode renders on top of existing content. */\n\tprivate async showExtensionCustom<T>(\n\t\tfactory: (\n\t\t\ttui: TUI,\n\t\t\ttheme: Theme,\n\t\t\tkeybindings: KeybindingsManager,\n\t\t\tdone: (result: T) => void,\n\t\t) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,\n\t\toptions?: {\n\t\t\toverlay?: boolean;\n\t\t\toverlayOptions?: OverlayOptions | (() => OverlayOptions);\n\t\t\tonHandle?: (handle: OverlayHandle) => void;\n\t\t},\n\t): Promise<T> {\n\t\tconst savedText = this.ui.getEditor().getText();\n\t\tconst isOverlay = options?.overlay ?? false;\n\n\t\tconst restoreEditor = () => {\n\t\t\tthis.ui.getEditor().setText(savedText);\n\t\t\tthis.ui.overlayHost.swap(this.ui.getEditor(), { focusMode: \"restore\" });\n\t\t};\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tlet component: Component & { dispose?(): void };\n\t\t\tlet closed = false;\n\n\t\t\tconst close = (result: T) => {\n\t\t\t\tif (closed) return;\n\t\t\t\tclosed = true;\n\t\t\t\tif (isOverlay) this.ui.tui.hideOverlay();\n\t\t\t\telse restoreEditor();\n\t\t\t\t// Note: both branches above already call requestRender\n\t\t\t\tresolve(result);\n\t\t\t\ttry {\n\t\t\t\t\tcomponent?.dispose?.();\n\t\t\t\t} catch {\n\t\t\t\t\t/* ignore dispose errors */\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tPromise.resolve(factory(this.ui.tui, theme, this.ui.keybindings, close))\n\t\t\t\t.then((c) => {\n\t\t\t\t\tif (closed) return;\n\t\t\t\t\tcomponent = c;\n\t\t\t\t\tif (isOverlay) {\n\t\t\t\t\t\t// Resolve overlay options - can be static or dynamic function\n\t\t\t\t\t\tconst resolveOptions = (): OverlayOptions | undefined => {\n\t\t\t\t\t\t\tif (options?.overlayOptions) {\n\t\t\t\t\t\t\t\tconst opts =\n\t\t\t\t\t\t\t\t\ttypeof options.overlayOptions === \"function\"\n\t\t\t\t\t\t\t\t\t\t? options.overlayOptions()\n\t\t\t\t\t\t\t\t\t\t: options.overlayOptions;\n\t\t\t\t\t\t\t\treturn opts;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// Fallback: use component's width property if available\n\t\t\t\t\t\t\tconst w = (component as { width?: number }).width;\n\t\t\t\t\t\t\treturn w ? { width: w } : undefined;\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst handle = this.ui.tui.showOverlay(component, resolveOptions());\n\t\t\t\t\t\t// Expose handle to caller for visibility control\n\t\t\t\t\t\toptions?.onHandle?.(handle);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.ui.overlayHost.swap(component);\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.catch((err) => {\n\t\t\t\t\tif (closed) return;\n\t\t\t\t\tif (!isOverlay) restoreEditor();\n\t\t\t\t\treject(err);\n\t\t\t\t});\n\t\t});\n\t}\n\n\t/**\n\t * Show an extension error in the UI.\n\t */\n\tshowExtensionError(extensionPath: string, error: string, stack?: string): void {\n\t\tconst errorMsg = `Extension \"${extensionPath}\" error: ${error}`;\n\t\tconst errorText = new Text(theme.fg(\"error\", errorMsg), 1, 0);\n\t\tthis.ui.chatContainer.addChild(errorText);\n\t\tif (stack) {\n\t\t\t// Show stack trace in dim color, indented\n\t\t\tconst stackLines = stack\n\t\t\t\t.split(\"\\n\")\n\t\t\t\t.slice(1) // Skip first line (duplicates error message)\n\t\t\t\t.map((line) => theme.fg(\"dim\", ` ${line.trim()}`))\n\t\t\t\t.join(\"\\n\");\n\t\t\tif (stackLines) {\n\t\t\t\tthis.ui.chatContainer.addChild(new Text(stackLines, 1, 0));\n\t\t\t}\n\t\t}\n\t\tthis.ui.tui.requestRender();\n\t}\n}\n"]}
|