@co0ontty/wand 4.102.0 → 4.103.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/dist/ai-team-live.js +1 -1
- package/dist/apns-client.d.ts +45 -0
- package/dist/apns-client.js +156 -0
- package/dist/apns-service.d.ts +29 -0
- package/dist/apns-service.js +156 -0
- package/dist/build-info.json +3 -3
- package/dist/conversation-service.d.ts +2 -0
- package/dist/conversation-service.js +23 -3
- package/dist/conversation-types.d.ts +2 -0
- package/dist/dingtalk-auth.d.ts +41 -0
- package/dist/dingtalk-auth.js +127 -0
- package/dist/dingtalk-dws.d.ts +45 -0
- package/dist/dingtalk-dws.js +240 -0
- package/dist/dingtalk-service.d.ts +100 -0
- package/dist/dingtalk-service.js +865 -0
- package/dist/dingtalk-stream.d.ts +24 -0
- package/dist/dingtalk-stream.js +115 -0
- package/dist/dingtalk-types.d.ts +138 -0
- package/dist/dingtalk-types.js +1 -0
- package/dist/employee-knowledge.js +8 -0
- package/dist/live-activity-service.d.ts +26 -0
- package/dist/live-activity-service.js +158 -0
- package/dist/live-activity-state.d.ts +37 -0
- package/dist/live-activity-state.js +108 -0
- package/dist/middleware/api-scope.js +2 -0
- package/dist/pi-cli-checkpoint.d.ts +17 -0
- package/dist/pi-cli-checkpoint.js +10 -0
- package/dist/pi-event-projection.js +13 -0
- package/dist/pi-execution.js +2 -1
- package/dist/pi-protocol-reader.d.ts +6 -2
- package/dist/pi-protocol-reader.js +36 -12
- package/dist/pi-tmux-host.d.ts +1 -1
- package/dist/pi-tmux-host.js +28 -8
- package/dist/pi-tool-result-preview.d.ts +23 -0
- package/dist/pi-tool-result-preview.js +185 -0
- package/dist/render-daemon-client.js +1 -1
- package/dist/render-host.d.ts +3 -3
- package/dist/render-host.js +3 -3
- package/dist/server-apns-routes.d.ts +12 -0
- package/dist/server-apns-routes.js +111 -0
- package/dist/server-dingtalk-routes.d.ts +8 -0
- package/dist/server-dingtalk-routes.js +35 -0
- package/dist/server.js +49 -3
- package/dist/session-completion.d.ts +2 -1
- package/dist/session-completion.js +6 -2
- package/dist/session-registry.d.ts +3 -1
- package/dist/session-registry.js +3 -1
- package/dist/storage/apns-repository.d.ts +52 -0
- package/dist/storage/apns-repository.js +108 -0
- package/dist/storage/conversations-repository.d.ts +2 -0
- package/dist/storage/conversations-repository.js +10 -0
- package/dist/storage/dingtalk-repository.d.ts +20 -0
- package/dist/storage/dingtalk-repository.js +75 -0
- package/dist/storage/live-activity-repository.d.ts +68 -0
- package/dist/storage/live-activity-repository.js +117 -0
- package/dist/storage/migrations.js +22 -0
- package/dist/storage/sessions-repository.d.ts +5 -0
- package/dist/storage/sessions-repository.js +43 -1
- package/dist/storage-schema.js +2 -0
- package/dist/storage.d.ts +7 -1
- package/dist/storage.js +10 -0
- package/dist/structured-exec-host.d.ts +9 -1
- package/dist/structured-exec-pump.d.ts +11 -0
- package/dist/structured-exec-pump.js +27 -8
- package/dist/structured-pi-adapter.js +5 -0
- package/dist/structured-runner.d.ts +2 -0
- package/dist/structured-session-manager.d.ts +2 -0
- package/dist/structured-session-manager.js +51 -4
- package/dist/types.d.ts +7 -0
- package/dist/web-ui/content/module-manifest.json +1 -1
- package/dist/web-ui/content/modules/{4f30a913cc5b3f89 → 02fad74223254019}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{acb21c7e7fd6cb67 → 033e0a741edd2ba1}/page.js +1 -1
- package/dist/web-ui/content/modules/{e61136c22675ec7d → 0c04f94bbfd39a2f}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{119874145414688d → 12e91deb96ace24d}/shell-file-panel-drawer.js +1 -1
- package/dist/web-ui/content/modules/15279373f0e1461f/host.js +1 -0
- package/dist/web-ui/content/modules/{78a46adbf9807f89 → 17603a34d545c784}/chunk.js +1 -1
- package/dist/web-ui/content/modules/1f041aa8d3673d93/chunk.js +1 -0
- package/dist/web-ui/content/modules/{96dd661559b67ebf → 20483d6e57cfe9d7}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{f55031a02775c89e → 217595f72a1c3fc5}/chunk.js +1 -1
- package/dist/web-ui/content/modules/2230c51655163cf3/runtime.js +279 -0
- package/dist/web-ui/content/modules/{82e40fdf0abfb8cd → 2460e872ab3a1f37}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{9e754b01fa4207fb → 2a47853ca7752c1f}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{e95b60ab18a79499 → 2afbc201474adacc}/login-visual.js +1 -1
- package/dist/web-ui/content/modules/{38917c19e2cf1702 → 30169d15cea782f0}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{5513bcbacdf44588 → 37abb74d1a0a0791}/notice-view.js +1 -1
- package/dist/web-ui/content/modules/{b62294e53dcb3147 → 38bb024e09cb5d5d}/host.js +1 -1
- package/dist/web-ui/content/modules/39faef0cffbad28e/home.js +6 -0
- package/dist/web-ui/content/modules/3a2fa12c8b537964/chunk.js +1 -0
- package/dist/web-ui/content/modules/{35a8fd2cfd31d7a0 → 3a72b79418f0f80a}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{321bd9c755bc30fc → 3bd3c84c4257ba0d}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{2981483671797b92 → 3d10e3f6f6f92b55}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{6f341fe3e8aee844 → 3d4451bc069c1498}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{4a9a3e1f87b2c32b → 3d5c498522b48225}/dialog-modal.js +1 -1
- package/dist/web-ui/content/modules/426b8133daebff78/host.js +1 -0
- package/dist/web-ui/content/modules/455436229dc57d7f/sidebar-peek.js +1 -0
- package/dist/web-ui/content/modules/46de3c2e2ca680c1/host.js +1 -0
- package/dist/web-ui/content/modules/47af6b35e1d01ea7/host.js +30 -0
- package/dist/web-ui/content/modules/4841e45cb7c5cc67/queue-view-adapter.js +3 -0
- package/dist/web-ui/content/modules/{47419445e0aaa3cc → 497789a07177ed7b}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{32c461164a1b364c → 5472191457a926d3}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{c152782274c04fad → 55a47b6e2c2016f2}/chunk.js +1 -1
- package/dist/web-ui/content/modules/5743d8116d15696b/employee-card.js +1 -0
- package/dist/web-ui/content/modules/{c81a3d95b14cdcd4 → 578703196f82222d}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{8ce71665ae55ccf1 → 57d68f0a47e8fd7c}/toast-region.js +1 -1
- package/dist/web-ui/content/modules/58866143a3365521/chunk.js +1 -0
- package/dist/web-ui/content/modules/{3601736ae2d678ef → 5bb5db89a343817f}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{a098c7b1aa4e2f6b → 5c9e8245d4a8a312}/sha256.js +1 -1
- package/dist/web-ui/content/modules/{8198bfd269c0caca → 5de35db482fb1a42}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{aab92f2990a7185f → 5e7104e569359449}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{4b9deeeb77570db3 → 601a2767ae24d1cb}/chunk.js +1 -1
- package/dist/web-ui/content/modules/6687ad7aa57f443b/task-board-host.js +22 -0
- package/dist/web-ui/content/modules/{c422f47fd13dc4bc → 684db83476dcba20}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{cf715621edb1d12e → 6b0bafa239dfe4e2}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{cd22aa55f6cc5276 → 6bd6ee3c9f05e2f5}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{09ef1bb1d195d727 → 79b0c18f2bf35740}/host.js +1 -1
- package/dist/web-ui/content/modules/{905f8bc3f8a3efb8 → 7ae83a3f06f2daa3}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{84b79a33c64e146c → 7f32261c1627c3cd}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{eece3c2d2374e8f7 → 805b859b1b76ce4d}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{eecaebc5482ee203 → 84358b5b729cbe48}/chunk.js +1 -1
- package/dist/web-ui/content/modules/85181d84d5ce62b3/employee-profile.js +1 -0
- package/dist/web-ui/content/modules/87edd7cc79811bb8/composer-surface-adapter.js +1 -0
- package/dist/web-ui/content/modules/{b0254a2d3652a925 → 8ea498cf092dba30}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{a4af270960a39ea6 → 8f0d4e22c18a58b0}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{c72c4f1d47c3726d → 907dcf0ea5b5f23f}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{9f33acc1c4284e03 → 916021ce252882ca}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{98c14fc0474455a5 → 91ee32135a3582b0}/chunk.js +1 -1
- package/dist/web-ui/content/modules/92df279c40bc4218/host.js +1 -0
- package/dist/web-ui/content/modules/{544dc812abdfbbb8 → 9661b076f08e64b4}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{5c94fb2b7abcd1a5 → 97ef63ff1f0784d9}/chunk.js +1 -1
- package/dist/web-ui/content/modules/99e48e07517d3f83/dropdown-surface.js +1 -0
- package/dist/web-ui/content/modules/9eb83751622e2b97/chunk.js +10 -0
- package/dist/web-ui/content/modules/{7375574a2ea42740 → a1715be0f97c2479}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{456634525d44777d → a2be1fd01cf06837}/chunk.js +1 -1
- package/dist/web-ui/content/modules/a33666b143331cd6/workspace-window.js +1 -0
- package/dist/web-ui/content/modules/{b38924f45bfa71d6 → a41351130003e6bb}/chunk.js +1 -1
- package/dist/web-ui/content/modules/a59c83b5b5b85da2/host.js +1 -0
- package/dist/web-ui/content/modules/{a4dca2ffaf656d3c → a63f0b3d73d97e0b}/chunk.js +1 -1
- package/dist/web-ui/content/modules/a685793c5d9b90d1/chunk.js +1 -0
- package/dist/web-ui/content/modules/a750f4868304b089/host.js +1 -0
- package/dist/web-ui/content/modules/abfe4b88dcd89d88/host.js +1 -0
- package/dist/web-ui/content/modules/{15bcc550ef9fd5b7 → acda148b7423bdab}/host.js +1 -1
- package/dist/web-ui/content/modules/{ad1136919a65fc6e → af6ff78aeb782cf2}/host.js +1 -1
- package/dist/web-ui/content/modules/b32c9addcd31f54b/panel-host.js +1 -0
- package/dist/web-ui/content/modules/{b937f1a939618be9 → b358eaf2e45849a6}/host.js +1 -1
- package/dist/web-ui/content/modules/{476ff7911bce1a38 → b7aeeb22bf604ec7}/chunk.js +1 -1
- package/dist/web-ui/content/modules/bad2c42c7ddeab94/host.js +1 -0
- package/dist/web-ui/content/modules/{622a8d12c4c622bf → bb59e8cb4323bf06}/host.js +1 -1
- package/dist/web-ui/content/modules/{08c7b047195f138d → be497f1d39753979}/chunk.js +1 -1
- package/dist/web-ui/content/modules/bfa8e2c827201374/chunk.js +1 -0
- package/dist/web-ui/content/modules/{5395fa57a47e9b96 → bfe51130a84795fb}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{8e29056fb56439f7 → c03e0f84f798d33a}/chunk.js +1 -1
- package/dist/web-ui/content/modules/c118e0c96bc023ee/host-bridge.js +1 -0
- package/dist/web-ui/content/modules/c3512b18c4c0e4ac/brand-identity.js +1 -0
- package/dist/web-ui/content/modules/c3e0de0a61f0db34/chunk.js +1 -0
- package/dist/web-ui/content/modules/{9d48d1ea3c9228bd → c3e1f083e07d6840}/library-bridge.js +1 -1
- package/dist/web-ui/content/modules/cd5e90a37ccdf2dc/group-editor.js +1 -0
- package/dist/web-ui/content/modules/{2edc19639ac1bd4b → cdb8281bb89da303}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{f6f86d43c12b0864 → ce90d0b55bbe6430}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{72b13a7486b6d437 → d274491ffc8ee646}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{b1d43ed06a5d1a57 → d3cc94cf4a47a12a}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{b0c4eed46c1b6ad4 → d4b07cef43d36e79}/chat-render.js +5 -4
- package/dist/web-ui/content/modules/{8a4f0ef2c0812af6 → d7b540917fb90755}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{ef7f76b551fdc619 → d856bfc56bea5f20}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{42c439d3acfe22bd → d857ea8244b1026e}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{36ce8123a2079807 → d9019922118b2c37}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{58836e616e6d12ff → d90e77f9f8d4f1ec}/workspaces-panel.js +1 -1
- package/dist/web-ui/content/modules/{4d4870f20eb1c52d → da1aa5fa0186fea7}/chunk.js +1 -1
- package/dist/web-ui/content/modules/de977ff0b3ad3882/progress.js +1 -0
- package/dist/web-ui/content/modules/{222f48948517167e → e01769df89873feb}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{5df1b522f5d3c99d → e0ab9fa5a8882433}/host.js +1 -1
- package/dist/web-ui/content/modules/e25dea52e13504b6/markdown.js +1 -0
- package/dist/web-ui/content/modules/{36bd608366c56b72 → e43af4aa407885fd}/chunk.js +1 -1
- package/dist/web-ui/content/modules/ee893f06cdb2b4f7/panel.js +1 -0
- package/dist/web-ui/content/modules/{f90f80d7207dff1f → f2bdbdc4a1141826}/host.js +1 -1
- package/dist/web-ui/content/modules/{e33f6d66a02ec26c → f36b4d3d44491852}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{665e81fe210f1b7a → fa79ed5d045c24dd}/chunk.js +1 -1
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/embedded-assets.d.ts +412 -406
- package/dist/web-ui/embedded-assets.js +4 -4
- package/package.json +1 -1
- package/scripts/install-render-binary.js +1 -1
- package/dist/web-ui/content/modules/1a7303b87763fd43/employee-profile.js +0 -1
- package/dist/web-ui/content/modules/1d35739f634745f6/task-board-host.js +0 -22
- package/dist/web-ui/content/modules/2099d37d57856cbd/brand-identity.js +0 -1
- package/dist/web-ui/content/modules/2d6339b2f6c2e2d0/host.js +0 -1
- package/dist/web-ui/content/modules/2f88fc37c6105f7e/sidebar-peek.js +0 -1
- package/dist/web-ui/content/modules/31124fb7ee6939df/host.js +0 -1
- package/dist/web-ui/content/modules/31b28c5d0019c363/host-bridge.js +0 -1
- package/dist/web-ui/content/modules/36e5c3a6d6705358/employee-card.js +0 -1
- package/dist/web-ui/content/modules/3700d99c54d3c400/host.js +0 -1
- package/dist/web-ui/content/modules/3ae29086b227f9d9/markdown.js +0 -1
- package/dist/web-ui/content/modules/42db858e4417c702/chunk.js +0 -10
- package/dist/web-ui/content/modules/443dc81eac29f233/host.js +0 -1
- package/dist/web-ui/content/modules/4af10e1f8a789204/runtime.js +0 -275
- package/dist/web-ui/content/modules/55b75ebcf6924001/host.js +0 -30
- package/dist/web-ui/content/modules/626c7ce0aa1714b0/host.js +0 -1
- package/dist/web-ui/content/modules/76adde4019731df1/chunk.js +0 -1
- package/dist/web-ui/content/modules/7925b65867b7aa4a/queue-view-adapter.js +0 -3
- package/dist/web-ui/content/modules/8a6844a4251a1271/chunk.js +0 -1
- package/dist/web-ui/content/modules/8bd214570df4a7a0/host.js +0 -1
- package/dist/web-ui/content/modules/8bff85e108320aab/panel.js +0 -1
- package/dist/web-ui/content/modules/988e01b67e6f1883/composer-surface-adapter.js +0 -1
- package/dist/web-ui/content/modules/a975ff38c3d43ba8/workspace-window.js +0 -1
- package/dist/web-ui/content/modules/b550260fc9c5363a/dropdown-surface.js +0 -1
- package/dist/web-ui/content/modules/bce467c603299460/chunk.js +0 -1
- package/dist/web-ui/content/modules/c138b442c1682189/panel-host.js +0 -1
- package/dist/web-ui/content/modules/c32d14632487364f/group-editor.js +0 -1
- package/dist/web-ui/content/modules/c6d0a1b2753a4883/host.js +0 -1
- package/dist/web-ui/content/modules/cdff6e7f72879c88/progress.js +0 -1
- package/dist/web-ui/content/modules/cfee7b0d79587d59/home.js +0 -6
- package/dist/web-ui/content/modules/e5e93fe5304838a1/chunk.js +0 -1
- package/dist/web-ui/content/modules/fbe35e7ddd7384fa/host.js +0 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { projectPiToolResult } from "./pi-tool-result-preview.js";
|
|
1
2
|
/** Fields consumed by Wand's Pi reducer. Context/entries are CLI-owned history,
|
|
2
3
|
* not new output. Never journal repeated copies of that history in native mode. */
|
|
3
4
|
export const PI_PROTOCOL_FIELDS = new Set([
|
|
@@ -24,5 +25,17 @@ export function projectPiProtocolEvent(event) {
|
|
|
24
25
|
const last = lastPiAssistantMessage(event);
|
|
25
26
|
projected.messages = last ? [last] : [];
|
|
26
27
|
}
|
|
28
|
+
const message = event.message;
|
|
29
|
+
if (message && typeof message === "object" && !Array.isArray(message)
|
|
30
|
+
&& ["user", "toolResult"].includes(String(message.role))) {
|
|
31
|
+
// These are duplicate transcript snapshots. tool_execution_end already
|
|
32
|
+
// carries the result; do not serialize its image/body a second time.
|
|
33
|
+
projected.message = { role: message.role };
|
|
34
|
+
}
|
|
35
|
+
if (event.type === "tool_execution_end" || event.type === "tool_execution_update") {
|
|
36
|
+
const key = event.type === "tool_execution_end" ? "result" : "partialResult";
|
|
37
|
+
if (key in projected)
|
|
38
|
+
projected[key] = projectPiToolResult(projected[key]);
|
|
39
|
+
}
|
|
27
40
|
return projected;
|
|
28
41
|
}
|
package/dist/pi-execution.js
CHANGED
|
@@ -79,7 +79,8 @@ export function projectPiExecution(raw, source, fallbackState) {
|
|
|
79
79
|
version: 1, source, mode: text(d.mode) ?? "workflow", name: text(d.name),
|
|
80
80
|
runId: text(d.runId ?? children.workflowRunId ?? d.asyncId),
|
|
81
81
|
state: state(children.workflowState ?? d.state ?? (d.asyncId ? "background" : fallbackState)),
|
|
82
|
-
inventoryComplete:
|
|
82
|
+
inventoryComplete: d.inventoryComplete === false ? false
|
|
83
|
+
: typeof children.inventoryComplete === "boolean" ? children.inventoryComplete : source === "tool-result" && !d.asyncId,
|
|
83
84
|
updatedAt: number(d.lastUpdate ?? d.updatedAt), nodes: rows.slice(0, MAX_NODES),
|
|
84
85
|
trace: trace.slice(-MAX_TRACE).map(t => ({ key: text(t.key) ?? "", operation: text(t.operation) ?? "", state: text(t.state) ?? "unknown",
|
|
85
86
|
phase: text(t.phase), durationMs: number(t.durationMs), error: text(t.error, 1000) })),
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* no regex/type-prefix guessing and no JSON truncation. Field order and chunk
|
|
3
3
|
* boundaries do not matter. The only retained history member is the latest
|
|
4
4
|
* assistant, which reconciles the streamed reply and preserves terminal errors.
|
|
5
|
-
*
|
|
6
|
-
* original
|
|
5
|
+
* Tool results get a bounded display preview when their exact value is too
|
|
6
|
+
* large; Pi retains the original for its own model context. Assistant payloads
|
|
7
|
+
* and other providers retain their strict safety limits. */
|
|
7
8
|
export declare class PiProtocolReader {
|
|
8
9
|
private readonly onLine;
|
|
9
10
|
private parser;
|
|
@@ -13,6 +14,7 @@ export declare class PiProtocolReader {
|
|
|
13
14
|
private key;
|
|
14
15
|
private inKey;
|
|
15
16
|
private field;
|
|
17
|
+
private toolPreview;
|
|
16
18
|
private fieldDepth;
|
|
17
19
|
private messages;
|
|
18
20
|
private latestAssistant;
|
|
@@ -25,6 +27,8 @@ export declare class PiProtocolReader {
|
|
|
25
27
|
});
|
|
26
28
|
push(text: string): void;
|
|
27
29
|
finish(): void;
|
|
30
|
+
/** Checkpoints may only advance after complete records, never mid-string. */
|
|
31
|
+
get atBoundary(): boolean;
|
|
28
32
|
private tokenize;
|
|
29
33
|
private consume;
|
|
30
34
|
private token;
|
|
@@ -3,12 +3,13 @@ import * as streamJson from "stream-json/core/parser.js";
|
|
|
3
3
|
import Assembler from "stream-json/core/assembler.js";
|
|
4
4
|
import { MAX_PROTOCOL_LINE_BYTES } from "./bounded-process-output.js";
|
|
5
5
|
import { PI_PROTOCOL_FIELDS } from "./pi-event-projection.js";
|
|
6
|
+
import { PiToolResultPreview, projectPiToolResult } from "./pi-tool-result-preview.js";
|
|
6
7
|
const { jsonParser } = streamJson;
|
|
7
8
|
const CHUNK_CHARS = 16 * 1024;
|
|
8
9
|
const MAX_DEPTH = 128;
|
|
9
10
|
const MAX_KEY_CHARS = 1024;
|
|
10
|
-
const MAX_ENVELOPE_BYTES = 256 * 1024 * 1024;
|
|
11
11
|
const HISTORY_EVENTS = new Set(["agent_end", "turn_end", "message_start", "message_end", "message_update"]);
|
|
12
|
+
const TOOL_RESULT_EVENTS = new Set(["tool_execution_end", "tool_execution_update"]);
|
|
12
13
|
const failure = (reason) => new Error(`Pi CLI 协议${reason};本轮已停止,送达结果未知,不自动重试。`);
|
|
13
14
|
/** A single retained value, never the entire history array. Both strings and
|
|
14
15
|
* structural tokens count toward the budget; overflow releases partial objects.
|
|
@@ -24,7 +25,7 @@ class BoundedValue {
|
|
|
24
25
|
role = "";
|
|
25
26
|
overflow = false;
|
|
26
27
|
consume(token) {
|
|
27
|
-
this.bytes += "value" in token && typeof token.value === "string" ? Buffer.byteLength(token.value) + 16 : 16;
|
|
28
|
+
this.bytes += "value" in token && typeof token.value === "string" ? Buffer.byteLength(JSON.stringify(token.value)) + 16 : 16;
|
|
28
29
|
if (this.bytes > MAX_PROTOCOL_LINE_BYTES) {
|
|
29
30
|
this.overflow = true;
|
|
30
31
|
this.assembler = null;
|
|
@@ -72,8 +73,9 @@ class BoundedValue {
|
|
|
72
73
|
* no regex/type-prefix guessing and no JSON truncation. Field order and chunk
|
|
73
74
|
* boundaries do not matter. The only retained history member is the latest
|
|
74
75
|
* assistant, which reconciles the streamed reply and preserves terminal errors.
|
|
75
|
-
*
|
|
76
|
-
* original
|
|
76
|
+
* Tool results get a bounded display preview when their exact value is too
|
|
77
|
+
* large; Pi retains the original for its own model context. Assistant payloads
|
|
78
|
+
* and other providers retain their strict safety limits. */
|
|
77
79
|
export class PiProtocolReader {
|
|
78
80
|
onLine;
|
|
79
81
|
parser = null;
|
|
@@ -83,6 +85,7 @@ export class PiProtocolReader {
|
|
|
83
85
|
key = "";
|
|
84
86
|
inKey = false;
|
|
85
87
|
field = null;
|
|
88
|
+
toolPreview = null;
|
|
86
89
|
fieldDepth = 0;
|
|
87
90
|
messages = false;
|
|
88
91
|
latestAssistant;
|
|
@@ -118,8 +121,8 @@ export class PiProtocolReader {
|
|
|
118
121
|
}
|
|
119
122
|
else {
|
|
120
123
|
this.parser ??= jsonParser({ packValues: false });
|
|
121
|
-
|
|
122
|
-
|
|
124
|
+
// SAX validates discarded bodies without retaining them. Envelope
|
|
125
|
+
// size is not a memory budget; retained fields have their own caps.
|
|
123
126
|
this.tokenize(part);
|
|
124
127
|
}
|
|
125
128
|
}
|
|
@@ -129,6 +132,8 @@ export class PiProtocolReader {
|
|
|
129
132
|
}
|
|
130
133
|
}
|
|
131
134
|
finish() { this.endLine(); }
|
|
135
|
+
/** Checkpoints may only advance after complete records, never mid-string. */
|
|
136
|
+
get atBoundary() { return !this.parser && this.noise === null && !this.discardHead; }
|
|
132
137
|
tokenize(input) {
|
|
133
138
|
let tokens;
|
|
134
139
|
try {
|
|
@@ -167,8 +172,11 @@ export class PiProtocolReader {
|
|
|
167
172
|
this.inKey = false;
|
|
168
173
|
if (this.depth === 1)
|
|
169
174
|
this.rootKey = this.key;
|
|
170
|
-
else
|
|
171
|
-
|
|
175
|
+
else {
|
|
176
|
+
const keyToken = { name: "keyValue", value: this.key };
|
|
177
|
+
this.field?.consume(keyToken);
|
|
178
|
+
this.toolPreview?.consume(keyToken);
|
|
179
|
+
}
|
|
172
180
|
return;
|
|
173
181
|
}
|
|
174
182
|
const opening = token.name === "startObject" || token.name === "startArray";
|
|
@@ -186,6 +194,8 @@ export class PiProtocolReader {
|
|
|
186
194
|
}
|
|
187
195
|
else if (PI_PROTOCOL_FIELDS.has(this.rootKey)) {
|
|
188
196
|
this.field = new BoundedValue();
|
|
197
|
+
if (this.rootKey === "result" || this.rootKey === "partialResult")
|
|
198
|
+
this.toolPreview = new PiToolResultPreview();
|
|
189
199
|
this.fieldDepth = 1;
|
|
190
200
|
}
|
|
191
201
|
}
|
|
@@ -194,6 +204,7 @@ export class PiProtocolReader {
|
|
|
194
204
|
this.fieldDepth = 2;
|
|
195
205
|
}
|
|
196
206
|
this.field?.consume(token);
|
|
207
|
+
this.toolPreview?.consume(token);
|
|
197
208
|
if (opening) {
|
|
198
209
|
this.depth++;
|
|
199
210
|
if (this.depth > MAX_DEPTH)
|
|
@@ -212,12 +223,15 @@ export class PiProtocolReader {
|
|
|
212
223
|
}
|
|
213
224
|
}
|
|
214
225
|
else {
|
|
215
|
-
|
|
226
|
+
const transcriptOnly = this.rootKey === "message" && ["user", "toolResult"].includes(this.field.role);
|
|
227
|
+
if (this.field.overflow && !this.toolPreview && !transcriptOnly) {
|
|
216
228
|
throw failure(`有效载荷 ${this.rootKey} 超过 8 MiB 安全容量`);
|
|
217
229
|
}
|
|
218
|
-
this.event[this.rootKey] =
|
|
230
|
+
this.event[this.rootKey] = transcriptOnly ? { role: this.field.role }
|
|
231
|
+
: this.field.overflow ? this.toolPreview?.value ?? { role: this.field.role } : this.field.value;
|
|
219
232
|
}
|
|
220
233
|
this.field = null;
|
|
234
|
+
this.toolPreview = null;
|
|
221
235
|
}
|
|
222
236
|
if (this.messages && closing && this.depth === 1) {
|
|
223
237
|
this.event.messages = this.latestAssistant ? [this.latestAssistant] : [];
|
|
@@ -248,10 +262,19 @@ export class PiProtocolReader {
|
|
|
248
262
|
throw failure("事件不完整");
|
|
249
263
|
if (typeof this.event.type !== "string")
|
|
250
264
|
throw failure("事件缺少类型");
|
|
251
|
-
if (this.rawBytes > MAX_PROTOCOL_LINE_BYTES && !HISTORY_EVENTS.has(this.event.type)) {
|
|
265
|
+
if (this.rawBytes > MAX_PROTOCOL_LINE_BYTES && !HISTORY_EVENTS.has(this.event.type) && !TOOL_RESULT_EVENTS.has(this.event.type)) {
|
|
252
266
|
throw failure("非汇总事件超过 8 MiB 安全容量");
|
|
253
267
|
}
|
|
254
|
-
|
|
268
|
+
let line = JSON.stringify(this.event);
|
|
269
|
+
if (Buffer.byteLength(line) > MAX_PROTOCOL_LINE_BYTES && TOOL_RESULT_EVENTS.has(this.event.type)) {
|
|
270
|
+
// The aggregate may exceed the transport budget even when each field
|
|
271
|
+
// fits (or when JSON escapes expand a string). Keep the event whole.
|
|
272
|
+
for (const key of ["result", "partialResult"]) {
|
|
273
|
+
if (key in this.event)
|
|
274
|
+
this.event[key] = projectPiToolResult(this.event[key], true);
|
|
275
|
+
}
|
|
276
|
+
line = JSON.stringify(this.event);
|
|
277
|
+
}
|
|
255
278
|
if (Buffer.byteLength(line) > MAX_PROTOCOL_LINE_BYTES)
|
|
256
279
|
throw failure("有效载荷超过 8 MiB 安全容量");
|
|
257
280
|
this.onLine(line);
|
|
@@ -271,6 +294,7 @@ export class PiProtocolReader {
|
|
|
271
294
|
this.rootKey = this.key = "";
|
|
272
295
|
this.inKey = this.messages = false;
|
|
273
296
|
this.field = null;
|
|
297
|
+
this.toolPreview = null;
|
|
274
298
|
this.latestAssistant = undefined;
|
|
275
299
|
this.rawBytes = 0;
|
|
276
300
|
}
|
package/dist/pi-tmux-host.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ export declare class PiTmuxHost implements StructuredExecHost {
|
|
|
36
36
|
state(job: NativeJob): WorkerState;
|
|
37
37
|
private snapshot;
|
|
38
38
|
spawnStructured(request: StructuredSpawnRequest): Promise<StructuredExecProcess>;
|
|
39
|
-
attachRun(runId: string): Promise<StructuredRunState | null>;
|
|
39
|
+
attachRun(runId: string, checkpoint?: import("./pi-cli-checkpoint.js").PiCliCheckpoint): Promise<StructuredRunState | null>;
|
|
40
40
|
adoptRun(runId: string): Promise<StructuredExecProcess | null>;
|
|
41
41
|
listRuns(): Promise<StructuredRunState[]>;
|
|
42
42
|
kill(job: NativeJob): Promise<void>;
|
package/dist/pi-tmux-host.js
CHANGED
|
@@ -49,6 +49,9 @@ class NativeProcess {
|
|
|
49
49
|
this.offsets.stderr = after.stderrSeq;
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
+
get protocolSource() {
|
|
53
|
+
return { file: this.job.events, ...this.identities.stdout };
|
|
54
|
+
}
|
|
52
55
|
get pid() { return this.host.state(this.job).pid; }
|
|
53
56
|
interrupt() { void this.host.kill(this.job).catch((error) => logInfo(`Pi native stop failed: ${error instanceof Error ? error.message : String(error)}`)); }
|
|
54
57
|
onStream(listener) {
|
|
@@ -177,25 +180,42 @@ export class PiTmuxHost {
|
|
|
177
180
|
}
|
|
178
181
|
return value;
|
|
179
182
|
}
|
|
180
|
-
snapshot(job) {
|
|
183
|
+
snapshot(job, checkpoint) {
|
|
181
184
|
const state = this.state(job);
|
|
185
|
+
let stdoutFrom;
|
|
182
186
|
const log = (file) => {
|
|
183
187
|
const fd = openSync(file, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
184
188
|
try {
|
|
185
|
-
const
|
|
186
|
-
const
|
|
189
|
+
const stat = fstatSync(fd);
|
|
190
|
+
const size = stat.size;
|
|
191
|
+
const matched = file === job.events && checkpoint?.incarnationId === job.incarnationId
|
|
192
|
+
&& checkpoint.source.file === file && checkpoint.source.dev === stat.dev && checkpoint.source.ino === stat.ino
|
|
193
|
+
&& checkpoint.offset <= size && size - checkpoint.offset <= REPLAY_BYTES;
|
|
194
|
+
// A checkpoint is usable only if it ends at a real source record
|
|
195
|
+
// boundary. Never splice an arbitrary byte position into JSON.
|
|
196
|
+
let boundary = checkpoint?.offset === 0;
|
|
197
|
+
if (matched && checkpoint.offset > 0) {
|
|
198
|
+
const byte = Buffer.alloc(1);
|
|
199
|
+
boundary = readSync(fd, byte, 0, 1, checkpoint.offset - 1) === 1 && byte[0] === 10;
|
|
200
|
+
}
|
|
201
|
+
const start = matched && boundary ? checkpoint.offset : Math.max(0, size - REPLAY_BYTES);
|
|
202
|
+
if (matched && boundary)
|
|
203
|
+
stdoutFrom = start;
|
|
204
|
+
const count = size - start;
|
|
187
205
|
const bytes = Buffer.alloc(count);
|
|
188
206
|
let read = 0;
|
|
189
207
|
while (read < count) {
|
|
190
|
-
const n = readSync(fd, bytes, read, count - read,
|
|
208
|
+
const n = readSync(fd, bytes, read, count - read, start + read);
|
|
191
209
|
if (!n)
|
|
192
210
|
break;
|
|
193
211
|
read += n;
|
|
194
212
|
}
|
|
213
|
+
if (read !== count)
|
|
214
|
+
throw new Error("Pi 原生事件文件读取不完整。");
|
|
195
215
|
let begin = 0;
|
|
196
216
|
while (begin < read && (bytes[begin] & 0xc0) === 0x80)
|
|
197
217
|
begin++;
|
|
198
|
-
return { text: bytes.subarray(begin, read).toString("utf8"), size, truncated: size > count };
|
|
218
|
+
return { text: bytes.subarray(begin, read).toString("utf8"), size, truncated: (file !== job.events || stdoutFrom === undefined) && size > count };
|
|
199
219
|
}
|
|
200
220
|
finally {
|
|
201
221
|
closeSync(fd);
|
|
@@ -208,7 +228,7 @@ export class PiTmuxHost {
|
|
|
208
228
|
signal: state.signal ? signalNumberFromName(state.signal) : null,
|
|
209
229
|
stdoutSeq: stdout.size, stderrSeq: stderr.size, stdoutLog: stdout.text, stderrLog: stderr.text,
|
|
210
230
|
stdoutTruncated: stdout.truncated, stderrTruncated: stderr.truncated,
|
|
211
|
-
nativeFile: job.events };
|
|
231
|
+
nativeFile: job.events, ...(stdoutFrom !== undefined ? { stdoutFrom } : {}) };
|
|
212
232
|
}
|
|
213
233
|
async spawnStructured(request) {
|
|
214
234
|
if (!request.nativePi) {
|
|
@@ -327,10 +347,10 @@ export class PiTmuxHost {
|
|
|
327
347
|
rmSync(launchDir, { recursive: true, force: true });
|
|
328
348
|
}
|
|
329
349
|
}
|
|
330
|
-
async attachRun(runId) {
|
|
350
|
+
async attachRun(runId, checkpoint) {
|
|
331
351
|
const job = this.job(runId);
|
|
332
352
|
if (job && !existsSync(job.ack)) {
|
|
333
|
-
const state = this.snapshot(job);
|
|
353
|
+
const state = this.snapshot(job, checkpoint);
|
|
334
354
|
this.observed.set(runId, state);
|
|
335
355
|
this.owners.set(runId, "native");
|
|
336
356
|
return state;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Token } from "stream-json/core/parser.js";
|
|
2
|
+
/** A display-only fallback, built alongside the exact bounded value. It never
|
|
3
|
+
* changes Pi's own tool result/model context. Independent root-field budgets
|
|
4
|
+
* keep a large content body from swallowing later execution details (or vice
|
|
5
|
+
* versa). All tokens, including omitted subtrees, still pass the JSON parser. */
|
|
6
|
+
export declare class PiToolResultPreview {
|
|
7
|
+
private readonly budgets;
|
|
8
|
+
private readonly stack;
|
|
9
|
+
private root;
|
|
10
|
+
private scalar;
|
|
11
|
+
private tail;
|
|
12
|
+
private scalarOmitted;
|
|
13
|
+
private scalarLimit;
|
|
14
|
+
private detailsOmitted;
|
|
15
|
+
private budget;
|
|
16
|
+
private attach;
|
|
17
|
+
consume(token: Token): void;
|
|
18
|
+
private keep;
|
|
19
|
+
get value(): Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
/** Native extension events already exist as objects. Walk them in small scalar
|
|
22
|
+
* slices instead of allocating a second giant JSON string before projection. */
|
|
23
|
+
export declare function projectPiToolResult(value: unknown, forcePreview?: boolean): unknown;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { MAX_PROTOCOL_LINE_BYTES } from "./bounded-process-output.js";
|
|
2
|
+
const GROUP_BYTES = 192 * 1024;
|
|
3
|
+
const STRING_CHARS = 16 * 1024;
|
|
4
|
+
const NOTICE = "[工具结果较大,Wand 仅展示有界预览;工具执行与后续答复继续。]";
|
|
5
|
+
const OMITTED_IMAGE = "[大图片未内嵌到工具结果预览中]";
|
|
6
|
+
/** A display-only fallback, built alongside the exact bounded value. It never
|
|
7
|
+
* changes Pi's own tool result/model context. Independent root-field budgets
|
|
8
|
+
* keep a large content body from swallowing later execution details (or vice
|
|
9
|
+
* versa). All tokens, including omitted subtrees, still pass the JSON parser. */
|
|
10
|
+
export class PiToolResultPreview {
|
|
11
|
+
budgets = new Map();
|
|
12
|
+
stack = [];
|
|
13
|
+
root;
|
|
14
|
+
scalar = "";
|
|
15
|
+
tail = "";
|
|
16
|
+
scalarOmitted = false;
|
|
17
|
+
scalarLimit = STRING_CHARS;
|
|
18
|
+
detailsOmitted = false;
|
|
19
|
+
budget() {
|
|
20
|
+
const field = this.stack[0]?.key;
|
|
21
|
+
const group = field === "content" || field === "details" ? field : "other";
|
|
22
|
+
let budget = this.budgets.get(group);
|
|
23
|
+
if (!budget)
|
|
24
|
+
this.budgets.set(group, budget = { remaining: GROUP_BYTES });
|
|
25
|
+
return this.stack.length > 1 ? this.stack.at(-1).budget : budget;
|
|
26
|
+
}
|
|
27
|
+
attach(value, omitted) {
|
|
28
|
+
const parent = this.stack.at(-1);
|
|
29
|
+
if (!parent) {
|
|
30
|
+
this.root = value;
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
parent.omitted ||= omitted;
|
|
34
|
+
if (this.stack.length === 1 && parent.key === "details")
|
|
35
|
+
this.detailsOmitted ||= omitted;
|
|
36
|
+
if (!parent.retained || value === undefined)
|
|
37
|
+
return;
|
|
38
|
+
if (Array.isArray(parent.value))
|
|
39
|
+
parent.value.push(value);
|
|
40
|
+
else
|
|
41
|
+
Object.defineProperty(parent.value, parent.key, { value, enumerable: true, configurable: true, writable: true });
|
|
42
|
+
}
|
|
43
|
+
consume(token) {
|
|
44
|
+
if (token.name === "keyValue") {
|
|
45
|
+
const parent = this.stack.at(-1);
|
|
46
|
+
if (parent)
|
|
47
|
+
parent.key = token.value;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (token.name === "startObject" || token.name === "startArray") {
|
|
51
|
+
const budget = this.budget();
|
|
52
|
+
const key = this.stack.at(-1)?.key ?? "";
|
|
53
|
+
const cost = 32 + Buffer.byteLength(JSON.stringify(key));
|
|
54
|
+
const retained = (this.stack.at(-1)?.retained ?? true) && budget.remaining >= cost;
|
|
55
|
+
if (retained)
|
|
56
|
+
budget.remaining -= cost;
|
|
57
|
+
this.stack.push({ value: token.name === "startArray" ? [] : {}, key: "", budget, retained, omitted: !retained });
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (token.name === "endObject" || token.name === "endArray") {
|
|
61
|
+
const frame = this.stack.pop();
|
|
62
|
+
let value = frame.retained ? frame.value : undefined;
|
|
63
|
+
// A shortened base64/data URL is not a valid image. Replace its complete
|
|
64
|
+
// part with readable text rather than sending corrupted image data.
|
|
65
|
+
if (frame.omitted && !Array.isArray(value) && (value?.type === "image" || value?.type === "image_url")) {
|
|
66
|
+
value = { type: "text", text: OMITTED_IMAGE };
|
|
67
|
+
}
|
|
68
|
+
this.attach(value, frame.omitted);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (token.name === "startString" || token.name === "startNumber") {
|
|
72
|
+
this.scalar = this.tail = "";
|
|
73
|
+
this.scalarOmitted = false;
|
|
74
|
+
// Worst-case JSON escaping costs six bytes per character. Leave room
|
|
75
|
+
// for the omission marker and key so the assembled fallback stays small.
|
|
76
|
+
this.scalarLimit = Math.min(STRING_CHARS, Math.max(0, Math.floor((this.budget().remaining - 512) / 6)));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (token.name === "stringChunk" || token.name === "numberChunk") {
|
|
80
|
+
const room = this.scalarLimit - this.scalar.length;
|
|
81
|
+
this.scalar += token.value.slice(0, room);
|
|
82
|
+
if (token.value.length > room) {
|
|
83
|
+
this.scalarOmitted = true;
|
|
84
|
+
this.tail = this.scalarLimit ? (this.tail + token.value.slice(room)).slice(-this.scalarLimit) : "";
|
|
85
|
+
}
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (token.name === "endString" || token.name === "endNumber") {
|
|
89
|
+
// Prefix/tail cuts may land between the two UTF-16 halves of a scalar.
|
|
90
|
+
const head = this.scalarOmitted && /[\uD800-\uDBFF]$/.test(this.scalar) ? this.scalar.slice(0, -1) : this.scalar;
|
|
91
|
+
const tail = /^[\uDC00-\uDFFF]/.test(this.tail) ? this.tail.slice(1) : this.tail;
|
|
92
|
+
const value = token.name === "endNumber" ? (this.scalarOmitted ? null : Number(this.scalar))
|
|
93
|
+
: this.scalarOmitted ? head + "\n[…预览省略…]\n" + tail : this.scalar;
|
|
94
|
+
this.keep(value, this.scalarOmitted);
|
|
95
|
+
this.scalar = this.tail = "";
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (token.name === "trueValue" || token.name === "falseValue" || token.name === "nullValue") {
|
|
99
|
+
this.keep(token.name === "nullValue" ? null : token.name === "trueValue", false);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
keep(value, omitted) {
|
|
103
|
+
const budget = this.budget();
|
|
104
|
+
const key = this.stack.at(-1)?.key ?? "";
|
|
105
|
+
const bytes = Buffer.byteLength(JSON.stringify(value)) + Buffer.byteLength(JSON.stringify(key)) + 2;
|
|
106
|
+
if (bytes > budget.remaining) {
|
|
107
|
+
this.attach(undefined, true);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
budget.remaining -= bytes;
|
|
111
|
+
this.attach(value, omitted);
|
|
112
|
+
}
|
|
113
|
+
get value() {
|
|
114
|
+
const result = this.root && typeof this.root === "object" && !Array.isArray(this.root)
|
|
115
|
+
? this.root : {};
|
|
116
|
+
const raw = result.content ?? result.output ?? this.root;
|
|
117
|
+
const text = typeof raw === "string" ? raw : raw === undefined ? "" : JSON.stringify(raw);
|
|
118
|
+
const content = Array.isArray(raw) ? [...raw, { type: "text", text: NOTICE }]
|
|
119
|
+
: [{ type: "text", text: (text ? text + "\n" : "") + NOTICE }];
|
|
120
|
+
// Partial execution inventories must not be advertised as complete.
|
|
121
|
+
const details = result.details && typeof result.details === "object" && !Array.isArray(result.details)
|
|
122
|
+
? this.detailsOmitted ? { ...result.details, inventoryComplete: false } : result.details : undefined;
|
|
123
|
+
return { content, ...(details ? { details } : {}) };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** Native extension events already exist as objects. Walk them in small scalar
|
|
127
|
+
* slices instead of allocating a second giant JSON string before projection. */
|
|
128
|
+
export function projectPiToolResult(value, forcePreview = false) {
|
|
129
|
+
const preview = new PiToolResultPreview();
|
|
130
|
+
const seen = new Set();
|
|
131
|
+
let bytes = 0;
|
|
132
|
+
const emit = (token) => {
|
|
133
|
+
bytes += "value" in token && typeof token.value === "string" ? Buffer.byteLength(JSON.stringify(token.value)) + 16 : 16;
|
|
134
|
+
preview.consume(token);
|
|
135
|
+
};
|
|
136
|
+
function visit(raw, depth) {
|
|
137
|
+
if (depth > 128)
|
|
138
|
+
throw new Error("Pi 工具结果嵌套超过安全深度");
|
|
139
|
+
if (typeof raw === "string" || typeof raw === "number") {
|
|
140
|
+
const string = typeof raw === "string";
|
|
141
|
+
const text = string ? raw : String(raw);
|
|
142
|
+
if (!string && !Number.isFinite(raw)) {
|
|
143
|
+
emit({ name: "nullValue", value: null });
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
emit({ name: string ? "startString" : "startNumber" });
|
|
147
|
+
for (let offset = 0; offset < text.length; offset += STRING_CHARS) {
|
|
148
|
+
emit({ name: string ? "stringChunk" : "numberChunk", value: text.slice(offset, offset + STRING_CHARS) });
|
|
149
|
+
}
|
|
150
|
+
emit({ name: string ? "endString" : "endNumber" });
|
|
151
|
+
}
|
|
152
|
+
else if (typeof raw === "boolean")
|
|
153
|
+
emit(raw ? { name: "trueValue", value: true } : { name: "falseValue", value: false });
|
|
154
|
+
else if (!raw || typeof raw !== "object") {
|
|
155
|
+
if (typeof raw === "bigint")
|
|
156
|
+
throw new TypeError("Pi 工具结果包含不可序列化的 bigint");
|
|
157
|
+
emit({ name: "nullValue", value: null });
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
if (seen.has(raw))
|
|
161
|
+
throw new TypeError("Pi 工具结果包含循环引用");
|
|
162
|
+
seen.add(raw);
|
|
163
|
+
if (Array.isArray(raw)) {
|
|
164
|
+
emit({ name: "startArray" });
|
|
165
|
+
for (const item of raw)
|
|
166
|
+
visit(item, depth + 1);
|
|
167
|
+
emit({ name: "endArray" });
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
emit({ name: "startObject" });
|
|
171
|
+
for (const key of Object.keys(raw)) {
|
|
172
|
+
const item = raw[key];
|
|
173
|
+
if (item === undefined || typeof item === "function" || typeof item === "symbol")
|
|
174
|
+
continue;
|
|
175
|
+
emit({ name: "keyValue", value: key });
|
|
176
|
+
visit(item, depth + 1);
|
|
177
|
+
}
|
|
178
|
+
emit({ name: "endObject" });
|
|
179
|
+
}
|
|
180
|
+
seen.delete(raw);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
visit(value, 0);
|
|
184
|
+
return forcePreview || bytes > MAX_PROTOCOL_LINE_BYTES ? preview.value : value;
|
|
185
|
+
}
|
|
@@ -867,7 +867,7 @@ export class RenderDaemonClient {
|
|
|
867
867
|
return;
|
|
868
868
|
// 端点本身没了(socket 文件被临时目录清理器删掉,或 daemon 崩了没留下 socket):
|
|
869
869
|
// 重试同一个路径永远不会成功,先把 daemon 拉回来再连一次。只要 pid 文件里还有
|
|
870
|
-
// 活进程,回调就不会动手 —— 那个 daemon 会自己重建端点(docs/render-protocol.md §6)。
|
|
870
|
+
// 活进程,回调就不会动手 —— 那个 daemon 会自己重建端点(render/docs/render-protocol.md §6)。
|
|
871
871
|
if (await this.reviveDaemonForRetry()) {
|
|
872
872
|
try {
|
|
873
873
|
await this.connect();
|
package/dist/render-host.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ export interface RenderHostOptions {
|
|
|
19
19
|
* - daemon 被 kill -9 / 崩溃,socket 文件作为陈留文件留了下来(没有监听者)。
|
|
20
20
|
*
|
|
21
21
|
* 保守之处:pid 文件里还有活进程就不插手(那个 daemon 会自己重建端点,
|
|
22
|
-
* docs/render-protocol.md §6 single instance);能连上监听者也不插手;同一个窗口只 spawn 一次。
|
|
22
|
+
* render/docs/render-protocol.md §6 single instance);能连上监听者也不插手;同一个窗口只 spawn 一次。
|
|
23
23
|
*/
|
|
24
24
|
export declare function createRenderDaemonReviver(configPath: string, binaryPath: string | null): RenderDaemonReviver;
|
|
25
25
|
export interface UpgradeAwareTerminalHost {
|
|
@@ -63,7 +63,7 @@ export declare function createUpgradeAwareTerminalHost(configPath: string, optio
|
|
|
63
63
|
/**
|
|
64
64
|
* adopt 已存在的 Render,不存在则 spawn 后等待就绪。
|
|
65
65
|
*
|
|
66
|
-
* 协议(docs/render-protocol.md §6):pid 活着但 socket 不可用 → 等待就绪,
|
|
66
|
+
* 协议(render/docs/render-protocol.md §6):pid 活着但 socket 不可用 → 等待就绪,
|
|
67
67
|
* 不另起第二个 daemon;协议版本不符 → 明确抛错,**不做降级运行**。
|
|
68
68
|
*/
|
|
69
69
|
export declare function createRenderTerminalHost(configPath: string, options?: {
|
|
@@ -78,7 +78,7 @@ export declare function shouldUpgradeRenderDaemon(daemonVersion: string | null,
|
|
|
78
78
|
/**
|
|
79
79
|
* detached spawn(stdio 忽略、unref),返回子进程 pid。
|
|
80
80
|
*
|
|
81
|
-
* `-c <configPath>` 是按 config 隔离的既定约定(docs/render-protocol.md §6):
|
|
81
|
+
* `-c <configPath>` 是按 config 隔离的既定约定(render/docs/render-protocol.md §6):
|
|
82
82
|
* socket / token / pid / meta 全部由它派生。
|
|
83
83
|
*/
|
|
84
84
|
export declare function spawnDetachedRenderProcess(configPath: string, binaryPath: string): number | null;
|
package/dist/render-host.js
CHANGED
|
@@ -25,7 +25,7 @@ const RENDER_STALE_PROBE_DELAY_MS = 250;
|
|
|
25
25
|
* - daemon 被 kill -9 / 崩溃,socket 文件作为陈留文件留了下来(没有监听者)。
|
|
26
26
|
*
|
|
27
27
|
* 保守之处:pid 文件里还有活进程就不插手(那个 daemon 会自己重建端点,
|
|
28
|
-
* docs/render-protocol.md §6 single instance);能连上监听者也不插手;同一个窗口只 spawn 一次。
|
|
28
|
+
* render/docs/render-protocol.md §6 single instance);能连上监听者也不插手;同一个窗口只 spawn 一次。
|
|
29
29
|
*/
|
|
30
30
|
export function createRenderDaemonReviver(configPath, binaryPath) {
|
|
31
31
|
let inFlight = null;
|
|
@@ -214,7 +214,7 @@ export async function createUpgradeAwareTerminalHost(configPath, options = {}) {
|
|
|
214
214
|
/**
|
|
215
215
|
* adopt 已存在的 Render,不存在则 spawn 后等待就绪。
|
|
216
216
|
*
|
|
217
|
-
* 协议(docs/render-protocol.md §6):pid 活着但 socket 不可用 → 等待就绪,
|
|
217
|
+
* 协议(render/docs/render-protocol.md §6):pid 活着但 socket 不可用 → 等待就绪,
|
|
218
218
|
* 不另起第二个 daemon;协议版本不符 → 明确抛错,**不做降级运行**。
|
|
219
219
|
*/
|
|
220
220
|
export async function createRenderTerminalHost(configPath, options = {}) {
|
|
@@ -274,7 +274,7 @@ export function shouldUpgradeRenderDaemon(daemonVersion, binaryVersion, sessionC
|
|
|
274
274
|
/**
|
|
275
275
|
* detached spawn(stdio 忽略、unref),返回子进程 pid。
|
|
276
276
|
*
|
|
277
|
-
* `-c <configPath>` 是按 config 隔离的既定约定(docs/render-protocol.md §6):
|
|
277
|
+
* `-c <configPath>` 是按 config 隔离的既定约定(render/docs/render-protocol.md §6):
|
|
278
278
|
* socket / token / pid / meta 全部由它派生。
|
|
279
279
|
*/
|
|
280
280
|
export function spawnDetachedRenderProcess(configPath, binaryPath) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type Express } from "express";
|
|
2
|
+
import type { ApnsService } from "./apns-service.js";
|
|
3
|
+
import type { LiveActivityService } from "./live-activity-service.js";
|
|
4
|
+
import type { WandStorage } from "./storage.js";
|
|
5
|
+
import type { WandConfig } from "./types.js";
|
|
6
|
+
/** Mount before the global API auth gate: PUT requires the app bearer; DELETE has revoke-only authority. */
|
|
7
|
+
export declare function registerApnsRoutes(app: Express, deps: {
|
|
8
|
+
storage: WandStorage;
|
|
9
|
+
config: WandConfig;
|
|
10
|
+
service: ApnsService;
|
|
11
|
+
liveActivity?: LiveActivityService;
|
|
12
|
+
}): void;
|