@gajae-code/coding-agent 0.15.5 → 0.16.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 +85 -1
- package/dist/types/async/job-manager.d.ts +83 -0
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/auth-gateway-cli.d.ts +17 -0
- package/dist/types/cli/model-presets-cli.d.ts +9 -0
- package/dist/types/cli/root-flags.d.ts +14 -0
- package/dist/types/cli/update-cli.d.ts +15 -0
- package/dist/types/cli.d.ts +7 -0
- package/dist/types/commands/auth-gateway.d.ts +3 -0
- package/dist/types/commands/gc.d.ts +13 -0
- package/dist/types/commands/launch.d.ts +10 -0
- package/dist/types/commands/model-presets.d.ts +21 -0
- package/dist/types/config/autorouting-tier-map.d.ts +120 -0
- package/dist/types/config/file-lock.d.ts +54 -2
- package/dist/types/config/internal/model-preset-registry-test-state.d.ts +4 -0
- package/dist/types/config/internal/model-preset-registry-test-support.d.ts +3 -0
- package/dist/types/config/keybindings.d.ts +3 -2
- package/dist/types/config/model-preset-registry.d.ts +368 -0
- package/dist/types/config/model-profile-activation.d.ts +39 -0
- package/dist/types/config/model-profiles.d.ts +5 -3
- package/dist/types/config/model-registry.d.ts +17 -5
- package/dist/types/config/settings-schema.d.ts +19 -1
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/coordinator-mcp/model-preset.d.ts +7 -5
- package/dist/types/coordinator-mcp/projection-scan.d.ts +54 -0
- package/dist/types/crash/record-loader.d.ts +15 -0
- package/dist/types/extensibility/extensions/types.d.ts +2 -5
- package/dist/types/gjc-runtime/empty-delete-gc.d.ts +58 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +2 -0
- package/dist/types/gjc-runtime/launch-worktree-reservation.d.ts +21 -0
- package/dist/types/gjc-runtime/managed-owner-supervisor.d.ts +2 -0
- package/dist/types/gjc-runtime/session-state-lock.d.ts +35 -6
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +72 -1
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +1 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +27 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/main.d.ts +3 -1
- package/dist/types/master-mode/context.d.ts +21 -0
- package/dist/types/master-mode/first-request.d.ts +34 -0
- package/dist/types/master-mode/snapshot.d.ts +32 -0
- package/dist/types/modes/components/frictionless-onboarding-selector.d.ts +10 -0
- package/dist/types/modes/components/jobs-overlay-model.d.ts +4 -2
- package/dist/types/modes/components/oauth-selector.d.ts +1 -0
- package/dist/types/modes/components/status-line/model-name.d.ts +18 -0
- package/dist/types/modes/components/status-line/priority-row.d.ts +34 -0
- package/dist/types/modes/components/status-line/segments.d.ts +10 -0
- package/dist/types/modes/jobs-observer.d.ts +36 -0
- package/dist/types/modes/print-mode.d.ts +17 -0
- package/dist/types/modes/ui-language.d.ts +7 -1
- package/dist/types/sdk/broker/broker.d.ts +80 -1
- package/dist/types/sdk/broker/endpoint-authority.d.ts +24 -0
- package/dist/types/sdk/broker/index.d.ts +3 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +26 -0
- package/dist/types/sdk/broker/master-capability.d.ts +10 -0
- package/dist/types/sdk/broker/session-index.d.ts +40 -11
- package/dist/types/sdk/broker/session-scope.d.ts +72 -0
- package/dist/types/sdk/broker/spawn-authority.d.ts +201 -0
- package/dist/types/sdk/broker/spawn-substrate.d.ts +30 -0
- package/dist/types/sdk/broker/state-version.d.ts +4 -1
- package/dist/types/sdk/broker/worktree-occupancy.d.ts +28 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +3 -0
- package/dist/types/sdk/bus/index.d.ts +6 -0
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +3 -0
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +4 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
- package/dist/types/sdk/cli/index.d.ts +1 -0
- package/dist/types/sdk/cli/master-cli.d.ts +63 -0
- package/dist/types/sdk/cli/rows.d.ts +4 -3
- package/dist/types/sdk/cli/session-cli.d.ts +18 -2
- package/dist/types/sdk/client/client.d.ts +3 -2
- package/dist/types/sdk/client/discovery.d.ts +1 -0
- package/dist/types/sdk/host/control/dispatch.d.ts +10 -0
- package/dist/types/sdk/host/control/index.d.ts +1 -1
- package/dist/types/sdk/host/control/operations.d.ts +2 -0
- package/dist/types/sdk/host/control/runtime-gate.d.ts +21 -7
- package/dist/types/sdk/host/host.d.ts +10 -0
- package/dist/types/sdk/host/model-pin.d.ts +7 -2
- package/dist/types/sdk/host/sdk-run-capability.d.ts +1 -0
- package/dist/types/sdk/host/session-runtime.d.ts +67 -1
- package/dist/types/sdk/lifecycle/broker-client.d.ts +6 -0
- package/dist/types/sdk/lifecycle/service.d.ts +15 -3
- package/dist/types/sdk/lifecycle-session.d.ts +2 -0
- package/dist/types/sdk/prompt-deadline-manager.d.ts +11 -1
- package/dist/types/sdk/protocol/adapter-validation.d.ts +2 -0
- package/dist/types/sdk/router/session-router.d.ts +11 -0
- package/dist/types/sdk/session.d.ts +16 -0
- package/dist/types/sdk/startup-capability.d.ts +2 -1
- package/dist/types/session/account-inventory.d.ts +1 -0
- package/dist/types/session/agent-session.d.ts +24 -7
- package/dist/types/session/credential-store-errors.d.ts +1 -0
- package/dist/types/session/fold-coordinator.d.ts +201 -0
- package/dist/types/session/sdk-run-capability-internal.d.ts +7 -0
- package/dist/types/session/sdk-run-capability.d.ts +2 -0
- package/dist/types/session/session-manager.d.ts +65 -0
- package/dist/types/session/yield-queue.d.ts +5 -4
- package/dist/types/setup/frictionless-onboarding.d.ts +1 -0
- package/dist/types/telemetry/control.d.ts +11 -0
- package/dist/types/telemetry/events.d.ts +20 -0
- package/dist/types/telemetry/index.d.ts +3 -0
- package/dist/types/telemetry/transport.d.ts +22 -0
- package/dist/types/tools/bash-interactive.d.ts +23 -0
- package/dist/types/tools/bash.d.ts +4 -0
- package/dist/types/tools/gh.d.ts +8 -0
- package/dist/types/tools/index.d.ts +10 -5
- package/dist/types/web/search/providers/base.d.ts +9 -4
- package/dist/types/web/search/providers/xai.d.ts +2 -1
- package/dist/types/web/search/types.d.ts +14 -0
- package/package.json +10 -7
- package/scripts/generate-hotkeys-docs.ts +4 -0
- package/scripts/generate-sdk-operation-inventory.ts +9 -3
- package/scripts/verify-gjc-sdk-canonicalization.ts +35 -1
- package/src/async/job-manager.ts +671 -39
- package/src/cli/accounts-cli.ts +9 -5
- package/src/cli/args.ts +17 -0
- package/src/cli/auth-broker-cli.ts +40 -12
- package/src/cli/auth-gateway-cli.ts +273 -97
- package/src/cli/config-cli.ts +2 -2
- package/src/cli/fast-help.ts +4 -0
- package/src/cli/model-presets-cli.ts +100 -0
- package/src/cli/root-flags.ts +5 -0
- package/src/cli/update-cli.ts +117 -16
- package/src/cli.ts +6 -2
- package/src/commands/auth-gateway.ts +12 -8
- package/src/commands/gc.ts +19 -0
- package/src/commands/launch.ts +86 -18
- package/src/commands/model-presets.ts +24 -0
- package/src/commands/sdk.ts +113 -21
- package/src/config/autorouting-tier-map.ts +44 -0
- package/src/config/file-lock-gc.ts +9 -4
- package/src/config/file-lock.ts +890 -99
- package/src/config/internal/model-preset-registry-test-state.ts +28 -0
- package/src/config/internal/model-preset-registry-test-support.ts +11 -0
- package/src/config/keybindings.ts +6 -2
- package/src/config/model-preset-registry.ts +2795 -0
- package/src/config/model-profile-activation.ts +115 -28
- package/src/config/model-profile-contract.ts +4 -1
- package/src/config/model-profiles.ts +56 -14
- package/src/config/model-registry.ts +1044 -122
- package/src/config/model-resolver.ts +5 -0
- package/src/config/settings-schema.ts +19 -1
- package/src/config/settings.ts +11 -1
- package/src/coordinator-mcp/model-preset.ts +32 -17
- package/src/coordinator-mcp/projection-scan.ts +592 -0
- package/src/coordinator-mcp/question-state.ts +9 -11
- package/src/coordinator-mcp/server.ts +111 -42
- package/src/coordinator-mcp/session-reaper.ts +9 -3
- package/src/crash/index-store.ts +2 -0
- package/src/crash/record-loader.ts +10 -0
- package/src/extensibility/extensions/loader.ts +0 -1
- package/src/extensibility/extensions/types.ts +2 -3
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +1 -0
- package/src/gjc-runtime/empty-delete-gc.ts +301 -0
- package/src/gjc-runtime/gc-render.ts +29 -0
- package/src/gjc-runtime/gc-runtime.ts +174 -11
- package/src/gjc-runtime/launch-worktree-reservation.ts +242 -0
- package/src/gjc-runtime/managed-owner-supervisor.ts +63 -51
- package/src/gjc-runtime/session-state-lock.ts +1321 -147
- package/src/gjc-runtime/session-state-sidecar.ts +1004 -25
- package/src/gjc-runtime/tmux-owner-isolation-cli.ts +4 -7
- package/src/gjc-runtime/tmux-owner-isolation.ts +2 -2
- package/src/gjc-runtime/tmux-sessions.ts +252 -13
- package/src/hooks/skill-state.ts +6 -0
- package/src/index.ts +1 -0
- package/src/internal-urls/docs-index.generated.ts +14 -13
- package/src/internal-urls/issue-pr-protocol.ts +4 -2
- package/src/lsp/config.ts +4 -3
- package/src/lsp/path-trust.ts +12 -6
- package/src/main.ts +66 -4
- package/src/master-mode/context.ts +50 -0
- package/src/master-mode/first-request.ts +65 -0
- package/src/master-mode/snapshot.ts +205 -0
- package/src/modes/acp/acp-agent.ts +616 -161
- package/src/modes/components/footer.ts +4 -2
- package/src/modes/components/frictionless-onboarding-selector.ts +48 -2
- package/src/modes/components/jobs-overlay-model.ts +99 -16
- package/src/modes/components/model-selector.ts +158 -18
- package/src/modes/components/oauth-selector.ts +6 -1
- package/src/modes/components/settings-selector.ts +1 -0
- package/src/modes/components/status-line/model-name.ts +53 -0
- package/src/modes/components/status-line/priority-row.ts +107 -0
- package/src/modes/components/status-line/segments.ts +29 -10
- package/src/modes/components/tool-status-header.ts +151 -15
- package/src/modes/controllers/extension-ui-controller.ts +1 -1
- package/src/modes/controllers/input-controller.ts +51 -6
- package/src/modes/controllers/selector-controller.ts +191 -96
- package/src/modes/interactive-mode.ts +14 -1
- package/src/modes/jobs-observer.ts +149 -16
- package/src/modes/print-mode.ts +64 -0
- package/src/modes/runtime-init.ts +1 -1
- package/src/modes/ui-language.ts +49 -2
- package/src/modes/utils/injected-user-submission.ts +15 -1
- package/src/prompts/system/master-mode.md +28 -0
- package/src/prompts/tools/bash.md +2 -2
- package/src/prompts/tools/fold-receipt.md +3 -0
- package/src/prompts/tools/job.md +1 -1
- package/src/sdk/acp/adapter.ts +17 -2
- package/src/sdk/broker/broker.ts +1671 -34
- package/src/sdk/broker/endpoint-authority.ts +86 -0
- package/src/sdk/broker/ensure.ts +5 -0
- package/src/sdk/broker/index.ts +3 -0
- package/src/sdk/broker/lifecycle.ts +217 -46
- package/src/sdk/broker/master-capability.ts +232 -0
- package/src/sdk/broker/session-index.ts +121 -17
- package/src/sdk/broker/session-scope.ts +272 -0
- package/src/sdk/broker/spawn-authority.ts +1031 -0
- package/src/sdk/broker/spawn-substrate.ts +548 -0
- package/src/sdk/broker/state-version.ts +26 -14
- package/src/sdk/broker/transport.ts +1 -0
- package/src/sdk/broker/worktree-occupancy.ts +90 -0
- package/src/sdk/bus/chat-daemon-control.ts +5 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +46 -5
- package/src/sdk/bus/index.ts +51 -4
- package/src/sdk/bus/kind-aware-reconciliation.ts +36 -6
- package/src/sdk/bus/prompt-reconciliation.ts +28 -4
- package/src/sdk/bus/slack-conversation.ts +5 -1
- package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
- package/src/sdk/cli/index.ts +1 -0
- package/src/sdk/cli/master-cli.ts +259 -0
- package/src/sdk/cli/rows.ts +11 -8
- package/src/sdk/cli/session-cli.ts +276 -6
- package/src/sdk/client/client.ts +14 -8
- package/src/sdk/client/discovery.ts +3 -1
- package/src/sdk/host/control/dispatch.ts +101 -37
- package/src/sdk/host/control/index.ts +2 -0
- package/src/sdk/host/control/operations.ts +2 -0
- package/src/sdk/host/control/runtime-gate.ts +203 -11
- package/src/sdk/host/host.ts +27 -2
- package/src/sdk/host/model-pin.ts +185 -25
- package/src/sdk/host/sdk-run-capability.ts +1 -0
- package/src/sdk/host/session-runtime.test.ts +581 -43
- package/src/sdk/host/session-runtime.ts +655 -101
- package/src/sdk/lifecycle/broker-client.ts +28 -0
- package/src/sdk/lifecycle/service.ts +279 -20
- package/src/sdk/lifecycle-session.ts +10 -2
- package/src/sdk/prompt-deadline-manager.ts +16 -1
- package/src/sdk/protocol/adapter-validation.ts +27 -0
- package/src/sdk/protocol/operation-inventory.generated.json +53 -2
- package/src/sdk/protocol/operation-registry.ts +17 -4
- package/src/sdk/router/session-router.ts +294 -154
- package/src/sdk/session.ts +440 -167
- package/src/sdk/startup-capability.ts +1 -0
- package/src/session/account-inventory.ts +27 -11
- package/src/session/agent-session.ts +1292 -180
- package/src/session/blob-store.ts +15 -5
- package/src/session/credential-store-errors.ts +2 -0
- package/src/session/fold-coordinator.ts +433 -0
- package/src/session/internal/managed-session-storage.ts +153 -78
- package/src/session/sdk-run-capability-internal.ts +15 -0
- package/src/session/sdk-run-capability.ts +2 -0
- package/src/session/session-manager.ts +342 -28
- package/src/session/yield-queue.ts +24 -10
- package/src/setup/frictionless-onboarding.ts +2 -0
- package/src/setup/provider-presets.json +1 -1
- package/src/slash-commands/helpers/credential-switch.ts +6 -1
- package/src/task/executor.ts +19 -3
- package/src/telemetry/control.ts +26 -0
- package/src/telemetry/events.ts +1268 -0
- package/src/telemetry/index.ts +3 -0
- package/src/telemetry/transport.ts +87 -0
- package/src/tools/bash-interactive.ts +182 -85
- package/src/tools/bash.ts +1028 -109
- package/src/tools/gh.ts +104 -48
- package/src/tools/github-cache.ts +2 -2
- package/src/tools/index.ts +11 -5
- package/src/tools/tool-catalog.generated.ts +1 -1
- package/src/web/search/provider.ts +120 -11
- package/src/web/search/providers/anthropic.ts +89 -17
- package/src/web/search/providers/base.ts +9 -4
- package/src/web/search/providers/gemini.ts +87 -15
- package/src/web/search/providers/openai-compatible.ts +23 -8
- package/src/web/search/providers/xai.ts +98 -12
- package/src/web/search/types.ts +16 -0
|
@@ -9,6 +9,7 @@ import * as git from "../../utils/git";
|
|
|
9
9
|
import { sanitizeStatusText } from "../shared";
|
|
10
10
|
import { getContextUsageLevel, getContextUsageThemeColor } from "./status-line/context-thresholds";
|
|
11
11
|
import { resolveCurrentBranch } from "./status-line/git-utils";
|
|
12
|
+
import { shortenModelId } from "./status-line/model-name";
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Footer component that shows pwd, token stats, and context usage
|
|
@@ -191,8 +192,9 @@ export class FooterComponent implements Component {
|
|
|
191
192
|
|
|
192
193
|
let statsLeft = statsParts.join(" ");
|
|
193
194
|
|
|
194
|
-
// Add model name on the right side, plus thinking level if model supports it
|
|
195
|
-
|
|
195
|
+
// Add model name on the right side, plus thinking level if model supports it.
|
|
196
|
+
// Same shortest-recognizable form the status rail renders.
|
|
197
|
+
const modelName = shortenModelId(state.model?.id ?? state.model?.name);
|
|
196
198
|
|
|
197
199
|
// Add thinking level hint when the current model advertises supported efforts
|
|
198
200
|
let rightSide = modelName;
|
|
@@ -1,9 +1,55 @@
|
|
|
1
|
-
import { Container, matchesKey, Spacer, TruncatedText } from "@gajae-code/tui";
|
|
1
|
+
import { Container, matchesKey, type SelectItem, SelectList, Spacer, TruncatedText } from "@gajae-code/tui";
|
|
2
2
|
import type { OnboardingProfile } from "../../setup/frictionless-onboarding";
|
|
3
|
-
import { theme } from "../theme/theme";
|
|
3
|
+
import { getSelectListTheme, theme } from "../theme/theme";
|
|
4
|
+
import { UI_LANGUAGES, type UiLanguage } from "../ui-language";
|
|
4
5
|
import { matchesSelectCancel } from "../utils/keybinding-matchers";
|
|
5
6
|
import { DynamicBorder } from "./dynamic-border";
|
|
6
7
|
|
|
8
|
+
export const UI_LANGUAGE_CHOICES: readonly { value: UiLanguage; label: string }[] = [
|
|
9
|
+
{ value: "en", label: "English" },
|
|
10
|
+
{ value: "ko", label: "한국어" },
|
|
11
|
+
{ value: "zh", label: "简体中文" },
|
|
12
|
+
{ value: "ja", label: "日本語" },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
export class InterfaceLanguageSelectorComponent extends Container {
|
|
16
|
+
readonly #selectList: SelectList;
|
|
17
|
+
|
|
18
|
+
constructor(onSelect: (language: UiLanguage) => void, onCancel: () => void) {
|
|
19
|
+
super();
|
|
20
|
+
this.addChild(new DynamicBorder());
|
|
21
|
+
this.addChild(new Spacer(1));
|
|
22
|
+
this.addChild(
|
|
23
|
+
new TruncatedText(
|
|
24
|
+
theme.bold(
|
|
25
|
+
"Choose your interface language / 언어를 선택하세요 / 选择界面语言 / インターフェース言語を選択",
|
|
26
|
+
),
|
|
27
|
+
),
|
|
28
|
+
);
|
|
29
|
+
this.addChild(new Spacer(1));
|
|
30
|
+
const items: SelectItem[] = UI_LANGUAGE_CHOICES.map(choice => ({ value: choice.value, label: choice.label }));
|
|
31
|
+
this.#selectList = new SelectList(items, items.length, getSelectListTheme());
|
|
32
|
+
this.#selectList.onSelect = item => {
|
|
33
|
+
if (UI_LANGUAGES.includes(item.value as UiLanguage)) onSelect(item.value as UiLanguage);
|
|
34
|
+
};
|
|
35
|
+
this.#selectList.onCancel = onCancel;
|
|
36
|
+
this.addChild(this.#selectList);
|
|
37
|
+
this.addChild(new Spacer(1));
|
|
38
|
+
this.addChild(new TruncatedText(theme.fg("dim", "↑/↓ select · Enter choose · Esc use English")));
|
|
39
|
+
this.addChild(new DynamicBorder());
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
handleInput(keyData: string): void {
|
|
43
|
+
// The app.exit action is owned by the parent editor and is not a TUI
|
|
44
|
+
// selector key id. Ctrl-D is its default byte when the editor is empty.
|
|
45
|
+
if (keyData === "\x04") {
|
|
46
|
+
this.#selectList.onCancel?.();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
this.#selectList.handleInput(keyData);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
7
53
|
export type FrictionlessOnboardingAction =
|
|
8
54
|
| "analyze"
|
|
9
55
|
| "apply"
|
|
@@ -6,20 +6,41 @@
|
|
|
6
6
|
* SelectItem lists into nested SelectLists (list -> detail -> confirm).
|
|
7
7
|
*/
|
|
8
8
|
import type { SelectItem } from "@gajae-code/tui";
|
|
9
|
+
import { replaceTabs, shortenPath, TRUNCATE_LENGTHS, truncateToWidth } from "../../tools/render-utils";
|
|
9
10
|
import type { JobsSnapshot } from "../jobs-observer";
|
|
11
|
+
import { sanitizeStatusText } from "../shared";
|
|
10
12
|
|
|
11
|
-
export type JobRefKind = "monitor" | "cron";
|
|
13
|
+
export type JobRefKind = "monitor" | "cron" | "folded";
|
|
12
14
|
|
|
13
15
|
export interface JobRef {
|
|
14
16
|
kind: JobRefKind;
|
|
15
17
|
id: string;
|
|
18
|
+
generation?: string;
|
|
16
19
|
}
|
|
17
20
|
|
|
18
|
-
|
|
21
|
+
function encodeJobRefPart(value: string): string {
|
|
22
|
+
return encodeURIComponent(value);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function decodeJobRefPart(value: string): string | undefined {
|
|
26
|
+
try {
|
|
27
|
+
return decodeURIComponent(value);
|
|
28
|
+
} catch {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function displayText(text: string, max: number = TRUNCATE_LENGTHS.CONTENT): string {
|
|
34
|
+
const sanitized = shortenPath(replaceTabs(sanitizeStatusText(text)));
|
|
35
|
+
return truncateToWidth(sanitized, max);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function preview(text: string, max: number = TRUNCATE_LENGTHS.TITLE): string {
|
|
39
|
+
return displayText(text, max);
|
|
40
|
+
}
|
|
19
41
|
|
|
20
|
-
function
|
|
21
|
-
|
|
22
|
-
return oneLine.length > max ? `${oneLine.slice(0, max - 1)}…` : oneLine;
|
|
42
|
+
function snapshotJobKey(id: string, generation: string | undefined): string {
|
|
43
|
+
return `${id}\u0000${generation ?? ""}`;
|
|
23
44
|
}
|
|
24
45
|
|
|
25
46
|
/** Compact relative time, e.g. "in 5m", "2m ago", "now". */
|
|
@@ -42,30 +63,71 @@ export function parseJobRef(value: string): JobRef | null {
|
|
|
42
63
|
if ((kind === "monitor" || kind === "cron") && id.length > 0) {
|
|
43
64
|
return { kind, id };
|
|
44
65
|
}
|
|
66
|
+
if (kind === "folded") {
|
|
67
|
+
const generationSep = id.indexOf(":");
|
|
68
|
+
if (generationSep > 0 && generationSep < id.length - 1) {
|
|
69
|
+
const decodedId = decodeJobRefPart(id.slice(0, generationSep));
|
|
70
|
+
const generation = decodeJobRefPart(id.slice(generationSep + 1));
|
|
71
|
+
if (decodedId && generation) return { kind, id: decodedId, generation };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
45
74
|
return null;
|
|
46
75
|
}
|
|
47
76
|
|
|
48
77
|
/**
|
|
49
78
|
* Build the grouped jobs list: monitors first (newest-first), then crons
|
|
50
|
-
* (newest-first). The snapshot arrays are already
|
|
79
|
+
* (newest-first), then read-only folded jobs. The snapshot arrays are already
|
|
80
|
+
* sorted newest-first where a source has a timestamp.
|
|
51
81
|
*/
|
|
52
82
|
export function buildJobsListItems(snapshot: JobsSnapshot): SelectItem[] {
|
|
53
83
|
const items: SelectItem[] = [];
|
|
84
|
+
const foldedJobs = snapshot.foldedJobs ?? [];
|
|
85
|
+
const foldedKeys = new Set(foldedJobs.map(job => snapshotJobKey(job.id, job.generation)));
|
|
54
86
|
for (const monitor of snapshot.monitors) {
|
|
87
|
+
// A monitor can also be backgrounded; render it only in the folded section
|
|
88
|
+
// so a public job state has exactly one row.
|
|
89
|
+
if (monitor.backgrounded || foldedKeys.has(snapshotJobKey(monitor.id, monitor.generation))) continue;
|
|
55
90
|
items.push({
|
|
56
91
|
value: `monitor:${monitor.id}`,
|
|
57
|
-
label: `monitor · ${preview(monitor.label,
|
|
58
|
-
description: monitor.status,
|
|
59
|
-
hint: monitor.status === "failed" ? "failed" : undefined,
|
|
92
|
+
label: `monitor · ${preview(monitor.label, TRUNCATE_LENGTHS.SHORT)}`,
|
|
93
|
+
description: displayText(monitor.status, TRUNCATE_LENGTHS.SHORT),
|
|
94
|
+
hint: monitor.status === "failed" || monitor.deliveryState === "failed-visible" ? "failed" : undefined,
|
|
60
95
|
});
|
|
61
96
|
}
|
|
62
97
|
for (const cron of snapshot.crons) {
|
|
63
98
|
items.push({
|
|
64
99
|
value: `cron:${cron.id}`,
|
|
65
|
-
label: `cron · ${cron.humanSchedule}`,
|
|
100
|
+
label: `cron · ${preview(cron.humanSchedule, TRUNCATE_LENGTHS.SHORT)}`,
|
|
66
101
|
description: preview(cron.prompt),
|
|
67
102
|
});
|
|
68
103
|
}
|
|
104
|
+
for (const job of foldedJobs) {
|
|
105
|
+
const state = displayText(job.deliveryState, TRUNCATE_LENGTHS.SHORT);
|
|
106
|
+
const kind = preview(job.kind, TRUNCATE_LENGTHS.SHORT);
|
|
107
|
+
const deadLetter = (snapshot.deadLettered ?? []).find(
|
|
108
|
+
entry => snapshotJobKey(entry.jobId, entry.generation) === snapshotJobKey(job.id, job.generation),
|
|
109
|
+
);
|
|
110
|
+
const errorText = job.errorText ?? deadLetter?.lastError;
|
|
111
|
+
const description =
|
|
112
|
+
deadLetter || errorText
|
|
113
|
+
? displayText(
|
|
114
|
+
[
|
|
115
|
+
state,
|
|
116
|
+
deadLetter ? `attempt ${deadLetter.attempt}` : undefined,
|
|
117
|
+
errorText ? `error: ${errorText}` : undefined,
|
|
118
|
+
]
|
|
119
|
+
.filter((part): part is string => part !== undefined)
|
|
120
|
+
.join(" · "),
|
|
121
|
+
TRUNCATE_LENGTHS.CONTENT,
|
|
122
|
+
)
|
|
123
|
+
: state;
|
|
124
|
+
items.push({
|
|
125
|
+
value: `folded:${encodeJobRefPart(job.id)}:${encodeJobRefPart(job.generation)}`,
|
|
126
|
+
label: `${kind} · ${preview(job.label, TRUNCATE_LENGTHS.SHORT)}`,
|
|
127
|
+
description,
|
|
128
|
+
hint: job.status === "failed" || job.deliveryState === "failed-visible" ? "failed" : "read-only",
|
|
129
|
+
});
|
|
130
|
+
}
|
|
69
131
|
return items;
|
|
70
132
|
}
|
|
71
133
|
|
|
@@ -80,21 +142,41 @@ export function buildJobDetailItems(snapshot: JobsSnapshot, ref: JobRef, output
|
|
|
80
142
|
if (!monitor) return [{ value: "back", label: "Back (job no longer present)" }];
|
|
81
143
|
const lastOutput = output.trim().split("\n").filter(Boolean).slice(-1)[0] ?? "(no output captured)";
|
|
82
144
|
return [
|
|
83
|
-
{ value: "noop", label: "Status", description: monitor.status },
|
|
145
|
+
{ value: "noop", label: "Status", description: displayText(monitor.status, TRUNCATE_LENGTHS.SHORT) },
|
|
84
146
|
{ value: "noop", label: "Label", description: preview(monitor.label) },
|
|
85
147
|
{ value: "noop", label: "Started", description: formatRelative(monitor.startTime) },
|
|
86
|
-
{ value: "noop", label: "Output", description: preview(lastOutput,
|
|
148
|
+
{ value: "noop", label: "Output", description: preview(lastOutput, TRUNCATE_LENGTHS.CONTENT) },
|
|
87
149
|
{ value: "action:cancel", label: "Cancel this monitor", hint: "stops the running job" },
|
|
88
150
|
{ value: "back", label: "Back" },
|
|
89
151
|
];
|
|
90
152
|
}
|
|
153
|
+
if (ref.kind === "folded") {
|
|
154
|
+
const job = snapshot.foldedJobs?.find(
|
|
155
|
+
entry => entry.id === ref.id && (ref.generation === undefined || entry.generation === ref.generation),
|
|
156
|
+
);
|
|
157
|
+
if (!job) return [{ value: "back", label: "Back (job no longer present)" }];
|
|
158
|
+
return [
|
|
159
|
+
{ value: "noop", label: "Status", description: displayText(job.status, TRUNCATE_LENGTHS.SHORT) },
|
|
160
|
+
{ value: "noop", label: "Kind", description: preview(job.kind, TRUNCATE_LENGTHS.SHORT) },
|
|
161
|
+
{ value: "noop", label: "Label", description: preview(job.label) },
|
|
162
|
+
{ value: "noop", label: "Generation", description: preview(job.generation, TRUNCATE_LENGTHS.CONTENT) },
|
|
163
|
+
...(job.errorText
|
|
164
|
+
? [{ value: "noop", label: "Error", description: preview(job.errorText, TRUNCATE_LENGTHS.CONTENT) }]
|
|
165
|
+
: []),
|
|
166
|
+
{ value: "back", label: "Back" },
|
|
167
|
+
];
|
|
168
|
+
}
|
|
91
169
|
const cron = snapshot.crons.find(c => c.id === ref.id);
|
|
92
170
|
if (!cron) return [{ value: "back", label: "Back (job no longer present)" }];
|
|
93
171
|
return [
|
|
94
|
-
{
|
|
172
|
+
{
|
|
173
|
+
value: "noop",
|
|
174
|
+
label: "Schedule",
|
|
175
|
+
description: `${preview(cron.humanSchedule, TRUNCATE_LENGTHS.SHORT)} (${preview(cron.cronExpression, TRUNCATE_LENGTHS.SHORT)})`,
|
|
176
|
+
},
|
|
95
177
|
{ value: "noop", label: "Recurring", description: cron.recurring ? "yes" : "no" },
|
|
96
178
|
{ value: "noop", label: "Next fire", description: formatRelative(cron.nextFireAt) },
|
|
97
|
-
{ value: "noop", label: "Prompt", description: preview(cron.prompt,
|
|
179
|
+
{ value: "noop", label: "Prompt", description: preview(cron.prompt, TRUNCATE_LENGTHS.CONTENT) },
|
|
98
180
|
{ value: "action:delete", label: "Delete this cron", hint: "removes the schedule" },
|
|
99
181
|
{ value: "back", label: "Back" },
|
|
100
182
|
];
|
|
@@ -102,8 +184,9 @@ export function buildJobDetailItems(snapshot: JobsSnapshot, ref: JobRef, output
|
|
|
102
184
|
|
|
103
185
|
/** Yes/No confirm items for a destructive action. */
|
|
104
186
|
export function buildConfirmItems(actionLabel: string): SelectItem[] {
|
|
187
|
+
const safeActionLabel = displayText(actionLabel, TRUNCATE_LENGTHS.TITLE);
|
|
105
188
|
return [
|
|
106
|
-
{ value: "no", label:
|
|
107
|
-
{ value: "yes", label: `Yes, ${
|
|
189
|
+
{ value: "no", label: "No, keep it" },
|
|
190
|
+
{ value: "yes", label: `Yes, ${safeActionLabel}` },
|
|
108
191
|
];
|
|
109
192
|
}
|
|
@@ -22,9 +22,18 @@ import {
|
|
|
22
22
|
normalizeTierMap,
|
|
23
23
|
validateAutoroutingSetup,
|
|
24
24
|
} from "../../config/autorouting-contract";
|
|
25
|
+
import {
|
|
26
|
+
getProxyRoutableProviders,
|
|
27
|
+
inspectProxyProviderId,
|
|
28
|
+
requiresQualifiedModelProfileRoleResolution,
|
|
29
|
+
resolveProxyMode,
|
|
30
|
+
rewriteSelectorForProxy,
|
|
31
|
+
tryResolveProxyProviderId,
|
|
32
|
+
} from "../../config/model-profile-activation";
|
|
25
33
|
|
|
26
34
|
import { isModelProfileProviderAvailable } from "../../config/model-profile-contract";
|
|
27
35
|
import {
|
|
36
|
+
deriveModelProfileMappedProviders,
|
|
28
37
|
getModelProfilePresentation,
|
|
29
38
|
groupModelProfilesForPresetLanding,
|
|
30
39
|
type ModelProfileDefinition,
|
|
@@ -399,6 +408,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
399
408
|
#closeAfterAssignment = false;
|
|
400
409
|
#unsubscribeCatalogChanged: () => void = () => {};
|
|
401
410
|
#unsubscribeProviderOrderChanged: () => void = () => {};
|
|
411
|
+
#unsubscribeAuthGeneration: () => void = () => {};
|
|
402
412
|
#disposed = false;
|
|
403
413
|
/** Standalone smart-routing entry: cancel closes the selector instead of falling back to the preset landing. */
|
|
404
414
|
#smartRoutingOnly = false;
|
|
@@ -413,6 +423,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
413
423
|
#providerAuthById = new Map<string, boolean>();
|
|
414
424
|
#bareProfileAuthByName = new Map<string, boolean>();
|
|
415
425
|
#providerAuthPending: boolean = false;
|
|
426
|
+
#providerAuthRefreshGeneration = 0;
|
|
416
427
|
#presetLoginHint?: string;
|
|
417
428
|
#authSessionId?: string;
|
|
418
429
|
#imageRoleFilter: boolean = false;
|
|
@@ -524,6 +535,14 @@ export class ModelSelectorComponent extends Container {
|
|
|
524
535
|
if (typeof this.#modelRegistry.onCatalogChanged === "function") {
|
|
525
536
|
this.#unsubscribeCatalogChanged = this.#modelRegistry.onCatalogChanged(() => {
|
|
526
537
|
if (this.#disposed) return;
|
|
538
|
+
if (this.#viewMode === "presets") {
|
|
539
|
+
this.#refreshCatalogView();
|
|
540
|
+
this.#clampPresetCursor();
|
|
541
|
+
void this.#refreshProviderAuth();
|
|
542
|
+
this.#renderPresetLanding();
|
|
543
|
+
this.#tui.requestRender();
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
527
546
|
if (this.#refreshCatalogView()) this.#tui.requestRender();
|
|
528
547
|
});
|
|
529
548
|
}
|
|
@@ -538,6 +557,11 @@ export class ModelSelectorComponent extends Container {
|
|
|
538
557
|
panel.updateProviderOrderHint(this.#providerOrderHintFor(panel.getProviderOrder()));
|
|
539
558
|
this.#tui.requestRender();
|
|
540
559
|
});
|
|
560
|
+
this.#unsubscribeAuthGeneration =
|
|
561
|
+
this.#modelRegistry.authStorage?.onGenerationChanged?.(() => {
|
|
562
|
+
if (this.#disposed || this.#viewMode !== "presets") return;
|
|
563
|
+
void this.#refreshProviderAuth();
|
|
564
|
+
}) ?? (() => {});
|
|
541
565
|
|
|
542
566
|
// Load models and do initial render
|
|
543
567
|
this.#loadModels().then(() => {
|
|
@@ -572,8 +596,10 @@ export class ModelSelectorComponent extends Container {
|
|
|
572
596
|
override dispose(): void {
|
|
573
597
|
if (this.#disposed) return;
|
|
574
598
|
this.#disposed = true;
|
|
599
|
+
this.#providerAuthRefreshGeneration++;
|
|
575
600
|
this.#unsubscribeCatalogChanged();
|
|
576
601
|
this.#unsubscribeProviderOrderChanged();
|
|
602
|
+
this.#unsubscribeAuthGeneration();
|
|
577
603
|
super.dispose();
|
|
578
604
|
}
|
|
579
605
|
|
|
@@ -1301,13 +1327,61 @@ export class ModelSelectorComponent extends Container {
|
|
|
1301
1327
|
return this.#providerAuthById.get(providerId);
|
|
1302
1328
|
}
|
|
1303
1329
|
|
|
1330
|
+
#getProfileAvailableModels(): Model[] {
|
|
1331
|
+
const getAvailableForProfileActivation = this.#modelRegistry.getAvailableForProfileActivation;
|
|
1332
|
+
return typeof getAvailableForProfileActivation === "function"
|
|
1333
|
+
? getAvailableForProfileActivation.call(this.#modelRegistry)
|
|
1334
|
+
: this.#modelRegistry.getAvailable();
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
#createProfileResolutionRegistry(availableModels: readonly Model[]) {
|
|
1338
|
+
return {
|
|
1339
|
+
getAvailable: () => availableModels as Model[],
|
|
1340
|
+
getApiKey: this.#modelRegistry.getApiKey?.bind(this.#modelRegistry) ?? (async () => undefined),
|
|
1341
|
+
resolveCanonicalModel: this.#modelRegistry.resolveCanonicalModel?.bind(this.#modelRegistry),
|
|
1342
|
+
getCanonicalVariants: this.#modelRegistry.getCanonicalVariants?.bind(this.#modelRegistry),
|
|
1343
|
+
getCanonicalId: this.#modelRegistry.getCanonicalId?.bind(this.#modelRegistry),
|
|
1344
|
+
resolveModelByLookupAlias: this.#modelRegistry.resolveModelByLookupAlias?.bind(this.#modelRegistry),
|
|
1345
|
+
lookupAliasExists: this.#modelRegistry.lookupAliasExists?.bind(this.#modelRegistry),
|
|
1346
|
+
clearCanonicalVariant: this.#modelRegistry.clearCanonicalVariant?.bind(this.#modelRegistry),
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
#getProfileAuthenticatedProviders(profile: ModelProfileDefinition): Set<string> {
|
|
1351
|
+
if (profile.source !== "user" && inspectProxyProviderId(this.#settings).status === "invalid") return new Set();
|
|
1352
|
+
const authenticated = new Set(
|
|
1353
|
+
[...this.#providerAuthById]
|
|
1354
|
+
.filter(([, providerAuthenticated]) => providerAuthenticated)
|
|
1355
|
+
.map(([provider]) => provider),
|
|
1356
|
+
);
|
|
1357
|
+
const proxyProvider = profile.source === "user" ? undefined : tryResolveProxyProviderId(this.#settings);
|
|
1358
|
+
if (proxyProvider !== undefined && this.#isProviderAuthenticated(proxyProvider) === true) {
|
|
1359
|
+
for (const provider of getProxyRoutableProviders(profile)) authenticated.add(provider);
|
|
1360
|
+
}
|
|
1361
|
+
return authenticated;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
#rewriteProfileSelectorForProxy(profile: ModelProfileDefinition, selector: string): string {
|
|
1365
|
+
if (profile.source === "user") return selector;
|
|
1366
|
+
const proxyProvider = tryResolveProxyProviderId(this.#settings);
|
|
1367
|
+
if (proxyProvider === undefined || this.#isProviderAuthenticated(proxyProvider) !== true) return selector;
|
|
1368
|
+
return rewriteSelectorForProxy(
|
|
1369
|
+
selector,
|
|
1370
|
+
proxyProvider,
|
|
1371
|
+
resolveProxyMode(this.#settings),
|
|
1372
|
+
this.#getProfileAvailableModels(),
|
|
1373
|
+
new Set(
|
|
1374
|
+
[...this.#providerAuthById].filter(([, authenticated]) => authenticated).map(([provider]) => provider),
|
|
1375
|
+
),
|
|
1376
|
+
getProxyRoutableProviders(profile),
|
|
1377
|
+
);
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1304
1380
|
#getMissingProviders(profileOrProfiles: ModelProfileDefinition | ModelProfileDefinition[]): string[] {
|
|
1305
1381
|
const profiles = Array.isArray(profileOrProfiles) ? profileOrProfiles : [profileOrProfiles];
|
|
1306
1382
|
const missing = new Set<string>();
|
|
1307
1383
|
for (const profile of profiles) {
|
|
1308
|
-
const authenticated =
|
|
1309
|
-
profileRequiredProviders(profile).filter(provider => this.#isProviderAuthenticated(provider) === true),
|
|
1310
|
-
);
|
|
1384
|
+
const authenticated = this.#getProfileAuthenticatedProviders(profile);
|
|
1311
1385
|
if (isModelProfileProviderAvailable(profile, authenticated)) continue;
|
|
1312
1386
|
const alternativeGroups = profile.alternativeProviderGroups ?? [];
|
|
1313
1387
|
const alternativeProviders = new Set(alternativeGroups.flat());
|
|
@@ -1325,18 +1399,52 @@ export class ModelSelectorComponent extends Container {
|
|
|
1325
1399
|
#isPresetAuthenticated(profileOrProfiles: ModelProfileDefinition | ModelProfileDefinition[]): boolean {
|
|
1326
1400
|
const profiles = Array.isArray(profileOrProfiles) ? profileOrProfiles : [profileOrProfiles];
|
|
1327
1401
|
return profiles.every(profile => {
|
|
1402
|
+
if (profile.source !== "user") {
|
|
1403
|
+
if (inspectProxyProviderId(this.#settings).status === "invalid") return false;
|
|
1404
|
+
const proxyProvider = tryResolveProxyProviderId(this.#settings);
|
|
1405
|
+
if (
|
|
1406
|
+
proxyProvider !== undefined &&
|
|
1407
|
+
!(this.#modelRegistry.getConfiguredProviderIds?.() ?? []).includes(proxyProvider)
|
|
1408
|
+
)
|
|
1409
|
+
return false;
|
|
1410
|
+
try {
|
|
1411
|
+
if (resolveProxyMode(this.#settings) === "always") {
|
|
1412
|
+
const configuredProviders = this.#modelRegistry.getConfiguredProviderIds?.() ?? [];
|
|
1413
|
+
if (
|
|
1414
|
+
proxyProvider === undefined ||
|
|
1415
|
+
this.#isProviderAuthenticated(proxyProvider) !== true ||
|
|
1416
|
+
!configuredProviders.includes(proxyProvider)
|
|
1417
|
+
)
|
|
1418
|
+
return false;
|
|
1419
|
+
}
|
|
1420
|
+
} catch {
|
|
1421
|
+
return false;
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1328
1424
|
if (this.#getMissingProviders(profile).length > 0) return false;
|
|
1329
1425
|
const bindings = resolveProfileBindings(profile);
|
|
1330
|
-
const
|
|
1331
|
-
...(bindings.defaultSelector ? [bindings.defaultSelector] : []),
|
|
1332
|
-
...Object.values(bindings.modelRoles),
|
|
1333
|
-
...Object.values(bindings.agentModelOverrides),
|
|
1426
|
+
const assignments = [
|
|
1427
|
+
...(bindings.defaultSelector !== undefined ? [{ value: bindings.defaultSelector, isDefault: true }] : []),
|
|
1428
|
+
...Object.values(bindings.modelRoles).map(value => ({ value, isDefault: false })),
|
|
1429
|
+
...Object.values(bindings.agentModelOverrides).map(value => ({ value, isDefault: false })),
|
|
1334
1430
|
];
|
|
1335
1431
|
const bareAssignmentsAvailable = this.#bareProfileAuthByName.get(profile.name) ?? true;
|
|
1336
|
-
|
|
1337
|
-
|
|
1432
|
+
const availableModels = this.#getProfileAvailableModels();
|
|
1433
|
+
return assignments.every(assignment => {
|
|
1434
|
+
let selectors: string[];
|
|
1435
|
+
try {
|
|
1436
|
+
selectors = normalizeModelSelectorValue(assignment.value).map(selector =>
|
|
1437
|
+
this.#rewriteProfileSelectorForProxy(profile, selector),
|
|
1438
|
+
);
|
|
1439
|
+
} catch {
|
|
1440
|
+
return false;
|
|
1441
|
+
}
|
|
1442
|
+
const hasBareSelector = selectors.some(selector => !selector.includes("/"));
|
|
1443
|
+
if (!assignment.isDefault && !requiresQualifiedModelProfileRoleResolution(profile) && !hasBareSelector)
|
|
1444
|
+
return true;
|
|
1445
|
+
return selectors.some(selector => {
|
|
1338
1446
|
if (!selector.includes("/")) return bareAssignmentsAvailable;
|
|
1339
|
-
const resolved = resolveModelRoleValue(selector,
|
|
1447
|
+
const resolved = resolveModelRoleValue(selector, availableModels, {
|
|
1340
1448
|
settings: this.#settings,
|
|
1341
1449
|
modelRegistry: this.#modelRegistry,
|
|
1342
1450
|
aliasIntent: "preset-equivalent",
|
|
@@ -1357,7 +1465,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
1357
1465
|
return (
|
|
1358
1466
|
resolveModelRoleValue(
|
|
1359
1467
|
replacementSelector,
|
|
1360
|
-
|
|
1468
|
+
availableModels.filter(model => model.provider === provider),
|
|
1361
1469
|
{
|
|
1362
1470
|
settings: this.#settings,
|
|
1363
1471
|
modelRegistry: this.#modelRegistry,
|
|
@@ -1368,8 +1476,8 @@ export class ModelSelectorComponent extends Container {
|
|
|
1368
1476
|
);
|
|
1369
1477
|
}) ?? false
|
|
1370
1478
|
);
|
|
1371
|
-
})
|
|
1372
|
-
);
|
|
1479
|
+
});
|
|
1480
|
+
});
|
|
1373
1481
|
});
|
|
1374
1482
|
}
|
|
1375
1483
|
|
|
@@ -1383,10 +1491,23 @@ export class ModelSelectorComponent extends Container {
|
|
|
1383
1491
|
}
|
|
1384
1492
|
|
|
1385
1493
|
async #refreshProviderAuth(): Promise<void> {
|
|
1494
|
+
const refreshGeneration = ++this.#providerAuthRefreshGeneration;
|
|
1495
|
+
this.#providerAuthById = new Map();
|
|
1496
|
+
this.#bareProfileAuthByName = new Map();
|
|
1497
|
+
const availableModels = this.#getProfileAvailableModels();
|
|
1498
|
+
const resolutionRegistry = this.#createProfileResolutionRegistry(availableModels);
|
|
1386
1499
|
const providers = new Set<string>();
|
|
1387
1500
|
for (const profiles of this.#getPresetGroups().values()) {
|
|
1388
1501
|
for (const profile of profiles) {
|
|
1389
1502
|
for (const provider of profileRequiredProviders(profile)) providers.add(provider);
|
|
1503
|
+
for (const group of profile.alternativeProviderGroups ?? []) {
|
|
1504
|
+
for (const provider of group) providers.add(provider);
|
|
1505
|
+
}
|
|
1506
|
+
for (const provider of deriveModelProfileMappedProviders(profile)) providers.add(provider);
|
|
1507
|
+
if (profile.source !== "user") {
|
|
1508
|
+
const proxyProvider = tryResolveProxyProviderId(this.#settings);
|
|
1509
|
+
if (proxyProvider !== undefined) providers.add(proxyProvider);
|
|
1510
|
+
}
|
|
1390
1511
|
const bindings = resolveProfileBindings(profile);
|
|
1391
1512
|
const values = [
|
|
1392
1513
|
...(bindings.defaultSelector ? [bindings.defaultSelector] : []),
|
|
@@ -1395,7 +1516,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
1395
1516
|
];
|
|
1396
1517
|
for (const value of values) {
|
|
1397
1518
|
for (const selector of normalizeModelSelectorValue(value)) {
|
|
1398
|
-
const resolved = resolveModelRoleValue(selector,
|
|
1519
|
+
const resolved = resolveModelRoleValue(selector, availableModels, {
|
|
1399
1520
|
settings: this.#settings,
|
|
1400
1521
|
modelRegistry: this.#modelRegistry,
|
|
1401
1522
|
aliasIntent: "preset-equivalent",
|
|
@@ -1419,6 +1540,12 @@ export class ModelSelectorComponent extends Container {
|
|
|
1419
1540
|
}
|
|
1420
1541
|
}),
|
|
1421
1542
|
);
|
|
1543
|
+
if (
|
|
1544
|
+
this.#disposed ||
|
|
1545
|
+
this.#viewMode !== "presets" ||
|
|
1546
|
+
refreshGeneration !== this.#providerAuthRefreshGeneration
|
|
1547
|
+
)
|
|
1548
|
+
return;
|
|
1422
1549
|
this.#providerAuthById = new Map(entries);
|
|
1423
1550
|
const profileAuthEntries = await Promise.all(
|
|
1424
1551
|
[...this.#getPresetGroups().values()].flat().map(async profile => {
|
|
@@ -1436,7 +1563,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
1436
1563
|
try {
|
|
1437
1564
|
const resolution = await resolveModelChainWithAuth(
|
|
1438
1565
|
normalizeModelSelectorValue(value),
|
|
1439
|
-
|
|
1566
|
+
resolutionRegistry,
|
|
1440
1567
|
this.#settings,
|
|
1441
1568
|
this.#authSessionId,
|
|
1442
1569
|
{
|
|
@@ -1455,11 +1582,23 @@ export class ModelSelectorComponent extends Container {
|
|
|
1455
1582
|
return [profile.name, available.every(Boolean)] as const;
|
|
1456
1583
|
}),
|
|
1457
1584
|
);
|
|
1585
|
+
if (
|
|
1586
|
+
this.#disposed ||
|
|
1587
|
+
this.#viewMode !== "presets" ||
|
|
1588
|
+
refreshGeneration !== this.#providerAuthRefreshGeneration
|
|
1589
|
+
)
|
|
1590
|
+
return;
|
|
1458
1591
|
this.#bareProfileAuthByName = new Map(profileAuthEntries);
|
|
1459
1592
|
} finally {
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1593
|
+
if (
|
|
1594
|
+
!this.#disposed &&
|
|
1595
|
+
this.#viewMode === "presets" &&
|
|
1596
|
+
refreshGeneration === this.#providerAuthRefreshGeneration
|
|
1597
|
+
) {
|
|
1598
|
+
this.#providerAuthPending = false;
|
|
1599
|
+
this.#renderPresetLanding();
|
|
1600
|
+
this.#tui.requestRender();
|
|
1601
|
+
}
|
|
1463
1602
|
}
|
|
1464
1603
|
}
|
|
1465
1604
|
|
|
@@ -1676,6 +1815,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
1676
1815
|
this.#smartRoutingPanel = undefined;
|
|
1677
1816
|
this.#viewMode = "presets";
|
|
1678
1817
|
this.#presetCursor = Math.min(this.#presetCursor, Math.max(0, this.#getPresetRows().length - 1));
|
|
1818
|
+
void this.#refreshProviderAuth();
|
|
1679
1819
|
this.#renderPresetLanding();
|
|
1680
1820
|
this.#tui.requestRender();
|
|
1681
1821
|
}
|
|
@@ -56,6 +56,7 @@ export class OAuthSelectorComponent extends Container {
|
|
|
56
56
|
#onCancelCallback: () => void;
|
|
57
57
|
#statusMessage: string | undefined;
|
|
58
58
|
#validateAuthCallback?: (providerId: string) => Promise<boolean>;
|
|
59
|
+
#onValidationError?: (error: unknown) => boolean;
|
|
59
60
|
#requestRenderCallback?: () => void;
|
|
60
61
|
#authState: Map<string, "checking" | "valid" | "invalid"> = new Map();
|
|
61
62
|
#externalCredentialCandidates: ImportableCredential[] = [];
|
|
@@ -76,6 +77,7 @@ export class OAuthSelectorComponent extends Container {
|
|
|
76
77
|
onCancel: () => void,
|
|
77
78
|
options?: {
|
|
78
79
|
validateAuth?: (providerId: string) => Promise<boolean>;
|
|
80
|
+
onValidationError?: (error: unknown) => boolean;
|
|
79
81
|
requestRender?: () => void;
|
|
80
82
|
externalCredentialCandidates?: ImportableCredential[];
|
|
81
83
|
} & Partial<OAuthSelectorAccountOptions>,
|
|
@@ -86,6 +88,7 @@ export class OAuthSelectorComponent extends Container {
|
|
|
86
88
|
this.#onSelectCallback = onSelect;
|
|
87
89
|
this.#onCancelCallback = onCancel;
|
|
88
90
|
this.#validateAuthCallback = options?.validateAuth;
|
|
91
|
+
this.#onValidationError = options?.onValidationError;
|
|
89
92
|
this.#requestRenderCallback = options?.requestRender;
|
|
90
93
|
this.#externalCredentialCandidates = options?.externalCredentialCandidates ?? [];
|
|
91
94
|
this.#accountProviderId = options?.accountProviderId;
|
|
@@ -194,7 +197,9 @@ export class OAuthSelectorComponent extends Container {
|
|
|
194
197
|
let isValid = false;
|
|
195
198
|
try {
|
|
196
199
|
isValid = await this.#validateAuthCallback(providerId);
|
|
197
|
-
} catch {
|
|
200
|
+
} catch (error) {
|
|
201
|
+
if (generation !== this.#validationGeneration) return;
|
|
202
|
+
if (this.#onValidationError?.(error)) return;
|
|
198
203
|
isValid = false;
|
|
199
204
|
}
|
|
200
205
|
if (generation !== this.#validationGeneration) return;
|
|
@@ -1009,6 +1009,7 @@ export class SettingsSelectorComponent extends Container {
|
|
|
1009
1009
|
options = [
|
|
1010
1010
|
{ value: "en", label: uiString(language, "settings.language.english") },
|
|
1011
1011
|
{ value: "ko", label: uiString(language, "settings.language.korean") },
|
|
1012
|
+
{ value: "zh", label: uiString(language, "settings.language.chinese") },
|
|
1012
1013
|
{ value: "ja", label: uiString(language, "settings.language.japanese") },
|
|
1013
1014
|
];
|
|
1014
1015
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shortest recognizable model label, derived purely from the model id.
|
|
3
|
+
*
|
|
4
|
+
* The rail and the footer both render this form, so a narrow terminal spends
|
|
5
|
+
* its cells on the part of the id that actually identifies the model:
|
|
6
|
+
*
|
|
7
|
+
* `anthropic/claude-sonnet-4-5-20250929` -> `sonnet-4.5`
|
|
8
|
+
* `openai/gpt-4o-2024-05-13` -> `gpt-4o`
|
|
9
|
+
* `google/gemini-3-pro` -> `gemini-3-pro`
|
|
10
|
+
* `qwen2.5:7b` -> `qwen2.5:7b`
|
|
11
|
+
*
|
|
12
|
+
* Deliberately a pure heuristic with no curated override table: every provider
|
|
13
|
+
* in `models.json` must degrade sanely without per-model maintenance. The
|
|
14
|
+
* function never returns an empty string — an id that the heuristic would
|
|
15
|
+
* strip to nothing falls back to the previous, longer form.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Vendor family words that carry no information once the provider prefix is
|
|
20
|
+
* gone. Deliberately minimal: `gpt` and `gemini` are part of how those models
|
|
21
|
+
* are named and are kept.
|
|
22
|
+
*/
|
|
23
|
+
const VENDOR_FAMILY_WORDS: ReadonlySet<string> = new Set(["claude"]);
|
|
24
|
+
|
|
25
|
+
/** Trailing snapshot dates and build stamps: `-20250929`, `-2024-05-13`, `-v2`. */
|
|
26
|
+
const DATE_OR_BUILD_SUFFIX = /-(?:\d{4}-\d{2}-\d{2}|\d{8}|\d{6}|v\d+(?:\.\d+)*)$/;
|
|
27
|
+
|
|
28
|
+
/** Split version parts read as one number: `sonnet-4-5` -> `sonnet-4.5`. */
|
|
29
|
+
const SPLIT_VERSION = /(\d)-(\d)/g;
|
|
30
|
+
|
|
31
|
+
export const NO_MODEL_LABEL = "no-model";
|
|
32
|
+
|
|
33
|
+
export function shortenModelId(rawId: string | undefined | null): string {
|
|
34
|
+
const raw = (rawId ?? "").trim();
|
|
35
|
+
if (!raw) return NO_MODEL_LABEL;
|
|
36
|
+
|
|
37
|
+
// Provider prefix: `anthropic/claude-…`, `openrouter/anthropic/claude-…`.
|
|
38
|
+
const afterProvider = raw.slice(raw.lastIndexOf("/") + 1).trim() || raw;
|
|
39
|
+
|
|
40
|
+
const withoutDate = afterProvider.replace(DATE_OR_BUILD_SUFFIX, "") || afterProvider;
|
|
41
|
+
|
|
42
|
+
let name = withoutDate;
|
|
43
|
+
const dashIndex = name.indexOf("-");
|
|
44
|
+
if (dashIndex > 0) {
|
|
45
|
+
const head = name.slice(0, dashIndex).toLowerCase();
|
|
46
|
+
const rest = name.slice(dashIndex + 1);
|
|
47
|
+
if (rest && VENDOR_FAMILY_WORDS.has(head)) name = rest;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
name = name.replace(SPLIT_VERSION, "$1.$2");
|
|
51
|
+
|
|
52
|
+
return name || withoutDate || afterProvider;
|
|
53
|
+
}
|