@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
|
@@ -14,6 +14,15 @@ import {
|
|
|
14
14
|
truncateToWidth,
|
|
15
15
|
} from "@gajae-code/tui";
|
|
16
16
|
import { sanitizeText } from "@gajae-code/utils";
|
|
17
|
+
import {
|
|
18
|
+
type AutoroutingProviderOrderHint,
|
|
19
|
+
type AutoroutingSetup,
|
|
20
|
+
autoroutingProviderOrderHint,
|
|
21
|
+
evaluateAutoroutingProvenanceState,
|
|
22
|
+
normalizeTierMap,
|
|
23
|
+
validateAutoroutingSetup,
|
|
24
|
+
} from "../../config/autorouting-contract";
|
|
25
|
+
|
|
17
26
|
import { isModelProfileProviderAvailable } from "../../config/model-profile-contract";
|
|
18
27
|
import {
|
|
19
28
|
getModelProfilePresentation,
|
|
@@ -46,6 +55,8 @@ import { formatModelOnboardingInlineHint } from "../../setup/model-onboarding-gu
|
|
|
46
55
|
import { formatClampedModelSelector, getThinkingLevelMetadata, parseThinkingLevel } from "../../thinking";
|
|
47
56
|
import { getTabBarTheme } from "../shared";
|
|
48
57
|
import { DynamicBorder } from "./dynamic-border";
|
|
58
|
+
import type { SmartRoutingIntent, SmartRoutingPreview } from "./smart-routing-panel";
|
|
59
|
+
import { SmartRoutingPanelComponent } from "./smart-routing-panel";
|
|
49
60
|
|
|
50
61
|
function makeInvertedBadge(label: string, color: ThemeColor): string {
|
|
51
62
|
const fgAnsi = theme.getFgAnsi(color);
|
|
@@ -147,6 +158,10 @@ export type ModelSelectorSelection =
|
|
|
147
158
|
| {
|
|
148
159
|
kind: "deleteProfile";
|
|
149
160
|
profileName: string;
|
|
161
|
+
}
|
|
162
|
+
| {
|
|
163
|
+
kind: "smartRouting";
|
|
164
|
+
intent: SmartRoutingIntent;
|
|
150
165
|
};
|
|
151
166
|
|
|
152
167
|
interface PendingThinkingChoice {
|
|
@@ -180,7 +195,7 @@ function createProviderTab(providerId: string): ProviderTabState {
|
|
|
180
195
|
return { id: providerId, label: formatProviderTabLabel(providerId), providerId };
|
|
181
196
|
}
|
|
182
197
|
|
|
183
|
-
type ModelSelectorViewMode = "presets" | "models";
|
|
198
|
+
type ModelSelectorViewMode = "presets" | "models" | "smart-routing";
|
|
184
199
|
|
|
185
200
|
interface PresetGroupRow {
|
|
186
201
|
kind: "group";
|
|
@@ -216,6 +231,10 @@ interface PresetImageRoleRow {
|
|
|
216
231
|
kind: "imageRole";
|
|
217
232
|
}
|
|
218
233
|
|
|
234
|
+
interface PresetSmartRoutingRow {
|
|
235
|
+
kind: "smartRouting";
|
|
236
|
+
}
|
|
237
|
+
|
|
219
238
|
type PresetLandingRow =
|
|
220
239
|
| PresetGroupRow
|
|
221
240
|
| PresetProfileRow
|
|
@@ -223,7 +242,8 @@ type PresetLandingRow =
|
|
|
223
242
|
| PresetCreateUnavailableRow
|
|
224
243
|
| PresetAlreadySavedRow
|
|
225
244
|
| PresetBrowseRow
|
|
226
|
-
| PresetImageRoleRow
|
|
245
|
+
| PresetImageRoleRow
|
|
246
|
+
| PresetSmartRoutingRow;
|
|
227
247
|
|
|
228
248
|
// Stable logical identity for a preset landing row, independent of its current
|
|
229
249
|
// list position. Used to relocate the cursor after the expanded group changes so
|
|
@@ -244,6 +264,8 @@ function presetRowIdentity(row: PresetLandingRow): string {
|
|
|
244
264
|
return `alreadySaved:${row.profile.name}`;
|
|
245
265
|
case "imageRole":
|
|
246
266
|
return "imageRole";
|
|
267
|
+
case "smartRouting":
|
|
268
|
+
return "smartRouting";
|
|
247
269
|
}
|
|
248
270
|
}
|
|
249
271
|
|
|
@@ -376,7 +398,10 @@ export class ModelSelectorComponent extends Container {
|
|
|
376
398
|
#assignmentState: "idle" | "assigning" = "idle";
|
|
377
399
|
#closeAfterAssignment = false;
|
|
378
400
|
#unsubscribeCatalogChanged: () => void = () => {};
|
|
401
|
+
#unsubscribeProviderOrderChanged: () => void = () => {};
|
|
379
402
|
#disposed = false;
|
|
403
|
+
/** Standalone smart-routing entry: cancel closes the selector instead of falling back to the preset landing. */
|
|
404
|
+
#smartRoutingOnly = false;
|
|
380
405
|
|
|
381
406
|
// Preset landing state
|
|
382
407
|
#viewMode: ModelSelectorViewMode = "presets";
|
|
@@ -391,6 +416,8 @@ export class ModelSelectorComponent extends Container {
|
|
|
391
416
|
#presetLoginHint?: string;
|
|
392
417
|
#authSessionId?: string;
|
|
393
418
|
#imageRoleFilter: boolean = false;
|
|
419
|
+
#smartRoutingPanel?: SmartRoutingPanelComponent;
|
|
420
|
+
#smartRoutingPreviewBuilder?: (draft: AutoroutingSetup) => SmartRoutingPreview;
|
|
394
421
|
|
|
395
422
|
// Tab state
|
|
396
423
|
#providers: ProviderTabState[] = STATIC_PROVIDER_TABS;
|
|
@@ -414,6 +441,9 @@ export class ModelSelectorComponent extends Container {
|
|
|
414
441
|
currentThinkingLevel?: ThinkingLevel;
|
|
415
442
|
activeModelProfile?: string;
|
|
416
443
|
configuredDefaultChain?: readonly string[];
|
|
444
|
+
smartRoutingPreview?: (draft: AutoroutingSetup) => SmartRoutingPreview;
|
|
445
|
+
/** Open the smart-routing panel directly instead of the preset landing. */
|
|
446
|
+
smartRoutingOnly?: boolean;
|
|
417
447
|
},
|
|
418
448
|
) {
|
|
419
449
|
super();
|
|
@@ -426,6 +456,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
426
456
|
this.#onCancelCallback = onCancel;
|
|
427
457
|
this.#temporaryOnly = options?.temporaryOnly ?? false;
|
|
428
458
|
this.#authSessionId = options?.sessionId;
|
|
459
|
+
this.#smartRoutingPreviewBuilder = options?.smartRoutingPreview;
|
|
429
460
|
this.#currentModel = _currentModel;
|
|
430
461
|
this.#currentThinkingLevel = options?.currentThinkingLevel;
|
|
431
462
|
this.#activeModelProfile = options?.activeModelProfile;
|
|
@@ -442,7 +473,12 @@ export class ModelSelectorComponent extends Container {
|
|
|
442
473
|
? this.#isFastForProvider(this.#currentModel.provider, modelSupportsServiceTier(this.#currentModel))
|
|
443
474
|
: false);
|
|
444
475
|
const initialSearchInput = options?.initialSearchInput;
|
|
445
|
-
this.#
|
|
476
|
+
this.#smartRoutingOnly = options?.smartRoutingOnly === true;
|
|
477
|
+
this.#viewMode = this.#smartRoutingOnly
|
|
478
|
+
? "smart-routing"
|
|
479
|
+
: this.#temporaryOnly || initialSearchInput || scopedModels.length > 0
|
|
480
|
+
? "models"
|
|
481
|
+
: "presets";
|
|
446
482
|
|
|
447
483
|
// Load current role assignments from settings
|
|
448
484
|
this.#rebuildRoleModels();
|
|
@@ -492,9 +528,25 @@ export class ModelSelectorComponent extends Container {
|
|
|
492
528
|
});
|
|
493
529
|
}
|
|
494
530
|
|
|
531
|
+
// Advisory drift only, and only while the smart-routing panel is mounted. This
|
|
532
|
+
// must not call refreshState: that would discard the user's unsaved draft.
|
|
533
|
+
this.#unsubscribeProviderOrderChanged = this.#settings.onChanged?.(path => {
|
|
534
|
+
if (this.#disposed || path !== "modelProviderOrder") return;
|
|
535
|
+
if (this.#viewMode !== "smart-routing") return;
|
|
536
|
+
const panel = this.#smartRoutingPanel;
|
|
537
|
+
if (!panel) return;
|
|
538
|
+
panel.updateProviderOrderHint(this.#providerOrderHintFor(panel.getProviderOrder()));
|
|
539
|
+
this.#tui.requestRender();
|
|
540
|
+
});
|
|
541
|
+
|
|
495
542
|
// Load models and do initial render
|
|
496
543
|
this.#loadModels().then(() => {
|
|
497
544
|
this.#buildProviderTabs();
|
|
545
|
+
if (this.#smartRoutingOnly) {
|
|
546
|
+
this.#enterSmartRoutingMode();
|
|
547
|
+
this.#tui.requestRender();
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
498
550
|
if (this.#viewMode === "presets" && (this.#modelRegistry.getModelProfiles?.().size ?? 0) === 0) {
|
|
499
551
|
this.#viewMode = "models";
|
|
500
552
|
}
|
|
@@ -521,6 +573,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
521
573
|
if (this.#disposed) return;
|
|
522
574
|
this.#disposed = true;
|
|
523
575
|
this.#unsubscribeCatalogChanged();
|
|
576
|
+
this.#unsubscribeProviderOrderChanged();
|
|
524
577
|
super.dispose();
|
|
525
578
|
}
|
|
526
579
|
|
|
@@ -805,17 +858,15 @@ export class ModelSelectorComponent extends Container {
|
|
|
805
858
|
|
|
806
859
|
#materializeModels(models: ModelItem[]): MaterializedCatalog {
|
|
807
860
|
const candidateModels = models.map(item => item.model);
|
|
808
|
-
const
|
|
861
|
+
const canonicalSelections = this.#modelRegistry.getCanonicalModelSelections({
|
|
809
862
|
availableOnly: this.#scopedModels.length === 0,
|
|
810
863
|
candidates: candidateModels,
|
|
811
864
|
});
|
|
812
865
|
const scopedThinkingBySelector = new Map(models.map(item => [item.selector, item.thinkingLevel]));
|
|
813
|
-
const canonicalModels =
|
|
814
|
-
.map((
|
|
815
|
-
const
|
|
816
|
-
|
|
817
|
-
candidates: candidateModels,
|
|
818
|
-
});
|
|
866
|
+
const canonicalModels = canonicalSelections
|
|
867
|
+
.map((selection): CanonicalModelItem | undefined => {
|
|
868
|
+
const record = selection.record;
|
|
869
|
+
const selectedModel = selection.model;
|
|
819
870
|
if (!selectedModel) return undefined;
|
|
820
871
|
const selectedSelector = `${selectedModel.provider}/${selectedModel.id}`;
|
|
821
872
|
const searchText = [
|
|
@@ -954,7 +1005,10 @@ export class ModelSelectorComponent extends Container {
|
|
|
954
1005
|
|
|
955
1006
|
let refreshError: unknown;
|
|
956
1007
|
try {
|
|
957
|
-
|
|
1008
|
+
// Cache-aware: a fresh discovery cache answers instantly instead of
|
|
1009
|
+
// forcing a provider round-trip (hundreds of ms on remote gateways)
|
|
1010
|
+
// on every provider-tab visit. Stale/missing cache still fetches.
|
|
1011
|
+
await this.#modelRegistry.refreshProvider(providerId, "online-if-uncached");
|
|
958
1012
|
} catch (error) {
|
|
959
1013
|
refreshError = error;
|
|
960
1014
|
}
|
|
@@ -1229,6 +1283,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
1229
1283
|
rows.push({ kind: "createUnavailable", label: "Select a model before creating a custom preset" });
|
|
1230
1284
|
}
|
|
1231
1285
|
rows.push({ kind: "imageRole" });
|
|
1286
|
+
rows.push({ kind: "smartRouting" });
|
|
1232
1287
|
rows.push({ kind: "browse" });
|
|
1233
1288
|
return rows;
|
|
1234
1289
|
}
|
|
@@ -1435,7 +1490,8 @@ export class ModelSelectorComponent extends Container {
|
|
|
1435
1490
|
selected.kind === "create" ||
|
|
1436
1491
|
selected.kind === "createUnavailable" ||
|
|
1437
1492
|
selected.kind === "alreadySaved" ||
|
|
1438
|
-
selected.kind === "imageRole"
|
|
1493
|
+
selected.kind === "imageRole" ||
|
|
1494
|
+
selected.kind === "smartRouting"
|
|
1439
1495
|
)
|
|
1440
1496
|
return;
|
|
1441
1497
|
if (this.#expandedPresetProviderId === selected.groupId) return;
|
|
@@ -1452,7 +1508,8 @@ export class ModelSelectorComponent extends Container {
|
|
|
1452
1508
|
selected.kind === "create" ||
|
|
1453
1509
|
selected.kind === "createUnavailable" ||
|
|
1454
1510
|
selected.kind === "alreadySaved" ||
|
|
1455
|
-
selected.kind === "imageRole"
|
|
1511
|
+
selected.kind === "imageRole" ||
|
|
1512
|
+
selected.kind === "smartRouting"
|
|
1456
1513
|
)
|
|
1457
1514
|
return;
|
|
1458
1515
|
if (this.#expandedPresetProviderId !== selected.groupId) return;
|
|
@@ -1518,6 +1575,128 @@ export class ModelSelectorComponent extends Container {
|
|
|
1518
1575
|
return truncateToWidth(label, ROLE_BINDING_MAX_WIDTH);
|
|
1519
1576
|
}
|
|
1520
1577
|
|
|
1578
|
+
/**
|
|
1579
|
+
* A recorded declaration always wins; otherwise seed from the deterministic
|
|
1580
|
+
* provider priority so the draft reflects the user's configured order instead of
|
|
1581
|
+
* raw catalog iteration. No hardcoded provider fallback: an empty catalog means
|
|
1582
|
+
* there is nothing to generate tiers from, and the caller refuses entry.
|
|
1583
|
+
*/
|
|
1584
|
+
#smartRoutingSetup(): AutoroutingSetup {
|
|
1585
|
+
const stored = this.#settings.get("task.autorouting.setup");
|
|
1586
|
+
if (validateAutoroutingSetup(stored).length === 0 && stored !== undefined) return structuredClone(stored);
|
|
1587
|
+
return { schema: 1, providers: [...this.#modelRegistry.autoroutingProviderOrder()] };
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
/**
|
|
1591
|
+
* Advisory hint input is the panel's *current draft*, not the persisted setup, so
|
|
1592
|
+
* a user mid-reorder sees drift for what they are actually editing.
|
|
1593
|
+
*/
|
|
1594
|
+
#providerOrderHintFor(declared: readonly string[]): AutoroutingProviderOrderHint {
|
|
1595
|
+
return autoroutingProviderOrderHint(declared, this.#modelRegistry.autoroutingProviderOrder());
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
#smartRoutingPreview(setup: AutoroutingSetup): SmartRoutingPreview {
|
|
1599
|
+
if (!this.#smartRoutingPreviewBuilder) {
|
|
1600
|
+
throw new Error("Smart-routing preview is unavailable in this selector context.");
|
|
1601
|
+
}
|
|
1602
|
+
return this.#smartRoutingPreviewBuilder(setup);
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
#smartRoutingIsStale(): boolean {
|
|
1606
|
+
const provenance = this.#settings.get("task.autorouting.provenance");
|
|
1607
|
+
if (!provenance || validateAutoroutingSetup(this.#smartRoutingSetup()).length > 0) return false;
|
|
1608
|
+
try {
|
|
1609
|
+
const preview = this.#smartRoutingPreview(this.#smartRoutingSetup());
|
|
1610
|
+
const state = evaluateAutoroutingProvenanceState(provenance, {
|
|
1611
|
+
catalogFingerprint: preview.sourceIdentity.catalogFingerprint,
|
|
1612
|
+
mapFingerprint: preview.sourceIdentity.mapFingerprint,
|
|
1613
|
+
tiers: this.#settings.get("task.autorouting.tiers") ?? {},
|
|
1614
|
+
});
|
|
1615
|
+
return state.staleMap || state.staleCatalog || state.handEdited;
|
|
1616
|
+
} catch {
|
|
1617
|
+
return true;
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
#smartRoutingReadOnly(): boolean {
|
|
1622
|
+
return this.#temporaryOnly || this.#scopedModels.length > 0 || !this.#settings.canWriteDurableConfig();
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
#enterSmartRoutingMode(): void {
|
|
1626
|
+
if (!this.#smartRoutingPreviewBuilder) {
|
|
1627
|
+
if (this.#smartRoutingOnly) {
|
|
1628
|
+
this.#onCancelCallback();
|
|
1629
|
+
return;
|
|
1630
|
+
}
|
|
1631
|
+
this.#presetLoginHint = "Smart-routing setup is unavailable in this selector context.";
|
|
1632
|
+
this.#renderPresetLanding();
|
|
1633
|
+
return;
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
const setup = this.#smartRoutingSetup();
|
|
1637
|
+
if (setup.providers.length === 0) {
|
|
1638
|
+
// Nothing to generate tiers from; refuse entry instead of seeding a guess.
|
|
1639
|
+
if (this.#smartRoutingOnly) {
|
|
1640
|
+
this.#onCancelCallback();
|
|
1641
|
+
return;
|
|
1642
|
+
}
|
|
1643
|
+
this.#presetLoginHint = "No providers are available to generate routing tiers.";
|
|
1644
|
+
this.#renderPresetLanding();
|
|
1645
|
+
return;
|
|
1646
|
+
}
|
|
1647
|
+
const preview = this.#smartRoutingPreview(setup);
|
|
1648
|
+
const tiers = normalizeTierMap(this.#settings.get("task.autorouting.tiers"));
|
|
1649
|
+
const provenance = this.#settings.get("task.autorouting.provenance");
|
|
1650
|
+
this.#viewMode = "smart-routing";
|
|
1651
|
+
this.#smartRoutingPanel = new SmartRoutingPanelComponent({
|
|
1652
|
+
setup,
|
|
1653
|
+
tiers,
|
|
1654
|
+
provenance,
|
|
1655
|
+
enabled: this.#settings.get("task.autorouting.enabled") === true,
|
|
1656
|
+
providerOrderHint: this.#providerOrderHintFor(setup.providers),
|
|
1657
|
+
readOnly: this.#smartRoutingReadOnly(),
|
|
1658
|
+
stale: this.#smartRoutingIsStale(),
|
|
1659
|
+
preview,
|
|
1660
|
+
generatePreview: draft => this.#smartRoutingPreview(draft),
|
|
1661
|
+
onSelect: async intent => {
|
|
1662
|
+
await this.#onSelectCallback({ kind: "smartRouting", intent });
|
|
1663
|
+
return intent.kind === "apply" ? this.#smartRoutingPreview(intent.draft) : undefined;
|
|
1664
|
+
},
|
|
1665
|
+
onCancel: () => (this.#smartRoutingOnly ? this.#onCancelCallback() : this.#switchToPresetMode()),
|
|
1666
|
+
});
|
|
1667
|
+
this.#headerContainer.clear();
|
|
1668
|
+
this.#headerContainer.addChild(new Text(theme.fg("accent", "Smart routing"), 0, 0));
|
|
1669
|
+
this.#tabBar = null;
|
|
1670
|
+
this.#listContainer.clear();
|
|
1671
|
+
this.#listContainer.addChild(this.#smartRoutingPanel);
|
|
1672
|
+
this.#tui.requestRender();
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
#switchToPresetMode(): void {
|
|
1676
|
+
this.#smartRoutingPanel = undefined;
|
|
1677
|
+
this.#viewMode = "presets";
|
|
1678
|
+
this.#presetCursor = Math.min(this.#presetCursor, Math.max(0, this.#getPresetRows().length - 1));
|
|
1679
|
+
this.#renderPresetLanding();
|
|
1680
|
+
this.#tui.requestRender();
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
refreshSmartRoutingState(): void {
|
|
1684
|
+
const panel = this.#smartRoutingPanel;
|
|
1685
|
+
if (!panel || this.#viewMode !== "smart-routing") return;
|
|
1686
|
+
const setup = this.#smartRoutingSetup();
|
|
1687
|
+
const preview = this.#smartRoutingPreview(setup);
|
|
1688
|
+
panel.refreshState({
|
|
1689
|
+
setup,
|
|
1690
|
+
tiers: normalizeTierMap(this.#settings.get("task.autorouting.tiers")),
|
|
1691
|
+
provenance: this.#settings.get("task.autorouting.provenance"),
|
|
1692
|
+
enabled: this.#settings.get("task.autorouting.enabled") === true,
|
|
1693
|
+
providerOrderHint: this.#providerOrderHintFor(setup.providers),
|
|
1694
|
+
stale: this.#smartRoutingIsStale(),
|
|
1695
|
+
preview,
|
|
1696
|
+
});
|
|
1697
|
+
this.#tui.requestRender();
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1521
1700
|
#renderPresetLanding(): void {
|
|
1522
1701
|
this.#headerContainer.clear();
|
|
1523
1702
|
this.#tabBar = null;
|
|
@@ -1531,6 +1710,12 @@ export class ModelSelectorComponent extends Container {
|
|
|
1531
1710
|
const row = rows[i];
|
|
1532
1711
|
const selected = i === this.#presetCursor;
|
|
1533
1712
|
const prefix = selected ? theme.fg("accent", `${theme.nav.cursor} `) : " ";
|
|
1713
|
+
if (row.kind === "smartRouting") {
|
|
1714
|
+
const stale = this.#smartRoutingIsStale();
|
|
1715
|
+
const label = stale ? "Smart routing (stale)" : "Smart routing setup";
|
|
1716
|
+
this.#listContainer.addChild(new Text(`${prefix}${selected ? theme.fg("accent", label) : label}`, 0, 0));
|
|
1717
|
+
continue;
|
|
1718
|
+
}
|
|
1534
1719
|
if (row.kind === "create") {
|
|
1535
1720
|
const label = "Create custom preset";
|
|
1536
1721
|
this.#listContainer.addChild(new Text(`${prefix}${selected ? theme.fg("accent", label) : label}`, 0, 0));
|
|
@@ -1921,6 +2106,10 @@ export class ModelSelectorComponent extends Container {
|
|
|
1921
2106
|
}
|
|
1922
2107
|
|
|
1923
2108
|
handleInput(keyData: string): void {
|
|
2109
|
+
if (this.#viewMode === "smart-routing") {
|
|
2110
|
+
this.#smartRoutingPanel?.handleInput(keyData);
|
|
2111
|
+
return;
|
|
2112
|
+
}
|
|
1924
2113
|
if (this.#assignmentState === "assigning") {
|
|
1925
2114
|
if (getKeybindings().matches(keyData, "tui.select.cancel")) {
|
|
1926
2115
|
this.#closeAfterAssignment = true;
|
|
@@ -2106,6 +2295,10 @@ export class ModelSelectorComponent extends Container {
|
|
|
2106
2295
|
}
|
|
2107
2296
|
const row = this.#getSelectedPresetRow();
|
|
2108
2297
|
if (!row) return;
|
|
2298
|
+
if (row.kind === "smartRouting") {
|
|
2299
|
+
this.#enterSmartRoutingMode();
|
|
2300
|
+
return;
|
|
2301
|
+
}
|
|
2109
2302
|
if (row.kind === "create") {
|
|
2110
2303
|
this.#onSelectCallback({ kind: "createProfile", profile: this.#buildCustomModelProfileSnapshot() });
|
|
2111
2304
|
return;
|
|
@@ -2431,6 +2624,16 @@ export class ModelSelectorComponent extends Container {
|
|
|
2431
2624
|
const row = this.#getSelectedPresetRow();
|
|
2432
2625
|
return row ? presetRowIdentity(row) : undefined;
|
|
2433
2626
|
}
|
|
2627
|
+
__testGetSmartRoutingPanel(): SmartRoutingPanelComponent | undefined {
|
|
2628
|
+
return this.#smartRoutingPanel;
|
|
2629
|
+
}
|
|
2630
|
+
|
|
2631
|
+
__testViewMode(): ModelSelectorViewMode {
|
|
2632
|
+
return this.#viewMode;
|
|
2633
|
+
}
|
|
2634
|
+
__testOpenSmartRoutingPanel(): void {
|
|
2635
|
+
this.#enterSmartRoutingMode();
|
|
2636
|
+
}
|
|
2434
2637
|
}
|
|
2435
2638
|
|
|
2436
2639
|
function getSelectableThinkingLevels(model: Model): ThinkingLevel[] {
|
|
@@ -146,15 +146,15 @@ export class OAuthSelectorComponent extends Container {
|
|
|
146
146
|
this.#authStorage.hasRuntimeApiKey(provider) ||
|
|
147
147
|
this.#authStorage.hasConfigApiKey(provider) ||
|
|
148
148
|
Boolean(getEnvApiKey(provider));
|
|
149
|
-
const rows =
|
|
149
|
+
const rows = this.#mode === "logout" ? inventory : inventory.filter(row => row.credentialKind === "oauth");
|
|
150
150
|
this.#accountEntries = rows.map(row => ({
|
|
151
151
|
kind: "account" as const,
|
|
152
152
|
row,
|
|
153
153
|
target: targets.get(row.id),
|
|
154
154
|
selectable:
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
155
|
+
this.#mode === "login"
|
|
156
|
+
? row.credentialKind === "oauth" && !apiKeyOverrideActive && !row.disabled
|
|
157
|
+
: targets.has(row.id),
|
|
158
158
|
}));
|
|
159
159
|
if (this.#mode === "login") {
|
|
160
160
|
this.#accountEntries.push(
|
|
@@ -441,8 +441,8 @@ export class OAuthSelectorComponent extends Container {
|
|
|
441
441
|
this.#statusMessage = undefined;
|
|
442
442
|
this.stopValidation();
|
|
443
443
|
if (selected.kind === "account") {
|
|
444
|
-
if (selected.row.credentialKind !== "oauth") return;
|
|
445
444
|
if (this.#mode === "login") {
|
|
445
|
+
if (selected.row.credentialKind !== "oauth") return;
|
|
446
446
|
void this.#onAccountSelect?.({ kind: "id", value: String(selected.row.id) });
|
|
447
447
|
} else if (selected.target) {
|
|
448
448
|
this.#confirmOrArmAccountRemoval([selected.target], "Remove this account?");
|
|
@@ -1,28 +1,24 @@
|
|
|
1
|
+
import { truncateToWidth, visibleWidth } from "@gajae-code/tui";
|
|
1
2
|
import {
|
|
2
3
|
collapsePlanningPipeline,
|
|
3
4
|
type SkillActiveEntry,
|
|
4
5
|
type WorkflowHudChip,
|
|
5
6
|
} from "../../../skill-state/active-state";
|
|
6
7
|
import { workflowReceiptStatus } from "../../../skill-state/workflow-state-contract";
|
|
8
|
+
import { theme } from "../../theme/theme";
|
|
7
9
|
|
|
8
|
-
const ANSI_RESET_FG = "\x1b[39m";
|
|
9
|
-
const ANSI_RESET_BOLD = "\x1b[22m";
|
|
10
|
-
const ANSI_BORDER = "\x1b[90m";
|
|
11
|
-
const ANSI_ACCENT = "\x1b[36m";
|
|
12
|
-
const ANSI_DIM = "\x1b[2m";
|
|
13
|
-
const ANSI_BOLD = "\x1b[1m";
|
|
14
10
|
const ANSI_PATTERN = /\x1b\[[0-9;?]*[ -/]*[@-~]/g;
|
|
11
|
+
const MAX_HUD_ROWS = 2;
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
type WidthTier = "wide" | "medium" | "tight";
|
|
14
|
+
type HudToken = { text: string; mandatory: boolean; startsEntry?: boolean; reserveAfter?: number };
|
|
15
|
+
|
|
16
|
+
function color(role: "border" | "accent" | "dim" | "muted" | "warning" | "error", text: string): string {
|
|
17
|
+
return theme?.fg(role, text) ?? text;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
function
|
|
21
|
-
|
|
22
|
-
if (visibleWidth(text) <= maxWidth) return text;
|
|
23
|
-
const plain = text.replace(ANSI_PATTERN, "");
|
|
24
|
-
if (maxWidth === 1) return "…";
|
|
25
|
-
return `${plain.slice(0, maxWidth - 1)}…`;
|
|
20
|
+
function statusSymbol(kind: "warning" | "error"): string {
|
|
21
|
+
return theme?.status[kind] ?? (kind === "error" ? "[!!]" : "[!]");
|
|
26
22
|
}
|
|
27
23
|
|
|
28
24
|
function sanitizeHudPart(value: string | undefined): string {
|
|
@@ -40,10 +36,21 @@ function compareChips(a: WorkflowHudChip, b: WorkflowHudChip): number {
|
|
|
40
36
|
return (a.priority ?? 50) - (b.priority ?? 50) || a.label.localeCompare(b.label);
|
|
41
37
|
}
|
|
42
38
|
|
|
43
|
-
function
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
function tierForWidth(width: number): WidthTier {
|
|
40
|
+
return width >= 100 ? "wide" : width >= 60 ? "medium" : "tight";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function severityOf(chip: WorkflowHudChip): "error" | "warning" | undefined {
|
|
44
|
+
return chip.severity === "error" || chip.severity === "blocked"
|
|
45
|
+
? "error"
|
|
46
|
+
: chip.severity === "warning"
|
|
47
|
+
? "warning"
|
|
48
|
+
: undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function severityGlyph(severity: WorkflowHudChip["severity"]): string {
|
|
52
|
+
if (severity === "error" || severity === "blocked") return color("error", statusSymbol("error"));
|
|
53
|
+
if (severity === "warning") return color("warning", statusSymbol("warning"));
|
|
47
54
|
return "";
|
|
48
55
|
}
|
|
49
56
|
|
|
@@ -52,32 +59,163 @@ function formatChip(chip: WorkflowHudChip): string | null {
|
|
|
52
59
|
const value = sanitizeHudPart(chip.value);
|
|
53
60
|
if (!label) return null;
|
|
54
61
|
const body = value ? `${label}=${value}` : label;
|
|
55
|
-
const
|
|
56
|
-
return
|
|
62
|
+
const role = severityOf(chip);
|
|
63
|
+
return role ? color(role, body) : color("dim", body);
|
|
57
64
|
}
|
|
58
65
|
|
|
59
|
-
function
|
|
66
|
+
function keyMetricChip(skill: string, chips: readonly WorkflowHudChip[]): WorkflowHudChip | undefined {
|
|
67
|
+
const preferredBySkill: Record<string, readonly string[]> = {
|
|
68
|
+
"deep-interview": ["ambiguity"],
|
|
69
|
+
ralplan: ["iter", "round", "stage"],
|
|
70
|
+
ultragoal: ["goals", "current"],
|
|
71
|
+
autoresearch: ["exp", "experiments"],
|
|
72
|
+
};
|
|
73
|
+
const preferred = preferredBySkill[skill] ?? [];
|
|
74
|
+
return (
|
|
75
|
+
preferred.map(label => chips.find(chip => chip.label === label && !severityOf(chip))).find(Boolean) ??
|
|
76
|
+
chips.find(chip => !severityOf(chip))
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function buildEntryTokens(entry: SkillActiveEntry, tier: WidthTier, width: number): HudToken[] {
|
|
60
81
|
const skill = sanitizeHudPart(entry.skill);
|
|
61
82
|
const phase = sanitizeHudPart(entry.phase);
|
|
62
83
|
const base = phase ? `${skill}:${phase}` : skill;
|
|
63
|
-
const chips = [...(entry.hud?.chips ?? [])]
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
84
|
+
const chips = [...(entry.hud?.chips ?? [])].sort(compareChips);
|
|
85
|
+
if (entry.stale === true) chips.unshift({ label: "stale", priority: 0, severity: "warning" });
|
|
86
|
+
if (workflowReceiptStatus(entry.receipt) === "stale") {
|
|
87
|
+
chips.unshift({ label: "receipt", value: "stale", priority: 1, severity: "warning" });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const severity =
|
|
91
|
+
chips.find(chip => chip.severity === "error" || chip.severity === "blocked")?.severity ??
|
|
92
|
+
chips.find(chip => chip.severity === "warning")?.severity;
|
|
93
|
+
const glyph = severityGlyph(severity);
|
|
94
|
+
const baseText = color("accent", tier === "tight" ? skill : base);
|
|
95
|
+
if (
|
|
96
|
+
tier === "tight" &&
|
|
97
|
+
glyph &&
|
|
98
|
+
visibleWidth(glyph) <= width &&
|
|
99
|
+
visibleWidth(baseText) + 1 + visibleWidth(glyph) > width
|
|
100
|
+
) {
|
|
101
|
+
return [{ text: glyph, mandatory: true, startsEntry: true }];
|
|
102
|
+
}
|
|
103
|
+
const tokens: HudToken[] = [
|
|
104
|
+
{
|
|
105
|
+
text: baseText,
|
|
106
|
+
mandatory: true,
|
|
107
|
+
startsEntry: true,
|
|
108
|
+
reserveAfter: glyph ? visibleWidth(` ${glyph}`) : 0,
|
|
109
|
+
},
|
|
110
|
+
];
|
|
111
|
+
if (glyph) tokens.push({ text: glyph, mandatory: true });
|
|
112
|
+
|
|
113
|
+
if (tier === "medium") {
|
|
114
|
+
const metric = keyMetricChip(skill, chips);
|
|
115
|
+
const metricText = metric ? formatChip(metric) : null;
|
|
116
|
+
if (metricText) tokens.push({ text: metricText, mandatory: false });
|
|
117
|
+
return tokens;
|
|
118
|
+
}
|
|
119
|
+
|
|
71
120
|
const summary = sanitizeHudPart(entry.hud?.summary);
|
|
72
|
-
|
|
121
|
+
if (summary) tokens.push({ text: color("muted", summary), mandatory: false });
|
|
122
|
+
for (const chip of chips) {
|
|
123
|
+
const formatted = formatChip(chip);
|
|
124
|
+
if (formatted) tokens.push({ text: formatted, mandatory: false });
|
|
125
|
+
}
|
|
126
|
+
return tokens;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function appendOverflowMarker(row: string, width: number, protectedToken?: string): string {
|
|
130
|
+
if (!row || width <= 0) return row;
|
|
131
|
+
if (protectedToken) {
|
|
132
|
+
const tokenIndex = row.lastIndexOf(protectedToken);
|
|
133
|
+
if (tokenIndex >= 0) {
|
|
134
|
+
const prefix = row.slice(0, tokenIndex);
|
|
135
|
+
const tokenWidth = visibleWidth(protectedToken);
|
|
136
|
+
const plainPrefix = Bun.stripANSI(prefix).trimEnd();
|
|
137
|
+
const needsMarker = !plainPrefix.endsWith("…");
|
|
138
|
+
const prefixWidth = Math.max(0, width - tokenWidth - (needsMarker ? 1 : 0));
|
|
139
|
+
return `${truncateToWidth(prefix, prefixWidth)}${needsMarker ? "…" : ""}${protectedToken}`;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return truncateToWidth(`${row}…`, width);
|
|
73
143
|
}
|
|
74
144
|
|
|
75
|
-
|
|
145
|
+
/**
|
|
146
|
+
* Render the HUD as physical rows rather than embedding a newline in one row.
|
|
147
|
+
* The bottom-pinned TUI layout counts array elements as rows; a newline inside
|
|
148
|
+
* one element corrupts that accounting and can overwrite the editor.
|
|
149
|
+
*/
|
|
150
|
+
export function renderSkillHudBar(entries: readonly SkillActiveEntry[], width: number): string[] | null {
|
|
76
151
|
const visible = collapsePlanningPipeline(entries.filter(entry => entry.active !== false));
|
|
77
152
|
const active = visible.filter(entry => sanitizeHudPart(entry.skill)).sort(compareEntries);
|
|
78
153
|
if (active.length === 0 || width <= 0) return null;
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
|
|
154
|
+
|
|
155
|
+
const tier = tierForWidth(width);
|
|
156
|
+
const rail = color("border", "◆");
|
|
157
|
+
const separator = color("dim", " + ");
|
|
158
|
+
const firstPrefix = visibleWidth(rail) + 1 <= width ? `${rail} ` : "";
|
|
159
|
+
const continuationPrefix = firstPrefix ? " ".repeat(visibleWidth(firstPrefix)) : "";
|
|
160
|
+
const rows: string[] = [];
|
|
161
|
+
let row = firstPrefix;
|
|
162
|
+
let rowIndex = 0;
|
|
163
|
+
let hasToken = false;
|
|
164
|
+
let omitted = false;
|
|
165
|
+
let lastSeverityToken: string | undefined;
|
|
166
|
+
|
|
167
|
+
const tryAppend = (token: HudToken, allowTruncate: boolean): boolean => {
|
|
168
|
+
let joiner = hasToken ? (token.startsEntry ? separator : " ") : "";
|
|
169
|
+
let available = width - visibleWidth(row) - visibleWidth(joiner);
|
|
170
|
+
if (!hasToken && visibleWidth(row) > 0 && visibleWidth(token.text) > available) {
|
|
171
|
+
row = "";
|
|
172
|
+
joiner = "";
|
|
173
|
+
available = width;
|
|
174
|
+
}
|
|
175
|
+
if (available <= 0) return false;
|
|
176
|
+
const textAvailable = Math.max(0, available - (allowTruncate ? (token.reserveAfter ?? 0) : 0));
|
|
177
|
+
const text =
|
|
178
|
+
visibleWidth(token.text) <= textAvailable
|
|
179
|
+
? token.text
|
|
180
|
+
: allowTruncate
|
|
181
|
+
? truncateToWidth(token.text, textAvailable)
|
|
182
|
+
: "";
|
|
183
|
+
if (!text || visibleWidth(text) <= 0) return false;
|
|
184
|
+
row = `${row}${joiner}${text}`;
|
|
185
|
+
hasToken = true;
|
|
186
|
+
return true;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const startNextRow = (): boolean => {
|
|
190
|
+
if (rowIndex >= MAX_HUD_ROWS - 1) return false;
|
|
191
|
+
if (hasToken) rows.push(truncateToWidth(row, width));
|
|
192
|
+
rowIndex += 1;
|
|
193
|
+
row = continuationPrefix;
|
|
194
|
+
hasToken = false;
|
|
195
|
+
return true;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
for (const entry of active) {
|
|
199
|
+
for (const token of buildEntryTokens(entry, tier, width)) {
|
|
200
|
+
const append = (allowTruncate: boolean): boolean => {
|
|
201
|
+
const appended = tryAppend(token, allowTruncate);
|
|
202
|
+
if (appended && token.mandatory && !token.startsEntry) lastSeverityToken = token.text;
|
|
203
|
+
return appended;
|
|
204
|
+
};
|
|
205
|
+
if (append(false)) continue;
|
|
206
|
+
if (startNextRow() && append(false)) continue;
|
|
207
|
+
if (append(true)) continue;
|
|
208
|
+
if (token.mandatory && startNextRow() && append(true)) continue;
|
|
209
|
+
omitted = true;
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
if (omitted) break;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (hasToken) rows.push(truncateToWidth(row, width));
|
|
216
|
+
if (omitted && rows.length > 0) {
|
|
217
|
+
const last = rows.length - 1;
|
|
218
|
+
rows[last] = appendOverflowMarker(rows[last] ?? "", width, lastSeverityToken);
|
|
219
|
+
}
|
|
220
|
+
return rows.length > 0 ? rows.slice(0, MAX_HUD_ROWS) : null;
|
|
83
221
|
}
|