@gajae-code/coding-agent 0.14.2 → 0.15.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 +120 -1
- package/dist/types/async/job-manager.d.ts +4 -1
- package/dist/types/autoresearch/dashboard.d.ts +29 -4
- package/dist/types/autoresearch/data-context.d.ts +25 -0
- package/dist/types/autoresearch/git.d.ts +65 -7
- package/dist/types/autoresearch/harness.d.ts +58 -0
- package/dist/types/autoresearch/prompts.d.ts +51 -0
- package/dist/types/autoresearch/runs.d.ts +173 -0
- package/dist/types/autoresearch/types.d.ts +9 -139
- package/dist/types/capability/types.d.ts +20 -0
- package/dist/types/cli/accounts-cli.d.ts +1 -0
- package/dist/types/commands/{rlm.d.ts → autoresearch.d.ts} +2 -4
- package/dist/types/config/autorouting-contract.d.ts +146 -0
- package/dist/types/config/autorouting-generator.d.ts +40 -0
- package/dist/types/config/autorouting-tier-map.d.ts +16844 -0
- package/dist/types/config/autorouting.d.ts +35 -0
- package/dist/types/config/model-discovery-manager.d.ts +8 -1
- package/dist/types/config/model-registry.d.ts +30 -1
- package/dist/types/config/provider-selection-policy.d.ts +23 -0
- package/dist/types/config/settings-schema.d.ts +61 -3
- package/dist/types/config/settings.d.ts +3 -0
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/coordinator-mcp/durability.d.ts +29 -0
- package/dist/types/coordinator-mcp/event-webhook.d.ts +50 -0
- package/dist/types/coordinator-mcp/question-state.d.ts +1 -1
- package/dist/types/coordinator-mcp/server.d.ts +14 -0
- package/dist/types/crash/index-store.d.ts +3 -0
- package/dist/types/crash/record-loader.d.ts +2 -0
- package/dist/types/crash/upstream/relay.d.ts +5 -3
- package/dist/types/cursor.d.ts +12 -0
- package/dist/types/defaults/gjc-defaults.d.ts +35 -1
- package/dist/types/defaults/gjc-skills.generated.d.ts +1 -1
- package/dist/types/discovery/helpers.d.ts +1 -1
- package/dist/types/edit/index.d.ts +2 -1
- package/dist/types/eval/py/executor.d.ts +2 -0
- package/dist/types/extensibility/extensions/loader.d.ts +32 -0
- package/dist/types/extensibility/gjc-plugins/types.d.ts +1 -1
- package/dist/types/extensibility/skills.d.ts +2 -0
- package/dist/types/gjc-runtime/autoresearch-runtime.d.ts +194 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +1 -1
- package/dist/types/gjc-runtime/psmux-detect.d.ts +1 -1
- package/dist/types/gjc-runtime/python-transcript.d.ts +19 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +2 -8
- package/dist/types/gjc-runtime/session-layout.d.ts +6 -3
- package/dist/types/gjc-runtime/state-renderer.d.ts +0 -22
- package/dist/types/gjc-runtime/state-schema.d.ts +7 -7
- package/dist/types/gjc-runtime/tmux-common.d.ts +2 -2
- package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +38 -1
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +2 -24
- package/dist/types/gjc-runtime/ultragoal-validation-policy.d.ts +73 -0
- package/dist/types/gjc-runtime/workflow-recovery-projection.d.ts +100 -0
- package/dist/types/hooks/skill-keywords.d.ts +1 -1
- package/dist/types/main.d.ts +0 -10
- package/dist/types/modes/acp/acp-agent.d.ts +13 -6
- package/dist/types/modes/components/model-selector.d.ts +14 -0
- package/dist/types/modes/components/skill-hud/render.d.ts +6 -1
- package/dist/types/modes/components/smart-routing-panel.d.ts +82 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +14 -0
- package/dist/types/modes/interactive-mode.d.ts +3 -0
- package/dist/types/modes/types.d.ts +9 -0
- package/dist/types/rlm/artifacts.d.ts +4 -4
- package/dist/types/runtime/optional-runtime-services.d.ts +2 -1
- package/dist/types/runtime/workspace-tree-service.d.ts +5 -1
- package/dist/types/runtime-mcp/config.d.ts +6 -0
- package/dist/types/runtime-mcp/manager.d.ts +10 -0
- package/dist/types/sdk/acp/adapter.d.ts +0 -4
- package/dist/types/sdk/broker/broker.d.ts +1 -1
- package/dist/types/sdk/broker/lifecycle.d.ts +57 -2
- package/dist/types/sdk/broker/session-index.d.ts +15 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +6 -0
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +4 -0
- package/dist/types/sdk/bus/index.d.ts +8 -0
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +2 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +12 -2
- package/dist/types/sdk/client/client.d.ts +39 -4
- package/dist/types/sdk/host/host.d.ts +1 -0
- package/dist/types/sdk/host/internal-autorouting-state.d.ts +3 -0
- package/dist/types/sdk/host/model-pin.d.ts +29 -0
- package/dist/types/sdk/host/session-runtime.d.ts +2 -1
- package/dist/types/sdk/lifecycle-session.d.ts +13 -0
- package/dist/types/sdk/router/session-router.d.ts +27 -1
- package/dist/types/sdk/session.d.ts +11 -1
- package/dist/types/session/agent-session.d.ts +36 -6
- package/dist/types/session/artifacts.d.ts +16 -1
- package/dist/types/session/internal/managed-session-storage.d.ts +7 -0
- package/dist/types/session/session-manager.d.ts +84 -2
- package/dist/types/session/session-staging-paths.d.ts +3 -0
- package/dist/types/skill-state/canonical-skills.d.ts +1 -1
- package/dist/types/skill-state/workflow-hud.d.ts +12 -21
- package/dist/types/skill-state/workflow-mutation-guard.d.ts +1 -0
- package/dist/types/slash-commands/helpers/autorouting-status.d.ts +8 -0
- package/dist/types/system-prompt.d.ts +23 -0
- package/dist/types/task/executor.d.ts +35 -4
- package/dist/types/task/index.d.ts +17 -3
- package/dist/types/task/receipt.d.ts +3 -1
- package/dist/types/task/types.d.ts +144 -0
- package/dist/types/tools/atomic-file-write.d.ts +72 -0
- package/dist/types/tools/descriptors.d.ts +1 -0
- package/dist/types/tools/index.d.ts +36 -1
- package/dist/types/tools/move-session.d.ts +40 -0
- package/dist/types/tools/python.d.ts +12 -0
- package/dist/types/tools/resource-gc.d.ts +0 -3
- package/dist/types/tools/search.d.ts +8 -1
- package/dist/types/tools/skill.d.ts +9 -4
- package/dist/types/tools/subagent.d.ts +33 -3
- package/dist/types/utils/xml-escape.d.ts +2 -0
- package/dist/types/workflow/workflow-intent-diff.d.ts +2 -2
- package/dist/types/workflow/workflow-intent-report-consumer.d.ts +1 -1
- package/package.json +11 -12
- package/scripts/build-sdk-package-smoke.ts +7 -5
- package/scripts/check-autorouting-tier-map.ts +183 -0
- package/scripts/generate-sdk-operation-inventory.ts +12 -0
- package/scripts/generate-tool-catalog.ts +40 -5
- package/src/async/job-manager.ts +5 -2
- package/src/autoresearch/dashboard.ts +134 -314
- package/src/autoresearch/data-context.ts +35 -0
- package/src/autoresearch/git.ts +186 -26
- package/src/autoresearch/{helpers.ts → harness.ts} +65 -40
- package/src/autoresearch/{prompt-setup.md → prompts/prompt-setup.md} +7 -7
- package/src/autoresearch/prompts/prompt.md +103 -0
- package/src/autoresearch/prompts.ts +126 -0
- package/src/autoresearch/runs.ts +708 -0
- package/src/autoresearch/types.ts +9 -150
- package/src/capability/index.ts +4 -4
- package/src/capability/types.ts +20 -0
- package/src/cli/accounts-cli.ts +21 -5
- package/src/cli/config-cli.ts +25 -1
- package/src/cli/fast-help.ts +1 -2
- package/src/cli/list-models.ts +5 -8
- package/src/cli/setup-cli.ts +8 -0
- package/src/cli.ts +2 -21
- package/src/commands/autoresearch.ts +33 -0
- package/src/commands/coordinator.ts +15 -0
- package/src/commands/launch.ts +2 -4
- package/src/commands/quick-lane.ts +1 -1
- package/src/commands/sdk.ts +24 -0
- package/src/commands/state.ts +1 -1
- package/src/config/autorouting-contract.ts +474 -0
- package/src/config/autorouting-generator.ts +238 -0
- package/src/config/autorouting-tier-map.ts +6108 -0
- package/src/config/autorouting.ts +120 -0
- package/src/config/model-discovery-manager.ts +42 -11
- package/src/config/model-registry.ts +348 -68
- package/src/config/provider-selection-policy.ts +60 -9
- package/src/config/settings-schema.ts +195 -6
- package/src/config/settings.ts +62 -1
- package/src/config.ts +2 -3
- package/src/coordinator/contract.ts +0 -1
- package/src/coordinator-mcp/codex-handoff.ts +68 -42
- package/src/coordinator-mcp/durability.ts +200 -0
- package/src/coordinator-mcp/event-webhook.ts +278 -0
- package/src/coordinator-mcp/question-state.ts +109 -46
- package/src/coordinator-mcp/server.ts +932 -504
- package/src/crash/index-store.ts +126 -22
- package/src/crash/record-loader.ts +7 -0
- package/src/crash/upstream/relay.ts +82 -34
- package/src/cursor.ts +84 -1
- package/src/debug/index.ts +26 -0
- package/src/defaults/gjc/skills/autoresearch/SKILL.md +116 -0
- package/src/defaults/gjc/skills/autoresearch/auto-critic.md +46 -0
- package/src/defaults/gjc/skills/autoresearch/auto-iterate.md +42 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +17 -37
- package/src/defaults/gjc/skills/ralplan/SKILL.md +21 -18
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +6 -17
- package/src/defaults/gjc/skills/ultragoal/ai-slop-cleaner.md +2 -2
- package/src/defaults/gjc-defaults.ts +89 -2
- package/src/defaults/gjc-skills.generated.ts +24 -17
- package/src/discovery/builtin.ts +18 -5
- package/src/discovery/helpers.ts +6 -6
- package/src/edit/index.ts +5 -2
- package/src/eval/py/executor.ts +8 -0
- package/src/extensibility/extensions/loader.ts +135 -13
- package/src/extensibility/extensions/wrapper.ts +1 -2
- package/src/extensibility/gjc-plugins/schema.ts +1 -1
- package/src/extensibility/runtime-skill-discovery.ts +2 -2
- package/src/extensibility/skill-management.ts +2 -3
- package/src/extensibility/skills.ts +16 -8
- package/src/gjc-runtime/autoresearch-runtime.ts +1509 -0
- package/src/gjc-runtime/gc-render.ts +0 -1
- package/src/gjc-runtime/gc-runtime.ts +1 -11
- package/src/gjc-runtime/launch-tmux.ts +3 -3
- package/src/gjc-runtime/psmux-detect.ts +2 -2
- package/src/gjc-runtime/python-transcript.ts +58 -0
- package/src/gjc-runtime/ralplan-runtime.ts +6 -18
- package/src/gjc-runtime/session-layout.ts +18 -7
- package/src/gjc-runtime/session-state-sidecar.ts +58 -9
- package/src/gjc-runtime/state-renderer.ts +0 -38
- package/src/gjc-runtime/state-runtime.ts +43 -22
- package/src/gjc-runtime/state-schema.ts +1 -1
- package/src/gjc-runtime/tmux-common.ts +7 -7
- package/src/gjc-runtime/ultragoal-change-set.ts +232 -11
- package/src/gjc-runtime/ultragoal-runtime.ts +309 -114
- package/src/gjc-runtime/ultragoal-validation-policy.ts +247 -0
- package/src/gjc-runtime/workflow-command-ref.ts +15 -11
- package/src/gjc-runtime/workflow-manifest.generated.json +413 -532
- package/src/gjc-runtime/workflow-manifest.ts +43 -83
- package/src/gjc-runtime/workflow-recovery-projection.ts +616 -0
- package/src/hooks/skill-keywords.ts +3 -3
- package/src/hooks/skill-state.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +26 -24
- package/src/lsp/index.ts +25 -5
- package/src/main.ts +2 -27
- package/src/modes/acp/acp-agent.ts +200 -75
- package/src/modes/components/model-selector.ts +216 -13
- package/src/modes/components/oauth-selector.ts +5 -5
- package/src/modes/components/skill-hud/render.ts +173 -35
- package/src/modes/components/smart-routing-panel.ts +531 -0
- package/src/modes/components/tool-status-header.ts +6 -3
- package/src/modes/controllers/command-controller.ts +17 -7
- package/src/modes/controllers/input-controller.ts +44 -9
- package/src/modes/controllers/runtime-mcp-command-controller.ts +4 -0
- package/src/modes/controllers/selector-controller.ts +298 -65
- package/src/modes/interactive-mode.ts +8 -1
- package/src/modes/shared/agent-wire/workflow-gate-types.ts +1 -1
- package/src/modes/types.ts +9 -1
- package/src/prompts/agents/planner.md +1 -1
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/prompts/system/volatile-project-context.md +2 -1
- package/src/prompts/tools/move-session.md +15 -0
- package/src/prompts/tools/python.md +12 -0
- package/src/prompts/tools/skill-discovery.md +1 -1
- package/src/prompts/tools/skill.md +2 -2
- package/src/prompts/tools/task-summary.md +1 -0
- package/src/prompts/tools/task.md +6 -0
- package/src/rlm/artifacts.ts +12 -10
- package/src/runtime/optional-runtime-services.ts +3 -2
- package/src/runtime/workspace-tree-service.ts +11 -3
- package/src/runtime-mcp/config.ts +8 -1
- package/src/runtime-mcp/manager.ts +33 -0
- package/src/sdk/acp/adapter.ts +48 -1
- package/src/sdk/broker/broker.ts +30 -2
- package/src/sdk/broker/discovery.ts +217 -2
- package/src/sdk/broker/lifecycle.ts +385 -59
- package/src/sdk/broker/session-index.ts +243 -48
- package/src/sdk/broker/transport.ts +1 -0
- package/src/sdk/bus/chat-daemon-control.ts +8 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +5 -0
- package/src/sdk/bus/index.ts +208 -11
- package/src/sdk/bus/kind-aware-reconciliation.ts +16 -0
- package/src/sdk/bus/reconciliation-store.ts +27 -5
- package/src/sdk/bus/telegram-daemon-contract.ts +12 -2
- package/src/sdk/client/client.ts +204 -22
- package/src/sdk/host/host.ts +16 -0
- package/src/sdk/host/internal-autorouting-state.ts +13 -0
- package/src/sdk/host/model-pin.ts +101 -0
- package/src/sdk/host/reverse-leases.ts +2 -1
- package/src/sdk/host/session-runtime.test.ts +224 -1
- package/src/sdk/host/session-runtime.ts +125 -37
- package/src/sdk/lifecycle/service.ts +3 -1
- package/src/sdk/lifecycle-session.ts +42 -1
- package/src/sdk/protocol/operation-inventory.generated.json +66 -0
- package/src/sdk/router/session-router.ts +375 -36
- package/src/sdk/session.ts +547 -47
- package/src/session/account-inventory.ts +1 -1
- package/src/session/agent-session.ts +765 -189
- package/src/session/artifacts.ts +234 -4
- package/src/session/internal/managed-session-storage.ts +91 -4
- package/src/session/provider-safety-stop-hint.ts +3 -2
- package/src/session/session-manager.ts +775 -16
- package/src/session/session-staging-paths.ts +8 -0
- package/src/setup/hermes/templates/operator-instructions.v1.md +0 -1
- package/src/setup/host-plugin-setup.ts +2 -2
- package/src/skill-state/active-state.ts +2 -2
- package/src/skill-state/canonical-skills.ts +1 -1
- package/src/skill-state/initial-phase.ts +1 -1
- package/src/skill-state/workflow-hud.ts +41 -41
- package/src/skill-state/workflow-mutation-guard.ts +26 -7
- package/src/slash-commands/builtin-registry.ts +77 -0
- package/src/slash-commands/helpers/autorouting-status.ts +55 -0
- package/src/system-prompt.ts +102 -5
- package/src/task/discovery.ts +2 -3
- package/src/task/executor.ts +617 -43
- package/src/task/index.ts +262 -15
- package/src/task/receipt.ts +30 -0
- package/src/task/render.ts +11 -1
- package/src/task/types.ts +316 -0
- package/src/tools/atomic-file-write.ts +594 -0
- package/src/tools/bisect.ts +3 -2
- package/src/tools/descriptors.test.ts +3 -0
- package/src/tools/descriptors.ts +24 -0
- package/src/tools/image-gen.ts +5 -1
- package/src/tools/index.ts +62 -5
- package/src/tools/move-session.ts +107 -0
- package/src/tools/python.ts +205 -0
- package/src/tools/read.ts +88 -15
- package/src/tools/renderers.ts +2 -0
- package/src/tools/resource-gc.ts +8 -177
- package/src/tools/search.ts +30 -6
- package/src/tools/skill-discovery.ts +1 -0
- package/src/tools/skill.ts +37 -7
- package/src/tools/subagent-render.ts +119 -13
- package/src/tools/subagent.ts +91 -74
- package/src/tools/tool-catalog.generated.ts +64 -2
- package/src/tools/write.ts +16 -12
- package/src/utils/xml-escape.ts +14 -0
- package/src/web/search/providers/codex.ts +2 -2
- package/src/workflow/workflow-intent-diff.ts +13 -12
- package/src/workflow/workflow-intent-report-consumer.ts +6 -6
- package/dist/types/autoresearch/helpers.d.ts +0 -24
- package/dist/types/autoresearch/index.d.ts +0 -2
- package/dist/types/autoresearch/state.d.ts +0 -17
- package/dist/types/autoresearch/storage.d.ts +0 -142
- package/dist/types/autoresearch/tools/init-experiment.d.ts +0 -31
- package/dist/types/autoresearch/tools/log-experiment.d.ts +0 -23
- package/dist/types/autoresearch/tools/run-experiment.d.ts +0 -8
- package/dist/types/autoresearch/tools/update-notes.d.ts +0 -12
- package/dist/types/commands/team.d.ts +0 -24
- package/dist/types/gjc-runtime/team-gc.d.ts +0 -7
- package/dist/types/gjc-runtime/team-launch.d.ts +0 -61
- package/dist/types/gjc-runtime/team-notify.d.ts +0 -45
- package/dist/types/gjc-runtime/team-runtime.d.ts +0 -336
- package/dist/types/gjc-runtime/team-store.d.ts +0 -168
- package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +0 -43
- package/dist/types/gjc-runtime/team-worker-memory-guard.d.ts +0 -92
- package/dist/types/gjc-runtime/team-workers.d.ts +0 -83
- package/dist/types/rlm/index.d.ts +0 -47
- package/dist/types/rlm/preset.d.ts +0 -23
- package/dist/types/rlm/python-tool.d.ts +0 -16
- package/src/autoresearch/command-resume.md +0 -14
- package/src/autoresearch/index.ts +0 -536
- package/src/autoresearch/prompt.md +0 -103
- package/src/autoresearch/resume-message.md +0 -10
- package/src/autoresearch/state.ts +0 -273
- package/src/autoresearch/storage.ts +0 -699
- package/src/autoresearch/tools/init-experiment.ts +0 -272
- package/src/autoresearch/tools/log-experiment.ts +0 -524
- package/src/autoresearch/tools/run-experiment.ts +0 -475
- package/src/autoresearch/tools/update-notes.ts +0 -109
- package/src/commands/rlm.ts +0 -19
- package/src/commands/team.ts +0 -238
- package/src/defaults/gjc/skills/deep-interview/auto-research-greenfield.md +0 -42
- package/src/defaults/gjc/skills/team/SKILL.md +0 -467
- package/src/gjc-runtime/team-gc.ts +0 -49
- package/src/gjc-runtime/team-launch.ts +0 -404
- package/src/gjc-runtime/team-notify.ts +0 -326
- package/src/gjc-runtime/team-runtime.ts +0 -5923
- package/src/gjc-runtime/team-store.ts +0 -878
- package/src/gjc-runtime/team-worker-heartbeat.ts +0 -149
- package/src/gjc-runtime/team-worker-memory-guard.ts +0 -363
- package/src/gjc-runtime/team-workers.ts +0 -1123
- package/src/prompts/system/rlm-report-command.md +0 -1
- package/src/prompts/system/rlm-research.md +0 -23
- package/src/rlm/index.ts +0 -365
- package/src/rlm/preset.ts +0 -76
- package/src/rlm/python-tool.ts +0 -68
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
import { Container, getKeybindings, matchesKey, replaceTabs, Spacer, Text, truncateToWidth } from "@gajae-code/tui";
|
|
2
|
+
import { sanitizeDisplayLine } from "@gajae-code/utils";
|
|
3
|
+
import type {
|
|
4
|
+
AutoroutingProvenance,
|
|
5
|
+
AutoroutingProviderOrderHint,
|
|
6
|
+
AutoroutingSetup,
|
|
7
|
+
AutoroutingTier,
|
|
8
|
+
TierMap,
|
|
9
|
+
} from "../../config/autorouting-contract";
|
|
10
|
+
import type { AutoroutingSourceIdentity } from "../../config/autorouting-generator";
|
|
11
|
+
import { theme } from "../theme/theme";
|
|
12
|
+
|
|
13
|
+
/** Longest rendered panel line before truncation. */
|
|
14
|
+
export const MAX_PANEL_LINE_WIDTH = 200;
|
|
15
|
+
|
|
16
|
+
export type SmartRoutingPreview = {
|
|
17
|
+
readonly setup: AutoroutingSetup;
|
|
18
|
+
readonly tiers: TierMap;
|
|
19
|
+
readonly provenance: AutoroutingProvenance;
|
|
20
|
+
readonly sourceIdentity: AutoroutingSourceIdentity;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type SmartRoutingIntent =
|
|
24
|
+
| {
|
|
25
|
+
kind: "apply";
|
|
26
|
+
draft: AutoroutingSetup;
|
|
27
|
+
preview: SmartRoutingPreview;
|
|
28
|
+
confirmHandEdit?: boolean;
|
|
29
|
+
}
|
|
30
|
+
| {
|
|
31
|
+
kind: "refresh";
|
|
32
|
+
confirmHandEdit?: boolean;
|
|
33
|
+
}
|
|
34
|
+
| {
|
|
35
|
+
kind: "clear";
|
|
36
|
+
}
|
|
37
|
+
| {
|
|
38
|
+
kind: "toggle";
|
|
39
|
+
enabled: boolean;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type SmartRoutingPanelMode = "editing" | "confirming" | "committing" | "done" | "error";
|
|
43
|
+
export type SmartRoutingConfirmation = "clear" | "hand-edit" | undefined;
|
|
44
|
+
|
|
45
|
+
export interface SmartRoutingPanelOptions {
|
|
46
|
+
setup: AutoroutingSetup;
|
|
47
|
+
tiers?: TierMap;
|
|
48
|
+
provenance?: AutoroutingProvenance;
|
|
49
|
+
/** Advisory drift between the recorded declaration and current provider priority. */
|
|
50
|
+
providerOrderHint?: AutoroutingProviderOrderHint;
|
|
51
|
+
enabled: boolean;
|
|
52
|
+
readOnly: boolean;
|
|
53
|
+
stale: boolean;
|
|
54
|
+
preview: SmartRoutingPreview;
|
|
55
|
+
generatePreview: (draft: AutoroutingSetup) => SmartRoutingPreview;
|
|
56
|
+
onSelect: (intent: SmartRoutingIntent) => undefined | Promise<SmartRoutingPreview | undefined>;
|
|
57
|
+
onCancel: () => void;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function cloneSetup(setup: AutoroutingSetup): AutoroutingSetup {
|
|
61
|
+
return structuredClone(setup);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function clonePreview(preview: SmartRoutingPreview): SmartRoutingPreview {
|
|
65
|
+
return structuredClone(preview);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function isPrintable(data: string): boolean {
|
|
69
|
+
return data.length === 1 && data >= " " && data !== "\x7f";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isBackspace(data: string): boolean {
|
|
73
|
+
return data === "\x7f" || data === "\b";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Provider names, allowlist entries, generated selectors, and error text all
|
|
78
|
+
* originate in hand-editable config or catalog data, so any of them can carry
|
|
79
|
+
* tabs, control bytes, line breaks, or terminal escape sequences. Any of those
|
|
80
|
+
* can inject extra rows or evade the width cap, so flatten and bound the value
|
|
81
|
+
* before it reaches a renderer.
|
|
82
|
+
*/
|
|
83
|
+
function displaySafe(text: string): string {
|
|
84
|
+
return truncateToWidth(replaceTabs(sanitizeDisplayLine(text)), MAX_PANEL_LINE_WIDTH);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function formatTier(tier: AutoroutingTier, tiers: TierMap | undefined): string {
|
|
88
|
+
const selectors = tiers?.[tier];
|
|
89
|
+
return displaySafe(
|
|
90
|
+
`${tier}: ${selectors && selectors.length > 0 ? selectors.join(", ") : "(empty; manual fallback)"}`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Presentational smart-routing setup editor. It owns only ephemeral draft and
|
|
96
|
+
* preview state; all durable changes are emitted as typed intents.
|
|
97
|
+
*/
|
|
98
|
+
export class SmartRoutingPanelComponent extends Container {
|
|
99
|
+
readonly #onSelect: SmartRoutingPanelOptions["onSelect"];
|
|
100
|
+
readonly #onCancel: () => void;
|
|
101
|
+
readonly #generatePreview: (draft: AutoroutingSetup) => SmartRoutingPreview;
|
|
102
|
+
readonly #readOnly: boolean;
|
|
103
|
+
#draft: AutoroutingSetup;
|
|
104
|
+
|
|
105
|
+
#tiers?: TierMap;
|
|
106
|
+
#provenance?: AutoroutingProvenance;
|
|
107
|
+
#enabled: boolean;
|
|
108
|
+
#stale: boolean;
|
|
109
|
+
#preview: SmartRoutingPreview;
|
|
110
|
+
#mode: SmartRoutingPanelMode = "editing";
|
|
111
|
+
#confirmation: SmartRoutingConfirmation;
|
|
112
|
+
/** Intent that triggered the current hand-edit confirmation, replayed verbatim on confirm. */
|
|
113
|
+
#pendingIntent: SmartRoutingIntent | undefined;
|
|
114
|
+
#providerCursor = 0;
|
|
115
|
+
#allowlistEditing = false;
|
|
116
|
+
#allowlistBuffer = "";
|
|
117
|
+
#providerOrderHint: AutoroutingProviderOrderHint | undefined;
|
|
118
|
+
#status = "";
|
|
119
|
+
#error = "";
|
|
120
|
+
|
|
121
|
+
constructor(options: SmartRoutingPanelOptions) {
|
|
122
|
+
super();
|
|
123
|
+
this.#onSelect = options.onSelect;
|
|
124
|
+
this.#onCancel = options.onCancel;
|
|
125
|
+
this.#generatePreview = options.generatePreview;
|
|
126
|
+
this.#readOnly = options.readOnly;
|
|
127
|
+
this.#draft = cloneSetup(options.setup);
|
|
128
|
+
|
|
129
|
+
this.#tiers = options.tiers ? structuredClone(options.tiers) : undefined;
|
|
130
|
+
this.#provenance = options.provenance ? structuredClone(options.provenance) : undefined;
|
|
131
|
+
this.#enabled = options.enabled;
|
|
132
|
+
this.#providerOrderHint = options.providerOrderHint ? structuredClone(options.providerOrderHint) : undefined;
|
|
133
|
+
this.#stale = options.stale;
|
|
134
|
+
this.#preview = clonePreview(options.preview);
|
|
135
|
+
this.#render();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
get mode(): SmartRoutingPanelMode {
|
|
139
|
+
return this.#mode;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
get confirmation(): SmartRoutingConfirmation {
|
|
143
|
+
return this.#confirmation;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
getDraft(): AutoroutingSetup {
|
|
147
|
+
return cloneSetup(this.#draft);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
getPreviewPayload(): SmartRoutingPreview {
|
|
151
|
+
return clonePreview(this.#preview);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
getProviderOrder(): readonly string[] {
|
|
155
|
+
return [...this.#draft.providers];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Advisory-only, non-destructive hint update.
|
|
160
|
+
*
|
|
161
|
+
* `refreshState` deliberately replaces the draft and clears editor state, so it
|
|
162
|
+
* must never be used for a background settings change: an external
|
|
163
|
+
* `modelProviderOrder` edit would then discard the user's unsaved reordering,
|
|
164
|
+
* allowlist buffer, cursor, and confirmation. This replaces the hint and nothing
|
|
165
|
+
* else.
|
|
166
|
+
*/
|
|
167
|
+
updateProviderOrderHint(hint: AutoroutingProviderOrderHint | undefined): void {
|
|
168
|
+
this.#providerOrderHint = hint ? structuredClone(hint) : undefined;
|
|
169
|
+
this.#render();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Replace the panel's persisted snapshot after a controller mutation. */
|
|
173
|
+
refreshState(options: {
|
|
174
|
+
setup: AutoroutingSetup;
|
|
175
|
+
tiers?: TierMap;
|
|
176
|
+
provenance?: AutoroutingProvenance;
|
|
177
|
+
enabled: boolean;
|
|
178
|
+
providerOrderHint?: AutoroutingProviderOrderHint;
|
|
179
|
+
stale: boolean;
|
|
180
|
+
preview: SmartRoutingPreview;
|
|
181
|
+
}): void {
|
|
182
|
+
this.#draft = cloneSetup(options.setup);
|
|
183
|
+
this.#tiers = options.tiers ? structuredClone(options.tiers) : undefined;
|
|
184
|
+
this.#provenance = options.provenance ? structuredClone(options.provenance) : undefined;
|
|
185
|
+
this.#enabled = options.enabled;
|
|
186
|
+
this.#stale = options.stale;
|
|
187
|
+
this.#providerOrderHint = options.providerOrderHint ? structuredClone(options.providerOrderHint) : undefined;
|
|
188
|
+
this.#preview = clonePreview(options.preview);
|
|
189
|
+
this.#providerCursor = Math.min(this.#providerCursor, Math.max(0, this.#draft.providers.length - 1));
|
|
190
|
+
this.#mode = "editing";
|
|
191
|
+
this.#confirmation = undefined;
|
|
192
|
+
this.#status = "";
|
|
193
|
+
this.#error = "";
|
|
194
|
+
this.#render();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
#render(): void {
|
|
198
|
+
this.detachAll();
|
|
199
|
+
this.addChild(new Text(theme.fg("accent", "Smart routing setup"), 0, 0));
|
|
200
|
+
this.addChild(
|
|
201
|
+
new Text(theme.fg("muted", "Declare providers in priority order; generated chains are deterministic."), 0, 0),
|
|
202
|
+
);
|
|
203
|
+
if (this.#readOnly) {
|
|
204
|
+
this.addChild(
|
|
205
|
+
new Text(
|
|
206
|
+
theme.fg("warning", "Read-only: temporary or --models-scoped sessions cannot write autorouting."),
|
|
207
|
+
0,
|
|
208
|
+
0,
|
|
209
|
+
),
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
if (this.#stale) {
|
|
213
|
+
this.addChild(
|
|
214
|
+
new Text(
|
|
215
|
+
theme.fg("warning", "Stale generated setup: catalog/map or hand-edited tiers differ from provenance."),
|
|
216
|
+
0,
|
|
217
|
+
0,
|
|
218
|
+
),
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
// Advisory only: a changed provider priority is a new suggestion, never proof
|
|
222
|
+
// that the persisted tiers went stale.
|
|
223
|
+
if (this.#providerOrderHint?.reordered) {
|
|
224
|
+
this.addChild(
|
|
225
|
+
new Text(
|
|
226
|
+
theme.fg("muted", "Provider priority changed since this setup was generated. Press r to refresh."),
|
|
227
|
+
0,
|
|
228
|
+
0,
|
|
229
|
+
),
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
if (this.#providerOrderHint && this.#providerOrderHint.missing.length > 0) {
|
|
233
|
+
this.addChild(
|
|
234
|
+
new Text(
|
|
235
|
+
theme.fg(
|
|
236
|
+
"muted",
|
|
237
|
+
displaySafe(
|
|
238
|
+
`Declared providers missing from the catalog: ${this.#providerOrderHint.missing.join(", ")}`,
|
|
239
|
+
),
|
|
240
|
+
),
|
|
241
|
+
0,
|
|
242
|
+
0,
|
|
243
|
+
),
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
if (this.#provenance)
|
|
247
|
+
this.addChild(new Text(theme.fg("dim", "Provenance recorded for this generated setup."), 0, 0));
|
|
248
|
+
this.addChild(new Spacer(1));
|
|
249
|
+
this.addChild(new Text(theme.fg("muted", `Enabled: ${this.#enabled ? "yes" : "no"}`), 0, 0));
|
|
250
|
+
this.addChild(new Text(theme.fg("muted", "Providers (↑/↓ reorder; x removes; m edits allowlist):"), 0, 0));
|
|
251
|
+
for (let index = 0; index < this.#draft.providers.length; index++) {
|
|
252
|
+
const provider = this.#draft.providers[index] ?? "";
|
|
253
|
+
const prefix = index === this.#providerCursor ? theme.fg("accent", `${theme.nav.cursor} `) : " ";
|
|
254
|
+
this.addChild(new Text(`${prefix}${displaySafe(provider)}`, 0, 0));
|
|
255
|
+
}
|
|
256
|
+
if (this.#draft.providers.length === 0)
|
|
257
|
+
this.addChild(new Text(theme.fg("error", " No providers declared."), 0, 0));
|
|
258
|
+
if (this.#allowlistEditing) {
|
|
259
|
+
this.addChild(
|
|
260
|
+
new Text(theme.fg("accent", displaySafe(`Allowlist: ${this.#allowlistBuffer || "(all models)"}`)), 0, 0),
|
|
261
|
+
);
|
|
262
|
+
this.addChild(
|
|
263
|
+
new Text(theme.fg("muted", " Type provider/model values separated by commas, then Enter."), 0, 0),
|
|
264
|
+
);
|
|
265
|
+
} else {
|
|
266
|
+
this.addChild(
|
|
267
|
+
new Text(
|
|
268
|
+
theme.fg(
|
|
269
|
+
"dim",
|
|
270
|
+
displaySafe(
|
|
271
|
+
`Allowlist: ${this.#draft.models && this.#draft.models.length > 0 ? this.#draft.models.join(", ") : "(all labeled models)"}`,
|
|
272
|
+
),
|
|
273
|
+
),
|
|
274
|
+
0,
|
|
275
|
+
0,
|
|
276
|
+
),
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
this.addChild(new Spacer(1));
|
|
280
|
+
this.addChild(new Text(theme.fg("muted", "Preview:"), 0, 0));
|
|
281
|
+
for (const tier of ["fast", "balanced", "strong"] as const) {
|
|
282
|
+
this.addChild(new Text(` ${formatTier(tier, this.#preview.tiers)}`, 0, 0));
|
|
283
|
+
}
|
|
284
|
+
if (this.#status) this.addChild(new Text(theme.fg("success", displaySafe(this.#status)), 0, 0));
|
|
285
|
+
if (this.#error) this.addChild(new Text(theme.fg("error", displaySafe(this.#error)), 0, 0));
|
|
286
|
+
if (this.#confirmation === "clear") {
|
|
287
|
+
this.addChild(
|
|
288
|
+
new Text(
|
|
289
|
+
theme.fg("warning", "Clear generated tiers, setup, and provenance? Enter confirms; Esc cancels."),
|
|
290
|
+
0,
|
|
291
|
+
0,
|
|
292
|
+
),
|
|
293
|
+
);
|
|
294
|
+
} else if (this.#confirmation === "hand-edit") {
|
|
295
|
+
this.addChild(
|
|
296
|
+
new Text(
|
|
297
|
+
theme.fg(
|
|
298
|
+
"warning",
|
|
299
|
+
"Stored tiers were hand-edited. Regenerate and replace them? Enter confirms; Esc cancels.",
|
|
300
|
+
),
|
|
301
|
+
0,
|
|
302
|
+
0,
|
|
303
|
+
),
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
this.addChild(new Spacer(1));
|
|
307
|
+
const actions = this.#readOnly ? "Esc: cancel" : "a: apply · r: refresh · t: toggle · c: clear · Esc: cancel";
|
|
308
|
+
this.addChild(new Text(theme.fg("muted", actions), 0, 0));
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
#recomputePreview(): void {
|
|
312
|
+
try {
|
|
313
|
+
this.#preview = clonePreview(this.#generatePreview(this.#draft));
|
|
314
|
+
this.#error = "";
|
|
315
|
+
} catch (error) {
|
|
316
|
+
this.#error = error instanceof Error ? error.message : String(error);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async #emit(intent: SmartRoutingIntent): Promise<void> {
|
|
321
|
+
if (this.#readOnly || this.#mode === "committing") return;
|
|
322
|
+
this.#mode = "committing";
|
|
323
|
+
this.#status = "Committing smart-routing settings…";
|
|
324
|
+
this.#error = "";
|
|
325
|
+
this.#confirmation = undefined;
|
|
326
|
+
this.#render();
|
|
327
|
+
try {
|
|
328
|
+
const result = await this.#onSelect(intent);
|
|
329
|
+
if (intent.kind === "apply") {
|
|
330
|
+
const appliedPreview = result ?? this.#generatePreview(intent.draft);
|
|
331
|
+
this.#draft = cloneSetup(appliedPreview.setup);
|
|
332
|
+
this.#preview = clonePreview(appliedPreview);
|
|
333
|
+
} else if (intent.kind === "toggle") {
|
|
334
|
+
this.#enabled = intent.enabled;
|
|
335
|
+
}
|
|
336
|
+
this.#mode = "done";
|
|
337
|
+
this.#status = intent.kind === "clear" ? "Generated setup cleared." : "Smart-routing settings saved.";
|
|
338
|
+
} catch (error) {
|
|
339
|
+
const typed = error as { code?: unknown } | undefined;
|
|
340
|
+
if (typed?.code === "autorouting-hand-edited") {
|
|
341
|
+
this.#mode = "confirming";
|
|
342
|
+
this.#confirmation = "hand-edit";
|
|
343
|
+
// Retain the intent that triggered the guard so confirming replays THAT intent. An Apply
|
|
344
|
+
// carrying an edited draft must not be silently downgraded to a Refresh, which would
|
|
345
|
+
// discard the frozen preview and commit the previously recorded setup instead (AC6).
|
|
346
|
+
this.#pendingIntent = intent;
|
|
347
|
+
this.#status = "";
|
|
348
|
+
this.#error = "";
|
|
349
|
+
} else {
|
|
350
|
+
this.#mode = "error";
|
|
351
|
+
this.#error = error instanceof Error ? error.message : String(error);
|
|
352
|
+
this.#status = "";
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
this.#render();
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
#beginClearConfirmation(): void {
|
|
359
|
+
if (this.#readOnly || this.#mode !== "editing") return;
|
|
360
|
+
this.#mode = "confirming";
|
|
361
|
+
this.#confirmation = "clear";
|
|
362
|
+
this.#render();
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
#commitAllowlistBuffer(): void {
|
|
366
|
+
const values = this.#allowlistBuffer
|
|
367
|
+
.split(",")
|
|
368
|
+
.map(value => value.trim())
|
|
369
|
+
.filter(value => value.length > 0);
|
|
370
|
+
this.#draft =
|
|
371
|
+
values.length > 0
|
|
372
|
+
? { ...this.#draft, models: [...new Set(values)] }
|
|
373
|
+
: { schema: 1, providers: [...this.#draft.providers] };
|
|
374
|
+
this.#allowlistEditing = false;
|
|
375
|
+
this.#allowlistBuffer = "";
|
|
376
|
+
this.#recomputePreview();
|
|
377
|
+
this.#render();
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
handleInput(data: string): void {
|
|
381
|
+
if (this.#mode === "committing") return;
|
|
382
|
+
if (this.#mode === "done" || this.#mode === "error") {
|
|
383
|
+
if (getKeybindings().matches(data, "tui.select.cancel")) this.#onCancel();
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
if (this.#confirmation) {
|
|
387
|
+
if (getKeybindings().matches(data, "tui.select.cancel")) {
|
|
388
|
+
this.#confirmation = undefined;
|
|
389
|
+
this.#pendingIntent = undefined;
|
|
390
|
+
this.#mode = "editing";
|
|
391
|
+
this.#render();
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
if (matchesKey(data, "enter") || matchesKey(data, "return") || data === "\n") {
|
|
395
|
+
void this.#emitConfirmed();
|
|
396
|
+
}
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
if (this.#allowlistEditing) {
|
|
400
|
+
if (matchesKey(data, "enter") || matchesKey(data, "return") || data === "\n") {
|
|
401
|
+
this.#commitAllowlistBuffer();
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
if (getKeybindings().matches(data, "tui.select.cancel")) {
|
|
405
|
+
this.#allowlistEditing = false;
|
|
406
|
+
this.#allowlistBuffer = "";
|
|
407
|
+
this.#render();
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
if (isBackspace(data)) {
|
|
411
|
+
this.#allowlistBuffer = this.#allowlistBuffer.slice(0, -1);
|
|
412
|
+
this.#render();
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
if (isPrintable(data)) {
|
|
416
|
+
this.#allowlistBuffer += data;
|
|
417
|
+
this.#render();
|
|
418
|
+
}
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
if (getKeybindings().matches(data, "tui.select.cancel")) {
|
|
422
|
+
this.#onCancel();
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
if (this.#readOnly) return;
|
|
426
|
+
if (data === "m" || data === "M") {
|
|
427
|
+
this.#allowlistEditing = true;
|
|
428
|
+
this.#allowlistBuffer = this.#draft.models?.join(", ") ?? "";
|
|
429
|
+
this.#render();
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
if (data === "x" || data === "X") {
|
|
433
|
+
if (this.#draft.providers.length <= 1) {
|
|
434
|
+
this.#error = "At least one provider must remain declared.";
|
|
435
|
+
this.#render();
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
const providers = this.#draft.providers.filter((_, index) => index !== this.#providerCursor);
|
|
439
|
+
this.#draft = { ...this.#draft, providers };
|
|
440
|
+
this.#providerCursor = Math.min(this.#providerCursor, providers.length - 1);
|
|
441
|
+
this.#recomputePreview();
|
|
442
|
+
this.#render();
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
if (matchesKey(data, "up")) {
|
|
446
|
+
if (this.#providerCursor > 0) {
|
|
447
|
+
const providers = [...this.#draft.providers];
|
|
448
|
+
[providers[this.#providerCursor - 1], providers[this.#providerCursor]] = [
|
|
449
|
+
providers[this.#providerCursor]!,
|
|
450
|
+
providers[this.#providerCursor - 1]!,
|
|
451
|
+
];
|
|
452
|
+
this.#draft = { ...this.#draft, providers };
|
|
453
|
+
this.#providerCursor--;
|
|
454
|
+
this.#recomputePreview();
|
|
455
|
+
this.#render();
|
|
456
|
+
}
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
if (matchesKey(data, "down")) {
|
|
460
|
+
if (this.#providerCursor < this.#draft.providers.length - 1) {
|
|
461
|
+
const providers = [...this.#draft.providers];
|
|
462
|
+
[providers[this.#providerCursor], providers[this.#providerCursor + 1]] = [
|
|
463
|
+
providers[this.#providerCursor + 1]!,
|
|
464
|
+
providers[this.#providerCursor]!,
|
|
465
|
+
];
|
|
466
|
+
this.#draft = { ...this.#draft, providers };
|
|
467
|
+
this.#providerCursor++;
|
|
468
|
+
this.#recomputePreview();
|
|
469
|
+
this.#render();
|
|
470
|
+
}
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
if (data === "a" || data === "A") {
|
|
474
|
+
void this.#emit({ kind: "apply", draft: cloneSetup(this.#draft), preview: clonePreview(this.#preview) });
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
if (data === "r" || data === "R") {
|
|
478
|
+
void this.#emit({ kind: "refresh" });
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
if (data === "t" || data === "T") {
|
|
482
|
+
void this.#emit({ kind: "toggle", enabled: !this.#enabled });
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
if (data === "c" || data === "C") {
|
|
486
|
+
this.#beginClearConfirmation();
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
__testApply(): Promise<void> {
|
|
491
|
+
return this.#emit({ kind: "apply", draft: cloneSetup(this.#draft), preview: clonePreview(this.#preview) });
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/** Test hook mirroring an in-panel provider reorder/edit: mutates the draft and re-previews. */
|
|
495
|
+
__testSetProviders(providers: string[]): void {
|
|
496
|
+
this.#draft = { ...this.#draft, providers: [...providers] };
|
|
497
|
+
this.#preview = this.#generatePreview(this.#draft);
|
|
498
|
+
this.#providerCursor = 0;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
__testRefresh(confirmHandEdit = false): Promise<void> {
|
|
502
|
+
return this.#emit({ kind: "refresh", ...(confirmHandEdit ? { confirmHandEdit: true } : {}) });
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
__testToggle(enabled = !this.#enabled): Promise<void> {
|
|
506
|
+
return this.#emit({ kind: "toggle", enabled });
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
__testClear(): Promise<void> {
|
|
510
|
+
return this.#emit({ kind: "clear" });
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
__testConfirm(): Promise<void> {
|
|
514
|
+
return this.#emitConfirmed();
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Replay the intent the confirmation was raised for. A `clear` confirmation always emits `clear`;
|
|
519
|
+
* a hand-edit confirmation re-emits the ORIGINAL intent with `confirmHandEdit`, so a guarded Apply
|
|
520
|
+
* commits its own edited draft instead of being downgraded to a Refresh of the recorded setup.
|
|
521
|
+
*/
|
|
522
|
+
#emitConfirmed(): Promise<void> {
|
|
523
|
+
if (this.#confirmation === "clear") return this.#emit({ kind: "clear" });
|
|
524
|
+
if (this.#confirmation !== "hand-edit") return Promise.resolve();
|
|
525
|
+
const pending = this.#pendingIntent;
|
|
526
|
+
this.#pendingIntent = undefined;
|
|
527
|
+
if (pending?.kind === "apply")
|
|
528
|
+
return this.#emit({ kind: "apply", draft: pending.draft, preview: pending.preview, confirmHandEdit: true });
|
|
529
|
+
return this.#emit({ kind: "refresh", confirmHandEdit: true });
|
|
530
|
+
}
|
|
531
|
+
}
|
|
@@ -1048,9 +1048,12 @@ export class StatusLineComponent implements Component {
|
|
|
1048
1048
|
render(width: number): string[] {
|
|
1049
1049
|
const lines: string[] = [];
|
|
1050
1050
|
this.#refreshSkillHudInBackground();
|
|
1051
|
-
const
|
|
1052
|
-
|
|
1053
|
-
|
|
1051
|
+
const skillHudRows =
|
|
1052
|
+
this.#settings.showSkillHud === false ? null : renderSkillHudBar(this.#skillHudEntries, width);
|
|
1053
|
+
if (skillHudRows) {
|
|
1054
|
+
for (const skillHudRow of skillHudRows) {
|
|
1055
|
+
if (skillHudRow) lines.push(truncateToWidth(skillHudRow, width));
|
|
1056
|
+
}
|
|
1054
1057
|
}
|
|
1055
1058
|
|
|
1056
1059
|
const statusRows = this.#buildStatusRows(width, this.#resolveMaxRows());
|
|
@@ -188,6 +188,10 @@ export class CommandController {
|
|
|
188
188
|
const customShare = await loadCustomShare();
|
|
189
189
|
if (customShare) {
|
|
190
190
|
const loader = new BorderedLoader(this.ctx.ui, theme, "Sharing...");
|
|
191
|
+
// The composer is reusable across overlays; detach it before clearing so
|
|
192
|
+
// clear() disposes only the transient loader, not the editor's
|
|
193
|
+
// tab-width listener / paste state (disposal is terminal).
|
|
194
|
+
this.ctx.editorContainer.detachChild(this.ctx.editor);
|
|
191
195
|
this.ctx.editorContainer.clear();
|
|
192
196
|
this.ctx.editorContainer.addChild(loader);
|
|
193
197
|
this.ctx.ui.setFocus(loader);
|
|
@@ -244,6 +248,10 @@ export class CommandController {
|
|
|
244
248
|
return;
|
|
245
249
|
}
|
|
246
250
|
|
|
251
|
+
// The composer is reusable across overlays; detach it before clearing so
|
|
252
|
+
// clear() disposes only the transient loader, not the editor's
|
|
253
|
+
// tab-width listener / paste state (disposal is terminal).
|
|
254
|
+
this.ctx.editorContainer.detachChild(this.ctx.editor);
|
|
247
255
|
const loader = new BorderedLoader(this.ctx.ui, theme, "Creating gist...");
|
|
248
256
|
this.ctx.editorContainer.clear();
|
|
249
257
|
this.ctx.editorContainer.addChild(loader);
|
|
@@ -1153,13 +1161,15 @@ export class CommandController {
|
|
|
1153
1161
|
}
|
|
1154
1162
|
|
|
1155
1163
|
try {
|
|
1156
|
-
await this.ctx.sessionManager.
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1164
|
+
await this.ctx.sessionManager.runExclusiveCwdTransition(async () => {
|
|
1165
|
+
await this.ctx.sessionManager.flush();
|
|
1166
|
+
await this.ctx.sessionManager.moveTo(resolvedPath);
|
|
1167
|
+
setProjectDir(resolvedPath);
|
|
1168
|
+
clearClaudePluginRootsCache();
|
|
1169
|
+
resetCapabilities();
|
|
1170
|
+
await this.ctx.refreshSlashCommandState(resolvedPath);
|
|
1171
|
+
await this.ctx.session.refreshSshTool({ activateIfAvailable: true });
|
|
1172
|
+
});
|
|
1163
1173
|
|
|
1164
1174
|
this.ctx.statusLine.invalidate();
|
|
1165
1175
|
this.ctx.updateEditorTopBorder();
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
type ComposerSubmissionOptions,
|
|
16
16
|
canApplyComposerSubmission,
|
|
17
17
|
type InteractiveModeContext,
|
|
18
|
+
stopInteractiveActivityIndicator,
|
|
18
19
|
} from "../../modes/types";
|
|
19
20
|
import type { AgentSessionEvent, QueuedMessageEditEntry } from "../../session/agent-session";
|
|
20
21
|
import { SKILL_PROMPT_MESSAGE_TYPE, type SkillPromptDetails } from "../../session/messages";
|
|
@@ -219,9 +220,13 @@ export class InputController {
|
|
|
219
220
|
case "app.queue.togglePane":
|
|
220
221
|
return true;
|
|
221
222
|
case "app.message.sendNow":
|
|
223
|
+
// `sendNow()` falls back to `getQueuedMessageEntries()[0]`, which returns
|
|
224
|
+
// only visible steering/follow-up entries, so advertising the action for
|
|
225
|
+
// hidden next-turn context enabled a keybinding whose only outcome is
|
|
226
|
+
// "No visible queued message to send".
|
|
222
227
|
return (
|
|
223
228
|
this.ctx.session.isStreaming &&
|
|
224
|
-
(this.ctx.editor.getText().trim().length > 0 || this.ctx.session.
|
|
229
|
+
(this.ctx.editor.getText().trim().length > 0 || this.ctx.session.drainableQueuedMessageCount > 0)
|
|
225
230
|
);
|
|
226
231
|
default:
|
|
227
232
|
return true;
|
|
@@ -356,8 +361,29 @@ export class InputController {
|
|
|
356
361
|
if (this.ctx.cancelPendingSubmission()) {
|
|
357
362
|
return true;
|
|
358
363
|
}
|
|
359
|
-
this.
|
|
360
|
-
|
|
364
|
+
// Only count queues this handler can actually drain. The aggregate
|
|
365
|
+
// `queuedMessageCount` also counts hidden next-turn context, which
|
|
366
|
+
// `restoreQueuedMessagesToEditor()` never returns and which survives turn
|
|
367
|
+
// completion by design, so gating on the aggregate left a permanently
|
|
368
|
+
// nonzero count that made every press a no-op abort — the #4741 lockout.
|
|
369
|
+
const hasCancellableWork =
|
|
370
|
+
this.ctx.hasPendingSubmission() ||
|
|
371
|
+
this.ctx.session.drainableQueuedMessageCount > 0 ||
|
|
372
|
+
(this.ctx.compactionQueuedMessages?.length ?? 0) > 0 ||
|
|
373
|
+
this.ctx.session.isStreaming ||
|
|
374
|
+
this.ctx.session.isCompacting;
|
|
375
|
+
if (hasCancellableWork) {
|
|
376
|
+
this.restoreQueuedMessagesToEditor({ abort: true });
|
|
377
|
+
return true;
|
|
378
|
+
}
|
|
379
|
+
// The loader outlived its turn and no submission is pending (a started
|
|
380
|
+
// submission still inside prompt preflight stays cancellable above, so
|
|
381
|
+
// Esc aborts it instead of letting it start later), so nothing is
|
|
382
|
+
// cancellable even though the busy indicator is still mounted (#4741).
|
|
383
|
+
// Stop the stale indicator so completed work clears the busy state and
|
|
384
|
+
// keep evaluating the remaining states; when none match, the key falls
|
|
385
|
+
// through to idle semantics instead of a no-op abort that swallows it.
|
|
386
|
+
stopInteractiveActivityIndicator(this.ctx);
|
|
361
387
|
}
|
|
362
388
|
if (options.processes && this.ctx.session.isBashRunning) {
|
|
363
389
|
this.ctx.session.abortBash();
|
|
@@ -440,7 +466,7 @@ export class InputController {
|
|
|
440
466
|
if (isClearKey && !isInterruptKey) {
|
|
441
467
|
if (this.#handlePendingSteerInterrupt()) return { consume: true };
|
|
442
468
|
if (
|
|
443
|
-
|
|
469
|
+
this.#handleCancellableWorkEscape({
|
|
444
470
|
loading: true,
|
|
445
471
|
processes: true,
|
|
446
472
|
modes: true,
|
|
@@ -449,10 +475,12 @@ export class InputController {
|
|
|
449
475
|
streaming: true,
|
|
450
476
|
})
|
|
451
477
|
) {
|
|
452
|
-
|
|
478
|
+
this.#resetEscapeGestures();
|
|
479
|
+
return { consume: true };
|
|
453
480
|
}
|
|
454
|
-
|
|
455
|
-
|
|
481
|
+
// A stale loader was stopped and nothing else consumed the clear key:
|
|
482
|
+
// release it to the editor so the idle clear path still runs (#4741).
|
|
483
|
+
return undefined;
|
|
456
484
|
}
|
|
457
485
|
if (this.ctx.hasActiveBtw() && this.ctx.handleBtwEscape()) {
|
|
458
486
|
this.#resetEscapeGestures();
|
|
@@ -861,8 +889,11 @@ export class InputController {
|
|
|
861
889
|
}
|
|
862
890
|
}
|
|
863
891
|
|
|
864
|
-
// Empty submit while streaming with queued messages: flush queues immediately
|
|
865
|
-
|
|
892
|
+
// Empty submit while streaming with queued messages: flush queues immediately.
|
|
893
|
+
// Only drainable queues justify the abort — aborting for hidden next-turn
|
|
894
|
+
// context would kill the live turn without flushing anything, since that
|
|
895
|
+
// context is not delivered by the resume path this abort relies on.
|
|
896
|
+
if (!text && this.ctx.session.isStreaming && this.ctx.session.drainableQueuedMessageCount > 0) {
|
|
866
897
|
// Abort current stream and let queued messages be processed
|
|
867
898
|
await this.#abortInteractive();
|
|
868
899
|
return;
|
|
@@ -1341,6 +1372,10 @@ export class InputController {
|
|
|
1341
1372
|
),
|
|
1342
1373
|
},
|
|
1343
1374
|
);
|
|
1375
|
+
// The composer is reusable across overlays; detach it before clearing so
|
|
1376
|
+
// clear() disposes only the transient selector, not the editor's
|
|
1377
|
+
// tab-width listener (disposal is terminal).
|
|
1378
|
+
this.ctx.editorContainer.detachChild(this.ctx.editor);
|
|
1344
1379
|
this.ctx.editorContainer.clear();
|
|
1345
1380
|
this.ctx.editorContainer.addChild(selector);
|
|
1346
1381
|
this.ctx.ui.setFocus(selector);
|