@gajae-code/coding-agent 0.7.7 → 0.7.9
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 +38 -1
- package/README.md +14 -0
- package/dist/types/cli/local-provider-smoke.d.ts +59 -0
- package/dist/types/cli/notify-cli.d.ts +1 -1
- package/dist/types/cli/update-cli.d.ts +24 -1
- package/dist/types/commands/local-provider.d.ts +34 -0
- package/dist/types/config/keybindings.d.ts +1 -1
- package/dist/types/config/model-profile-activation.d.ts +19 -2
- package/dist/types/config/model-profiles.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +9 -0
- package/dist/types/config/models-config-schema.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +45 -0
- package/dist/types/config/settings.d.ts +7 -0
- package/dist/types/coordinator-mcp/server.d.ts +8 -0
- package/dist/types/gjc-runtime/launch-tmux.d.ts +14 -5
- package/dist/types/gjc-runtime/team-runtime.d.ts +1 -1
- package/dist/types/gjc-runtime/tmux-common.d.ts +12 -0
- package/dist/types/gjc-runtime/ultragoal-guard.d.ts +3 -1
- package/dist/types/modes/components/custom-model-preset-wizard.d.ts +1 -1
- package/dist/types/modes/components/hook-input.d.ts +1 -0
- package/dist/types/modes/components/model-selector.d.ts +11 -0
- package/dist/types/modes/components/settings-selector.d.ts +4 -1
- package/dist/types/modes/components/status-line/types.d.ts +5 -7
- package/dist/types/modes/components/status-line.d.ts +7 -3
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +3 -1
- package/dist/types/modes/controllers/input-controller.d.ts +1 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
- package/dist/types/modes/interactive-mode.d.ts +3 -1
- package/dist/types/modes/prompt-action-autocomplete.d.ts +1 -0
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +21 -1
- package/dist/types/modes/tmux-scroll.d.ts +18 -0
- package/dist/types/modes/types.d.ts +3 -1
- package/dist/types/modes/utils/terminal-bell.d.ts +3 -0
- package/dist/types/notifications/daemon-paths.d.ts +9 -0
- package/dist/types/notifications/telegram-daemon-cli.d.ts +21 -4
- package/dist/types/notifications/telegram-daemon.d.ts +1 -10
- package/dist/types/tools/ask.d.ts +24 -0
- package/dist/types/tools/ultragoal-ask-guard.d.ts +9 -2
- package/package.json +7 -7
- package/src/cli/local-provider-smoke.ts +632 -0
- package/src/cli/notify-cli.ts +5 -3
- package/src/cli/update-cli.ts +155 -13
- package/src/cli.ts +19 -0
- package/src/commands/launch.ts +46 -1
- package/src/commands/local-provider.ts +62 -0
- package/src/commands/notify.ts +1 -1
- package/src/config/keybindings.ts +1 -1
- package/src/config/model-profile-activation.ts +92 -11
- package/src/config/model-profiles.ts +15 -2
- package/src/config/model-registry.ts +136 -6
- package/src/config/models-config-schema.ts +9 -0
- package/src/config/settings-schema.ts +48 -0
- package/src/config/settings.ts +11 -0
- package/src/coordinator-mcp/server.ts +280 -36
- package/src/deep-interview/render-middleware.ts +1 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +14 -6
- package/src/defaults/gjc/skills/ralplan/SKILL.md +14 -7
- package/src/gjc-runtime/deep-interview-runtime.ts +1 -1
- package/src/gjc-runtime/launch-tmux.ts +321 -38
- package/src/gjc-runtime/launch-worktree.ts +16 -2
- package/src/gjc-runtime/team-runtime.ts +87 -7
- package/src/gjc-runtime/tmux-common.ts +16 -0
- package/src/gjc-runtime/tmux-sessions.ts +13 -9
- package/src/gjc-runtime/ultragoal-guard.ts +11 -5
- package/src/internal-urls/docs-index.generated.ts +10 -9
- package/src/modes/components/agent-dashboard.ts +2 -1
- package/src/modes/components/custom-model-preset-wizard.ts +31 -192
- package/src/modes/components/hook-editor.ts +7 -4
- package/src/modes/components/hook-input.ts +4 -0
- package/src/modes/components/model-selector.ts +260 -12
- package/src/modes/components/session-observer-overlay.ts +3 -3
- package/src/modes/components/settings-selector.ts +506 -5
- package/src/modes/components/status-line/segments.ts +16 -19
- package/src/modes/components/status-line/types.ts +6 -2
- package/src/modes/components/status-line.ts +70 -35
- package/src/modes/controllers/event-controller.ts +98 -4
- package/src/modes/controllers/extension-ui-controller.ts +5 -0
- package/src/modes/controllers/input-controller.ts +15 -0
- package/src/modes/controllers/selector-controller.ts +142 -11
- package/src/modes/interactive-mode.ts +7 -2
- package/src/modes/prompt-action-autocomplete.ts +8 -0
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +11 -4
- package/src/modes/tmux-scroll.ts +92 -0
- package/src/modes/types.ts +6 -1
- package/src/modes/utils/terminal-bell.ts +48 -0
- package/src/notifications/daemon-paths.ts +22 -0
- package/src/notifications/telegram-daemon-cli.ts +125 -13
- package/src/notifications/telegram-daemon.ts +6 -21
- package/src/prompts/system/system-prompt.md +5 -0
- package/src/sdk.ts +10 -1
- package/src/session/agent-session.ts +84 -6
- package/src/slash-commands/builtin-registry.ts +106 -13
- package/src/tools/ask.ts +11 -1
- package/src/tools/eval.ts +2 -2
- package/src/tools/render-utils.ts +2 -0
- package/src/tools/ultragoal-ask-guard.ts +39 -5
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
resolveModelRoleValue,
|
|
29
29
|
type ScopedModelSelection,
|
|
30
30
|
} from "../../config/model-resolver";
|
|
31
|
+
import type { ModelProfileConfig } from "../../config/models-config-schema";
|
|
31
32
|
import type { Settings } from "../../config/settings";
|
|
32
33
|
import { type ThemeColor, theme } from "../../modes/theme/theme";
|
|
33
34
|
import { formatModelOnboardingInlineHint } from "../../setup/model-onboarding-guidance";
|
|
@@ -105,6 +106,15 @@ export type ModelSelectorSelection =
|
|
|
105
106
|
}
|
|
106
107
|
| {
|
|
107
108
|
kind: "createProfile";
|
|
109
|
+
profile: ModelProfileConfig;
|
|
110
|
+
}
|
|
111
|
+
| {
|
|
112
|
+
kind: "renameProfile";
|
|
113
|
+
profileName: string;
|
|
114
|
+
}
|
|
115
|
+
| {
|
|
116
|
+
kind: "deleteProfile";
|
|
117
|
+
profileName: string;
|
|
108
118
|
};
|
|
109
119
|
|
|
110
120
|
interface PendingThinkingChoice {
|
|
@@ -155,11 +165,27 @@ interface PresetCreateRow {
|
|
|
155
165
|
kind: "create";
|
|
156
166
|
}
|
|
157
167
|
|
|
168
|
+
interface PresetCreateUnavailableRow {
|
|
169
|
+
kind: "createUnavailable";
|
|
170
|
+
label: string;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
interface PresetAlreadySavedRow {
|
|
174
|
+
kind: "alreadySaved";
|
|
175
|
+
profile: ModelProfileDefinition;
|
|
176
|
+
}
|
|
177
|
+
|
|
158
178
|
interface PresetBrowseRow {
|
|
159
179
|
kind: "browse";
|
|
160
180
|
}
|
|
161
181
|
|
|
162
|
-
type PresetLandingRow =
|
|
182
|
+
type PresetLandingRow =
|
|
183
|
+
| PresetGroupRow
|
|
184
|
+
| PresetProfileRow
|
|
185
|
+
| PresetCreateRow
|
|
186
|
+
| PresetCreateUnavailableRow
|
|
187
|
+
| PresetAlreadySavedRow
|
|
188
|
+
| PresetBrowseRow;
|
|
163
189
|
|
|
164
190
|
// Stable logical identity for a preset landing row, independent of its current
|
|
165
191
|
// list position. Used to relocate the cursor after the expanded group changes so
|
|
@@ -174,6 +200,10 @@ function presetRowIdentity(row: PresetLandingRow): string {
|
|
|
174
200
|
return "browse";
|
|
175
201
|
case "create":
|
|
176
202
|
return "create";
|
|
203
|
+
case "createUnavailable":
|
|
204
|
+
return "createUnavailable";
|
|
205
|
+
case "alreadySaved":
|
|
206
|
+
return `alreadySaved:${row.profile.name}`;
|
|
177
207
|
}
|
|
178
208
|
}
|
|
179
209
|
|
|
@@ -185,6 +215,7 @@ const PROFILE_ROLE_PREVIEW_ORDER: GjcModelAssignmentTargetId[] = [
|
|
|
185
215
|
"architect",
|
|
186
216
|
];
|
|
187
217
|
const PRESET_SCOPE_LABELS = ["Apply for this session", "Set as default"];
|
|
218
|
+
const CUSTOM_PRESET_SCOPE_LABELS = ["Apply for this session", "Set as default", "Rename", "Delete"];
|
|
188
219
|
|
|
189
220
|
function isPrintableCharacter(keyData: string): boolean {
|
|
190
221
|
return keyData.length === 1 && keyData >= " " && keyData !== "\x7f";
|
|
@@ -193,6 +224,68 @@ function isPrintableCharacter(keyData: string): boolean {
|
|
|
193
224
|
function profileRequiredProviders(profile: ModelProfileDefinition): string[] {
|
|
194
225
|
return [...new Set(profile.requiredProviders)].sort((a, b) => a.localeCompare(b));
|
|
195
226
|
}
|
|
227
|
+
|
|
228
|
+
function isInheritedRoleSelector(value: string | undefined): boolean {
|
|
229
|
+
const normalized = value?.trim();
|
|
230
|
+
return !normalized || normalized === "default" || normalized === "pi/default";
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function getDefaultAliasThinkingLevel(value: string | undefined): ThinkingLevel | undefined {
|
|
234
|
+
const normalized = value?.trim();
|
|
235
|
+
if (!normalized?.startsWith("pi/default:")) return undefined;
|
|
236
|
+
return parseThinkingLevel(normalized.slice("pi/default:".length));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function getSelectorProvider(selector: string): string | undefined {
|
|
240
|
+
const slashIndex = selector.indexOf("/");
|
|
241
|
+
return slashIndex > 0 ? selector.slice(0, slashIndex) : undefined;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function deriveRequiredProviders(modelMapping: ModelProfileConfig["model_mapping"]): string[] {
|
|
245
|
+
const providers = new Set<string>();
|
|
246
|
+
for (const selector of Object.values(modelMapping)) {
|
|
247
|
+
const provider = getSelectorProvider(selector);
|
|
248
|
+
if (provider) providers.add(provider);
|
|
249
|
+
}
|
|
250
|
+
return [...providers].sort((a, b) => a.localeCompare(b));
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function sameStringRecord(
|
|
254
|
+
left: Readonly<Record<string, string | undefined>>,
|
|
255
|
+
right: Readonly<Record<string, string | undefined>>,
|
|
256
|
+
): boolean {
|
|
257
|
+
const leftEntries = Object.entries(left)
|
|
258
|
+
.filter((entry): entry is [string, string] => entry[1] !== undefined)
|
|
259
|
+
.sort(([a], [b]) => a.localeCompare(b));
|
|
260
|
+
const rightEntries = Object.entries(right)
|
|
261
|
+
.filter((entry): entry is [string, string] => entry[1] !== undefined)
|
|
262
|
+
.sort(([a], [b]) => a.localeCompare(b));
|
|
263
|
+
if (leftEntries.length !== rightEntries.length) return false;
|
|
264
|
+
for (let i = 0; i < leftEntries.length; i++) {
|
|
265
|
+
const leftEntry = leftEntries[i];
|
|
266
|
+
const rightEntry = rightEntries[i];
|
|
267
|
+
if (!leftEntry || !rightEntry || leftEntry[0] !== rightEntry[0] || leftEntry[1] !== rightEntry[1]) {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function sameStringArray(left: readonly string[], right: readonly string[]): boolean {
|
|
275
|
+
if (left.length !== right.length) return false;
|
|
276
|
+
for (let i = 0; i < left.length; i++) {
|
|
277
|
+
if (left[i] !== right[i]) return false;
|
|
278
|
+
}
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function hasPersistableProfileSnapshot(snapshot: ModelProfileConfig): boolean {
|
|
283
|
+
return Object.keys(snapshot.model_mapping).length > 0 && snapshot.required_providers.length > 0;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function isCustomUserProfile(profile: ModelProfileDefinition): boolean {
|
|
287
|
+
return profile.source === "user";
|
|
288
|
+
}
|
|
196
289
|
/**
|
|
197
290
|
* Component that renders a canonical model selector with provider tabs.
|
|
198
291
|
* - Preset landing Left/Right: Collapse/expand selected provider
|
|
@@ -733,6 +826,67 @@ export class ModelSelectorComponent extends Container {
|
|
|
733
826
|
return groupModelProfilesForPresetLanding(this.#modelRegistry.getModelProfiles?.() ?? new Map());
|
|
734
827
|
}
|
|
735
828
|
|
|
829
|
+
#formatCurrentModelSelector(
|
|
830
|
+
thinkingLevel: ThinkingLevel | undefined = this.#currentThinkingLevel,
|
|
831
|
+
): string | undefined {
|
|
832
|
+
if (!this.#currentModel) return undefined;
|
|
833
|
+
return formatModelSelectorValue(`${this.#currentModel.provider}/${this.#currentModel.id}`, thinkingLevel);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
#resolveProfileModelSelector(value: string | undefined): string | undefined {
|
|
837
|
+
const normalized = value?.trim();
|
|
838
|
+
if (isInheritedRoleSelector(normalized)) return undefined;
|
|
839
|
+
|
|
840
|
+
const resolved = resolveModelRoleValue(normalized, this.#modelRegistry.getAll(), {
|
|
841
|
+
settings: this.#settings,
|
|
842
|
+
matchPreferences: { usageOrder: this.#settings.getStorage()?.getModelUsageOrder() },
|
|
843
|
+
modelRegistry: this.#modelRegistry,
|
|
844
|
+
});
|
|
845
|
+
if (resolved.model) {
|
|
846
|
+
return formatModelSelectorValue(`${resolved.model.provider}/${resolved.model.id}`, resolved.thinkingLevel);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
const inheritedDefaultThinkingLevel = getDefaultAliasThinkingLevel(normalized);
|
|
850
|
+
if (inheritedDefaultThinkingLevel) return this.#formatCurrentModelSelector(inheritedDefaultThinkingLevel);
|
|
851
|
+
return undefined;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
#buildCustomModelProfileSnapshot(): ModelProfileConfig {
|
|
855
|
+
const modelMapping: ModelProfileConfig["model_mapping"] = {};
|
|
856
|
+
const currentModelSelector = this.#formatCurrentModelSelector();
|
|
857
|
+
if (currentModelSelector) {
|
|
858
|
+
modelMapping.default = currentModelSelector;
|
|
859
|
+
} else {
|
|
860
|
+
const defaultRole = this.#settings.getModelRole("default");
|
|
861
|
+
const defaultSelector = this.#resolveProfileModelSelector(defaultRole);
|
|
862
|
+
if (defaultSelector) modelMapping.default = defaultSelector;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
const agentOverrides = this.#settings.get("task.agentModelOverrides");
|
|
866
|
+
for (const role of GJC_MODEL_ASSIGNMENT_TARGET_IDS) {
|
|
867
|
+
if (role === "default") continue;
|
|
868
|
+
const selector = this.#resolveProfileModelSelector(agentOverrides[role]);
|
|
869
|
+
if (selector) modelMapping[role] = selector;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
return {
|
|
873
|
+
required_providers: deriveRequiredProviders(modelMapping),
|
|
874
|
+
model_mapping: modelMapping,
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
#findDuplicateGeneratedProfile(snapshot: ModelProfileConfig): ModelProfileDefinition | undefined {
|
|
879
|
+
for (const profile of this.#modelRegistry.getModelProfiles?.().values() ?? []) {
|
|
880
|
+
if (
|
|
881
|
+
sameStringRecord(profile.modelMapping, snapshot.model_mapping) &&
|
|
882
|
+
sameStringArray(profile.requiredProviders, snapshot.required_providers)
|
|
883
|
+
) {
|
|
884
|
+
return profile;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
return undefined;
|
|
888
|
+
}
|
|
889
|
+
|
|
736
890
|
#getPresetRows(): PresetLandingRow[] {
|
|
737
891
|
const rows: PresetLandingRow[] = [];
|
|
738
892
|
for (const [groupId, profiles] of this.#getPresetGroups()) {
|
|
@@ -741,7 +895,13 @@ export class ModelSelectorComponent extends Container {
|
|
|
741
895
|
for (const profile of profiles) rows.push({ kind: "profile", groupId, profile });
|
|
742
896
|
}
|
|
743
897
|
}
|
|
744
|
-
|
|
898
|
+
const snapshot = this.#buildCustomModelProfileSnapshot();
|
|
899
|
+
if (hasPersistableProfileSnapshot(snapshot)) {
|
|
900
|
+
const duplicateProfile = this.#findDuplicateGeneratedProfile(snapshot);
|
|
901
|
+
rows.push(duplicateProfile ? { kind: "alreadySaved", profile: duplicateProfile } : { kind: "create" });
|
|
902
|
+
} else {
|
|
903
|
+
rows.push({ kind: "createUnavailable", label: "Select a model before creating a custom preset" });
|
|
904
|
+
}
|
|
745
905
|
rows.push({ kind: "browse" });
|
|
746
906
|
return rows;
|
|
747
907
|
}
|
|
@@ -812,10 +972,24 @@ export class ModelSelectorComponent extends Container {
|
|
|
812
972
|
this.#presetCursor = relocated;
|
|
813
973
|
return true;
|
|
814
974
|
}
|
|
815
|
-
|
|
975
|
+
#relocatePresetCursorForProfile(profileName: string): boolean {
|
|
976
|
+
for (const [groupId, profiles] of this.#getPresetGroups()) {
|
|
977
|
+
if (!profiles.some(profile => profile.name === profileName)) continue;
|
|
978
|
+
this.#expandedPresetProviderId = groupId;
|
|
979
|
+
return this.#relocatePresetCursor(`profile:${groupId}:${profileName}`);
|
|
980
|
+
}
|
|
981
|
+
return false;
|
|
982
|
+
}
|
|
816
983
|
#expandSelectedPresetProvider(): void {
|
|
817
984
|
const selected = this.#getSelectedPresetRow();
|
|
818
|
-
if (
|
|
985
|
+
if (
|
|
986
|
+
!selected ||
|
|
987
|
+
selected.kind === "browse" ||
|
|
988
|
+
selected.kind === "create" ||
|
|
989
|
+
selected.kind === "createUnavailable" ||
|
|
990
|
+
selected.kind === "alreadySaved"
|
|
991
|
+
)
|
|
992
|
+
return;
|
|
819
993
|
if (this.#expandedPresetProviderId === selected.groupId) return;
|
|
820
994
|
const targetIdentity = presetRowIdentity(selected);
|
|
821
995
|
this.#expandedPresetProviderId = selected.groupId;
|
|
@@ -824,7 +998,14 @@ export class ModelSelectorComponent extends Container {
|
|
|
824
998
|
|
|
825
999
|
#collapseSelectedPresetProvider(): void {
|
|
826
1000
|
const selected = this.#getSelectedPresetRow();
|
|
827
|
-
if (
|
|
1001
|
+
if (
|
|
1002
|
+
!selected ||
|
|
1003
|
+
selected.kind === "browse" ||
|
|
1004
|
+
selected.kind === "create" ||
|
|
1005
|
+
selected.kind === "createUnavailable" ||
|
|
1006
|
+
selected.kind === "alreadySaved"
|
|
1007
|
+
)
|
|
1008
|
+
return;
|
|
828
1009
|
if (this.#expandedPresetProviderId !== selected.groupId) return;
|
|
829
1010
|
const targetIdentity = selected.kind === "profile" ? `group:${selected.groupId}` : presetRowIdentity(selected);
|
|
830
1011
|
this.#expandedPresetProviderId = undefined;
|
|
@@ -860,6 +1041,18 @@ export class ModelSelectorComponent extends Container {
|
|
|
860
1041
|
this.#listContainer.addChild(new Text(`${prefix}${selected ? theme.fg("accent", label) : label}`, 0, 0));
|
|
861
1042
|
continue;
|
|
862
1043
|
}
|
|
1044
|
+
if (row.kind === "createUnavailable") {
|
|
1045
|
+
const renderedLabel = selected ? theme.fg("accent", row.label) : theme.fg("dim", row.label);
|
|
1046
|
+
this.#listContainer.addChild(new Text(`${prefix}${renderedLabel}`, 0, 0));
|
|
1047
|
+
continue;
|
|
1048
|
+
}
|
|
1049
|
+
if (row.kind === "alreadySaved") {
|
|
1050
|
+
const presentation = getModelProfilePresentation(row.profile);
|
|
1051
|
+
const label = `Already saved as ${presentation.displayName}`;
|
|
1052
|
+
const renderedLabel = selected ? theme.fg("accent", label) : theme.fg("dim", label);
|
|
1053
|
+
this.#listContainer.addChild(new Text(`${prefix}${renderedLabel}`, 0, 0));
|
|
1054
|
+
continue;
|
|
1055
|
+
}
|
|
863
1056
|
if (row.kind === "browse") {
|
|
864
1057
|
const label = "Browse all models";
|
|
865
1058
|
this.#listContainer.addChild(new Text(`${prefix}${selected ? theme.fg("accent", label) : label}`, 0, 0));
|
|
@@ -908,8 +1101,9 @@ export class ModelSelectorComponent extends Container {
|
|
|
908
1101
|
}
|
|
909
1102
|
this.#listContainer.addChild(new Spacer(1));
|
|
910
1103
|
if (this.#presetScopeMenuOpen) {
|
|
911
|
-
|
|
912
|
-
|
|
1104
|
+
const actionLabels = this.#getPresetScopeLabels(profile);
|
|
1105
|
+
for (let i = 0; i < actionLabels.length; i++) {
|
|
1106
|
+
const label = actionLabels[i] ?? "";
|
|
913
1107
|
const prefix = i === this.#presetScopeIndex ? theme.fg("accent", `${theme.nav.cursor} `) : " ";
|
|
914
1108
|
this.#listContainer.addChild(
|
|
915
1109
|
new Text(`${prefix}${i === this.#presetScopeIndex ? theme.fg("accent", label) : label}`, 0, 0),
|
|
@@ -1220,8 +1414,8 @@ export class ModelSelectorComponent extends Container {
|
|
|
1220
1414
|
const rows = this.#getPresetRows();
|
|
1221
1415
|
if (rows.length === 0) return;
|
|
1222
1416
|
if (this.#presetScopeMenuOpen) {
|
|
1223
|
-
this.#
|
|
1224
|
-
|
|
1417
|
+
const actionCount = this.#getPreviewPresetScopeLabels().length;
|
|
1418
|
+
this.#presetScopeIndex = this.#presetScopeIndex === 0 ? actionCount - 1 : this.#presetScopeIndex - 1;
|
|
1225
1419
|
} else {
|
|
1226
1420
|
this.#presetCursor = this.#presetCursor === 0 ? rows.length - 1 : this.#presetCursor - 1;
|
|
1227
1421
|
this.#previewProfileName = undefined;
|
|
@@ -1235,7 +1429,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
1235
1429
|
const rows = this.#getPresetRows();
|
|
1236
1430
|
if (rows.length === 0) return;
|
|
1237
1431
|
if (this.#presetScopeMenuOpen) {
|
|
1238
|
-
this.#presetScopeIndex = (this.#presetScopeIndex + 1) %
|
|
1432
|
+
this.#presetScopeIndex = (this.#presetScopeIndex + 1) % this.#getPreviewPresetScopeLabels().length;
|
|
1239
1433
|
} else {
|
|
1240
1434
|
this.#presetCursor = (this.#presetCursor + 1) % rows.length;
|
|
1241
1435
|
this.#previewProfileName = undefined;
|
|
@@ -1290,6 +1484,22 @@ export class ModelSelectorComponent extends Container {
|
|
|
1290
1484
|
|
|
1291
1485
|
#handlePresetEnter(): void {
|
|
1292
1486
|
if (this.#presetScopeMenuOpen && this.#previewProfileName) {
|
|
1487
|
+
const profile = this.#getProfileByName(this.#previewProfileName);
|
|
1488
|
+
if (!profile) return;
|
|
1489
|
+
if (this.#presetScopeIndex === 2 && isCustomUserProfile(profile)) {
|
|
1490
|
+
this.#onSelectCallback({ kind: "renameProfile", profileName: this.#previewProfileName });
|
|
1491
|
+
return;
|
|
1492
|
+
}
|
|
1493
|
+
if (this.#presetScopeIndex === 3 && isCustomUserProfile(profile)) {
|
|
1494
|
+
this.#onSelectCallback({ kind: "deleteProfile", profileName: this.#previewProfileName });
|
|
1495
|
+
return;
|
|
1496
|
+
}
|
|
1497
|
+
const missing = this.#getMissingProviders(profile);
|
|
1498
|
+
if (missing.length > 0) {
|
|
1499
|
+
this.#presetLoginHint = `Run ${missing.map(provider => `/login ${provider}`).join(", ")}`;
|
|
1500
|
+
this.#renderPresetLanding();
|
|
1501
|
+
return;
|
|
1502
|
+
}
|
|
1293
1503
|
this.#onSelectCallback({
|
|
1294
1504
|
kind: "profile",
|
|
1295
1505
|
profileName: this.#previewProfileName,
|
|
@@ -1306,7 +1516,10 @@ export class ModelSelectorComponent extends Container {
|
|
|
1306
1516
|
const row = this.#getSelectedPresetRow();
|
|
1307
1517
|
if (!row) return;
|
|
1308
1518
|
if (row.kind === "create") {
|
|
1309
|
-
this.#onSelectCallback({ kind: "createProfile" });
|
|
1519
|
+
this.#onSelectCallback({ kind: "createProfile", profile: this.#buildCustomModelProfileSnapshot() });
|
|
1520
|
+
return;
|
|
1521
|
+
}
|
|
1522
|
+
if (row.kind === "alreadySaved" || row.kind === "createUnavailable") {
|
|
1310
1523
|
return;
|
|
1311
1524
|
}
|
|
1312
1525
|
if (row.kind === "browse") {
|
|
@@ -1325,7 +1538,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
1325
1538
|
return;
|
|
1326
1539
|
}
|
|
1327
1540
|
const missing = this.#getMissingProviders(row.profile);
|
|
1328
|
-
if (missing.length > 0) {
|
|
1541
|
+
if (missing.length > 0 && !isCustomUserProfile(row.profile)) {
|
|
1329
1542
|
this.#presetLoginHint = `Run ${missing.map(provider => `/login ${provider}`).join(", ")}`;
|
|
1330
1543
|
this.#renderPresetLanding();
|
|
1331
1544
|
return;
|
|
@@ -1335,6 +1548,31 @@ export class ModelSelectorComponent extends Container {
|
|
|
1335
1548
|
this.#renderPresetLanding();
|
|
1336
1549
|
}
|
|
1337
1550
|
|
|
1551
|
+
#getPresetScopeLabels(profile: ModelProfileDefinition): string[] {
|
|
1552
|
+
return isCustomUserProfile(profile) ? CUSTOM_PRESET_SCOPE_LABELS : PRESET_SCOPE_LABELS;
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
#getPreviewPresetScopeLabels(): string[] {
|
|
1556
|
+
const profile = this.#getProfileByName(this.#previewProfileName);
|
|
1557
|
+
return profile ? this.#getPresetScopeLabels(profile) : PRESET_SCOPE_LABELS;
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
refreshPresetProfiles(profileName?: string): void {
|
|
1561
|
+
if (profileName) {
|
|
1562
|
+
this.#previewProfileName = profileName;
|
|
1563
|
+
this.#presetLoginHint = undefined;
|
|
1564
|
+
if (!this.#relocatePresetCursorForProfile(profileName)) this.#clampPresetCursor();
|
|
1565
|
+
} else {
|
|
1566
|
+
this.#previewProfileName = undefined;
|
|
1567
|
+
this.#presetScopeMenuOpen = false;
|
|
1568
|
+
this.#presetScopeIndex = 0;
|
|
1569
|
+
this.#presetLoginHint = undefined;
|
|
1570
|
+
this.#clampPresetCursor();
|
|
1571
|
+
}
|
|
1572
|
+
this.#renderPresetLanding();
|
|
1573
|
+
this.#tui.requestRender();
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1338
1576
|
#beginActionMenuOrSelect(item: ModelItem | CanonicalModelItem): void {
|
|
1339
1577
|
if (this.#temporaryOnly) {
|
|
1340
1578
|
this.#handleSelect(item, null);
|
|
@@ -1454,6 +1692,16 @@ export class ModelSelectorComponent extends Container {
|
|
|
1454
1692
|
async __testSelectProfile(profileName: string, setDefault: boolean): Promise<void> {
|
|
1455
1693
|
await this.#onSelectCallback({ kind: "profile", profileName, setDefault });
|
|
1456
1694
|
}
|
|
1695
|
+
async __testSelectPresetAction(profileName: string, action: "rename" | "delete"): Promise<void> {
|
|
1696
|
+
await this.#onSelectCallback({
|
|
1697
|
+
kind: action === "rename" ? "renameProfile" : "deleteProfile",
|
|
1698
|
+
profileName,
|
|
1699
|
+
});
|
|
1700
|
+
}
|
|
1701
|
+
__testSelectedPresetRowIdentity(): string | undefined {
|
|
1702
|
+
const row = this.#getSelectedPresetRow();
|
|
1703
|
+
return row ? presetRowIdentity(row) : undefined;
|
|
1704
|
+
}
|
|
1457
1705
|
}
|
|
1458
1706
|
|
|
1459
1707
|
function requiresExplicitThinkingChoice(model: Model): boolean {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* - Enter on main session -> close overlay (jump back)
|
|
16
16
|
*/
|
|
17
17
|
import type { ToolResultMessage } from "@gajae-code/ai";
|
|
18
|
-
import { Container, Markdown, type MarkdownTheme, matchesKey } from "@gajae-code/tui";
|
|
18
|
+
import { Container, Markdown, type MarkdownTheme, matchesKey, resolveTerminalColumns } from "@gajae-code/tui";
|
|
19
19
|
import { formatDuration, formatNumber, logger } from "@gajae-code/utils";
|
|
20
20
|
import type { KeyId } from "../../config/keybindings";
|
|
21
21
|
import { isSilentAbort } from "../../session/messages";
|
|
@@ -39,7 +39,7 @@ const INDENT = " ";
|
|
|
39
39
|
|
|
40
40
|
/** Compute the max content width for the current terminal, accounting for indent and chrome. */
|
|
41
41
|
function contentWidth(indent = INDENT): number {
|
|
42
|
-
return Math.max(TRUNCATE_LENGTHS.SHORT, (
|
|
42
|
+
return Math.max(TRUNCATE_LENGTHS.SHORT, resolveTerminalColumns() - indent.length - 2);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
/** Sanitize a line for TUI display: replace tabs, then truncate to viewport width. */
|
|
@@ -390,7 +390,7 @@ export class SessionObserverOverlayComponent extends Container {
|
|
|
390
390
|
|
|
391
391
|
/** Render markdown text into indented lines using the theme's markdown renderer */
|
|
392
392
|
#renderMarkdownToLines(text: string, indent: string = INDENT): string[] {
|
|
393
|
-
const width = Math.max(40, (
|
|
393
|
+
const width = Math.max(40, resolveTerminalColumns() - indent.length - 4);
|
|
394
394
|
const md = new Markdown(text, 0, 0, this.#mdTheme);
|
|
395
395
|
const rendered = md.render(width);
|
|
396
396
|
return rendered.map(line => `${indent}${line.trimEnd()}`);
|