@oh-my-pi/pi-coding-agent 17.0.8 → 17.1.0
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/CHANGELOG.md +87 -1
- package/README.md +1 -1
- package/dist/cli.js +5495 -5021
- package/dist/types/async/job-manager.d.ts +37 -1
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/bench-cli.d.ts +41 -0
- package/dist/types/commands/bench.d.ts +15 -3
- package/dist/types/commands/launch.d.ts +4 -0
- package/dist/types/config/__tests__/model-registry.test.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +30 -1
- package/dist/types/config/models-config-schema.d.ts +18 -6
- package/dist/types/config/models-config.d.ts +14 -5
- package/dist/types/config/provider-globals.d.ts +2 -2
- package/dist/types/config/service-tier.d.ts +5 -1
- package/dist/types/config/settings-schema.d.ts +350 -50
- package/dist/types/config/settings.d.ts +7 -0
- package/dist/types/dap/client.d.ts +20 -0
- package/dist/types/exec/bash-executor.d.ts +38 -0
- package/dist/types/exec/direnv.d.ts +33 -0
- package/dist/types/export/html/args.d.ts +15 -0
- package/dist/types/export/html/index.d.ts +11 -26
- package/dist/types/export/html/web-palette.d.ts +55 -116
- package/dist/types/extensibility/extensions/loader.d.ts +3 -0
- package/dist/types/extensibility/extensions/types.d.ts +17 -2
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +1 -0
- package/dist/types/extensibility/legacy-pi-tui-shim.d.ts +10 -0
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +14 -1
- package/dist/types/internal-urls/registry-helpers.d.ts +11 -0
- package/dist/types/live/controller.d.ts +40 -0
- package/dist/types/live/protocol.d.ts +85 -0
- package/dist/types/live/protocol.test.d.ts +1 -0
- package/dist/types/live/transport.d.ts +48 -0
- package/dist/types/live/visualizer.d.ts +33 -0
- package/dist/types/lsp/client.d.ts +18 -1
- package/dist/types/mcp/config-writer.test.d.ts +1 -0
- package/dist/types/mcp/manager.d.ts +6 -2
- package/dist/types/mcp/oauth-discovery.d.ts +2 -0
- package/dist/types/mcp/render.d.ts +2 -2
- package/dist/types/mcp/smithery-auth.d.ts +1 -1
- package/dist/types/mcp/smithery-auth.test.d.ts +1 -0
- package/dist/types/mcp/tool-bridge.d.ts +7 -3
- package/dist/types/mcp/types.d.ts +6 -0
- package/dist/types/memory-backend/tool-names.d.ts +2 -0
- package/dist/types/modes/components/login-dialog.d.ts +2 -2
- package/dist/types/modes/components/oauth-selector.d.ts +8 -0
- package/dist/types/modes/components/plan-review-overlay.d.ts +26 -0
- package/dist/types/modes/components/settings-defs.d.ts +8 -1
- package/dist/types/modes/components/status-line/component.jj-cache.test.d.ts +1 -0
- package/dist/types/modes/components/tool-execution.d.ts +9 -1
- package/dist/types/modes/controllers/command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/event-controller.d.ts +6 -1
- package/dist/types/modes/controllers/live-command-controller.d.ts +14 -0
- package/dist/types/modes/controllers/mcp-command-controller.d.ts +3 -0
- package/dist/types/modes/interactive-mode.d.ts +5 -0
- package/dist/types/modes/rpc/rpc-client.d.ts +10 -1
- package/dist/types/modes/rpc/rpc-frame.d.ts +16 -0
- package/dist/types/modes/rpc/rpc-messages.d.ts +26 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +40 -0
- package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +1 -1
- package/dist/types/modes/setup-wizard/scenes/types.d.ts +9 -1
- package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +1 -1
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +3 -1
- package/dist/types/modes/utils/context-usage.d.ts +12 -2
- package/dist/types/plan-mode/plan-files.d.ts +10 -0
- package/dist/types/registry/agent-lifecycle.d.ts +21 -9
- package/dist/types/registry/agent-registry.d.ts +11 -4
- package/dist/types/sdk.d.ts +15 -1
- package/dist/types/secrets/index.d.ts +15 -2
- package/dist/types/secrets/obfuscator.d.ts +113 -15
- package/dist/types/session/acp-permission-gate.d.ts +19 -0
- package/dist/types/session/agent-session-events.d.ts +84 -0
- package/dist/types/session/agent-session-types.d.ts +314 -0
- package/dist/types/session/agent-session.d.ts +120 -750
- package/dist/types/session/async-job-delivery.d.ts +28 -0
- package/dist/types/session/bash-runner.d.ts +75 -0
- package/dist/types/session/blob-store.d.ts +11 -1
- package/dist/types/session/blob-store.test.d.ts +1 -0
- package/dist/types/session/checkpoint-entries.d.ts +28 -0
- package/dist/types/session/eval-runner.d.ts +49 -0
- package/dist/types/session/irc-bridge.d.ts +46 -0
- package/dist/types/session/messages.d.ts +22 -0
- package/dist/types/session/model-controls.d.ts +180 -0
- package/dist/types/session/prewalk.d.ts +46 -0
- package/dist/types/session/queued-messages.d.ts +22 -0
- package/dist/types/session/redis-session-storage.d.ts +1 -0
- package/dist/types/session/retry-fallback-chains.d.ts +74 -0
- package/dist/types/session/role-models.d.ts +15 -0
- package/dist/types/session/session-advisors.d.ts +228 -0
- package/dist/types/session/session-entries.d.ts +8 -0
- package/dist/types/session/session-handoff.d.ts +70 -0
- package/dist/types/session/session-history-format.d.ts +6 -1
- package/dist/types/session/session-loader.d.ts +6 -4
- package/dist/types/session/session-maintenance.d.ts +246 -0
- package/dist/types/session/session-manager.d.ts +41 -0
- package/dist/types/session/session-memory.d.ts +56 -0
- package/dist/types/session/session-provider-boundary.d.ts +62 -0
- package/dist/types/session/session-stats.d.ts +48 -0
- package/dist/types/session/session-tools.d.ts +143 -0
- package/dist/types/session/session-workspace.d.ts +32 -0
- package/dist/types/session/stream-guards.d.ts +44 -0
- package/dist/types/session/todo-tracker.d.ts +56 -0
- package/dist/types/session/ttsr-coordinator.d.ts +51 -0
- package/dist/types/session/turn-recovery.d.ts +225 -0
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +4 -1
- package/dist/types/task/output-manager.d.ts +3 -1
- package/dist/types/task/types.d.ts +14 -0
- package/dist/types/tools/approval.d.ts +1 -0
- package/dist/types/tools/browser/launch.d.ts +5 -0
- package/dist/types/tools/hub/jobs.d.ts +1 -1
- package/dist/types/tools/image-gen.d.ts +4 -4
- package/dist/types/tools/image-providers.d.ts +38 -0
- package/dist/types/tools/index.d.ts +8 -0
- package/dist/types/tools/path-utils.d.ts +8 -0
- package/dist/types/tools/report-tool-issue.d.ts +24 -9
- package/dist/types/tools/todo.d.ts +10 -4
- package/dist/types/tools/xdev.d.ts +5 -1
- package/dist/types/tts/streaming-player.d.ts +14 -0
- package/dist/types/tts/vocalizer.d.ts +5 -0
- package/dist/types/utils/jj.d.ts +29 -0
- package/dist/types/utils/lang-from-path.d.ts +1 -0
- package/dist/types/utils/title-generator.d.ts +30 -0
- package/dist/types/vibe/runtime.d.ts +36 -8
- package/dist/types/web/search/index.d.ts +1 -1
- package/dist/types/web/search/provider.d.ts +15 -5
- package/dist/types/web/search/providers/firecrawl.d.ts +9 -0
- package/dist/types/web/search/types.d.ts +95 -1
- package/package.json +13 -12
- package/scripts/generate-share-viewer.ts +4 -6
- package/scripts/legacy-pi-virtual-module.ts +1 -1
- package/src/advisor/__tests__/advisor.test.ts +643 -0
- package/src/advisor/runtime.ts +144 -43
- package/src/async/job-manager.ts +90 -2
- package/src/cli/args.ts +2 -0
- package/src/cli/auth-gateway-cli.ts +18 -3
- package/src/cli/bench-cli.ts +395 -29
- package/src/cli/flag-tables.ts +3 -0
- package/src/cli/gc-cli.ts +2 -2
- package/src/cli/grievances-cli.ts +2 -2
- package/src/cli/usage-cli.ts +11 -2
- package/src/collab/host.ts +3 -2
- package/src/commands/bench.ts +18 -4
- package/src/commands/launch.ts +4 -0
- package/src/config/__tests__/model-registry.test.ts +147 -0
- package/src/config/model-registry.ts +50 -2
- package/src/config/model-resolver.ts +0 -1
- package/src/config/models-config-schema.ts +15 -4
- package/src/config/provider-globals.ts +9 -9
- package/src/config/service-tier.ts +26 -1
- package/src/config/settings-schema.ts +250 -54
- package/src/config/settings.ts +81 -2
- package/src/dap/client.ts +37 -4
- package/src/edit/index.ts +49 -12
- package/src/eval/py/executor.ts +103 -33
- package/src/exec/bash-executor.ts +89 -6
- package/src/exec/direnv.ts +145 -0
- package/src/export/html/args.ts +20 -0
- package/src/export/html/index.ts +45 -47
- package/src/export/html/template.css +19 -1
- package/src/export/html/template.html +6 -0
- package/src/export/html/template.js +21 -0
- package/src/export/html/tool-views.generated.js +31 -31
- package/src/export/html/web-palette.ts +116 -132
- package/src/export/share.ts +249 -49
- package/src/extensibility/extensions/loader.ts +29 -1
- package/src/extensibility/extensions/runner.ts +6 -0
- package/src/extensibility/extensions/types.ts +33 -2
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +1 -0
- package/src/extensibility/legacy-pi-tui-shim.ts +10 -0
- package/src/extensibility/plugins/legacy-pi-compat.ts +870 -311
- package/src/hindsight/state.ts +64 -6
- package/src/internal-urls/local-protocol.ts +2 -1
- package/src/internal-urls/memory-protocol.ts +2 -2
- package/src/internal-urls/registry-helpers.ts +40 -0
- package/src/internal-urls/skill-protocol.ts +2 -2
- package/src/internal-urls/ssh-protocol.ts +2 -1
- package/src/internal-urls/vault-protocol.ts +2 -1
- package/src/live/audio-worklet.txt +59 -0
- package/src/live/browser-runtime.txt +221 -0
- package/src/live/controller.ts +464 -0
- package/src/live/prompts/agent-final-message.md +3 -0
- package/src/live/prompts/live-instructions.md +23 -0
- package/src/live/protocol.test.ts +140 -0
- package/src/live/protocol.ts +233 -0
- package/src/live/transport.ts +490 -0
- package/src/live/visualizer.ts +234 -0
- package/src/lsp/client.ts +52 -9
- package/src/lsp/clients/biome-client.ts +3 -4
- package/src/lsp/index.ts +44 -11
- package/src/main.ts +9 -1
- package/src/mcp/config-writer.test.ts +43 -0
- package/src/mcp/config-writer.ts +109 -82
- package/src/mcp/manager.ts +39 -8
- package/src/mcp/oauth-discovery.ts +10 -2
- package/src/mcp/render.ts +94 -35
- package/src/mcp/smithery-auth.test.ts +29 -0
- package/src/mcp/smithery-auth.ts +3 -2
- package/src/mcp/tool-bridge.ts +107 -11
- package/src/mcp/types.ts +7 -0
- package/src/memories/index.ts +40 -20
- package/src/memory-backend/tool-names.ts +2 -0
- package/src/mnemopi/backend.ts +24 -7
- package/src/modes/acp/acp-agent.ts +56 -10
- package/src/modes/acp/acp-event-mapper.ts +8 -1
- package/src/modes/components/agent-hub.ts +27 -10
- package/src/modes/components/login-dialog.ts +14 -4
- package/src/modes/components/oauth-selector.ts +24 -7
- package/src/modes/components/plan-review-overlay.ts +350 -35
- package/src/modes/components/settings-defs.ts +28 -3
- package/src/modes/components/settings-selector.ts +178 -6
- package/src/modes/components/status-line/component.jj-cache.test.ts +229 -0
- package/src/modes/components/status-line/component.ts +123 -2
- package/src/modes/components/tool-execution.test.ts +63 -2
- package/src/modes/components/tool-execution.ts +11 -2
- package/src/modes/controllers/command-controller.ts +70 -22
- package/src/modes/controllers/event-controller.ts +111 -16
- package/src/modes/controllers/extension-ui-controller.test.ts +1 -0
- package/src/modes/controllers/extension-ui-controller.ts +20 -4
- package/src/modes/controllers/live-command-controller.ts +178 -0
- package/src/modes/controllers/mcp-command-controller.ts +80 -41
- package/src/modes/controllers/omfg-controller.ts +44 -40
- package/src/modes/controllers/selector-controller.ts +18 -10
- package/src/modes/controllers/session-focus-controller.ts +6 -1
- package/src/modes/interactive-mode.ts +160 -38
- package/src/modes/rpc/rpc-client.ts +94 -3
- package/src/modes/rpc/rpc-frame.ts +164 -4
- package/src/modes/rpc/rpc-messages.ts +127 -0
- package/src/modes/rpc/rpc-mode.ts +79 -7
- package/src/modes/rpc/rpc-types.ts +34 -2
- package/src/modes/runtime-init.ts +2 -0
- package/src/modes/setup-wizard/scenes/model.ts +5 -7
- package/src/modes/setup-wizard/scenes/providers.ts +3 -2
- package/src/modes/setup-wizard/scenes/sign-in.ts +8 -2
- package/src/modes/setup-wizard/scenes/theme.ts +13 -3
- package/src/modes/setup-wizard/scenes/types.ts +9 -1
- package/src/modes/setup-wizard/scenes/web-search.ts +23 -10
- package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
- package/src/modes/theme/theme.ts +1 -1
- package/src/modes/types.ts +3 -1
- package/src/modes/utils/context-usage.ts +23 -7
- package/src/modes/utils/ui-helpers.ts +10 -5
- package/src/plan-mode/plan-files.ts +40 -0
- package/src/prompts/bench/cache-prefix-chunk.md +1 -0
- package/src/prompts/bench/cache-prefix.md +3 -0
- package/src/prompts/bench/cache-suffix.md +1 -0
- package/src/prompts/goals/guided-goal-system.md +24 -3
- package/src/prompts/system/custom-system-prompt.md +1 -1
- package/src/prompts/system/project-prompt.md +8 -1
- package/src/prompts/system/subagent-async-pending.md +6 -0
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/prompts/system/workflow-notice.md +48 -40
- package/src/prompts/system/xdev-mount-notice.md +4 -0
- package/src/prompts/tools/task.md +12 -2
- package/src/prompts/tools/todo.md +3 -1
- package/src/registry/agent-lifecycle.ts +116 -46
- package/src/registry/agent-registry.ts +35 -8
- package/src/registry/persisted-agents.ts +26 -2
- package/src/sdk.ts +255 -139
- package/src/secrets/index.ts +127 -4
- package/src/secrets/obfuscator.ts +2328 -105
- package/src/session/acp-permission-gate.ts +165 -0
- package/src/session/agent-session-error-log.test.ts +1 -1
- package/src/session/agent-session-events.ts +66 -0
- package/src/session/agent-session-types.ts +334 -0
- package/src/session/agent-session.ts +1910 -12213
- package/src/session/artifacts.ts +6 -5
- package/src/session/async-job-delivery.ts +74 -0
- package/src/session/bash-runner.ts +326 -0
- package/src/session/blob-store.test.ts +56 -0
- package/src/session/blob-store.ts +18 -2
- package/src/session/checkpoint-entries.ts +81 -0
- package/src/session/eval-runner.ts +212 -0
- package/src/session/indexed-session-storage.ts +9 -2
- package/src/session/irc-bridge.ts +203 -0
- package/src/session/messages.ts +263 -1
- package/src/session/model-controls.ts +714 -0
- package/src/session/prewalk.ts +252 -0
- package/src/session/queued-messages.ts +93 -0
- package/src/session/redis-session-storage.ts +51 -11
- package/src/session/retry-fallback-chains.ts +455 -0
- package/src/session/role-models.ts +85 -0
- package/src/session/session-advisors.ts +1679 -0
- package/src/session/session-context.test.ts +224 -1
- package/src/session/session-context.ts +47 -5
- package/src/session/session-entries.ts +8 -0
- package/src/session/session-handoff.ts +308 -0
- package/src/session/session-history-format.ts +20 -9
- package/src/session/session-loader.ts +10 -51
- package/src/session/session-maintenance.ts +2983 -0
- package/src/session/session-manager.ts +390 -30
- package/src/session/session-memory.ts +222 -0
- package/src/session/session-provider-boundary.ts +307 -0
- package/src/session/session-stats.ts +293 -0
- package/src/session/session-tools.ts +943 -0
- package/src/session/session-workspace.ts +53 -0
- package/src/session/stream-guards.ts +417 -0
- package/src/session/todo-tracker.ts +380 -0
- package/src/session/ttsr-coordinator.ts +496 -0
- package/src/session/turn-recovery.ts +1629 -0
- package/src/slash-commands/builtin-registry.ts +92 -11
- package/src/slash-commands/helpers/usage-report.ts +25 -4
- package/src/system-prompt.ts +41 -17
- package/src/task/executor.ts +288 -70
- package/src/task/index.ts +148 -111
- package/src/task/output-manager.ts +25 -3
- package/src/task/persisted-revive.ts +4 -3
- package/src/task/structured-subagent.ts +4 -1
- package/src/task/types.ts +16 -0
- package/src/tools/approval.ts +55 -10
- package/src/tools/bash-interactive.ts +26 -0
- package/src/tools/bash-skill-urls.ts +28 -2
- package/src/tools/bash.ts +339 -123
- package/src/tools/browser/launch.ts +9 -1
- package/src/tools/browser/tab-supervisor.ts +8 -5
- package/src/tools/browser.ts +94 -47
- package/src/tools/hub/index.ts +1 -1
- package/src/tools/hub/jobs.ts +67 -8
- package/src/tools/image-gen.ts +20 -28
- package/src/tools/image-providers.ts +50 -0
- package/src/tools/index.ts +12 -0
- package/src/tools/path-utils.ts +49 -2
- package/src/tools/read.ts +156 -88
- package/src/tools/renderers.ts +7 -1
- package/src/tools/report-tool-issue.ts +79 -28
- package/src/tools/todo.ts +101 -11
- package/src/tools/vibe.ts +1 -2
- package/src/tools/write.ts +51 -1
- package/src/tools/xdev.ts +67 -3
- package/src/tts/streaming-player.ts +62 -4
- package/src/tts/vocalizer.ts +18 -1
- package/src/utils/jj.ts +125 -4
- package/src/utils/lang-from-path.ts +7 -0
- package/src/utils/title-generator.ts +137 -1
- package/src/vibe/runtime.ts +852 -72
- package/src/web/search/index.ts +3 -6
- package/src/web/search/provider.ts +31 -16
- package/src/web/search/providers/firecrawl.ts +46 -13
- package/src/web/search/providers/xai.ts +7 -1
- package/src/web/search/types.ts +8 -1
|
@@ -1,8 +1,24 @@
|
|
|
1
1
|
import { isDeepStrictEqual } from "node:util";
|
|
2
2
|
import { isRecord } from "@oh-my-pi/pi-utils";
|
|
3
|
+
import type { RpcChunkFrame } from "./rpc-types";
|
|
3
4
|
|
|
4
5
|
/** Maximum UTF-8 size of one newline-delimited RPC frame, including the newline. */
|
|
5
6
|
export const MAX_RPC_FRAME_BYTES = 1024 * 1024;
|
|
7
|
+
/** Maximum UTF-8 size of one logical frame reassembled by protocol v2. */
|
|
8
|
+
export const MAX_RPC_REASSEMBLED_BYTES = 64 * 1024 * 1024;
|
|
9
|
+
|
|
10
|
+
const RPC_CHUNK_PAYLOAD_BYTES = 256 * 1024;
|
|
11
|
+
|
|
12
|
+
export type RpcProtocolVersion = 1 | 2;
|
|
13
|
+
|
|
14
|
+
interface PendingRpcChunks {
|
|
15
|
+
chunkId: string;
|
|
16
|
+
count: number;
|
|
17
|
+
byteLength: number;
|
|
18
|
+
nextIndex: number;
|
|
19
|
+
chunks: Buffer[];
|
|
20
|
+
receivedBytes: number;
|
|
21
|
+
}
|
|
6
22
|
|
|
7
23
|
interface ShrinkPass {
|
|
8
24
|
stringCap: number;
|
|
@@ -68,6 +84,110 @@ function encodedMessageSnapshot(encoded: string): { message: unknown } | undefin
|
|
|
68
84
|
: undefined;
|
|
69
85
|
}
|
|
70
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Emit protocol v2 chunk frames for one pre-serialized logical frame, one physical
|
|
89
|
+
* JSONL line at a time so callers can write with backpressure instead of holding the
|
|
90
|
+
* whole ~4/3-sized base64 transport in memory. The reassembly ceiling is enforced on
|
|
91
|
+
* `Buffer.byteLength` BEFORE any full-payload allocation.
|
|
92
|
+
*/
|
|
93
|
+
function* encodeChunkedRpcFrames(frame: object, json: string, chunkId: string): Generator<string> {
|
|
94
|
+
const byteLength = Buffer.byteLength(json, "utf8");
|
|
95
|
+
if (byteLength > MAX_RPC_REASSEMBLED_BYTES) {
|
|
96
|
+
yield `${JSON.stringify(overflowFrame(frame))}\n`;
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const bytes = Buffer.from(json, "utf8");
|
|
100
|
+
const count = Math.ceil(byteLength / RPC_CHUNK_PAYLOAD_BYTES);
|
|
101
|
+
for (let index = 0; index < count; index++) {
|
|
102
|
+
const chunk: RpcChunkFrame = {
|
|
103
|
+
type: "rpc_chunk",
|
|
104
|
+
chunkId,
|
|
105
|
+
index,
|
|
106
|
+
count,
|
|
107
|
+
byteLength,
|
|
108
|
+
data: bytes
|
|
109
|
+
.subarray(index * RPC_CHUNK_PAYLOAD_BYTES, (index + 1) * RPC_CHUNK_PAYLOAD_BYTES)
|
|
110
|
+
.toString("base64"),
|
|
111
|
+
};
|
|
112
|
+
const line = `${JSON.stringify(chunk)}\n`;
|
|
113
|
+
if (serializedFrameBytes(line.slice(0, -1)) > MAX_RPC_FRAME_BYTES)
|
|
114
|
+
throw new Error("RPC chunk exceeded the transport limit");
|
|
115
|
+
yield line;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function isRpcChunkFrame(value: unknown): value is RpcChunkFrame {
|
|
120
|
+
return isRecord(value) && value.type === "rpc_chunk";
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function decodeBase64(data: unknown): Buffer {
|
|
124
|
+
if (
|
|
125
|
+
typeof data !== "string" ||
|
|
126
|
+
data.length === 0 ||
|
|
127
|
+
!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(data)
|
|
128
|
+
)
|
|
129
|
+
throw new Error("invalid rpc chunk data");
|
|
130
|
+
const bytes = Buffer.from(data, "base64");
|
|
131
|
+
if (bytes.toString("base64") !== data) throw new Error("invalid rpc chunk data");
|
|
132
|
+
return bytes;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Reassemble protocol v2 chunk frames after each JSONL line has been parsed. */
|
|
136
|
+
export class RpcFrameDecoder {
|
|
137
|
+
#pending?: PendingRpcChunks;
|
|
138
|
+
|
|
139
|
+
push(value: unknown): object | undefined {
|
|
140
|
+
if (!isRpcChunkFrame(value)) {
|
|
141
|
+
if (this.#pending) throw new Error("rpc chunk sequence interrupted");
|
|
142
|
+
if (!isRecord(value)) throw new Error("rpc frame must be an object");
|
|
143
|
+
return value;
|
|
144
|
+
}
|
|
145
|
+
const { chunkId, index, count, byteLength } = value;
|
|
146
|
+
if (
|
|
147
|
+
typeof chunkId !== "string" ||
|
|
148
|
+
chunkId.length === 0 ||
|
|
149
|
+
chunkId.length > 128 ||
|
|
150
|
+
!Number.isSafeInteger(index) ||
|
|
151
|
+
!Number.isSafeInteger(count) ||
|
|
152
|
+
!Number.isSafeInteger(byteLength) ||
|
|
153
|
+
index < 0 ||
|
|
154
|
+
count < 2 ||
|
|
155
|
+
count > Math.ceil(MAX_RPC_REASSEMBLED_BYTES / RPC_CHUNK_PAYLOAD_BYTES) ||
|
|
156
|
+
index >= count ||
|
|
157
|
+
byteLength < MAX_RPC_FRAME_BYTES ||
|
|
158
|
+
byteLength > MAX_RPC_REASSEMBLED_BYTES
|
|
159
|
+
)
|
|
160
|
+
throw new Error("invalid rpc chunk metadata");
|
|
161
|
+
const bytes = decodeBase64(value.data);
|
|
162
|
+
if (bytes.byteLength > RPC_CHUNK_PAYLOAD_BYTES) throw new Error("rpc chunk payload exceeds the transport limit");
|
|
163
|
+
|
|
164
|
+
if (!this.#pending) {
|
|
165
|
+
if (index !== 0) throw new Error("rpc chunk sequence must start at index 0");
|
|
166
|
+
this.#pending = { chunkId, count, byteLength, nextIndex: 0, chunks: [], receivedBytes: 0 };
|
|
167
|
+
}
|
|
168
|
+
const pending = this.#pending;
|
|
169
|
+
if (
|
|
170
|
+
pending.chunkId !== chunkId ||
|
|
171
|
+
pending.count !== count ||
|
|
172
|
+
pending.byteLength !== byteLength ||
|
|
173
|
+
pending.nextIndex !== index
|
|
174
|
+
)
|
|
175
|
+
throw new Error("rpc chunk sequence mismatch");
|
|
176
|
+
pending.chunks.push(bytes);
|
|
177
|
+
pending.receivedBytes += bytes.byteLength;
|
|
178
|
+
pending.nextIndex++;
|
|
179
|
+
if (pending.receivedBytes > pending.byteLength) throw new Error("rpc chunk sequence exceeds declared length");
|
|
180
|
+
if (pending.nextIndex < pending.count) return undefined;
|
|
181
|
+
if (pending.receivedBytes !== pending.byteLength) throw new Error("rpc chunk sequence length mismatch");
|
|
182
|
+
|
|
183
|
+
this.#pending = undefined;
|
|
184
|
+
const decoded = new TextDecoder("utf-8", { fatal: true }).decode(Buffer.concat(pending.chunks));
|
|
185
|
+
const frame: unknown = JSON.parse(decoded);
|
|
186
|
+
if (!isRecord(frame)) throw new Error("rpc frame must be an object");
|
|
187
|
+
return frame;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
71
191
|
function compactTerminalFrame(
|
|
72
192
|
frame: object,
|
|
73
193
|
streamedMessageCount: number,
|
|
@@ -142,15 +262,55 @@ export function encodeRpcFrame(frame: object, streamedMessageCount = 0, streamed
|
|
|
142
262
|
/** Stateful encoder that tracks which messages a client has already received. */
|
|
143
263
|
export class RpcFrameEncoder {
|
|
144
264
|
#streamedMessages: unknown[] = [];
|
|
265
|
+
#protocolVersion: RpcProtocolVersion = 1;
|
|
266
|
+
#chunkCounter = 0;
|
|
145
267
|
|
|
146
|
-
|
|
268
|
+
setProtocolVersion(version: number): void {
|
|
269
|
+
if (version !== 1 && version !== 2) throw new Error(`Unsupported RPC protocol version: ${version}`);
|
|
270
|
+
this.#protocolVersion = version;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Encode one logical frame into physical JSONL lines. Encoder bookkeeping runs
|
|
275
|
+
* eagerly; only chunk emission is lazy, so a chunked result can be streamed to
|
|
276
|
+
* stdout with backpressure without holding the whole transport in memory. The
|
|
277
|
+
* returned iterable MUST be fully consumed exactly once.
|
|
278
|
+
*/
|
|
279
|
+
encodeFrames(frame: object): Iterable<string> {
|
|
147
280
|
if (isRecord(frame) && frame.type === "agent_start") this.#streamedMessages = [];
|
|
148
|
-
const
|
|
149
|
-
|
|
281
|
+
const json = JSON.stringify(frame);
|
|
282
|
+
let frames: Iterable<string>;
|
|
283
|
+
let singleFrame: string | undefined;
|
|
284
|
+
if (this.#protocolVersion === 2 && serializedFrameBytes(json) > MAX_RPC_FRAME_BYTES) {
|
|
285
|
+
const compacted = compactTerminalFrame(frame, this.#streamedMessages.length, this.#streamedMessages);
|
|
286
|
+
// Reuse the original serialization when compaction was a no-op.
|
|
287
|
+
const compactedJson = compacted === frame ? json : JSON.stringify(compacted);
|
|
288
|
+
if (serializedFrameBytes(compactedJson) > MAX_RPC_FRAME_BYTES) {
|
|
289
|
+
frames = encodeChunkedRpcFrames(compacted, compactedJson, `rpc-${++this.#chunkCounter}`);
|
|
290
|
+
} else {
|
|
291
|
+
singleFrame = `${compactedJson}\n`;
|
|
292
|
+
frames = [singleFrame];
|
|
293
|
+
}
|
|
294
|
+
} else {
|
|
295
|
+
singleFrame = encodeRpcFrame(frame, this.#streamedMessages.length, this.#streamedMessages);
|
|
296
|
+
frames = [singleFrame];
|
|
297
|
+
}
|
|
298
|
+
if (!isRecord(frame)) return frames;
|
|
150
299
|
if (frame.type === "message_end") {
|
|
151
|
-
const snapshot =
|
|
300
|
+
const snapshot =
|
|
301
|
+
this.#protocolVersion === 2 && Object.hasOwn(frame, "message")
|
|
302
|
+
? { message: jsonSnapshot(frame.message) }
|
|
303
|
+
: singleFrame !== undefined
|
|
304
|
+
? encodedMessageSnapshot(singleFrame)
|
|
305
|
+
: undefined;
|
|
152
306
|
if (snapshot) this.#streamedMessages.push(snapshot.message);
|
|
153
307
|
} else if (frame.type === "agent_end" && frame.willContinue !== true) this.#streamedMessages = [];
|
|
308
|
+
return frames;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
encode(frame: object): string {
|
|
312
|
+
let encoded = "";
|
|
313
|
+
for (const line of this.encodeFrames(frame)) encoded += line;
|
|
154
314
|
return encoded;
|
|
155
315
|
}
|
|
156
316
|
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
2
|
+
import { isRecord } from "@oh-my-pi/pi-utils";
|
|
3
|
+
|
|
4
|
+
const DEFAULT_RPC_MESSAGE_PAGE_LIMIT = 100;
|
|
5
|
+
const MAX_RPC_MESSAGE_PAGE_LIMIT = 256;
|
|
6
|
+
const MAX_RPC_MESSAGE_PAGE_BYTES = 768 * 1024;
|
|
7
|
+
const MAX_RPC_MESSAGE_CURSOR_CHARS = 2048;
|
|
8
|
+
|
|
9
|
+
export const RPC_MESSAGES_PAGE_BUSY_ERROR = "Cannot page messages while the session is changing";
|
|
10
|
+
export const RPC_MESSAGES_PAGE_STALE_ERROR = "RPC message cursor is stale";
|
|
11
|
+
|
|
12
|
+
/** Machine-readable reasons a `get_messages_page` request can fail; carried as `code` on the error response. */
|
|
13
|
+
export type RpcMessagesPageErrorCode = "session_busy" | "stale_cursor";
|
|
14
|
+
|
|
15
|
+
/** Paging failure that maps to a structured wire `code`, so clients can react without matching message text. */
|
|
16
|
+
export class RpcMessagesPageError extends Error {
|
|
17
|
+
constructor(
|
|
18
|
+
message: string,
|
|
19
|
+
readonly code: RpcMessagesPageErrorCode,
|
|
20
|
+
) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = "RpcMessagesPageError";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface RpcMessageSnapshot {
|
|
27
|
+
sessionId: string;
|
|
28
|
+
leafId: string | null;
|
|
29
|
+
messageCount: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface RpcMessagesPage {
|
|
33
|
+
messages: AgentMessage[];
|
|
34
|
+
nextCursor?: string;
|
|
35
|
+
totalMessages: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface RpcMessageCursorPayload extends RpcMessageSnapshot {
|
|
39
|
+
version: 1;
|
|
40
|
+
offset: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface RpcMessagesPageOptions {
|
|
44
|
+
cursor?: string;
|
|
45
|
+
limit?: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function encodeCursor(snapshot: RpcMessageSnapshot, offset: number): string {
|
|
49
|
+
const payload: RpcMessageCursorPayload = { version: 1, ...snapshot, offset };
|
|
50
|
+
return Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function decodeCursor(cursor: string): RpcMessageCursorPayload {
|
|
54
|
+
if (cursor.length === 0 || cursor.length > MAX_RPC_MESSAGE_CURSOR_CHARS || !/^[A-Za-z0-9_-]+$/.test(cursor))
|
|
55
|
+
throw new Error("Invalid RPC message cursor");
|
|
56
|
+
const bytes = Buffer.from(cursor, "base64url");
|
|
57
|
+
if (bytes.toString("base64url") !== cursor) throw new Error("Invalid RPC message cursor");
|
|
58
|
+
let value: unknown;
|
|
59
|
+
try {
|
|
60
|
+
value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
61
|
+
} catch {
|
|
62
|
+
throw new Error("Invalid RPC message cursor");
|
|
63
|
+
}
|
|
64
|
+
if (!isRecord(value)) throw new Error("Invalid RPC message cursor");
|
|
65
|
+
const { version, sessionId, leafId, messageCount, offset } = value;
|
|
66
|
+
if (
|
|
67
|
+
version !== 1 ||
|
|
68
|
+
typeof sessionId !== "string" ||
|
|
69
|
+
sessionId.length === 0 ||
|
|
70
|
+
sessionId.length > 256 ||
|
|
71
|
+
!((typeof leafId === "string" && leafId.length > 0 && leafId.length <= 256) || leafId === null) ||
|
|
72
|
+
typeof messageCount !== "number" ||
|
|
73
|
+
!Number.isSafeInteger(messageCount) ||
|
|
74
|
+
messageCount < 0 ||
|
|
75
|
+
typeof offset !== "number" ||
|
|
76
|
+
!Number.isSafeInteger(offset) ||
|
|
77
|
+
offset < 0 ||
|
|
78
|
+
offset > messageCount
|
|
79
|
+
)
|
|
80
|
+
throw new Error("Invalid RPC message cursor");
|
|
81
|
+
return { version, sessionId, leafId, messageCount, offset };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function sameSnapshot(cursor: RpcMessageCursorPayload, snapshot: RpcMessageSnapshot): boolean {
|
|
85
|
+
return (
|
|
86
|
+
cursor.sessionId === snapshot.sessionId &&
|
|
87
|
+
cursor.leafId === snapshot.leafId &&
|
|
88
|
+
cursor.messageCount === snapshot.messageCount
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Page one stable in-memory message snapshot without crossing the v1 frame budget. */
|
|
93
|
+
export function pageRpcMessages(
|
|
94
|
+
messages: readonly AgentMessage[],
|
|
95
|
+
snapshot: RpcMessageSnapshot,
|
|
96
|
+
options: RpcMessagesPageOptions = {},
|
|
97
|
+
): RpcMessagesPage {
|
|
98
|
+
if (snapshot.messageCount !== messages.length)
|
|
99
|
+
throw new Error("RPC message snapshot does not match current messages");
|
|
100
|
+
const limit = options.limit ?? DEFAULT_RPC_MESSAGE_PAGE_LIMIT;
|
|
101
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_RPC_MESSAGE_PAGE_LIMIT)
|
|
102
|
+
throw new Error(`RPC message page limit must be between 1 and ${MAX_RPC_MESSAGE_PAGE_LIMIT}`);
|
|
103
|
+
let offset = 0;
|
|
104
|
+
if (options.cursor !== undefined) {
|
|
105
|
+
const cursor = decodeCursor(options.cursor);
|
|
106
|
+
if (!sameSnapshot(cursor, snapshot))
|
|
107
|
+
throw new RpcMessagesPageError(RPC_MESSAGES_PAGE_STALE_ERROR, "stale_cursor");
|
|
108
|
+
offset = cursor.offset;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const page: AgentMessage[] = [];
|
|
112
|
+
let pageBytes = 2;
|
|
113
|
+
while (offset + page.length < messages.length && page.length < limit) {
|
|
114
|
+
const message = messages[offset + page.length];
|
|
115
|
+
const messageBytes = Buffer.byteLength(JSON.stringify(message), "utf8") + (page.length === 0 ? 0 : 1);
|
|
116
|
+
if (page.length > 0 && pageBytes + messageBytes > MAX_RPC_MESSAGE_PAGE_BYTES) break;
|
|
117
|
+
page.push(message);
|
|
118
|
+
pageBytes += messageBytes;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const nextOffset = offset + page.length;
|
|
122
|
+
return {
|
|
123
|
+
messages: page,
|
|
124
|
+
...(nextOffset < messages.length ? { nextCursor: encodeCursor(snapshot, nextOffset) } : {}),
|
|
125
|
+
totalMessages: messages.length,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* - Events: AgentSessionEvent objects streamed as they occur
|
|
11
11
|
* - Extension UI: Extension UI requests are emitted, client responds with extension_ui_response
|
|
12
12
|
*/
|
|
13
|
+
import { once } from "node:events";
|
|
13
14
|
import { getOAuthProviders } from "@oh-my-pi/pi-ai/oauth";
|
|
14
15
|
import { isZodSchema, zodToWireSchema } from "@oh-my-pi/pi-ai/utils/schema";
|
|
15
16
|
import { $env, isRecord, readLines, Snowflake } from "@oh-my-pi/pi-utils";
|
|
@@ -34,8 +35,9 @@ import type { EventBus } from "../../utils/event-bus";
|
|
|
34
35
|
import { initializeExtensions } from "../runtime-init";
|
|
35
36
|
import { isRpcHostToolResult, isRpcHostToolUpdate, RpcHostToolBridge } from "./host-tools";
|
|
36
37
|
import { isRpcHostUriResult, RpcHostUriBridge } from "./host-uris";
|
|
37
|
-
import { RpcFrameEncoder } from "./rpc-frame";
|
|
38
|
+
import { MAX_RPC_FRAME_BYTES, MAX_RPC_REASSEMBLED_BYTES, RpcFrameEncoder } from "./rpc-frame";
|
|
38
39
|
import { claimRpcInput } from "./rpc-input";
|
|
40
|
+
import { pageRpcMessages, RPC_MESSAGES_PAGE_BUSY_ERROR, RpcMessagesPageError } from "./rpc-messages";
|
|
39
41
|
import { RpcSubagentRegistry, readRpcSubagentTranscript } from "./rpc-subagents";
|
|
40
42
|
import type {
|
|
41
43
|
RpcCommand,
|
|
@@ -619,9 +621,33 @@ export async function runRpcMode(
|
|
|
619
621
|
process.env.PI_NOTIFICATIONS = "off";
|
|
620
622
|
|
|
621
623
|
const frameEncoder = new RpcFrameEncoder();
|
|
622
|
-
|
|
624
|
+
// Ordered stdout writer honoring backpressure: chunked v2 frames are produced
|
|
625
|
+
// lazily by the encoder and written one physical line at a time, so a near-limit
|
|
626
|
+
// logical frame never materializes its full base64 transport in memory.
|
|
627
|
+
let stdoutQueue: Promise<void> = Promise.resolve();
|
|
628
|
+
const writeFrames = (frames: Iterable<string>) => {
|
|
629
|
+
stdoutQueue = stdoutQueue
|
|
630
|
+
.then(async () => {
|
|
631
|
+
for (const line of frames) {
|
|
632
|
+
if (!process.stdout.write(line)) await once(process.stdout, "drain");
|
|
633
|
+
}
|
|
634
|
+
})
|
|
635
|
+
// stdout gone (host exited) — nothing left to deliver; keep the queue alive.
|
|
636
|
+
.catch(() => {});
|
|
637
|
+
};
|
|
638
|
+
writeFrames(
|
|
639
|
+
frameEncoder.encodeFrames({
|
|
640
|
+
type: "ready",
|
|
641
|
+
protocolVersion: 1,
|
|
642
|
+
supportedProtocolVersions: [1, 2],
|
|
643
|
+
maxFrameBytes: MAX_RPC_FRAME_BYTES,
|
|
644
|
+
maxReassembledFrameBytes: MAX_RPC_REASSEMBLED_BYTES,
|
|
645
|
+
}),
|
|
646
|
+
);
|
|
623
647
|
const output = (obj: RpcResponse | RpcExtensionUIRequest | object) => {
|
|
624
|
-
|
|
648
|
+
writeFrames(frameEncoder.encodeFrames(obj));
|
|
649
|
+
if (isRecord(obj) && obj.type === "response" && obj.command === "negotiate_protocol" && obj.success === true)
|
|
650
|
+
frameEncoder.setProtocolVersion(2);
|
|
625
651
|
};
|
|
626
652
|
const emitRpcTitles = shouldEmitRpcTitles();
|
|
627
653
|
|
|
@@ -636,8 +662,8 @@ export async function runRpcMode(
|
|
|
636
662
|
return { id, type: "response", command, success: true, data } as RpcResponse;
|
|
637
663
|
};
|
|
638
664
|
|
|
639
|
-
const error = (id: string | undefined, command: string, message: string): RpcResponse => {
|
|
640
|
-
return { id, type: "response", command, success: false, error: message };
|
|
665
|
+
const error = (id: string | undefined, command: string, message: string, code?: string): RpcResponse => {
|
|
666
|
+
return { id, type: "response", command, success: false, error: message, ...(code ? { code } : {}) };
|
|
641
667
|
};
|
|
642
668
|
|
|
643
669
|
const extensionUserMessageTracker = new RpcExtensionUserMessageTracker();
|
|
@@ -936,6 +962,12 @@ export async function runRpcMode(
|
|
|
936
962
|
const id = command.id;
|
|
937
963
|
|
|
938
964
|
switch (command.type) {
|
|
965
|
+
case "negotiate_protocol": {
|
|
966
|
+
if (command.protocolVersion !== 2)
|
|
967
|
+
return error(id, "negotiate_protocol", `Unsupported RPC protocol version: ${command.protocolVersion}`);
|
|
968
|
+
return success(id, "negotiate_protocol", { protocolVersion: 2 });
|
|
969
|
+
}
|
|
970
|
+
|
|
939
971
|
// =================================================================
|
|
940
972
|
// Prompting
|
|
941
973
|
// =================================================================
|
|
@@ -1122,8 +1154,19 @@ export async function runRpcMode(
|
|
|
1122
1154
|
// =================================================================
|
|
1123
1155
|
|
|
1124
1156
|
case "set_model": {
|
|
1125
|
-
|
|
1126
|
-
|
|
1157
|
+
let models = session.getAvailableModels();
|
|
1158
|
+
let model = models.find(m => m.provider === command.provider && m.id === command.modelId);
|
|
1159
|
+
if (!model) {
|
|
1160
|
+
// Model not in the current catalog. Wait for in-flight
|
|
1161
|
+
// background discovery before declaring it missing: on cold
|
|
1162
|
+
// start, discovery-backed providers (proxy / ollama / etc.)
|
|
1163
|
+
// populate seconds after session ready. Models already in
|
|
1164
|
+
// the bundled catalog skip this await entirely so the RPC
|
|
1165
|
+
// queue is not stalled behind unrelated discovery.
|
|
1166
|
+
await session.modelRegistry.awaitBackgroundRefresh();
|
|
1167
|
+
models = session.getAvailableModels();
|
|
1168
|
+
model = models.find(m => m.provider === command.provider && m.id === command.modelId);
|
|
1169
|
+
}
|
|
1127
1170
|
if (!model) {
|
|
1128
1171
|
return error(id, "set_model", `Model not found: ${command.provider}/${command.modelId}`);
|
|
1129
1172
|
}
|
|
@@ -1140,6 +1183,7 @@ export async function runRpcMode(
|
|
|
1140
1183
|
}
|
|
1141
1184
|
|
|
1142
1185
|
case "get_available_models": {
|
|
1186
|
+
await session.modelRegistry.awaitBackgroundRefresh();
|
|
1143
1187
|
const models = session.getAvailableModels();
|
|
1144
1188
|
return success(id, "get_available_models", { models });
|
|
1145
1189
|
}
|
|
@@ -1277,6 +1321,34 @@ export async function runRpcMode(
|
|
|
1277
1321
|
return success(id, "get_messages", { messages: session.messages });
|
|
1278
1322
|
}
|
|
1279
1323
|
|
|
1324
|
+
case "get_messages_page": {
|
|
1325
|
+
if (session.isStreaming || session.isCompacting)
|
|
1326
|
+
return error(id, "get_messages_page", RPC_MESSAGES_PAGE_BUSY_ERROR, "session_busy");
|
|
1327
|
+
const messages = session.messages;
|
|
1328
|
+
try {
|
|
1329
|
+
return success(
|
|
1330
|
+
id,
|
|
1331
|
+
"get_messages_page",
|
|
1332
|
+
pageRpcMessages(
|
|
1333
|
+
messages,
|
|
1334
|
+
{
|
|
1335
|
+
sessionId: session.sessionId,
|
|
1336
|
+
leafId: session.sessionManager.getLeafId(),
|
|
1337
|
+
messageCount: messages.length,
|
|
1338
|
+
},
|
|
1339
|
+
{ cursor: command.cursor, limit: command.limit },
|
|
1340
|
+
),
|
|
1341
|
+
);
|
|
1342
|
+
} catch (pageError) {
|
|
1343
|
+
return error(
|
|
1344
|
+
id,
|
|
1345
|
+
"get_messages_page",
|
|
1346
|
+
pageError instanceof Error ? pageError.message : String(pageError),
|
|
1347
|
+
pageError instanceof RpcMessagesPageError ? pageError.code : undefined,
|
|
1348
|
+
);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1280
1352
|
// =================================================================
|
|
1281
1353
|
// Login
|
|
1282
1354
|
// =================================================================
|
|
@@ -19,12 +19,16 @@ import type {
|
|
|
19
19
|
SubagentProgressPayload,
|
|
20
20
|
} from "../../task";
|
|
21
21
|
import type { TodoPhase } from "../../tools/todo";
|
|
22
|
+
import type { RpcMessagesPage } from "./rpc-messages";
|
|
22
23
|
|
|
23
24
|
// ============================================================================
|
|
24
25
|
// RPC Commands (stdin)
|
|
25
26
|
// ============================================================================
|
|
26
27
|
|
|
27
28
|
export type RpcCommand =
|
|
29
|
+
// Protocol
|
|
30
|
+
| { id?: string; type: "negotiate_protocol"; protocolVersion: number }
|
|
31
|
+
|
|
28
32
|
// Prompting
|
|
29
33
|
| { id?: string; type: "prompt"; message: string; images?: ImageContent[]; streamingBehavior?: "steer" | "followUp" }
|
|
30
34
|
| { id?: string; type: "steer"; message: string; images?: ImageContent[] }
|
|
@@ -81,6 +85,7 @@ export type RpcCommand =
|
|
|
81
85
|
|
|
82
86
|
// Messages
|
|
83
87
|
| { id?: string; type: "get_messages" }
|
|
88
|
+
| { id?: string; type: "get_messages_page"; cursor?: string; limit?: number }
|
|
84
89
|
|
|
85
90
|
// Login
|
|
86
91
|
| { id?: string; type: "get_login_providers" }
|
|
@@ -132,6 +137,23 @@ export interface RpcPromptResultFrame {
|
|
|
132
137
|
agentInvoked: boolean;
|
|
133
138
|
}
|
|
134
139
|
|
|
140
|
+
export interface RpcReadyFrame {
|
|
141
|
+
type: "ready";
|
|
142
|
+
protocolVersion: 1;
|
|
143
|
+
supportedProtocolVersions: [1, 2];
|
|
144
|
+
maxFrameBytes: number;
|
|
145
|
+
maxReassembledFrameBytes: number;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface RpcChunkFrame {
|
|
149
|
+
type: "rpc_chunk";
|
|
150
|
+
chunkId: string;
|
|
151
|
+
index: number;
|
|
152
|
+
count: number;
|
|
153
|
+
byteLength: number;
|
|
154
|
+
data: string;
|
|
155
|
+
}
|
|
156
|
+
|
|
135
157
|
export interface RpcHandoffResult {
|
|
136
158
|
savedPath?: string;
|
|
137
159
|
}
|
|
@@ -168,6 +190,15 @@ export interface RpcSubagentMessagesResult {
|
|
|
168
190
|
|
|
169
191
|
// Success responses with data
|
|
170
192
|
export type RpcResponse =
|
|
193
|
+
// Protocol
|
|
194
|
+
| {
|
|
195
|
+
id?: string;
|
|
196
|
+
type: "response";
|
|
197
|
+
command: "negotiate_protocol";
|
|
198
|
+
success: true;
|
|
199
|
+
data: { protocolVersion: 2 };
|
|
200
|
+
}
|
|
201
|
+
|
|
171
202
|
// Prompting (async - events follow)
|
|
172
203
|
| { id?: string; type: "response"; command: "prompt"; success: true; data?: { agentInvoked: boolean } }
|
|
173
204
|
| { id?: string; type: "response"; command: "steer"; success: true }
|
|
@@ -284,6 +315,7 @@ export type RpcResponse =
|
|
|
284
315
|
|
|
285
316
|
// Messages
|
|
286
317
|
| { id?: string; type: "response"; command: "get_messages"; success: true; data: { messages: AgentMessage[] } }
|
|
318
|
+
| { id?: string; type: "response"; command: "get_messages_page"; success: true; data: RpcMessagesPage }
|
|
287
319
|
|
|
288
320
|
// Login
|
|
289
321
|
| {
|
|
@@ -295,8 +327,8 @@ export type RpcResponse =
|
|
|
295
327
|
}
|
|
296
328
|
| { id?: string; type: "response"; command: "login"; success: true; data: { providerId: string } }
|
|
297
329
|
|
|
298
|
-
// Error response (any command can fail)
|
|
299
|
-
| { id?: string; type: "response"; command: string; success: false; error: string };
|
|
330
|
+
// Error response (any command can fail); `code` is an optional machine-readable reason.
|
|
331
|
+
| { id?: string; type: "response"; command: string; success: false; error: string; code?: string };
|
|
300
332
|
|
|
301
333
|
// ============================================================================
|
|
302
334
|
// Subagent Events (stdout)
|
|
@@ -90,6 +90,8 @@ export async function initializeExtensions(session: AgentSession, options: Initi
|
|
|
90
90
|
setModel: model => runExtensionSetModel(session, model),
|
|
91
91
|
getThinkingLevel: () => session.thinkingLevel,
|
|
92
92
|
setThinkingLevel: level => session.setThinkingLevel(level),
|
|
93
|
+
getServiceTiers: () => session.serviceTierByFamily,
|
|
94
|
+
setServiceTier: (family, tier) => session.setServiceTierFamily(family, tier),
|
|
93
95
|
getSessionName: () => session.sessionManager.getSessionName(),
|
|
94
96
|
setSessionName: async name => {
|
|
95
97
|
await session.sessionManager.setSessionName(name, "user");
|
|
@@ -10,7 +10,8 @@ import { theme } from "../../theme/theme";
|
|
|
10
10
|
import type { SetupScene, SetupSceneController, SetupSceneHost } from "./types";
|
|
11
11
|
|
|
12
12
|
const MAX_VISIBLE_MODELS = 10;
|
|
13
|
-
|
|
13
|
+
/** ModelBrowser chrome: search row + blank above the list, blank + two detail rows below. */
|
|
14
|
+
const BROWSER_FRAME_ROWS = 5;
|
|
14
15
|
|
|
15
16
|
class ModelSceneController implements SetupSceneController {
|
|
16
17
|
title = "Choose your default model";
|
|
@@ -54,16 +55,13 @@ class ModelSceneController implements SetupSceneController {
|
|
|
54
55
|
this.#browser.routeMouse(event, line - this.#browserRowStart);
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
render(width: number): readonly string[] {
|
|
58
|
-
const visibleRows = Math.max(
|
|
59
|
-
1,
|
|
60
|
-
Math.min(MAX_VISIBLE_MODELS, this.host.ctx.ui.terminal.rows - WIZARD_SCREEN_RESERVE),
|
|
61
|
-
);
|
|
62
|
-
this.#browser.setMaxVisible(visibleRows);
|
|
58
|
+
render(width: number, maxLines?: number): readonly string[] {
|
|
63
59
|
const lines = [
|
|
64
60
|
this.#status ?? theme.fg("muted", "Type to search. Enter saves the highlighted model as your default."),
|
|
65
61
|
"",
|
|
66
62
|
];
|
|
63
|
+
const budget = maxLines === undefined ? MAX_VISIBLE_MODELS : maxLines - lines.length - BROWSER_FRAME_ROWS;
|
|
64
|
+
this.#browser.setMaxVisible(Math.max(1, Math.min(MAX_VISIBLE_MODELS, budget)));
|
|
67
65
|
this.#browserRowStart = lines.length;
|
|
68
66
|
lines.push(...this.#browser.render(width));
|
|
69
67
|
return lines;
|
|
@@ -85,10 +85,11 @@ class ProvidersSceneController implements SetupSceneController {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
render(width: number): readonly string[] {
|
|
88
|
+
render(width: number, maxLines?: number): readonly string[] {
|
|
89
89
|
const tabLines = this.#tabBar.render(width);
|
|
90
90
|
this.#tabRowCount = tabLines.length;
|
|
91
|
-
|
|
91
|
+
const tabBudget = maxLines === undefined ? undefined : Math.max(1, maxLines - tabLines.length - 1);
|
|
92
|
+
return [...tabLines, "", ...this.#activeTab().render(width, tabBudget)];
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
dispose(): void {
|
|
@@ -131,13 +131,19 @@ export class SignInTab implements SetupTab {
|
|
|
131
131
|
this.#selector.routeMouse(event, line - this.#selectorRowStart, col);
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
render(width: number): readonly string[] {
|
|
134
|
+
render(width: number, maxLines?: number): readonly string[] {
|
|
135
135
|
const lines: string[] = [];
|
|
136
136
|
if (this.#loggingInProvider) {
|
|
137
137
|
lines.push(theme.bold(`Signing in to ${this.#loggingInProvider}`));
|
|
138
138
|
} else {
|
|
139
|
-
|
|
139
|
+
// Hint + blank cost two rows; the wizard subtitle already explains
|
|
140
|
+
// this panel, so on short screens the rows go to the provider list
|
|
141
|
+
// instead (17 = full selector: 4 chrome above, 10 rows, 3 below).
|
|
142
|
+
if (maxLines === undefined || maxLines >= 17 + 2) {
|
|
143
|
+
lines.push(theme.fg("muted", "Pick a provider to sign in — you can connect more than one."), "");
|
|
144
|
+
}
|
|
140
145
|
this.#selectorRowStart = lines.length;
|
|
146
|
+
if (maxLines !== undefined) this.#selector.setMaxHeight(maxLines - lines.length);
|
|
141
147
|
lines.push(...this.#selector.render(width));
|
|
142
148
|
}
|
|
143
149
|
|
|
@@ -136,21 +136,31 @@ class ThemeSceneController implements SetupSceneController {
|
|
|
136
136
|
routeSelectListMouse(this.#selectList, event, listLine);
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
render(width: number): readonly string[] {
|
|
139
|
+
render(width: number, maxLines?: number): readonly string[] {
|
|
140
|
+
const budget = maxLines ?? Number.POSITIVE_INFINITY;
|
|
140
141
|
const lines = [
|
|
141
142
|
theme.fg("muted", "Theme changes preview live. Nothing is saved until you press Enter."),
|
|
142
143
|
this.#mode === "all"
|
|
143
144
|
? theme.fg("dim", "Browsing all themes · Esc returns to curated choices")
|
|
144
145
|
: theme.fg("dim", "Esc skips this step"),
|
|
145
146
|
"",
|
|
146
|
-
...renderThemePreview(width),
|
|
147
|
-
"",
|
|
148
147
|
];
|
|
148
|
+
// The mock status-line/editor block is decorative — the wizard itself
|
|
149
|
+
// re-renders in the highlighted theme — so it yields to the list when
|
|
150
|
+
// it would squeeze the window below the six curated rows (+1 for the
|
|
151
|
+
// list's own search-status row).
|
|
152
|
+
const preview = renderThemePreview(width);
|
|
153
|
+
if (budget - lines.length - (preview.length + 1) - 1 >= CURATED_ITEMS.length) {
|
|
154
|
+
lines.push(...preview, "");
|
|
155
|
+
}
|
|
149
156
|
if (this.#loadingAllThemes) {
|
|
150
157
|
this.#listRowStart = -1;
|
|
151
158
|
lines.push(theme.fg("dim", "Loading themes…"));
|
|
152
159
|
} else {
|
|
153
160
|
this.#listRowStart = lines.length;
|
|
161
|
+
if (maxLines !== undefined) {
|
|
162
|
+
this.#selectList.setMaxVisible(Math.max(1, Math.min(10, budget - lines.length - 1)));
|
|
163
|
+
}
|
|
154
164
|
lines.push(...this.#selectList.render(width));
|
|
155
165
|
}
|
|
156
166
|
if (this.#message) {
|