@sayknow-cli/coding-agent 0.3.7 → 0.3.8
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 +105 -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/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/interactive-mode.d.ts +2 -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-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 +1 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -0
- package/dist/types/notifications/config.d.ts +14 -1
- package/dist/types/notifications/daemon-paths.d.ts +1 -0
- package/dist/types/notifications/index.d.ts +6 -3
- package/dist/types/notifications/lifecycle-commands.d.ts +11 -3
- package/dist/types/notifications/telegram-daemon.d.ts +23 -3
- package/dist/types/notifications/telegram-reference.d.ts +0 -5
- package/dist/types/notifications/threaded-render.d.ts +6 -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/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/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 +92 -8
- package/src/cli/telegram-cli.ts +1 -1
- package/src/cli/update-cli.ts +85 -9
- 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 +2 -2
- package/src/config/model-profile-activation.ts +65 -0
- package/src/config/model-profiles.ts +31 -2
- package/src/config/settings-schema.ts +68 -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 +14 -5
- 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/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/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 +66 -47
- package/src/modes/controllers/extension-ui-controller.ts +4 -10
- package/src/modes/controllers/input-controller.ts +180 -18
- package/src/modes/controllers/selector-controller.ts +127 -24
- package/src/modes/interactive-mode.ts +51 -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 +29 -3
- 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 +1 -0
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +20 -7
- package/src/notifications/config.ts +25 -5
- package/src/notifications/daemon-paths.ts +2 -0
- package/src/notifications/index.ts +117 -14
- package/src/notifications/lifecycle-commands.ts +37 -7
- package/src/notifications/lifecycle-orchestrator.ts +19 -6
- package/src/notifications/telegram-daemon-control.ts +5 -3
- package/src/notifications/telegram-daemon.ts +302 -51
- package/src/notifications/telegram-reference.ts +0 -16
- package/src/notifications/threaded-render.ts +17 -1
- 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 +1 -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 +52 -28
- package/src/session/agent-session.ts +1011 -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/ultragoal-guard.ts +55 -66
- package/src/skc-runtime/ultragoal-receipt-freshness.ts +315 -0
- package/src/skc-runtime/ultragoal-runtime.ts +1663 -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 +218 -71
- 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/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
|
@@ -11,7 +11,7 @@ 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";
|
|
@@ -19,6 +19,7 @@ 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
21
|
import { generateSessionTitle, setSessionTerminalTitle } from "../../utils/title-generator";
|
|
22
|
+
import { type QueuedMessageMoveDirection, QueuedMessageSelectorComponent } from "../components/queued-message-selector";
|
|
22
23
|
|
|
23
24
|
interface Expandable {
|
|
24
25
|
setExpanded(expanded: boolean): void;
|
|
@@ -313,14 +314,7 @@ export class InputController {
|
|
|
313
314
|
this.ctx.editor.onDequeue = () => this.handleDequeue();
|
|
314
315
|
this.ctx.editor.setActionKeys("app.message.queue", this.ctx.keybindings.getKeys("app.message.queue"));
|
|
315
316
|
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
|
-
};
|
|
317
|
+
|
|
324
318
|
this.ctx.editor.onViewportPageScroll = direction => this.ctx.ui.scrollViewportPages(direction);
|
|
325
319
|
this.ctx.editor.onViewportFollowLive = () => {
|
|
326
320
|
this.ctx.ui.followLiveViewport();
|
|
@@ -614,12 +608,182 @@ export class InputController {
|
|
|
614
608
|
}
|
|
615
609
|
|
|
616
610
|
handleDequeue(): void {
|
|
617
|
-
const
|
|
618
|
-
if (
|
|
611
|
+
const entries = this.#getEditableQueuedMessages();
|
|
612
|
+
if (entries.length === 0) {
|
|
613
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
619
614
|
this.ctx.showStatus("No queued messages to restore");
|
|
620
|
-
|
|
621
|
-
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
if (entries.length === 1) {
|
|
618
|
+
const restored = this.#restoreQueuedMessageToEditor(entries[0]);
|
|
619
|
+
this.ctx.showStatus(
|
|
620
|
+
restored === 0 ? "Queued message is no longer available" : "Restored queued message to editor",
|
|
621
|
+
);
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
this.#showQueuedMessageSelector(entries, this.#newestQueuedMessageIndex(entries));
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
#compactionQueuedMessageId(index: number): string {
|
|
628
|
+
return `compaction:${index}`;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
#getEditableQueuedMessages(): QueuedMessageEditEntry[] {
|
|
632
|
+
const compactionEntries = this.ctx.compactionQueuedMessages.map((entry, index): QueuedMessageEditEntry => {
|
|
633
|
+
const label = entry.mode === "steer" ? "Steer" : "Queued";
|
|
634
|
+
return {
|
|
635
|
+
id: this.#compactionQueuedMessageId(index),
|
|
636
|
+
text: entry.text,
|
|
637
|
+
mode: entry.mode,
|
|
638
|
+
label,
|
|
639
|
+
};
|
|
640
|
+
});
|
|
641
|
+
return [...compactionEntries, ...this.ctx.session.getQueuedMessageEntries()];
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
#queuedMessageStableSequence(entry: QueuedMessageEditEntry): number | undefined {
|
|
645
|
+
const [mode, sequenceText] = entry.id.split(":");
|
|
646
|
+
if ((mode !== "steer" && mode !== "followUp") || sequenceText === undefined) return undefined;
|
|
647
|
+
const sequence = Number(sequenceText);
|
|
648
|
+
return Number.isInteger(sequence) ? sequence : undefined;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
#newestQueuedMessageIndex(entries: QueuedMessageEditEntry[]): number {
|
|
652
|
+
let selectedIndex = entries.length - 1;
|
|
653
|
+
let newestSequence = Number.NEGATIVE_INFINITY;
|
|
654
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
655
|
+
const sequence = this.#queuedMessageStableSequence(entries[index]);
|
|
656
|
+
if (sequence !== undefined && sequence > newestSequence) {
|
|
657
|
+
newestSequence = sequence;
|
|
658
|
+
selectedIndex = index;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
return Math.max(0, selectedIndex);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
#restoreEditorFocus(): void {
|
|
665
|
+
this.ctx.editorContainer.clear();
|
|
666
|
+
this.ctx.editorContainer.addChild(this.ctx.editor);
|
|
667
|
+
this.ctx.ui.setFocus(this.ctx.editor);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
#showQueuedMessageSelector(entries: QueuedMessageEditEntry[], selectedIndex = 0): void {
|
|
671
|
+
const selector = new QueuedMessageSelectorComponent(
|
|
672
|
+
entries,
|
|
673
|
+
entry => {
|
|
674
|
+
const restored = this.#restoreQueuedMessageToEditor(entry);
|
|
675
|
+
this.#restoreEditorFocus();
|
|
676
|
+
this.ctx.showStatus(
|
|
677
|
+
restored === 0 ? "Queued message is no longer available" : "Restored queued message to editor",
|
|
678
|
+
);
|
|
679
|
+
this.ctx.ui.requestRender();
|
|
680
|
+
},
|
|
681
|
+
(entry, index) => {
|
|
682
|
+
const deleted = this.#deleteQueuedMessage(entry);
|
|
683
|
+
const nextEntries = this.#getEditableQueuedMessages();
|
|
684
|
+
if (nextEntries.length === 0) {
|
|
685
|
+
this.#restoreEditorFocus();
|
|
686
|
+
this.ctx.showStatus(deleted ? "Deleted queued message" : "Queued message is no longer available");
|
|
687
|
+
this.ctx.ui.requestRender();
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
this.ctx.showStatus(deleted ? "Deleted queued message" : "Queued message is no longer available");
|
|
691
|
+
this.#showQueuedMessageSelector(nextEntries, Math.min(index, nextEntries.length - 1));
|
|
692
|
+
},
|
|
693
|
+
(entry, index, direction) => {
|
|
694
|
+
const moved = this.#moveQueuedMessage(entry, direction);
|
|
695
|
+
const nextEntries = this.#getEditableQueuedMessages();
|
|
696
|
+
if (nextEntries.length === 0) {
|
|
697
|
+
this.#restoreEditorFocus();
|
|
698
|
+
this.ctx.showStatus("Queued message is no longer available");
|
|
699
|
+
this.ctx.ui.requestRender();
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
const nextIndex = direction === "up" ? index - 1 : index + 1;
|
|
703
|
+
const selectedNextIndex = moved ? nextIndex : index;
|
|
704
|
+
this.ctx.showStatus(moved ? "Moved queued message" : "Queued message cannot move further");
|
|
705
|
+
this.#showQueuedMessageSelector(
|
|
706
|
+
nextEntries,
|
|
707
|
+
Math.max(0, Math.min(selectedNextIndex, nextEntries.length - 1)),
|
|
708
|
+
);
|
|
709
|
+
},
|
|
710
|
+
() => {
|
|
711
|
+
this.#restoreEditorFocus();
|
|
712
|
+
this.ctx.ui.requestRender();
|
|
713
|
+
},
|
|
714
|
+
{ selectedIndex },
|
|
715
|
+
);
|
|
716
|
+
this.ctx.editorContainer.clear();
|
|
717
|
+
this.ctx.editorContainer.addChild(selector);
|
|
718
|
+
this.ctx.ui.setFocus(selector);
|
|
719
|
+
this.ctx.ui.requestRender();
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
#removeQueuedMessageForEditing(id: string): string | undefined {
|
|
723
|
+
const compactionPrefix = "compaction:";
|
|
724
|
+
if (id.startsWith(compactionPrefix)) {
|
|
725
|
+
const index = Number(id.slice(compactionPrefix.length));
|
|
726
|
+
if (!Number.isInteger(index)) return undefined;
|
|
727
|
+
const [entry] = this.ctx.compactionQueuedMessages.splice(index, 1);
|
|
728
|
+
return entry?.text;
|
|
729
|
+
}
|
|
730
|
+
return this.ctx.session.removeQueuedMessageForEditing(id);
|
|
731
|
+
}
|
|
732
|
+
#parseCompactionQueuedMessageId(id: string): number | undefined {
|
|
733
|
+
const compactionPrefix = "compaction:";
|
|
734
|
+
if (!id.startsWith(compactionPrefix)) return undefined;
|
|
735
|
+
const index = Number(id.slice(compactionPrefix.length));
|
|
736
|
+
return Number.isInteger(index) ? index : undefined;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
#moveCompactionQueuedMessage(index: number, direction: QueuedMessageMoveDirection): boolean {
|
|
740
|
+
const targetIndex = direction === "up" ? index - 1 : index + 1;
|
|
741
|
+
if (index < 0 || index >= this.ctx.compactionQueuedMessages.length) return false;
|
|
742
|
+
if (targetIndex < 0 || targetIndex >= this.ctx.compactionQueuedMessages.length) return false;
|
|
743
|
+
const current = this.ctx.compactionQueuedMessages[index];
|
|
744
|
+
const target = this.ctx.compactionQueuedMessages[targetIndex];
|
|
745
|
+
if (!current || !target) return false;
|
|
746
|
+
this.ctx.compactionQueuedMessages[index] = target;
|
|
747
|
+
this.ctx.compactionQueuedMessages[targetIndex] = current;
|
|
748
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
749
|
+
return true;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
#moveQueuedMessage(entry: QueuedMessageEditEntry, direction: QueuedMessageMoveDirection): boolean {
|
|
753
|
+
const compactionIndex = this.#parseCompactionQueuedMessageId(entry.id);
|
|
754
|
+
if (compactionIndex !== undefined) {
|
|
755
|
+
return this.#moveCompactionQueuedMessage(compactionIndex, direction);
|
|
756
|
+
}
|
|
757
|
+
const moved = this.ctx.session.moveQueuedMessageForEditing(entry.id, direction);
|
|
758
|
+
if (moved) {
|
|
759
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
622
760
|
}
|
|
761
|
+
return moved;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
#deleteQueuedMessage(entry: QueuedMessageEditEntry): boolean {
|
|
765
|
+
const queuedText = this.#removeQueuedMessageForEditing(entry.id);
|
|
766
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
767
|
+
if (!queuedText) return false;
|
|
768
|
+
this.ctx.locallySubmittedUserSignatures.delete(`${queuedText}\u00000`);
|
|
769
|
+
return true;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
#restoreQueuedMessageToEditor(entry: QueuedMessageEditEntry | undefined): number {
|
|
773
|
+
if (!entry) {
|
|
774
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
775
|
+
return 0;
|
|
776
|
+
}
|
|
777
|
+
const queuedText = this.#removeQueuedMessageForEditing(entry.id);
|
|
778
|
+
if (!queuedText) {
|
|
779
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
780
|
+
return 0;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
this.ctx.locallySubmittedUserSignatures.delete(`${queuedText}\u00000`);
|
|
784
|
+
this.ctx.editor.setText(queuedText);
|
|
785
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
786
|
+
return 1;
|
|
623
787
|
}
|
|
624
788
|
|
|
625
789
|
/**
|
|
@@ -766,7 +930,7 @@ export class InputController {
|
|
|
766
930
|
return this.handleFollowUp();
|
|
767
931
|
}
|
|
768
932
|
|
|
769
|
-
restoreLatestQueuedMessageToEditor(
|
|
933
|
+
restoreLatestQueuedMessageToEditor(): number {
|
|
770
934
|
const compactionQueued = this.ctx.compactionQueuedMessages.pop();
|
|
771
935
|
const queuedText = compactionQueued?.text ?? this.ctx.session.popLastQueuedMessage();
|
|
772
936
|
if (!queuedText) {
|
|
@@ -775,9 +939,7 @@ export class InputController {
|
|
|
775
939
|
}
|
|
776
940
|
|
|
777
941
|
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);
|
|
942
|
+
this.ctx.editor.setText(queuedText);
|
|
781
943
|
this.ctx.updatePendingMessagesDisplay();
|
|
782
944
|
return 1;
|
|
783
945
|
}
|
|
@@ -1146,7 +1308,7 @@ export class InputController {
|
|
|
1146
1308
|
// If streaming, re-add the streaming component with updated visibility and re-render
|
|
1147
1309
|
if (this.ctx.streamingComponent && this.ctx.streamingMessage) {
|
|
1148
1310
|
this.ctx.streamingComponent.setHideThinkingBlock(this.ctx.hideThinkingBlock);
|
|
1149
|
-
this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage);
|
|
1311
|
+
this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage, { streaming: true });
|
|
1150
1312
|
this.ctx.chatContainer.addChild(this.ctx.streamingComponent);
|
|
1151
1313
|
}
|
|
1152
1314
|
|
|
@@ -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";
|
|
@@ -332,6 +336,7 @@ export class SelectorController {
|
|
|
332
336
|
availableThinkingLevels: [...this.ctx.session.getAvailableThinkingLevels()],
|
|
333
337
|
thinkingLevel: this.ctx.session.thinkingLevel,
|
|
334
338
|
availableThemes,
|
|
339
|
+
availableModelProfiles: [...this.ctx.session.modelRegistry.getModelProfiles().keys()],
|
|
335
340
|
cwd: getProjectDir(),
|
|
336
341
|
},
|
|
337
342
|
{
|
|
@@ -528,6 +533,18 @@ export class SelectorController {
|
|
|
528
533
|
this.ctx.updateEditorBorderColor();
|
|
529
534
|
break;
|
|
530
535
|
|
|
536
|
+
case "modelProfile.default": {
|
|
537
|
+
// Applying the default profile live mirrors the /model preset flow so the
|
|
538
|
+
// running session switches immediately, not only on next startup.
|
|
539
|
+
const profileName = typeof value === "string" ? value : "";
|
|
540
|
+
if (!profileName) break;
|
|
541
|
+
this.#applyModelProfile(profileName, true)
|
|
542
|
+
.then(() => this.ctx.ui.requestRender())
|
|
543
|
+
.catch(error => {
|
|
544
|
+
this.ctx.showError(error instanceof Error ? error.message : String(error));
|
|
545
|
+
});
|
|
546
|
+
break;
|
|
547
|
+
}
|
|
531
548
|
case "clearOnShrink":
|
|
532
549
|
this.ctx.ui.setClearOnShrink(value as boolean);
|
|
533
550
|
break;
|
|
@@ -678,6 +695,29 @@ export class SelectorController {
|
|
|
678
695
|
}
|
|
679
696
|
}
|
|
680
697
|
|
|
698
|
+
/**
|
|
699
|
+
* Activate a model profile through the shared /model + /settings path: swap the
|
|
700
|
+
* live session model (and, when persistDefault, persist it as the startup
|
|
701
|
+
* default) then refresh the status surfaces. Rethrows so callers surface errors.
|
|
702
|
+
*/
|
|
703
|
+
async #applyModelProfile(profileName: string, persistDefault: boolean): Promise<void> {
|
|
704
|
+
const profileLabel = formatModelProfileDisplayLabel(
|
|
705
|
+
this.ctx.session.modelRegistry.getModelProfile(profileName) ?? { name: profileName },
|
|
706
|
+
);
|
|
707
|
+
await activateModelProfile(
|
|
708
|
+
{
|
|
709
|
+
session: this.ctx.session,
|
|
710
|
+
modelRegistry: this.ctx.session.modelRegistry,
|
|
711
|
+
settings: this.ctx.settings,
|
|
712
|
+
profileName,
|
|
713
|
+
},
|
|
714
|
+
{ persistDefault },
|
|
715
|
+
);
|
|
716
|
+
this.ctx.statusLine.invalidate();
|
|
717
|
+
this.ctx.updateEditorBorderColor();
|
|
718
|
+
this.ctx.showStatus(persistDefault ? `Default model profile: ${profileLabel}` : `Model profile: ${profileLabel}`);
|
|
719
|
+
}
|
|
720
|
+
|
|
681
721
|
showModelSelector(options?: { temporaryOnly?: boolean }): void {
|
|
682
722
|
this.showSelector(done => {
|
|
683
723
|
let modelSelector: ModelSelectorComponent;
|
|
@@ -708,22 +748,7 @@ export class SelectorController {
|
|
|
708
748
|
return;
|
|
709
749
|
}
|
|
710
750
|
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
|
-
);
|
|
751
|
+
await this.#applyModelProfile(selection.profileName, selection.setDefault);
|
|
727
752
|
done();
|
|
728
753
|
this.ctx.ui.requestRender();
|
|
729
754
|
return;
|
|
@@ -737,6 +762,79 @@ export class SelectorController {
|
|
|
737
762
|
this.ctx.showStatus(`Temporary model: ${selectedSelector ?? model.id}`);
|
|
738
763
|
done();
|
|
739
764
|
this.ctx.ui.requestRender();
|
|
765
|
+
} else if (selection.roles) {
|
|
766
|
+
const targetRoles: readonly SkcModelAssignmentTargetId[] = selection.roles;
|
|
767
|
+
const includesDefault = targetRoles.includes("default");
|
|
768
|
+
const includesRoleAgent = targetRoles.some(targetRole => targetRole !== "default");
|
|
769
|
+
if (includesRoleAgent) {
|
|
770
|
+
const apiKey = await this.ctx.session.modelRegistry.getApiKey(
|
|
771
|
+
model,
|
|
772
|
+
this.ctx.session.sessionId,
|
|
773
|
+
);
|
|
774
|
+
if (!apiKey) {
|
|
775
|
+
throw new Error(`No API key for ${model.provider}/${model.id}`);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
const value =
|
|
779
|
+
selectedSelector ?? formatModelSelectorValue(`${model.provider}/${model.id}`, thinkingLevel);
|
|
780
|
+
const assignments = new Map<SkcModelAssignmentTargetId, string>();
|
|
781
|
+
for (const targetRole of targetRoles) assignments.set(targetRole, value);
|
|
782
|
+
const defaultSelector =
|
|
783
|
+
selectedSelector && thinkingLevel && selectedSelector.endsWith(`:${thinkingLevel}`)
|
|
784
|
+
? selectedSelector.slice(0, -thinkingLevel.length - 1)
|
|
785
|
+
: selectedSelector;
|
|
786
|
+
|
|
787
|
+
if (includesDefault) {
|
|
788
|
+
await this.ctx.session.setModel(model, "default", {
|
|
789
|
+
selector: defaultSelector,
|
|
790
|
+
thinkingLevel,
|
|
791
|
+
});
|
|
792
|
+
if (thinkingLevel && thinkingLevel !== ThinkingLevel.Inherit) {
|
|
793
|
+
this.ctx.session.setThinkingLevel(thinkingLevel);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
const materializedProfile = materializeActiveModelProfileAssignments({
|
|
797
|
+
session: this.ctx.session,
|
|
798
|
+
settings: this.ctx.settings,
|
|
799
|
+
assignments,
|
|
800
|
+
});
|
|
801
|
+
if (!materializedProfile) {
|
|
802
|
+
const overrides = this.ctx.settings.get("task.agentModelOverrides");
|
|
803
|
+
const nextOverrides = { ...overrides };
|
|
804
|
+
let writesOverrides = false;
|
|
805
|
+
for (const targetRole of targetRoles) {
|
|
806
|
+
const target = SKC_MODEL_ASSIGNMENT_TARGETS[targetRole];
|
|
807
|
+
if (target.settingsPath === "modelRoles") {
|
|
808
|
+
this.ctx.settings.setModelRole(targetRole, value);
|
|
809
|
+
} else {
|
|
810
|
+
nextOverrides[targetRole] = value;
|
|
811
|
+
writesOverrides = true;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
if (writesOverrides) {
|
|
815
|
+
this.ctx.settings.set("task.agentModelOverrides", nextOverrides);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
modelSelector.refreshRoleAssignments({
|
|
819
|
+
currentModel: this.ctx.session.model,
|
|
820
|
+
currentThinkingLevel: this.ctx.session.thinkingLevel,
|
|
821
|
+
activeModelProfile:
|
|
822
|
+
this.ctx.session.getActiveModelProfile?.() ?? this.ctx.settings.get("modelProfile.default"),
|
|
823
|
+
});
|
|
824
|
+
this.ctx.settings.getStorage()?.recordModelUsage(`${model.provider}/${model.id}`);
|
|
825
|
+
this.ctx.statusLine.invalidate();
|
|
826
|
+
this.ctx.updateEditorBorderColor();
|
|
827
|
+
await this.ctx.notifyConfigChanged?.();
|
|
828
|
+
const labels = targetRoles.map(
|
|
829
|
+
targetRole => SKC_MODEL_ASSIGNMENT_TARGETS[targetRole].tag ?? targetRole.toUpperCase(),
|
|
830
|
+
);
|
|
831
|
+
this.ctx.showStatus(
|
|
832
|
+
includesDefault
|
|
833
|
+
? `All model targets set to ${value} for ${labels.join(", ")}.`
|
|
834
|
+
: `Role-agent models set to ${value} for ${labels.join(", ")}.`,
|
|
835
|
+
);
|
|
836
|
+
done();
|
|
837
|
+
this.ctx.ui.requestRender();
|
|
740
838
|
} else if (role === "default") {
|
|
741
839
|
// Default: update agent state and persist as the active default model.
|
|
742
840
|
await this.ctx.session.setModel(model, role, {
|
|
@@ -774,19 +872,21 @@ export class SelectorController {
|
|
|
774
872
|
}
|
|
775
873
|
const value =
|
|
776
874
|
selectedSelector ?? formatModelSelectorValue(`${model.provider}/${model.id}`, thinkingLevel);
|
|
777
|
-
const
|
|
875
|
+
const assignments = new Map<SkcModelAssignmentTargetId, string>([[role, value]]);
|
|
876
|
+
const materializedProfile = materializeActiveModelProfileAssignments({
|
|
778
877
|
session: this.ctx.session,
|
|
779
878
|
settings: this.ctx.settings,
|
|
780
|
-
|
|
781
|
-
selector: value,
|
|
879
|
+
assignments,
|
|
782
880
|
});
|
|
783
881
|
if (!materializedProfile) {
|
|
784
882
|
const target = SKC_MODEL_ASSIGNMENT_TARGETS[role];
|
|
785
883
|
if (target.settingsPath === "modelRoles") {
|
|
786
884
|
this.ctx.settings.setModelRole(role, value);
|
|
787
885
|
} else {
|
|
788
|
-
|
|
789
|
-
|
|
886
|
+
this.ctx.settings.set("task.agentModelOverrides", {
|
|
887
|
+
...this.ctx.settings.get("task.agentModelOverrides"),
|
|
888
|
+
[role]: value,
|
|
889
|
+
});
|
|
790
890
|
}
|
|
791
891
|
}
|
|
792
892
|
modelSelector.refreshRoleAssignments({
|
|
@@ -796,6 +896,9 @@ export class SelectorController {
|
|
|
796
896
|
this.ctx.session.getActiveModelProfile?.() ?? this.ctx.settings.get("modelProfile.default"),
|
|
797
897
|
});
|
|
798
898
|
this.ctx.settings.getStorage()?.recordModelUsage(`${model.provider}/${model.id}`);
|
|
899
|
+
this.ctx.statusLine.invalidate();
|
|
900
|
+
this.ctx.updateEditorBorderColor();
|
|
901
|
+
await this.ctx.notifyConfigChanged?.();
|
|
799
902
|
this.ctx.showStatus(`${role} agent model: ${value}`);
|
|
800
903
|
done();
|
|
801
904
|
this.ctx.ui.requestRender();
|
|
@@ -118,12 +118,14 @@ import type { CompactionQueuedMessage, InteractiveModeContext, SubmittedUserInpu
|
|
|
118
118
|
import { UiHelpers } from "./utils/ui-helpers";
|
|
119
119
|
|
|
120
120
|
const INTERACTIVE_ABORT_CLEANUP_TIMEOUT_MS = 5_000;
|
|
121
|
-
const
|
|
121
|
+
const COMPOSER_NEWLINE_HINT = process.platform === "win32" ? "Alt+Enter/Ctrl+J" : "Shift+Enter/Ctrl+J";
|
|
122
|
+
export const DEFAULT_COMPOSER_PLACEHOLDER = `Type your message... ${COMPOSER_NEWLINE_HINT}: New line · Ctrl+C: Clear · Ctrl+R: Search history · Shift+Tab: Reasoning`;
|
|
123
|
+
const WELCOME_RESERVED_CONTAINER_CHILD_LIMIT = 8;
|
|
122
124
|
const FRIENDLY_KEY_PARTS: Record<string, string> = {
|
|
123
125
|
alt: "Alt",
|
|
124
|
-
cmd: "
|
|
125
|
-
command: "
|
|
126
|
-
ctrl: "
|
|
126
|
+
cmd: "Cmd",
|
|
127
|
+
command: "Cmd",
|
|
128
|
+
ctrl: "Ctrl",
|
|
127
129
|
enter: "Enter",
|
|
128
130
|
meta: process.platform === "darwin" ? "Command" : "Meta",
|
|
129
131
|
option: "Option",
|
|
@@ -133,7 +135,7 @@ const FRIENDLY_KEY_PARTS: Record<string, string> = {
|
|
|
133
135
|
function formatShortcutForPlaceholder(key: string): string {
|
|
134
136
|
return key
|
|
135
137
|
.split("+")
|
|
136
|
-
.map(part => FRIENDLY_KEY_PARTS[part.toLowerCase()] ?? part)
|
|
138
|
+
.map(part => FRIENDLY_KEY_PARTS[part.toLowerCase()] ?? (part.length === 1 ? part.toUpperCase() : part))
|
|
137
139
|
.join("+");
|
|
138
140
|
}
|
|
139
141
|
|
|
@@ -162,6 +164,7 @@ function configureDefaultComposerChrome(editor: CustomEditor): void {
|
|
|
162
164
|
editor.setInputPrefix(getDefaultInputPrefix());
|
|
163
165
|
editor.setPlaceholder(DEFAULT_COMPOSER_PLACEHOLDER);
|
|
164
166
|
editor.setPaddingX(1);
|
|
167
|
+
editor.setRightGutterWidth(1);
|
|
165
168
|
editor.setTopBorder(undefined);
|
|
166
169
|
}
|
|
167
170
|
|
|
@@ -444,7 +447,6 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
444
447
|
logger.warn("History storage unavailable", { error: String(error) });
|
|
445
448
|
}
|
|
446
449
|
this.hookWidgetContainerAbove = new Container();
|
|
447
|
-
this.hookWidgetContainerAbove.addChild(new Spacer(1));
|
|
448
450
|
this.hookWidgetContainerBelow = new Container();
|
|
449
451
|
this.editorContainer = new Container();
|
|
450
452
|
this.editorContainer.addChild(this.editor);
|
|
@@ -532,13 +534,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
532
534
|
}
|
|
533
535
|
|
|
534
536
|
if (!startupQuiet) {
|
|
535
|
-
const getWelcomeReservedBottomRows = (width: number): number =>
|
|
536
|
-
[
|
|
537
|
-
this.statusLine,
|
|
538
|
-
this.hookWidgetContainerAbove,
|
|
539
|
-
this.editorContainer,
|
|
540
|
-
this.hookWidgetContainerBelow,
|
|
541
|
-
].reduce((rows, component) => rows + component.render(width).length, 0);
|
|
537
|
+
const getWelcomeReservedBottomRows = (width: number): number => this.#getWelcomeReservedRows(width);
|
|
542
538
|
|
|
543
539
|
// Add welcome header
|
|
544
540
|
this.#welcomeComponent = new WelcomeComponent(
|
|
@@ -941,11 +937,37 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
941
937
|
|
|
942
938
|
#getComposerPlaceholder(): string {
|
|
943
939
|
if (!this.#isPromptDeliveryBusy()) return DEFAULT_COMPOSER_PLACEHOLDER;
|
|
944
|
-
const enterAction = this.settings.get("busyPromptMode") === "steer" ? "
|
|
940
|
+
const enterAction = this.settings.get("busyPromptMode") === "steer" ? "Steer" : "Queue";
|
|
945
941
|
const parts = [`Enter: ${enterAction}`];
|
|
946
942
|
const queueKey = this.#getMessageQueueShortcut();
|
|
947
|
-
if (queueKey) parts.push(`${formatShortcutForPlaceholder(queueKey)}:
|
|
948
|
-
return `${DEFAULT_COMPOSER_PLACEHOLDER} ${parts.join(" · ")}`;
|
|
943
|
+
if (queueKey) parts.push(`${formatShortcutForPlaceholder(queueKey)}: Queue`);
|
|
944
|
+
return `${DEFAULT_COMPOSER_PLACEHOLDER} · ${parts.join(" · ")}`;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
#getWelcomeReservedRows(width: number): number {
|
|
948
|
+
const transientRows = [
|
|
949
|
+
this.chatContainer,
|
|
950
|
+
this.pendingMessagesContainer,
|
|
951
|
+
this.statusContainer,
|
|
952
|
+
this.todoContainer,
|
|
953
|
+
this.btwContainer,
|
|
954
|
+
].reduce((rows, container) => rows + this.#renderShortContainerRowsForWelcomeReservation(width, container), 0);
|
|
955
|
+
|
|
956
|
+
const pinnedRows = [
|
|
957
|
+
this.statusLine,
|
|
958
|
+
this.hookWidgetContainerAbove,
|
|
959
|
+
this.editorContainer,
|
|
960
|
+
this.hookWidgetContainerBelow,
|
|
961
|
+
].reduce((rows, component) => rows + component.render(width).length, 0);
|
|
962
|
+
|
|
963
|
+
return transientRows + pinnedRows;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
#renderShortContainerRowsForWelcomeReservation(width: number, container: Container): number {
|
|
967
|
+
if (container.children.length === 0 || container.children.length > WELCOME_RESERVED_CONTAINER_CHILD_LIMIT) {
|
|
968
|
+
return 0;
|
|
969
|
+
}
|
|
970
|
+
return container.render(width).length;
|
|
949
971
|
}
|
|
950
972
|
|
|
951
973
|
updateEditorChrome(): void {
|
|
@@ -1352,6 +1374,12 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1352
1374
|
return;
|
|
1353
1375
|
}
|
|
1354
1376
|
|
|
1377
|
+
// Leaving plan mode invalidates the plan-mode steering and standing
|
|
1378
|
+
// resolve handler that the in-flight turn may still be acting on. Abort
|
|
1379
|
+
// before clearing that state so the model cannot continue from stale
|
|
1380
|
+
// plan-mode context and retry `resolve` against a retired handler.
|
|
1381
|
+
await this.session.abort({ timeoutMs: INTERACTIVE_ABORT_CLEANUP_TIMEOUT_MS });
|
|
1382
|
+
|
|
1355
1383
|
const previousTools = this.#planModePreviousTools;
|
|
1356
1384
|
if (previousTools && previousTools.length > 0) {
|
|
1357
1385
|
await this.session.setActiveToolsByName(previousTools);
|
|
@@ -1689,6 +1717,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1689
1717
|
planContent,
|
|
1690
1718
|
finalPlanFilePath: options.finalPlanFilePath,
|
|
1691
1719
|
contextPreserved: options.preserveContext === true,
|
|
1720
|
+
tools: this.session.getActiveToolNames(),
|
|
1692
1721
|
});
|
|
1693
1722
|
await this.session.prompt(planModePrompt, { synthetic: true });
|
|
1694
1723
|
}
|
|
@@ -2180,6 +2209,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2180
2209
|
message => renderWorkingMessage(message, this.#getWorkingMessageAccent()),
|
|
2181
2210
|
this.#defaultWorkingMessage,
|
|
2182
2211
|
getSymbolTheme().spinnerFrames,
|
|
2212
|
+
{ timeDependentColor: true },
|
|
2183
2213
|
);
|
|
2184
2214
|
this.statusContainer.addChild(this.loadingAnimation);
|
|
2185
2215
|
}
|
|
@@ -2337,6 +2367,11 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2337
2367
|
return this.#commandController.handleClearCommand();
|
|
2338
2368
|
}
|
|
2339
2369
|
|
|
2370
|
+
handleContextClearCommand(): Promise<void> {
|
|
2371
|
+
this.#prepareSessionSwitch();
|
|
2372
|
+
return this.#commandController.handleContextClearCommand();
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2340
2375
|
handleDropCommand(): Promise<void> {
|
|
2341
2376
|
this.#prepareSessionSwitch();
|
|
2342
2377
|
return this.#commandController.handleDropCommand();
|
package/src/modes/print-mode.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* - `skc -p "prompt"` - text output
|
|
6
6
|
* - `skc --mode json "prompt"` - JSON event stream
|
|
7
7
|
*/
|
|
8
|
-
import type
|
|
8
|
+
import { type AssistantMessage, type ImageContent, isContextOverflow } from "@sayknow-cli/ai";
|
|
9
9
|
import { logger, sanitizeText } from "@sayknow-cli/utils";
|
|
10
10
|
import type { AgentSession } from "../session/agent-session";
|
|
11
11
|
import { isSilentAbort } from "../session/messages";
|
|
@@ -31,6 +31,33 @@ export interface PrintModeOptions {
|
|
|
31
31
|
suppressProcessExit?: boolean;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Exit code used when a non-interactive **text-mode** run (`skc -p`) terminates
|
|
36
|
+
* because the model context window is exhausted and automatic compaction could
|
|
37
|
+
* not bring the request under the limit. Distinct from the generic failure code
|
|
38
|
+
* (1) so text-mode callers can detect context exhaustion specifically instead of
|
|
39
|
+
* parsing the raw provider error string.
|
|
40
|
+
*
|
|
41
|
+
* Scope: text-mode final-response path only. JSON mode (`--mode json`) streams
|
|
42
|
+
* events from the subscription and does not run this terminal-error branch, so
|
|
43
|
+
* it is intentionally NOT covered by this exit code.
|
|
44
|
+
*/
|
|
45
|
+
export const CONTEXT_OVERFLOW_EXIT_CODE = 2;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Build an actionable stderr diagnostic for a terminal context-overflow error in
|
|
49
|
+
* text mode. The raw provider message is preserved (appended) for debugging, but
|
|
50
|
+
* the leading guidance explains what happened and what the operator can do —
|
|
51
|
+
* tailored to whether auto-compaction was even enabled.
|
|
52
|
+
*/
|
|
53
|
+
function formatContextOverflowError(message: AssistantMessage, autoCompactionEnabled: boolean): string {
|
|
54
|
+
const providerDetail = message.errorMessage ? ` (provider error: ${sanitizeText(message.errorMessage)})` : "";
|
|
55
|
+
const guidance = autoCompactionEnabled
|
|
56
|
+
? "Context window exhausted: automatic compaction ran but could not reduce the request below the model's context limit. Reduce the input size (smaller file reads / tool output), raise the compaction threshold, or switch to a larger-context model."
|
|
57
|
+
: "Context window exhausted and automatic compaction is disabled. Enable it (compaction.enabled=true with a non-off compaction.strategy) so SKC can compact and continue, reduce the input size, or switch to a larger-context model.";
|
|
58
|
+
return `${guidance}${providerDetail}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
34
61
|
/**
|
|
35
62
|
* Run in print (single-shot) mode.
|
|
36
63
|
* Sends prompts to the agent and outputs the result.
|
|
@@ -88,15 +115,27 @@ export async function runPrintMode(session: AgentSession, options: PrintModeOpti
|
|
|
88
115
|
(assistantMsg.stopReason === "error" || assistantMsg.stopReason === "aborted") &&
|
|
89
116
|
!isSilentAbort(assistantMsg.errorMessage)
|
|
90
117
|
) {
|
|
91
|
-
|
|
118
|
+
// Context-overflow is an expected, recoverable-in-principle condition — not
|
|
119
|
+
// an opaque crash. Auto-compaction has already run inside session.prompt();
|
|
120
|
+
// if we still land here the request could not be made to fit. In this
|
|
121
|
+
// text-mode final-response path, surface an actionable diagnostic and a
|
|
122
|
+
// distinct exit code so text-mode (`skc -p`) callers can detect context
|
|
123
|
+
// exhaustion instead of parsing the raw provider error string. (JSON mode
|
|
124
|
+
// does not reach this branch and is intentionally out of scope.)
|
|
125
|
+
const isOverflow =
|
|
126
|
+
assistantMsg.stopReason === "error" && isContextOverflow(assistantMsg, session.model?.contextWindow);
|
|
127
|
+
const errorLine = isOverflow
|
|
128
|
+
? formatContextOverflowError(assistantMsg, session.autoCompactionEnabled)
|
|
129
|
+
: sanitizeText(assistantMsg.errorMessage || `Request ${assistantMsg.stopReason}`);
|
|
130
|
+
const exitCode = isOverflow ? CONTEXT_OVERFLOW_EXIT_CODE : 1;
|
|
92
131
|
const flushed = process.stderr.write(`${errorLine}\n`);
|
|
93
132
|
// When the caller owns finalization (RLM autonomous), return instead of
|
|
94
133
|
// exiting so its cleanup runs; the caller surfaces a non-zero exit itself.
|
|
95
134
|
if (!options.suppressProcessExit) {
|
|
96
135
|
if (flushed) {
|
|
97
|
-
process.exit(
|
|
136
|
+
process.exit(exitCode);
|
|
98
137
|
} else {
|
|
99
|
-
process.stderr.once("drain", () => process.exit(
|
|
138
|
+
process.stderr.once("drain", () => process.exit(exitCode));
|
|
100
139
|
}
|
|
101
140
|
}
|
|
102
141
|
}
|