@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.
Files changed (212) hide show
  1. package/dist/ai-team-live.js +1 -1
  2. package/dist/apns-client.d.ts +45 -0
  3. package/dist/apns-client.js +156 -0
  4. package/dist/apns-service.d.ts +29 -0
  5. package/dist/apns-service.js +156 -0
  6. package/dist/build-info.json +3 -3
  7. package/dist/conversation-service.d.ts +2 -0
  8. package/dist/conversation-service.js +23 -3
  9. package/dist/conversation-types.d.ts +2 -0
  10. package/dist/dingtalk-auth.d.ts +41 -0
  11. package/dist/dingtalk-auth.js +127 -0
  12. package/dist/dingtalk-dws.d.ts +45 -0
  13. package/dist/dingtalk-dws.js +240 -0
  14. package/dist/dingtalk-service.d.ts +100 -0
  15. package/dist/dingtalk-service.js +865 -0
  16. package/dist/dingtalk-stream.d.ts +24 -0
  17. package/dist/dingtalk-stream.js +115 -0
  18. package/dist/dingtalk-types.d.ts +138 -0
  19. package/dist/dingtalk-types.js +1 -0
  20. package/dist/employee-knowledge.js +8 -0
  21. package/dist/live-activity-service.d.ts +26 -0
  22. package/dist/live-activity-service.js +158 -0
  23. package/dist/live-activity-state.d.ts +37 -0
  24. package/dist/live-activity-state.js +108 -0
  25. package/dist/middleware/api-scope.js +2 -0
  26. package/dist/pi-cli-checkpoint.d.ts +17 -0
  27. package/dist/pi-cli-checkpoint.js +10 -0
  28. package/dist/pi-event-projection.js +13 -0
  29. package/dist/pi-execution.js +2 -1
  30. package/dist/pi-protocol-reader.d.ts +6 -2
  31. package/dist/pi-protocol-reader.js +36 -12
  32. package/dist/pi-tmux-host.d.ts +1 -1
  33. package/dist/pi-tmux-host.js +28 -8
  34. package/dist/pi-tool-result-preview.d.ts +23 -0
  35. package/dist/pi-tool-result-preview.js +185 -0
  36. package/dist/render-daemon-client.js +1 -1
  37. package/dist/render-host.d.ts +3 -3
  38. package/dist/render-host.js +3 -3
  39. package/dist/server-apns-routes.d.ts +12 -0
  40. package/dist/server-apns-routes.js +111 -0
  41. package/dist/server-dingtalk-routes.d.ts +8 -0
  42. package/dist/server-dingtalk-routes.js +35 -0
  43. package/dist/server.js +49 -3
  44. package/dist/session-completion.d.ts +2 -1
  45. package/dist/session-completion.js +6 -2
  46. package/dist/session-registry.d.ts +3 -1
  47. package/dist/session-registry.js +3 -1
  48. package/dist/storage/apns-repository.d.ts +52 -0
  49. package/dist/storage/apns-repository.js +108 -0
  50. package/dist/storage/conversations-repository.d.ts +2 -0
  51. package/dist/storage/conversations-repository.js +10 -0
  52. package/dist/storage/dingtalk-repository.d.ts +20 -0
  53. package/dist/storage/dingtalk-repository.js +75 -0
  54. package/dist/storage/live-activity-repository.d.ts +68 -0
  55. package/dist/storage/live-activity-repository.js +117 -0
  56. package/dist/storage/migrations.js +22 -0
  57. package/dist/storage/sessions-repository.d.ts +5 -0
  58. package/dist/storage/sessions-repository.js +43 -1
  59. package/dist/storage-schema.js +2 -0
  60. package/dist/storage.d.ts +7 -1
  61. package/dist/storage.js +10 -0
  62. package/dist/structured-exec-host.d.ts +9 -1
  63. package/dist/structured-exec-pump.d.ts +11 -0
  64. package/dist/structured-exec-pump.js +27 -8
  65. package/dist/structured-pi-adapter.js +5 -0
  66. package/dist/structured-runner.d.ts +2 -0
  67. package/dist/structured-session-manager.d.ts +2 -0
  68. package/dist/structured-session-manager.js +51 -4
  69. package/dist/types.d.ts +7 -0
  70. package/dist/web-ui/content/module-manifest.json +1 -1
  71. package/dist/web-ui/content/modules/{4f30a913cc5b3f89 → 02fad74223254019}/chunk.js +1 -1
  72. package/dist/web-ui/content/modules/{acb21c7e7fd6cb67 → 033e0a741edd2ba1}/page.js +1 -1
  73. package/dist/web-ui/content/modules/{e61136c22675ec7d → 0c04f94bbfd39a2f}/chunk.js +1 -1
  74. package/dist/web-ui/content/modules/{119874145414688d → 12e91deb96ace24d}/shell-file-panel-drawer.js +1 -1
  75. package/dist/web-ui/content/modules/15279373f0e1461f/host.js +1 -0
  76. package/dist/web-ui/content/modules/{78a46adbf9807f89 → 17603a34d545c784}/chunk.js +1 -1
  77. package/dist/web-ui/content/modules/1f041aa8d3673d93/chunk.js +1 -0
  78. package/dist/web-ui/content/modules/{96dd661559b67ebf → 20483d6e57cfe9d7}/chunk.js +1 -1
  79. package/dist/web-ui/content/modules/{f55031a02775c89e → 217595f72a1c3fc5}/chunk.js +1 -1
  80. package/dist/web-ui/content/modules/2230c51655163cf3/runtime.js +279 -0
  81. package/dist/web-ui/content/modules/{82e40fdf0abfb8cd → 2460e872ab3a1f37}/chunk.js +1 -1
  82. package/dist/web-ui/content/modules/{9e754b01fa4207fb → 2a47853ca7752c1f}/chunk.js +1 -1
  83. package/dist/web-ui/content/modules/{e95b60ab18a79499 → 2afbc201474adacc}/login-visual.js +1 -1
  84. package/dist/web-ui/content/modules/{38917c19e2cf1702 → 30169d15cea782f0}/chunk.js +1 -1
  85. package/dist/web-ui/content/modules/{5513bcbacdf44588 → 37abb74d1a0a0791}/notice-view.js +1 -1
  86. package/dist/web-ui/content/modules/{b62294e53dcb3147 → 38bb024e09cb5d5d}/host.js +1 -1
  87. package/dist/web-ui/content/modules/39faef0cffbad28e/home.js +6 -0
  88. package/dist/web-ui/content/modules/3a2fa12c8b537964/chunk.js +1 -0
  89. package/dist/web-ui/content/modules/{35a8fd2cfd31d7a0 → 3a72b79418f0f80a}/chunk.js +1 -1
  90. package/dist/web-ui/content/modules/{321bd9c755bc30fc → 3bd3c84c4257ba0d}/chunk.js +1 -1
  91. package/dist/web-ui/content/modules/{2981483671797b92 → 3d10e3f6f6f92b55}/chunk.js +1 -1
  92. package/dist/web-ui/content/modules/{6f341fe3e8aee844 → 3d4451bc069c1498}/chunk.js +1 -1
  93. package/dist/web-ui/content/modules/{4a9a3e1f87b2c32b → 3d5c498522b48225}/dialog-modal.js +1 -1
  94. package/dist/web-ui/content/modules/426b8133daebff78/host.js +1 -0
  95. package/dist/web-ui/content/modules/455436229dc57d7f/sidebar-peek.js +1 -0
  96. package/dist/web-ui/content/modules/46de3c2e2ca680c1/host.js +1 -0
  97. package/dist/web-ui/content/modules/47af6b35e1d01ea7/host.js +30 -0
  98. package/dist/web-ui/content/modules/4841e45cb7c5cc67/queue-view-adapter.js +3 -0
  99. package/dist/web-ui/content/modules/{47419445e0aaa3cc → 497789a07177ed7b}/chunk.js +1 -1
  100. package/dist/web-ui/content/modules/{32c461164a1b364c → 5472191457a926d3}/chunk.js +1 -1
  101. package/dist/web-ui/content/modules/{c152782274c04fad → 55a47b6e2c2016f2}/chunk.js +1 -1
  102. package/dist/web-ui/content/modules/5743d8116d15696b/employee-card.js +1 -0
  103. package/dist/web-ui/content/modules/{c81a3d95b14cdcd4 → 578703196f82222d}/chunk.js +1 -1
  104. package/dist/web-ui/content/modules/{8ce71665ae55ccf1 → 57d68f0a47e8fd7c}/toast-region.js +1 -1
  105. package/dist/web-ui/content/modules/58866143a3365521/chunk.js +1 -0
  106. package/dist/web-ui/content/modules/{3601736ae2d678ef → 5bb5db89a343817f}/chunk.js +1 -1
  107. package/dist/web-ui/content/modules/{a098c7b1aa4e2f6b → 5c9e8245d4a8a312}/sha256.js +1 -1
  108. package/dist/web-ui/content/modules/{8198bfd269c0caca → 5de35db482fb1a42}/chunk.js +1 -1
  109. package/dist/web-ui/content/modules/{aab92f2990a7185f → 5e7104e569359449}/chunk.js +1 -1
  110. package/dist/web-ui/content/modules/{4b9deeeb77570db3 → 601a2767ae24d1cb}/chunk.js +1 -1
  111. package/dist/web-ui/content/modules/6687ad7aa57f443b/task-board-host.js +22 -0
  112. package/dist/web-ui/content/modules/{c422f47fd13dc4bc → 684db83476dcba20}/chunk.js +1 -1
  113. package/dist/web-ui/content/modules/{cf715621edb1d12e → 6b0bafa239dfe4e2}/chunk.js +1 -1
  114. package/dist/web-ui/content/modules/{cd22aa55f6cc5276 → 6bd6ee3c9f05e2f5}/chunk.js +1 -1
  115. package/dist/web-ui/content/modules/{09ef1bb1d195d727 → 79b0c18f2bf35740}/host.js +1 -1
  116. package/dist/web-ui/content/modules/{905f8bc3f8a3efb8 → 7ae83a3f06f2daa3}/chunk.js +1 -1
  117. package/dist/web-ui/content/modules/{84b79a33c64e146c → 7f32261c1627c3cd}/chunk.js +1 -1
  118. package/dist/web-ui/content/modules/{eece3c2d2374e8f7 → 805b859b1b76ce4d}/chunk.js +1 -1
  119. package/dist/web-ui/content/modules/{eecaebc5482ee203 → 84358b5b729cbe48}/chunk.js +1 -1
  120. package/dist/web-ui/content/modules/85181d84d5ce62b3/employee-profile.js +1 -0
  121. package/dist/web-ui/content/modules/87edd7cc79811bb8/composer-surface-adapter.js +1 -0
  122. package/dist/web-ui/content/modules/{b0254a2d3652a925 → 8ea498cf092dba30}/chunk.js +1 -1
  123. package/dist/web-ui/content/modules/{a4af270960a39ea6 → 8f0d4e22c18a58b0}/chunk.js +1 -1
  124. package/dist/web-ui/content/modules/{c72c4f1d47c3726d → 907dcf0ea5b5f23f}/chunk.js +1 -1
  125. package/dist/web-ui/content/modules/{9f33acc1c4284e03 → 916021ce252882ca}/chunk.js +1 -1
  126. package/dist/web-ui/content/modules/{98c14fc0474455a5 → 91ee32135a3582b0}/chunk.js +1 -1
  127. package/dist/web-ui/content/modules/92df279c40bc4218/host.js +1 -0
  128. package/dist/web-ui/content/modules/{544dc812abdfbbb8 → 9661b076f08e64b4}/chunk.js +1 -1
  129. package/dist/web-ui/content/modules/{5c94fb2b7abcd1a5 → 97ef63ff1f0784d9}/chunk.js +1 -1
  130. package/dist/web-ui/content/modules/99e48e07517d3f83/dropdown-surface.js +1 -0
  131. package/dist/web-ui/content/modules/9eb83751622e2b97/chunk.js +10 -0
  132. package/dist/web-ui/content/modules/{7375574a2ea42740 → a1715be0f97c2479}/chunk.js +1 -1
  133. package/dist/web-ui/content/modules/{456634525d44777d → a2be1fd01cf06837}/chunk.js +1 -1
  134. package/dist/web-ui/content/modules/a33666b143331cd6/workspace-window.js +1 -0
  135. package/dist/web-ui/content/modules/{b38924f45bfa71d6 → a41351130003e6bb}/chunk.js +1 -1
  136. package/dist/web-ui/content/modules/a59c83b5b5b85da2/host.js +1 -0
  137. package/dist/web-ui/content/modules/{a4dca2ffaf656d3c → a63f0b3d73d97e0b}/chunk.js +1 -1
  138. package/dist/web-ui/content/modules/a685793c5d9b90d1/chunk.js +1 -0
  139. package/dist/web-ui/content/modules/a750f4868304b089/host.js +1 -0
  140. package/dist/web-ui/content/modules/abfe4b88dcd89d88/host.js +1 -0
  141. package/dist/web-ui/content/modules/{15bcc550ef9fd5b7 → acda148b7423bdab}/host.js +1 -1
  142. package/dist/web-ui/content/modules/{ad1136919a65fc6e → af6ff78aeb782cf2}/host.js +1 -1
  143. package/dist/web-ui/content/modules/b32c9addcd31f54b/panel-host.js +1 -0
  144. package/dist/web-ui/content/modules/{b937f1a939618be9 → b358eaf2e45849a6}/host.js +1 -1
  145. package/dist/web-ui/content/modules/{476ff7911bce1a38 → b7aeeb22bf604ec7}/chunk.js +1 -1
  146. package/dist/web-ui/content/modules/bad2c42c7ddeab94/host.js +1 -0
  147. package/dist/web-ui/content/modules/{622a8d12c4c622bf → bb59e8cb4323bf06}/host.js +1 -1
  148. package/dist/web-ui/content/modules/{08c7b047195f138d → be497f1d39753979}/chunk.js +1 -1
  149. package/dist/web-ui/content/modules/bfa8e2c827201374/chunk.js +1 -0
  150. package/dist/web-ui/content/modules/{5395fa57a47e9b96 → bfe51130a84795fb}/chunk.js +1 -1
  151. package/dist/web-ui/content/modules/{8e29056fb56439f7 → c03e0f84f798d33a}/chunk.js +1 -1
  152. package/dist/web-ui/content/modules/c118e0c96bc023ee/host-bridge.js +1 -0
  153. package/dist/web-ui/content/modules/c3512b18c4c0e4ac/brand-identity.js +1 -0
  154. package/dist/web-ui/content/modules/c3e0de0a61f0db34/chunk.js +1 -0
  155. package/dist/web-ui/content/modules/{9d48d1ea3c9228bd → c3e1f083e07d6840}/library-bridge.js +1 -1
  156. package/dist/web-ui/content/modules/cd5e90a37ccdf2dc/group-editor.js +1 -0
  157. package/dist/web-ui/content/modules/{2edc19639ac1bd4b → cdb8281bb89da303}/chunk.js +1 -1
  158. package/dist/web-ui/content/modules/{f6f86d43c12b0864 → ce90d0b55bbe6430}/chunk.js +1 -1
  159. package/dist/web-ui/content/modules/{72b13a7486b6d437 → d274491ffc8ee646}/chunk.js +1 -1
  160. package/dist/web-ui/content/modules/{b1d43ed06a5d1a57 → d3cc94cf4a47a12a}/chunk.js +1 -1
  161. package/dist/web-ui/content/modules/{b0c4eed46c1b6ad4 → d4b07cef43d36e79}/chat-render.js +5 -4
  162. package/dist/web-ui/content/modules/{8a4f0ef2c0812af6 → d7b540917fb90755}/chunk.js +1 -1
  163. package/dist/web-ui/content/modules/{ef7f76b551fdc619 → d856bfc56bea5f20}/chunk.js +1 -1
  164. package/dist/web-ui/content/modules/{42c439d3acfe22bd → d857ea8244b1026e}/chunk.js +1 -1
  165. package/dist/web-ui/content/modules/{36ce8123a2079807 → d9019922118b2c37}/chunk.js +1 -1
  166. package/dist/web-ui/content/modules/{58836e616e6d12ff → d90e77f9f8d4f1ec}/workspaces-panel.js +1 -1
  167. package/dist/web-ui/content/modules/{4d4870f20eb1c52d → da1aa5fa0186fea7}/chunk.js +1 -1
  168. package/dist/web-ui/content/modules/de977ff0b3ad3882/progress.js +1 -0
  169. package/dist/web-ui/content/modules/{222f48948517167e → e01769df89873feb}/chunk.js +1 -1
  170. package/dist/web-ui/content/modules/{5df1b522f5d3c99d → e0ab9fa5a8882433}/host.js +1 -1
  171. package/dist/web-ui/content/modules/e25dea52e13504b6/markdown.js +1 -0
  172. package/dist/web-ui/content/modules/{36bd608366c56b72 → e43af4aa407885fd}/chunk.js +1 -1
  173. package/dist/web-ui/content/modules/ee893f06cdb2b4f7/panel.js +1 -0
  174. package/dist/web-ui/content/modules/{f90f80d7207dff1f → f2bdbdc4a1141826}/host.js +1 -1
  175. package/dist/web-ui/content/modules/{e33f6d66a02ec26c → f36b4d3d44491852}/chunk.js +1 -1
  176. package/dist/web-ui/content/modules/{665e81fe210f1b7a → fa79ed5d045c24dd}/chunk.js +1 -1
  177. package/dist/web-ui/content/styles.css +1 -1
  178. package/dist/web-ui/embedded-assets.d.ts +412 -406
  179. package/dist/web-ui/embedded-assets.js +4 -4
  180. package/package.json +1 -1
  181. package/scripts/install-render-binary.js +1 -1
  182. package/dist/web-ui/content/modules/1a7303b87763fd43/employee-profile.js +0 -1
  183. package/dist/web-ui/content/modules/1d35739f634745f6/task-board-host.js +0 -22
  184. package/dist/web-ui/content/modules/2099d37d57856cbd/brand-identity.js +0 -1
  185. package/dist/web-ui/content/modules/2d6339b2f6c2e2d0/host.js +0 -1
  186. package/dist/web-ui/content/modules/2f88fc37c6105f7e/sidebar-peek.js +0 -1
  187. package/dist/web-ui/content/modules/31124fb7ee6939df/host.js +0 -1
  188. package/dist/web-ui/content/modules/31b28c5d0019c363/host-bridge.js +0 -1
  189. package/dist/web-ui/content/modules/36e5c3a6d6705358/employee-card.js +0 -1
  190. package/dist/web-ui/content/modules/3700d99c54d3c400/host.js +0 -1
  191. package/dist/web-ui/content/modules/3ae29086b227f9d9/markdown.js +0 -1
  192. package/dist/web-ui/content/modules/42db858e4417c702/chunk.js +0 -10
  193. package/dist/web-ui/content/modules/443dc81eac29f233/host.js +0 -1
  194. package/dist/web-ui/content/modules/4af10e1f8a789204/runtime.js +0 -275
  195. package/dist/web-ui/content/modules/55b75ebcf6924001/host.js +0 -30
  196. package/dist/web-ui/content/modules/626c7ce0aa1714b0/host.js +0 -1
  197. package/dist/web-ui/content/modules/76adde4019731df1/chunk.js +0 -1
  198. package/dist/web-ui/content/modules/7925b65867b7aa4a/queue-view-adapter.js +0 -3
  199. package/dist/web-ui/content/modules/8a6844a4251a1271/chunk.js +0 -1
  200. package/dist/web-ui/content/modules/8bd214570df4a7a0/host.js +0 -1
  201. package/dist/web-ui/content/modules/8bff85e108320aab/panel.js +0 -1
  202. package/dist/web-ui/content/modules/988e01b67e6f1883/composer-surface-adapter.js +0 -1
  203. package/dist/web-ui/content/modules/a975ff38c3d43ba8/workspace-window.js +0 -1
  204. package/dist/web-ui/content/modules/b550260fc9c5363a/dropdown-surface.js +0 -1
  205. package/dist/web-ui/content/modules/bce467c603299460/chunk.js +0 -1
  206. package/dist/web-ui/content/modules/c138b442c1682189/panel-host.js +0 -1
  207. package/dist/web-ui/content/modules/c32d14632487364f/group-editor.js +0 -1
  208. package/dist/web-ui/content/modules/c6d0a1b2753a4883/host.js +0 -1
  209. package/dist/web-ui/content/modules/cdff6e7f72879c88/progress.js +0 -1
  210. package/dist/web-ui/content/modules/cfee7b0d79587d59/home.js +0 -6
  211. package/dist/web-ui/content/modules/e5e93fe5304838a1/chunk.js +0 -1
  212. 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
  }
@@ -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: typeof children.inventoryComplete === "boolean" ? children.inventoryComplete : source === "tool-result" && !d.asyncId,
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
- * Genuine oversized payloads still fail closed; other providers keep their
6
- * original 8 MiB line limit. */
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
- * Genuine oversized payloads still fail closed; other providers keep their
76
- * original 8 MiB line limit. */
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
- if (this.rawBytes > MAX_ENVELOPE_BYTES)
122
- throw failure("事件封套超过安全容量");
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
- this.field?.consume({ name: "keyValue", value: this.key });
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
- if (this.field.overflow && !(this.rootKey === "message" && ["user", "toolResult"].includes(this.field.role))) {
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] = this.field.overflow ? { role: this.field.role } : this.field.value;
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
- const line = JSON.stringify(this.event);
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
  }
@@ -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>;
@@ -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 size = fstatSync(fd).size;
186
- const count = Math.min(size, REPLAY_BYTES);
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, size - 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();
@@ -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;
@@ -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;