@sayknow-cli/coding-agent 0.3.5 → 0.3.7
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/README.md +76 -0
- package/dist/types/capability/mcp.d.ts +2 -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/telegram-cli.d.ts +13 -0
- package/dist/types/cli/update-cli.d.ts +24 -1
- package/dist/types/commands/local-provider.d.ts +34 -0
- package/dist/types/commands/telegram.d.ts +22 -0
- package/dist/types/config/keybindings.d.ts +12 -6
- package/dist/types/config/model-registry.d.ts +5 -0
- package/dist/types/config/models-config-schema.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +284 -4
- package/dist/types/config/settings.d.ts +7 -0
- package/dist/types/config/telegram-autostart.d.ts +14 -0
- package/dist/types/config/telegram-env-bridge.d.ts +18 -0
- package/dist/types/coordinator-mcp/server.d.ts +8 -0
- package/dist/types/defaults/skc-defaults.d.ts +7 -0
- package/dist/types/discovery/cursor.d.ts +4 -1
- package/dist/types/discovery/index.d.ts +0 -1
- package/dist/types/discovery/windsurf.d.ts +3 -1
- package/dist/types/extensibility/extensions/types.d.ts +11 -0
- package/dist/types/i18n/messages/en.d.ts +1 -0
- package/dist/types/modes/components/custom-editor.d.ts +6 -1
- package/dist/types/modes/components/custom-model-preset-wizard.d.ts +1 -1
- package/dist/types/modes/components/hook-selector.d.ts +5 -0
- package/dist/types/modes/components/model-selector.d.ts +2 -0
- package/dist/types/modes/components/settings-selector.d.ts +5 -1
- package/dist/types/modes/components/skill-message.d.ts +1 -0
- package/dist/types/modes/components/status-line/types.d.ts +5 -7
- package/dist/types/modes/components/status-line.d.ts +15 -3
- package/dist/types/modes/components/two-column-body.d.ts +9 -0
- package/dist/types/modes/components/welcome.d.ts +9 -1
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/input-controller.d.ts +6 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +10 -1
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/prompt-action-autocomplete.d.ts +3 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +1 -0
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +41 -7
- package/dist/types/modes/slash-command-visibility.d.ts +1 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/tmux-scroll.d.ts +18 -0
- package/dist/types/modes/types.d.ts +5 -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/html-format.d.ts +2 -0
- package/dist/types/notifications/recent-activity.d.ts +5 -1
- package/dist/types/notifications/telegram-daemon-cli.d.ts +21 -4
- package/dist/types/notifications/telegram-daemon-control.d.ts +8 -0
- package/dist/types/notifications/telegram-daemon.d.ts +4 -10
- package/dist/types/notifications/telegram-reference.d.ts +4 -0
- package/dist/types/notifications/threaded-render.d.ts +1 -0
- package/dist/types/runtime-mcp/config-writer.d.ts +9 -0
- package/dist/types/runtime-mcp/config.d.ts +2 -0
- package/dist/types/runtime-mcp/loader.d.ts +2 -0
- package/dist/types/runtime-mcp/manager.d.ts +2 -0
- package/dist/types/runtime-mcp/types.d.ts +6 -0
- package/dist/types/sdk.d.ts +1 -1
- package/dist/types/skc-runtime/launch-tmux.d.ts +2 -2
- package/dist/types/skc-runtime/ralplan-runtime.d.ts +3 -2
- package/dist/types/skc-runtime/restricted-role-agent-bash.d.ts +1 -0
- package/dist/types/skc-runtime/session-state-sidecar.d.ts +5 -0
- package/dist/types/skc-runtime/state-writer.d.ts +1 -0
- package/dist/types/skc-runtime/team-runtime.d.ts +1 -1
- package/dist/types/skc-runtime/tmux-common.d.ts +12 -0
- package/dist/types/skc-runtime/ultragoal-guard.d.ts +7 -1
- package/dist/types/skc-runtime/ultragoal-runtime.d.ts +0 -4
- package/dist/types/slash-commands/types.d.ts +2 -0
- package/dist/types/system-prompt.d.ts +7 -0
- package/dist/types/tools/skill.d.ts +1 -1
- package/dist/types/utils/cmux-workspace.d.ts +52 -0
- package/dist/types/web/search/index.d.ts +4 -2
- package/dist/types/web/search/provider.d.ts +14 -0
- package/dist/types/web/search/providers/utils.d.ts +42 -13
- package/dist/types/web/search/render.d.ts +1 -0
- package/dist/types/workflow/workflow-intent-diff.d.ts +19 -0
- package/package.json +8 -8
- package/scripts/build-binary.ts +3 -0
- package/scripts/verify-insane-vendor.ts +0 -5
- package/src/capability/mcp.ts +2 -0
- package/src/cli/local-provider-smoke.ts +632 -0
- package/src/cli/notify-cli.ts +4 -2
- package/src/cli/telegram-cli.ts +156 -0
- package/src/cli/update-cli.ts +179 -13
- package/src/cli/web-search-cli.ts +5 -7
- package/src/cli.ts +20 -1
- package/src/commands/launch.ts +47 -2
- package/src/commands/local-provider.ts +62 -0
- package/src/commands/notify.ts +1 -1
- package/src/commands/ralplan.ts +1 -0
- package/src/commands/telegram.ts +47 -0
- package/src/config/keybindings.ts +15 -6
- package/src/config/mcp-schema.json +4 -0
- package/src/config/model-registry.ts +55 -1
- package/src/config/models-config-schema.ts +9 -0
- package/src/config/settings-schema.ts +256 -4
- package/src/config/settings.ts +11 -0
- package/src/config/telegram-autostart.ts +107 -0
- package/src/config/telegram-env-bridge.ts +92 -0
- package/src/coordinator-mcp/server.ts +276 -34
- package/src/defaults/skc/skills/ralplan/SKILL.md +29 -20
- package/src/defaults/skc/skills/ultragoal/SKILL.md +37 -112
- package/src/defaults/skc-defaults.ts +16 -0
- package/src/discovery/builtin.ts +12 -0
- package/src/discovery/cursor.ts +5 -84
- package/src/discovery/gemini.ts +3 -89
- package/src/discovery/index.ts +4 -1
- package/src/discovery/mcp-json.ts +11 -0
- package/src/discovery/opencode.ts +5 -122
- package/src/discovery/windsurf.ts +4 -82
- package/src/edit/read-file.ts +9 -1
- package/src/extensibility/extensions/runner.ts +2 -1
- package/src/extensibility/extensions/types.ts +11 -0
- package/src/extensibility/skills.ts +30 -2
- package/src/hooks/skill-state.ts +33 -73
- package/src/i18n/messages/de.ts +1 -0
- package/src/i18n/messages/en.ts +1 -0
- package/src/i18n/messages/es.ts +1 -0
- package/src/i18n/messages/fr.ts +1 -0
- package/src/i18n/messages/ja.settings.ts +28 -0
- package/src/i18n/messages/ja.ts +1 -0
- package/src/i18n/messages/ko.settings.ts +28 -0
- package/src/i18n/messages/ko.ts +1 -0
- package/src/i18n/messages/zh.settings.ts +28 -0
- package/src/i18n/messages/zh.ts +1 -0
- package/src/internal-urls/docs-index.generated.ts +12 -7
- package/src/main.ts +5 -2
- package/src/migrate/executor.ts +23 -6
- package/src/modes/acp/acp-agent.ts +7 -6
- package/src/modes/components/agent-dashboard.ts +3 -35
- package/src/modes/components/custom-editor.ts +26 -0
- package/src/modes/components/custom-model-preset-wizard.ts +30 -189
- package/src/modes/components/extensions/extension-dashboard.ts +2 -48
- package/src/modes/components/hook-selector.ts +21 -3
- package/src/modes/components/model-selector.ts +246 -11
- package/src/modes/components/session-observer-overlay.ts +3 -3
- package/src/modes/components/session-selector.ts +19 -9
- package/src/modes/components/settings-selector.ts +551 -11
- package/src/modes/components/skill-message.ts +18 -10
- package/src/modes/components/status-line/presets.ts +11 -0
- 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 +281 -117
- package/src/modes/components/tree-selector.ts +28 -8
- package/src/modes/components/two-column-body.ts +36 -0
- package/src/modes/components/welcome.ts +165 -30
- package/src/modes/controllers/command-controller.ts +26 -2
- package/src/modes/controllers/event-controller.ts +102 -4
- package/src/modes/controllers/extension-ui-controller.ts +21 -2
- package/src/modes/controllers/input-controller.ts +214 -47
- package/src/modes/controllers/selector-controller.ts +35 -33
- package/src/modes/interactive-mode.ts +36 -30
- package/src/modes/prompt-action-autocomplete.ts +48 -8
- package/src/modes/rpc/rpc-types.ts +1 -0
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +72 -15
- package/src/modes/shared/agent-wire/workflow-gate-schema.ts +20 -0
- package/src/modes/slash-command-visibility.ts +9 -0
- package/src/modes/theme/theme.ts +5 -1
- package/src/modes/tmux-scroll.ts +92 -0
- package/src/modes/types.ts +8 -1
- package/src/modes/utils/hotkeys-markdown.ts +5 -2
- package/src/modes/utils/terminal-bell.ts +48 -0
- package/src/modes/utils/ui-helpers.ts +76 -3
- package/src/notifications/daemon-paths.ts +22 -0
- package/src/notifications/html-format.ts +75 -1
- package/src/notifications/index.ts +32 -2
- package/src/notifications/lifecycle-control-runtime.ts +12 -3
- package/src/notifications/recent-activity.ts +32 -8
- package/src/notifications/telegram-daemon-cli.ts +125 -13
- package/src/notifications/telegram-daemon-control.ts +9 -0
- package/src/notifications/telegram-daemon.ts +88 -46
- package/src/notifications/telegram-reference.ts +36 -14
- package/src/notifications/threaded-render.ts +14 -6
- package/src/prompts/agents/architect.md +7 -4
- package/src/prompts/agents/critic.md +15 -6
- package/src/prompts/agents/planner.md +8 -3
- package/src/prompts/system/system-prompt.md +20 -2
- package/src/prompts/tools/bash.md +1 -1
- package/src/runtime-mcp/config-writer.ts +28 -0
- package/src/runtime-mcp/config.ts +7 -0
- package/src/runtime-mcp/loader.ts +3 -0
- package/src/runtime-mcp/manager.ts +3 -0
- package/src/runtime-mcp/types.ts +6 -0
- package/src/sdk.ts +10 -11
- package/src/session/agent-session.ts +103 -13
- package/src/session/session-manager.ts +58 -18
- package/src/skc-runtime/launch-tmux.ts +34 -8
- package/src/skc-runtime/launch-worktree.ts +16 -2
- package/src/skc-runtime/ralplan-runtime.ts +22 -6
- package/src/skc-runtime/restricted-role-agent-bash.ts +1 -0
- package/src/skc-runtime/session-state-sidecar.ts +167 -24
- package/src/skc-runtime/state-runtime.ts +8 -5
- package/src/skc-runtime/state-writer.ts +5 -5
- package/src/skc-runtime/team-runtime.ts +21 -6
- package/src/skc-runtime/tmux-common.ts +16 -0
- package/src/skc-runtime/tmux-sessions.ts +13 -9
- package/src/skc-runtime/ultragoal-guard.ts +150 -24
- package/src/skc-runtime/ultragoal-runtime.ts +16 -124
- package/src/skc-runtime/workflow-manifest.generated.json +0 -8
- package/src/skc-runtime/workflow-manifest.ts +0 -1
- package/src/slash-commands/builtin-registry.ts +136 -23
- package/src/slash-commands/types.ts +2 -0
- package/src/system-prompt.ts +8 -0
- package/src/task/executor.ts +5 -0
- package/src/tools/ast-grep.ts +10 -7
- package/src/tools/bash.ts +39 -14
- package/src/tools/index.ts +8 -1
- package/src/tools/skill.ts +1 -1
- package/src/utils/cmux-workspace.ts +233 -0
- package/src/utils/title-generator.ts +2 -0
- package/src/web/kagi.ts +1 -1
- package/src/web/parallel.ts +2 -2
- package/src/web/search/index.ts +137 -52
- package/src/web/search/provider.ts +80 -0
- package/src/web/search/providers/anthropic.ts +1 -1
- package/src/web/search/providers/brave.ts +1 -1
- package/src/web/search/providers/codex.ts +1 -1
- package/src/web/search/providers/duckduckgo.ts +1 -1
- package/src/web/search/providers/exa.ts +1 -1
- package/src/web/search/providers/gemini.ts +7 -3
- package/src/web/search/providers/jina.ts +1 -1
- package/src/web/search/providers/kimi.ts +6 -1
- package/src/web/search/providers/openai-compatible.ts +1 -1
- package/src/web/search/providers/parallel.ts +1 -1
- package/src/web/search/providers/perplexity.ts +2 -2
- package/src/web/search/providers/searxng.ts +1 -1
- package/src/web/search/providers/synthetic.ts +1 -1
- package/src/web/search/providers/tavily.ts +1 -1
- package/src/web/search/providers/utils.ts +67 -17
- package/src/web/search/providers/xai.ts +1 -1
- package/src/web/search/providers/zai.ts +1 -1
- package/src/web/search/render.ts +3 -0
- package/src/workflow/workflow-intent-diff.ts +199 -0
- package/vendor/insane-search/MANIFEST.json +1 -3
- package/vendor/insane-search/engine/tests/test_u1.py +0 -28
- package/vendor/insane-search/engine/transport.py +2 -3
- package/vendor/insane-search/engine/validators.py +0 -14
- package/src/defaults/skc/rules/ponytail.md +0 -68
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
resolveModelRoleValue,
|
|
30
30
|
type ScopedModelSelection,
|
|
31
31
|
} from "../../config/model-resolver";
|
|
32
|
+
import type { ModelProfileConfig } from "../../config/models-config-schema";
|
|
32
33
|
import type { Settings } from "../../config/settings";
|
|
33
34
|
import { t } from "../../i18n";
|
|
34
35
|
import { type ThemeColor, theme } from "../../modes/theme/theme";
|
|
@@ -107,10 +108,9 @@ export type ModelSelectorSelection =
|
|
|
107
108
|
}
|
|
108
109
|
| {
|
|
109
110
|
kind: "createProfile";
|
|
111
|
+
profile: ModelProfileConfig;
|
|
110
112
|
}
|
|
111
113
|
| {
|
|
112
|
-
// User picked an unauthenticated preset/model — launch OAuth login for
|
|
113
|
-
// the first missing provider instead of only printing a hint.
|
|
114
114
|
kind: "login";
|
|
115
115
|
providerId: string;
|
|
116
116
|
};
|
|
@@ -163,11 +163,27 @@ interface PresetCreateRow {
|
|
|
163
163
|
kind: "create";
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
interface PresetCreateUnavailableRow {
|
|
167
|
+
kind: "createUnavailable";
|
|
168
|
+
label: string;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
interface PresetAlreadySavedRow {
|
|
172
|
+
kind: "alreadySaved";
|
|
173
|
+
profileName: string;
|
|
174
|
+
}
|
|
175
|
+
|
|
166
176
|
interface PresetBrowseRow {
|
|
167
177
|
kind: "browse";
|
|
168
178
|
}
|
|
169
179
|
|
|
170
|
-
type PresetLandingRow =
|
|
180
|
+
type PresetLandingRow =
|
|
181
|
+
| PresetGroupRow
|
|
182
|
+
| PresetProfileRow
|
|
183
|
+
| PresetCreateRow
|
|
184
|
+
| PresetCreateUnavailableRow
|
|
185
|
+
| PresetAlreadySavedRow
|
|
186
|
+
| PresetBrowseRow;
|
|
171
187
|
|
|
172
188
|
// Stable logical identity for a preset landing row, independent of its current
|
|
173
189
|
// list position. Used to relocate the cursor after the expanded group changes so
|
|
@@ -182,6 +198,10 @@ function presetRowIdentity(row: PresetLandingRow): string {
|
|
|
182
198
|
return "browse";
|
|
183
199
|
case "create":
|
|
184
200
|
return "create";
|
|
201
|
+
case "createUnavailable":
|
|
202
|
+
return "createUnavailable";
|
|
203
|
+
case "alreadySaved":
|
|
204
|
+
return `alreadySaved:${row.profileName}`;
|
|
185
205
|
}
|
|
186
206
|
}
|
|
187
207
|
|
|
@@ -201,6 +221,64 @@ function isPrintableCharacter(keyData: string): boolean {
|
|
|
201
221
|
function profileRequiredProviders(profile: ModelProfileDefinition): string[] {
|
|
202
222
|
return [...new Set(profile.requiredProviders)].sort((a, b) => a.localeCompare(b));
|
|
203
223
|
}
|
|
224
|
+
|
|
225
|
+
function isInheritedRoleSelector(value: string | undefined): boolean {
|
|
226
|
+
const normalized = value?.trim();
|
|
227
|
+
return !normalized || normalized === "default" || normalized === "pi/default";
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function getDefaultAliasThinkingLevel(value: string | undefined): ThinkingLevel | undefined {
|
|
231
|
+
const normalized = value?.trim();
|
|
232
|
+
if (!normalized?.startsWith("pi/default:")) return undefined;
|
|
233
|
+
return parseThinkingLevel(normalized.slice("pi/default:".length));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function getSelectorProvider(selector: string): string | undefined {
|
|
237
|
+
const slashIndex = selector.indexOf("/");
|
|
238
|
+
return slashIndex > 0 ? selector.slice(0, slashIndex) : undefined;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function deriveRequiredProviders(modelMapping: ModelProfileConfig["model_mapping"]): string[] {
|
|
242
|
+
const providers = new Set<string>();
|
|
243
|
+
for (const selector of Object.values(modelMapping)) {
|
|
244
|
+
const provider = getSelectorProvider(selector);
|
|
245
|
+
if (provider) providers.add(provider);
|
|
246
|
+
}
|
|
247
|
+
return [...providers].sort((a, b) => a.localeCompare(b));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function sameStringRecord(
|
|
251
|
+
left: Readonly<Record<string, string | undefined>>,
|
|
252
|
+
right: Readonly<Record<string, string | undefined>>,
|
|
253
|
+
): boolean {
|
|
254
|
+
const leftEntries = Object.entries(left)
|
|
255
|
+
.filter((entry): entry is [string, string] => entry[1] !== undefined)
|
|
256
|
+
.sort(([a], [b]) => a.localeCompare(b));
|
|
257
|
+
const rightEntries = Object.entries(right)
|
|
258
|
+
.filter((entry): entry is [string, string] => entry[1] !== undefined)
|
|
259
|
+
.sort(([a], [b]) => a.localeCompare(b));
|
|
260
|
+
if (leftEntries.length !== rightEntries.length) return false;
|
|
261
|
+
for (let i = 0; i < leftEntries.length; i++) {
|
|
262
|
+
const leftEntry = leftEntries[i];
|
|
263
|
+
const rightEntry = rightEntries[i];
|
|
264
|
+
if (!leftEntry || !rightEntry || leftEntry[0] !== rightEntry[0] || leftEntry[1] !== rightEntry[1]) {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function sameStringArray(left: readonly string[], right: readonly string[]): boolean {
|
|
272
|
+
if (left.length !== right.length) return false;
|
|
273
|
+
for (let i = 0; i < left.length; i++) {
|
|
274
|
+
if (left[i] !== right[i]) return false;
|
|
275
|
+
}
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function hasPersistableProfileSnapshot(snapshot: ModelProfileConfig): boolean {
|
|
280
|
+
return Object.keys(snapshot.model_mapping).length > 0 && snapshot.required_providers.length > 0;
|
|
281
|
+
}
|
|
204
282
|
/**
|
|
205
283
|
* Component that renders a canonical model selector with provider tabs.
|
|
206
284
|
* - Preset landing Left/Right: Collapse/expand selected provider
|
|
@@ -319,7 +397,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
319
397
|
// Create search input
|
|
320
398
|
this.#searchInput = new Input();
|
|
321
399
|
if (initialSearchInput) {
|
|
322
|
-
this.#
|
|
400
|
+
this.#setSearchInputValue(initialSearchInput);
|
|
323
401
|
}
|
|
324
402
|
this.#searchInput.onSubmit = () => {
|
|
325
403
|
const selectedItem = this.#getSelectedItem();
|
|
@@ -740,6 +818,67 @@ export class ModelSelectorComponent extends Container {
|
|
|
740
818
|
return groupModelProfilesForPresetLanding(this.#modelRegistry.getModelProfiles?.() ?? new Map());
|
|
741
819
|
}
|
|
742
820
|
|
|
821
|
+
#formatCurrentModelSelector(
|
|
822
|
+
thinkingLevel: ThinkingLevel | undefined = this.#currentThinkingLevel,
|
|
823
|
+
): string | undefined {
|
|
824
|
+
if (!this.#currentModel) return undefined;
|
|
825
|
+
return formatModelSelectorValue(`${this.#currentModel.provider}/${this.#currentModel.id}`, thinkingLevel);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
#resolveProfileModelSelector(value: string | undefined): string | undefined {
|
|
829
|
+
const normalized = value?.trim();
|
|
830
|
+
if (isInheritedRoleSelector(normalized)) return undefined;
|
|
831
|
+
|
|
832
|
+
const resolved = resolveModelRoleValue(normalized, this.#modelRegistry.getAll(), {
|
|
833
|
+
settings: this.#settings,
|
|
834
|
+
matchPreferences: { usageOrder: this.#settings.getStorage()?.getModelUsageOrder() },
|
|
835
|
+
modelRegistry: this.#modelRegistry,
|
|
836
|
+
});
|
|
837
|
+
if (resolved.model) {
|
|
838
|
+
return formatModelSelectorValue(`${resolved.model.provider}/${resolved.model.id}`, resolved.thinkingLevel);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
const inheritedDefaultThinkingLevel = getDefaultAliasThinkingLevel(normalized);
|
|
842
|
+
if (inheritedDefaultThinkingLevel) return this.#formatCurrentModelSelector(inheritedDefaultThinkingLevel);
|
|
843
|
+
return undefined;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
#buildCustomModelProfileSnapshot(): ModelProfileConfig {
|
|
847
|
+
const modelMapping: ModelProfileConfig["model_mapping"] = {};
|
|
848
|
+
const currentModelSelector = this.#formatCurrentModelSelector();
|
|
849
|
+
if (currentModelSelector) {
|
|
850
|
+
modelMapping.default = currentModelSelector;
|
|
851
|
+
} else {
|
|
852
|
+
const defaultRole = this.#settings.getModelRole("default");
|
|
853
|
+
const defaultSelector = this.#resolveProfileModelSelector(defaultRole);
|
|
854
|
+
if (defaultSelector) modelMapping.default = defaultSelector;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
const agentOverrides = this.#settings.get("task.agentModelOverrides");
|
|
858
|
+
for (const role of SKC_MODEL_ASSIGNMENT_TARGET_IDS) {
|
|
859
|
+
if (role === "default") continue;
|
|
860
|
+
const selector = this.#resolveProfileModelSelector(agentOverrides[role]);
|
|
861
|
+
if (selector) modelMapping[role] = selector;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
return {
|
|
865
|
+
required_providers: deriveRequiredProviders(modelMapping),
|
|
866
|
+
model_mapping: modelMapping,
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
#findDuplicateGeneratedProfile(snapshot: ModelProfileConfig): ModelProfileDefinition | undefined {
|
|
871
|
+
for (const profile of this.#modelRegistry.getModelProfiles?.().values() ?? []) {
|
|
872
|
+
if (
|
|
873
|
+
sameStringRecord(profile.modelMapping, snapshot.model_mapping) &&
|
|
874
|
+
sameStringArray(profile.requiredProviders, snapshot.required_providers)
|
|
875
|
+
) {
|
|
876
|
+
return profile;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
return undefined;
|
|
880
|
+
}
|
|
881
|
+
|
|
743
882
|
#getPresetRows(): PresetLandingRow[] {
|
|
744
883
|
const rows: PresetLandingRow[] = [];
|
|
745
884
|
for (const [groupId, profiles] of this.#getPresetGroups()) {
|
|
@@ -748,7 +887,15 @@ export class ModelSelectorComponent extends Container {
|
|
|
748
887
|
for (const profile of profiles) rows.push({ kind: "profile", groupId, profile });
|
|
749
888
|
}
|
|
750
889
|
}
|
|
751
|
-
|
|
890
|
+
const snapshot = this.#buildCustomModelProfileSnapshot();
|
|
891
|
+
if (hasPersistableProfileSnapshot(snapshot)) {
|
|
892
|
+
const duplicateProfile = this.#findDuplicateGeneratedProfile(snapshot);
|
|
893
|
+
rows.push(
|
|
894
|
+
duplicateProfile ? { kind: "alreadySaved", profileName: duplicateProfile.name } : { kind: "create" },
|
|
895
|
+
);
|
|
896
|
+
} else {
|
|
897
|
+
rows.push({ kind: "createUnavailable", label: "Select a model before creating a custom preset" });
|
|
898
|
+
}
|
|
752
899
|
rows.push({ kind: "browse" });
|
|
753
900
|
return rows;
|
|
754
901
|
}
|
|
@@ -835,7 +982,14 @@ export class ModelSelectorComponent extends Container {
|
|
|
835
982
|
|
|
836
983
|
#expandSelectedPresetProvider(): void {
|
|
837
984
|
const selected = this.#getSelectedPresetRow();
|
|
838
|
-
if (
|
|
985
|
+
if (
|
|
986
|
+
!selected ||
|
|
987
|
+
selected.kind === "browse" ||
|
|
988
|
+
selected.kind === "create" ||
|
|
989
|
+
selected.kind === "createUnavailable" ||
|
|
990
|
+
selected.kind === "alreadySaved"
|
|
991
|
+
)
|
|
992
|
+
return;
|
|
839
993
|
if (this.#expandedPresetProviderId === selected.groupId) return;
|
|
840
994
|
const targetIdentity = presetRowIdentity(selected);
|
|
841
995
|
this.#expandedPresetProviderId = selected.groupId;
|
|
@@ -844,13 +998,24 @@ export class ModelSelectorComponent extends Container {
|
|
|
844
998
|
|
|
845
999
|
#collapseSelectedPresetProvider(): void {
|
|
846
1000
|
const selected = this.#getSelectedPresetRow();
|
|
847
|
-
if (
|
|
1001
|
+
if (
|
|
1002
|
+
!selected ||
|
|
1003
|
+
selected.kind === "browse" ||
|
|
1004
|
+
selected.kind === "create" ||
|
|
1005
|
+
selected.kind === "createUnavailable" ||
|
|
1006
|
+
selected.kind === "alreadySaved"
|
|
1007
|
+
)
|
|
1008
|
+
return;
|
|
848
1009
|
if (this.#expandedPresetProviderId !== selected.groupId) return;
|
|
849
1010
|
const targetIdentity = selected.kind === "profile" ? `group:${selected.groupId}` : presetRowIdentity(selected);
|
|
850
1011
|
this.#expandedPresetProviderId = undefined;
|
|
851
1012
|
if (!this.#relocatePresetCursor(targetIdentity)) this.#clampPresetCursor();
|
|
852
1013
|
}
|
|
853
1014
|
|
|
1015
|
+
#setSearchInputValue(value: string): void {
|
|
1016
|
+
this.#searchInput.setValue(value);
|
|
1017
|
+
}
|
|
1018
|
+
|
|
854
1019
|
#switchToModelMode(seed?: string): void {
|
|
855
1020
|
this.#viewMode = "models";
|
|
856
1021
|
this.#expandedPresetProviderId = undefined;
|
|
@@ -860,16 +1025,56 @@ export class ModelSelectorComponent extends Container {
|
|
|
860
1025
|
this.#presetLoginHint = undefined;
|
|
861
1026
|
this.#activeTabIndex = 0;
|
|
862
1027
|
this.#selectedIndex = 0;
|
|
863
|
-
this.#
|
|
1028
|
+
this.#setSearchInputValue(seed ?? this.#searchInput.getValue());
|
|
864
1029
|
this.#updateTabBar();
|
|
865
1030
|
this.#filterModels(this.#searchInput.getValue());
|
|
866
1031
|
}
|
|
867
1032
|
|
|
1033
|
+
/**
|
|
1034
|
+
* "What is this session running right now" summary for the preset landing
|
|
1035
|
+
* header: active preset (when one is applied), the effective current model
|
|
1036
|
+
* and thinking level, and one line per assigned role (default, executor,
|
|
1037
|
+
* planner, critic, architect).
|
|
1038
|
+
*/
|
|
1039
|
+
#formatCurrentSessionLines(): string[] {
|
|
1040
|
+
const lines: string[] = [];
|
|
1041
|
+
const parts: string[] = [];
|
|
1042
|
+
if (this.#activeModelProfile) {
|
|
1043
|
+
const profile = this.#getProfileByName(this.#activeModelProfile);
|
|
1044
|
+
const displayName = profile ? getModelProfilePresentation(profile).displayName : this.#activeModelProfile;
|
|
1045
|
+
parts.push(`preset ${theme.fg("accent", displayName)}`);
|
|
1046
|
+
}
|
|
1047
|
+
if (this.#currentModel) {
|
|
1048
|
+
parts.push(this.#formatAssignedModelLabel(this.#currentModel, this.#currentThinkingLevel));
|
|
1049
|
+
}
|
|
1050
|
+
if (parts.length > 0) lines.push(theme.fg("muted", `Current: ${parts.join(" · ")}`));
|
|
1051
|
+
for (const role of PROFILE_ROLE_PREVIEW_ORDER) {
|
|
1052
|
+
const assigned = this.#roles[role];
|
|
1053
|
+
if (!assigned) continue;
|
|
1054
|
+
const label = SKC_MODEL_ASSIGNMENT_TARGETS[role].tag ?? role.toUpperCase();
|
|
1055
|
+
lines.push(
|
|
1056
|
+
theme.fg("dim", ` ${label}: ${this.#formatAssignedModelLabel(assigned.model, assigned.thinkingLevel)}`),
|
|
1057
|
+
);
|
|
1058
|
+
}
|
|
1059
|
+
return lines;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
#formatAssignedModelLabel(model: Model, thinkingLevel: ThinkingLevel | undefined): string {
|
|
1063
|
+
let label = `${model.provider}/${model.id}`;
|
|
1064
|
+
if (thinkingLevel && thinkingLevel !== ThinkingLevel.Inherit) {
|
|
1065
|
+
label += ` (${getThinkingLevelMetadata(thinkingLevel).label})`;
|
|
1066
|
+
}
|
|
1067
|
+
return label;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
868
1070
|
#renderPresetLanding(): void {
|
|
869
1071
|
this.#headerContainer.clear();
|
|
870
1072
|
this.#tabBar = null;
|
|
871
1073
|
this.#listContainer.clear();
|
|
872
1074
|
this.#headerContainer.addChild(new Text(theme.fg("accent", t("modelSelector.presets")), 0, 0));
|
|
1075
|
+
for (const line of this.#formatCurrentSessionLines()) {
|
|
1076
|
+
this.#headerContainer.addChild(new Text(line, 0, 0));
|
|
1077
|
+
}
|
|
873
1078
|
const rows = this.#getPresetRows();
|
|
874
1079
|
for (let i = 0; i < rows.length; i++) {
|
|
875
1080
|
const row = rows[i];
|
|
@@ -880,6 +1085,17 @@ export class ModelSelectorComponent extends Container {
|
|
|
880
1085
|
this.#listContainer.addChild(new Text(`${prefix}${selected ? theme.fg("accent", label) : label}`, 0, 0));
|
|
881
1086
|
continue;
|
|
882
1087
|
}
|
|
1088
|
+
if (row.kind === "createUnavailable") {
|
|
1089
|
+
const renderedLabel = selected ? theme.fg("accent", row.label) : theme.fg("dim", row.label);
|
|
1090
|
+
this.#listContainer.addChild(new Text(`${prefix}${renderedLabel}`, 0, 0));
|
|
1091
|
+
continue;
|
|
1092
|
+
}
|
|
1093
|
+
if (row.kind === "alreadySaved") {
|
|
1094
|
+
const label = `Already saved as ${row.profileName}`;
|
|
1095
|
+
const renderedLabel = selected ? theme.fg("accent", label) : theme.fg("dim", label);
|
|
1096
|
+
this.#listContainer.addChild(new Text(`${prefix}${renderedLabel}`, 0, 0));
|
|
1097
|
+
continue;
|
|
1098
|
+
}
|
|
883
1099
|
if (row.kind === "browse") {
|
|
884
1100
|
const label = t("modelSelector.browseAll");
|
|
885
1101
|
this.#listContainer.addChild(new Text(`${prefix}${selected ? theme.fg("accent", label) : label}`, 0, 0));
|
|
@@ -888,17 +1104,23 @@ export class ModelSelectorComponent extends Container {
|
|
|
888
1104
|
if (row.kind === "group") {
|
|
889
1105
|
const authenticated = this.#isPresetGroupUsable(row.profiles);
|
|
890
1106
|
const mark = this.#providerAuthPending ? "…" : authenticated ? "✓" : "✗";
|
|
1107
|
+
const containsActive =
|
|
1108
|
+
this.#activeModelProfile !== undefined &&
|
|
1109
|
+
row.profiles.some(profile => profile.name === this.#activeModelProfile);
|
|
891
1110
|
const label = `${mark} ${row.groupId}`;
|
|
892
1111
|
const renderedLabel = selected ? theme.fg("accent", label) : authenticated ? label : theme.fg("dim", label);
|
|
893
|
-
|
|
1112
|
+
const activeSuffix = containsActive ? theme.fg("muted", " (current)") : "";
|
|
1113
|
+
this.#listContainer.addChild(new Text(`${prefix}${renderedLabel}${activeSuffix}`, 0, 0));
|
|
894
1114
|
continue;
|
|
895
1115
|
}
|
|
896
1116
|
const presentation = getModelProfilePresentation(row.profile);
|
|
897
1117
|
const authenticated = this.#isPresetAuthenticated(row.profile);
|
|
898
1118
|
const mark = this.#providerAuthPending ? "…" : authenticated ? "✓" : "✗";
|
|
1119
|
+
const isActive = row.profile.name === this.#activeModelProfile;
|
|
899
1120
|
const label = ` ${mark} ${presentation.displayName}`;
|
|
900
1121
|
const renderedLabel = selected ? theme.fg("accent", label) : authenticated ? label : theme.fg("dim", label);
|
|
901
|
-
|
|
1122
|
+
const activeSuffix = isActive ? theme.fg("muted", " (current)") : "";
|
|
1123
|
+
this.#listContainer.addChild(new Text(`${prefix}${renderedLabel}${activeSuffix}`, 0, 0));
|
|
902
1124
|
}
|
|
903
1125
|
if (this.#presetLoginHint) {
|
|
904
1126
|
this.#listContainer.addChild(new Spacer(1));
|
|
@@ -1350,7 +1572,10 @@ export class ModelSelectorComponent extends Container {
|
|
|
1350
1572
|
const row = this.#getSelectedPresetRow();
|
|
1351
1573
|
if (!row) return;
|
|
1352
1574
|
if (row.kind === "create") {
|
|
1353
|
-
this.#onSelectCallback({ kind: "createProfile" });
|
|
1575
|
+
this.#onSelectCallback({ kind: "createProfile", profile: this.#buildCustomModelProfileSnapshot() });
|
|
1576
|
+
return;
|
|
1577
|
+
}
|
|
1578
|
+
if (row.kind === "alreadySaved" || row.kind === "createUnavailable") {
|
|
1354
1579
|
return;
|
|
1355
1580
|
}
|
|
1356
1581
|
if (row.kind === "browse") {
|
|
@@ -1366,7 +1591,17 @@ export class ModelSelectorComponent extends Container {
|
|
|
1366
1591
|
if (this.#startLoginForMissing(missing)) return;
|
|
1367
1592
|
this.#presetLoginHint = `Run ${missing.map(provider => `/login ${provider}`).join(", ")}`;
|
|
1368
1593
|
this.#renderPresetLanding();
|
|
1594
|
+
return;
|
|
1369
1595
|
}
|
|
1596
|
+
// Enter toggles a usable group's expansion so drilling into presets
|
|
1597
|
+
// works without reaching for the arrow keys (right/left still work).
|
|
1598
|
+
if (this.#expandedPresetProviderId === row.groupId) {
|
|
1599
|
+
this.#collapseSelectedPresetProvider();
|
|
1600
|
+
} else {
|
|
1601
|
+
this.#expandSelectedPresetProvider();
|
|
1602
|
+
}
|
|
1603
|
+
this.#presetLoginHint = undefined;
|
|
1604
|
+
this.#renderPresetLanding();
|
|
1370
1605
|
return;
|
|
1371
1606
|
}
|
|
1372
1607
|
const missing = this.#getMissingProviders(row.profile);
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* - Enter on main session -> close overlay (jump back)
|
|
16
16
|
*/
|
|
17
17
|
import type { ToolResultMessage } from "@sayknow-cli/ai";
|
|
18
|
-
import { Container, Markdown, type MarkdownTheme, matchesKey } from "@sayknow-cli/tui";
|
|
18
|
+
import { Container, Markdown, type MarkdownTheme, matchesKey, resolveTerminalColumns } from "@sayknow-cli/tui";
|
|
19
19
|
import { formatDuration, formatNumber, logger } from "@sayknow-cli/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()}`);
|
|
@@ -50,6 +50,14 @@ class SessionList implements Component {
|
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
#clampSelectedIndex(): void {
|
|
54
|
+
if (this.#filteredSessions.length === 0) {
|
|
55
|
+
this.#selectedIndex = 0;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
this.#selectedIndex = Math.max(0, Math.min(this.#selectedIndex, this.#filteredSessions.length - 1));
|
|
59
|
+
}
|
|
60
|
+
|
|
53
61
|
#filterSessions(query: string): void {
|
|
54
62
|
this.#filteredSessions = fuzzyFilter(this.allSessions, query, session => {
|
|
55
63
|
const parts = [
|
|
@@ -62,7 +70,7 @@ class SessionList implements Component {
|
|
|
62
70
|
];
|
|
63
71
|
return parts.filter(Boolean).join(" ");
|
|
64
72
|
});
|
|
65
|
-
this.#
|
|
73
|
+
this.#clampSelectedIndex();
|
|
66
74
|
}
|
|
67
75
|
|
|
68
76
|
removeSession(sessionPath: string): void {
|
|
@@ -71,10 +79,6 @@ class SessionList implements Component {
|
|
|
71
79
|
this.allSessions.splice(index, 1);
|
|
72
80
|
// Re-filter to update filteredSessions
|
|
73
81
|
this.#filterSessions(this.#searchInput.getValue());
|
|
74
|
-
// Adjust selectedIndex if we deleted the last item or beyond
|
|
75
|
-
if (this.#selectedIndex >= this.#filteredSessions.length) {
|
|
76
|
-
this.#selectedIndex = Math.max(0, this.#filteredSessions.length - 1);
|
|
77
|
-
}
|
|
78
82
|
}
|
|
79
83
|
|
|
80
84
|
invalidate(): void {
|
|
@@ -182,6 +186,12 @@ class SessionList implements Component {
|
|
|
182
186
|
}
|
|
183
187
|
|
|
184
188
|
handleInput(keyData: string): void {
|
|
189
|
+
const moveSelection = (delta: number): void => {
|
|
190
|
+
if (this.#filteredSessions.length === 0) return;
|
|
191
|
+
this.#selectedIndex += delta;
|
|
192
|
+
this.#clampSelectedIndex();
|
|
193
|
+
};
|
|
194
|
+
|
|
185
195
|
// Delete key - request delete confirmation from parent
|
|
186
196
|
if (matchesKey(keyData, "delete")) {
|
|
187
197
|
const selected = this.#filteredSessions[this.#selectedIndex];
|
|
@@ -193,22 +203,22 @@ class SessionList implements Component {
|
|
|
193
203
|
|
|
194
204
|
// Up arrow
|
|
195
205
|
if (matchesKey(keyData, "up")) {
|
|
196
|
-
|
|
206
|
+
moveSelection(-1);
|
|
197
207
|
return;
|
|
198
208
|
}
|
|
199
209
|
// Down arrow
|
|
200
210
|
if (matchesKey(keyData, "down")) {
|
|
201
|
-
|
|
211
|
+
moveSelection(1);
|
|
202
212
|
return;
|
|
203
213
|
}
|
|
204
214
|
// Page up - jump up by maxVisible items
|
|
205
215
|
if (matchesKey(keyData, "pageUp")) {
|
|
206
|
-
|
|
216
|
+
moveSelection(-this.#maxVisible);
|
|
207
217
|
return;
|
|
208
218
|
}
|
|
209
219
|
// Page down - jump down by maxVisible items
|
|
210
220
|
if (matchesKey(keyData, "pageDown")) {
|
|
211
|
-
|
|
221
|
+
moveSelection(this.#maxVisible);
|
|
212
222
|
return;
|
|
213
223
|
}
|
|
214
224
|
// Enter
|