@gajae-code/coding-agent 0.12.7 → 0.12.10
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 +48 -0
- package/dist/types/cli/notify-cli.d.ts +1 -0
- package/dist/types/commands/notify.d.ts +4 -0
- package/dist/types/commands/sdk.d.ts +23 -0
- package/dist/types/config/file-lock.d.ts +3 -0
- package/dist/types/config/model-discovery-manager.d.ts +6 -1
- package/dist/types/config/model-registry.d.ts +2 -0
- package/dist/types/config/resolve-config-value.d.ts +2 -1
- package/dist/types/config/settings-schema.d.ts +17 -1
- package/dist/types/discovery/agents-md.d.ts +11 -1
- package/dist/types/main.d.ts +1 -0
- package/dist/types/modes/components/hook-selector.d.ts +20 -0
- package/dist/types/modes/components/notifications-settings-editor.d.ts +53 -4
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +4 -2
- package/dist/types/modes/types.d.ts +6 -0
- package/dist/types/sdk/broker/broker.d.ts +5 -1
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +1 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +11 -1
- package/dist/types/sdk/bus/config.d.ts +75 -113
- package/dist/types/sdk/bus/daemon-paths.d.ts +1 -0
- package/dist/types/sdk/bus/discord-live-provider.d.ts +8 -2
- package/dist/types/sdk/bus/discord-provider.d.ts +19 -0
- package/dist/types/sdk/bus/index.d.ts +1 -1
- package/dist/types/sdk/bus/notification-orchestration.d.ts +127 -18
- package/dist/types/sdk/bus/notification-service.d.ts +54 -4
- package/dist/types/sdk/bus/session-control.d.ts +8 -3
- package/dist/types/sdk/bus/slack-live-provider.d.ts +9 -2
- package/dist/types/sdk/bus/slack-provider.d.ts +22 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +8 -1
- package/dist/types/sdk/client/discovery.d.ts +2 -1
- package/dist/types/sdk/host/query/handlers.d.ts +2 -0
- package/dist/types/sdk/index.d.ts +1 -0
- package/dist/types/sdk/providers.d.ts +22 -0
- package/dist/types/sdk/session-directory.d.ts +1 -1
- package/dist/types/session/internal/managed-session-scope.d.ts +17 -1
- package/dist/types/session/internal/managed-session-storage.d.ts +19 -1
- package/dist/types/session/internal/native-publish-outcome.d.ts +3 -2
- package/dist/types/session/internal/session-open-errors.d.ts +5 -0
- package/dist/types/session/session-manager.d.ts +3 -1
- package/dist/types/session/session-storage.d.ts +2 -0
- package/dist/types/system-prompt.d.ts +12 -1
- package/dist/types/tools/composer-bash-policy.d.ts +9 -1
- package/package.json +10 -8
- package/scripts/build-sdk-package-smoke.ts +51 -0
- package/scripts/capture-notifications-settings-showcase.ts +1 -1
- package/scripts/verify-gjc-sdk-canonicalization.ts +14 -1
- package/src/cli/notify-cli.ts +215 -99
- package/src/commands/notify.ts +19 -8
- package/src/commands/sdk.ts +54 -0
- package/src/config/file-lock.ts +6 -0
- package/src/config/model-discovery-manager.ts +50 -8
- package/src/config/model-profiles.ts +8 -0
- package/src/config/model-registry.ts +866 -76
- package/src/config/resolve-config-value.ts +10 -8
- package/src/config/settings-schema.ts +25 -15
- package/src/config/settings.ts +15 -15
- package/src/discovery/agents-md.ts +76 -19
- package/src/gjc-runtime/psmux-detect.ts +12 -16
- package/src/gjc-runtime/session-state-sidecar.ts +12 -2
- package/src/gjc-runtime/tmux-sessions.ts +8 -1
- package/src/internal-urls/docs-index.generated.ts +10 -9
- package/src/main.ts +43 -7
- package/src/modes/DESIGN.md +11 -11
- package/src/modes/components/extensions/inspector-panel.ts +9 -5
- package/src/modes/components/hook-selector.ts +155 -44
- package/src/modes/components/model-selector.ts +242 -49
- package/src/modes/components/notifications-settings-editor.ts +631 -85
- package/src/modes/controllers/extension-ui-controller.ts +114 -15
- package/src/modes/controllers/selector-controller.ts +525 -93
- package/src/modes/print-mode.ts +3 -0
- package/src/modes/types.ts +6 -0
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/sdk/broker/broker.ts +54 -10
- package/src/sdk/broker/lifecycle-ledger.ts +34 -1
- package/src/sdk/broker/lifecycle.ts +47 -50
- package/src/sdk/bus/chat-daemon-cli.ts +12 -5
- package/src/sdk/bus/chat-daemon-control.ts +35 -6
- package/src/sdk/bus/chat-daemon-runtime.ts +16 -2
- package/src/sdk/bus/config.ts +534 -286
- package/src/sdk/bus/daemon-paths.ts +2 -0
- package/src/sdk/bus/discord-live-provider.ts +57 -2
- package/src/sdk/bus/discord-provider.ts +21 -0
- package/src/sdk/bus/index.ts +97 -35
- package/src/sdk/bus/notification-orchestration.ts +371 -68
- package/src/sdk/bus/notification-service.ts +571 -137
- package/src/sdk/bus/session-control.ts +119 -44
- package/src/sdk/bus/slack-live-provider.ts +78 -2
- package/src/sdk/bus/slack-provider.ts +24 -0
- package/src/sdk/bus/telegram-daemon-cli.ts +9 -2
- package/src/sdk/bus/telegram-daemon-contract.ts +8 -1
- package/src/sdk/bus/telegram-daemon-control.ts +3 -3
- package/src/sdk/bus/telegram-daemon.ts +23 -15
- package/src/sdk/client/discovery.ts +10 -4
- package/src/sdk/host/query/handlers.ts +20 -1
- package/src/sdk/index.ts +1 -0
- package/src/sdk/protocol/operation-inventory.generated.json +18 -0
- package/src/sdk/protocol/operation-registry.ts +5 -2
- package/src/sdk/providers.ts +51 -0
- package/src/sdk/session-directory.ts +1 -0
- package/src/sdk/session.ts +34 -14
- package/src/session/agent-session.ts +109 -37
- package/src/session/internal/managed-session-scope.ts +981 -450
- package/src/session/internal/managed-session-storage.ts +418 -47
- package/src/session/internal/native-publish-outcome.ts +15 -3
- package/src/session/internal/session-open-errors.ts +11 -0
- package/src/session/session-manager.ts +27 -3
- package/src/session/session-storage.ts +72 -5
- package/src/slash-commands/builtin-registry.ts +117 -15
- package/src/system-prompt.ts +28 -15
- package/src/task/index.ts +9 -3
- package/src/tools/ask.ts +1 -1
- package/src/tools/bash.ts +4 -2
- package/src/tools/composer-bash-policy.ts +21 -5
- package/src/tools/telegram-send.ts +4 -2
- package/src/utils/command-args.ts +19 -30
- package/src/utils/lang-from-path.ts +2 -4
|
@@ -11,7 +11,9 @@ import {
|
|
|
11
11
|
TabBar,
|
|
12
12
|
Text,
|
|
13
13
|
type TUI,
|
|
14
|
+
truncateToWidth,
|
|
14
15
|
} from "@gajae-code/tui";
|
|
16
|
+
import { sanitizeText } from "@gajae-code/utils";
|
|
15
17
|
import {
|
|
16
18
|
getModelProfilePresentation,
|
|
17
19
|
groupModelProfilesForPresetLanding,
|
|
@@ -96,6 +98,26 @@ interface RoleAssignment {
|
|
|
96
98
|
thinkingLevel: ThinkingLevel;
|
|
97
99
|
}
|
|
98
100
|
|
|
101
|
+
type RoleAssignments = Record<string, RoleAssignment | undefined>;
|
|
102
|
+
|
|
103
|
+
interface MaterializedCatalog {
|
|
104
|
+
models: ModelItem[];
|
|
105
|
+
canonicalModels: CanonicalModelItem[];
|
|
106
|
+
roles: RoleAssignments;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
interface ModelSelectorViewSnapshot {
|
|
110
|
+
roles: RoleAssignments;
|
|
111
|
+
allModels: ModelItem[];
|
|
112
|
+
filteredModels: ModelItem[];
|
|
113
|
+
canonicalModels: CanonicalModelItem[];
|
|
114
|
+
filteredCanonicalModels: CanonicalModelItem[];
|
|
115
|
+
selectedIndex: number;
|
|
116
|
+
providers: ProviderTabState[];
|
|
117
|
+
activeTabIndex: number;
|
|
118
|
+
tabBar: TabBar | null;
|
|
119
|
+
}
|
|
120
|
+
|
|
99
121
|
export type ModelSelectorSelection =
|
|
100
122
|
| {
|
|
101
123
|
kind: "assignment";
|
|
@@ -246,6 +268,9 @@ function isInheritedRoleSelector(value: string): boolean {
|
|
|
246
268
|
return value === "default" || value === "pi/default";
|
|
247
269
|
}
|
|
248
270
|
|
|
271
|
+
/** Width bound for an unresolvable selector echoed back into the assignment menu. */
|
|
272
|
+
const ROLE_BINDING_MAX_WIDTH = 48;
|
|
273
|
+
|
|
249
274
|
function getDefaultAliasThinkingLevel(value: string | undefined): ThinkingLevel | undefined {
|
|
250
275
|
const normalized = value?.trim();
|
|
251
276
|
if (!normalized?.startsWith("pi/default:")) return undefined;
|
|
@@ -327,7 +352,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
327
352
|
#canonicalModels: CanonicalModelItem[] = [];
|
|
328
353
|
#filteredCanonicalModels: CanonicalModelItem[] = [];
|
|
329
354
|
#selectedIndex: number = 0;
|
|
330
|
-
#roles = {}
|
|
355
|
+
#roles: RoleAssignments = {};
|
|
331
356
|
#settings = null as unknown as Settings;
|
|
332
357
|
#modelRegistry = null as unknown as ModelRegistry;
|
|
333
358
|
#onSelectCallback = (() => {}) as RoleSelectCallback;
|
|
@@ -412,7 +437,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
412
437
|
this.#viewMode = this.#temporaryOnly || initialSearchInput || scopedModels.length > 0 ? "models" : "presets";
|
|
413
438
|
|
|
414
439
|
// Load current role assignments from settings
|
|
415
|
-
this.#
|
|
440
|
+
this.#rebuildRoleModels();
|
|
416
441
|
|
|
417
442
|
// Add top border
|
|
418
443
|
this.addChild(new DynamicBorder());
|
|
@@ -495,7 +520,8 @@ export class ModelSelectorComponent extends Container {
|
|
|
495
520
|
});
|
|
496
521
|
}
|
|
497
522
|
|
|
498
|
-
#loadRoleModels():
|
|
523
|
+
#loadRoleModels(): RoleAssignments {
|
|
524
|
+
const roles: RoleAssignments = {};
|
|
499
525
|
const allModels = this.#modelRegistry.getAll();
|
|
500
526
|
const matchPreferences = { usageOrder: this.#settings.getStorage()?.getModelUsageOrder() };
|
|
501
527
|
const agentModelOverrides = this.#settings.get("task.agentModelOverrides");
|
|
@@ -511,7 +537,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
511
537
|
modelRegistry: this.#modelRegistry,
|
|
512
538
|
});
|
|
513
539
|
if (resolved.model) {
|
|
514
|
-
|
|
540
|
+
roles[role] = {
|
|
515
541
|
model: resolved.model,
|
|
516
542
|
thinkingLevel:
|
|
517
543
|
resolved.explicitThinkingLevel && resolved.thinkingLevel !== undefined
|
|
@@ -526,11 +552,22 @@ export class ModelSelectorComponent extends Container {
|
|
|
526
552
|
this.#isActiveDefaultFallback()) &&
|
|
527
553
|
this.#currentModel
|
|
528
554
|
) {
|
|
529
|
-
|
|
555
|
+
roles.default = {
|
|
530
556
|
model: this.#currentModel,
|
|
531
557
|
thinkingLevel: this.#currentThinkingLevel ?? ThinkingLevel.Inherit,
|
|
532
558
|
};
|
|
533
559
|
}
|
|
560
|
+
return roles;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Re-resolve every role binding against the CURRENT model catalog. Role bindings
|
|
565
|
+
* are resolved snapshots, so any catalog change (initial async load, provider
|
|
566
|
+
* refresh) must rebuild them or badges, ranking, and the assignment menu keep
|
|
567
|
+
* reporting models that the catalog has since gained or lost.
|
|
568
|
+
*/
|
|
569
|
+
#rebuildRoleModels(): void {
|
|
570
|
+
this.#roles = this.#loadRoleModels();
|
|
534
571
|
}
|
|
535
572
|
|
|
536
573
|
refreshRoleAssignments(
|
|
@@ -539,9 +576,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
539
576
|
if ("currentModel" in options) this.#currentModel = options.currentModel;
|
|
540
577
|
if ("currentThinkingLevel" in options) this.#currentThinkingLevel = options.currentThinkingLevel;
|
|
541
578
|
if ("activeModelProfile" in options) this.#activeModelProfile = options.activeModelProfile;
|
|
542
|
-
this.#
|
|
543
|
-
this.#loadRoleModels();
|
|
544
|
-
this.#applyTabFilter();
|
|
579
|
+
this.#refreshCatalogView();
|
|
545
580
|
}
|
|
546
581
|
|
|
547
582
|
#resolveProviderAuthState(providerId: string): ProviderAuthState {
|
|
@@ -550,7 +585,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
550
585
|
return this.#modelRegistry.hasConfiguredProviderAuth(providerId) ? "configured" : "none";
|
|
551
586
|
}
|
|
552
587
|
|
|
553
|
-
#sortModels(models: ModelItem[]): void {
|
|
588
|
+
#sortModels(models: ModelItem[], roles: RoleAssignments = this.#roles): void {
|
|
554
589
|
// Sort: default-tagged model first, then MRU, then provider ranking
|
|
555
590
|
const mruOrder = this.#settings.getStorage()?.getModelUsageOrder() ?? [];
|
|
556
591
|
const mruIndex = new Map(mruOrder.map((key, i) => [key, i]));
|
|
@@ -561,7 +596,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
561
596
|
}
|
|
562
597
|
}
|
|
563
598
|
|
|
564
|
-
const modelRank = (item: ModelItem) => computeModelRank(item.model,
|
|
599
|
+
const modelRank = (item: ModelItem) => computeModelRank(item.model, roles);
|
|
565
600
|
|
|
566
601
|
const dateRe = /-(\d{8})$/;
|
|
567
602
|
const latestRe = /-latest$/;
|
|
@@ -630,7 +665,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
630
665
|
});
|
|
631
666
|
}
|
|
632
667
|
|
|
633
|
-
#sortCanonicalModels(models: CanonicalModelItem[]): void {
|
|
668
|
+
#sortCanonicalModels(models: CanonicalModelItem[], roles: RoleAssignments = this.#roles): void {
|
|
634
669
|
const mruOrder = this.#settings.getStorage()?.getModelUsageOrder() ?? [];
|
|
635
670
|
const mruIndex = new Map(mruOrder.map((key, i) => [key, i]));
|
|
636
671
|
const providerAuthStateById = new Map<string, ProviderAuthState>();
|
|
@@ -640,7 +675,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
640
675
|
}
|
|
641
676
|
}
|
|
642
677
|
|
|
643
|
-
const modelRank = (item: CanonicalModelItem) => computeModelRank(item.model,
|
|
678
|
+
const modelRank = (item: CanonicalModelItem) => computeModelRank(item.model, roles);
|
|
644
679
|
|
|
645
680
|
models.sort((a, b) => {
|
|
646
681
|
const aRank = modelRank(a);
|
|
@@ -669,23 +704,40 @@ export class ModelSelectorComponent extends Container {
|
|
|
669
704
|
});
|
|
670
705
|
}
|
|
671
706
|
|
|
672
|
-
|
|
707
|
+
#buildScopedModelItems(): ModelItem[] {
|
|
708
|
+
return this.#scopedModels.map(scoped => ({
|
|
709
|
+
kind: "provider",
|
|
710
|
+
provider: scoped.model.provider,
|
|
711
|
+
id: scoped.model.id,
|
|
712
|
+
model: scoped.model,
|
|
713
|
+
selector: `${scoped.model.provider}/${scoped.model.id}`,
|
|
714
|
+
thinkingLevel: scoped.thinkingLevel,
|
|
715
|
+
explicitThinkingLevel: scoped.explicitThinkingLevel,
|
|
716
|
+
}));
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
#buildAvailableModelItems(): ModelItem[] {
|
|
720
|
+
return this.#modelRegistry.getAvailable().map((model: Model) => ({
|
|
721
|
+
kind: "provider",
|
|
722
|
+
provider: model.provider,
|
|
723
|
+
id: model.id,
|
|
724
|
+
model,
|
|
725
|
+
selector: `${model.provider}/${model.id}`,
|
|
726
|
+
}));
|
|
727
|
+
}
|
|
728
|
+
async #loadModels(
|
|
729
|
+
options: { refreshRegistry?: boolean; throwOnCatalogError?: boolean; commit?: boolean } = {},
|
|
730
|
+
): Promise<MaterializedCatalog | undefined> {
|
|
673
731
|
let models: ModelItem[];
|
|
674
732
|
|
|
675
733
|
// Use scoped models if provided via --models flag
|
|
676
734
|
if (this.#scopedModels.length > 0) {
|
|
677
|
-
models = this.#
|
|
678
|
-
kind: "provider",
|
|
679
|
-
provider: scoped.model.provider,
|
|
680
|
-
id: scoped.model.id,
|
|
681
|
-
model: scoped.model,
|
|
682
|
-
selector: `${scoped.model.provider}/${scoped.model.id}`,
|
|
683
|
-
thinkingLevel: scoped.thinkingLevel,
|
|
684
|
-
explicitThinkingLevel: scoped.explicitThinkingLevel,
|
|
685
|
-
}));
|
|
735
|
+
models = this.#buildScopedModelItems();
|
|
686
736
|
} else {
|
|
687
737
|
// Reload config and cached discovery state without blocking on live provider refresh
|
|
688
|
-
|
|
738
|
+
if (options.refreshRegistry !== false) {
|
|
739
|
+
await this.#modelRegistry.refresh("offline");
|
|
740
|
+
}
|
|
689
741
|
|
|
690
742
|
// Check for models.json errors
|
|
691
743
|
const loadError = this.#modelRegistry.getError();
|
|
@@ -697,24 +749,25 @@ export class ModelSelectorComponent extends Container {
|
|
|
697
749
|
|
|
698
750
|
// Load available models (built-in models still work even if models.json failed)
|
|
699
751
|
try {
|
|
700
|
-
|
|
701
|
-
models = availableModels.map((model: Model) => ({
|
|
702
|
-
kind: "provider",
|
|
703
|
-
provider: model.provider,
|
|
704
|
-
id: model.id,
|
|
705
|
-
model,
|
|
706
|
-
selector: `${model.provider}/${model.id}`,
|
|
707
|
-
}));
|
|
752
|
+
models = this.#buildAvailableModelItems();
|
|
708
753
|
} catch (error) {
|
|
754
|
+
if (options.throwOnCatalogError) throw error;
|
|
709
755
|
this.#allModels = [];
|
|
710
756
|
this.#filteredModels = [];
|
|
711
757
|
this.#canonicalModels = [];
|
|
712
758
|
this.#filteredCanonicalModels = [];
|
|
713
759
|
this.#errorMessage = error instanceof Error ? error.message : String(error);
|
|
760
|
+
this.#rebuildRoleModels();
|
|
714
761
|
return;
|
|
715
762
|
}
|
|
716
763
|
}
|
|
717
764
|
|
|
765
|
+
const catalog = this.#materializeModels(models);
|
|
766
|
+
if (options.commit !== false) this.#commitMaterializedCatalog(catalog);
|
|
767
|
+
return catalog;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
#materializeModels(models: ModelItem[]): MaterializedCatalog {
|
|
718
771
|
const candidateModels = models.map(item => item.model);
|
|
719
772
|
const canonicalRecords = this.#modelRegistry.getCanonicalModels({
|
|
720
773
|
availableOnly: this.#scopedModels.length === 0,
|
|
@@ -758,20 +811,76 @@ export class ModelSelectorComponent extends Container {
|
|
|
758
811
|
return item;
|
|
759
812
|
})
|
|
760
813
|
.filter((item): item is CanonicalModelItem => item !== undefined);
|
|
814
|
+
const roles = this.#loadRoleModels();
|
|
761
815
|
|
|
762
|
-
this.#sortModels(models);
|
|
763
|
-
this.#sortCanonicalModels(canonicalModels);
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
this.#
|
|
816
|
+
this.#sortModels(models, roles);
|
|
817
|
+
this.#sortCanonicalModels(canonicalModels, roles);
|
|
818
|
+
return { models, canonicalModels, roles };
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
#commitMaterializedCatalog(catalog: MaterializedCatalog): void {
|
|
822
|
+
this.#roles = catalog.roles;
|
|
823
|
+
this.#allModels = catalog.models;
|
|
824
|
+
this.#filteredModels = catalog.models;
|
|
825
|
+
this.#canonicalModels = catalog.canonicalModels;
|
|
826
|
+
this.#filteredCanonicalModels = catalog.canonicalModels;
|
|
827
|
+
this.#selectedIndex = Math.min(this.#selectedIndex, Math.max(0, catalog.models.length - 1));
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
#captureViewSnapshot(): ModelSelectorViewSnapshot {
|
|
831
|
+
return {
|
|
832
|
+
roles: this.#roles,
|
|
833
|
+
allModels: this.#allModels,
|
|
834
|
+
filteredModels: this.#filteredModels,
|
|
835
|
+
canonicalModels: this.#canonicalModels,
|
|
836
|
+
filteredCanonicalModels: this.#filteredCanonicalModels,
|
|
837
|
+
selectedIndex: this.#selectedIndex,
|
|
838
|
+
providers: this.#providers,
|
|
839
|
+
activeTabIndex: this.#activeTabIndex,
|
|
840
|
+
tabBar: this.#tabBar,
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
#restoreViewSnapshot(snapshot: ModelSelectorViewSnapshot): void {
|
|
845
|
+
this.#roles = snapshot.roles;
|
|
846
|
+
this.#allModels = snapshot.allModels;
|
|
847
|
+
this.#filteredModels = snapshot.filteredModels;
|
|
848
|
+
this.#canonicalModels = snapshot.canonicalModels;
|
|
849
|
+
this.#filteredCanonicalModels = snapshot.filteredCanonicalModels;
|
|
850
|
+
this.#selectedIndex = snapshot.selectedIndex;
|
|
851
|
+
this.#providers = snapshot.providers;
|
|
852
|
+
this.#activeTabIndex = snapshot.activeTabIndex;
|
|
853
|
+
this.#tabBar = snapshot.tabBar;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
#refreshCatalogView(): boolean {
|
|
857
|
+
const previousView = this.#captureViewSnapshot();
|
|
858
|
+
try {
|
|
859
|
+
const models =
|
|
860
|
+
this.#scopedModels.length > 0 ? this.#buildScopedModelItems() : this.#buildAvailableModelItems();
|
|
861
|
+
const catalog = this.#materializeModels(models);
|
|
862
|
+
this.#buildProviderTabs(catalog.models);
|
|
863
|
+
this.#commitMaterializedCatalog(catalog);
|
|
864
|
+
this.#updateTabBar();
|
|
865
|
+
this.#applyTabFilter();
|
|
866
|
+
return true;
|
|
867
|
+
} catch (error) {
|
|
868
|
+
this.#errorMessage = error instanceof Error ? error.message : String(error);
|
|
869
|
+
this.#restoreViewSnapshot(previousView);
|
|
870
|
+
try {
|
|
871
|
+
this.#updateTabBar();
|
|
872
|
+
this.#applyTabFilter();
|
|
873
|
+
} catch {
|
|
874
|
+
// Keep the last-good component state even if the terminal view cannot be rebuilt.
|
|
875
|
+
}
|
|
876
|
+
return false;
|
|
877
|
+
}
|
|
769
878
|
}
|
|
770
879
|
|
|
771
|
-
#buildProviderTabs(): void {
|
|
880
|
+
#buildProviderTabs(models: readonly ModelItem[] = this.#allModels): void {
|
|
772
881
|
const activeTabId = this.#getActiveTab().id;
|
|
773
882
|
const providerSet = new Set<string>();
|
|
774
|
-
for (const item of
|
|
883
|
+
for (const item of models) {
|
|
775
884
|
providerSet.add(item.provider);
|
|
776
885
|
}
|
|
777
886
|
for (const provider of this.#modelRegistry.getDiscoverableProviders()) {
|
|
@@ -806,12 +915,58 @@ export class ModelSelectorComponent extends Container {
|
|
|
806
915
|
if (this.#scopedModels.length > 0 || !providerId) {
|
|
807
916
|
return;
|
|
808
917
|
}
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
918
|
+
|
|
919
|
+
let refreshError: unknown;
|
|
920
|
+
try {
|
|
921
|
+
await this.#modelRegistry.refreshProvider(providerId);
|
|
922
|
+
} catch (error) {
|
|
923
|
+
refreshError = error;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
let catalog: MaterializedCatalog | undefined;
|
|
927
|
+
try {
|
|
928
|
+
catalog = await this.#loadModels({
|
|
929
|
+
refreshRegistry: refreshError === undefined,
|
|
930
|
+
throwOnCatalogError: true,
|
|
931
|
+
commit: false,
|
|
932
|
+
});
|
|
933
|
+
} catch (catalogError) {
|
|
934
|
+
if (refreshError !== undefined) {
|
|
935
|
+
const refreshMessage = refreshError instanceof Error ? refreshError.message : String(refreshError);
|
|
936
|
+
const recoveryMessage = catalogError instanceof Error ? catalogError.message : String(catalogError);
|
|
937
|
+
throw new Error(`${refreshMessage}; catalog recovery failed: ${recoveryMessage}`, {
|
|
938
|
+
cause: refreshError,
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
throw catalogError;
|
|
942
|
+
}
|
|
943
|
+
if (!catalog) throw new Error("Model catalog could not be materialized.");
|
|
944
|
+
|
|
945
|
+
const previousView = this.#captureViewSnapshot();
|
|
946
|
+
try {
|
|
947
|
+
this.#buildProviderTabs(catalog.models);
|
|
948
|
+
this.#commitMaterializedCatalog(catalog);
|
|
949
|
+
this.#updateTabBar();
|
|
950
|
+
this.#applyTabFilter();
|
|
951
|
+
this.#tui.requestRender();
|
|
952
|
+
} catch (presentationError) {
|
|
953
|
+
const finalError =
|
|
954
|
+
refreshError !== undefined
|
|
955
|
+
? new Error(
|
|
956
|
+
`${refreshError instanceof Error ? refreshError.message : String(refreshError)}; catalog presentation failed: ${presentationError instanceof Error ? presentationError.message : String(presentationError)}`,
|
|
957
|
+
{ cause: refreshError },
|
|
958
|
+
)
|
|
959
|
+
: presentationError;
|
|
960
|
+
this.#restoreViewSnapshot(previousView);
|
|
961
|
+
try {
|
|
962
|
+
this.#updateTabBar();
|
|
963
|
+
this.#applyTabFilter();
|
|
964
|
+
} catch {
|
|
965
|
+
// Preserve the original presentation failure and the last-good component state.
|
|
966
|
+
}
|
|
967
|
+
throw finalError;
|
|
968
|
+
}
|
|
969
|
+
if (refreshError !== undefined) throw refreshError;
|
|
815
970
|
}
|
|
816
971
|
|
|
817
972
|
#updateTabBar(): void {
|
|
@@ -1193,11 +1348,12 @@ export class ModelSelectorComponent extends Container {
|
|
|
1193
1348
|
}
|
|
1194
1349
|
|
|
1195
1350
|
#formatAssignedModelLabel(model: Model, thinkingLevel: ThinkingLevel | undefined): string {
|
|
1196
|
-
|
|
1351
|
+
const modelLabel = sanitizeText(`${model.provider}/${model.id}`).replace(/\s+/g, " ").trim();
|
|
1352
|
+
let label = modelLabel;
|
|
1197
1353
|
if (thinkingLevel && thinkingLevel !== ThinkingLevel.Inherit) {
|
|
1198
1354
|
label += ` (${getThinkingLevelMetadata(thinkingLevel).label})`;
|
|
1199
1355
|
}
|
|
1200
|
-
return label;
|
|
1356
|
+
return truncateToWidth(label, ROLE_BINDING_MAX_WIDTH);
|
|
1201
1357
|
}
|
|
1202
1358
|
|
|
1203
1359
|
#renderPresetLanding(): void {
|
|
@@ -1510,7 +1666,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
1510
1666
|
const prefix = i === this.#selectedActionIndex ? theme.fg("accent", `${theme.nav.cursor} `) : " ";
|
|
1511
1667
|
const role = GJC_MODEL_ASSIGNMENT_TARGET_IDS[i];
|
|
1512
1668
|
const label = role
|
|
1513
|
-
? `Set as ${GJC_MODEL_ASSIGNMENT_TARGETS[role].tag ?? role.toUpperCase()} (${GJC_MODEL_ASSIGNMENT_TARGETS[role].name})`
|
|
1669
|
+
? `Set as ${GJC_MODEL_ASSIGNMENT_TARGETS[role].tag ?? role.toUpperCase()} (${GJC_MODEL_ASSIGNMENT_TARGETS[role].name}) — now: ${this.#formatRoleBinding(role)}`
|
|
1514
1670
|
: i === GJC_MODEL_ASSIGNMENT_TARGET_IDS.length
|
|
1515
1671
|
? "Set for all role agents"
|
|
1516
1672
|
: "Set for all targets";
|
|
@@ -1520,6 +1676,43 @@ export class ModelSelectorComponent extends Container {
|
|
|
1520
1676
|
}
|
|
1521
1677
|
}
|
|
1522
1678
|
|
|
1679
|
+
/**
|
|
1680
|
+
* Describe what a role currently points at so the assignment menu can show the
|
|
1681
|
+
* existing binding inline. Without it the role rows carry no binding and the
|
|
1682
|
+
* only way to learn a role's model is to scan the whole (800+ entry) model list
|
|
1683
|
+
* for role badges.
|
|
1684
|
+
*/
|
|
1685
|
+
#formatRoleBinding(role: GjcModelAssignmentTargetId): string {
|
|
1686
|
+
const target = GJC_MODEL_ASSIGNMENT_TARGETS[role];
|
|
1687
|
+
const configured =
|
|
1688
|
+
target.settingsPath === "modelRoles"
|
|
1689
|
+
? this.#settings.getModelRole(role)
|
|
1690
|
+
: this.#settings.get("task.agentModelOverrides")[role];
|
|
1691
|
+
const selectors = normalizeModelSelectorValue(configured);
|
|
1692
|
+
const head = selectors[0];
|
|
1693
|
+
const assigned = this.#roles[role];
|
|
1694
|
+
if (!head) {
|
|
1695
|
+
if (assigned) return this.#formatAssignedModelLabel(assigned.model, assigned.thinkingLevel);
|
|
1696
|
+
return role === "default" ? "unset" : "inherits default";
|
|
1697
|
+
}
|
|
1698
|
+
const inheritedThinkingLevel = getDefaultAliasThinkingLevel(head);
|
|
1699
|
+
const isInheritedAlias = head === "pi/default" || inheritedThinkingLevel !== undefined;
|
|
1700
|
+
// A role agent pinned to the `pi/default` alias inherits whatever the default
|
|
1701
|
+
// resolves to. Classify that before the resolved lookup: the alias itself resolves
|
|
1702
|
+
// to the default's model, which would otherwise render as a concrete id and hide the
|
|
1703
|
+
// inheritance. A fallback chain merely headed by the alias is NOT inherited —
|
|
1704
|
+
// resolution can advance to a tail entry — so it falls through to the resolved
|
|
1705
|
+
// binding below.
|
|
1706
|
+
if (role !== "default" && selectors.length === 1 && isInheritedAlias) {
|
|
1707
|
+
return inheritedThinkingLevel ? `inherits default (${inheritedThinkingLevel})` : "inherits default";
|
|
1708
|
+
}
|
|
1709
|
+
if (assigned) return this.#formatAssignedModelLabel(assigned.model, assigned.thinkingLevel);
|
|
1710
|
+
// Configured selectors are permissively validated, so bound and sanitize the raw
|
|
1711
|
+
// value before it reaches the terminal.
|
|
1712
|
+
const displayed = truncateToWidth(sanitizeText(head).replace(/\s+/g, " ").trim(), ROLE_BINDING_MAX_WIDTH);
|
|
1713
|
+
return `${displayed} (unavailable)`;
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1523
1716
|
#renderThinkingMenu(choice: PendingThinkingChoice): void {
|
|
1524
1717
|
const targetLabel = choice.roles
|
|
1525
1718
|
? choice.roles.includes("default")
|