@sayknow-cli/coding-agent 0.3.12 → 0.3.15
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/autoresearch/tools/init-experiment.d.ts +1 -1
- package/dist/types/autoresearch/tools/log-experiment.d.ts +2 -2
- package/dist/types/cli/classify-install-target.d.ts +0 -10
- package/dist/types/cli/daemon-cli.d.ts +2 -0
- package/dist/types/cli/initial-message.d.ts +1 -1
- package/dist/types/cli/malloc-env-guard.d.ts +65 -0
- package/dist/types/cli/malloc-env-guard.test.d.ts +1 -0
- package/dist/types/cli/notify-cli.d.ts +3 -1
- package/dist/types/cli/session-picker.d.ts +4 -3
- package/dist/types/cli/update-cli.d.ts +22 -3
- package/dist/types/cli.d.ts +5 -0
- package/dist/types/commands/daemon.d.ts +6 -2
- package/dist/types/commands/launch.d.ts +1 -0
- package/dist/types/commands/notify.d.ts +6 -0
- package/dist/types/commands/web-search.d.ts +1 -1
- package/dist/types/commit/agentic/tools/propose-changelog.d.ts +4 -4
- package/dist/types/commit/agentic/tools/propose-commit.d.ts +9 -9
- package/dist/types/commit/agentic/tools/schemas.d.ts +9 -9
- package/dist/types/commit/agentic/tools/split-commit.d.ts +9 -9
- package/dist/types/commit/analysis/conventional.d.ts +1 -1
- package/dist/types/commit/analysis/summary.d.ts +1 -1
- package/dist/types/commit/changelog/generate.d.ts +3 -3
- package/dist/types/commit/changelog/index.d.ts +2 -2
- package/dist/types/commit/map-reduce/map-phase.d.ts +1 -1
- package/dist/types/commit/map-reduce/reduce-phase.d.ts +1 -1
- package/dist/types/commit/shared-llm.d.ts +9 -9
- package/dist/types/config/keybindings.d.ts +134 -129
- package/dist/types/config/model-profile-activation.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +35 -35
- package/dist/types/config/models-config-schema.d.ts +111 -111
- package/dist/types/config/settings-schema.d.ts +56 -16
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/coordinator-mcp/model-preset.d.ts +45 -0
- package/dist/types/coordinator-mcp/policy.d.ts +3 -0
- package/dist/types/coordinator-mcp/server.d.ts +79 -6
- package/dist/types/coordinator-mcp/session-reaper.d.ts +53 -0
- package/dist/types/daemon/control-types.d.ts +10 -0
- package/dist/types/daemon/operator-contract.d.ts +47 -0
- package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/models/catalog.d.ts +4 -0
- package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/shared/errors.d.ts +12 -12
- package/dist/types/discovery/helpers.d.ts +1 -1
- package/dist/types/discovery/substitute-plugin-root.d.ts +0 -4
- package/dist/types/edit/modes/patch.d.ts +2 -2
- package/dist/types/extensibility/extensions/types.d.ts +3 -1
- package/dist/types/goals/tools/goal-tool.d.ts +6 -6
- package/dist/types/i18n/messages/en.d.ts +8 -8
- package/dist/types/internal-urls/local-protocol.d.ts +13 -9
- package/dist/types/lsp/index.d.ts +9 -9
- package/dist/types/lsp/types.d.ts +9 -9
- package/dist/types/main.d.ts +66 -2
- package/dist/types/modes/acp/acp-agent.d.ts +19 -2
- package/dist/types/modes/acp/acp-mode.d.ts +12 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -1
- package/dist/types/modes/components/custom-editor.d.ts +2 -0
- package/dist/types/modes/components/hook-selector.d.ts +5 -0
- package/dist/types/modes/components/irc-sidebar.d.ts +32 -0
- package/dist/types/modes/components/pet-selector.d.ts +11 -0
- package/dist/types/modes/components/sayknow-pet-widget.d.ts +59 -0
- package/dist/types/modes/components/session-selector.d.ts +22 -9
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/components/status-line/types.d.ts +1 -1
- package/dist/types/modes/components/status-line.d.ts +0 -10
- package/dist/types/modes/components/tool-execution.d.ts +1 -0
- package/dist/types/modes/components/user-message.d.ts +2 -1
- package/dist/types/modes/controllers/event-controller.d.ts +4 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +24 -2
- package/dist/types/modes/irc-observation-ledger.d.ts +22 -0
- package/dist/types/modes/print-mode.d.ts +3 -4
- package/dist/types/modes/rpc/rpc-client.d.ts +7 -1
- package/dist/types/modes/rpc/rpc-mode.d.ts +2 -1
- package/dist/types/modes/rpc/rpc-types.d.ts +17 -1
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +8 -0
- package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +5 -0
- package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +45 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +42 -2
- package/dist/types/modes/theme/defaults/index.d.ts +568 -568
- package/dist/types/modes/types.d.ts +28 -2
- package/dist/types/modes/utils/abort-message.d.ts +1 -1
- package/dist/types/modes/utils/context-usage.d.ts +4 -19
- package/dist/types/modes/utils/irc-message.d.ts +38 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +13 -4
- package/dist/types/notifications/config.d.ts +34 -0
- package/dist/types/notifications/daemon-paths.d.ts +7 -0
- package/dist/types/notifications/index.d.ts +27 -0
- package/dist/types/notifications/lifecycle-control-runtime.d.ts +12 -4
- package/dist/types/notifications/notification-service.d.ts +144 -0
- package/dist/types/notifications/rate-limit-pool.d.ts +27 -9
- package/dist/types/notifications/telegram-daemon-control.d.ts +6 -0
- package/dist/types/notifications/telegram-daemon.d.ts +130 -3
- package/dist/types/notifications/telegram-reference.d.ts +19 -2
- package/dist/types/notifications/topic-registry.d.ts +24 -4
- package/dist/types/registry/agent-registry.d.ts +2 -0
- package/dist/types/rlm/index.d.ts +1 -1
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/secrets/obfuscator.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +40 -3
- package/dist/types/session/cache-economics.d.ts +79 -0
- package/dist/types/session/context-estimation.d.ts +31 -0
- package/dist/types/session/provider-safety-stop.d.ts +5 -0
- package/dist/types/session/session-manager.d.ts +124 -4
- package/dist/types/session/session-storage.d.ts +155 -13
- package/dist/types/setup/credential-auto-import.d.ts +2 -2
- package/dist/types/skc-runtime/session-state-sidecar.d.ts +34 -3
- package/dist/types/skc-runtime/state-schema.d.ts +19 -19
- package/dist/types/skc-runtime/tmux-common.d.ts +6 -0
- package/dist/types/skc-runtime/tmux-owner-isolation-cli.d.ts +5 -0
- package/dist/types/skc-runtime/tmux-owner-isolation.d.ts +309 -0
- package/dist/types/skc-runtime/tmux-sessions.d.ts +49 -17
- package/dist/types/task/agents.d.ts +2 -2
- package/dist/types/task/executor.d.ts +3 -0
- package/dist/types/task/receipt.d.ts +3 -1
- package/dist/types/task/types.d.ts +35 -27
- package/dist/types/tools/ask-answer-registry.d.ts +5 -0
- package/dist/types/tools/bash.d.ts +1 -1
- package/dist/types/tools/browser/tab-supervisor.d.ts +6 -0
- package/dist/types/tools/browser.d.ts +28 -28
- package/dist/types/tools/computer.d.ts +20 -20
- package/dist/types/tools/cron.d.ts +2 -2
- package/dist/types/tools/debug.d.ts +40 -40
- package/dist/types/tools/gh.d.ts +10 -10
- package/dist/types/tools/image-gen.d.ts +2 -2
- package/dist/types/tools/index.d.ts +58 -6
- package/dist/types/tools/monitor.d.ts +6 -6
- package/dist/types/tools/skill-discovery.d.ts +4 -4
- package/dist/types/tools/subagent.d.ts +12 -12
- package/dist/types/tools/telegram-send.d.ts +7 -0
- package/dist/types/tools/todo-write.d.ts +8 -8
- package/dist/types/utils/external-editor.d.ts +2 -0
- package/dist/types/web/insane/bridge.d.ts +9 -9
- package/dist/types/web/search/index.d.ts +4 -4
- package/package.json +8 -8
- package/scripts/g004-tmux-smoke.ts +196 -36
- package/src/cli/daemon-cli.ts +19 -29
- package/src/cli/malloc-env-guard.test.ts +104 -0
- package/src/cli/malloc-env-guard.ts +149 -0
- package/src/cli/notify-cli.ts +78 -14
- package/src/cli/session-picker.ts +23 -40
- package/src/cli/update-cli.ts +56 -37
- package/src/cli.ts +51 -10
- package/src/commands/daemon.ts +19 -5
- package/src/commands/harness.ts +515 -47
- package/src/commands/launch.ts +11 -3
- package/src/commands/notify.ts +5 -1
- package/src/commands/session.ts +6 -1
- package/src/config/keybindings.ts +5 -0
- package/src/config/model-profile-activation.ts +1 -1
- package/src/config/model-profiles.ts +14 -14
- package/src/config/settings-schema.ts +41 -1
- package/src/config/settings.ts +96 -45
- package/src/coordinator/contract.ts +1 -0
- package/src/coordinator-mcp/model-preset.ts +100 -0
- package/src/coordinator-mcp/policy.ts +30 -0
- package/src/coordinator-mcp/server.ts +2958 -430
- package/src/coordinator-mcp/session-reaper.ts +129 -0
- package/src/daemon/control-types.ts +11 -0
- package/src/daemon/operator-contract.ts +121 -0
- package/src/defaults/skc/extensions/grok-cli-vendor/src/models/catalog.ts +57 -17
- package/src/defaults/skc/extensions/grok-cli-vendor/src/payload/sanitize.ts +15 -2
- package/src/defaults/skc/extensions/grok-cli-vendor/src/provider/register.ts +5 -1
- package/src/extensibility/extensions/runner.ts +1 -1
- package/src/extensibility/extensions/types.ts +3 -1
- package/src/internal-urls/docs-index.generated.ts +10 -9
- package/src/internal-urls/local-protocol.ts +29 -10
- package/src/main.ts +301 -80
- package/src/modes/acp/acp-agent.ts +730 -100
- package/src/modes/acp/acp-mode.ts +29 -2
- package/src/modes/components/assistant-message.ts +32 -3
- package/src/modes/components/bash-execution.ts +43 -13
- package/src/modes/components/custom-editor.ts +5 -0
- package/src/modes/components/footer.ts +3 -3
- package/src/modes/components/hook-selector.ts +12 -0
- package/src/modes/components/irc-sidebar.ts +206 -0
- package/src/modes/components/pet-selector.ts +55 -0
- package/src/modes/components/sayknow-pet-widget.ts +360 -0
- package/src/modes/components/session-selector.ts +222 -221
- package/src/modes/components/settings-selector.ts +10 -0
- package/src/modes/components/status-line/segments.ts +14 -7
- package/src/modes/components/status-line/types.ts +1 -1
- package/src/modes/components/status-line.ts +4 -171
- package/src/modes/components/tool-execution.ts +47 -6
- package/src/modes/components/user-message.ts +26 -9
- package/src/modes/controllers/command-controller.ts +28 -2
- package/src/modes/controllers/event-controller.ts +159 -36
- package/src/modes/controllers/extension-ui-controller.ts +37 -18
- package/src/modes/controllers/input-controller.ts +8 -2
- package/src/modes/controllers/selector-controller.ts +62 -15
- package/src/modes/interactive-mode.ts +199 -7
- package/src/modes/irc-observation-ledger.ts +127 -0
- package/src/modes/print-mode.ts +228 -92
- package/src/modes/rpc/rpc-client.ts +58 -1
- package/src/modes/rpc/rpc-mode.ts +286 -85
- package/src/modes/rpc/rpc-types.ts +15 -1
- package/src/modes/shared/agent-wire/command-dispatch.ts +33 -1
- package/src/modes/shared/agent-wire/command-validation.ts +10 -0
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +45 -2
- package/src/modes/shared/agent-wire/scopes.ts +1 -0
- package/src/modes/shared/agent-wire/unattended-session.ts +288 -1
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +130 -20
- package/src/modes/types.ts +34 -2
- package/src/modes/utils/context-usage.ts +56 -92
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/irc-message.ts +138 -0
- package/src/modes/utils/ui-helpers.ts +216 -50
- package/src/notifications/config.ts +44 -0
- package/src/notifications/daemon-paths.ts +8 -0
- package/src/notifications/index.ts +535 -75
- package/src/notifications/lifecycle-control-runtime.ts +930 -77
- package/src/notifications/notification-service.ts +710 -0
- package/src/notifications/rate-limit-pool.ts +46 -12
- package/src/notifications/telegram-daemon-control.ts +27 -2
- package/src/notifications/telegram-daemon.ts +635 -94
- package/src/notifications/telegram-reference.ts +73 -13
- package/src/notifications/topic-registry.ts +86 -9
- package/src/prompts/system/irc-peer-roster.md +1 -0
- package/src/prompts/system/subagent-system-prompt.md +1 -0
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/prompts/tools/irc.md +1 -1
- package/src/registry/agent-registry.ts +3 -0
- package/src/sdk.ts +52 -1
- package/src/secrets/obfuscator.ts +4 -2
- package/src/session/agent-session.ts +670 -217
- package/src/session/cache-economics.ts +275 -0
- package/src/session/context-estimation.ts +91 -0
- package/src/session/provider-safety-stop.ts +9 -0
- package/src/session/session-dump-format.ts +6 -0
- package/src/session/session-manager.ts +809 -39
- package/src/session/session-storage.ts +582 -38
- package/src/skc-runtime/session-state-sidecar.ts +756 -135
- package/src/skc-runtime/tmux-common.ts +26 -7
- package/src/skc-runtime/tmux-gc.ts +52 -9
- package/src/skc-runtime/tmux-owner-isolation-cli.ts +185 -0
- package/src/skc-runtime/tmux-owner-isolation.ts +2060 -0
- package/src/skc-runtime/tmux-sessions.ts +871 -68
- package/src/slash-commands/builtin-registry.ts +153 -9
- package/src/slash-commands/helpers/context-report.ts +26 -4
- package/src/task/executor.ts +12 -3
- package/src/task/index.ts +8 -0
- package/src/task/receipt.ts +7 -2
- package/src/task/types.ts +26 -0
- package/src/tools/ask-answer-registry.ts +25 -0
- package/src/tools/browser/tab-supervisor.ts +63 -6
- package/src/tools/browser.ts +8 -3
- package/src/tools/index.ts +81 -6
- package/src/tools/sqlite-reader.ts +8 -4
- package/src/tui/output-block.ts +18 -6
- package/src/utils/external-editor.ts +6 -1
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* - Extension UI: Extension UI requests are emitted, client responds with extension_ui_response
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import { $pickenv, logger, readLines, Snowflake } from "@sayknow-cli/utils";
|
|
14
|
+
import { $pickenv, isKnownSinkPeerClosedError, logger, readLines, Snowflake } from "@sayknow-cli/utils";
|
|
15
15
|
import type {
|
|
16
16
|
ExtensionUIContext,
|
|
17
17
|
ExtensionUIDialogOptions,
|
|
@@ -64,7 +64,18 @@ type RpcOutput = (
|
|
|
64
64
|
| RpcHostUriRequest
|
|
65
65
|
| RpcHostUriCancelRequest
|
|
66
66
|
| object,
|
|
67
|
-
) => void
|
|
67
|
+
) => Promise<void>;
|
|
68
|
+
type StdioTransportState = "open" | "terminalizing" | "closed";
|
|
69
|
+
|
|
70
|
+
function sinkErrorCode(error: unknown): string | undefined {
|
|
71
|
+
if (error === null || (typeof error !== "object" && typeof error !== "function")) return undefined;
|
|
72
|
+
try {
|
|
73
|
+
const code = Reflect.get(error, "code");
|
|
74
|
+
return typeof code === "string" ? code : undefined;
|
|
75
|
+
} catch {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
68
79
|
|
|
69
80
|
function parseValueDialogResponse(
|
|
70
81
|
response: RpcExtensionUIResponse,
|
|
@@ -150,17 +161,24 @@ export function isFastLaneRpcCommand(type: RpcCommand["type"]): boolean {
|
|
|
150
161
|
export function createRpcCommandScheduler(
|
|
151
162
|
run: (command: RpcCommand) => Promise<void>,
|
|
152
163
|
track: (task: Promise<void>) => void,
|
|
153
|
-
): { dispatch: (command: RpcCommand) => void } {
|
|
164
|
+
): { dispatch: (command: RpcCommand) => void; close: () => void } {
|
|
154
165
|
let orderedChain: Promise<void> = Promise.resolve();
|
|
166
|
+
let accepting = true;
|
|
155
167
|
return {
|
|
156
168
|
dispatch(command: RpcCommand): void {
|
|
169
|
+
if (!accepting) return;
|
|
157
170
|
if (isFastLaneRpcCommand(command.type)) {
|
|
158
171
|
track(run(command));
|
|
159
172
|
return;
|
|
160
173
|
}
|
|
161
|
-
orderedChain = orderedChain.then(() =>
|
|
174
|
+
orderedChain = orderedChain.then(async () => {
|
|
175
|
+
if (accepting) await run(command);
|
|
176
|
+
});
|
|
162
177
|
track(orderedChain);
|
|
163
178
|
},
|
|
179
|
+
close(): void {
|
|
180
|
+
accepting = false;
|
|
181
|
+
},
|
|
164
182
|
};
|
|
165
183
|
}
|
|
166
184
|
|
|
@@ -283,18 +301,94 @@ export async function runRpcMode(
|
|
|
283
301
|
// may write there.
|
|
284
302
|
process.env.PI_NOTIFICATIONS = "off";
|
|
285
303
|
|
|
286
|
-
// Frames
|
|
287
|
-
//
|
|
288
|
-
|
|
289
|
-
|
|
304
|
+
// Frames route to stdout for stdio and to the active client for --listen. A
|
|
305
|
+
// socket server begins with no peer, so it must never leak frames to stdout.
|
|
306
|
+
const noopSink = (_line: string): void => {};
|
|
307
|
+
let stdioTransportState: StdioTransportState = options?.listen ? "closed" : "open";
|
|
308
|
+
let beginStdioTerminalization: (() => void) | undefined;
|
|
309
|
+
const stdioFatalFailure = Promise.withResolvers<never>();
|
|
310
|
+
void stdioFatalFailure.promise.catch(() => {});
|
|
311
|
+
let hasStdioWriteFailure = false;
|
|
312
|
+
let stdioWriteFailure: unknown;
|
|
313
|
+
let stdioPeerClosed = false;
|
|
314
|
+
const pendingStdioWrites = new Set<{
|
|
315
|
+
completion: Promise<void>;
|
|
316
|
+
settle: (failed: boolean, failure?: unknown) => void;
|
|
317
|
+
}>();
|
|
318
|
+
const settlePendingStdioWrites = (failed: boolean, failure?: unknown): void => {
|
|
319
|
+
for (const pending of [...pendingStdioWrites]) pending.settle(failed, failure);
|
|
290
320
|
};
|
|
291
|
-
const
|
|
292
|
-
|
|
321
|
+
const waitForStdioWrites = async (): Promise<void> => {
|
|
322
|
+
while (pendingStdioWrites.size > 0) {
|
|
323
|
+
await Promise.allSettled([...pendingStdioWrites].map(pending => pending.completion));
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
const handleStdioWriteFailure = (failure: unknown): boolean => {
|
|
327
|
+
const code = sinkErrorCode(failure);
|
|
328
|
+
if (
|
|
329
|
+
isKnownSinkPeerClosedError(failure) &&
|
|
330
|
+
(code === "EPIPE" || (stdioPeerClosed && code === "ERR_STREAM_DESTROYED"))
|
|
331
|
+
) {
|
|
332
|
+
stdioPeerClosed = true;
|
|
333
|
+
if (stdioTransportState === "open") stdioTransportState = "terminalizing";
|
|
334
|
+
settlePendingStdioWrites(false);
|
|
335
|
+
beginStdioTerminalization?.();
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
if (!hasStdioWriteFailure) {
|
|
339
|
+
hasStdioWriteFailure = true;
|
|
340
|
+
stdioWriteFailure = failure;
|
|
341
|
+
stdioTransportState = "terminalizing";
|
|
342
|
+
stdioFatalFailure.reject(failure);
|
|
343
|
+
settlePendingStdioWrites(true, failure);
|
|
344
|
+
beginStdioTerminalization?.();
|
|
345
|
+
}
|
|
346
|
+
return true;
|
|
347
|
+
};
|
|
348
|
+
const onStdioError = (failure: Error): void => {
|
|
349
|
+
handleStdioWriteFailure(failure);
|
|
350
|
+
};
|
|
351
|
+
if (!options?.listen) process.stdout.on("error", onStdioError);
|
|
352
|
+
const writeStdioFrame = (line: string): Promise<void> => {
|
|
353
|
+
if (stdioTransportState !== "open") return Promise.resolve();
|
|
354
|
+
const completion = Promise.withResolvers<void>();
|
|
355
|
+
let settled = false;
|
|
356
|
+
let pending: { completion: Promise<void>; settle: (failed: boolean, failure?: unknown) => void };
|
|
357
|
+
pending = {
|
|
358
|
+
completion: completion.promise,
|
|
359
|
+
settle: (failed, failure) => {
|
|
360
|
+
if (settled) return;
|
|
361
|
+
settled = true;
|
|
362
|
+
pendingStdioWrites.delete(pending);
|
|
363
|
+
if (failed) completion.reject(failure);
|
|
364
|
+
else completion.resolve();
|
|
365
|
+
},
|
|
366
|
+
};
|
|
367
|
+
pendingStdioWrites.add(pending);
|
|
368
|
+
try {
|
|
369
|
+
process.stdout.write(line, writeError => {
|
|
370
|
+
if (settled) return;
|
|
371
|
+
if (writeError) pending.settle(handleStdioWriteFailure(writeError), writeError);
|
|
372
|
+
else pending.settle(false);
|
|
373
|
+
});
|
|
374
|
+
} catch (failure) {
|
|
375
|
+
pending.settle(handleStdioWriteFailure(failure), failure);
|
|
376
|
+
}
|
|
377
|
+
return completion.promise;
|
|
378
|
+
};
|
|
379
|
+
let frameSink: (line: string) => void | Promise<void> = options?.listen ? noopSink : writeStdioFrame;
|
|
380
|
+
const output: RpcOutput = obj => {
|
|
381
|
+
if (!options?.listen && stdioTransportState !== "open") return Promise.resolve();
|
|
382
|
+
let emission: Promise<void>;
|
|
383
|
+
try {
|
|
384
|
+
emission = Promise.resolve(frameSink(`${JSON.stringify(obj)}\n`));
|
|
385
|
+
} catch (failure) {
|
|
386
|
+
emission = Promise.reject(failure);
|
|
387
|
+
if (!options?.listen) handleStdioWriteFailure(failure);
|
|
388
|
+
}
|
|
389
|
+
void emission.catch(() => {});
|
|
390
|
+
return emission;
|
|
293
391
|
};
|
|
294
|
-
// stdio announces readiness immediately; the UDS server announces it per client connection.
|
|
295
|
-
if (!options?.listen) {
|
|
296
|
-
output({ type: "ready" });
|
|
297
|
-
}
|
|
298
392
|
const emitRpcTitles = shouldEmitRpcTitles();
|
|
299
393
|
const rpcCapabilities = { compactMessageUpdate: false };
|
|
300
394
|
const decodeError = (err: unknown): string => (err instanceof Error ? err.message : String(err));
|
|
@@ -341,7 +435,9 @@ export async function runRpcMode(
|
|
|
341
435
|
const unattendedControlPlane = new UnattendedSessionControlPlane({
|
|
342
436
|
runId: session.sessionId,
|
|
343
437
|
sessionId: session.sessionId,
|
|
344
|
-
emitFrame: gate =>
|
|
438
|
+
emitFrame: gate => {
|
|
439
|
+
void output(gate);
|
|
440
|
+
},
|
|
345
441
|
store: gateStore,
|
|
346
442
|
audit: recordAudit,
|
|
347
443
|
providerSupportsTokenCostMetrics: modelSupportsTokenCostMetrics(session.model),
|
|
@@ -350,45 +446,71 @@ export async function runRpcMode(
|
|
|
350
446
|
return { tokens: stats.tokens.total, costUsd: stats.cost };
|
|
351
447
|
},
|
|
352
448
|
});
|
|
353
|
-
unattendedControlPlane
|
|
354
|
-
.
|
|
355
|
-
|
|
356
|
-
output(error(undefined, "workflow_gate_recover", err instanceof Error ? err.message : String(err))),
|
|
357
|
-
);
|
|
449
|
+
unattendedControlPlane.recover().catch(err => {
|
|
450
|
+
void output(error(undefined, "workflow_gate_recover", err instanceof Error ? err.message : String(err)));
|
|
451
|
+
});
|
|
358
452
|
session.setWorkflowGateEmitter(unattendedControlPlane);
|
|
359
453
|
|
|
360
454
|
// Shutdown request flag (wrapped in object to allow mutation with const)
|
|
361
455
|
const shutdownState = { requested: false };
|
|
362
|
-
let
|
|
456
|
+
let detachStdioInput: (() => void) | undefined;
|
|
457
|
+
let closeCommandScheduler: (() => void) | undefined;
|
|
458
|
+
let shutdownPromise: Promise<never> | undefined;
|
|
459
|
+
let shutdownExitCode = 0;
|
|
460
|
+
let suppressShutdownProtocolOutput = false;
|
|
461
|
+
let shutdownDispatchStopped = false;
|
|
363
462
|
// Tracks in-flight non-blocking command handlers so shutdown can drain them.
|
|
364
463
|
const inFlightCommands = new Set<Promise<void>>();
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
if (
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
hostToolBridge.rejectAllPending(`${reason} before host tool execution completed`);
|
|
378
|
-
hostUriBridge.clear(`${reason} before host URI request completed`);
|
|
379
|
-
try {
|
|
380
|
-
await session.sessionManager.ensureOnDisk();
|
|
381
|
-
} catch (err) {
|
|
382
|
-
output(error(undefined, "shutdown", decodeError(err)));
|
|
383
|
-
process.exit(1);
|
|
464
|
+
function shutdown(
|
|
465
|
+
exitCode: number,
|
|
466
|
+
reason: string,
|
|
467
|
+
suppressProtocolOutput = false,
|
|
468
|
+
stopDispatch = true,
|
|
469
|
+
): Promise<never> {
|
|
470
|
+
shutdownExitCode = Math.max(shutdownExitCode, exitCode);
|
|
471
|
+
suppressShutdownProtocolOutput ||= suppressProtocolOutput;
|
|
472
|
+
if (stopDispatch && !shutdownDispatchStopped) {
|
|
473
|
+
shutdownDispatchStopped = true;
|
|
474
|
+
closeCommandScheduler?.();
|
|
475
|
+
detachStdioInput?.();
|
|
384
476
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
477
|
+
if (shutdownPromise) return shutdownPromise;
|
|
478
|
+
shutdownPromise = (async () => {
|
|
479
|
+
// Let in-flight non-blocking commands (bash/compact/handoff) finish and emit
|
|
480
|
+
// their responses before teardown, bounded so a never-resolving login cannot
|
|
481
|
+
// wedge shutdown (issue 13).
|
|
482
|
+
if (inFlightCommands.size > 0) {
|
|
483
|
+
await Promise.race([Promise.allSettled([...inFlightCommands]), Bun.sleep(5000)]);
|
|
484
|
+
}
|
|
485
|
+
if (!options?.listen) await waitForStdioWrites();
|
|
486
|
+
await unregisterRpcSession(session.sessionId).catch(() => {});
|
|
487
|
+
hostToolBridge.rejectAllPending(`${reason} before host tool execution completed`);
|
|
488
|
+
hostUriBridge.clear(`${reason} before host URI request completed`);
|
|
489
|
+
let cleanupFailure: unknown;
|
|
490
|
+
try {
|
|
491
|
+
await session.sessionManager.ensureOnDisk();
|
|
492
|
+
} catch (failure) {
|
|
493
|
+
cleanupFailure = failure;
|
|
494
|
+
}
|
|
495
|
+
try {
|
|
496
|
+
await session.dispose();
|
|
497
|
+
} catch (failure) {
|
|
498
|
+
if (cleanupFailure === undefined) cleanupFailure = failure;
|
|
499
|
+
}
|
|
500
|
+
if (cleanupFailure !== undefined && !suppressShutdownProtocolOutput) {
|
|
501
|
+
await output(error(undefined, "shutdown", decodeError(cleanupFailure))).catch(() => {});
|
|
502
|
+
}
|
|
503
|
+
if (!options?.listen) {
|
|
504
|
+
await waitForStdioWrites();
|
|
505
|
+
process.stdout.removeListener("error", onStdioError);
|
|
506
|
+
stdioTransportState = "closed";
|
|
507
|
+
}
|
|
508
|
+
const terminalFailure = hasStdioWriteFailure ? stdioWriteFailure : cleanupFailure;
|
|
509
|
+
const finalExitCode = terminalFailure !== undefined || hasStdioWriteFailure ? 1 : shutdownExitCode;
|
|
510
|
+
process.exit(finalExitCode);
|
|
511
|
+
return await new Promise<never>(() => {});
|
|
512
|
+
})();
|
|
513
|
+
return shutdownPromise;
|
|
392
514
|
}
|
|
393
515
|
|
|
394
516
|
/**
|
|
@@ -397,7 +519,7 @@ export async function runRpcMode(
|
|
|
397
519
|
class RpcExtensionUIContext implements ExtensionUIContext {
|
|
398
520
|
constructor(
|
|
399
521
|
private pendingRequests: Map<string, PendingExtensionRequest>,
|
|
400
|
-
private output:
|
|
522
|
+
private output: RpcOutput,
|
|
401
523
|
) {}
|
|
402
524
|
|
|
403
525
|
/** Helper for dialog methods with signal/timeout support */
|
|
@@ -665,17 +787,38 @@ export async function runRpcMode(
|
|
|
665
787
|
// fast-lane command reach a long-running `bash`/`compact`/`handoff`/`login`
|
|
666
788
|
// instead of being head-of-line-blocked behind it (issue 13).
|
|
667
789
|
const runCommand = async (command: RpcCommand): Promise<void> => {
|
|
790
|
+
let response: RpcResponse;
|
|
668
791
|
try {
|
|
669
|
-
|
|
792
|
+
response = await handleCommand(command);
|
|
670
793
|
} catch (err) {
|
|
671
|
-
|
|
794
|
+
response = error(command.id, command.type, decodeError(err));
|
|
672
795
|
}
|
|
796
|
+
await output(response);
|
|
673
797
|
};
|
|
674
798
|
const trackCommand = (task: Promise<void>): void => {
|
|
675
799
|
inFlightCommands.add(task);
|
|
676
|
-
void task.
|
|
800
|
+
void task.then(
|
|
801
|
+
() => inFlightCommands.delete(task),
|
|
802
|
+
() => inFlightCommands.delete(task),
|
|
803
|
+
);
|
|
677
804
|
};
|
|
678
|
-
const
|
|
805
|
+
const scheduler = createRpcCommandScheduler(runCommand, trackCommand);
|
|
806
|
+
const dispatchCommand = scheduler.dispatch;
|
|
807
|
+
closeCommandScheduler = scheduler.close;
|
|
808
|
+
let stdioTerminalizationStarted = false;
|
|
809
|
+
beginStdioTerminalization = () => {
|
|
810
|
+
if (options?.listen || stdioTerminalizationStarted) return;
|
|
811
|
+
stdioTerminalizationStarted = true;
|
|
812
|
+
closeCommandScheduler?.();
|
|
813
|
+
detachStdioInput?.();
|
|
814
|
+
void session.abort({ timeoutMs: 5000, cause: "internal", silent: true }).catch(() => {});
|
|
815
|
+
void shutdown(
|
|
816
|
+
hasStdioWriteFailure ? 1 : 0,
|
|
817
|
+
hasStdioWriteFailure ? "RPC stdout write failed" : "RPC stdout peer disconnected",
|
|
818
|
+
true,
|
|
819
|
+
).catch(() => {});
|
|
820
|
+
};
|
|
821
|
+
if (stdioTransportState !== "open") beginStdioTerminalization();
|
|
679
822
|
|
|
680
823
|
/**
|
|
681
824
|
* Check if shutdown was requested and perform shutdown if so.
|
|
@@ -690,17 +833,24 @@ export async function runRpcMode(
|
|
|
690
833
|
// persistent UDS server so both transports use the same command surface.
|
|
691
834
|
const inputDecoder = new TextDecoder("utf-8", { fatal: false });
|
|
692
835
|
async function handleInboundLine(text: string): Promise<void> {
|
|
836
|
+
if (!options?.listen && stdioTransportState !== "open") return;
|
|
693
837
|
let parsed: unknown;
|
|
838
|
+
let parseFailure: RpcResponse | undefined;
|
|
694
839
|
try {
|
|
695
840
|
parsed = JSON.parse(text);
|
|
696
841
|
} catch (err) {
|
|
697
|
-
|
|
842
|
+
parseFailure = error(undefined, "parse", `Failed to parse command: ${decodeError(err)}`);
|
|
843
|
+
}
|
|
844
|
+
if (parseFailure) {
|
|
845
|
+
output(parseFailure);
|
|
698
846
|
return;
|
|
699
847
|
}
|
|
848
|
+
|
|
849
|
+
let response: RpcResponse | undefined;
|
|
700
850
|
try {
|
|
701
851
|
if ((parsed as RpcExtensionUIResponse).type === "extension_ui_response") {
|
|
702
|
-
const
|
|
703
|
-
pendingExtensionRequests.get(
|
|
852
|
+
const extensionResponse = parsed as RpcExtensionUIResponse;
|
|
853
|
+
pendingExtensionRequests.get(extensionResponse.id)?.resolve(extensionResponse);
|
|
704
854
|
return;
|
|
705
855
|
}
|
|
706
856
|
if (isRpcHostToolResult(parsed)) {
|
|
@@ -722,22 +872,21 @@ export async function runRpcMode(
|
|
|
722
872
|
const requested = Array.isArray(command.capabilities) ? command.capabilities : [];
|
|
723
873
|
const acceptedCapabilities = requested.filter(capability => capability === "compact_message_update");
|
|
724
874
|
rpcCapabilities.compactMessageUpdate = acceptedCapabilities.includes("compact_message_update");
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
875
|
+
response = rpcSuccess(command.id, "set_capabilities", {
|
|
876
|
+
acceptedCapabilities,
|
|
877
|
+
unsupported: requested.filter(capability => capability !== "compact_message_update"),
|
|
878
|
+
});
|
|
879
|
+
} else {
|
|
880
|
+
// Ordered commands run through a serial chain to preserve causal order; the
|
|
881
|
+
// reader never blocks, so cancellation commands stay responsive even while a
|
|
882
|
+
// long command is in flight (issue 13).
|
|
883
|
+
dispatchCommand(parsed as RpcCommand);
|
|
884
|
+
await checkShutdownRequested();
|
|
732
885
|
}
|
|
733
|
-
// Ordered commands run through a serial chain to preserve causal order; the
|
|
734
|
-
// reader never blocks, so cancellation commands stay responsive even while a
|
|
735
|
-
// long command is in flight (issue 13).
|
|
736
|
-
dispatchCommand(parsed as RpcCommand);
|
|
737
|
-
await checkShutdownRequested();
|
|
738
886
|
} catch (err) {
|
|
739
|
-
|
|
887
|
+
response = error(undefined, "parse", `Failed to parse command: ${decodeError(err)}`);
|
|
740
888
|
}
|
|
889
|
+
if (response) output(response);
|
|
741
890
|
}
|
|
742
891
|
|
|
743
892
|
// Persistent UDS server (issue 09): keep the AgentSession alive across client
|
|
@@ -757,9 +906,24 @@ export async function runRpcMode(
|
|
|
757
906
|
endpoint: socketPath,
|
|
758
907
|
}).catch(() => {});
|
|
759
908
|
|
|
760
|
-
|
|
761
|
-
let currentSocket: object | undefined;
|
|
909
|
+
let currentSocket: Bun.Socket | undefined;
|
|
762
910
|
let buf = "";
|
|
911
|
+
const detachCurrentSocket = (socket: Bun.Socket): void => {
|
|
912
|
+
if (socket !== currentSocket) return;
|
|
913
|
+
currentSocket = undefined;
|
|
914
|
+
frameSink = noopSink;
|
|
915
|
+
};
|
|
916
|
+
const socketIsLocallyTerminal = (socket: Bun.Socket): boolean => socket.readyState <= 0;
|
|
917
|
+
const handleSocketFailure = (socket: Bun.Socket, failure: unknown): void => {
|
|
918
|
+
if (socket !== currentSocket) return;
|
|
919
|
+
const code = sinkErrorCode(failure);
|
|
920
|
+
const peerClosed =
|
|
921
|
+
isKnownSinkPeerClosedError(failure) &&
|
|
922
|
+
(code === "EPIPE" || (code === "ERR_STREAM_DESTROYED" && socketIsLocallyTerminal(socket)));
|
|
923
|
+
detachCurrentSocket(socket);
|
|
924
|
+
if (peerClosed) return;
|
|
925
|
+
void shutdown(1, "RPC socket output failed", true).catch(() => {});
|
|
926
|
+
};
|
|
763
927
|
const server = Bun.listen({
|
|
764
928
|
unix: socketPath,
|
|
765
929
|
socket: {
|
|
@@ -767,9 +931,23 @@ export async function runRpcMode(
|
|
|
767
931
|
currentSocket = socket;
|
|
768
932
|
buf = "";
|
|
769
933
|
frameSink = (line: string) => {
|
|
770
|
-
socket
|
|
934
|
+
if (socket !== currentSocket) return;
|
|
935
|
+
try {
|
|
936
|
+
const written = socket.write(line);
|
|
937
|
+
if (written >= 0) return;
|
|
938
|
+
if (socketIsLocallyTerminal(socket)) {
|
|
939
|
+
detachCurrentSocket(socket);
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
handleSocketFailure(
|
|
943
|
+
socket,
|
|
944
|
+
Object.assign(new Error("RPC socket write failed"), { code: "ERR_STREAM_DESTROYED" }),
|
|
945
|
+
);
|
|
946
|
+
} catch (failure) {
|
|
947
|
+
handleSocketFailure(socket, failure);
|
|
948
|
+
}
|
|
771
949
|
};
|
|
772
|
-
output({ type: "ready" });
|
|
950
|
+
void output({ type: "ready" });
|
|
773
951
|
},
|
|
774
952
|
data(socket, data) {
|
|
775
953
|
if (socket !== currentSocket) return;
|
|
@@ -783,12 +961,11 @@ export async function runRpcMode(
|
|
|
783
961
|
}
|
|
784
962
|
},
|
|
785
963
|
close(socket) {
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
964
|
+
detachCurrentSocket(socket);
|
|
965
|
+
},
|
|
966
|
+
error(socket, failure) {
|
|
967
|
+
handleSocketFailure(socket, failure);
|
|
790
968
|
},
|
|
791
|
-
error() {},
|
|
792
969
|
},
|
|
793
970
|
});
|
|
794
971
|
await verifyRpcSocketAfterListen(socketPath);
|
|
@@ -816,13 +993,37 @@ export async function runRpcMode(
|
|
|
816
993
|
|
|
817
994
|
// Listen for JSONL input using Bun's stdin. Parse frame-by-frame so a malformed
|
|
818
995
|
// command reports a parse error without poisoning the whole long-lived RPC session.
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
996
|
+
const inputLines = readLines(Bun.stdin.stream());
|
|
997
|
+
const inputIterator = inputLines[Symbol.asyncIterator]();
|
|
998
|
+
detachStdioInput = () => {
|
|
999
|
+
const detached = inputIterator.return?.(undefined);
|
|
1000
|
+
if (detached) void detached.catch(() => {});
|
|
1001
|
+
};
|
|
1002
|
+
let inputFailure: unknown;
|
|
1003
|
+
try {
|
|
1004
|
+
await output({ type: "ready" });
|
|
1005
|
+
while (stdioTransportState === "open") {
|
|
1006
|
+
const next = await Promise.race([inputIterator.next(), stdioFatalFailure.promise]);
|
|
1007
|
+
if (next.done) break;
|
|
1008
|
+
const text = inputDecoder.decode(next.value).trim();
|
|
1009
|
+
if (text) await handleInboundLine(text);
|
|
1010
|
+
}
|
|
1011
|
+
} catch (failure) {
|
|
1012
|
+
inputFailure = failure;
|
|
1013
|
+
} finally {
|
|
1014
|
+
detachStdioInput = undefined;
|
|
823
1015
|
}
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
1016
|
+
if (inputFailure !== undefined) {
|
|
1017
|
+
return await shutdown(
|
|
1018
|
+
1,
|
|
1019
|
+
hasStdioWriteFailure ? "RPC stdout write failed" : "RPC stdin failed",
|
|
1020
|
+
hasStdioWriteFailure,
|
|
1021
|
+
);
|
|
1022
|
+
}
|
|
1023
|
+
if (stdioTransportState === "open") return await shutdown(0, "RPC client disconnected", false, false);
|
|
1024
|
+
return await shutdown(
|
|
1025
|
+
hasStdioWriteFailure ? 1 : 0,
|
|
1026
|
+
hasStdioWriteFailure ? "RPC stdout write failed" : "RPC stdout peer disconnected",
|
|
1027
|
+
true,
|
|
1028
|
+
);
|
|
828
1029
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Commands are sent as JSON lines on stdin.
|
|
5
5
|
* Responses and events are emitted as JSON lines on stdout.
|
|
6
6
|
*/
|
|
7
|
-
import type { AgentMessage, AgentToolResult, ThinkingLevel } from "@sayknow-cli/agent-core";
|
|
7
|
+
import type { AgentMessage, AgentToolResult, ResolvedThinkingLevel, ThinkingLevel } from "@sayknow-cli/agent-core";
|
|
8
8
|
import type { CompactionResult } from "@sayknow-cli/agent-core/compaction";
|
|
9
9
|
import type { Effort, ImageContent, Model } from "@sayknow-cli/ai";
|
|
10
10
|
import type { BashResult } from "../../exec/bash-executor";
|
|
@@ -38,6 +38,13 @@ export type RpcCommand =
|
|
|
38
38
|
|
|
39
39
|
// Model
|
|
40
40
|
| { id?: string; type: "set_model"; provider: string; modelId: string }
|
|
41
|
+
| {
|
|
42
|
+
id?: string;
|
|
43
|
+
type: "set_default_model_selection";
|
|
44
|
+
provider: string;
|
|
45
|
+
modelId: string;
|
|
46
|
+
thinkingLevel?: ResolvedThinkingLevel;
|
|
47
|
+
}
|
|
41
48
|
| { id?: string; type: "cycle_model" }
|
|
42
49
|
| { id?: string; type: "get_available_models" }
|
|
43
50
|
|
|
@@ -158,6 +165,13 @@ export type RpcResponse =
|
|
|
158
165
|
success: true;
|
|
159
166
|
data: Model;
|
|
160
167
|
}
|
|
168
|
+
| {
|
|
169
|
+
id?: string;
|
|
170
|
+
type: "response";
|
|
171
|
+
command: "set_default_model_selection";
|
|
172
|
+
success: true;
|
|
173
|
+
data: { provider: string; modelId: string; thinkingLevel: ResolvedThinkingLevel };
|
|
174
|
+
}
|
|
161
175
|
| {
|
|
162
176
|
id?: string;
|
|
163
177
|
type: "response";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentTool } from "@sayknow-cli/agent-core";
|
|
1
|
+
import type { AgentTool, ResolvedThinkingLevel } from "@sayknow-cli/agent-core";
|
|
2
2
|
import { ThinkingLevel } from "@sayknow-cli/agent-core";
|
|
3
3
|
import { getOAuthProviders } from "@sayknow-cli/ai/utils/oauth";
|
|
4
4
|
import { Snowflake } from "@sayknow-cli/utils";
|
|
@@ -102,6 +102,14 @@ function serializeRpcDispatchError(err: unknown): string | object {
|
|
|
102
102
|
return err instanceof Error ? err.message : String(err);
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
function isConcreteThinkingLevel(value: unknown): value is ResolvedThinkingLevel {
|
|
106
|
+
return (
|
|
107
|
+
typeof value === "string" &&
|
|
108
|
+
value !== ThinkingLevel.Inherit &&
|
|
109
|
+
(Object.values(ThinkingLevel) as string[]).includes(value)
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
105
113
|
export async function dispatchRpcCommand(
|
|
106
114
|
command: RpcCommand,
|
|
107
115
|
context: RpcCommandDispatchContext,
|
|
@@ -239,6 +247,30 @@ export async function dispatchRpcCommand(
|
|
|
239
247
|
return rpcSuccess(id, "set_model", model);
|
|
240
248
|
}
|
|
241
249
|
|
|
250
|
+
case "set_default_model_selection": {
|
|
251
|
+
if (typeof command.provider !== "string" || !command.provider.trim()) {
|
|
252
|
+
return typedError("set_default_model_selection", "provider must be a non-empty string");
|
|
253
|
+
}
|
|
254
|
+
if (typeof command.modelId !== "string" || !command.modelId.trim()) {
|
|
255
|
+
return typedError("set_default_model_selection", "modelId must be a non-empty string");
|
|
256
|
+
}
|
|
257
|
+
const requestedLevel: unknown = command.thinkingLevel;
|
|
258
|
+
if (requestedLevel !== undefined && !isConcreteThinkingLevel(requestedLevel)) {
|
|
259
|
+
return typedError("set_default_model_selection", "thinkingLevel must be a concrete level");
|
|
260
|
+
}
|
|
261
|
+
const model = session
|
|
262
|
+
.getAvailableModels()
|
|
263
|
+
.find(candidate => candidate.provider === command.provider && candidate.id === command.modelId);
|
|
264
|
+
if (!model) {
|
|
265
|
+
return typedError(
|
|
266
|
+
"set_default_model_selection",
|
|
267
|
+
`Model not found: ${command.provider}/${command.modelId}`,
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
const selection = await session.setDefaultModelSelection(model, requestedLevel);
|
|
271
|
+
return rpcSuccess(id, "set_default_model_selection", selection);
|
|
272
|
+
}
|
|
273
|
+
|
|
242
274
|
case "cycle_model": {
|
|
243
275
|
const result = await session.cycleModel();
|
|
244
276
|
if (!result) {
|
|
@@ -18,6 +18,7 @@ function stringField(value: Record<string, unknown>, key: string): boolean {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
const THINKING_LEVELS = new Set(["inherit", "off", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
21
|
+
const RESOLVED_THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
21
22
|
const TODO_STATUSES = new Set(["pending", "in_progress", "completed", "abandoned"]);
|
|
22
23
|
|
|
23
24
|
function optionalBoolean(value: unknown): boolean {
|
|
@@ -136,6 +137,15 @@ export function isRpcCommand(value: unknown): value is RpcCommand {
|
|
|
136
137
|
);
|
|
137
138
|
case "set_model":
|
|
138
139
|
return stringField(value, "provider") && stringField(value, "modelId");
|
|
140
|
+
case "set_default_model_selection":
|
|
141
|
+
return (
|
|
142
|
+
typeof value.provider === "string" &&
|
|
143
|
+
value.provider.trim().length > 0 &&
|
|
144
|
+
typeof value.modelId === "string" &&
|
|
145
|
+
value.modelId.trim().length > 0 &&
|
|
146
|
+
(value.thinkingLevel === undefined ||
|
|
147
|
+
(typeof value.thinkingLevel === "string" && RESOLVED_THINKING_LEVELS.has(value.thinkingLevel)))
|
|
148
|
+
);
|
|
139
149
|
case "set_thinking_level":
|
|
140
150
|
return typeof value.level === "string" && THINKING_LEVELS.has(value.level);
|
|
141
151
|
case "set_steering_mode":
|