@gajae-code/coding-agent 0.16.3 → 0.16.4
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 +59 -0
- package/dist/types/cli/args.d.ts +2 -2
- package/dist/types/cli/root-flags.d.ts +4 -2
- package/dist/types/cli.d.ts +1 -1
- package/dist/types/commands/launch.d.ts +1 -1
- package/dist/types/config/autorouting-tier-map.d.ts +3 -0
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/config/model-resolver.d.ts +9 -0
- package/dist/types/config/settings-schema.d.ts +29 -1
- package/dist/types/discovery/claude-plugins.d.ts +3 -1
- package/dist/types/discovery/helpers.d.ts +2 -0
- package/dist/types/extensibility/gjc-plugins/mcp-policy.d.ts +16 -0
- package/dist/types/extensibility/gjc-plugins/paths.d.ts +2 -0
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +1 -1
- package/dist/types/extensibility/shared-events.d.ts +1 -1
- package/dist/types/gjc-runtime/session-state-lock.d.ts +2 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -2
- package/dist/types/modes/components/settings-selector.d.ts +6 -1
- package/dist/types/modes/components/status-line/command.d.ts +35 -0
- package/dist/types/modes/components/status-line/index.d.ts +1 -0
- package/dist/types/modes/components/status-line/types.d.ts +5 -0
- package/dist/types/modes/components/tool-status-header.d.ts +14 -0
- package/dist/types/modes/controllers/event-controller.d.ts +6 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/runtime-mcp/client.d.ts +9 -1
- package/dist/types/runtime-mcp/manager.d.ts +7 -1
- package/dist/types/runtime-mcp/pool.d.ts +22 -3
- package/dist/types/runtime-mcp/transports/stdio.d.ts +0 -4
- package/dist/types/runtime-mcp/types.d.ts +22 -0
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +12 -0
- package/dist/types/sdk/bus/slack-live-provider.d.ts +6 -0
- package/dist/types/sdk/bus/slack-provider.d.ts +12 -0
- package/dist/types/sdk/host/session-runtime.d.ts +23 -0
- package/dist/types/sdk/session-api-key.d.ts +26 -0
- package/dist/types/sdk/transport/serve-cli.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +9 -0
- package/dist/types/setup/paseo/announce.d.ts +111 -0
- package/dist/types/setup/paseo/setup-deps.d.ts +2 -1
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +2 -1
- package/dist/types/tools/fetch.d.ts +7 -0
- package/dist/types/tools/index.d.ts +8 -0
- package/dist/types/tools/path-utils.d.ts +2 -0
- package/dist/types/tools/search.d.ts +6 -0
- package/package.json +7 -7
- package/scripts/capture-status-line-custom-editor-showcase.ts +151 -0
- package/scripts/generate-sdk-operation-inventory.ts +9 -0
- package/scripts/verify-status-line-custom-editor-showcase.ts +309 -0
- package/src/cli/args.ts +20 -9
- package/src/cli/root-flags.ts +5 -3
- package/src/config/autorouting-tier-map.ts +1 -0
- package/src/config/file-lock.ts +181 -17
- package/src/config/model-preset-registry.ts +5 -3
- package/src/config/model-profiles.ts +40 -0
- package/src/config/model-registry.ts +57 -11
- package/src/config/model-resolver.ts +48 -0
- package/src/config/settings-schema.ts +33 -1
- package/src/config/settings.ts +1 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +11 -11
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +13 -2
- package/src/discovery/builtin.ts +13 -4
- package/src/discovery/claude-plugins.ts +12 -4
- package/src/discovery/helpers.ts +10 -2
- package/src/extensibility/gjc-plugins/compiler.ts +208 -58
- package/src/extensibility/gjc-plugins/installer.ts +13 -12
- package/src/extensibility/gjc-plugins/mcp-policy.ts +81 -52
- package/src/extensibility/gjc-plugins/paths.ts +13 -0
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +758 -16
- package/src/extensibility/runtime-skill-discovery.ts +77 -151
- package/src/extensibility/shared-events.ts +1 -1
- package/src/extensibility/skills.ts +44 -16
- package/src/gjc-runtime/repository-binding.ts +1 -1
- package/src/gjc-runtime/session-state-lock.ts +92 -2
- package/src/internal-urls/docs-index.generated.ts +7 -7
- package/src/main.ts +72 -34
- package/src/modes/DESIGN.md +54 -0
- package/src/modes/components/assistant-message.ts +22 -9
- package/src/modes/components/model-selector.ts +166 -33
- package/src/modes/components/settings-selector.ts +1075 -235
- package/src/modes/components/status-line/command.ts +190 -0
- package/src/modes/components/status-line/index.ts +1 -0
- package/src/modes/components/status-line/segments.ts +32 -5
- package/src/modes/components/status-line/types.ts +5 -0
- package/src/modes/components/tool-status-header.ts +393 -21
- package/src/modes/controllers/command-controller.ts +2 -0
- package/src/modes/controllers/event-controller.ts +148 -5
- package/src/modes/controllers/selector-controller.ts +21 -0
- package/src/modes/interactive-mode.ts +47 -1
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +1 -0
- package/src/prompts/system/system-prompt.md +8 -0
- package/src/prompts/tools/search.md +2 -0
- package/src/runtime-mcp/client.ts +60 -17
- package/src/runtime-mcp/manager.ts +171 -14
- package/src/runtime-mcp/pool.test.ts +209 -0
- package/src/runtime-mcp/pool.ts +224 -23
- package/src/runtime-mcp/transports/stdio.ts +158 -24
- package/src/runtime-mcp/types.ts +24 -0
- package/src/sdk/broker/broker.ts +5 -4
- package/src/sdk/bus/chat-daemon-control.ts +1 -1
- package/src/sdk/bus/chat-daemon-runtime.ts +37 -1
- package/src/sdk/bus/slack-daemon.ts +92 -2
- package/src/sdk/bus/slack-live-provider.ts +12 -0
- package/src/sdk/bus/slack-provider.ts +5 -0
- package/src/sdk/cli/session-cli.ts +9 -6
- package/src/sdk/host/session-runtime.test.ts +2 -2
- package/src/sdk/host/session-runtime.ts +275 -43
- package/src/sdk/protocol/operation-inventory.generated.json +66 -0
- package/src/sdk/session-api-key.ts +94 -0
- package/src/sdk/session.ts +73 -29
- package/src/sdk/transport/serve-cli.ts +3 -3
- package/src/session/agent-session.ts +123 -38
- package/src/session/contribution-prep.ts +214 -21
- package/src/setup/paseo/announce.ts +450 -0
- package/src/setup/paseo/setup-deps.ts +15 -3
- package/src/slash-commands/builtin-registry.ts +1 -0
- package/src/system-prompt.ts +4 -0
- package/src/task/executor.ts +2 -1
- package/src/task/index.ts +17 -4
- package/src/tools/bash.ts +35 -8
- package/src/tools/cron.ts +1 -1
- package/src/tools/fetch.ts +29 -1
- package/src/tools/index.ts +8 -0
- package/src/tools/job.ts +8 -2
- package/src/tools/monitor.ts +8 -2
- package/src/tools/path-utils.ts +7 -0
- package/src/tools/search.ts +116 -58
- package/src/tools/skill.ts +22 -2
- package/src/tools/subagent.ts +3 -1
- package/src/tools/tool-catalog.generated.ts +8 -1
- package/src/web/insane/bridge.ts +113 -23
- package/src/web/search/providers/perplexity.ts +102 -41
package/src/main.ts
CHANGED
|
@@ -31,7 +31,15 @@ import { selectSession } from "./cli/session-picker";
|
|
|
31
31
|
import { findConfigFile } from "./config";
|
|
32
32
|
import { activateModelProfile, ModelProfileCredentialError } from "./config/model-profile-activation";
|
|
33
33
|
import { ModelRegistry, ModelsConfigFile } from "./config/model-registry";
|
|
34
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
parseModelString,
|
|
36
|
+
refreshMissingQualifiedModelProviders,
|
|
37
|
+
resolveCliModel,
|
|
38
|
+
resolveModelRoleValue,
|
|
39
|
+
resolveModelScope,
|
|
40
|
+
resolveStartupModelRefreshSelectors,
|
|
41
|
+
type ScopedModel,
|
|
42
|
+
} from "./config/model-resolver";
|
|
35
43
|
import { selectorHead } from "./config/model-selector-value";
|
|
36
44
|
import { getDefault, type SettingPath, Settings, settings } from "./config/settings";
|
|
37
45
|
import { resolveMachineLocalUpdateChannel, type UpdateChannel } from "./config/update-channel";
|
|
@@ -49,7 +57,7 @@ import type { PrintModeOptions } from "./modes/print-mode";
|
|
|
49
57
|
import { initTheme, stopThemeWatcher } from "./modes/theme/theme";
|
|
50
58
|
import type { SubmittedUserInput } from "./modes/types";
|
|
51
59
|
import { applyCliRuntimeApiKeyOverride } from "./runtime-api-key";
|
|
52
|
-
import { parseCliCredentialSelector } from "./runtime-credential-selector";
|
|
60
|
+
import { type CliCredentialSelector, parseCliCredentialSelector } from "./runtime-credential-selector";
|
|
53
61
|
import type { MCPManager } from "./runtime-mcp";
|
|
54
62
|
import {
|
|
55
63
|
type CreateAgentSessionOptions,
|
|
@@ -1645,6 +1653,66 @@ export async function runRootCommand(
|
|
|
1645
1653
|
)
|
|
1646
1654
|
: undefined;
|
|
1647
1655
|
|
|
1656
|
+
if (parsedArgs.apiKey && parsedArgs.credential) {
|
|
1657
|
+
process.stderr.write(`${chalk.red("--api-key and --credential cannot be used together")}\n`);
|
|
1658
|
+
process.exit(1);
|
|
1659
|
+
}
|
|
1660
|
+
if (parsedArgs.credential && parsedArgs.preferCredential) {
|
|
1661
|
+
process.stderr.write(`${chalk.red("--credential and --prefer-credential cannot be used together")}\n`);
|
|
1662
|
+
process.exit(1);
|
|
1663
|
+
}
|
|
1664
|
+
if (parsedArgs.apiKey && parsedArgs.preferCredential) {
|
|
1665
|
+
process.stderr.write(`${chalk.red("--api-key and --prefer-credential cannot be used together")}\n`);
|
|
1666
|
+
process.exit(1);
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
let credentialSelector: CliCredentialSelector | undefined;
|
|
1670
|
+
let preferredCredentialSelector: CliCredentialSelector | undefined;
|
|
1671
|
+
try {
|
|
1672
|
+
credentialSelector = parsedArgs.credential ? parseCliCredentialSelector(parsedArgs.credential) : undefined;
|
|
1673
|
+
preferredCredentialSelector = parsedArgs.preferCredential
|
|
1674
|
+
? parseCliCredentialSelector(parsedArgs.preferCredential)
|
|
1675
|
+
: undefined;
|
|
1676
|
+
if (preferredCredentialSelector) {
|
|
1677
|
+
const provider =
|
|
1678
|
+
preferredCredentialSelector.provider ??
|
|
1679
|
+
authStorage.resolveRuntimePreferredCredentialSelectorProvider(preferredCredentialSelector.selector);
|
|
1680
|
+
authStorage.setRuntimePreferredCredentialSelector(provider, preferredCredentialSelector.selector);
|
|
1681
|
+
}
|
|
1682
|
+
} catch (error) {
|
|
1683
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1684
|
+
process.stderr.write(`${chalk.red(message)}\n`);
|
|
1685
|
+
process.exit(1);
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
const hasRootStartupProfile = Boolean(settingsInstance.get("modelProfile.default") || parsedArgs.mpreset);
|
|
1689
|
+
const startupModelSelectors = resolveStartupModelRefreshSelectors(
|
|
1690
|
+
{
|
|
1691
|
+
model: parsedArgs.model,
|
|
1692
|
+
provider: parsedArgs.provider,
|
|
1693
|
+
credential: parsedArgs.credential,
|
|
1694
|
+
continue: parsedArgs.continue,
|
|
1695
|
+
resume: parsedArgs.resume,
|
|
1696
|
+
hasStartupProfile: hasRootStartupProfile,
|
|
1697
|
+
},
|
|
1698
|
+
settingsInstance,
|
|
1699
|
+
);
|
|
1700
|
+
const explicitStartupModelSelector = parsedArgs.model
|
|
1701
|
+
? typeof startupModelSelectors === "string"
|
|
1702
|
+
? startupModelSelectors
|
|
1703
|
+
: parsedArgs.model
|
|
1704
|
+
: undefined;
|
|
1705
|
+
if (parsedArgs.apiKey && explicitStartupModelSelector) {
|
|
1706
|
+
const parsedModel = parseModelString(explicitStartupModelSelector);
|
|
1707
|
+
if (parsedModel) authStorage.setRuntimeApiKey(parsedModel.provider, parsedArgs.apiKey);
|
|
1708
|
+
}
|
|
1709
|
+
await logger.time(
|
|
1710
|
+
"refreshStartupModelProviders",
|
|
1711
|
+
refreshMissingQualifiedModelProviders,
|
|
1712
|
+
startupModelSelectors,
|
|
1713
|
+
modelRegistry,
|
|
1714
|
+
);
|
|
1715
|
+
|
|
1648
1716
|
let scopedModels: ScopedModel[] = [];
|
|
1649
1717
|
const modelPatterns = parsedArgs.models ?? settingsInstance.get("enabledModels");
|
|
1650
1718
|
const modelMatchPreferences = {
|
|
@@ -1779,7 +1847,6 @@ export async function runRootCommand(
|
|
|
1779
1847
|
if (isInteractive && sessionOptions.mcpConfigPath) {
|
|
1780
1848
|
sessionOptions.deferMcpConfigStartup = true;
|
|
1781
1849
|
}
|
|
1782
|
-
const hasRootStartupProfile = Boolean(settingsInstance.get("modelProfile.default") || parsedArgs.mpreset);
|
|
1783
1850
|
// ACP is not carved out: `gjc acp` is broker-backed and never builds a local
|
|
1784
1851
|
// session here, and the broker-launched lifecycle child defers memory startup
|
|
1785
1852
|
// unconditionally (createLifecycleAgentSession) so readiness never waits on
|
|
@@ -1790,37 +1857,8 @@ export async function runRootCommand(
|
|
|
1790
1857
|
const acpStartupOptions = mode === "acp" ? resolveAcpStartupOptions(parsedArgs, sessionOptions) : undefined;
|
|
1791
1858
|
|
|
1792
1859
|
// Handle CLI credential selection and --api-key as runtime overrides (not persisted).
|
|
1793
|
-
if (
|
|
1794
|
-
|
|
1795
|
-
process.exit(1);
|
|
1796
|
-
}
|
|
1797
|
-
if (parsedArgs.credential && parsedArgs.preferCredential) {
|
|
1798
|
-
process.stderr.write(`${chalk.red("--credential and --prefer-credential cannot be used together")}\n`);
|
|
1799
|
-
process.exit(1);
|
|
1800
|
-
}
|
|
1801
|
-
if (parsedArgs.apiKey && parsedArgs.preferCredential) {
|
|
1802
|
-
process.stderr.write(`${chalk.red("--api-key and --prefer-credential cannot be used together")}\n`);
|
|
1803
|
-
process.exit(1);
|
|
1804
|
-
}
|
|
1805
|
-
|
|
1806
|
-
if (parsedArgs.credential) {
|
|
1807
|
-
try {
|
|
1808
|
-
sessionOptions.credentialSelector = parseCliCredentialSelector(parsedArgs.credential);
|
|
1809
|
-
} catch (error) {
|
|
1810
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1811
|
-
process.stderr.write(`${chalk.red(message)}\n`);
|
|
1812
|
-
process.exit(1);
|
|
1813
|
-
}
|
|
1814
|
-
}
|
|
1815
|
-
if (parsedArgs.preferCredential) {
|
|
1816
|
-
try {
|
|
1817
|
-
sessionOptions.preferredCredentialSelector = parseCliCredentialSelector(parsedArgs.preferCredential);
|
|
1818
|
-
} catch (error) {
|
|
1819
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1820
|
-
process.stderr.write(`${chalk.red(message)}\n`);
|
|
1821
|
-
process.exit(1);
|
|
1822
|
-
}
|
|
1823
|
-
}
|
|
1860
|
+
if (credentialSelector) sessionOptions.credentialSelector = credentialSelector;
|
|
1861
|
+
if (preferredCredentialSelector) sessionOptions.preferredCredentialSelector = preferredCredentialSelector;
|
|
1824
1862
|
if (parsedArgs.apiKey) {
|
|
1825
1863
|
if (!sessionOptions.model && !sessionOptions.modelPattern) {
|
|
1826
1864
|
process.stderr.write(
|
package/src/modes/DESIGN.md
CHANGED
|
@@ -347,6 +347,60 @@ CJK/Latin todo, IRC, status, and BTW text break at semantic phrase boundaries,
|
|
|
347
347
|
not inside a phase/action/status label, short identifier, or grapheme cluster.
|
|
348
348
|
Those defects, width overflow, overlap, hidden focus/cursor, and lost anchors
|
|
349
349
|
are blocking visual-QA failures.
|
|
350
|
+
|
|
351
|
+
### Status Line Custom Editor simulation
|
|
352
|
+
|
|
353
|
+
The Appearance tab's Status Line Custom Editor is a spatial editor, not a
|
|
354
|
+
generic setting row list. It renders a simulated statusbar with left/right
|
|
355
|
+
segment areas, a hidden-segment palette, visible Separator and segment-option
|
|
356
|
+
controls, and Confirm/Exit actions. It must not render legacy `Segment`,
|
|
357
|
+
`Move left`, or `Move right` rows.
|
|
358
|
+
|
|
359
|
+
The editor owns transient state locally: focused statusbar slot, focused palette
|
|
360
|
+
item, focused root control, open visible-choice panel, picked segment, floating
|
|
361
|
+
ghost, empty origin slot, and overflow warning. Durable settings remain only the
|
|
362
|
+
existing `statusLine.leftSegments`, `statusLine.rightSegments`,
|
|
363
|
+
`statusLine.separator`, and `statusLine.segmentOptions` keys; Confirm persists
|
|
364
|
+
through the existing settings guard, while Exit or root-state Escape restores
|
|
365
|
+
the previous preview and closes.
|
|
366
|
+
|
|
367
|
+
Focus variants are `statusbar`, `palette`, `separator-control`,
|
|
368
|
+
`option-control(path)`, `choice(target, focusedValue, returnFocus)`, `confirm`,
|
|
369
|
+
and `exit`. Left/Right is owned by the editor while active and never switches
|
|
370
|
+
settings tabs. Enter selects/deselects, picks/drops, applies a focused choice,
|
|
371
|
+
or activates Confirm/Exit according to focus. Up/Down traverses palette,
|
|
372
|
+
visible root controls, actions, and choice candidates. Escape inside `choice`
|
|
373
|
+
returns to the opener without mutation; Escape everywhere else cancels and
|
|
374
|
+
restores. Delete only hides a focused visible statusbar segment.
|
|
375
|
+
|
|
376
|
+
Separator and every segment option are visible-choice controls, never cycle-only
|
|
377
|
+
rows. Descriptor data owns labels, allowed values, visible current-value
|
|
378
|
+
formatting, and typed draft application. Separator choices are Powerline, Thin
|
|
379
|
+
chevron, Slash, Pipe, Block, None, and ASCII. Boolean options show `true` and
|
|
380
|
+
`false`; enum options show their union values; `path.maxLength` shows `16`,
|
|
381
|
+
`24`, `32`, `40`, `50`, `60`, and `80`.
|
|
382
|
+
|
|
383
|
+
At constrained width the simulated statusbar renders as two rows and emits an
|
|
384
|
+
explicit overflow/wrap warning. ANSI-aware cell measurement remains mandatory;
|
|
385
|
+
ASCII/no-color output must retain textual focus, ghost/origin, selected choice,
|
|
386
|
+
and warning affordances.
|
|
387
|
+
|
|
388
|
+
Deterministic evidence belongs to
|
|
389
|
+
`test/fixtures/tui/status-line-custom-editor-showcase.ts`, captured by
|
|
390
|
+
`scripts/capture-status-line-custom-editor-showcase.ts --out
|
|
391
|
+
.gjc/qa/status-line-custom-editor-<run>` and verified by
|
|
392
|
+
`scripts/verify-status-line-custom-editor-showcase.ts --root
|
|
393
|
+
.gjc/qa/status-line-custom-editor-<run> --require-independent-review`. The
|
|
394
|
+
canonical states are `root-statusbar`, `picked-origin-slot`,
|
|
395
|
+
`palette-exact-insert`, `separator-control`, `separator-choice-focused`,
|
|
396
|
+
`separator-choice-applied`, `option-boolean-choice-focused`,
|
|
397
|
+
`option-enum-choice-focused`, `option-numeric-choice-focused`,
|
|
398
|
+
`option-choice-applied`, `exit-restored`, `confirm-persisted`,
|
|
399
|
+
`overflow-two-row-warning`, and `narrow-cjk`. Each state records
|
|
400
|
+
`terminal.txt`, `terminal-ansi.txt`, `terminal.html`, and `metadata.json`, plus
|
|
401
|
+
the root manifest. Missing state witnesses, ANSI/plain mismatch, invalid
|
|
402
|
+
metadata, and absent independent review are blocking visual-QA failures.
|
|
403
|
+
|
|
350
404
|
### Sticky viewport deterministic visual QA
|
|
351
405
|
|
|
352
406
|
`test/fixtures/tui/sticky-viewport-showcase.ts` is a fixed-clock, no-network,
|
|
@@ -106,16 +106,24 @@ export class AssistantMessageComponent extends Container {
|
|
|
106
106
|
#toolImagesRevision = 0;
|
|
107
107
|
#convertedToolImagesRevision = 0;
|
|
108
108
|
#disposed = false;
|
|
109
|
+
// Cached Markdown children can outlive their attachment to this assistant.
|
|
110
|
+
// Session/live-parent validity belongs to the owner, not the paint lifetime.
|
|
111
|
+
readonly #requestRepaint?: () => void;
|
|
109
112
|
constructor(
|
|
110
113
|
message?: AssistantMessage,
|
|
111
114
|
private hideThinkingBlock = false,
|
|
112
|
-
|
|
115
|
+
onImageUpdate?: () => void,
|
|
113
116
|
viewportAnchorId?: string,
|
|
114
117
|
private readonly onVisibleMutation?: () => void,
|
|
115
118
|
private readonly hintSession?: ProviderSafetyStopHintSession,
|
|
116
119
|
) {
|
|
117
120
|
super();
|
|
118
121
|
this.#viewportAnchorId = viewportAnchorId;
|
|
122
|
+
if (onImageUpdate) {
|
|
123
|
+
this.#requestRepaint = () => {
|
|
124
|
+
if (!this.#disposed) onImageUpdate();
|
|
125
|
+
};
|
|
126
|
+
}
|
|
119
127
|
|
|
120
128
|
this.#contentContainer = new Container();
|
|
121
129
|
this.addChild(this.#contentContainer);
|
|
@@ -172,6 +180,7 @@ export class AssistantMessageComponent extends Container {
|
|
|
172
180
|
}
|
|
173
181
|
|
|
174
182
|
#notifyVisibleMutation(): void {
|
|
183
|
+
if (this.#disposed) return;
|
|
175
184
|
const after = this.#visibleContentSignature();
|
|
176
185
|
if (after === this.#visibleSignature) return;
|
|
177
186
|
this.#visibleSignature = after;
|
|
@@ -186,6 +195,7 @@ export class AssistantMessageComponent extends Container {
|
|
|
186
195
|
}
|
|
187
196
|
|
|
188
197
|
override invalidate(): void {
|
|
198
|
+
if (this.#disposed) return;
|
|
189
199
|
super.invalidate();
|
|
190
200
|
if (this.#lastMessage) {
|
|
191
201
|
this.updateContent(this.#lastMessage, { streaming: this.#lastStreaming });
|
|
@@ -193,13 +203,14 @@ export class AssistantMessageComponent extends Container {
|
|
|
193
203
|
}
|
|
194
204
|
|
|
195
205
|
setHideThinkingBlock(hide: boolean): void {
|
|
206
|
+
if (this.#disposed) return;
|
|
196
207
|
if (this.hideThinkingBlock === hide) return;
|
|
197
208
|
this.hideThinkingBlock = hide;
|
|
198
209
|
this.#notifyVisibleMutation();
|
|
199
210
|
}
|
|
200
211
|
|
|
201
212
|
setToolResultImages(toolCallId: string, images: ImageContent[]): void {
|
|
202
|
-
if (!toolCallId) return;
|
|
213
|
+
if (this.#disposed || !toolCallId) return;
|
|
203
214
|
const validImages = images.filter(img => img.type === "image" && img.data && img.mimeType);
|
|
204
215
|
const previousImages = this.#toolImagesByCallId.get(toolCallId);
|
|
205
216
|
if (
|
|
@@ -268,7 +279,7 @@ export class AssistantMessageComponent extends Container {
|
|
|
268
279
|
this.#convertedKittyImages.set(key, { type: "image", data, mimeType: "image/png", source });
|
|
269
280
|
this.#convertedToolImagesRevision += 1;
|
|
270
281
|
if (this.#lastMessage) this.updateContent(this.#lastMessage, { streaming: this.#lastStreaming });
|
|
271
|
-
this
|
|
282
|
+
this.#requestRepaint?.();
|
|
272
283
|
})
|
|
273
284
|
.catch(() => {
|
|
274
285
|
if (
|
|
@@ -283,6 +294,7 @@ export class AssistantMessageComponent extends Container {
|
|
|
283
294
|
}
|
|
284
295
|
|
|
285
296
|
setUsageInfo(usage: Usage): void {
|
|
297
|
+
if (this.#disposed) return;
|
|
286
298
|
this.#usageInfo = usage;
|
|
287
299
|
if (this.#lastMessage) {
|
|
288
300
|
this.updateContent(this.#lastMessage, { streaming: this.#lastStreaming });
|
|
@@ -311,7 +323,7 @@ export class AssistantMessageComponent extends Container {
|
|
|
311
323
|
const cached = this.#contentBlocksCache.get(content);
|
|
312
324
|
if (cached?.source === content.text) {
|
|
313
325
|
if (cached.component instanceof Markdown) {
|
|
314
|
-
cached.component.setOnStaleThrottle(this
|
|
326
|
+
cached.component.setOnStaleThrottle(this.#requestRepaint);
|
|
315
327
|
cached.component.setStreaming(streaming);
|
|
316
328
|
}
|
|
317
329
|
return cached.component;
|
|
@@ -322,14 +334,14 @@ export class AssistantMessageComponent extends Container {
|
|
|
322
334
|
// instead of constructing a new one each chunk; combined with the markdown
|
|
323
335
|
// per-code-block highlight cache, appends no longer re-highlight the whole prefix.
|
|
324
336
|
if (!deepInterview && cached && cached.component instanceof Markdown) {
|
|
325
|
-
cached.component.setOnStaleThrottle(this
|
|
337
|
+
cached.component.setOnStaleThrottle(this.#requestRepaint);
|
|
326
338
|
cached.component.setText(trimmed, { streaming });
|
|
327
339
|
cached.source = content.text;
|
|
328
340
|
return cached.component;
|
|
329
341
|
}
|
|
330
342
|
const component = deepInterview ?? new Markdown(trimmed, 1, 0, getMarkdownTheme());
|
|
331
343
|
if (component instanceof Markdown) {
|
|
332
|
-
component.setOnStaleThrottle(this
|
|
344
|
+
component.setOnStaleThrottle(this.#requestRepaint);
|
|
333
345
|
component.setStreaming(streaming);
|
|
334
346
|
}
|
|
335
347
|
this.#contentBlocksCache.set(content, { source: content.text, component });
|
|
@@ -341,14 +353,14 @@ export class AssistantMessageComponent extends Container {
|
|
|
341
353
|
const cached = this.#contentBlocksCache.get(content);
|
|
342
354
|
if (cached?.source === content.thinking) {
|
|
343
355
|
if (cached.component instanceof Markdown) {
|
|
344
|
-
cached.component.setOnStaleThrottle(this
|
|
356
|
+
cached.component.setOnStaleThrottle(this.#requestRepaint);
|
|
345
357
|
cached.component.setStreaming(streaming);
|
|
346
358
|
}
|
|
347
359
|
return cached.component as Markdown;
|
|
348
360
|
}
|
|
349
361
|
const trimmed = elideRunawayThinkingRepetition(content.thinking.trim());
|
|
350
362
|
if (cached?.component instanceof Markdown) {
|
|
351
|
-
cached.component.setOnStaleThrottle(this
|
|
363
|
+
cached.component.setOnStaleThrottle(this.#requestRepaint);
|
|
352
364
|
cached.component.setText(trimmed, { streaming });
|
|
353
365
|
cached.source = content.thinking;
|
|
354
366
|
return cached.component;
|
|
@@ -357,7 +369,7 @@ export class AssistantMessageComponent extends Container {
|
|
|
357
369
|
color: (text: string) => theme.fg("thinkingText", text),
|
|
358
370
|
italic: true,
|
|
359
371
|
});
|
|
360
|
-
component.setOnStaleThrottle(this
|
|
372
|
+
component.setOnStaleThrottle(this.#requestRepaint);
|
|
361
373
|
component.setStreaming(streaming);
|
|
362
374
|
this.#contentBlocksCache.set(content, { source: content.thinking, component });
|
|
363
375
|
this.#reusableChildren.add(component);
|
|
@@ -437,6 +449,7 @@ export class AssistantMessageComponent extends Container {
|
|
|
437
449
|
}
|
|
438
450
|
|
|
439
451
|
updateContent(message: AssistantMessage, options?: AssistantMessageUpdateOptions): void {
|
|
452
|
+
if (this.#disposed) return;
|
|
440
453
|
this.#lastMessage = message;
|
|
441
454
|
this.#lastStreaming = options?.streaming ?? false;
|
|
442
455
|
|
|
@@ -410,6 +410,10 @@ export class ModelSelectorComponent extends Container {
|
|
|
410
410
|
#unsubscribeProviderOrderChanged: () => void = () => {};
|
|
411
411
|
#unsubscribeAuthGeneration: () => void = () => {};
|
|
412
412
|
#disposed = false;
|
|
413
|
+
#catalogLoaded = false;
|
|
414
|
+
#catalogLoadPromise?: Promise<void>;
|
|
415
|
+
#presetCatalogRefreshPending = false;
|
|
416
|
+
#openSmartRoutingAfterPresetRefresh = false;
|
|
413
417
|
/** Standalone smart-routing entry: cancel closes the selector instead of falling back to the preset landing. */
|
|
414
418
|
#smartRoutingOnly = false;
|
|
415
419
|
|
|
@@ -422,6 +426,8 @@ export class ModelSelectorComponent extends Container {
|
|
|
422
426
|
#presetScopeIndex: number = 0;
|
|
423
427
|
#providerAuthById = new Map<string, boolean>();
|
|
424
428
|
#bareProfileAuthByName = new Map<string, boolean>();
|
|
429
|
+
// Rebuilt with the provider and bare-selector snapshots on every catalog or credential refresh.
|
|
430
|
+
#profileAuthByName = new Map<string, boolean>();
|
|
425
431
|
#providerAuthPending: boolean = false;
|
|
426
432
|
#providerAuthRefreshGeneration = 0;
|
|
427
433
|
#presetLoginHint?: string;
|
|
@@ -545,21 +551,39 @@ export class ModelSelectorComponent extends Container {
|
|
|
545
551
|
this.#unsubscribeCatalogChanged = this.#modelRegistry.onCatalogChanged(() => {
|
|
546
552
|
if (this.#disposed) return;
|
|
547
553
|
if (this.#viewMode === "presets") {
|
|
548
|
-
this.#
|
|
554
|
+
if (this.#presetCatalogRefreshPending) return;
|
|
555
|
+
this.#rebuildRoleModels();
|
|
549
556
|
this.#clampPresetCursor();
|
|
550
557
|
void this.#refreshProviderAuth();
|
|
551
558
|
this.#renderPresetLanding();
|
|
552
559
|
this.#tui.requestRender();
|
|
553
560
|
return;
|
|
554
561
|
}
|
|
562
|
+
if (this.#viewMode === "smart-routing" && this.#smartRoutingPanel) return;
|
|
563
|
+
// The in-flight load already consumes the latest registry snapshot. Avoid
|
|
564
|
+
// materializing the same catalog again from its own change notification.
|
|
565
|
+
if (this.#catalogLoadPromise) return;
|
|
566
|
+
if (!this.#catalogLoaded) {
|
|
567
|
+
void this.#initializeCatalogView();
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
555
570
|
if (this.#refreshCatalogView()) this.#tui.requestRender();
|
|
556
571
|
});
|
|
557
572
|
}
|
|
558
573
|
|
|
559
|
-
//
|
|
560
|
-
//
|
|
574
|
+
// Proxy and provider-policy changes invalidate preset availability. Provider-order changes are
|
|
575
|
+
// advisory only while smart routing is mounted and must not discard its draft.
|
|
561
576
|
this.#unsubscribeProviderOrderChanged = this.#settings.onChanged?.(path => {
|
|
562
|
-
if (this.#disposed
|
|
577
|
+
if (this.#disposed) return;
|
|
578
|
+
if (
|
|
579
|
+
path === "modelProfile.proxyProvider" ||
|
|
580
|
+
path === "modelProfile.proxyMode" ||
|
|
581
|
+
path === "disabledProviders"
|
|
582
|
+
) {
|
|
583
|
+
if (this.#viewMode === "presets") void this.#refreshProviderAuth();
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
if (path !== "modelProviderOrder") return;
|
|
563
587
|
if (this.#viewMode !== "smart-routing") return;
|
|
564
588
|
const panel = this.#smartRoutingPanel;
|
|
565
589
|
if (!panel) return;
|
|
@@ -572,34 +596,21 @@ export class ModelSelectorComponent extends Container {
|
|
|
572
596
|
void this.#refreshProviderAuth();
|
|
573
597
|
}) ?? (() => {});
|
|
574
598
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
return;
|
|
582
|
-
}
|
|
583
|
-
if (this.#viewMode === "presets" && (this.#modelRegistry.getModelProfiles?.().size ?? 0) === 0) {
|
|
584
|
-
this.#viewMode = "models";
|
|
585
|
-
}
|
|
586
|
-
if (this.#viewMode === "presets") {
|
|
587
|
-
void this.#refreshProviderAuth();
|
|
599
|
+
if (this.#viewMode === "presets") {
|
|
600
|
+
// The landing only needs profile definitions and provider authentication.
|
|
601
|
+
// Refresh static configuration without enumerating and canonicalizing the
|
|
602
|
+
// full browser catalog, then resolve preset authentication once.
|
|
603
|
+
this.#presetCatalogRefreshPending = true;
|
|
604
|
+
if ((this.#modelRegistry.getModelProfiles?.().size ?? 0) > 0) {
|
|
588
605
|
this.#renderPresetLanding();
|
|
589
606
|
} else {
|
|
590
|
-
this.#
|
|
591
|
-
// Always apply the current search query — the user may have typed
|
|
592
|
-
// while models were loading asynchronously.
|
|
593
|
-
const currentQuery = this.#searchInput.getValue();
|
|
594
|
-
if (currentQuery) {
|
|
595
|
-
this.#filterModels(currentQuery);
|
|
596
|
-
} else {
|
|
597
|
-
this.#updateList();
|
|
598
|
-
}
|
|
607
|
+
this.#listContainer.addChild(new Text(theme.fg("muted", "Loading model presets..."), 0, 0));
|
|
599
608
|
}
|
|
600
|
-
// Request re-render after models are loaded
|
|
601
609
|
this.#tui.requestRender();
|
|
602
|
-
|
|
610
|
+
void this.#initializePresetLanding();
|
|
611
|
+
} else {
|
|
612
|
+
void this.#initializeCatalogView();
|
|
613
|
+
}
|
|
603
614
|
}
|
|
604
615
|
|
|
605
616
|
override dispose(): void {
|
|
@@ -1332,6 +1343,10 @@ export class ModelSelectorComponent extends Container {
|
|
|
1332
1343
|
return this.#modelRegistry.getModelProfile?.(name) ?? this.#modelRegistry.getModelProfiles?.().get(name);
|
|
1333
1344
|
}
|
|
1334
1345
|
|
|
1346
|
+
#isPresetAvailabilityPending(): boolean {
|
|
1347
|
+
return this.#presetCatalogRefreshPending || this.#providerAuthPending;
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1335
1350
|
#isProviderAuthenticated(providerId: string): boolean | undefined {
|
|
1336
1351
|
return this.#providerAuthById.get(providerId);
|
|
1337
1352
|
}
|
|
@@ -1405,9 +1420,13 @@ export class ModelSelectorComponent extends Container {
|
|
|
1405
1420
|
return [...missing].sort((a, b) => a.localeCompare(b));
|
|
1406
1421
|
}
|
|
1407
1422
|
|
|
1408
|
-
#isPresetAuthenticated(
|
|
1423
|
+
#isPresetAuthenticated(
|
|
1424
|
+
profileOrProfiles: ModelProfileDefinition | ModelProfileDefinition[],
|
|
1425
|
+
computeCurrent = false,
|
|
1426
|
+
): boolean {
|
|
1409
1427
|
const profiles = Array.isArray(profileOrProfiles) ? profileOrProfiles : [profileOrProfiles];
|
|
1410
1428
|
return profiles.every(profile => {
|
|
1429
|
+
if (!computeCurrent) return this.#profileAuthByName.get(profile.name) === true;
|
|
1411
1430
|
if (profile.source !== "user") {
|
|
1412
1431
|
if (inspectProxyProviderId(this.#settings).status === "invalid") return false;
|
|
1413
1432
|
const proxyProvider = tryResolveProxyProviderId(this.#settings);
|
|
@@ -1503,6 +1522,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
1503
1522
|
const refreshGeneration = ++this.#providerAuthRefreshGeneration;
|
|
1504
1523
|
this.#providerAuthById = new Map();
|
|
1505
1524
|
this.#bareProfileAuthByName = new Map();
|
|
1525
|
+
this.#profileAuthByName = new Map();
|
|
1506
1526
|
const availableModels = this.#getProfileAvailableModels();
|
|
1507
1527
|
const resolutionRegistry = this.#createProfileResolutionRegistry(availableModels);
|
|
1508
1528
|
const providers = new Set<string>();
|
|
@@ -1612,6 +1632,11 @@ export class ModelSelectorComponent extends Container {
|
|
|
1612
1632
|
)
|
|
1613
1633
|
return;
|
|
1614
1634
|
this.#bareProfileAuthByName = new Map(profileAuthEntries);
|
|
1635
|
+
this.#profileAuthByName = new Map(
|
|
1636
|
+
[...this.#getPresetGroups().values()]
|
|
1637
|
+
.flat()
|
|
1638
|
+
.map(profile => [profile.name, this.#isPresetAuthenticated(profile, true)]),
|
|
1639
|
+
);
|
|
1615
1640
|
} finally {
|
|
1616
1641
|
if (
|
|
1617
1642
|
!this.#disposed &&
|
|
@@ -1684,6 +1709,39 @@ export class ModelSelectorComponent extends Container {
|
|
|
1684
1709
|
this.#searchInput.setValue(value);
|
|
1685
1710
|
}
|
|
1686
1711
|
|
|
1712
|
+
async #initializePresetLanding(): Promise<void> {
|
|
1713
|
+
this.#presetCatalogRefreshPending = true;
|
|
1714
|
+
let refreshFailed = false;
|
|
1715
|
+
try {
|
|
1716
|
+
const refreshStatic = this.#modelRegistry.refreshStatic;
|
|
1717
|
+
if (typeof refreshStatic === "function") await refreshStatic.call(this.#modelRegistry);
|
|
1718
|
+
this.#errorMessage = this.#modelRegistry.getError();
|
|
1719
|
+
} catch (error) {
|
|
1720
|
+
refreshFailed = true;
|
|
1721
|
+
this.#errorMessage = error instanceof Error ? error.message : String(error);
|
|
1722
|
+
} finally {
|
|
1723
|
+
this.#presetCatalogRefreshPending = false;
|
|
1724
|
+
}
|
|
1725
|
+
if (this.#disposed || this.#viewMode !== "presets") return;
|
|
1726
|
+
if ((this.#modelRegistry.getModelProfiles?.().size ?? 0) === 0) {
|
|
1727
|
+
this.#viewMode = "models";
|
|
1728
|
+
void this.#initializeCatalogView();
|
|
1729
|
+
return;
|
|
1730
|
+
}
|
|
1731
|
+
this.#rebuildRoleModels();
|
|
1732
|
+
this.#clampPresetCursor();
|
|
1733
|
+
if (this.#openSmartRoutingAfterPresetRefresh) {
|
|
1734
|
+
this.#openSmartRoutingAfterPresetRefresh = false;
|
|
1735
|
+
if (!refreshFailed) {
|
|
1736
|
+
this.#enterSmartRoutingMode();
|
|
1737
|
+
return;
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
void this.#refreshProviderAuth();
|
|
1741
|
+
this.#renderPresetLanding();
|
|
1742
|
+
this.#tui.requestRender();
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1687
1745
|
#switchToModelMode(seed?: string, options?: { imageRoleFilter?: boolean }): void {
|
|
1688
1746
|
this.#viewMode = "models";
|
|
1689
1747
|
this.#expandedPresetProviderId = undefined;
|
|
@@ -1695,8 +1753,52 @@ export class ModelSelectorComponent extends Container {
|
|
|
1695
1753
|
this.#selectedIndex = 0;
|
|
1696
1754
|
this.#imageRoleFilter = options?.imageRoleFilter ?? false;
|
|
1697
1755
|
this.#setSearchInputValue(seed ?? this.#searchInput.getValue());
|
|
1756
|
+
void this.#initializeCatalogView();
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
async #initializeCatalogView(): Promise<void> {
|
|
1760
|
+
if (this.#catalogLoaded) {
|
|
1761
|
+
this.#presentCatalogView();
|
|
1762
|
+
return;
|
|
1763
|
+
}
|
|
1764
|
+
if (this.#catalogLoadPromise) return this.#catalogLoadPromise;
|
|
1765
|
+
|
|
1766
|
+
this.#headerContainer.clear();
|
|
1767
|
+
this.#listContainer.clear();
|
|
1768
|
+
this.#listContainer.addChild(new Text(theme.fg("muted", "Loading models..."), 0, 0));
|
|
1769
|
+
this.#tui.requestRender();
|
|
1770
|
+
|
|
1771
|
+
const load = this.#loadModels().then(() => {
|
|
1772
|
+
this.#catalogLoaded = true;
|
|
1773
|
+
if (this.#disposed) return;
|
|
1774
|
+
if (this.#smartRoutingOnly) {
|
|
1775
|
+
this.#enterSmartRoutingMode();
|
|
1776
|
+
return;
|
|
1777
|
+
}
|
|
1778
|
+
if (this.#viewMode === "models") this.#presentCatalogView();
|
|
1779
|
+
});
|
|
1780
|
+
this.#catalogLoadPromise = load;
|
|
1781
|
+
try {
|
|
1782
|
+
await load;
|
|
1783
|
+
} catch (error) {
|
|
1784
|
+
if (!this.#disposed && (this.#viewMode === "models" || this.#viewMode === "smart-routing")) {
|
|
1785
|
+
this.#errorMessage = error instanceof Error ? error.message : String(error);
|
|
1786
|
+
this.#buildProviderTabs();
|
|
1787
|
+
this.#updateTabBar();
|
|
1788
|
+
this.#updateList();
|
|
1789
|
+
this.#tui.requestRender();
|
|
1790
|
+
}
|
|
1791
|
+
} finally {
|
|
1792
|
+
if (this.#catalogLoadPromise === load) this.#catalogLoadPromise = undefined;
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
#presentCatalogView(): void {
|
|
1797
|
+
this.#buildProviderTabs();
|
|
1698
1798
|
this.#updateTabBar();
|
|
1799
|
+
// Apply the latest query: input remains responsive while the catalog loads.
|
|
1699
1800
|
this.#filterModels(this.#searchInput.getValue());
|
|
1801
|
+
this.#tui.requestRender();
|
|
1700
1802
|
}
|
|
1701
1803
|
|
|
1702
1804
|
/**
|
|
@@ -1880,6 +1982,12 @@ export class ModelSelectorComponent extends Container {
|
|
|
1880
1982
|
for (const line of this.#formatCurrentSessionLines()) {
|
|
1881
1983
|
this.#headerContainer.addChild(new Text(line, 0, 0));
|
|
1882
1984
|
}
|
|
1985
|
+
if (this.#errorMessage) {
|
|
1986
|
+
for (const line of String(this.#errorMessage).split("\n")) {
|
|
1987
|
+
this.#listContainer.addChild(new Text(theme.fg("error", line), 0, 0));
|
|
1988
|
+
}
|
|
1989
|
+
this.#listContainer.addChild(new Spacer(1));
|
|
1990
|
+
}
|
|
1883
1991
|
const rows = this.#getPresetRows();
|
|
1884
1992
|
for (let i = 0; i < rows.length; i++) {
|
|
1885
1993
|
const row = rows[i];
|
|
@@ -1926,7 +2034,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
1926
2034
|
}
|
|
1927
2035
|
if (row.kind === "group") {
|
|
1928
2036
|
const authenticated = this.#isPresetGroupUsable(row.profiles);
|
|
1929
|
-
const mark = this.#
|
|
2037
|
+
const mark = this.#isPresetAvailabilityPending() ? "…" : authenticated ? "✓" : "✗";
|
|
1930
2038
|
const containsActive =
|
|
1931
2039
|
this.#activeModelProfile !== undefined &&
|
|
1932
2040
|
row.profiles.some(profile => profile.name === this.#activeModelProfile);
|
|
@@ -1938,7 +2046,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
1938
2046
|
}
|
|
1939
2047
|
const presentation = getModelProfilePresentation(row.profile);
|
|
1940
2048
|
const authenticated = this.#isPresetAuthenticated(row.profile);
|
|
1941
|
-
const mark = this.#
|
|
2049
|
+
const mark = this.#isPresetAvailabilityPending() ? "…" : authenticated ? "✓" : "✗";
|
|
1942
2050
|
const isActive = row.profile.name === this.#activeModelProfile;
|
|
1943
2051
|
const label = ` ${mark} ${presentation.displayName}`;
|
|
1944
2052
|
const renderedLabel = selected ? theme.fg("accent", label) : authenticated ? label : theme.fg("dim", label);
|
|
@@ -2305,7 +2413,11 @@ export class ModelSelectorComponent extends Container {
|
|
|
2305
2413
|
|
|
2306
2414
|
handleInput(keyData: string): void {
|
|
2307
2415
|
if (this.#viewMode === "smart-routing") {
|
|
2308
|
-
this.#smartRoutingPanel
|
|
2416
|
+
if (this.#smartRoutingPanel) {
|
|
2417
|
+
this.#smartRoutingPanel.handleInput(keyData);
|
|
2418
|
+
} else if (getKeybindings().matches(keyData, "tui.select.cancel")) {
|
|
2419
|
+
this.#onCancelCallback();
|
|
2420
|
+
}
|
|
2309
2421
|
return;
|
|
2310
2422
|
}
|
|
2311
2423
|
if (this.#assignmentState === "assigning") {
|
|
@@ -2477,6 +2589,11 @@ export class ModelSelectorComponent extends Container {
|
|
|
2477
2589
|
this.#onSelectCallback({ kind: "deleteProfile", profileName: this.#previewProfileName });
|
|
2478
2590
|
return;
|
|
2479
2591
|
}
|
|
2592
|
+
if (this.#isPresetAvailabilityPending()) {
|
|
2593
|
+
this.#presetLoginHint = "Refreshing preset availability...";
|
|
2594
|
+
this.#renderPresetLanding();
|
|
2595
|
+
return;
|
|
2596
|
+
}
|
|
2480
2597
|
const missing = this.#getMissingProviders(profile);
|
|
2481
2598
|
if (missing.length > 0) {
|
|
2482
2599
|
this.#presetLoginHint = `Run ${missing.map(provider => `/login ${provider}`).join(", ")}`;
|
|
@@ -2504,6 +2621,12 @@ export class ModelSelectorComponent extends Container {
|
|
|
2504
2621
|
const row = this.#getSelectedPresetRow();
|
|
2505
2622
|
if (!row) return;
|
|
2506
2623
|
if (row.kind === "smartRouting") {
|
|
2624
|
+
if (this.#presetCatalogRefreshPending) {
|
|
2625
|
+
this.#openSmartRoutingAfterPresetRefresh = true;
|
|
2626
|
+
this.#presetLoginHint = "Refreshing model configuration...";
|
|
2627
|
+
this.#renderPresetLanding();
|
|
2628
|
+
return;
|
|
2629
|
+
}
|
|
2507
2630
|
this.#enterSmartRoutingMode();
|
|
2508
2631
|
return;
|
|
2509
2632
|
}
|
|
@@ -2523,6 +2646,11 @@ export class ModelSelectorComponent extends Container {
|
|
|
2523
2646
|
return;
|
|
2524
2647
|
}
|
|
2525
2648
|
if (row.kind === "group") {
|
|
2649
|
+
if (this.#isPresetAvailabilityPending()) {
|
|
2650
|
+
this.#presetLoginHint = "Refreshing preset availability...";
|
|
2651
|
+
this.#renderPresetLanding();
|
|
2652
|
+
return;
|
|
2653
|
+
}
|
|
2526
2654
|
// A group is a list of alternative presets; only surface a login hint
|
|
2527
2655
|
// when none of its members are usable. A partially-usable group stays
|
|
2528
2656
|
// navigable so the user can drill in and pick a usable member.
|
|
@@ -2546,6 +2674,11 @@ export class ModelSelectorComponent extends Container {
|
|
|
2546
2674
|
this.#renderPresetLanding();
|
|
2547
2675
|
return;
|
|
2548
2676
|
}
|
|
2677
|
+
if (this.#isPresetAvailabilityPending() && !isCustomUserProfile(row.profile)) {
|
|
2678
|
+
this.#presetLoginHint = "Refreshing preset availability...";
|
|
2679
|
+
this.#renderPresetLanding();
|
|
2680
|
+
return;
|
|
2681
|
+
}
|
|
2549
2682
|
const missing = this.#getMissingProviders(row.profile);
|
|
2550
2683
|
if (missing.length > 0 && !isCustomUserProfile(row.profile)) {
|
|
2551
2684
|
this.#presetLoginHint = `Run ${missing.map(provider => `/login ${provider}`).join(", ")}`;
|
|
@@ -2583,7 +2716,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
2583
2716
|
this.#presetLoginHint = undefined;
|
|
2584
2717
|
this.#clampPresetCursor();
|
|
2585
2718
|
}
|
|
2586
|
-
this.#
|
|
2719
|
+
void this.#refreshProviderAuth();
|
|
2587
2720
|
this.#tui.requestRender();
|
|
2588
2721
|
}
|
|
2589
2722
|
|