@sayknow-cli/coding-agent 0.3.7 → 0.3.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/dist/types/append-only-mode.d.ts +31 -0
- package/dist/types/capability/rule.d.ts +8 -0
- package/dist/types/cli/completion-cli.d.ts +41 -0
- package/dist/types/cli/config-cli.d.ts +1 -0
- package/dist/types/cli/fixture-report.d.ts +20 -0
- package/dist/types/cli/fixture-report.test.d.ts +1 -0
- package/dist/types/cli/nofile-limit.d.ts +11 -0
- package/dist/types/cli/notify-cli.d.ts +7 -0
- package/dist/types/cli/update-cli.d.ts +22 -0
- package/dist/types/cli.d.ts +124 -1
- package/dist/types/commands/completion.d.ts +29 -0
- package/dist/types/commands/config.d.ts +3 -0
- package/dist/types/commands/deep-interview.d.ts +3 -0
- package/dist/types/config/file-lock.d.ts +7 -6
- package/dist/types/config/keybindings.d.ts +2 -2
- package/dist/types/config/model-profile-activation.d.ts +6 -0
- package/dist/types/config/model-profiles.d.ts +1 -0
- package/dist/types/config/settings-schema.d.ts +119 -1
- package/dist/types/config/settings.d.ts +8 -0
- package/dist/types/discovery/index.d.ts +1 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/export/ttsr.d.ts +14 -3
- package/dist/types/extensibility/extensions/runner.d.ts +2 -1
- package/dist/types/extensibility/extensions/types.d.ts +8 -0
- package/dist/types/goals/runtime.d.ts +1 -0
- package/dist/types/hooks/native-skill-hook.d.ts +12 -0
- package/dist/types/main.d.ts +4 -1
- package/dist/types/modes/components/assistant-message.d.ts +5 -1
- package/dist/types/modes/components/model-selector.d.ts +4 -0
- package/dist/types/modes/components/queued-message-selector.d.ts +12 -0
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/components/status-line.d.ts +9 -0
- package/dist/types/modes/components/thinking-selector.d.ts +2 -2
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/event-controller.d.ts +5 -0
- package/dist/types/modes/controllers/input-controller.d.ts +1 -3
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +4 -0
- package/dist/types/modes/print-mode.d.ts +13 -1
- package/dist/types/modes/prompt-action-autocomplete.d.ts +1 -1
- package/dist/types/modes/rpc/rpc-mode.d.ts +1 -3
- package/dist/types/modes/rpc/rpc-socket-security.d.ts +3 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +14 -0
- package/dist/types/modes/shared/agent-wire/event-contract.d.ts +19 -0
- package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +23 -1
- package/dist/types/modes/types.d.ts +3 -0
- package/dist/types/modes/utils/injected-user-submission.d.ts +52 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -0
- package/dist/types/notifications/config-commands.d.ts +9 -0
- package/dist/types/notifications/config.d.ts +30 -1
- package/dist/types/notifications/daemon-paths.d.ts +1 -0
- package/dist/types/notifications/index.d.ts +8 -3
- package/dist/types/notifications/lifecycle-commands.d.ts +12 -3
- package/dist/types/notifications/lifecycle-control-runtime.d.ts +2 -1
- package/dist/types/notifications/reply-sent-store.d.ts +53 -0
- package/dist/types/notifications/rich-draft.d.ts +68 -0
- package/dist/types/notifications/rich-render.d.ts +90 -0
- package/dist/types/notifications/telegram-daemon.d.ts +45 -3
- package/dist/types/notifications/telegram-reference.d.ts +7 -5
- package/dist/types/notifications/threaded-render.d.ts +14 -0
- package/dist/types/runtime-api-key.d.ts +7 -0
- package/dist/types/sdk.d.ts +3 -0
- package/dist/types/session/agent-session.d.ts +61 -6
- package/dist/types/session/agent-storage.d.ts +1 -0
- package/dist/types/session/file-mention-pruning.d.ts +8 -0
- package/dist/types/session/messages.d.ts +4 -0
- package/dist/types/session/session-manager.d.ts +17 -1
- package/dist/types/skc-runtime/deep-interview-ambiguity.d.ts +39 -0
- package/dist/types/skc-runtime/deep-interview-recorder.d.ts +10 -1
- package/dist/types/skc-runtime/deep-interview-state.d.ts +17 -2
- package/dist/types/skc-runtime/session-state-sidecar.d.ts +7 -0
- package/dist/types/skc-runtime/state-writer.d.ts +3 -0
- package/dist/types/skc-runtime/tmux-common.d.ts +1 -0
- package/dist/types/skc-runtime/ultragoal-receipt-freshness.d.ts +42 -0
- package/dist/types/skc-runtime/ultragoal-runtime.d.ts +120 -13
- package/dist/types/skill-state/active-state.d.ts +18 -1
- package/dist/types/system-prompt.d.ts +18 -2
- package/dist/types/task/executor.d.ts +3 -0
- package/dist/types/task/fork-context-budget.d.ts +7 -0
- package/dist/types/task/token-log.d.ts +30 -0
- package/dist/types/task/token-log.test.d.ts +1 -0
- package/dist/types/tools/bisect.d.ts +81 -0
- package/dist/types/tools/computer.d.ts +2 -2
- package/dist/types/tools/index.d.ts +1 -0
- package/dist/types/tools/search.d.ts +2 -2
- package/dist/types/tools/ultragoal-ask-guard.d.ts +9 -2
- package/dist/types/utils/file-mentions.d.ts +14 -0
- package/dist/types/utils/git.d.ts +32 -0
- package/dist/types/utils/pasted-image-path.d.ts +30 -0
- package/dist/types/web/scrapers/youtube.d.ts +8 -0
- package/dist/types/workflow/workflow-intent-diff.d.ts +8 -2
- package/dist/types/workflow/workflow-intent-report-consumer.d.ts +50 -0
- package/dist/types/workflow/workflow-intent-report-schema.d.ts +2 -0
- package/dist/types/workflow/workflow-intent-report.d.ts +48 -0
- package/package.json +8 -8
- package/src/append-only-mode.ts +58 -0
- package/src/autoresearch/prompt.md +1 -1
- package/src/autoresearch/tools/log-experiment.ts +1 -1
- package/src/capability/rule.ts +9 -0
- package/src/cli/completion-cli.ts +234 -0
- package/src/cli/config-cli.ts +61 -9
- package/src/cli/fast-help.ts +1 -0
- package/src/cli/fixture-report.test.ts +230 -0
- package/src/cli/fixture-report.ts +171 -0
- package/src/cli/nofile-limit.ts +48 -0
- package/src/cli/notify-cli.ts +103 -17
- package/src/cli/telegram-cli.ts +1 -1
- package/src/cli/update-cli.ts +148 -33
- package/src/cli.ts +29 -1
- package/src/commands/completion.ts +84 -0
- package/src/commands/config.ts +2 -0
- package/src/commands/contribution-prep.ts +1 -1
- package/src/commands/deep-interview.ts +2 -1
- package/src/config/file-lock.ts +73 -34
- package/src/config/keybindings.ts +3 -3
- package/src/config/model-profile-activation.ts +65 -0
- package/src/config/model-profiles.ts +31 -2
- package/src/config/settings-schema.ts +76 -1
- package/src/config/settings.ts +22 -0
- package/src/defaults/skc/skills/team/SKILL.md +17 -14
- package/src/defaults/skc/skills/ultragoal/SKILL.md +59 -4
- package/src/defaults/skc/skills/ultragoal/pipeline-validation-contracts.md +30 -0
- package/src/defaults/skc-defaults.ts +9 -0
- package/src/discovery/helpers.ts +10 -0
- package/src/discovery/index.ts +1 -0
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +60 -2
- package/src/export/ttsr.ts +96 -18
- package/src/extensibility/extensions/runner.ts +183 -219
- package/src/extensibility/extensions/types.ts +9 -0
- package/src/extensibility/skc-plugins/runtime-adapters.ts +156 -44
- package/src/goals/runtime.ts +10 -2
- package/src/hindsight/backend.ts +1 -3
- package/src/hooks/native-skill-hook.ts +124 -4
- package/src/internal-urls/docs-index.generated.ts +17 -8
- package/src/lsp/client.ts +1 -0
- package/src/main.ts +95 -43
- package/src/memories/index.ts +12 -1
- package/src/modes/components/assistant-message.ts +241 -82
- package/src/modes/components/custom-editor.ts +1 -0
- package/src/modes/components/footer.ts +7 -2
- package/src/modes/components/model-selector.ts +54 -11
- package/src/modes/components/queued-message-selector.ts +92 -0
- package/src/modes/components/settings-selector.ts +4 -0
- package/src/modes/components/status-line.ts +167 -54
- package/src/modes/components/thinking-selector.ts +10 -8
- package/src/modes/components/tool-execution.ts +44 -19
- package/src/modes/components/welcome.ts +181 -44
- package/src/modes/controllers/command-controller.ts +40 -4
- package/src/modes/controllers/event-controller.ts +77 -49
- package/src/modes/controllers/extension-ui-controller.ts +12 -11
- package/src/modes/controllers/input-controller.ts +192 -35
- package/src/modes/controllers/selector-controller.ts +168 -24
- package/src/modes/interactive-mode.ts +56 -16
- package/src/modes/print-mode.ts +43 -4
- package/src/modes/prompt-action-autocomplete.ts +21 -20
- package/src/modes/rpc/rpc-mode.ts +32 -12
- package/src/modes/rpc/rpc-socket-security.ts +13 -1
- package/src/modes/rpc/rpc-types.ts +9 -0
- package/src/modes/shared/agent-wire/command-validation.ts +4 -0
- package/src/modes/shared/agent-wire/event-contract.ts +22 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +82 -0
- package/src/modes/shared/agent-wire/scopes.ts +1 -0
- package/src/modes/types.ts +3 -0
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/injected-user-submission.ts +94 -0
- package/src/modes/utils/ui-helpers.ts +20 -7
- package/src/notifications/config-commands.ts +20 -0
- package/src/notifications/config.ts +52 -5
- package/src/notifications/daemon-paths.ts +2 -0
- package/src/notifications/index.ts +144 -18
- package/src/notifications/lifecycle-commands.ts +74 -23
- package/src/notifications/lifecycle-control-runtime.ts +8 -3
- package/src/notifications/lifecycle-orchestrator.ts +19 -6
- package/src/notifications/reply-sent-store.ts +134 -0
- package/src/notifications/rich-draft.ts +107 -0
- package/src/notifications/rich-render.ts +142 -0
- package/src/notifications/telegram-daemon-control.ts +5 -3
- package/src/notifications/telegram-daemon.ts +606 -98
- package/src/notifications/telegram-reference.ts +17 -16
- package/src/notifications/threaded-render.ts +45 -2
- package/src/prompts/agents/architect.md +19 -10
- package/src/prompts/agents/critic.md +16 -18
- package/src/prompts/agents/executor.md +14 -11
- package/src/prompts/agents/planner.md +18 -13
- package/src/prompts/ci-green-request.md +3 -8
- package/src/prompts/goals/goal-continuation.md +0 -5
- package/src/prompts/goals/goal-mode-active.md +0 -5
- package/src/prompts/system/agent-creation-architect.md +2 -2
- package/src/prompts/system/plan-mode-active.md +1 -1
- package/src/prompts/system/plan-mode-subagent.md +3 -3
- package/src/prompts/system/project-prompt.md +0 -13
- package/src/prompts/system/subagent-system-prompt.md +3 -2
- package/src/prompts/system/system-prompt.md +29 -17
- package/src/prompts/system/volatile-project-context.md +10 -0
- package/src/prompts/system/web-search.md +4 -7
- package/src/prompts/tools/apply-patch.md +6 -0
- package/src/prompts/tools/ast-edit.md +1 -0
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/bisect.md +22 -0
- package/src/prompts/tools/browser.md +1 -1
- package/src/prompts/tools/computer.md +2 -2
- package/src/prompts/tools/cron.md +0 -2
- package/src/prompts/tools/eval.md +2 -2
- package/src/prompts/tools/find.md +1 -1
- package/src/prompts/tools/github.md +6 -6
- package/src/prompts/tools/goal.md +1 -3
- package/src/prompts/tools/image-gen.md +12 -5
- package/src/prompts/tools/irc.md +5 -4
- package/src/prompts/tools/job.md +2 -2
- package/src/prompts/tools/lsp.md +1 -1
- package/src/prompts/tools/monitor.md +2 -2
- package/src/prompts/tools/patch.md +2 -2
- package/src/prompts/tools/replace.md +2 -21
- package/src/prompts/tools/rewind.md +1 -1
- package/src/prompts/tools/search-tool-bm25.md +6 -11
- package/src/prompts/tools/search.md +1 -1
- package/src/prompts/tools/skill.md +3 -2
- package/src/prompts/tools/ssh.md +2 -1
- package/src/prompts/tools/subagent.md +2 -2
- package/src/prompts/tools/task.md +4 -5
- package/src/prompts/tools/todo-write.md +1 -1
- package/src/prompts/tools/vim.md +1 -0
- package/src/prompts/tools/web-search.md +2 -0
- package/src/prompts/tools/write.md +8 -2
- package/src/runtime-api-key.ts +16 -0
- package/src/sdk.ts +88 -36
- package/src/session/agent-session.ts +1013 -141
- package/src/session/agent-storage.ts +4 -0
- package/src/session/file-mention-pruning.ts +64 -0
- package/src/session/messages.ts +4 -0
- package/src/session/session-manager.ts +103 -4
- package/src/skc-runtime/deep-interview-ambiguity.ts +206 -0
- package/src/skc-runtime/deep-interview-recorder.ts +62 -8
- package/src/skc-runtime/deep-interview-state.ts +44 -2
- package/src/skc-runtime/session-state-sidecar.ts +263 -20
- package/src/skc-runtime/state-runtime.ts +12 -2
- package/src/skc-runtime/state-writer.ts +15 -0
- package/src/skc-runtime/tmux-common.ts +1 -0
- package/src/skc-runtime/ultragoal-guard.ts +55 -66
- package/src/skc-runtime/ultragoal-receipt-freshness.ts +315 -0
- package/src/skc-runtime/ultragoal-runtime.ts +1672 -159
- package/src/skc-runtime/workflow-manifest.generated.json +113 -2
- package/src/skc-runtime/workflow-manifest.ts +74 -1
- package/src/skill-state/active-state.ts +141 -5
- package/src/skill-state/deep-interview-mutation-guard.ts +67 -3
- package/src/slash-commands/builtin-registry.ts +296 -72
- package/src/system-prompt.ts +57 -16
- package/src/task/executor.ts +132 -24
- package/src/task/fork-context-advisory.ts +2 -7
- package/src/task/fork-context-budget.ts +9 -0
- package/src/task/index.ts +13 -6
- package/src/task/output-manager.ts +20 -8
- package/src/task/receipt.ts +26 -7
- package/src/task/token-log.test.ts +188 -0
- package/src/task/token-log.ts +179 -0
- package/src/tools/bisect.ts +367 -0
- package/src/tools/computer.ts +246 -43
- package/src/tools/index.ts +3 -0
- package/src/tools/output-meta.ts +116 -4
- package/src/tools/search.ts +8 -2
- package/src/tools/skill.ts +15 -0
- package/src/tools/telegram-send.ts +6 -0
- package/src/tools/ultragoal-ask-guard.ts +39 -5
- package/src/utils/file-mentions.ts +39 -7
- package/src/utils/git.ts +73 -0
- package/src/utils/pasted-image-path.ts +170 -0
- package/src/web/scrapers/youtube.ts +24 -12
- package/src/workflow/workflow-intent-diff.ts +29 -6
- package/src/workflow/workflow-intent-report-consumer.ts +221 -0
- package/src/workflow/workflow-intent-report-schema.ts +129 -0
- package/src/workflow/workflow-intent-report.ts +135 -0
|
@@ -24,6 +24,7 @@ import { HookSelectorComponent } from "../../modes/components/hook-selector";
|
|
|
24
24
|
import { getAvailableThemesWithPaths, getThemeByName, setTheme, type Theme, theme } from "../../modes/theme/theme";
|
|
25
25
|
import type { InteractiveModeContext } from "../../modes/types";
|
|
26
26
|
import { setSessionTerminalTitle, setTerminalTitle } from "../../utils/title-generator";
|
|
27
|
+
import { applyInjectedUserSubmission } from "../utils/injected-user-submission";
|
|
27
28
|
import { classifyHookSelectorBellEvent, ringTerminalBell } from "../utils/terminal-bell";
|
|
28
29
|
|
|
29
30
|
const MAX_WIDGET_LINES = 10;
|
|
@@ -192,9 +193,8 @@ export class ExtensionUiController {
|
|
|
192
193
|
this.ctx.streamingMessage = undefined;
|
|
193
194
|
this.ctx.pendingTools.clear();
|
|
194
195
|
|
|
195
|
-
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
196
196
|
this.ctx.chatContainer.addChild(
|
|
197
|
-
new Text(`${theme.fg("accent", `${theme.status.success} New session started`)}`, 1,
|
|
197
|
+
new Text(`${theme.fg("accent", `${theme.status.success} New session started`)}`, 1, 0),
|
|
198
198
|
);
|
|
199
199
|
await this.ctx.reloadTodos();
|
|
200
200
|
this.ctx.ui.requestRender();
|
|
@@ -300,15 +300,14 @@ export class ExtensionUiController {
|
|
|
300
300
|
}
|
|
301
301
|
|
|
302
302
|
#rebuildHookWidgets(): void {
|
|
303
|
-
this.#renderHookWidgetContainer(this.ctx.hookWidgetContainerAbove, this.#hookWidgetsAbove, true
|
|
304
|
-
this.#renderHookWidgetContainer(this.ctx.hookWidgetContainerBelow, this.#hookWidgetsBelow, false
|
|
303
|
+
this.#renderHookWidgetContainer(this.ctx.hookWidgetContainerAbove, this.#hookWidgetsAbove, true);
|
|
304
|
+
this.#renderHookWidgetContainer(this.ctx.hookWidgetContainerBelow, this.#hookWidgetsBelow, false);
|
|
305
305
|
this.ctx.ui.requestRender();
|
|
306
306
|
}
|
|
307
307
|
|
|
308
308
|
#renderHookWidgetContainer(
|
|
309
309
|
container: Container,
|
|
310
310
|
widgets: Map<string, ExtensionUiComponent>,
|
|
311
|
-
spacerWhenEmpty: boolean,
|
|
312
311
|
leadingSpacer: boolean,
|
|
313
312
|
): void {
|
|
314
313
|
// Detach (not dispose): hook widgets are persistent instances owned by the
|
|
@@ -318,9 +317,6 @@ export class ExtensionUiController {
|
|
|
318
317
|
container.detachAll();
|
|
319
318
|
|
|
320
319
|
if (widgets.size === 0) {
|
|
321
|
-
if (spacerWhenEmpty) {
|
|
322
|
-
container.addChild(new Spacer(1));
|
|
323
|
-
}
|
|
324
320
|
return;
|
|
325
321
|
}
|
|
326
322
|
|
|
@@ -435,9 +431,8 @@ export class ExtensionUiController {
|
|
|
435
431
|
this.ctx.streamingMessage = undefined;
|
|
436
432
|
this.ctx.pendingTools.clear();
|
|
437
433
|
|
|
438
|
-
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
439
434
|
this.ctx.chatContainer.addChild(
|
|
440
|
-
new Text(`${theme.fg("accent", `${theme.status.success} New session started`)}`, 1,
|
|
435
|
+
new Text(`${theme.fg("accent", `${theme.status.success} New session started`)}`, 1, 0),
|
|
441
436
|
);
|
|
442
437
|
await this.ctx.reloadTodos();
|
|
443
438
|
this.ctx.ui.requestRender();
|
|
@@ -960,7 +955,13 @@ export class ExtensionUiController {
|
|
|
960
955
|
}
|
|
961
956
|
|
|
962
957
|
#sendExtensionUserMessage: SendUserMessageHandler = (content, options) => {
|
|
963
|
-
|
|
958
|
+
// Compute queued BEFORE send: prompt() may flip session.isStreaming synchronously.
|
|
959
|
+
const queued = Boolean(options?.deliverAs) || this.ctx.session.isStreaming;
|
|
960
|
+
// Call send first so the busy/queued path finds the session queue populated
|
|
961
|
+
// (queueSteer/queueFollowUp push synchronously) before refreshing pending display.
|
|
962
|
+
const send = this.ctx.session.sendUserMessage(content, options);
|
|
963
|
+
applyInjectedUserSubmission(this.ctx, { content, queued });
|
|
964
|
+
send.catch((err: unknown) => {
|
|
964
965
|
this.ctx.showError(`Extension sendUserMessage failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
965
966
|
});
|
|
966
967
|
};
|
|
@@ -11,22 +11,22 @@ import { createPromptActionAutocompleteProvider } from "../../modes/prompt-actio
|
|
|
11
11
|
import { theme } from "../../modes/theme/theme";
|
|
12
12
|
import { scrollTmuxToPreviousUserInput as scrollTmuxPaneToPreviousUserInput } from "../../modes/tmux-scroll";
|
|
13
13
|
import type { InteractiveModeContext } from "../../modes/types";
|
|
14
|
-
import type { AgentSessionEvent } from "../../session/agent-session";
|
|
14
|
+
import type { AgentSessionEvent, QueuedMessageEditEntry } from "../../session/agent-session";
|
|
15
15
|
import { SKILL_PROMPT_MESSAGE_TYPE, type SkillPromptDetails } from "../../session/messages";
|
|
16
16
|
import { executeBuiltinSlashCommand } from "../../slash-commands/builtin-registry";
|
|
17
17
|
import { copyToClipboard, readImageFromClipboard } from "../../utils/clipboard";
|
|
18
18
|
import { getEditorCommand, openInEditor } from "../../utils/external-editor";
|
|
19
19
|
import { ensureSupportedImageInput, ImageInputTooLargeError, loadImageInput } from "../../utils/image-loading";
|
|
20
20
|
import { resizeImage } from "../../utils/image-resize";
|
|
21
|
+
import { resolvePastedImagePath } from "../../utils/pasted-image-path";
|
|
21
22
|
import { generateSessionTitle, setSessionTerminalTitle } from "../../utils/title-generator";
|
|
23
|
+
import { type QueuedMessageMoveDirection, QueuedMessageSelectorComponent } from "../components/queued-message-selector";
|
|
22
24
|
|
|
23
25
|
interface Expandable {
|
|
24
26
|
setExpanded(expanded: boolean): void;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
const INTERACTIVE_ABORT_CLEANUP_TIMEOUT_MS = 5_000;
|
|
28
|
-
const CLIPBOARD_TEMP_IMAGE_FILE_PATTERN = /^clipboard-\d{4}-\d{2}-\d{2}-\d{6}-[A-Za-z0-9]+\.(?:png|jpe?g|gif|webp)$/i;
|
|
29
|
-
const MACOS_CLIPBOARD_TEMP_DIR_PATTERN = /^\/var\/folders\/[^/]+\/[^/]+\/T$/;
|
|
30
30
|
|
|
31
31
|
function isExpandable(obj: unknown): obj is Expandable {
|
|
32
32
|
return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function";
|
|
@@ -313,14 +313,7 @@ export class InputController {
|
|
|
313
313
|
this.ctx.editor.onDequeue = () => this.handleDequeue();
|
|
314
314
|
this.ctx.editor.setActionKeys("app.message.queue", this.ctx.keybindings.getKeys("app.message.queue"));
|
|
315
315
|
this.ctx.editor.onQueue = () => void this.handleQueueSubmit();
|
|
316
|
-
|
|
317
|
-
if (!this.ctx.session.isStreaming && !this.ctx.session.isCompacting) return false;
|
|
318
|
-
void this.handleQueueSubmit();
|
|
319
|
-
return true;
|
|
320
|
-
};
|
|
321
|
-
this.ctx.editor.onTabDeclined = () => {
|
|
322
|
-
if (this.ctx.session.isStreaming || this.ctx.session.isCompacting) void this.handleQueueSubmit();
|
|
323
|
-
};
|
|
316
|
+
|
|
324
317
|
this.ctx.editor.onViewportPageScroll = direction => this.ctx.ui.scrollViewportPages(direction);
|
|
325
318
|
this.ctx.editor.onViewportFollowLive = () => {
|
|
326
319
|
this.ctx.ui.followLiveViewport();
|
|
@@ -614,12 +607,182 @@ export class InputController {
|
|
|
614
607
|
}
|
|
615
608
|
|
|
616
609
|
handleDequeue(): void {
|
|
617
|
-
const
|
|
618
|
-
if (
|
|
610
|
+
const entries = this.#getEditableQueuedMessages();
|
|
611
|
+
if (entries.length === 0) {
|
|
612
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
619
613
|
this.ctx.showStatus("No queued messages to restore");
|
|
620
|
-
|
|
621
|
-
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
if (entries.length === 1) {
|
|
617
|
+
const restored = this.#restoreQueuedMessageToEditor(entries[0]);
|
|
618
|
+
this.ctx.showStatus(
|
|
619
|
+
restored === 0 ? "Queued message is no longer available" : "Restored queued message to editor",
|
|
620
|
+
);
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
this.#showQueuedMessageSelector(entries, this.#newestQueuedMessageIndex(entries));
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
#compactionQueuedMessageId(index: number): string {
|
|
627
|
+
return `compaction:${index}`;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
#getEditableQueuedMessages(): QueuedMessageEditEntry[] {
|
|
631
|
+
const compactionEntries = this.ctx.compactionQueuedMessages.map((entry, index): QueuedMessageEditEntry => {
|
|
632
|
+
const label = entry.mode === "steer" ? "Steer" : "Queued";
|
|
633
|
+
return {
|
|
634
|
+
id: this.#compactionQueuedMessageId(index),
|
|
635
|
+
text: entry.text,
|
|
636
|
+
mode: entry.mode,
|
|
637
|
+
label,
|
|
638
|
+
};
|
|
639
|
+
});
|
|
640
|
+
return [...compactionEntries, ...this.ctx.session.getQueuedMessageEntries()];
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
#queuedMessageStableSequence(entry: QueuedMessageEditEntry): number | undefined {
|
|
644
|
+
const [mode, sequenceText] = entry.id.split(":");
|
|
645
|
+
if ((mode !== "steer" && mode !== "followUp") || sequenceText === undefined) return undefined;
|
|
646
|
+
const sequence = Number(sequenceText);
|
|
647
|
+
return Number.isInteger(sequence) ? sequence : undefined;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
#newestQueuedMessageIndex(entries: QueuedMessageEditEntry[]): number {
|
|
651
|
+
let selectedIndex = entries.length - 1;
|
|
652
|
+
let newestSequence = Number.NEGATIVE_INFINITY;
|
|
653
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
654
|
+
const sequence = this.#queuedMessageStableSequence(entries[index]);
|
|
655
|
+
if (sequence !== undefined && sequence > newestSequence) {
|
|
656
|
+
newestSequence = sequence;
|
|
657
|
+
selectedIndex = index;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
return Math.max(0, selectedIndex);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
#restoreEditorFocus(): void {
|
|
664
|
+
this.ctx.editorContainer.clear();
|
|
665
|
+
this.ctx.editorContainer.addChild(this.ctx.editor);
|
|
666
|
+
this.ctx.ui.setFocus(this.ctx.editor);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
#showQueuedMessageSelector(entries: QueuedMessageEditEntry[], selectedIndex = 0): void {
|
|
670
|
+
const selector = new QueuedMessageSelectorComponent(
|
|
671
|
+
entries,
|
|
672
|
+
entry => {
|
|
673
|
+
const restored = this.#restoreQueuedMessageToEditor(entry);
|
|
674
|
+
this.#restoreEditorFocus();
|
|
675
|
+
this.ctx.showStatus(
|
|
676
|
+
restored === 0 ? "Queued message is no longer available" : "Restored queued message to editor",
|
|
677
|
+
);
|
|
678
|
+
this.ctx.ui.requestRender();
|
|
679
|
+
},
|
|
680
|
+
(entry, index) => {
|
|
681
|
+
const deleted = this.#deleteQueuedMessage(entry);
|
|
682
|
+
const nextEntries = this.#getEditableQueuedMessages();
|
|
683
|
+
if (nextEntries.length === 0) {
|
|
684
|
+
this.#restoreEditorFocus();
|
|
685
|
+
this.ctx.showStatus(deleted ? "Deleted queued message" : "Queued message is no longer available");
|
|
686
|
+
this.ctx.ui.requestRender();
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
this.ctx.showStatus(deleted ? "Deleted queued message" : "Queued message is no longer available");
|
|
690
|
+
this.#showQueuedMessageSelector(nextEntries, Math.min(index, nextEntries.length - 1));
|
|
691
|
+
},
|
|
692
|
+
(entry, index, direction) => {
|
|
693
|
+
const moved = this.#moveQueuedMessage(entry, direction);
|
|
694
|
+
const nextEntries = this.#getEditableQueuedMessages();
|
|
695
|
+
if (nextEntries.length === 0) {
|
|
696
|
+
this.#restoreEditorFocus();
|
|
697
|
+
this.ctx.showStatus("Queued message is no longer available");
|
|
698
|
+
this.ctx.ui.requestRender();
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
const nextIndex = direction === "up" ? index - 1 : index + 1;
|
|
702
|
+
const selectedNextIndex = moved ? nextIndex : index;
|
|
703
|
+
this.ctx.showStatus(moved ? "Moved queued message" : "Queued message cannot move further");
|
|
704
|
+
this.#showQueuedMessageSelector(
|
|
705
|
+
nextEntries,
|
|
706
|
+
Math.max(0, Math.min(selectedNextIndex, nextEntries.length - 1)),
|
|
707
|
+
);
|
|
708
|
+
},
|
|
709
|
+
() => {
|
|
710
|
+
this.#restoreEditorFocus();
|
|
711
|
+
this.ctx.ui.requestRender();
|
|
712
|
+
},
|
|
713
|
+
{ selectedIndex },
|
|
714
|
+
);
|
|
715
|
+
this.ctx.editorContainer.clear();
|
|
716
|
+
this.ctx.editorContainer.addChild(selector);
|
|
717
|
+
this.ctx.ui.setFocus(selector);
|
|
718
|
+
this.ctx.ui.requestRender();
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
#removeQueuedMessageForEditing(id: string): string | undefined {
|
|
722
|
+
const compactionPrefix = "compaction:";
|
|
723
|
+
if (id.startsWith(compactionPrefix)) {
|
|
724
|
+
const index = Number(id.slice(compactionPrefix.length));
|
|
725
|
+
if (!Number.isInteger(index)) return undefined;
|
|
726
|
+
const [entry] = this.ctx.compactionQueuedMessages.splice(index, 1);
|
|
727
|
+
return entry?.text;
|
|
622
728
|
}
|
|
729
|
+
return this.ctx.session.removeQueuedMessageForEditing(id);
|
|
730
|
+
}
|
|
731
|
+
#parseCompactionQueuedMessageId(id: string): number | undefined {
|
|
732
|
+
const compactionPrefix = "compaction:";
|
|
733
|
+
if (!id.startsWith(compactionPrefix)) return undefined;
|
|
734
|
+
const index = Number(id.slice(compactionPrefix.length));
|
|
735
|
+
return Number.isInteger(index) ? index : undefined;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
#moveCompactionQueuedMessage(index: number, direction: QueuedMessageMoveDirection): boolean {
|
|
739
|
+
const targetIndex = direction === "up" ? index - 1 : index + 1;
|
|
740
|
+
if (index < 0 || index >= this.ctx.compactionQueuedMessages.length) return false;
|
|
741
|
+
if (targetIndex < 0 || targetIndex >= this.ctx.compactionQueuedMessages.length) return false;
|
|
742
|
+
const current = this.ctx.compactionQueuedMessages[index];
|
|
743
|
+
const target = this.ctx.compactionQueuedMessages[targetIndex];
|
|
744
|
+
if (!current || !target) return false;
|
|
745
|
+
this.ctx.compactionQueuedMessages[index] = target;
|
|
746
|
+
this.ctx.compactionQueuedMessages[targetIndex] = current;
|
|
747
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
748
|
+
return true;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
#moveQueuedMessage(entry: QueuedMessageEditEntry, direction: QueuedMessageMoveDirection): boolean {
|
|
752
|
+
const compactionIndex = this.#parseCompactionQueuedMessageId(entry.id);
|
|
753
|
+
if (compactionIndex !== undefined) {
|
|
754
|
+
return this.#moveCompactionQueuedMessage(compactionIndex, direction);
|
|
755
|
+
}
|
|
756
|
+
const moved = this.ctx.session.moveQueuedMessageForEditing(entry.id, direction);
|
|
757
|
+
if (moved) {
|
|
758
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
759
|
+
}
|
|
760
|
+
return moved;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
#deleteQueuedMessage(entry: QueuedMessageEditEntry): boolean {
|
|
764
|
+
const queuedText = this.#removeQueuedMessageForEditing(entry.id);
|
|
765
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
766
|
+
if (!queuedText) return false;
|
|
767
|
+
this.ctx.locallySubmittedUserSignatures.delete(`${queuedText}\u00000`);
|
|
768
|
+
return true;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
#restoreQueuedMessageToEditor(entry: QueuedMessageEditEntry | undefined): number {
|
|
772
|
+
if (!entry) {
|
|
773
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
774
|
+
return 0;
|
|
775
|
+
}
|
|
776
|
+
const queuedText = this.#removeQueuedMessageForEditing(entry.id);
|
|
777
|
+
if (!queuedText) {
|
|
778
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
779
|
+
return 0;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
this.ctx.locallySubmittedUserSignatures.delete(`${queuedText}\u00000`);
|
|
783
|
+
this.ctx.editor.setText(queuedText);
|
|
784
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
785
|
+
return 1;
|
|
623
786
|
}
|
|
624
787
|
|
|
625
788
|
/**
|
|
@@ -766,7 +929,7 @@ export class InputController {
|
|
|
766
929
|
return this.handleFollowUp();
|
|
767
930
|
}
|
|
768
931
|
|
|
769
|
-
restoreLatestQueuedMessageToEditor(
|
|
932
|
+
restoreLatestQueuedMessageToEditor(): number {
|
|
770
933
|
const compactionQueued = this.ctx.compactionQueuedMessages.pop();
|
|
771
934
|
const queuedText = compactionQueued?.text ?? this.ctx.session.popLastQueuedMessage();
|
|
772
935
|
if (!queuedText) {
|
|
@@ -775,9 +938,7 @@ export class InputController {
|
|
|
775
938
|
}
|
|
776
939
|
|
|
777
940
|
this.ctx.locallySubmittedUserSignatures.delete(`${queuedText}\u00000`);
|
|
778
|
-
|
|
779
|
-
const combinedText = [queuedText, currentText].filter(t => t.trim()).join("\n\n");
|
|
780
|
-
this.ctx.editor.setText(combinedText);
|
|
941
|
+
this.ctx.editor.setText(queuedText);
|
|
781
942
|
this.ctx.updatePendingMessagesDisplay();
|
|
782
943
|
return 1;
|
|
783
944
|
}
|
|
@@ -899,10 +1060,15 @@ export class InputController {
|
|
|
899
1060
|
}
|
|
900
1061
|
|
|
901
1062
|
handleTextPaste(text: string): boolean | Promise<boolean> {
|
|
902
|
-
const imagePath = this
|
|
1063
|
+
const imagePath = resolvePastedImagePath(text, { cwd: this.ctx.sessionManager.getCwd() });
|
|
903
1064
|
return imagePath ? this.#attachPastedImagePath(imagePath) : false;
|
|
904
1065
|
}
|
|
905
1066
|
|
|
1067
|
+
/**
|
|
1068
|
+
* Returns `false` on every failure path so the editor replays the original
|
|
1069
|
+
* bracketed paste — the raw path text must never be lost when attachment
|
|
1070
|
+
* is impossible (unsupported content, oversized image, load error).
|
|
1071
|
+
*/
|
|
906
1072
|
async #attachPastedImagePath(imagePath: string): Promise<boolean> {
|
|
907
1073
|
try {
|
|
908
1074
|
const image = await loadImageInput({
|
|
@@ -911,8 +1077,8 @@ export class InputController {
|
|
|
911
1077
|
autoResize: this.ctx.settings.get("images.autoResize"),
|
|
912
1078
|
});
|
|
913
1079
|
if (!image) {
|
|
914
|
-
this.ctx.showStatus("Unsupported pasted
|
|
915
|
-
return
|
|
1080
|
+
this.ctx.showStatus("Unsupported pasted image file");
|
|
1081
|
+
return false;
|
|
916
1082
|
}
|
|
917
1083
|
|
|
918
1084
|
this.ctx.pendingImages.push({
|
|
@@ -927,22 +1093,13 @@ export class InputController {
|
|
|
927
1093
|
} catch (error) {
|
|
928
1094
|
if (error instanceof ImageInputTooLargeError) {
|
|
929
1095
|
this.ctx.showStatus(error.message);
|
|
930
|
-
return
|
|
1096
|
+
return false;
|
|
931
1097
|
}
|
|
932
|
-
this.ctx.showStatus("Failed to attach pasted
|
|
933
|
-
return
|
|
1098
|
+
this.ctx.showStatus("Failed to attach pasted image");
|
|
1099
|
+
return false;
|
|
934
1100
|
}
|
|
935
1101
|
}
|
|
936
1102
|
|
|
937
|
-
#getPastedImagePathCandidate(text: string): string | undefined {
|
|
938
|
-
const resolvedPath = path.resolve(text.trim());
|
|
939
|
-
const parentDir = path.dirname(resolvedPath);
|
|
940
|
-
const isClipboardTempPath =
|
|
941
|
-
(parentDir === "/tmp" || MACOS_CLIPBOARD_TEMP_DIR_PATTERN.test(parentDir)) &&
|
|
942
|
-
CLIPBOARD_TEMP_IMAGE_FILE_PATTERN.test(path.basename(resolvedPath));
|
|
943
|
-
return isClipboardTempPath ? resolvedPath : undefined;
|
|
944
|
-
}
|
|
945
|
-
|
|
946
1103
|
#nextImagePlaceholder(): string {
|
|
947
1104
|
return `[image ${this.ctx.pendingImages.length}]`;
|
|
948
1105
|
}
|
|
@@ -1146,7 +1303,7 @@ export class InputController {
|
|
|
1146
1303
|
// If streaming, re-add the streaming component with updated visibility and re-render
|
|
1147
1304
|
if (this.ctx.streamingComponent && this.ctx.streamingMessage) {
|
|
1148
1305
|
this.ctx.streamingComponent.setHideThinkingBlock(this.ctx.hideThinkingBlock);
|
|
1149
|
-
this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage);
|
|
1306
|
+
this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage, { streaming: true });
|
|
1150
1307
|
this.ctx.chatContainer.addChild(this.ctx.streamingComponent);
|
|
1151
1308
|
}
|
|
1152
1309
|
|
|
@@ -4,9 +4,13 @@ import type { OAuthProvider } from "@sayknow-cli/ai/utils/oauth/types";
|
|
|
4
4
|
import type { Component, OverlayHandle } from "@sayknow-cli/tui";
|
|
5
5
|
import { Input, Loader, Spacer, Text } from "@sayknow-cli/tui";
|
|
6
6
|
import { getAgentDbPath, getProjectDir } from "@sayknow-cli/utils";
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
import {
|
|
8
|
+
activateModelProfile,
|
|
9
|
+
materializeActiveModelProfileAssignment,
|
|
10
|
+
materializeActiveModelProfileAssignments,
|
|
11
|
+
} from "../../config/model-profile-activation";
|
|
12
|
+
import { formatModelProfileDisplayLabel, recommendModelProfileForProvider } from "../../config/model-profiles";
|
|
13
|
+
import { SKC_MODEL_ASSIGNMENT_TARGETS, type SkcModelAssignmentTargetId } from "../../config/model-registry";
|
|
10
14
|
import { formatModelSelectorValue } from "../../config/model-resolver";
|
|
11
15
|
import type { ModelProfileConfig } from "../../config/models-config-schema";
|
|
12
16
|
import { type Settings, settings } from "../../config/settings";
|
|
@@ -77,6 +81,7 @@ import { SessionSelectorComponent } from "../components/session-selector";
|
|
|
77
81
|
import { SettingsSelectorComponent } from "../components/settings-selector";
|
|
78
82
|
import type { StatusLineSettings } from "../components/status-line";
|
|
79
83
|
import { ThemeSelectorComponent } from "../components/theme-selector";
|
|
84
|
+
import { ThinkingSelectorComponent } from "../components/thinking-selector";
|
|
80
85
|
import { ToolExecutionComponent } from "../components/tool-execution";
|
|
81
86
|
import { TreeSelectorComponent } from "../components/tree-selector";
|
|
82
87
|
import { UserMessageSelectorComponent } from "../components/user-message-selector";
|
|
@@ -324,6 +329,46 @@ export class SelectorController {
|
|
|
324
329
|
});
|
|
325
330
|
}
|
|
326
331
|
|
|
332
|
+
showEffortSelector(): void {
|
|
333
|
+
const availableLevels = [
|
|
334
|
+
ThinkingLevel.Inherit,
|
|
335
|
+
ThinkingLevel.Off,
|
|
336
|
+
...this.ctx.session.getAvailableThinkingLevels(),
|
|
337
|
+
];
|
|
338
|
+
|
|
339
|
+
this.showSelector(done => {
|
|
340
|
+
const selector = new ThinkingSelectorComponent(
|
|
341
|
+
this.ctx.session.thinkingLevel,
|
|
342
|
+
availableLevels,
|
|
343
|
+
level => {
|
|
344
|
+
done();
|
|
345
|
+
|
|
346
|
+
const configuredDefault = this.ctx.settings.get("defaultThinkingLevel");
|
|
347
|
+
const levelToApply = level === ThinkingLevel.Inherit ? configuredDefault : level;
|
|
348
|
+
this.ctx.session.setThinkingLevel(levelToApply, false);
|
|
349
|
+
const effectiveLevel = this.ctx.session.thinkingLevel ?? ThinkingLevel.Off;
|
|
350
|
+
const requestedLabel =
|
|
351
|
+
level === ThinkingLevel.Inherit ? `${level} (configured default: ${configuredDefault})` : level;
|
|
352
|
+
const clampedSuffix =
|
|
353
|
+
effectiveLevel === levelToApply ? "" : ` Requested ${levelToApply}; effective ${effectiveLevel}.`;
|
|
354
|
+
|
|
355
|
+
this.ctx.statusLine.invalidate();
|
|
356
|
+
this.ctx.updateEditorBorderColor();
|
|
357
|
+
this.ctx.updateEditorTopBorder();
|
|
358
|
+
this.ctx.ui.requestRender();
|
|
359
|
+
this.ctx.showStatus(
|
|
360
|
+
`Reasoning effort set to ${requestedLabel}. Effective effort: ${effectiveLevel}.${clampedSuffix}`,
|
|
361
|
+
);
|
|
362
|
+
},
|
|
363
|
+
() => {
|
|
364
|
+
done();
|
|
365
|
+
this.ctx.ui.requestRender();
|
|
366
|
+
},
|
|
367
|
+
);
|
|
368
|
+
return { component: selector, focus: selector.getSelectList() };
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
|
|
327
372
|
showSettingsSelector(): void {
|
|
328
373
|
getAvailableThemes().then(availableThemes => {
|
|
329
374
|
this.showSelector(done => {
|
|
@@ -332,6 +377,7 @@ export class SelectorController {
|
|
|
332
377
|
availableThinkingLevels: [...this.ctx.session.getAvailableThinkingLevels()],
|
|
333
378
|
thinkingLevel: this.ctx.session.thinkingLevel,
|
|
334
379
|
availableThemes,
|
|
380
|
+
availableModelProfiles: [...this.ctx.session.modelRegistry.getModelProfiles().keys()],
|
|
335
381
|
cwd: getProjectDir(),
|
|
336
382
|
},
|
|
337
383
|
{
|
|
@@ -528,6 +574,18 @@ export class SelectorController {
|
|
|
528
574
|
this.ctx.updateEditorBorderColor();
|
|
529
575
|
break;
|
|
530
576
|
|
|
577
|
+
case "modelProfile.default": {
|
|
578
|
+
// Applying the default profile live mirrors the /model preset flow so the
|
|
579
|
+
// running session switches immediately, not only on next startup.
|
|
580
|
+
const profileName = typeof value === "string" ? value : "";
|
|
581
|
+
if (!profileName) break;
|
|
582
|
+
this.#applyModelProfile(profileName, true)
|
|
583
|
+
.then(() => this.ctx.ui.requestRender())
|
|
584
|
+
.catch(error => {
|
|
585
|
+
this.ctx.showError(error instanceof Error ? error.message : String(error));
|
|
586
|
+
});
|
|
587
|
+
break;
|
|
588
|
+
}
|
|
531
589
|
case "clearOnShrink":
|
|
532
590
|
this.ctx.ui.setClearOnShrink(value as boolean);
|
|
533
591
|
break;
|
|
@@ -678,6 +736,29 @@ export class SelectorController {
|
|
|
678
736
|
}
|
|
679
737
|
}
|
|
680
738
|
|
|
739
|
+
/**
|
|
740
|
+
* Activate a model profile through the shared /model + /settings path: swap the
|
|
741
|
+
* live session model (and, when persistDefault, persist it as the startup
|
|
742
|
+
* default) then refresh the status surfaces. Rethrows so callers surface errors.
|
|
743
|
+
*/
|
|
744
|
+
async #applyModelProfile(profileName: string, persistDefault: boolean): Promise<void> {
|
|
745
|
+
const profileLabel = formatModelProfileDisplayLabel(
|
|
746
|
+
this.ctx.session.modelRegistry.getModelProfile(profileName) ?? { name: profileName },
|
|
747
|
+
);
|
|
748
|
+
await activateModelProfile(
|
|
749
|
+
{
|
|
750
|
+
session: this.ctx.session,
|
|
751
|
+
modelRegistry: this.ctx.session.modelRegistry,
|
|
752
|
+
settings: this.ctx.settings,
|
|
753
|
+
profileName,
|
|
754
|
+
},
|
|
755
|
+
{ persistDefault },
|
|
756
|
+
);
|
|
757
|
+
this.ctx.statusLine.invalidate();
|
|
758
|
+
this.ctx.updateEditorBorderColor();
|
|
759
|
+
this.ctx.showStatus(persistDefault ? `Default model profile: ${profileLabel}` : `Model profile: ${profileLabel}`);
|
|
760
|
+
}
|
|
761
|
+
|
|
681
762
|
showModelSelector(options?: { temporaryOnly?: boolean }): void {
|
|
682
763
|
this.showSelector(done => {
|
|
683
764
|
let modelSelector: ModelSelectorComponent;
|
|
@@ -708,22 +789,7 @@ export class SelectorController {
|
|
|
708
789
|
return;
|
|
709
790
|
}
|
|
710
791
|
if (selection.kind === "profile") {
|
|
711
|
-
await
|
|
712
|
-
{
|
|
713
|
-
session: this.ctx.session,
|
|
714
|
-
modelRegistry: this.ctx.session.modelRegistry,
|
|
715
|
-
settings: this.ctx.settings,
|
|
716
|
-
profileName: selection.profileName,
|
|
717
|
-
},
|
|
718
|
-
{ persistDefault: selection.setDefault },
|
|
719
|
-
);
|
|
720
|
-
this.ctx.statusLine.invalidate();
|
|
721
|
-
this.ctx.updateEditorBorderColor();
|
|
722
|
-
this.ctx.showStatus(
|
|
723
|
-
selection.setDefault
|
|
724
|
-
? `Default model profile: ${selection.profileName}`
|
|
725
|
-
: `Model profile: ${selection.profileName}`,
|
|
726
|
-
);
|
|
792
|
+
await this.#applyModelProfile(selection.profileName, selection.setDefault);
|
|
727
793
|
done();
|
|
728
794
|
this.ctx.ui.requestRender();
|
|
729
795
|
return;
|
|
@@ -737,6 +803,79 @@ export class SelectorController {
|
|
|
737
803
|
this.ctx.showStatus(`Temporary model: ${selectedSelector ?? model.id}`);
|
|
738
804
|
done();
|
|
739
805
|
this.ctx.ui.requestRender();
|
|
806
|
+
} else if (selection.roles) {
|
|
807
|
+
const targetRoles: readonly SkcModelAssignmentTargetId[] = selection.roles;
|
|
808
|
+
const includesDefault = targetRoles.includes("default");
|
|
809
|
+
const includesRoleAgent = targetRoles.some(targetRole => targetRole !== "default");
|
|
810
|
+
if (includesRoleAgent) {
|
|
811
|
+
const apiKey = await this.ctx.session.modelRegistry.getApiKey(
|
|
812
|
+
model,
|
|
813
|
+
this.ctx.session.sessionId,
|
|
814
|
+
);
|
|
815
|
+
if (!apiKey) {
|
|
816
|
+
throw new Error(`No API key for ${model.provider}/${model.id}`);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
const value =
|
|
820
|
+
selectedSelector ?? formatModelSelectorValue(`${model.provider}/${model.id}`, thinkingLevel);
|
|
821
|
+
const assignments = new Map<SkcModelAssignmentTargetId, string>();
|
|
822
|
+
for (const targetRole of targetRoles) assignments.set(targetRole, value);
|
|
823
|
+
const defaultSelector =
|
|
824
|
+
selectedSelector && thinkingLevel && selectedSelector.endsWith(`:${thinkingLevel}`)
|
|
825
|
+
? selectedSelector.slice(0, -thinkingLevel.length - 1)
|
|
826
|
+
: selectedSelector;
|
|
827
|
+
|
|
828
|
+
if (includesDefault) {
|
|
829
|
+
await this.ctx.session.setModel(model, "default", {
|
|
830
|
+
selector: defaultSelector,
|
|
831
|
+
thinkingLevel,
|
|
832
|
+
});
|
|
833
|
+
if (thinkingLevel && thinkingLevel !== ThinkingLevel.Inherit) {
|
|
834
|
+
this.ctx.session.setThinkingLevel(thinkingLevel);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
const materializedProfile = materializeActiveModelProfileAssignments({
|
|
838
|
+
session: this.ctx.session,
|
|
839
|
+
settings: this.ctx.settings,
|
|
840
|
+
assignments,
|
|
841
|
+
});
|
|
842
|
+
if (!materializedProfile) {
|
|
843
|
+
const overrides = this.ctx.settings.get("task.agentModelOverrides");
|
|
844
|
+
const nextOverrides = { ...overrides };
|
|
845
|
+
let writesOverrides = false;
|
|
846
|
+
for (const targetRole of targetRoles) {
|
|
847
|
+
const target = SKC_MODEL_ASSIGNMENT_TARGETS[targetRole];
|
|
848
|
+
if (target.settingsPath === "modelRoles") {
|
|
849
|
+
this.ctx.settings.setModelRole(targetRole, value);
|
|
850
|
+
} else {
|
|
851
|
+
nextOverrides[targetRole] = value;
|
|
852
|
+
writesOverrides = true;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
if (writesOverrides) {
|
|
856
|
+
this.ctx.settings.set("task.agentModelOverrides", nextOverrides);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
modelSelector.refreshRoleAssignments({
|
|
860
|
+
currentModel: this.ctx.session.model,
|
|
861
|
+
currentThinkingLevel: this.ctx.session.thinkingLevel,
|
|
862
|
+
activeModelProfile:
|
|
863
|
+
this.ctx.session.getActiveModelProfile?.() ?? this.ctx.settings.get("modelProfile.default"),
|
|
864
|
+
});
|
|
865
|
+
this.ctx.settings.getStorage()?.recordModelUsage(`${model.provider}/${model.id}`);
|
|
866
|
+
this.ctx.statusLine.invalidate();
|
|
867
|
+
this.ctx.updateEditorBorderColor();
|
|
868
|
+
await this.ctx.notifyConfigChanged?.();
|
|
869
|
+
const labels = targetRoles.map(
|
|
870
|
+
targetRole => SKC_MODEL_ASSIGNMENT_TARGETS[targetRole].tag ?? targetRole.toUpperCase(),
|
|
871
|
+
);
|
|
872
|
+
this.ctx.showStatus(
|
|
873
|
+
includesDefault
|
|
874
|
+
? `All model targets set to ${value} for ${labels.join(", ")}.`
|
|
875
|
+
: `Role-agent models set to ${value} for ${labels.join(", ")}.`,
|
|
876
|
+
);
|
|
877
|
+
done();
|
|
878
|
+
this.ctx.ui.requestRender();
|
|
740
879
|
} else if (role === "default") {
|
|
741
880
|
// Default: update agent state and persist as the active default model.
|
|
742
881
|
await this.ctx.session.setModel(model, role, {
|
|
@@ -774,19 +913,21 @@ export class SelectorController {
|
|
|
774
913
|
}
|
|
775
914
|
const value =
|
|
776
915
|
selectedSelector ?? formatModelSelectorValue(`${model.provider}/${model.id}`, thinkingLevel);
|
|
777
|
-
const
|
|
916
|
+
const assignments = new Map<SkcModelAssignmentTargetId, string>([[role, value]]);
|
|
917
|
+
const materializedProfile = materializeActiveModelProfileAssignments({
|
|
778
918
|
session: this.ctx.session,
|
|
779
919
|
settings: this.ctx.settings,
|
|
780
|
-
|
|
781
|
-
selector: value,
|
|
920
|
+
assignments,
|
|
782
921
|
});
|
|
783
922
|
if (!materializedProfile) {
|
|
784
923
|
const target = SKC_MODEL_ASSIGNMENT_TARGETS[role];
|
|
785
924
|
if (target.settingsPath === "modelRoles") {
|
|
786
925
|
this.ctx.settings.setModelRole(role, value);
|
|
787
926
|
} else {
|
|
788
|
-
|
|
789
|
-
|
|
927
|
+
this.ctx.settings.set("task.agentModelOverrides", {
|
|
928
|
+
...this.ctx.settings.get("task.agentModelOverrides"),
|
|
929
|
+
[role]: value,
|
|
930
|
+
});
|
|
790
931
|
}
|
|
791
932
|
}
|
|
792
933
|
modelSelector.refreshRoleAssignments({
|
|
@@ -796,6 +937,9 @@ export class SelectorController {
|
|
|
796
937
|
this.ctx.session.getActiveModelProfile?.() ?? this.ctx.settings.get("modelProfile.default"),
|
|
797
938
|
});
|
|
798
939
|
this.ctx.settings.getStorage()?.recordModelUsage(`${model.provider}/${model.id}`);
|
|
940
|
+
this.ctx.statusLine.invalidate();
|
|
941
|
+
this.ctx.updateEditorBorderColor();
|
|
942
|
+
await this.ctx.notifyConfigChanged?.();
|
|
799
943
|
this.ctx.showStatus(`${role} agent model: ${value}`);
|
|
800
944
|
done();
|
|
801
945
|
this.ctx.ui.requestRender();
|