@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
|
@@ -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
|
|
|
@@ -328,6 +331,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
328
331
|
onInputCallback?: (input: SubmittedUserInput) => void;
|
|
329
332
|
optimisticUserMessageSignature: string | undefined = undefined;
|
|
330
333
|
locallySubmittedUserSignatures: Set<string> = new Set();
|
|
334
|
+
optimisticInjectedSignatures: Map<string, number> = new Map();
|
|
331
335
|
#pendingSubmittedInput: SubmittedUserInput | undefined;
|
|
332
336
|
#pendingSubmissionDispose: (() => void) | undefined;
|
|
333
337
|
lastSigintTime = 0;
|
|
@@ -444,7 +448,6 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
444
448
|
logger.warn("History storage unavailable", { error: String(error) });
|
|
445
449
|
}
|
|
446
450
|
this.hookWidgetContainerAbove = new Container();
|
|
447
|
-
this.hookWidgetContainerAbove.addChild(new Spacer(1));
|
|
448
451
|
this.hookWidgetContainerBelow = new Container();
|
|
449
452
|
this.editorContainer = new Container();
|
|
450
453
|
this.editorContainer.addChild(this.editor);
|
|
@@ -532,13 +535,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
532
535
|
}
|
|
533
536
|
|
|
534
537
|
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);
|
|
538
|
+
const getWelcomeReservedBottomRows = (width: number): number => this.#getWelcomeReservedRows(width);
|
|
542
539
|
|
|
543
540
|
// Add welcome header
|
|
544
541
|
this.#welcomeComponent = new WelcomeComponent(
|
|
@@ -941,11 +938,37 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
941
938
|
|
|
942
939
|
#getComposerPlaceholder(): string {
|
|
943
940
|
if (!this.#isPromptDeliveryBusy()) return DEFAULT_COMPOSER_PLACEHOLDER;
|
|
944
|
-
const enterAction = this.settings.get("busyPromptMode") === "steer" ? "
|
|
941
|
+
const enterAction = this.settings.get("busyPromptMode") === "steer" ? "Steer" : "Queue";
|
|
945
942
|
const parts = [`Enter: ${enterAction}`];
|
|
946
943
|
const queueKey = this.#getMessageQueueShortcut();
|
|
947
|
-
if (queueKey) parts.push(`${formatShortcutForPlaceholder(queueKey)}:
|
|
948
|
-
return `${DEFAULT_COMPOSER_PLACEHOLDER} ${parts.join(" · ")}`;
|
|
944
|
+
if (queueKey) parts.push(`${formatShortcutForPlaceholder(queueKey)}: Queue`);
|
|
945
|
+
return `${DEFAULT_COMPOSER_PLACEHOLDER} · ${parts.join(" · ")}`;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
#getWelcomeReservedRows(width: number): number {
|
|
949
|
+
const transientRows = [
|
|
950
|
+
this.chatContainer,
|
|
951
|
+
this.pendingMessagesContainer,
|
|
952
|
+
this.statusContainer,
|
|
953
|
+
this.todoContainer,
|
|
954
|
+
this.btwContainer,
|
|
955
|
+
].reduce((rows, container) => rows + this.#renderShortContainerRowsForWelcomeReservation(width, container), 0);
|
|
956
|
+
|
|
957
|
+
const pinnedRows = [
|
|
958
|
+
this.statusLine,
|
|
959
|
+
this.hookWidgetContainerAbove,
|
|
960
|
+
this.editorContainer,
|
|
961
|
+
this.hookWidgetContainerBelow,
|
|
962
|
+
].reduce((rows, component) => rows + component.render(width).length, 0);
|
|
963
|
+
|
|
964
|
+
return transientRows + pinnedRows;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
#renderShortContainerRowsForWelcomeReservation(width: number, container: Container): number {
|
|
968
|
+
if (container.children.length === 0 || container.children.length > WELCOME_RESERVED_CONTAINER_CHILD_LIMIT) {
|
|
969
|
+
return 0;
|
|
970
|
+
}
|
|
971
|
+
return container.render(width).length;
|
|
949
972
|
}
|
|
950
973
|
|
|
951
974
|
updateEditorChrome(): void {
|
|
@@ -1352,6 +1375,12 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1352
1375
|
return;
|
|
1353
1376
|
}
|
|
1354
1377
|
|
|
1378
|
+
// Leaving plan mode invalidates the plan-mode steering and standing
|
|
1379
|
+
// resolve handler that the in-flight turn may still be acting on. Abort
|
|
1380
|
+
// before clearing that state so the model cannot continue from stale
|
|
1381
|
+
// plan-mode context and retry `resolve` against a retired handler.
|
|
1382
|
+
await this.session.abort({ timeoutMs: INTERACTIVE_ABORT_CLEANUP_TIMEOUT_MS });
|
|
1383
|
+
|
|
1355
1384
|
const previousTools = this.#planModePreviousTools;
|
|
1356
1385
|
if (previousTools && previousTools.length > 0) {
|
|
1357
1386
|
await this.session.setActiveToolsByName(previousTools);
|
|
@@ -1689,6 +1718,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1689
1718
|
planContent,
|
|
1690
1719
|
finalPlanFilePath: options.finalPlanFilePath,
|
|
1691
1720
|
contextPreserved: options.preserveContext === true,
|
|
1721
|
+
tools: this.session.getActiveToolNames(),
|
|
1692
1722
|
});
|
|
1693
1723
|
await this.session.prompt(planModePrompt, { synthetic: true });
|
|
1694
1724
|
}
|
|
@@ -2180,6 +2210,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2180
2210
|
message => renderWorkingMessage(message, this.#getWorkingMessageAccent()),
|
|
2181
2211
|
this.#defaultWorkingMessage,
|
|
2182
2212
|
getSymbolTheme().spinnerFrames,
|
|
2213
|
+
{ timeDependentColor: true },
|
|
2183
2214
|
);
|
|
2184
2215
|
this.statusContainer.addChild(this.loadingAnimation);
|
|
2185
2216
|
}
|
|
@@ -2337,6 +2368,11 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2337
2368
|
return this.#commandController.handleClearCommand();
|
|
2338
2369
|
}
|
|
2339
2370
|
|
|
2371
|
+
handleContextClearCommand(): Promise<void> {
|
|
2372
|
+
this.#prepareSessionSwitch();
|
|
2373
|
+
return this.#commandController.handleContextClearCommand();
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2340
2376
|
handleDropCommand(): Promise<void> {
|
|
2341
2377
|
this.#prepareSessionSwitch();
|
|
2342
2378
|
return this.#commandController.handleDropCommand();
|
|
@@ -2526,6 +2562,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2526
2562
|
this.#selectorController.showModelSelector(options);
|
|
2527
2563
|
}
|
|
2528
2564
|
|
|
2565
|
+
showEffortSelector(): void {
|
|
2566
|
+
this.#selectorController.showEffortSelector();
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2529
2569
|
showProviderOnboarding(): void {
|
|
2530
2570
|
this.#selectorController.showProviderOnboarding();
|
|
2531
2571
|
}
|
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
|
}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
type AutocompleteItem,
|
|
3
|
+
type AutocompleteProvider,
|
|
4
|
+
CombinedAutocompleteProvider,
|
|
5
|
+
extractSlashCommandTokenPrefix,
|
|
6
|
+
getKeybindings,
|
|
7
|
+
getSlashCommandMatchRank,
|
|
8
|
+
type SlashCommand,
|
|
9
|
+
} from "@sayknow-cli/tui";
|
|
3
10
|
import type { KeybindingsManager } from "../config/keybindings";
|
|
4
11
|
import { formatKeyHints } from "../config/keybindings";
|
|
5
12
|
import { isSettingsInitialized, settings } from "../config/settings";
|
|
@@ -147,6 +154,10 @@ function sortSlashCommandSuggestions(
|
|
|
147
154
|
return { ...suggestions, items };
|
|
148
155
|
}
|
|
149
156
|
|
|
157
|
+
function isRootPathSuggestionResult(suggestions: { items: AutocompleteItem[]; prefix: string } | null): boolean {
|
|
158
|
+
return suggestions?.prefix.startsWith("/") === true && suggestions.items.some(item => item.value.startsWith("/"));
|
|
159
|
+
}
|
|
160
|
+
|
|
150
161
|
function withoutSkillCommandSuggestions(
|
|
151
162
|
suggestions: { items: AutocompleteItem[]; prefix: string } | null,
|
|
152
163
|
): { items: AutocompleteItem[]; prefix: string } | null {
|
|
@@ -168,18 +179,9 @@ function getPromptActionPrefix(textBeforeCursor: string): string | null {
|
|
|
168
179
|
}
|
|
169
180
|
|
|
170
181
|
function getSlashTokenPrefix(textBeforeCursor: string): string | null {
|
|
171
|
-
|
|
172
|
-
if (slashIndex === -1) return null;
|
|
173
|
-
const beforeSlash = textBeforeCursor.slice(0, slashIndex);
|
|
174
|
-
if (beforeSlash && !/\s$/.test(beforeSlash)) return null;
|
|
175
|
-
const token = textBeforeCursor.slice(slashIndex);
|
|
176
|
-
if (/[\s]/.test(token)) return null;
|
|
177
|
-
return token;
|
|
182
|
+
return extractSlashCommandTokenPrefix(textBeforeCursor);
|
|
178
183
|
}
|
|
179
184
|
|
|
180
|
-
function isLeadingSlashToken(textBeforeCursor: string, slashPrefix: string): boolean {
|
|
181
|
-
return textBeforeCursor.trimStart() === slashPrefix;
|
|
182
|
-
}
|
|
183
185
|
export class PromptActionAutocompleteProvider implements AutocompleteProvider {
|
|
184
186
|
#baseProvider: CombinedAutocompleteProvider;
|
|
185
187
|
#actions: PromptActionDefinition[];
|
|
@@ -226,14 +228,13 @@ export class PromptActionAutocompleteProvider implements AutocompleteProvider {
|
|
|
226
228
|
|
|
227
229
|
const slashPrefix = getSlashTokenPrefix(textBeforeCursor);
|
|
228
230
|
if (slashPrefix) {
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const skillCommandSuggestions =
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
: null;
|
|
231
|
+
const baseSuggestions = withoutSkillCommandSuggestions(
|
|
232
|
+
await this.#baseProvider.getSuggestions(lines, cursorLine, cursorCol),
|
|
233
|
+
);
|
|
234
|
+
if (isRootPathSuggestionResult(baseSuggestions)) return baseSuggestions;
|
|
235
|
+
const skillCommandSuggestions = this.#getSkillCommandSuggestions(textBeforeCursor, {
|
|
236
|
+
includeEmpty: false,
|
|
237
|
+
});
|
|
237
238
|
return sortSlashCommandSuggestions(
|
|
238
239
|
mergeAutocompleteSuggestions(baseSuggestions, skillCommandSuggestions),
|
|
239
240
|
this.#commands,
|
|
@@ -22,8 +22,13 @@ import type { AgentSession } from "../../session/agent-session";
|
|
|
22
22
|
import { workflowGatePath } from "../../skc-runtime/session-layout";
|
|
23
23
|
import { initializeExtensions } from "../runtime-init";
|
|
24
24
|
import { dispatchRpcCommand } from "../shared/agent-wire/command-dispatch";
|
|
25
|
-
import {
|
|
26
|
-
|
|
25
|
+
import {
|
|
26
|
+
AgentWireCompactEventEncoder,
|
|
27
|
+
AgentWireFrameSequencer,
|
|
28
|
+
toAgentWireCompactEventFrame,
|
|
29
|
+
toAgentWireEventFrame,
|
|
30
|
+
} from "../shared/agent-wire/event-envelope";
|
|
31
|
+
import { rpcError as error, rpcSuccess } from "../shared/agent-wire/responses";
|
|
27
32
|
import { registerRpcSession, unregisterRpcSession } from "../shared/agent-wire/session-registry";
|
|
28
33
|
import { defaultAuditPath, UnattendedAuditLog } from "../shared/agent-wire/unattended-audit";
|
|
29
34
|
import { modelSupportsTokenCostMetrics, UnattendedSessionControlPlane } from "../shared/agent-wire/unattended-session";
|
|
@@ -168,15 +173,9 @@ function auditOutcomeFor(event: string): "accepted" | "rejected" | "denied" | "e
|
|
|
168
173
|
return "info";
|
|
169
174
|
}
|
|
170
175
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
`RPC --listen refused: a live server is already listening on ${socketPath}. ` +
|
|
175
|
-
"Stop it first or choose a different --listen path.",
|
|
176
|
-
);
|
|
177
|
-
this.name = "RpcListenRefusedError";
|
|
178
|
-
}
|
|
179
|
-
}
|
|
176
|
+
// Thrown by prepareRpcSocketPath when the --listen path has a live owner;
|
|
177
|
+
// re-exported here because main.ts catches it at the RPC launch boundary.
|
|
178
|
+
export { RpcListenRefusedError } from "./rpc-socket-security";
|
|
180
179
|
|
|
181
180
|
/**
|
|
182
181
|
* Probe whether a unix-domain socket path has a live server accepting
|
|
@@ -297,6 +296,7 @@ export async function runRpcMode(
|
|
|
297
296
|
output({ type: "ready" });
|
|
298
297
|
}
|
|
299
298
|
const emitRpcTitles = shouldEmitRpcTitles();
|
|
299
|
+
const rpcCapabilities = { compactMessageUpdate: false };
|
|
300
300
|
const decodeError = (err: unknown): string => (err instanceof Error ? err.message : String(err));
|
|
301
301
|
|
|
302
302
|
const pendingExtensionRequests = new Map<string, PendingExtensionRequest>();
|
|
@@ -636,8 +636,13 @@ export async function runRpcMode(
|
|
|
636
636
|
// Output all agent events as canonical agent-wire `event` frames (docs/rpc.md):
|
|
637
637
|
// { type:"event", protocol_version, session_id, seq, frame_id, payload:{ event_type, event } }.
|
|
638
638
|
const eventSequencer = new AgentWireFrameSequencer(session.sessionId);
|
|
639
|
+
const compactEventEncoder = new AgentWireCompactEventEncoder(eventSequencer);
|
|
639
640
|
session.subscribe(event => {
|
|
640
|
-
output(
|
|
641
|
+
output(
|
|
642
|
+
rpcCapabilities.compactMessageUpdate
|
|
643
|
+
? toAgentWireCompactEventFrame(event, compactEventEncoder)
|
|
644
|
+
: toAgentWireEventFrame(event, eventSequencer),
|
|
645
|
+
);
|
|
641
646
|
});
|
|
642
647
|
|
|
643
648
|
// Handle a single command through the shared agent-wire dispatcher so RPC
|
|
@@ -710,6 +715,21 @@ export async function runRpcMode(
|
|
|
710
715
|
hostUriBridge.handleResult(parsed);
|
|
711
716
|
return;
|
|
712
717
|
}
|
|
718
|
+
if ((parsed as RpcCommand).type === "set_capabilities") {
|
|
719
|
+
// RPC has no startup handshake; clients negotiate optional event-shape capabilities
|
|
720
|
+
// with this explicit command. Defaults remain legacy/full-frame until accepted here.
|
|
721
|
+
const command = parsed as Extract<RpcCommand, { type: "set_capabilities" }>;
|
|
722
|
+
const requested = Array.isArray(command.capabilities) ? command.capabilities : [];
|
|
723
|
+
const acceptedCapabilities = requested.filter(capability => capability === "compact_message_update");
|
|
724
|
+
rpcCapabilities.compactMessageUpdate = acceptedCapabilities.includes("compact_message_update");
|
|
725
|
+
output(
|
|
726
|
+
rpcSuccess(command.id, "set_capabilities", {
|
|
727
|
+
acceptedCapabilities,
|
|
728
|
+
unsupported: requested.filter(capability => capability !== "compact_message_update"),
|
|
729
|
+
}),
|
|
730
|
+
);
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
713
733
|
// Ordered commands run through a serial chain to preserve causal order; the
|
|
714
734
|
// reader never blocks, so cancellation commands stay responsive even while a
|
|
715
735
|
// long command is in flight (issue 13).
|
|
@@ -9,6 +9,18 @@ export class RpcSocketSecurityError extends Error {
|
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
// The launch boundary in main.ts catches exactly this class to turn a
|
|
13
|
+
// duplicate --listen into a clean stderr diagnostic + non-zero exit (issue 19).
|
|
14
|
+
export class RpcListenRefusedError extends Error {
|
|
15
|
+
constructor(socketPath: string) {
|
|
16
|
+
super(
|
|
17
|
+
`RPC --listen refused: a live server is already listening on ${socketPath}. ` +
|
|
18
|
+
"Stop it first or choose a different --listen path.",
|
|
19
|
+
);
|
|
20
|
+
this.name = "RpcListenRefusedError";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
12
24
|
const unsafeBits = 0o077;
|
|
13
25
|
|
|
14
26
|
function currentUid(): number | undefined {
|
|
@@ -53,7 +65,7 @@ export async function prepareRpcSocketPath(socketPath: string): Promise<void> {
|
|
|
53
65
|
if (!existing.isSocket()) throw new RpcSocketSecurityError(`RPC socket path is not a socket: ${socketPath}`);
|
|
54
66
|
assertPrivateMode(existing.mode, socketPath);
|
|
55
67
|
if (await probeUnixSocketAlive(socketPath)) {
|
|
56
|
-
throw new
|
|
68
|
+
throw new RpcListenRefusedError(socketPath);
|
|
57
69
|
}
|
|
58
70
|
await fs.unlink(socketPath);
|
|
59
71
|
}
|
|
@@ -13,6 +13,7 @@ import type { SessionStats } from "../../session/agent-session";
|
|
|
13
13
|
import type { TodoPhase } from "../../tools/todo-write";
|
|
14
14
|
|
|
15
15
|
export type RpcGetStateInclude = "tools" | "dumpTools" | "systemPrompt";
|
|
16
|
+
export type RpcCapability = "compact_message_update";
|
|
16
17
|
|
|
17
18
|
// ============================================================================
|
|
18
19
|
// RPC Commands (stdin)
|
|
@@ -33,6 +34,7 @@ export type RpcCommand =
|
|
|
33
34
|
| { id?: string; type: "set_host_tools"; tools: RpcHostToolDefinition[] }
|
|
34
35
|
| { id?: string; type: "set_host_uri_schemes"; schemes: RpcHostUriSchemeDefinition[] }
|
|
35
36
|
| { id?: string; type: "get_pending_workflow_gates" }
|
|
37
|
+
| { id?: string; type: "set_capabilities"; capabilities: RpcCapability[] }
|
|
36
38
|
|
|
37
39
|
// Model
|
|
38
40
|
| { id?: string; type: "set_model"; provider: string; modelId: string }
|
|
@@ -140,6 +142,13 @@ export type RpcResponse =
|
|
|
140
142
|
success: true;
|
|
141
143
|
data: { gates: RpcWorkflowGate[] };
|
|
142
144
|
}
|
|
145
|
+
| {
|
|
146
|
+
id?: string;
|
|
147
|
+
type: "response";
|
|
148
|
+
command: "set_capabilities";
|
|
149
|
+
success: true;
|
|
150
|
+
data: { acceptedCapabilities: RpcCapability[]; unsupported: string[] };
|
|
151
|
+
}
|
|
143
152
|
|
|
144
153
|
// Model
|
|
145
154
|
| {
|
|
@@ -130,6 +130,10 @@ export function isRpcCommand(value: unknown): value is RpcCommand {
|
|
|
130
130
|
return Array.isArray(value.tools) && value.tools.every(hostToolDefinition);
|
|
131
131
|
case "set_host_uri_schemes":
|
|
132
132
|
return Array.isArray(value.schemes) && value.schemes.every(hostUriScheme);
|
|
133
|
+
case "set_capabilities":
|
|
134
|
+
return (
|
|
135
|
+
Array.isArray(value.capabilities) && value.capabilities.every(capability => typeof capability === "string")
|
|
136
|
+
);
|
|
133
137
|
case "set_model":
|
|
134
138
|
return stringField(value, "provider") && stringField(value, "modelId");
|
|
135
139
|
case "set_thinking_level":
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
* an `AgentSessionEvent` variant fails typecheck until it is registered, and so
|
|
14
14
|
* conformance tests can assert fixture coverage equals the registry exactly.
|
|
15
15
|
*/
|
|
16
|
+
|
|
17
|
+
import type { AssistantMessageEvent } from "@sayknow-cli/ai";
|
|
16
18
|
import type { AgentSessionEvent } from "../../../session/agent-session";
|
|
17
19
|
|
|
18
20
|
/** Wire protocol version. Bump on breaking envelope/semantic changes. */
|
|
@@ -68,6 +70,25 @@ export interface AgentWireEventPayload {
|
|
|
68
70
|
event_type: AgentWireEventType;
|
|
69
71
|
event: AgentSessionEvent;
|
|
70
72
|
}
|
|
73
|
+
export type AgentWireCompactAssistantMessageEvent = AssistantMessageEvent extends infer TEvent
|
|
74
|
+
? TEvent extends { type: string }
|
|
75
|
+
? Omit<TEvent, "partial" | "message" | "error">
|
|
76
|
+
: never
|
|
77
|
+
: never;
|
|
78
|
+
|
|
79
|
+
export interface AgentWireCompactMessageUpdatePayload {
|
|
80
|
+
event_type: "message_update";
|
|
81
|
+
/** Compact RPC opt-in payload: delta-only update plus minimal routing metadata. */
|
|
82
|
+
compact: true;
|
|
83
|
+
message_id: string | null;
|
|
84
|
+
content_index: number | null;
|
|
85
|
+
assistantMessageEvent: AgentWireCompactAssistantMessageEvent;
|
|
86
|
+
/** Full message checkpoint, snapshotted synchronously when this frame is enqueued. */
|
|
87
|
+
checkpoint_message?: Extract<AgentSessionEvent, { type: "message_update" }>["message"];
|
|
88
|
+
checkpoint_reason?: "periodic";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export type AgentWireEventFramePayload = AgentWireEventPayload | AgentWireCompactMessageUpdatePayload;
|
|
71
92
|
|
|
72
93
|
/**
|
|
73
94
|
* Bounded observed-signal vocabulary surfaced to owner control planes. Mirrors
|
|
@@ -146,3 +167,4 @@ export interface AgentWireFrameEnvelope<TType extends AgentWireFrameType = Agent
|
|
|
146
167
|
|
|
147
168
|
/** An `AgentSessionEvent` serialized into a versioned wire frame. */
|
|
148
169
|
export type AgentWireEventFrame = AgentWireFrameEnvelope<"event", AgentWireEventPayload>;
|
|
170
|
+
export type AgentWireCompactEventFrame = AgentWireFrameEnvelope<"event", AgentWireEventFramePayload>;
|
|
@@ -12,6 +12,9 @@ import { randomUUID } from "node:crypto";
|
|
|
12
12
|
import type { AgentSessionEvent } from "../../../session/agent-session";
|
|
13
13
|
import {
|
|
14
14
|
AGENT_WIRE_PROTOCOL_VERSION,
|
|
15
|
+
type AgentWireCompactAssistantMessageEvent,
|
|
16
|
+
type AgentWireCompactEventFrame,
|
|
17
|
+
type AgentWireCompactMessageUpdatePayload,
|
|
15
18
|
type AgentWireEventFrame,
|
|
16
19
|
type AgentWireEventPayload,
|
|
17
20
|
type AgentWireEventType,
|
|
@@ -19,6 +22,26 @@ import {
|
|
|
19
22
|
type AgentWireFrameType,
|
|
20
23
|
} from "./event-contract";
|
|
21
24
|
|
|
25
|
+
export const AGENT_WIRE_COMPACT_MESSAGE_UPDATE_CHECKPOINT_INTERVAL = 32;
|
|
26
|
+
|
|
27
|
+
function recordObject(value: unknown): Record<string, unknown> | undefined {
|
|
28
|
+
return value && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function messageIdOf(message: unknown): string | null {
|
|
32
|
+
const id = recordObject(message)?.id;
|
|
33
|
+
return typeof id === "string" ? id : null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function contentIndexOf(assistantMessageEvent: unknown): number | null {
|
|
37
|
+
const contentIndex = recordObject(assistantMessageEvent)?.contentIndex;
|
|
38
|
+
return typeof contentIndex === "number" && Number.isInteger(contentIndex) ? contentIndex : null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function snapshotMessage<T>(message: T): T {
|
|
42
|
+
return structuredClone(message);
|
|
43
|
+
}
|
|
44
|
+
|
|
22
45
|
function assertNever(value: never): never {
|
|
23
46
|
throw new Error(`Unhandled AgentSessionEvent variant: ${JSON.stringify(value)}`);
|
|
24
47
|
}
|
|
@@ -123,6 +146,65 @@ export function toAgentWireEventFrame(
|
|
|
123
146
|
export function toAgentWireEventPayload(event: AgentSessionEvent): AgentWireEventPayload {
|
|
124
147
|
return { event_type: agentSessionEventType(event), event };
|
|
125
148
|
}
|
|
149
|
+
export function sanitizeAssistantMessageEventForCompact(
|
|
150
|
+
assistantMessageEvent: Extract<AgentSessionEvent, { type: "message_update" }>["assistantMessageEvent"],
|
|
151
|
+
): AgentWireCompactAssistantMessageEvent {
|
|
152
|
+
const {
|
|
153
|
+
partial: _partial,
|
|
154
|
+
message: _message,
|
|
155
|
+
error: _error,
|
|
156
|
+
...deltaOnly
|
|
157
|
+
} = assistantMessageEvent as Record<string, unknown>;
|
|
158
|
+
return deltaOnly as AgentWireCompactAssistantMessageEvent;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface AgentWireCompactMessageUpdateOptions {
|
|
162
|
+
checkpointInterval?: number;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Stateful compact event serializer for opt-in RPC clients.
|
|
167
|
+
*
|
|
168
|
+
* Only `message_update` changes shape: it carries the immutable provider delta
|
|
169
|
+
* plus message/content identifiers, with periodic full-message checkpoints that
|
|
170
|
+
* are cloned synchronously before the caller returns to the event loop. All other
|
|
171
|
+
* event variants remain canonical full event frames so legacy bridge/RPC behavior
|
|
172
|
+
* and terminal `message_end` reconstruction are unchanged.
|
|
173
|
+
*/
|
|
174
|
+
export class AgentWireCompactEventEncoder {
|
|
175
|
+
readonly #sequencer: AgentWireFrameSequencer;
|
|
176
|
+
readonly #checkpointInterval: number;
|
|
177
|
+
#messageUpdateCount = 0;
|
|
178
|
+
|
|
179
|
+
constructor(sequencer: AgentWireFrameSequencer, options: AgentWireCompactMessageUpdateOptions = {}) {
|
|
180
|
+
this.#sequencer = sequencer;
|
|
181
|
+
this.#checkpointInterval = options.checkpointInterval ?? AGENT_WIRE_COMPACT_MESSAGE_UPDATE_CHECKPOINT_INTERVAL;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
frame(event: AgentSessionEvent): AgentWireCompactEventFrame {
|
|
185
|
+
if (event.type !== "message_update") return toAgentWireEventFrame(event, this.#sequencer);
|
|
186
|
+
this.#messageUpdateCount += 1;
|
|
187
|
+
const payload: AgentWireCompactMessageUpdatePayload = {
|
|
188
|
+
event_type: "message_update",
|
|
189
|
+
compact: true,
|
|
190
|
+
message_id: messageIdOf(event.message),
|
|
191
|
+
content_index: contentIndexOf(event.assistantMessageEvent),
|
|
192
|
+
assistantMessageEvent: sanitizeAssistantMessageEventForCompact(event.assistantMessageEvent),
|
|
193
|
+
};
|
|
194
|
+
if (this.#checkpointInterval > 0 && this.#messageUpdateCount % this.#checkpointInterval === 0) {
|
|
195
|
+
payload.checkpoint_message = snapshotMessage(event.message);
|
|
196
|
+
payload.checkpoint_reason = "periodic";
|
|
197
|
+
}
|
|
198
|
+
return this.#sequencer.next("event", payload);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function toAgentWireCompactEventFrame(
|
|
203
|
+
event: AgentSessionEvent,
|
|
204
|
+
encoder: AgentWireCompactEventEncoder,
|
|
205
|
+
): AgentWireCompactEventFrame {
|
|
206
|
+
return encoder.frame(event);
|
|
207
|
+
}
|
|
126
208
|
|
|
127
209
|
/** Back-compat alias for {@link toAgentWireEventFrame}. */
|
|
128
210
|
export const toBridgeEventFrame = toAgentWireEventFrame;
|
|
@@ -47,6 +47,7 @@ const RPC_COMMAND_SCOPE_REGISTRY: Record<RpcCommandType, BridgeCommandScope> = {
|
|
|
47
47
|
set_host_tools: "host_tools",
|
|
48
48
|
set_host_uri_schemes: "host_uri",
|
|
49
49
|
get_pending_workflow_gates: "message:read",
|
|
50
|
+
set_capabilities: "control",
|
|
50
51
|
set_model: "model",
|
|
51
52
|
cycle_model: "model",
|
|
52
53
|
get_available_models: "model",
|
package/src/modes/types.ts
CHANGED
|
@@ -116,6 +116,7 @@ export interface InteractiveModeContext {
|
|
|
116
116
|
onInputCallback?: (input: SubmittedUserInput) => void;
|
|
117
117
|
optimisticUserMessageSignature: string | undefined;
|
|
118
118
|
locallySubmittedUserSignatures: Set<string>;
|
|
119
|
+
optimisticInjectedSignatures: Map<string, number>;
|
|
119
120
|
lastSigintTime: number;
|
|
120
121
|
lastEscapeTime: number;
|
|
121
122
|
lastComposerClearEscapeTime: number;
|
|
@@ -216,6 +217,7 @@ export interface InteractiveModeContext {
|
|
|
216
217
|
handleDumpCommand(): void;
|
|
217
218
|
handleDebugTranscriptCommand(): Promise<void>;
|
|
218
219
|
handleClearCommand(): Promise<void>;
|
|
220
|
+
handleContextClearCommand(): Promise<void>;
|
|
219
221
|
handleDropCommand(): Promise<void>;
|
|
220
222
|
handleForkCommand(): Promise<void>;
|
|
221
223
|
handleBashCommand(command: string, excludeFromContext?: boolean): Promise<void>;
|
|
@@ -243,6 +245,7 @@ export interface InteractiveModeContext {
|
|
|
243
245
|
showExtensionsDashboard(): void;
|
|
244
246
|
showAgentsDashboard(): void;
|
|
245
247
|
showModelSelector(options?: { temporaryOnly?: boolean }): void;
|
|
248
|
+
showEffortSelector(): void;
|
|
246
249
|
showProviderOnboarding(): void;
|
|
247
250
|
showPluginSelector(mode?: "install" | "uninstall"): void;
|
|
248
251
|
showUserMessageSelector(): void;
|
|
@@ -23,6 +23,7 @@ export function buildHotkeysMarkdown(bindings: HotkeysMarkdownBindings): string
|
|
|
23
23
|
"|-----|--------|",
|
|
24
24
|
"| `Enter` | Send / queue while busy |",
|
|
25
25
|
`| \`${appKey(bindings, "app.message.queue")}\` | Queue message for next turn |`,
|
|
26
|
+
`| \`${appKey(bindings, "app.message.dequeue")}\` | Select queued message to edit |`,
|
|
26
27
|
"| `Shift+Enter` / `Ctrl+J` | New line |",
|
|
27
28
|
"| `Ctrl+W` / `Option+Backspace` | Delete word backwards |",
|
|
28
29
|
"| `Ctrl+U` | Delete to start of line |",
|