@gajae-code/coding-agent 0.11.1 → 0.11.2
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 +54 -0
- package/dist/types/cli/config-cli.d.ts +11 -1
- package/dist/types/cli/notify-cli.d.ts +1 -0
- package/dist/types/cli/session-picker.d.ts +2 -1
- package/dist/types/cli/setup-cli.d.ts +1 -1
- package/dist/types/commands/mcp-serve.d.ts +33 -0
- package/dist/types/commands/mcp.d.ts +1 -1
- package/dist/types/commands/notify.d.ts +0 -2
- package/dist/types/commands/sdk.d.ts +1 -1
- package/dist/types/config/atomic-yaml-patch.d.ts +18 -0
- package/dist/types/config/file-lock.d.ts +14 -4
- package/dist/types/config/keybindings.d.ts +42 -1
- package/dist/types/config/model-auth.d.ts +3 -0
- package/dist/types/config/model-bindings-applier.d.ts +13 -0
- package/dist/types/config/model-discovery-manager.d.ts +49 -0
- package/dist/types/config/model-equivalence.d.ts +20 -0
- package/dist/types/config/model-registry.d.ts +16 -15
- package/dist/types/config/model-resolver.d.ts +28 -18
- package/dist/types/config/models-config-schema.d.ts +11 -0
- package/dist/types/config/settings-schema.d.ts +56 -1
- package/dist/types/config/settings.d.ts +14 -11
- package/dist/types/coordinator-mcp/policy.d.ts +5 -0
- package/dist/types/coordinator-mcp/question-gate-codec.d.ts +129 -0
- package/dist/types/coordinator-mcp/question-state.d.ts +356 -0
- package/dist/types/coordinator-mcp/server.d.ts +4 -0
- package/dist/types/discovery/helpers.d.ts +0 -6
- package/dist/types/edit/index.d.ts +3 -0
- package/dist/types/eval/py/env.d.ts +9 -0
- package/dist/types/extensibility/extensions/loader.d.ts +2 -1
- package/dist/types/extensibility/extensions/runner.d.ts +2 -2
- package/dist/types/extensibility/extensions/types.d.ts +34 -3
- package/dist/types/extensibility/hooks/runner.d.ts +1 -1
- package/dist/types/extensibility/shared-events.d.ts +2 -0
- package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +10 -1
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +44 -0
- package/dist/types/gjc-runtime/goal-mode-request.d.ts +11 -1
- package/dist/types/gjc-runtime/launch-worktree.d.ts +4 -2
- package/dist/types/gjc-runtime/linux-proc.d.ts +29 -0
- package/dist/types/gjc-runtime/session-resolution.d.ts +2 -2
- package/dist/types/gjc-runtime/state-migrations.d.ts +1 -1
- package/dist/types/gjc-runtime/state-writer.d.ts +2 -1
- package/dist/types/gjc-runtime/team-launch.d.ts +59 -0
- package/dist/types/gjc-runtime/team-notify.d.ts +45 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +29 -121
- package/dist/types/gjc-runtime/team-store.d.ts +160 -0
- package/dist/types/gjc-runtime/team-workers.d.ts +78 -0
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +8 -0
- package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +13 -0
- package/dist/types/gjc-runtime/ultragoal-evidence.d.ts +38 -0
- package/dist/types/gjc-runtime/ultragoal-guard.d.ts +7 -0
- package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +23 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +55 -33
- package/dist/types/gjc-runtime/workflow-cli-common.d.ts +8 -0
- package/dist/types/gjc-runtime/workflow-manifest.d.ts +3 -0
- package/dist/types/goals/continuation-timeout-guard.d.ts +22 -0
- package/dist/types/goals/runtime.d.ts +1 -0
- package/dist/types/goals/state.d.ts +8 -0
- package/dist/types/harness-control-plane/lifecycle-reconciliation.d.ts +17 -0
- package/dist/types/harness-control-plane/phase-rollup.d.ts +4 -7
- package/dist/types/harness-control-plane/receipt-ingest.d.ts +5 -0
- package/dist/types/harness-control-plane/receipt-spool.d.ts +4 -0
- package/dist/types/hindsight/state.d.ts +11 -4
- package/dist/types/lsp/lspmux.d.ts +5 -3
- package/dist/types/lsp/path-trust.d.ts +1 -0
- package/dist/types/main.d.ts +3 -2
- package/dist/types/memories/storage.d.ts +1 -0
- package/dist/types/modes/action-registry.d.ts +29 -0
- package/dist/types/modes/components/chrome.d.ts +14 -0
- package/dist/types/modes/components/command-palette.d.ts +27 -0
- package/dist/types/modes/components/custom-provider-wizard.d.ts +1 -0
- package/dist/types/modes/components/index.d.ts +2 -1
- package/dist/types/modes/components/pet-selector.d.ts +1 -1
- package/dist/types/modes/components/plan-preview-overlay.d.ts +43 -0
- package/dist/types/modes/components/queue-mode-selector.d.ts +1 -1
- package/dist/types/modes/components/queue-pane.d.ts +12 -0
- package/dist/types/modes/components/read-tool-group.d.ts +6 -0
- package/dist/types/modes/components/session-observer-overlay.d.ts +3 -4
- package/dist/types/modes/components/sessions-dashboard.d.ts +32 -0
- package/dist/types/modes/components/settings-selector.d.ts +1 -1
- package/dist/types/modes/components/show-images-selector.d.ts +1 -1
- package/dist/types/modes/components/status-line/types.d.ts +1 -1
- package/dist/types/modes/components/status-line.d.ts +1 -94
- package/dist/types/modes/components/tasks-pane.d.ts +15 -0
- package/dist/types/modes/components/theme-selector.d.ts +1 -1
- package/dist/types/modes/components/tool-execution.d.ts +15 -0
- package/dist/types/modes/components/tool-status-header.d.ts +111 -0
- package/dist/types/modes/components/transcript-viewer-overlay.d.ts +42 -0
- package/dist/types/modes/controllers/goal-mode-controller.d.ts +62 -0
- package/dist/types/modes/controllers/input-controller.d.ts +8 -1
- package/dist/types/modes/controllers/mode-gate.d.ts +8 -0
- package/dist/types/modes/controllers/plan-mode-controller.d.ts +65 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +15 -3
- package/dist/types/modes/controllers/stt-controller.d.ts +26 -0
- package/dist/types/modes/interactive-mode.d.ts +19 -17
- package/dist/types/modes/jobs-observer.d.ts +2 -0
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +4 -11
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +20 -2
- package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +31 -0
- package/dist/types/modes/tasks-aggregator.d.ts +39 -0
- package/dist/types/modes/transcript-item-registry.d.ts +60 -0
- package/dist/types/modes/types.d.ts +20 -13
- package/dist/types/modes/utils/ui-helpers.d.ts +2 -2
- package/dist/types/runtime-mcp/manager.d.ts +1 -1
- package/dist/types/sdk/broker/broker.d.ts +78 -12
- package/dist/types/sdk/broker/lifecycle-codec.d.ts +4 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +15 -1
- package/dist/types/sdk/broker/lifecycle.d.ts +6 -2
- package/dist/types/sdk/broker/process-incarnation.d.ts +2 -0
- package/dist/types/sdk/broker/session-index.d.ts +0 -1
- package/dist/types/sdk/broker/state-version.d.ts +2 -0
- package/dist/types/sdk/bus/chat-effect-journal.d.ts +1 -0
- package/dist/types/sdk/bus/config-commands.d.ts +1 -1
- package/dist/types/sdk/bus/config.d.ts +11 -0
- package/dist/types/sdk/bus/daemon-paths.d.ts +1 -0
- package/dist/types/sdk/bus/html-format.d.ts +3 -2
- package/dist/types/sdk/bus/index.d.ts +27 -0
- package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
- package/dist/types/sdk/bus/notification-service.d.ts +20 -0
- package/dist/types/sdk/bus/rate-limit-pool.d.ts +13 -2
- package/dist/types/sdk/bus/recent-activity.d.ts +21 -12
- package/dist/types/sdk/bus/rich-document.d.ts +7 -0
- package/dist/types/sdk/bus/rich-render.d.ts +18 -41
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +9 -4
- package/dist/types/sdk/bus/telegram-daemon.d.ts +46 -3
- package/dist/types/sdk/bus/threaded-inbound.d.ts +1 -1
- package/dist/types/sdk/bus/threaded-render.d.ts +13 -0
- package/dist/types/sdk/bus/topic-registry.d.ts +24 -2
- package/dist/types/sdk/host/control/operations.d.ts +1 -1
- package/dist/types/sdk/host/host.d.ts +2 -0
- package/dist/types/sdk/index.d.ts +1 -0
- package/dist/types/sdk/session-directory.d.ts +56 -0
- package/dist/types/session/agent-session.d.ts +82 -47
- package/dist/types/session/agent-storage.d.ts +7 -0
- package/dist/types/session/fallback-chain-controller.d.ts +10 -2
- package/dist/types/session/internal/managed-session-scope.d.ts +102 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +38 -0
- package/dist/types/session/messages.d.ts +16 -0
- package/dist/types/session/session-manager-internal.d.ts +14 -0
- package/dist/types/session/session-manager.d.ts +82 -12
- package/dist/types/session/session-storage.d.ts +43 -3
- package/dist/types/session/volatile-context-pruning.d.ts +15 -0
- package/dist/types/skill-state/{deep-interview-mutation-guard.d.ts → workflow-mutation-guard.d.ts} +24 -8
- package/dist/types/skill-state/workflow-state-contract.d.ts +1 -3
- package/dist/types/slash-commands/builtin-registry.d.ts +4 -1
- package/dist/types/system-prompt.d.ts +2 -11
- package/dist/types/task/executor.d.ts +3 -1
- package/dist/types/thinking.d.ts +7 -0
- package/dist/types/tool-discovery/tool-index.d.ts +1 -50
- package/dist/types/tools/ask.d.ts +64 -2
- package/dist/types/tools/bash.d.ts +3 -0
- package/dist/types/tools/browser/dead-tab-recovery.d.ts +24 -0
- package/dist/types/tools/browser/tab-supervisor.d.ts +21 -0
- package/dist/types/tools/cron.d.ts +2 -0
- package/dist/types/tools/fetch.d.ts +1 -0
- package/dist/types/tools/index.d.ts +30 -15
- package/dist/types/tools/output-meta.d.ts +1 -0
- package/dist/types/tools/read.d.ts +3 -0
- package/dist/types/tools/search-tool-bm25.d.ts +3 -4
- package/dist/types/utils/event-bus.d.ts +1 -1
- package/dist/types/web/search/index.d.ts +3 -1
- package/package.json +21 -9
- package/scripts/build-sdk-package-smoke.ts +34 -1
- package/scripts/generate-hotkeys-docs.ts +74 -0
- package/scripts/generate-sdk-operation-inventory.ts +64 -20
- package/scripts/verify-gjc-sdk-canonicalization.ts +182 -2
- package/src/async/job-manager.ts +49 -6
- package/src/cli/config-cli.ts +75 -2
- package/src/cli/notify-cli.ts +92 -26
- package/src/cli/session-picker.ts +20 -3
- package/src/cli/setup-cli.ts +1 -1
- package/src/cli.ts +1 -1
- package/src/commands/config.ts +1 -1
- package/src/commands/harness.ts +34 -106
- package/src/commands/launch.ts +6 -4
- package/src/commands/mcp-serve.ts +85 -8
- package/src/commands/mcp.ts +2 -2
- package/src/commands/notify.ts +8 -4
- package/src/commands/sdk.ts +61 -40
- package/src/commands/ultragoal.ts +4 -1
- package/src/config/atomic-yaml-patch.ts +80 -13
- package/src/config/file-lock.ts +105 -43
- package/src/config/keybindings.ts +64 -20
- package/src/config/model-auth.ts +9 -0
- package/src/config/model-bindings-applier.ts +139 -0
- package/src/config/model-discovery-manager.ts +224 -0
- package/src/config/model-equivalence.ts +66 -0
- package/src/config/model-registry.ts +169 -302
- package/src/config/model-resolver.ts +361 -369
- package/src/config/models-config-schema.ts +5 -4
- package/src/config/settings-schema.ts +159 -2
- package/src/config/settings.ts +458 -482
- package/src/coordinator-mcp/policy.ts +76 -11
- package/src/coordinator-mcp/question-gate-codec.ts +274 -0
- package/src/coordinator-mcp/question-state.ts +778 -0
- package/src/coordinator-mcp/server.ts +1702 -238
- package/src/debug/index.ts +1 -1
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +13 -1
- package/src/defaults/gjc/skills/team/SKILL.md +16 -0
- package/src/discovery/helpers.ts +1 -50
- package/src/edit/index.ts +64 -0
- package/src/eval/py/env.ts +26 -0
- package/src/eval/py/kernel.ts +5 -3
- package/src/extensibility/extensions/loader.ts +9 -1
- package/src/extensibility/extensions/runner.ts +12 -3
- package/src/extensibility/extensions/types.ts +39 -1
- package/src/extensibility/gjc-plugins/installer.ts +16 -16
- package/src/extensibility/hooks/runner.ts +2 -2
- package/src/extensibility/runtime-skill-discovery.ts +33 -13
- package/src/extensibility/shared-events.ts +2 -0
- package/src/gjc-runtime/deep-interview-recorder.ts +63 -3
- package/src/gjc-runtime/deep-interview-runtime.ts +76 -27
- package/src/gjc-runtime/deep-interview-state.ts +317 -5
- package/src/gjc-runtime/goal-mode-request.ts +55 -8
- package/src/gjc-runtime/launch-tmux.ts +2 -5
- package/src/gjc-runtime/launch-worktree.ts +5 -5
- package/src/gjc-runtime/linux-proc.ts +71 -0
- package/src/gjc-runtime/ralplan-runtime.ts +74 -90
- package/src/gjc-runtime/session-resolution.ts +18 -3
- package/src/gjc-runtime/session-state-sidecar.ts +6 -107
- package/src/gjc-runtime/state-migrations.ts +1 -1
- package/src/gjc-runtime/state-runtime.ts +320 -249
- package/src/gjc-runtime/state-writer.ts +36 -40
- package/src/gjc-runtime/team-launch.ts +319 -0
- package/src/gjc-runtime/team-notify.ts +326 -0
- package/src/gjc-runtime/team-runtime.ts +1099 -1482
- package/src/gjc-runtime/team-store.ts +828 -0
- package/src/gjc-runtime/team-workers.ts +962 -0
- package/src/gjc-runtime/tmux-owner-isolation-cli.ts +2 -11
- package/src/gjc-runtime/tmux-owner-isolation.ts +14 -9
- package/src/gjc-runtime/tmux-sessions.ts +4 -21
- package/src/gjc-runtime/ultragoal-change-set.ts +172 -0
- package/src/gjc-runtime/ultragoal-evidence.ts +874 -0
- package/src/gjc-runtime/ultragoal-guard.ts +29 -5
- package/src/gjc-runtime/ultragoal-pipeline.ts +306 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +84 -1311
- package/src/gjc-runtime/workflow-cli-common.ts +30 -0
- package/src/gjc-runtime/workflow-manifest.generated.json +58 -0
- package/src/gjc-runtime/workflow-manifest.ts +18 -0
- package/src/goals/continuation-timeout-guard.ts +79 -0
- package/src/goals/runtime.ts +7 -5
- package/src/goals/state.ts +18 -0
- package/src/harness-control-plane/lifecycle-reconciliation.ts +64 -0
- package/src/harness-control-plane/phase-rollup.ts +4 -7
- package/src/harness-control-plane/receipt-ingest.ts +5 -0
- package/src/harness-control-plane/receipt-spool.ts +51 -7
- package/src/harness-control-plane/session-lease.ts +12 -130
- package/src/hindsight/backend.ts +26 -27
- package/src/hindsight/state.ts +101 -32
- package/src/hooks/skill-state.ts +17 -11
- package/src/internal-urls/docs-index.generated.ts +21 -18
- package/src/lsp/client.ts +1 -1
- package/src/lsp/config.ts +145 -26
- package/src/lsp/index.ts +1 -1
- package/src/lsp/lspmux.ts +39 -10
- package/src/lsp/path-trust.ts +81 -0
- package/src/main.ts +28 -8
- package/src/memories/index.ts +1 -0
- package/src/memories/storage.ts +6 -2
- package/src/modes/action-registry.ts +165 -0
- package/src/modes/components/chrome.ts +40 -0
- package/src/modes/components/command-palette.ts +168 -0
- package/src/modes/components/custom-editor.ts +1 -1
- package/src/modes/components/custom-provider-wizard.ts +51 -14
- package/src/modes/components/index.ts +2 -1
- package/src/modes/components/pet-selector.ts +11 -17
- package/src/modes/components/plan-preview-overlay.ts +295 -0
- package/src/modes/components/queue-mode-selector.ts +10 -27
- package/src/modes/components/queue-pane.ts +79 -0
- package/src/modes/components/read-tool-group.ts +13 -0
- package/src/modes/components/session-observer-overlay.ts +207 -772
- package/src/modes/components/sessions-dashboard.ts +181 -0
- package/src/modes/components/settings-selector.ts +1 -1
- package/src/modes/components/show-images-selector.ts +10 -24
- package/src/modes/components/status-line/types.ts +1 -1
- package/src/modes/components/status-line.ts +1 -969
- package/src/modes/components/tasks-pane.ts +73 -0
- package/src/modes/components/theme-selector.ts +11 -31
- package/src/modes/components/tool-execution.ts +22 -0
- package/src/modes/components/tool-status-header.ts +1056 -0
- package/src/modes/components/transcript-viewer-overlay.ts +384 -0
- package/src/modes/controllers/btw-controller.ts +1 -0
- package/src/modes/controllers/event-controller.ts +3 -2
- package/src/modes/controllers/extension-ui-controller.ts +24 -1
- package/src/modes/controllers/goal-mode-controller.ts +474 -0
- package/src/modes/controllers/input-controller.ts +764 -229
- package/src/modes/controllers/mode-gate.ts +24 -0
- package/src/modes/controllers/plan-mode-controller.ts +517 -0
- package/src/modes/controllers/selector-controller.ts +225 -10
- package/src/modes/controllers/stt-controller.ts +125 -0
- package/src/modes/interactive-mode.ts +289 -1150
- package/src/modes/jobs-observer.ts +3 -0
- package/src/modes/runtime-init.ts +8 -0
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +31 -106
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +71 -16
- package/src/modes/shared/agent-wire/workflow-gate-types.ts +177 -0
- package/src/modes/tasks-aggregator.ts +199 -0
- package/src/modes/transcript-item-registry.ts +229 -0
- package/src/modes/types.ts +45 -18
- package/src/modes/utils/ui-helpers.ts +17 -9
- package/src/prompts/agent-fragments/ralplan-persistence.md +6 -0
- package/src/prompts/agent-fragments/restricted-bash.md +1 -0
- package/src/prompts/agents/architect.md +2 -8
- package/src/prompts/agents/critic.md +2 -8
- package/src/prompts/agents/executor.md +3 -0
- package/src/prompts/agents/planner.md +4 -12
- package/src/prompts/system/custom-system-prompt.md +1 -23
- package/src/prompts/system/plan-mode-approved.md +8 -0
- package/src/prompts/system/system-prompt.md +17 -96
- package/src/prompts/tools/bash.md +4 -4
- package/src/prompts/tools/browser.md +15 -52
- package/src/prompts/tools/find.md +2 -2
- package/src/prompts/tools/hashline.md +13 -55
- package/src/prompts/tools/read.md +3 -6
- package/src/prompts/tools/search-tool-bm25.md +0 -2
- package/src/prompts/tools/search.md +2 -4
- package/src/prompts/tools/skill-discovery.md +1 -2
- package/src/prompts/tools/task.md +1 -1
- package/src/runtime-mcp/manager.ts +1 -1
- package/src/sdk/broker/broker.ts +206 -78
- package/src/sdk/broker/discovery.ts +1 -1
- package/src/sdk/broker/ensure.ts +3 -1
- package/src/sdk/broker/lifecycle-codec.ts +11 -0
- package/src/sdk/broker/lifecycle-ledger.ts +371 -74
- package/src/sdk/broker/lifecycle.ts +1399 -142
- package/src/sdk/broker/process-incarnation.ts +5 -13
- package/src/sdk/broker/session-index.ts +145 -56
- package/src/sdk/broker/state-version.ts +21 -0
- package/src/sdk/broker/transport.ts +1 -1
- package/src/sdk/bus/chat-daemon-cli.ts +2 -0
- package/src/sdk/bus/chat-daemon-control.ts +8 -28
- package/src/sdk/bus/chat-effect-journal.ts +10 -2
- package/src/sdk/bus/config-commands.ts +5 -4
- package/src/sdk/bus/config.ts +98 -0
- package/src/sdk/bus/conversation-store.ts +5 -29
- package/src/sdk/bus/daemon-paths.ts +2 -0
- package/src/sdk/bus/html-format.ts +20 -3
- package/src/sdk/bus/index.ts +778 -93
- package/src/sdk/bus/lifecycle-control-runtime.ts +91 -33
- package/src/sdk/bus/natives-augment.d.ts +26 -1
- package/src/sdk/bus/notification-service.ts +168 -20
- package/src/sdk/bus/rate-limit-pool.ts +58 -14
- package/src/sdk/bus/recent-activity.ts +188 -74
- package/src/sdk/bus/rich-document.ts +258 -0
- package/src/sdk/bus/rich-render.ts +62 -70
- package/src/sdk/bus/telegram-daemon-cli.ts +15 -59
- package/src/sdk/bus/telegram-daemon-contract.ts +9 -4
- package/src/sdk/bus/telegram-daemon.ts +1389 -237
- package/src/sdk/bus/threaded-inbound.ts +8 -3
- package/src/sdk/bus/threaded-render.ts +57 -1
- package/src/sdk/bus/topic-registry.ts +120 -13
- package/src/sdk/host/control/dispatch.ts +14 -2
- package/src/sdk/host/control/operations.ts +6 -1
- package/src/sdk/host/host.ts +31 -1
- package/src/sdk/host/query/handlers.ts +20 -22
- package/src/sdk/host/query/revision-store.ts +80 -57
- package/src/sdk/index.ts +1 -0
- package/src/sdk/protocol/operation-inventory.generated.json +943 -1907
- package/src/sdk/session-directory.ts +141 -0
- package/src/sdk/session.ts +458 -479
- package/src/session/agent-session.ts +1700 -583
- package/src/session/agent-storage.ts +18 -0
- package/src/session/cache-economics.ts +25 -8
- package/src/session/fallback-chain-controller.ts +53 -3
- package/src/session/internal/managed-session-scope.ts +2226 -0
- package/src/session/internal/managed-session-storage.ts +453 -0
- package/src/session/messages.ts +91 -2
- package/src/session/session-manager-internal.ts +33 -0
- package/src/session/session-manager.ts +1112 -333
- package/src/session/session-storage.ts +444 -39
- package/src/session/volatile-context-pruning.ts +78 -0
- package/src/setup/credential-auto-import.ts +2 -1
- package/src/setup/hermes/templates/operator-instructions.v1.md +1 -1
- package/src/setup/provider-onboarding.ts +36 -61
- package/src/setup/provider-presets.json +5 -0
- package/src/skill-state/active-state.ts +3 -13
- package/src/skill-state/{deep-interview-mutation-guard.ts → workflow-mutation-guard.ts} +47 -29
- package/src/skill-state/workflow-state-contract.ts +6 -41
- package/src/slash-commands/builtin-registry.ts +46 -11
- package/src/system-prompt.ts +37 -44
- package/src/task/agents.ts +19 -1
- package/src/task/executor.ts +90 -6
- package/src/task/index.ts +36 -21
- package/src/thinking.ts +21 -6
- package/src/tool-discovery/tool-index.ts +2 -134
- package/src/tools/ask.ts +157 -21
- package/src/tools/ast-edit.ts +1 -1
- package/src/tools/bash.ts +38 -0
- package/src/tools/browser/dead-tab-recovery.ts +78 -0
- package/src/tools/browser/tab-supervisor.ts +282 -36
- package/src/tools/computer.ts +10 -3
- package/src/tools/cron.ts +30 -16
- package/src/tools/eval.ts +1 -2
- package/src/tools/fetch.ts +11 -4
- package/src/tools/index.ts +76 -24
- package/src/tools/output-meta.ts +13 -5
- package/src/tools/read.ts +37 -1
- package/src/tools/resource-gc.ts +2 -2
- package/src/tools/search-tool-bm25.ts +11 -74
- package/src/utils/event-bus.ts +9 -4
- package/src/web/search/index.ts +3 -3
- package/dist/types/discovery/plugin-dir-roots.d.ts +0 -15
- package/dist/types/runtime-mcp/discoverable-tool-metadata.d.ts +0 -7
- package/src/discovery/plugin-dir-roots.ts +0 -28
- package/src/runtime-mcp/discoverable-tool-metadata.ts +0 -25
|
@@ -3,6 +3,7 @@ import * as path from "node:path";
|
|
|
3
3
|
import { type AgentMessage, ThinkingLevel } from "@gajae-code/agent-core";
|
|
4
4
|
import { type AutocompleteProvider, matchesKey, type SlashCommand } from "@gajae-code/tui";
|
|
5
5
|
import { $env, sanitizeText } from "@gajae-code/utils";
|
|
6
|
+
import { type AppKeybinding, KEYBINDINGS } from "../../config/keybindings";
|
|
6
7
|
import { isSettingsInitialized, settings } from "../../config/settings";
|
|
7
8
|
import { resolveSubskillActivationForSkillInvocation } from "../../extensibility/gjc-plugins";
|
|
8
9
|
import { buildSkillPromptMessage, parseSkillInvocations } from "../../extensibility/skills";
|
|
@@ -10,9 +11,14 @@ import { expandEmoticons } from "../../modes/emoji-autocomplete";
|
|
|
10
11
|
import { createPromptActionAutocompleteProvider } from "../../modes/prompt-action-autocomplete";
|
|
11
12
|
import { theme } from "../../modes/theme/theme";
|
|
12
13
|
import { scrollTmuxToPreviousUserInput as scrollTmuxPaneToPreviousUserInput } from "../../modes/tmux-scroll";
|
|
13
|
-
import
|
|
14
|
+
import {
|
|
15
|
+
type ComposerSubmissionOptions,
|
|
16
|
+
canApplyComposerSubmission,
|
|
17
|
+
type InteractiveModeContext,
|
|
18
|
+
} from "../../modes/types";
|
|
14
19
|
import type { AgentSessionEvent, QueuedMessageEditEntry } from "../../session/agent-session";
|
|
15
20
|
import { SKILL_PROMPT_MESSAGE_TYPE, type SkillPromptDetails } from "../../session/messages";
|
|
21
|
+
import { getUserMessageViewportAnchorIds } from "../../session/session-manager";
|
|
16
22
|
import { executeBuiltinSlashCommand } from "../../slash-commands/builtin-registry";
|
|
17
23
|
import { copyToClipboard, readImageFromClipboard } from "../../utils/clipboard";
|
|
18
24
|
import { getEditorCommand, openInEditor } from "../../utils/external-editor";
|
|
@@ -20,13 +26,21 @@ import { ensureSupportedImageInput, ImageInputTooLargeError, loadImageInput } fr
|
|
|
20
26
|
import { resizeImage } from "../../utils/image-resize";
|
|
21
27
|
import { formatPastedImageReference, resolvePastedImagePath } from "../../utils/pasted-image-path";
|
|
22
28
|
import { generateSessionTitle, setSessionTerminalTitle } from "../../utils/title-generator";
|
|
29
|
+
import { ActionRegistry, APP_ACTION_METADATA } from "../action-registry";
|
|
30
|
+
import { CommandPalette, type CommandPaletteAction, type CommandPaletteEntry } from "../components/command-palette";
|
|
31
|
+
import { QueuePaneComponent } from "../components/queue-pane";
|
|
23
32
|
import { type QueuedMessageMoveDirection, QueuedMessageSelectorComponent } from "../components/queued-message-selector";
|
|
24
33
|
|
|
25
34
|
interface Expandable {
|
|
26
35
|
setExpanded(expanded: boolean): void;
|
|
36
|
+
setManuallyExpanded?(expanded: boolean): void;
|
|
27
37
|
}
|
|
28
38
|
|
|
29
|
-
const INTERACTIVE_ABORT_CLEANUP_TIMEOUT_MS = 5_000;
|
|
39
|
+
export const INTERACTIVE_ABORT_CLEANUP_TIMEOUT_MS = 5_000;
|
|
40
|
+
export const BACKGROUND_FOLD_DOUBLE_PRESS_MS = 750;
|
|
41
|
+
|
|
42
|
+
const DRAFT_CLEAR_DOUBLE_ESCAPE_WINDOW_MS = 800;
|
|
43
|
+
const EMPTY_EDITOR_DOUBLE_ESCAPE_WINDOW_MS = 500;
|
|
30
44
|
const IMAGE_PLACEHOLDER_PATTERN = /\[image ([1-9]\d*)\]/g;
|
|
31
45
|
const IMAGE_PLACEHOLDER_PRESENT_PATTERN = /\[image [1-9]\d*\]/;
|
|
32
46
|
|
|
@@ -35,16 +49,222 @@ function isExpandable(obj: unknown): obj is Expandable {
|
|
|
35
49
|
}
|
|
36
50
|
|
|
37
51
|
export class InputController {
|
|
38
|
-
|
|
52
|
+
readonly actionRegistry: ActionRegistry<void>;
|
|
53
|
+
|
|
54
|
+
constructor(private ctx: InteractiveModeContext) {
|
|
55
|
+
this.actionRegistry = new ActionRegistry({
|
|
56
|
+
context: undefined,
|
|
57
|
+
showError: actionId => this.ctx.showError(actionId),
|
|
58
|
+
});
|
|
59
|
+
this.#registerActions();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
#registerActions(): void {
|
|
63
|
+
const callbacks: Partial<Record<(typeof APP_ACTION_METADATA)[number]["id"], () => void | Promise<void>>> = {
|
|
64
|
+
"app.interrupt": () => this.ctx.editor.onEscape?.(),
|
|
65
|
+
"app.clear": () => this.handleCtrlC(),
|
|
66
|
+
"app.exit": () => this.handleCtrlD(),
|
|
67
|
+
"app.suspend": () => this.handleCtrlZ(),
|
|
68
|
+
"app.thinking.cycle": () => this.cycleThinkingLevel(),
|
|
69
|
+
"app.thinking.toggle": () => this.toggleThinkingBlockVisibility(),
|
|
70
|
+
"app.commandPalette.open": () => this.openCommandPalette(),
|
|
71
|
+
"app.model.cycleForward": () => this.cycleRoleModel(),
|
|
72
|
+
"app.model.cycleBackward": () => this.cycleRoleModel({ temporary: true }),
|
|
73
|
+
"app.model.select": () => this.ctx.showModelSelector(),
|
|
74
|
+
"app.model.selectTemporary": () => this.ctx.showModelSelector({ temporaryOnly: true }),
|
|
75
|
+
"app.tools.expand": () => this.toggleToolOutputExpansion(),
|
|
76
|
+
"app.tool.backgroundFold": () => {
|
|
77
|
+
this.handleForegroundToolBackgroundFold();
|
|
78
|
+
},
|
|
79
|
+
"app.editor.external": () => this.openExternalEditor(),
|
|
80
|
+
"app.message.followUp": () => this.handleFollowUp(),
|
|
81
|
+
"app.message.queue": () => this.handleQueueSubmit(),
|
|
82
|
+
"app.message.dequeue": () => this.handleDequeue(),
|
|
83
|
+
"app.clipboard.pasteImage": async () => {
|
|
84
|
+
await this.handleImagePaste();
|
|
85
|
+
},
|
|
86
|
+
"app.clipboard.copyLine": () => this.handleCopyCurrentLine(),
|
|
87
|
+
"app.clipboard.copyPrompt": () => this.handleCopyPrompt(),
|
|
88
|
+
"app.session.new": async () => {
|
|
89
|
+
await this.ctx.handleClearCommand();
|
|
90
|
+
},
|
|
91
|
+
"app.session.tree": () => this.ctx.showTreeSelector(),
|
|
92
|
+
"app.session.fork": () => this.ctx.showUserMessageSelector(),
|
|
93
|
+
"app.session.resume": () => this.ctx.showSessionSelector(),
|
|
94
|
+
"app.session.observe": async () => {
|
|
95
|
+
await this.ctx.showSessionObserver();
|
|
96
|
+
},
|
|
97
|
+
"app.session.dashboard": () => this.ctx.showSessionsDashboard(),
|
|
98
|
+
"app.transcript.browse": () => this.ctx.showTranscriptViewer(),
|
|
99
|
+
"app.jobs.open": () => this.ctx.showJobsOverlay(),
|
|
100
|
+
"app.plan.toggle": () => this.ctx.planModeController.handleCommand(),
|
|
101
|
+
"app.mode.cycle": () => this.ctx.planModeController.handleCommand(),
|
|
102
|
+
"app.history.search": () => this.ctx.showHistorySearch(),
|
|
103
|
+
"app.stt.toggle": () => this.ctx.handleSTTToggle(),
|
|
104
|
+
"app.irc.sidebar.toggle": () => this.ctx.toggleIrcSidebar(),
|
|
105
|
+
"app.transcript.prevTurn": () => this.#jumpTranscriptTurn(-1),
|
|
106
|
+
"app.transcript.nextTurn": () => this.#jumpTranscriptTurn(1),
|
|
107
|
+
"app.tasks.toggle": () => this.ctx.showTasksPane(),
|
|
108
|
+
"app.queue.togglePane": () => this.toggleQueuePane(),
|
|
109
|
+
"app.message.sendNow": () => this.sendNow(),
|
|
110
|
+
};
|
|
111
|
+
for (const metadata of APP_ACTION_METADATA) {
|
|
112
|
+
const callback = callbacks[metadata.id];
|
|
113
|
+
this.actionRegistry.register({
|
|
114
|
+
...metadata,
|
|
115
|
+
availability: () => Boolean(callback) && this.#isActionAvailable(metadata.id),
|
|
116
|
+
execute: async () => {
|
|
117
|
+
if (!callback) throw new Error(`Unavailable action executed: ${metadata.id}`);
|
|
118
|
+
await callback();
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
#isActionAvailable(id: (typeof APP_ACTION_METADATA)[number]["id"]): boolean {
|
|
125
|
+
switch (id) {
|
|
126
|
+
case "app.suspend":
|
|
127
|
+
return process.platform !== "win32";
|
|
128
|
+
case "app.thinking.cycle":
|
|
129
|
+
case "app.thinking.toggle":
|
|
130
|
+
return Boolean(this.ctx.session.model?.reasoning);
|
|
131
|
+
case "app.commandPalette.open":
|
|
132
|
+
return this.ctx.editor.getText().trim().length === 0;
|
|
133
|
+
case "app.model.cycleForward":
|
|
134
|
+
case "app.model.cycleBackward":
|
|
135
|
+
return this.ctx.session.getRoleModelCycleCandidateCount() > 1;
|
|
136
|
+
case "app.tools.expand":
|
|
137
|
+
return this.ctx.chatContainer.children.some(isExpandable);
|
|
138
|
+
case "app.tool.backgroundFold":
|
|
139
|
+
return Boolean(this.ctx.session.hasForegroundBashBackgroundRequestHandler?.());
|
|
140
|
+
case "app.editor.external":
|
|
141
|
+
return Boolean(getEditorCommand());
|
|
142
|
+
case "app.message.followUp":
|
|
143
|
+
case "app.message.queue":
|
|
144
|
+
return (
|
|
145
|
+
this.ctx.session.isStreaming ||
|
|
146
|
+
this.ctx.session.isCompacting ||
|
|
147
|
+
this.ctx.session.isBashRunning ||
|
|
148
|
+
this.ctx.session.isEvalRunning
|
|
149
|
+
);
|
|
150
|
+
case "app.message.dequeue":
|
|
151
|
+
return this.ctx.session.queuedMessageCount > 0;
|
|
152
|
+
case "app.clipboard.copyPrompt":
|
|
153
|
+
return this.ctx.editor.getText().length > 0;
|
|
154
|
+
case "app.session.tree":
|
|
155
|
+
case "app.session.fork":
|
|
156
|
+
return this.ctx.session.messages.length > 0;
|
|
157
|
+
case "app.plan.toggle":
|
|
158
|
+
return this.ctx.planModeController.enabled && !this.ctx.goalModeController.enabled;
|
|
159
|
+
case "app.history.search":
|
|
160
|
+
return (this.ctx.historyStorage?.getRecent(1).length ?? 0) > 0;
|
|
161
|
+
case "app.stt.toggle":
|
|
162
|
+
return Boolean(this.ctx.settings.get("stt.enabled"));
|
|
163
|
+
case "app.transcript.browse":
|
|
164
|
+
return this.ctx.session.messages.length > 0;
|
|
165
|
+
case "app.transcript.prevTurn":
|
|
166
|
+
case "app.transcript.nextTurn":
|
|
167
|
+
return getUserMessageViewportAnchorIds(this.ctx.session.messages).length > 0;
|
|
168
|
+
case "app.mode.cycle":
|
|
169
|
+
return (
|
|
170
|
+
Boolean(this.ctx.settings.get("plan.enabled")) &&
|
|
171
|
+
!this.ctx.goalModeController.enabled &&
|
|
172
|
+
!this.ctx.goalModeController.paused
|
|
173
|
+
);
|
|
174
|
+
case "app.queue.togglePane":
|
|
175
|
+
return true;
|
|
176
|
+
case "app.message.sendNow":
|
|
177
|
+
return (
|
|
178
|
+
this.ctx.session.isStreaming &&
|
|
179
|
+
(this.ctx.editor.getText().trim().length > 0 || this.ctx.session.queuedMessageCount > 0)
|
|
180
|
+
);
|
|
181
|
+
default:
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
#executeAction(id: (typeof APP_ACTION_METADATA)[number]["id"]): void {
|
|
187
|
+
void this.actionRegistry.execute(id);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
#transcriptTurnAnchorIds: readonly string[] = [];
|
|
191
|
+
#transcriptTurnPosition = 0;
|
|
192
|
+
|
|
193
|
+
#syncTranscriptTurnPosition(): readonly string[] {
|
|
194
|
+
const anchorIds = getUserMessageViewportAnchorIds(this.ctx.session.messages);
|
|
195
|
+
if (
|
|
196
|
+
anchorIds.length !== this.#transcriptTurnAnchorIds.length ||
|
|
197
|
+
anchorIds.some(id => !this.#transcriptTurnAnchorIds.includes(id))
|
|
198
|
+
) {
|
|
199
|
+
const hadAnchors = this.#transcriptTurnAnchorIds.length > 0;
|
|
200
|
+
const currentAnchorId = this.#transcriptTurnAnchorIds[this.#transcriptTurnPosition];
|
|
201
|
+
const retainedAnchorIds = this.#transcriptTurnAnchorIds.filter(id => anchorIds.includes(id));
|
|
202
|
+
this.#transcriptTurnAnchorIds = [
|
|
203
|
+
...retainedAnchorIds,
|
|
204
|
+
...anchorIds.filter(id => !retainedAnchorIds.includes(id)),
|
|
205
|
+
];
|
|
206
|
+
const currentPosition = currentAnchorId ? this.#transcriptTurnAnchorIds.indexOf(currentAnchorId) : -1;
|
|
207
|
+
this.#transcriptTurnPosition =
|
|
208
|
+
currentPosition >= 0
|
|
209
|
+
? currentPosition
|
|
210
|
+
: currentAnchorId || hadAnchors
|
|
211
|
+
? Math.min(this.#transcriptTurnPosition, this.#transcriptTurnAnchorIds.length)
|
|
212
|
+
: this.#transcriptTurnAnchorIds.length;
|
|
213
|
+
}
|
|
214
|
+
return this.#transcriptTurnAnchorIds;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
#jumpTranscriptTurn(direction: -1 | 1): void {
|
|
218
|
+
const anchorIds = this.#syncTranscriptTurnPosition();
|
|
219
|
+
const targetPosition = this.#transcriptTurnPosition + direction;
|
|
220
|
+
if (targetPosition < 0 || targetPosition >= anchorIds.length) return;
|
|
221
|
+
if (this.ctx.ui.revealViewportAnchor(anchorIds[targetPosition], "top")) {
|
|
222
|
+
this.#transcriptTurnPosition = targetPosition;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
39
225
|
|
|
40
226
|
#lastBackgroundFoldKeyTime = 0;
|
|
227
|
+
#slashCommands: SlashCommand[] = [];
|
|
41
228
|
|
|
42
229
|
/** Set after a first Esc silently consumes a queued steer. Kept until the
|
|
43
230
|
* queued steer is either cancelled by a second Esc or drained by continuation,
|
|
44
231
|
* so abort cleanup going idle cannot turn the second Esc into an idle action. */
|
|
45
232
|
#steerConsumePending = false;
|
|
233
|
+
#commandPaletteActions = new Map<AppKeybinding, CommandPaletteAction>();
|
|
234
|
+
#paletteCommandInFlight = false;
|
|
235
|
+
|
|
236
|
+
#registerCommandPaletteAction(action: AppKeybinding, handler: () => void | Promise<void>): void {
|
|
237
|
+
this.#commandPaletteActions.set(action, {
|
|
238
|
+
id: action,
|
|
239
|
+
label: KEYBINDINGS[action].description,
|
|
240
|
+
handler,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
46
243
|
|
|
47
244
|
#globalInterruptUnsubscribe: (() => void) | undefined;
|
|
245
|
+
#draftClearEscapeText: string | undefined;
|
|
246
|
+
|
|
247
|
+
#resetEscapeGestures(): void {
|
|
248
|
+
this.ctx.lastEscapeTime = 0;
|
|
249
|
+
this.ctx.lastComposerClearEscapeTime = 0;
|
|
250
|
+
this.#draftClearEscapeText = undefined;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
#armDraftClearEscape(text: string, now: number): void {
|
|
254
|
+
this.ctx.lastEscapeTime = 0;
|
|
255
|
+
this.ctx.lastComposerClearEscapeTime = now;
|
|
256
|
+
this.#draftClearEscapeText = text;
|
|
257
|
+
this.ctx.showStatus("press Esc again to clear");
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
#resetDraftClearEscape(): void {
|
|
261
|
+
this.ctx.lastComposerClearEscapeTime = 0;
|
|
262
|
+
this.#draftClearEscapeText = undefined;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
#resetEmptyEditorEscape(): void {
|
|
266
|
+
this.ctx.lastEscapeTime = 0;
|
|
267
|
+
}
|
|
48
268
|
|
|
49
269
|
#matchesInterruptKey(data: string): boolean {
|
|
50
270
|
return this.ctx.keybindings.getKeys("app.interrupt").some(key => matchesKey(data, key));
|
|
@@ -146,6 +366,7 @@ export class InputController {
|
|
|
146
366
|
return undefined;
|
|
147
367
|
}
|
|
148
368
|
if (this.ctx.hasActiveBtw() && this.ctx.handleBtwEscape()) {
|
|
369
|
+
this.#resetEscapeGestures();
|
|
149
370
|
return { consume: true };
|
|
150
371
|
}
|
|
151
372
|
const hookDialogActive = this.#hasHookDialog();
|
|
@@ -153,6 +374,7 @@ export class InputController {
|
|
|
153
374
|
// Inline ask/custom-input editors use Esc to return to the option list.
|
|
154
375
|
// Let the focused selector see the key instead of converting a typo
|
|
155
376
|
// into a full workflow/session abort while the agent is streaming.
|
|
377
|
+
this.#resetEscapeGestures();
|
|
156
378
|
return undefined;
|
|
157
379
|
}
|
|
158
380
|
if (
|
|
@@ -165,6 +387,7 @@ export class InputController {
|
|
|
165
387
|
streaming: hookDialogActive,
|
|
166
388
|
})
|
|
167
389
|
) {
|
|
390
|
+
this.#resetEscapeGestures();
|
|
168
391
|
return { consume: true };
|
|
169
392
|
}
|
|
170
393
|
return undefined;
|
|
@@ -178,11 +401,18 @@ export class InputController {
|
|
|
178
401
|
silent: options?.silent,
|
|
179
402
|
});
|
|
180
403
|
}
|
|
404
|
+
#clearComposer(): void {
|
|
405
|
+
const text = this.ctx.editor.getText();
|
|
406
|
+
if (text.trim()) {
|
|
407
|
+
this.ctx.editor.addToHistory(text);
|
|
408
|
+
}
|
|
409
|
+
this.ctx.clearEditor();
|
|
410
|
+
}
|
|
181
411
|
|
|
182
412
|
setupKeyHandlers(): void {
|
|
183
413
|
this.ctx.editor.setActionKeys("app.interrupt", this.ctx.keybindings.getKeys("app.interrupt"));
|
|
184
|
-
this.ctx.editor.shouldBypassAutocompleteOnEscape = () =>
|
|
185
|
-
Boolean(
|
|
414
|
+
this.ctx.editor.shouldBypassAutocompleteOnEscape = () => {
|
|
415
|
+
const bypassAutocomplete = Boolean(
|
|
186
416
|
this.ctx.loadingAnimation ||
|
|
187
417
|
this.ctx.hasActiveBtw() ||
|
|
188
418
|
(this.#steerConsumePending && this.ctx.session.hasQueuedSteering) ||
|
|
@@ -197,18 +427,28 @@ export class InputController {
|
|
|
197
427
|
this.ctx.autoCompactionEscapeHandler ||
|
|
198
428
|
this.ctx.retryEscapeHandler,
|
|
199
429
|
);
|
|
430
|
+
if (!bypassAutocomplete) this.#resetEscapeGestures();
|
|
431
|
+
return bypassAutocomplete;
|
|
432
|
+
};
|
|
200
433
|
this.#installGlobalInterruptListener();
|
|
201
434
|
|
|
202
435
|
// An open btw panel must stay dismissable with Esc even while another
|
|
203
436
|
// controller (auto-compaction, auto-retry, manual compaction, etc.) has
|
|
204
437
|
// temporarily replaced editor.onEscape. This priority hook is never
|
|
205
438
|
// swapped out, so it always wins for the interrupt key.
|
|
206
|
-
this.ctx.editor.onInterruptPriority = () =>
|
|
439
|
+
this.ctx.editor.onInterruptPriority = () => {
|
|
440
|
+
if (!this.ctx.hasActiveBtw()) return false;
|
|
441
|
+
const consumed = this.ctx.handleBtwEscape();
|
|
442
|
+
if (consumed) this.#resetEscapeGestures();
|
|
443
|
+
return consumed;
|
|
444
|
+
};
|
|
207
445
|
this.ctx.editor.onEscape = () => {
|
|
208
446
|
if (this.ctx.hasActiveBtw() && this.ctx.handleBtwEscape()) {
|
|
447
|
+
this.#resetEscapeGestures();
|
|
209
448
|
return;
|
|
210
449
|
}
|
|
211
450
|
if (this.#steerConsumePending) {
|
|
451
|
+
this.#resetEscapeGestures();
|
|
212
452
|
if (this.ctx.session.hasQueuedSteering) {
|
|
213
453
|
// Second Esc before the scheduled steer continuation drains the
|
|
214
454
|
// queue: restore/drop the queued steer and perform a real abort,
|
|
@@ -220,20 +460,7 @@ export class InputController {
|
|
|
220
460
|
this.#steerConsumePending = false;
|
|
221
461
|
}
|
|
222
462
|
if (this.#handleCancellableWorkEscape({ maintenance: true, retry: true })) {
|
|
223
|
-
|
|
224
|
-
}
|
|
225
|
-
// Normal input state with user-typed text: Esc must not interrupt a
|
|
226
|
-
// running task (streaming turn, bash/eval). A double Esc within the
|
|
227
|
-
// 500ms window clears the composer instead. Bash/Python input modes
|
|
228
|
-
// keep their own Esc handling in the chain below.
|
|
229
|
-
if (!this.ctx.isBashMode && !this.ctx.isPythonMode && this.ctx.editor.getText().trim()) {
|
|
230
|
-
const now = Date.now();
|
|
231
|
-
if (now - this.ctx.lastComposerClearEscapeTime < 500) {
|
|
232
|
-
this.ctx.clearEditor();
|
|
233
|
-
this.ctx.lastComposerClearEscapeTime = 0;
|
|
234
|
-
} else {
|
|
235
|
-
this.ctx.lastComposerClearEscapeTime = now;
|
|
236
|
-
}
|
|
463
|
+
this.#resetEscapeGestures();
|
|
237
464
|
return;
|
|
238
465
|
}
|
|
239
466
|
if (
|
|
@@ -246,68 +473,118 @@ export class InputController {
|
|
|
246
473
|
streaming: true,
|
|
247
474
|
})
|
|
248
475
|
) {
|
|
476
|
+
this.#resetEscapeGestures();
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const text = this.ctx.editor.getText();
|
|
481
|
+
if (!this.ctx.isBashMode && !this.ctx.isPythonMode && text.trim()) {
|
|
482
|
+
this.#resetEmptyEditorEscape();
|
|
483
|
+
const now = Date.now();
|
|
484
|
+
if (
|
|
485
|
+
now - this.ctx.lastComposerClearEscapeTime < DRAFT_CLEAR_DOUBLE_ESCAPE_WINDOW_MS &&
|
|
486
|
+
this.#draftClearEscapeText === text
|
|
487
|
+
) {
|
|
488
|
+
this.#clearComposer();
|
|
489
|
+
this.#resetDraftClearEscape();
|
|
490
|
+
} else {
|
|
491
|
+
this.#armDraftClearEscape(text, now);
|
|
492
|
+
}
|
|
249
493
|
return;
|
|
250
494
|
}
|
|
251
|
-
|
|
495
|
+
|
|
496
|
+
if (!text.trim()) {
|
|
497
|
+
this.#resetDraftClearEscape();
|
|
252
498
|
// Double-interrupt with empty editor triggers /tree, /branch, or nothing based on setting
|
|
253
499
|
const action = settings.get("doubleEscapeAction");
|
|
254
|
-
if (action
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
this.ctx.
|
|
500
|
+
if (action === "none") {
|
|
501
|
+
this.#resetEmptyEditorEscape();
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const now = Date.now();
|
|
506
|
+
if (now - this.ctx.lastEscapeTime < EMPTY_EDITOR_DOUBLE_ESCAPE_WINDOW_MS) {
|
|
507
|
+
if (action === "tree") {
|
|
508
|
+
this.ctx.showTreeSelector();
|
|
263
509
|
} else {
|
|
264
|
-
this.ctx.
|
|
510
|
+
this.ctx.showUserMessageSelector();
|
|
265
511
|
}
|
|
512
|
+
this.#resetEmptyEditorEscape();
|
|
513
|
+
} else {
|
|
514
|
+
this.ctx.lastEscapeTime = now;
|
|
266
515
|
}
|
|
267
516
|
}
|
|
268
517
|
};
|
|
269
518
|
|
|
519
|
+
const clear = () => this.handleCtrlC();
|
|
270
520
|
this.ctx.editor.setActionKeys("app.clear", this.ctx.keybindings.getKeys("app.clear"));
|
|
271
|
-
this.ctx.editor.onClear = () => this.
|
|
521
|
+
this.ctx.editor.onClear = () => this.#executeAction("app.clear");
|
|
522
|
+
this.#registerCommandPaletteAction("app.clear", clear);
|
|
523
|
+
const exit = () => this.handleCtrlD();
|
|
272
524
|
this.ctx.editor.setActionKeys("app.exit", this.ctx.keybindings.getKeys("app.exit"));
|
|
273
|
-
this.ctx.editor.onExit = () => this.
|
|
525
|
+
this.ctx.editor.onExit = () => this.#executeAction("app.exit");
|
|
526
|
+
this.#registerCommandPaletteAction("app.exit", exit);
|
|
527
|
+
const suspend = () => this.handleCtrlZ();
|
|
274
528
|
this.ctx.editor.setActionKeys("app.suspend", this.ctx.keybindings.getKeys("app.suspend"));
|
|
275
|
-
this.ctx.editor.onSuspend = () => this.
|
|
529
|
+
this.ctx.editor.onSuspend = () => this.#executeAction("app.suspend");
|
|
530
|
+
this.#registerCommandPaletteAction("app.suspend", suspend);
|
|
531
|
+
const cycleThinking = () => this.cycleThinkingLevel();
|
|
276
532
|
this.ctx.editor.setActionKeys("app.thinking.cycle", this.ctx.keybindings.getKeys("app.thinking.cycle"));
|
|
277
|
-
this.ctx.editor.onCycleThinkingLevel = () => this.
|
|
533
|
+
this.ctx.editor.onCycleThinkingLevel = () => this.#executeAction("app.thinking.cycle");
|
|
534
|
+
this.#registerCommandPaletteAction("app.thinking.cycle", cycleThinking);
|
|
278
535
|
this.ctx.editor.setActionKeys("app.commandPalette.open", this.ctx.keybindings.getKeys("app.commandPalette.open"));
|
|
279
|
-
this.ctx.editor.onOpenCommandPalette = () => this.
|
|
536
|
+
this.ctx.editor.onOpenCommandPalette = () => this.#executeAction("app.commandPalette.open");
|
|
537
|
+
const cycleModelForward = () => this.cycleRoleModel();
|
|
280
538
|
this.ctx.editor.setActionKeys("app.model.cycleForward", this.ctx.keybindings.getKeys("app.model.cycleForward"));
|
|
281
|
-
this.ctx.editor.onCycleModelForward = () => this.
|
|
539
|
+
this.ctx.editor.onCycleModelForward = () => this.#executeAction("app.model.cycleForward");
|
|
540
|
+
this.#registerCommandPaletteAction("app.model.cycleForward", cycleModelForward);
|
|
541
|
+
const cycleModelBackward = () => this.cycleRoleModel({ temporary: true });
|
|
282
542
|
this.ctx.editor.setActionKeys("app.model.cycleBackward", this.ctx.keybindings.getKeys("app.model.cycleBackward"));
|
|
283
|
-
this.ctx.editor.onCycleModelBackward = () => this.
|
|
543
|
+
this.ctx.editor.onCycleModelBackward = () => this.#executeAction("app.model.cycleBackward");
|
|
544
|
+
this.#registerCommandPaletteAction("app.model.cycleBackward", cycleModelBackward);
|
|
545
|
+
const selectTemporaryModel = () => this.ctx.showModelSelector({ temporaryOnly: true });
|
|
284
546
|
this.ctx.editor.setActionKeys(
|
|
285
547
|
"app.model.selectTemporary",
|
|
286
548
|
this.ctx.keybindings.getKeys("app.model.selectTemporary"),
|
|
287
549
|
);
|
|
288
|
-
this.ctx.editor.onSelectModelTemporary = () => this.
|
|
550
|
+
this.ctx.editor.onSelectModelTemporary = () => this.#executeAction("app.model.selectTemporary");
|
|
551
|
+
this.#registerCommandPaletteAction("app.model.selectTemporary", selectTemporaryModel);
|
|
289
552
|
|
|
290
553
|
// Global debug handler on TUI (works regardless of focus)
|
|
291
554
|
this.ctx.ui.onDebug = () => this.ctx.showDebugSelector();
|
|
555
|
+
const selectModel = () => this.ctx.showModelSelector();
|
|
292
556
|
this.ctx.editor.setActionKeys("app.model.select", this.ctx.keybindings.getKeys("app.model.select"));
|
|
293
|
-
this.ctx.editor.onSelectModel = () => this.
|
|
557
|
+
this.ctx.editor.onSelectModel = () => this.#executeAction("app.model.select");
|
|
558
|
+
this.#registerCommandPaletteAction("app.model.select", selectModel);
|
|
559
|
+
const searchHistory = () => this.ctx.showHistorySearch();
|
|
294
560
|
this.ctx.editor.setActionKeys("app.history.search", this.ctx.keybindings.getKeys("app.history.search"));
|
|
295
|
-
this.ctx.editor.onHistorySearch = () => this.
|
|
561
|
+
this.ctx.editor.onHistorySearch = () => this.#executeAction("app.history.search");
|
|
562
|
+
this.#registerCommandPaletteAction("app.history.search", searchHistory);
|
|
563
|
+
const toggleThinking = () => this.ctx.toggleThinkingBlockVisibility();
|
|
296
564
|
this.ctx.editor.setActionKeys("app.thinking.toggle", this.ctx.keybindings.getKeys("app.thinking.toggle"));
|
|
297
|
-
this.ctx.editor.onToggleThinking = () => this.
|
|
565
|
+
this.ctx.editor.onToggleThinking = () => this.#executeAction("app.thinking.toggle");
|
|
566
|
+
this.#registerCommandPaletteAction("app.thinking.toggle", toggleThinking);
|
|
567
|
+
const externalEditor = () => this.openExternalEditor();
|
|
298
568
|
this.ctx.editor.setActionKeys("app.editor.external", this.ctx.keybindings.getKeys("app.editor.external"));
|
|
299
|
-
this.ctx.editor.onExternalEditor = () =>
|
|
569
|
+
this.ctx.editor.onExternalEditor = () => this.#executeAction("app.editor.external");
|
|
570
|
+
this.#registerCommandPaletteAction("app.editor.external", externalEditor);
|
|
300
571
|
this.ctx.editor.onShowHotkeys = () => this.ctx.handleHotkeysCommand();
|
|
572
|
+
const pasteImageFromPalette = async () => {
|
|
573
|
+
await this.handleImagePaste();
|
|
574
|
+
};
|
|
301
575
|
this.ctx.editor.setActionKeys(
|
|
302
576
|
"app.clipboard.pasteImage",
|
|
303
577
|
this.ctx.keybindings.getKeys("app.clipboard.pasteImage"),
|
|
304
578
|
);
|
|
305
|
-
this.ctx.editor.onPasteImage = () => this.
|
|
579
|
+
this.ctx.editor.onPasteImage = () => this.actionRegistry.execute("app.clipboard.pasteImage");
|
|
580
|
+
this.#registerCommandPaletteAction("app.clipboard.pasteImage", pasteImageFromPalette);
|
|
581
|
+
const copyPrompt = () => this.handleCopyPrompt();
|
|
306
582
|
this.ctx.editor.setActionKeys(
|
|
307
583
|
"app.clipboard.copyPrompt",
|
|
308
584
|
this.ctx.keybindings.getKeys("app.clipboard.copyPrompt"),
|
|
309
585
|
);
|
|
310
|
-
this.ctx.editor.onCopyPrompt = () => this.
|
|
586
|
+
this.ctx.editor.onCopyPrompt = () => this.#executeAction("app.clipboard.copyPrompt");
|
|
587
|
+
this.#registerCommandPaletteAction("app.clipboard.copyPrompt", copyPrompt);
|
|
311
588
|
this.ctx.editor.onPasteText = text => this.handleTextPaste(text);
|
|
312
589
|
this.ctx.editor.onPastePendingInputCleared = (reason, droppedInputCount) => {
|
|
313
590
|
const reasonText = reason === "timeout" ? "timed out" : "exceeded the input queue limit";
|
|
@@ -315,12 +592,18 @@ export class InputController {
|
|
|
315
592
|
`Paste handling ${reasonText}; discarded ${droppedInputCount} buffered input event${droppedInputCount === 1 ? "" : "s"}.`,
|
|
316
593
|
);
|
|
317
594
|
};
|
|
595
|
+
const expandTools = () => this.toggleToolOutputExpansion();
|
|
318
596
|
this.ctx.editor.setActionKeys("app.tools.expand", this.ctx.keybindings.getKeys("app.tools.expand"));
|
|
319
|
-
this.ctx.editor.onExpandTools = () => this.
|
|
597
|
+
this.ctx.editor.onExpandTools = () => this.#executeAction("app.tools.expand");
|
|
598
|
+
this.#registerCommandPaletteAction("app.tools.expand", expandTools);
|
|
599
|
+
const dequeue = () => this.handleDequeue();
|
|
320
600
|
this.ctx.editor.setActionKeys("app.message.dequeue", this.ctx.keybindings.getKeys("app.message.dequeue"));
|
|
321
|
-
this.ctx.editor.onDequeue = () => this.
|
|
601
|
+
this.ctx.editor.onDequeue = () => this.#executeAction("app.message.dequeue");
|
|
602
|
+
this.#registerCommandPaletteAction("app.message.dequeue", dequeue);
|
|
603
|
+
const queue = () => this.handleQueueSubmit();
|
|
322
604
|
this.ctx.editor.setActionKeys("app.message.queue", this.ctx.keybindings.getKeys("app.message.queue"));
|
|
323
|
-
this.ctx.editor.onQueue = () =>
|
|
605
|
+
this.ctx.editor.onQueue = () => this.#executeAction("app.message.queue");
|
|
606
|
+
this.#registerCommandPaletteAction("app.message.queue", queue);
|
|
324
607
|
|
|
325
608
|
this.ctx.editor.onViewportPageScroll = direction => this.ctx.ui.scrollViewportPages(direction);
|
|
326
609
|
this.ctx.editor.onViewportFollowLive = () => {
|
|
@@ -333,64 +616,106 @@ export class InputController {
|
|
|
333
616
|
|
|
334
617
|
for (const key of this.ctx.keybindings.getKeys("app.irc.sidebar.toggle")) {
|
|
335
618
|
this.ctx.editor.setCustomKeyHandler(key, () => {
|
|
336
|
-
this.
|
|
619
|
+
this.#executeAction("app.irc.sidebar.toggle");
|
|
337
620
|
return true;
|
|
338
621
|
});
|
|
339
622
|
}
|
|
340
623
|
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
|
|
624
|
+
const togglePlanMode = () => this.ctx.planModeController.handleCommand();
|
|
625
|
+
this.#registerCommandPaletteAction("app.plan.toggle", togglePlanMode);
|
|
626
|
+
for (const key of this.ctx.keybindings.getKeys("app.plan.toggle")) {
|
|
627
|
+
this.ctx.editor.setCustomKeyHandler(key, () => {
|
|
628
|
+
this.#executeAction("app.plan.toggle");
|
|
629
|
+
return true;
|
|
630
|
+
});
|
|
344
631
|
}
|
|
345
|
-
|
|
632
|
+
const newSession = async () => {
|
|
633
|
+
await this.ctx.handleClearCommand();
|
|
634
|
+
};
|
|
635
|
+
this.#registerCommandPaletteAction("app.session.new", newSession);
|
|
346
636
|
for (const key of this.ctx.keybindings.getKeys("app.session.new")) {
|
|
347
|
-
this.ctx.editor.setCustomKeyHandler(key, () =>
|
|
637
|
+
this.ctx.editor.setCustomKeyHandler(key, () => {
|
|
638
|
+
this.#executeAction("app.session.new");
|
|
639
|
+
return true;
|
|
640
|
+
});
|
|
348
641
|
}
|
|
642
|
+
const showTree = () => {
|
|
643
|
+
this.ctx.showTreeSelector();
|
|
644
|
+
return undefined;
|
|
645
|
+
};
|
|
646
|
+
this.#registerCommandPaletteAction("app.session.tree", showTree);
|
|
349
647
|
for (const key of this.ctx.keybindings.getKeys("app.session.tree")) {
|
|
350
648
|
this.ctx.editor.setCustomKeyHandler(key, () => {
|
|
351
|
-
this.
|
|
649
|
+
this.#executeAction("app.session.tree");
|
|
650
|
+
return true;
|
|
352
651
|
});
|
|
353
652
|
}
|
|
653
|
+
const forkSession = () => {
|
|
654
|
+
this.ctx.showUserMessageSelector();
|
|
655
|
+
return undefined;
|
|
656
|
+
};
|
|
657
|
+
this.#registerCommandPaletteAction("app.session.fork", forkSession);
|
|
354
658
|
for (const key of this.ctx.keybindings.getKeys("app.session.fork")) {
|
|
355
659
|
this.ctx.editor.setCustomKeyHandler(key, () => {
|
|
356
|
-
this.
|
|
660
|
+
this.#executeAction("app.session.fork");
|
|
661
|
+
return true;
|
|
357
662
|
});
|
|
358
663
|
}
|
|
664
|
+
const resumeSession = () => {
|
|
665
|
+
this.ctx.showSessionSelector();
|
|
666
|
+
return undefined;
|
|
667
|
+
};
|
|
668
|
+
this.#registerCommandPaletteAction("app.session.resume", resumeSession);
|
|
359
669
|
for (const key of this.ctx.keybindings.getKeys("app.session.resume")) {
|
|
360
670
|
this.ctx.editor.setCustomKeyHandler(key, () => {
|
|
361
|
-
this.
|
|
671
|
+
this.#executeAction("app.session.resume");
|
|
672
|
+
return true;
|
|
362
673
|
});
|
|
363
674
|
}
|
|
364
675
|
for (const key of this.ctx.keybindings.getKeys("app.message.followUp")) {
|
|
365
676
|
this.ctx.editor.setCustomKeyHandler(key, () => {
|
|
366
|
-
if (!this
|
|
367
|
-
|
|
677
|
+
if (!this.actionRegistry.isAvailable("app.message.followUp")) return false;
|
|
678
|
+
this.#executeAction("app.message.followUp");
|
|
368
679
|
return true;
|
|
369
680
|
});
|
|
370
681
|
}
|
|
371
682
|
for (const key of this.ctx.keybindings.getKeys("app.stt.toggle")) {
|
|
372
|
-
this.ctx.editor.setCustomKeyHandler(key, () =>
|
|
683
|
+
this.ctx.editor.setCustomKeyHandler(key, () => {
|
|
684
|
+
this.#executeAction("app.stt.toggle");
|
|
685
|
+
return true;
|
|
686
|
+
});
|
|
373
687
|
}
|
|
374
688
|
for (const key of this.ctx.keybindings.getKeys("app.clipboard.copyLine")) {
|
|
375
689
|
this.ctx.editor.setCustomKeyHandler(key, () => {
|
|
376
|
-
this.
|
|
690
|
+
this.#executeAction("app.clipboard.copyLine");
|
|
377
691
|
});
|
|
378
692
|
}
|
|
379
693
|
for (const key of this.ctx.keybindings.getKeys("app.session.observe")) {
|
|
380
694
|
this.ctx.editor.setCustomKeyHandler(key, () => {
|
|
381
|
-
this.
|
|
695
|
+
this.#executeAction("app.session.observe");
|
|
382
696
|
});
|
|
383
697
|
}
|
|
384
698
|
for (const key of this.ctx.keybindings.getKeys("app.jobs.open")) {
|
|
385
699
|
this.ctx.editor.setCustomKeyHandler(key, () => {
|
|
386
|
-
this.
|
|
700
|
+
this.#executeAction("app.jobs.open");
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
for (const key of this.ctx.keybindings.getKeys("app.tasks.toggle")) {
|
|
704
|
+
this.ctx.editor.setCustomKeyHandler(key, () => {
|
|
705
|
+
this.#executeAction("app.tasks.toggle");
|
|
706
|
+
return true;
|
|
387
707
|
});
|
|
388
708
|
}
|
|
389
709
|
for (const key of this.ctx.keybindings.getKeys("app.tool.backgroundFold")) {
|
|
390
|
-
this.ctx.editor.setCustomKeyHandler(key, () =>
|
|
710
|
+
this.ctx.editor.setCustomKeyHandler(key, () => {
|
|
711
|
+
if (!this.actionRegistry.isAvailable("app.tool.backgroundFold")) return false;
|
|
712
|
+
this.#executeAction("app.tool.backgroundFold");
|
|
713
|
+
return true;
|
|
714
|
+
});
|
|
391
715
|
}
|
|
392
716
|
|
|
393
717
|
this.ctx.editor.onChange = (text: string) => {
|
|
718
|
+
this.#resetEscapeGestures();
|
|
394
719
|
const wasBashMode = this.ctx.isBashMode;
|
|
395
720
|
const wasBashNoContext = this.ctx.isBashNoContext;
|
|
396
721
|
const wasPythonMode = this.ctx.isPythonMode;
|
|
@@ -410,188 +735,205 @@ export class InputController {
|
|
|
410
735
|
}
|
|
411
736
|
|
|
412
737
|
setupEditorSubmitHandler(): void {
|
|
413
|
-
this.ctx.editor.onSubmit =
|
|
414
|
-
|
|
415
|
-
if ((!isSettingsInitialized() || settings.get("emojiAutocomplete")) && text) text = expandEmoticons(text);
|
|
416
|
-
|
|
417
|
-
// Empty submit while streaming with queued messages: flush queues immediately
|
|
418
|
-
if (!text && this.ctx.session.isStreaming && this.ctx.session.queuedMessageCount > 0) {
|
|
419
|
-
// Abort current stream and let queued messages be processed
|
|
420
|
-
await this.#abortInteractive();
|
|
421
|
-
return;
|
|
422
|
-
}
|
|
738
|
+
this.ctx.editor.onSubmit = text => this.submitText(text, { ownsComposer: true, editor: this.ctx.editor });
|
|
739
|
+
}
|
|
423
740
|
|
|
424
|
-
|
|
741
|
+
async submitText(text: string, composer: ComposerSubmissionOptions): Promise<void> {
|
|
742
|
+
text = text.trim();
|
|
743
|
+
if ((!isSettingsInitialized() || settings.get("emojiAutocomplete")) && text) text = expandEmoticons(text);
|
|
425
744
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
}
|
|
433
|
-
return;
|
|
434
|
-
}
|
|
745
|
+
// Empty submit while streaming with queued messages: flush queues immediately
|
|
746
|
+
if (!text && this.ctx.session.isStreaming && this.ctx.session.queuedMessageCount > 0) {
|
|
747
|
+
// Abort current stream and let queued messages be processed
|
|
748
|
+
await this.#abortInteractive();
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
435
751
|
|
|
436
|
-
|
|
437
|
-
const pendingImages = this.ctx.pendingImages;
|
|
438
|
-
let inputImages = this.#visiblePendingImagesForText(text);
|
|
752
|
+
if (!text) return;
|
|
439
753
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
}
|
|
447
|
-
if (result?.text !== undefined) {
|
|
448
|
-
text = result.text.trim();
|
|
449
|
-
}
|
|
450
|
-
if (result?.images !== undefined) {
|
|
451
|
-
inputImages = result.images;
|
|
452
|
-
}
|
|
754
|
+
// Continue shortcuts: "." or "c" sends empty message (agent continues, no visible message)
|
|
755
|
+
if (text === "." || text === "c") {
|
|
756
|
+
if (this.ctx.onInputCallback) {
|
|
757
|
+
this.ctx.editor.setText("");
|
|
758
|
+
this.ctx.pendingImages = [];
|
|
759
|
+
this.ctx.onInputCallback({ text: "", cancelled: false, started: true });
|
|
453
760
|
}
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
454
763
|
|
|
455
|
-
|
|
764
|
+
const runner = this.ctx.session.extensionRunner;
|
|
765
|
+
const pendingImages = this.ctx.pendingImages;
|
|
766
|
+
let inputImages = this.#visiblePendingImagesForText(text);
|
|
456
767
|
|
|
457
|
-
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
768
|
+
if (runner?.hasHandlers("input")) {
|
|
769
|
+
const result = await runner.emitInput(text, inputImages, "interactive");
|
|
770
|
+
if (result?.handled) {
|
|
771
|
+
if (this.#canModifyComposer(composer)) {
|
|
772
|
+
this.ctx.editor.setText("");
|
|
773
|
+
}
|
|
774
|
+
this.#clearPendingImagesIfOwnedBy(pendingImages, composer);
|
|
463
775
|
return;
|
|
464
776
|
}
|
|
465
|
-
if (
|
|
466
|
-
|
|
467
|
-
text = slashResult;
|
|
777
|
+
if (result?.text !== undefined) {
|
|
778
|
+
text = result.text.trim();
|
|
468
779
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
// honors `busyPromptMode`: "steer" interrupts the active turn, "queue"
|
|
472
|
-
// runs after it completes (matches the free-text Enter semantics applied
|
|
473
|
-
// a few lines below at the streaming branch). Explicit queue shortcuts
|
|
474
|
-
// route through `handleFollowUp` and dispatch as `followUp`.
|
|
475
|
-
if (await this.#invokeSkillCommand(text, this.#busyStreamingBehavior())) {
|
|
476
|
-
return;
|
|
780
|
+
if (result?.images !== undefined) {
|
|
781
|
+
inputImages = result.images;
|
|
477
782
|
}
|
|
783
|
+
}
|
|
478
784
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
785
|
+
if (!text) return;
|
|
786
|
+
|
|
787
|
+
// Handle built-in slash commands
|
|
788
|
+
const slashResult = await executeBuiltinSlashCommand(text, {
|
|
789
|
+
ctx: this.ctx,
|
|
790
|
+
handleBackgroundCommand: () => this.handleBackgroundCommand(),
|
|
791
|
+
composer,
|
|
792
|
+
});
|
|
793
|
+
if (slashResult === true) {
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
if (typeof slashResult === "string") {
|
|
797
|
+
// Command handled but returned remaining text to use as prompt
|
|
798
|
+
text = slashResult;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// Handle skill commands (/skill:name [args]). While streaming, Enter
|
|
802
|
+
// honors `busyPromptMode`: "steer" interrupts the active turn, "queue"
|
|
803
|
+
// runs after it completes (matches the free-text Enter semantics applied
|
|
804
|
+
// a few lines below at the streaming branch). Explicit queue shortcuts
|
|
805
|
+
// route through `handleFollowUp` and dispatch as `followUp`.
|
|
806
|
+
if (await this.#invokeSkillCommand(text, this.#busyStreamingBehavior(), composer)) {
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// Handle bash command (! for normal, !! for excluded from context)
|
|
811
|
+
if (text.startsWith("!")) {
|
|
812
|
+
const isExcluded = text.startsWith("!!");
|
|
813
|
+
const command = isExcluded ? text.slice(2).trim() : text.slice(1).trim();
|
|
814
|
+
if (command) {
|
|
815
|
+
if (this.ctx.session.isBashRunning) {
|
|
816
|
+
this.ctx.showWarning("A bash command is already running. Press Esc to cancel it first.");
|
|
817
|
+
if (this.#canModifyComposer(composer)) {
|
|
486
818
|
this.ctx.editor.setText(text);
|
|
487
|
-
return;
|
|
488
819
|
}
|
|
489
|
-
this.ctx.editor.addToHistory(text);
|
|
490
|
-
await this.ctx.handleBashCommand(command, isExcluded);
|
|
491
|
-
this.ctx.isBashMode = false;
|
|
492
|
-
this.ctx.isBashNoContext = false;
|
|
493
|
-
this.ctx.updateEditorBorderColor();
|
|
494
820
|
return;
|
|
495
821
|
}
|
|
822
|
+
if (this.#canModifyComposer(composer)) {
|
|
823
|
+
this.ctx.editor.addToHistory(text);
|
|
824
|
+
}
|
|
825
|
+
await this.ctx.handleBashCommand(command, isExcluded);
|
|
826
|
+
this.ctx.isBashMode = false;
|
|
827
|
+
this.ctx.isBashNoContext = false;
|
|
828
|
+
this.ctx.updateEditorBorderColor();
|
|
829
|
+
return;
|
|
496
830
|
}
|
|
831
|
+
}
|
|
497
832
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
833
|
+
// Handle python command ($ for normal, $$ for excluded from context)
|
|
834
|
+
if (text.startsWith("$")) {
|
|
835
|
+
const isExcluded = text.startsWith("$$");
|
|
836
|
+
const code = isExcluded ? text.slice(2).trim() : text.slice(1).trim();
|
|
837
|
+
if (code) {
|
|
838
|
+
if (this.ctx.session.isEvalRunning) {
|
|
839
|
+
this.ctx.showWarning("A Python execution is already running. Press Esc to cancel it first.");
|
|
840
|
+
if (this.#canModifyComposer(composer)) {
|
|
505
841
|
this.ctx.editor.setText(text);
|
|
506
|
-
return;
|
|
507
842
|
}
|
|
508
|
-
this.ctx.editor.addToHistory(text);
|
|
509
|
-
await this.ctx.handlePythonCommand(code, isExcluded);
|
|
510
|
-
this.ctx.isPythonMode = false;
|
|
511
|
-
this.ctx.updateEditorBorderColor();
|
|
512
843
|
return;
|
|
513
844
|
}
|
|
845
|
+
if (this.#canModifyComposer(composer)) {
|
|
846
|
+
this.ctx.editor.addToHistory(text);
|
|
847
|
+
}
|
|
848
|
+
await this.ctx.handlePythonCommand(code, isExcluded);
|
|
849
|
+
this.ctx.isPythonMode = false;
|
|
850
|
+
this.ctx.updateEditorBorderColor();
|
|
851
|
+
return;
|
|
514
852
|
}
|
|
853
|
+
}
|
|
515
854
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
return;
|
|
521
|
-
}
|
|
522
|
-
this.ctx.queueCompactionMessage(text, "steer");
|
|
855
|
+
// Queue input during compaction
|
|
856
|
+
if (this.ctx.session.isCompacting) {
|
|
857
|
+
if ((inputImages?.length ?? 0) > 0) {
|
|
858
|
+
this.ctx.showStatus("Compaction in progress. Retry after it completes to send images.");
|
|
523
859
|
return;
|
|
524
860
|
}
|
|
861
|
+
this.ctx.queueCompactionMessage(text, "steer", composer);
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
525
864
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
865
|
+
// If streaming, use prompt() with the busy-prompt behavior the user
|
|
866
|
+
// selected: "steer" interrupts the active turn, "queue" defers the
|
|
867
|
+
// prompt to run after the active turn completes (in submission order).
|
|
868
|
+
// This handles extension commands (execute immediately), prompt template expansion, and queueing
|
|
869
|
+
if (this.ctx.session.isStreaming) {
|
|
870
|
+
if (this.#canModifyComposer(composer)) {
|
|
531
871
|
this.ctx.editor.addToHistory(text);
|
|
532
872
|
this.ctx.editor.setText("");
|
|
533
|
-
const images = inputImages && inputImages.length > 0 ? [...inputImages] : undefined;
|
|
534
|
-
this.#clearPendingImagesIfOwnedBy(pendingImages);
|
|
535
|
-
// Record the signature so the queued message's eventual delivery
|
|
536
|
-
// (a user-role `message_start` event) leaves any draft the user has
|
|
537
|
-
// typed since queuing intact. Same protection as #783, applied to
|
|
538
|
-
// the streaming/queue path.
|
|
539
|
-
const streamingBehavior = this.#busyStreamingBehavior();
|
|
540
|
-
const promptOptions =
|
|
541
|
-
streamingBehavior === "followUp"
|
|
542
|
-
? { streamingBehavior, images, followUpQueuePolicy: "sequential" as const }
|
|
543
|
-
: { streamingBehavior, images };
|
|
544
|
-
await this.ctx.withLocalSubmission(text, () => this.ctx.session.prompt(text, promptOptions), {
|
|
545
|
-
imageCount: images?.length ?? 0,
|
|
546
|
-
});
|
|
547
|
-
this.ctx.updatePendingMessagesDisplay();
|
|
548
|
-
this.ctx.ui.requestRender();
|
|
549
|
-
return;
|
|
550
873
|
}
|
|
874
|
+
const images = inputImages && inputImages.length > 0 ? [...inputImages] : undefined;
|
|
875
|
+
this.#clearPendingImagesIfOwnedBy(pendingImages, composer);
|
|
876
|
+
// Record the signature so the queued message's eventual delivery
|
|
877
|
+
// (a user-role `message_start` event) leaves any draft the user has
|
|
878
|
+
// typed since queuing intact. Same protection as #783, applied to
|
|
879
|
+
// the streaming/queue path.
|
|
880
|
+
const streamingBehavior = this.#busyStreamingBehavior();
|
|
881
|
+
const promptOptions =
|
|
882
|
+
streamingBehavior === "followUp"
|
|
883
|
+
? { streamingBehavior, images, followUpQueuePolicy: "sequential" as const }
|
|
884
|
+
: { streamingBehavior, images };
|
|
885
|
+
await this.ctx.withLocalSubmission(text, () => this.ctx.session.prompt(text, promptOptions), {
|
|
886
|
+
imageCount: images?.length ?? 0,
|
|
887
|
+
});
|
|
888
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
889
|
+
this.ctx.ui.requestRender();
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
551
892
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
}
|
|
893
|
+
// Normal message submission
|
|
894
|
+
// First, move any pending bash components to chat
|
|
895
|
+
this.ctx.flushPendingBashComponents();
|
|
896
|
+
|
|
897
|
+
// Generate session title on first message
|
|
898
|
+
const hasUserMessages = this.ctx.session.messages.some((m: AgentMessage) => m.role === "user");
|
|
899
|
+
if (!hasUserMessages && !this.ctx.sessionManager.getSessionName() && !$env.PI_NO_TITLE) {
|
|
900
|
+
const registry = this.ctx.session.modelRegistry;
|
|
901
|
+
generateSessionTitle(
|
|
902
|
+
text,
|
|
903
|
+
registry,
|
|
904
|
+
this.ctx.settings,
|
|
905
|
+
this.ctx.session.sessionId,
|
|
906
|
+
this.ctx.session.model,
|
|
907
|
+
provider => this.ctx.session.agent.metadataForProvider(provider),
|
|
908
|
+
)
|
|
909
|
+
.then(async title => {
|
|
910
|
+
if (title) {
|
|
911
|
+
const applied = await this.ctx.sessionManager.setSessionName(title, "auto");
|
|
912
|
+
if (applied) {
|
|
913
|
+
setSessionTerminalTitle(
|
|
914
|
+
this.ctx.sessionManager.getSessionName()!,
|
|
915
|
+
this.ctx.sessionManager.getCwd(),
|
|
916
|
+
);
|
|
917
|
+
this.ctx.updateEditorBorderColor();
|
|
578
918
|
}
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
|
|
919
|
+
}
|
|
920
|
+
})
|
|
921
|
+
.catch(() => {});
|
|
922
|
+
}
|
|
582
923
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
924
|
+
if (this.ctx.onInputCallback) {
|
|
925
|
+
// Include any pending images from clipboard paste
|
|
926
|
+
const images = inputImages && inputImages.length > 0 ? [...inputImages] : undefined;
|
|
927
|
+
this.#clearPendingImagesIfOwnedBy(pendingImages, composer);
|
|
587
928
|
|
|
588
|
-
|
|
589
|
-
|
|
929
|
+
// Render user message immediately, then let session events catch up
|
|
930
|
+
const submission = this.ctx.startPendingSubmission({ text, images }, composer);
|
|
590
931
|
|
|
591
|
-
|
|
592
|
-
|
|
932
|
+
this.ctx.onInputCallback(submission);
|
|
933
|
+
}
|
|
934
|
+
if (this.#canModifyComposer(composer)) {
|
|
593
935
|
this.ctx.editor.addToHistory(text);
|
|
594
|
-
}
|
|
936
|
+
}
|
|
595
937
|
}
|
|
596
938
|
|
|
597
939
|
handleCtrlC(): void {
|
|
@@ -625,6 +967,106 @@ export class InputController {
|
|
|
625
967
|
process.kill(0, "SIGTSTP");
|
|
626
968
|
}
|
|
627
969
|
|
|
970
|
+
#queuePaneOverlay: ReturnType<typeof this.ctx.ui.showOverlay> | undefined;
|
|
971
|
+
|
|
972
|
+
toggleQueuePane(): void {
|
|
973
|
+
if (this.#queuePaneOverlay) {
|
|
974
|
+
this.#queuePaneOverlay.hide();
|
|
975
|
+
this.#queuePaneOverlay = undefined;
|
|
976
|
+
this.ctx.ui.setFocus(this.ctx.editor);
|
|
977
|
+
this.ctx.ui.requestRender(true);
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
this.#showQueuePane();
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
#showQueuePane(selectedIndex = 0): void {
|
|
984
|
+
const entries = this.ctx.session.getQueuedMessageEntries();
|
|
985
|
+
if (entries.length === 0) {
|
|
986
|
+
this.ctx.showStatus("No queued messages");
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
const close = () => {
|
|
990
|
+
this.#queuePaneOverlay?.hide();
|
|
991
|
+
this.#queuePaneOverlay = undefined;
|
|
992
|
+
this.ctx.ui.setFocus(this.ctx.editor);
|
|
993
|
+
this.ctx.ui.requestRender(true);
|
|
994
|
+
};
|
|
995
|
+
const refresh = (nextIndex: number) => {
|
|
996
|
+
this.#queuePaneOverlay?.hide();
|
|
997
|
+
this.#queuePaneOverlay = undefined;
|
|
998
|
+
this.#showQueuePane(nextIndex);
|
|
999
|
+
};
|
|
1000
|
+
const pane = new QueuePaneComponent(entries, {
|
|
1001
|
+
selectedIndex,
|
|
1002
|
+
onDelete: (entry, index) => {
|
|
1003
|
+
const deleted = this.ctx.session.removeQueuedMessageForEditing(entry.id) !== undefined;
|
|
1004
|
+
const remaining = this.ctx.session.getQueuedMessageEntries();
|
|
1005
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
1006
|
+
if (remaining.length === 0) {
|
|
1007
|
+
close();
|
|
1008
|
+
this.ctx.showStatus(deleted ? "Deleted queued message" : "Queued message is no longer available");
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
this.ctx.showStatus(deleted ? "Deleted queued message" : "Queued message is no longer available");
|
|
1012
|
+
refresh(Math.min(index, remaining.length - 1));
|
|
1013
|
+
},
|
|
1014
|
+
onMove: (entry, index, direction) => {
|
|
1015
|
+
const moved = this.ctx.session.moveQueuedMessageForEditing(entry.id, direction);
|
|
1016
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
1017
|
+
this.ctx.showStatus(moved ? "Moved queued message" : "Queued message cannot move further");
|
|
1018
|
+
refresh(Math.max(0, Math.min(index + (direction === "up" ? -1 : 1), entries.length - 1)));
|
|
1019
|
+
},
|
|
1020
|
+
onClose: close,
|
|
1021
|
+
});
|
|
1022
|
+
this.#queuePaneOverlay = this.ctx.ui.showOverlay(pane, {
|
|
1023
|
+
anchor: "bottom-center",
|
|
1024
|
+
width: "100%",
|
|
1025
|
+
maxHeight: "100%",
|
|
1026
|
+
margin: 0,
|
|
1027
|
+
});
|
|
1028
|
+
this.ctx.ui.setFocus(pane);
|
|
1029
|
+
this.ctx.ui.requestRender();
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
async sendNow(): Promise<void> {
|
|
1033
|
+
const composerText = this.ctx.editor.getText().trim();
|
|
1034
|
+
let text = composerText;
|
|
1035
|
+
let queuedEntryId: string | undefined;
|
|
1036
|
+
if (!text) {
|
|
1037
|
+
if (this.ctx.session.isCompacting) {
|
|
1038
|
+
this.ctx.showWarning("Cannot send immediately while compaction is in progress");
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
const entry = this.ctx.session.getQueuedMessageEntries()[0];
|
|
1042
|
+
if (!entry) {
|
|
1043
|
+
this.ctx.showStatus("No visible queued message to send");
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
text = entry.text;
|
|
1047
|
+
queuedEntryId = entry.id;
|
|
1048
|
+
}
|
|
1049
|
+
const outcome = await this.ctx.session.cancelAndSubmit(text, { queuedEntryId });
|
|
1050
|
+
if (outcome.kind === "submitted") {
|
|
1051
|
+
if (composerText) this.ctx.clearEditor();
|
|
1052
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
if (outcome.kind === "rolled_back") {
|
|
1056
|
+
this.ctx.showWarning(
|
|
1057
|
+
outcome.outcome.kind === "timeout"
|
|
1058
|
+
? "Send was cancelled after forced recovery; queued messages were restored"
|
|
1059
|
+
: "Send failed; queued messages were restored",
|
|
1060
|
+
);
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1063
|
+
if (outcome.reason === "compaction") {
|
|
1064
|
+
this.ctx.showWarning("Cannot send immediately while compaction is in progress");
|
|
1065
|
+
} else {
|
|
1066
|
+
this.ctx.showStatus("Send already in progress");
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
|
|
628
1070
|
handleDequeue(): void {
|
|
629
1071
|
const entries = this.#getEditableQueuedMessages();
|
|
630
1072
|
if (entries.length === 0) {
|
|
@@ -815,15 +1257,6 @@ export class InputController {
|
|
|
815
1257
|
return this.ctx.settings.get("busyPromptMode") === "steer" ? "steer" : "followUp";
|
|
816
1258
|
}
|
|
817
1259
|
|
|
818
|
-
#isFollowUpShortcutActive(): boolean {
|
|
819
|
-
return (
|
|
820
|
-
this.ctx.session.isStreaming ||
|
|
821
|
-
this.ctx.session.isCompacting ||
|
|
822
|
-
this.ctx.session.isBashRunning ||
|
|
823
|
-
this.ctx.session.isEvalRunning
|
|
824
|
-
);
|
|
825
|
-
}
|
|
826
|
-
|
|
827
1260
|
/**
|
|
828
1261
|
* Dispatch skill slash invocation(s) (`/skill:<name>`) through custom messages
|
|
829
1262
|
* using the supplied `streamingBehavior`. Returns true if the text contains a
|
|
@@ -837,11 +1270,17 @@ export class InputController {
|
|
|
837
1270
|
* while the agent is streaming; the idle path of `promptCustomMessage`
|
|
838
1271
|
* ignores it.
|
|
839
1272
|
*/
|
|
840
|
-
async #invokeSkillCommand(
|
|
1273
|
+
async #invokeSkillCommand(
|
|
1274
|
+
text: string,
|
|
1275
|
+
streamingBehavior: "steer" | "followUp",
|
|
1276
|
+
options?: ComposerSubmissionOptions,
|
|
1277
|
+
): Promise<boolean> {
|
|
841
1278
|
const invocations = parseSkillInvocations(text, this.ctx.skillCommands ?? new Map());
|
|
842
1279
|
if (invocations.length === 0) return false;
|
|
843
|
-
this
|
|
844
|
-
|
|
1280
|
+
if (!options || this.#canModifyComposer(options)) {
|
|
1281
|
+
this.ctx.editor.addToHistory(text);
|
|
1282
|
+
this.ctx.editor.setText("");
|
|
1283
|
+
}
|
|
845
1284
|
try {
|
|
846
1285
|
for (let index = 0; index < invocations.length; index += 1) {
|
|
847
1286
|
const invocation = invocations[index];
|
|
@@ -1065,7 +1504,7 @@ export class InputController {
|
|
|
1065
1504
|
}
|
|
1066
1505
|
|
|
1067
1506
|
const now = Date.now();
|
|
1068
|
-
if (now - this.#lastBackgroundFoldKeyTime >
|
|
1507
|
+
if (now - this.#lastBackgroundFoldKeyTime > BACKGROUND_FOLD_DOUBLE_PRESS_MS) {
|
|
1069
1508
|
this.#lastBackgroundFoldKeyTime = now;
|
|
1070
1509
|
this.ctx.showStatus("Press Ctrl+B again to fold supported foreground bash into a background job");
|
|
1071
1510
|
return true;
|
|
@@ -1155,8 +1594,15 @@ export class InputController {
|
|
|
1155
1594
|
return images.length > 0 ? images : undefined;
|
|
1156
1595
|
}
|
|
1157
1596
|
|
|
1158
|
-
#
|
|
1159
|
-
|
|
1597
|
+
#canModifyComposer(options: ComposerSubmissionOptions): boolean {
|
|
1598
|
+
return canApplyComposerSubmission(options, this.ctx.editor);
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
#clearPendingImagesIfOwnedBy(
|
|
1602
|
+
pendingImages: InteractiveModeContext["pendingImages"],
|
|
1603
|
+
options: ComposerSubmissionOptions,
|
|
1604
|
+
): void {
|
|
1605
|
+
if (this.#canModifyComposer(options) && this.ctx.pendingImages === pendingImages) {
|
|
1160
1606
|
this.ctx.pendingImages = [];
|
|
1161
1607
|
}
|
|
1162
1608
|
}
|
|
@@ -1235,6 +1681,7 @@ export class InputController {
|
|
|
1235
1681
|
}
|
|
1236
1682
|
|
|
1237
1683
|
createAutocompleteProvider(commands: SlashCommand[], basePath: string): AutocompleteProvider {
|
|
1684
|
+
this.#slashCommands = commands;
|
|
1238
1685
|
return createPromptActionAutocompleteProvider({
|
|
1239
1686
|
commands,
|
|
1240
1687
|
basePath,
|
|
@@ -1288,12 +1735,90 @@ export class InputController {
|
|
|
1288
1735
|
}
|
|
1289
1736
|
}
|
|
1290
1737
|
|
|
1738
|
+
async #dispatchPaletteSlashCommand(name: string, restoreComposer: () => void): Promise<void> {
|
|
1739
|
+
if (this.#paletteCommandInFlight) {
|
|
1740
|
+
this.ctx.showStatus("A palette command is still running.");
|
|
1741
|
+
return;
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
if (this.ctx.editor.getText() || this.ctx.pendingImages.length > 0) {
|
|
1745
|
+
this.ctx.showStatus("Send or clear the draft before running a palette command.");
|
|
1746
|
+
return;
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
restoreComposer();
|
|
1750
|
+
this.#paletteCommandInFlight = true;
|
|
1751
|
+
try {
|
|
1752
|
+
await this.submitText(`/${name}`, { ownsComposer: false, editor: this.ctx.editor });
|
|
1753
|
+
} finally {
|
|
1754
|
+
this.#paletteCommandInFlight = false;
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1291
1758
|
openCommandPalette(): void {
|
|
1292
|
-
if (this.ctx.
|
|
1293
|
-
|
|
1759
|
+
if (this.ctx.isTranscriptViewerOpen?.()) return;
|
|
1760
|
+
if (this.#paletteCommandInFlight) {
|
|
1761
|
+
this.ctx.showStatus("A palette command is still running.");
|
|
1762
|
+
return;
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
const actions = [...this.#commandPaletteActions.values()];
|
|
1766
|
+
const slashCommands = [...(this.ctx.getSlashCommands?.() ?? this.#slashCommands)];
|
|
1767
|
+
if (!this.ctx.showCommandPalette) {
|
|
1768
|
+
let overlayHandle: ReturnType<typeof this.ctx.ui.showOverlay> | undefined;
|
|
1769
|
+
const close = () => {
|
|
1770
|
+
overlayHandle?.hide();
|
|
1771
|
+
this.ctx.ui.setFocus(this.ctx.editor);
|
|
1772
|
+
this.ctx.ui.requestRender(true);
|
|
1773
|
+
};
|
|
1774
|
+
const entries: CommandPaletteEntry[] = [
|
|
1775
|
+
...this.actionRegistry
|
|
1776
|
+
.all()
|
|
1777
|
+
.filter(action => this.actionRegistry.isAvailable(action.id))
|
|
1778
|
+
.map(action => ({
|
|
1779
|
+
id: `action:${action.id}`,
|
|
1780
|
+
label: action.title,
|
|
1781
|
+
description: action.category,
|
|
1782
|
+
keybinding: action.bindingId
|
|
1783
|
+
? this.ctx.keybindings.getKeys(action.bindingId).join(", ") || undefined
|
|
1784
|
+
: undefined,
|
|
1785
|
+
handler: () => this.actionRegistry.execute(action.id),
|
|
1786
|
+
})),
|
|
1787
|
+
...slashCommands.map(command => ({
|
|
1788
|
+
id: `slash:/${command.name}`,
|
|
1789
|
+
label: `/${command.name}`,
|
|
1790
|
+
description: command.description,
|
|
1791
|
+
})),
|
|
1792
|
+
];
|
|
1793
|
+
const palette = new CommandPalette(
|
|
1794
|
+
entries,
|
|
1795
|
+
entry => {
|
|
1796
|
+
if (entry.handler) {
|
|
1797
|
+
close();
|
|
1798
|
+
void entry.handler();
|
|
1799
|
+
} else {
|
|
1800
|
+
const command = entry.id.slice("slash:/".length);
|
|
1801
|
+
void this.#dispatchPaletteSlashCommand(command, close);
|
|
1802
|
+
}
|
|
1803
|
+
},
|
|
1804
|
+
close,
|
|
1805
|
+
);
|
|
1806
|
+
overlayHandle = this.ctx.ui.showOverlay(palette, {
|
|
1807
|
+
anchor: "bottom-center",
|
|
1808
|
+
width: "100%",
|
|
1809
|
+
maxHeight: "100%",
|
|
1810
|
+
margin: 0,
|
|
1811
|
+
});
|
|
1812
|
+
this.ctx.ui.setFocus(palette);
|
|
1813
|
+
this.ctx.ui.requestRender();
|
|
1294
1814
|
return;
|
|
1295
1815
|
}
|
|
1296
|
-
this.ctx.
|
|
1816
|
+
this.ctx.showCommandPalette(slashCommands, actions, name =>
|
|
1817
|
+
this.#dispatchPaletteSlashCommand(name, () => {
|
|
1818
|
+
this.ctx.ui.setFocus?.(this.ctx.editor);
|
|
1819
|
+
this.ctx.ui.requestRender(true);
|
|
1820
|
+
}),
|
|
1821
|
+
);
|
|
1297
1822
|
}
|
|
1298
1823
|
|
|
1299
1824
|
cycleThinkingLevel(): void {
|
|
@@ -1364,7 +1889,17 @@ export class InputController {
|
|
|
1364
1889
|
this.ctx.toolOutputExpanded = expanded;
|
|
1365
1890
|
for (const child of this.ctx.chatContainer.children) {
|
|
1366
1891
|
if (isExpandable(child)) {
|
|
1367
|
-
|
|
1892
|
+
// Callable guard: setManuallyExpanded is optional on the exported
|
|
1893
|
+
// contract, and hostile/legacy children may expose it as a
|
|
1894
|
+
// non-function property or an unstable getter. Read once, then
|
|
1895
|
+
// invoke only a real function; everything else falls back to the
|
|
1896
|
+
// automatic path.
|
|
1897
|
+
const setManuallyExpanded = child.setManuallyExpanded;
|
|
1898
|
+
if (typeof setManuallyExpanded === "function") {
|
|
1899
|
+
setManuallyExpanded.call(child, expanded);
|
|
1900
|
+
} else {
|
|
1901
|
+
child.setExpanded(expanded);
|
|
1902
|
+
}
|
|
1368
1903
|
}
|
|
1369
1904
|
}
|
|
1370
1905
|
this.ctx.ui.requestRender();
|