@ljwei-stak/dsh-model-route-bot 0.66.4 → 0.67.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 (271) hide show
  1. package/.dsh-plugin/bot-product.mjs +81 -5
  2. package/.dsh-plugin/client.js +17490 -1791
  3. package/README.i18n.yaml +4 -4
  4. package/README.md +409 -364
  5. package/README.zh.md +409 -364
  6. package/apps/integration-client/README.md +31 -7
  7. package/apps/integration-client/api.mjs +14 -3
  8. package/apps/integration-client/binding-editor.jsx +308 -109
  9. package/apps/integration-client/binding-model.mjs +262 -0
  10. package/apps/integration-client/binding.css +144 -0
  11. package/apps/integration-client/bot-model-status.mjs +18 -0
  12. package/apps/integration-client/bot-profile-identity.jsx +197 -0
  13. package/apps/integration-client/bot-profile.css +57 -0
  14. package/apps/integration-client/bot-profile.mjs +97 -0
  15. package/apps/integration-client/bot-settings-dialog.css +18 -0
  16. package/apps/integration-client/bot-settings-dialog.jsx +111 -0
  17. package/apps/integration-client/bot-thread-header-bridge.css +20 -0
  18. package/apps/integration-client/bot-thread-header.jsx +278 -0
  19. package/apps/integration-client/bot-threads-panel.jsx +378 -0
  20. package/apps/integration-client/bot-threads.css +169 -0
  21. package/apps/integration-client/chat-design.mjs +250 -0
  22. package/apps/integration-client/chat-ui.jsx +175 -0
  23. package/apps/integration-client/chat.css +477 -0
  24. package/apps/integration-client/composer-draft-binding.mjs +50 -0
  25. package/apps/integration-client/composer-draft-bound.jsx +14 -0
  26. package/apps/integration-client/composer-draft-host.jsx +142 -0
  27. package/apps/integration-client/composer-draft-session.jsx +287 -0
  28. package/apps/integration-client/composer-draft-witness.mjs +115 -0
  29. package/apps/integration-client/composer-draft.css +4 -0
  30. package/apps/integration-client/composer-draft.mjs +322 -0
  31. package/apps/integration-client/contact-attention.jsx +54 -0
  32. package/apps/integration-client/data-panel.jsx +439 -0
  33. package/apps/integration-client/data.css +69 -0
  34. package/apps/integration-client/desktop-bridge.mjs +1 -0
  35. package/apps/integration-client/desktop-http-socket.mjs +49 -0
  36. package/apps/integration-client/desktop-keyboard.mjs +32 -0
  37. package/apps/integration-client/desktop-pane.jsx +99 -30
  38. package/apps/integration-client/desktop-stream.mjs +13 -3
  39. package/apps/integration-client/desktop-tools.jsx +19 -0
  40. package/apps/integration-client/desktop-touch.mjs +27 -0
  41. package/apps/integration-client/desktop.css +41 -0
  42. package/apps/integration-client/independent-chat-controller.mjs +224 -0
  43. package/apps/integration-client/independent-chat-entry.jsx +61 -0
  44. package/apps/integration-client/independent-chat-navigation.mjs +21 -0
  45. package/apps/integration-client/independent-chat-page.jsx +148 -0
  46. package/apps/integration-client/independent-chat-rich.jsx +64 -0
  47. package/apps/integration-client/independent-chat-rich.mjs +30 -0
  48. package/apps/integration-client/independent-chat.css +73 -0
  49. package/apps/integration-client/index.jsx +24 -16
  50. package/apps/integration-client/library-panel.jsx +4 -1
  51. package/apps/integration-client/licenses/jsqr-1.4.0-LICENSE.txt +202 -0
  52. package/apps/integration-client/main-route-editor.jsx +94 -0
  53. package/apps/integration-client/main-route-model.mjs +100 -0
  54. package/apps/integration-client/model-picker.css +37 -0
  55. package/apps/integration-client/model-picker.jsx +83 -0
  56. package/apps/integration-client/native-activity.css +141 -0
  57. package/apps/integration-client/native-activity.jsx +276 -0
  58. package/apps/integration-client/native-activity.mjs +384 -0
  59. package/apps/integration-client/notifications-panel.jsx +186 -0
  60. package/apps/integration-client/notifications.css +126 -0
  61. package/apps/integration-client/pairing-qr.jsx +79 -0
  62. package/apps/integration-client/panels.jsx +126 -115
  63. package/apps/integration-client/product-slots.jsx +46 -0
  64. package/apps/integration-client/routine-recorder.mjs +23 -2
  65. package/apps/integration-client/secure-pairing.jsx +84 -0
  66. package/apps/integration-client/tasks-panel.jsx +32 -10
  67. package/apps/integration-client/ui-icons.jsx +17 -0
  68. package/apps/integration-client/usage-explorer.jsx +43 -0
  69. package/apps/integration-client/usage-report.mjs +71 -0
  70. package/apps/integration-client/usage.css +34 -0
  71. package/apps/integration-client/web-panel-model.mjs +140 -0
  72. package/apps/integration-client/web-panel-page.jsx +221 -0
  73. package/apps/integration-client/web-panel-pins-live.jsx +28 -0
  74. package/apps/integration-client/web-panel.css +139 -0
  75. package/apps/integration-client/workspace-model.mjs +67 -0
  76. package/apps/integration-client/workspace-navigation.mjs +56 -0
  77. package/apps/integration-client/workspace-ui.jsx +173 -0
  78. package/apps/integration-client/workspace.css +332 -0
  79. package/modules/bot-runtime/SOURCE.json +176 -50
  80. package/modules/bot-runtime/index.mjs +169 -13
  81. package/modules/bot-runtime/product-overlay/OVERLAY.json +128 -2
  82. package/modules/bot-runtime/product-overlay/base/src/client/header.js +244 -0
  83. package/modules/bot-runtime/product-overlay/base/src/client/learning-card.js +202 -0
  84. package/modules/bot-runtime/product-overlay/base/src/client/model-menu.js +371 -0
  85. package/modules/bot-runtime/product-overlay/base/src/client/secret-card.js +363 -0
  86. package/modules/bot-runtime/product-overlay/base/src/client/sources.js +68 -0
  87. package/modules/bot-runtime/product-overlay/base/src/client/turn-error.js +260 -0
  88. package/modules/bot-runtime/product-overlay/base/src/host/channel.js +647 -0
  89. package/modules/bot-runtime/product-overlay/base/src/host/memory.js +2050 -0
  90. package/modules/bot-runtime/product-overlay/base/src/host/models.js +314 -0
  91. package/modules/bot-runtime/product-overlay/base/src/host/parts.js +1473 -0
  92. package/modules/bot-runtime/product-overlay/base/src/host/privacy.js +368 -0
  93. package/modules/bot-runtime/product-overlay/base/src/host/roster.js +776 -0
  94. package/modules/bot-runtime/product-overlay/base/src/host/store.js +512 -0
  95. package/modules/bot-runtime/product-overlay/base/src/host/usage.js +653 -0
  96. package/modules/bot-runtime/product-overlay/patches/src/client/details-drawer.js.patch +48 -2
  97. package/modules/bot-runtime/product-overlay/patches/src/client/header.js.patch +41 -0
  98. package/modules/bot-runtime/product-overlay/patches/src/client/index.js.patch +33 -1
  99. package/modules/bot-runtime/product-overlay/patches/src/client/learning-card.js.patch +20 -0
  100. package/modules/bot-runtime/product-overlay/patches/src/client/model-menu.js.patch +24 -0
  101. package/modules/bot-runtime/product-overlay/patches/src/client/secret-card.js.patch +25 -0
  102. package/modules/bot-runtime/product-overlay/patches/src/client/sources.js.patch +52 -0
  103. package/modules/bot-runtime/product-overlay/patches/src/client/turn-error.js.patch +23 -0
  104. package/modules/bot-runtime/product-overlay/patches/src/host/channel.js.patch +46 -0
  105. package/modules/bot-runtime/product-overlay/patches/src/host/memory.js.patch +11 -0
  106. package/modules/bot-runtime/product-overlay/patches/src/host/models.js.patch +16 -0
  107. package/modules/bot-runtime/product-overlay/patches/src/host/parts.js.patch +95 -0
  108. package/modules/bot-runtime/product-overlay/patches/src/host/privacy.js.patch +11 -0
  109. package/modules/bot-runtime/product-overlay/patches/src/host/roster.js.patch +145 -0
  110. package/modules/bot-runtime/product-overlay/patches/src/host/store.js.patch +68 -0
  111. package/modules/bot-runtime/product-overlay/patches/src/host/usage.js.patch +11 -0
  112. package/modules/bot-runtime/read-model.mjs +19 -4
  113. package/modules/bot-runtime/vendor/ds-bot/src/client/details-drawer.js +15 -5
  114. package/modules/bot-runtime/vendor/ds-bot/src/client/header.js +9 -2
  115. package/modules/bot-runtime/vendor/ds-bot/src/client/index.js +9 -2
  116. package/modules/bot-runtime/vendor/ds-bot/src/client/learning-card.js +2 -2
  117. package/modules/bot-runtime/vendor/ds-bot/src/client/model-menu.js +5 -3
  118. package/modules/bot-runtime/vendor/ds-bot/src/client/secret-card.js +3 -3
  119. package/modules/bot-runtime/vendor/ds-bot/src/client/sources.js +25 -2
  120. package/modules/bot-runtime/vendor/ds-bot/src/client/turn-error.js +3 -2
  121. package/modules/bot-runtime/vendor/ds-bot/src/host/channel.js +14 -2
  122. package/modules/bot-runtime/vendor/ds-bot/src/host/memory.js +1 -1
  123. package/modules/bot-runtime/vendor/ds-bot/src/host/models.js +7 -0
  124. package/modules/bot-runtime/vendor/ds-bot/src/host/parts.js +26 -9
  125. package/modules/bot-runtime/vendor/ds-bot/src/host/privacy.js +1 -1
  126. package/modules/bot-runtime/vendor/ds-bot/src/host/roster.js +112 -3
  127. package/modules/bot-runtime/vendor/ds-bot/src/host/store.js +34 -1
  128. package/modules/bot-runtime/vendor/ds-bot/src/host/usage.js +1 -1
  129. package/modules/independent-runtime/index.mjs +152 -62
  130. package/modules/independent-runtime/remote-tools.mjs +8 -5
  131. package/modules/openmaus-protocol/NOTICE +10 -0
  132. package/modules/openmaus-protocol/README.md +15 -0
  133. package/modules/openmaus-protocol/SOURCE.json +68 -0
  134. package/modules/openmaus-protocol/dist/protocol.mjs +1203 -0
  135. package/modules/openmaus-protocol/index.mjs +1 -0
  136. package/modules/openmaus-protocol/transport.mjs +77 -0
  137. package/modules/openmaus-protocol/vendor/LICENSE +202 -0
  138. package/modules/openmaus-protocol/vendor/companion/src/e2e/README.md +405 -0
  139. package/modules/openmaus-protocol/vendor/companion/src/e2e/credentials.ts +128 -0
  140. package/modules/openmaus-protocol/vendor/companion/src/e2e/descriptor.ts +84 -0
  141. package/modules/openmaus-protocol/vendor/companion/src/e2e/noise.ts +456 -0
  142. package/modules/openmaus-protocol/vendor/companion/src/e2e/phone.ts +104 -0
  143. package/modules/openmaus-protocol/vendor/companion/src/e2e/session.ts +417 -0
  144. package/modules/openmaus-protocol/vendor/companion/src/e2e/vectors/v1.json +2565 -0
  145. package/modules/openmaus-protocol/vendor/companion/src/e2e/vectors/v1.sha256 +1 -0
  146. package/modules/openmaus-protocol/vendor/companion/src/e2e/yamux.ts +335 -0
  147. package/package.json +18 -6
  148. package/packages/bot-acp/README.md +156 -0
  149. package/packages/bot-acp/licenses/LICENSE-OpenMausBot +202 -0
  150. package/packages/bot-acp/licenses/NOTICE.md +27 -0
  151. package/packages/bot-acp/src/acp-adapter.mjs +795 -0
  152. package/packages/bot-acp/src/acp-host.mjs +148 -0
  153. package/packages/bot-acp/src/native-launch.mjs +121 -0
  154. package/packages/bot-acp/src/permission-policy.mjs +283 -0
  155. package/packages/bot-acp/src/rpc-codec.mjs +265 -0
  156. package/packages/bot-acp/src/session-core.mjs +581 -0
  157. package/packages/bot-acp/src/stdio-rpc.mjs +329 -0
  158. package/packages/bot-engines/README.md +281 -0
  159. package/packages/bot-engines/licenses/LICENSE-OpenMausBot +202 -0
  160. package/packages/bot-engines/licenses/NOTICE.md +32 -0
  161. package/packages/bot-engines/src/claude-engine.mjs +772 -0
  162. package/packages/bot-engines/src/claude-llm-adapter.mjs +797 -0
  163. package/packages/bot-engines/src/claude-llm-turn.mjs +292 -0
  164. package/packages/bot-engines/src/claude-permission-broker.mjs +130 -0
  165. package/packages/bot-engines/src/claude-permission-proxy.mjs +87 -0
  166. package/packages/bot-engines/src/claude-protocol.mjs +177 -0
  167. package/packages/bot-engines/src/driver-catalog.mjs +334 -0
  168. package/packages/bot-engines/src/host-loop-request.mjs +95 -0
  169. package/packages/bot-engines/src/host-registrations.mjs +105 -0
  170. package/packages/capability-broker/src/cli-bootstrap.mjs +86 -16
  171. package/packages/capability-broker/src/cli-bridge-response.mjs +80 -0
  172. package/packages/capability-broker/src/cli-stdio-bridge.mjs +21 -8
  173. package/packages/cli-runtime/src/codex-session-core.mjs +4 -0
  174. package/packages/cli-runtime/src/native-cli-output.mjs +57 -0
  175. package/packages/cli-runtime/src/native-peer.ps1 +51 -3
  176. package/packages/cli-runtime/src/native-process.mjs +77 -14
  177. package/packages/model-router-plugin/package.json +1 -1
  178. package/packages/orchestration/src/binding-product.mjs +19 -0
  179. package/packages/orchestration/src/bot-data-actions.mjs +45 -0
  180. package/packages/orchestration/src/bot-data-chart.mjs +109 -0
  181. package/packages/orchestration/src/bot-data-context.mjs +44 -0
  182. package/packages/orchestration/src/bot-data-engine.mjs +809 -0
  183. package/packages/orchestration/src/bot-data-io.mjs +186 -0
  184. package/packages/orchestration/src/bot-data-sql.mjs +195 -0
  185. package/packages/orchestration/src/bot-data-surface.mjs +144 -0
  186. package/packages/orchestration/src/bot-data-tools.mjs +68 -0
  187. package/packages/orchestration/src/bot-data-worker.mjs +228 -0
  188. package/packages/orchestration/src/bot-independent-execution.mjs +195 -0
  189. package/packages/orchestration/src/bot-independent-stream.mjs +74 -0
  190. package/packages/orchestration/src/bot-loop.mjs +152 -19
  191. package/packages/orchestration/src/bot-notifications.mjs +159 -0
  192. package/packages/orchestration/src/bot-policy.mjs +43 -4
  193. package/packages/orchestration/src/bot-profile.mjs +171 -0
  194. package/packages/orchestration/src/bot-routine.mjs +4 -4
  195. package/packages/orchestration/src/bot-runtime-assembly.mjs +255 -8
  196. package/packages/orchestration/src/bot-runtime-switch.mjs +13 -2
  197. package/packages/orchestration/src/bot-serving.mjs +164 -0
  198. package/packages/orchestration/src/bot-session-store.mjs +105 -9
  199. package/packages/orchestration/src/bot-settings.mjs +50 -11
  200. package/packages/orchestration/src/bot-thread-admission.mjs +84 -0
  201. package/packages/orchestration/src/bot-thread-host.mjs +187 -0
  202. package/packages/orchestration/src/bot-threads.mjs +790 -0
  203. package/packages/orchestration/src/data-product-access.mjs +37 -0
  204. package/packages/orchestration/src/data-tool-contract.mjs +291 -0
  205. package/packages/orchestration/src/desktop-http-carrier.mjs +120 -0
  206. package/packages/orchestration/src/desktop-proxy.mjs +311 -0
  207. package/packages/orchestration/src/desktop-sessions.mjs +43 -9
  208. package/packages/orchestration/src/ds-lifecycle.mjs +31 -4
  209. package/packages/orchestration/src/grant-refresh-queue.mjs +17 -0
  210. package/packages/orchestration/src/independent-chat-page.mjs +39 -0
  211. package/packages/orchestration/src/independent-chat-service.mjs +308 -0
  212. package/packages/orchestration/src/independent-context-state.mjs +44 -0
  213. package/packages/orchestration/src/independent-message-wire.mjs +84 -0
  214. package/packages/orchestration/src/independent-tool-catalog.mjs +175 -0
  215. package/packages/orchestration/src/independent-tool-families.mjs +73 -0
  216. package/packages/orchestration/src/independent-tool-host.mjs +344 -0
  217. package/packages/orchestration/src/main-bot-grant.mjs +15 -0
  218. package/packages/orchestration/src/main-model-routes.mjs +208 -0
  219. package/packages/orchestration/src/model-accounting.mjs +99 -35
  220. package/packages/orchestration/src/model-auxiliary-dispatch.mjs +187 -0
  221. package/packages/orchestration/src/model-loop-frame.mjs +179 -0
  222. package/packages/orchestration/src/model-prepared-generation.mjs +175 -0
  223. package/packages/orchestration/src/model-request-bounds.mjs +111 -0
  224. package/packages/orchestration/src/native-activity-contract.mjs +491 -0
  225. package/packages/orchestration/src/native-activity-service.mjs +102 -0
  226. package/packages/orchestration/src/native-activity-store.mjs +249 -0
  227. package/packages/orchestration/src/native-conversation-tools.mjs +78 -0
  228. package/packages/orchestration/src/native-data-tools.mjs +100 -0
  229. package/packages/orchestration/src/native-remote-actions.mjs +742 -0
  230. package/packages/orchestration/src/native-team-tools.mjs +73 -0
  231. package/packages/orchestration/src/pairing-invite.mjs +37 -0
  232. package/packages/orchestration/src/product-composition.mjs +1559 -1001
  233. package/packages/orchestration/src/remote-machines.mjs +82 -14
  234. package/packages/orchestration/src/routine-execution.mjs +5 -3
  235. package/packages/orchestration/src/routine-session.mjs +19 -13
  236. package/packages/orchestration/src/secure-pairing-host.mjs +112 -0
  237. package/packages/orchestration/src/task-journal.mjs +29 -0
  238. package/packages/orchestration/src/task-product.mjs +17 -0
  239. package/packages/orchestration/src/team-assignment-store.mjs +120 -0
  240. package/packages/orchestration/src/team-bridge.mjs +282 -0
  241. package/packages/orchestration/src/team-scheduler.mjs +83 -0
  242. package/packages/orchestration/src/team-sdk-tools.mjs +84 -0
  243. package/packages/orchestration/src/team-tool-contract.mjs +284 -0
  244. package/packages/orchestration/src/ui-host-namespace.mjs +23 -0
  245. package/packages/orchestration/src/web-panel-actions.mjs +103 -0
  246. package/packages/orchestration/src/web-panel-framing.mjs +227 -0
  247. package/packages/orchestration/src/web-panel-store.mjs +206 -0
  248. package/remote-computer-agent/README.md +39 -29
  249. package/remote-computer-agent/build.mjs +3 -0
  250. package/remote-computer-agent/dist/OPENMAUSBOT_LICENSE +202 -0
  251. package/remote-computer-agent/dist/OPENMAUSBOT_NOTICE +10 -0
  252. package/remote-computer-agent/dist/OPENMAUSBOT_SOURCE.json +68 -0
  253. package/remote-computer-agent/dist/cli-stdio-bridge.mjs +215 -25
  254. package/remote-computer-agent/dist/cli-stdio-bridge.mjs.map +4 -4
  255. package/remote-computer-agent/dist/cli.mjs +1688 -168
  256. package/remote-computer-agent/dist/cli.mjs.map +4 -4
  257. package/remote-computer-agent/dist/index.mjs +1579 -122
  258. package/remote-computer-agent/dist/index.mjs.map +4 -4
  259. package/remote-computer-agent/dist/native-peer.ps1 +51 -3
  260. package/remote-computer-agent/package-lock.json +308 -2
  261. package/remote-computer-agent/package.json +2 -1
  262. package/remote-computer-agent/pair-device.ps1 +20 -0
  263. package/remote-computer-agent/setup.ps1 +38 -16
  264. package/remote-computer-agent/src/cli.ts +24 -3
  265. package/remote-computer-agent/src/desktop-session.ts +11 -3
  266. package/remote-computer-agent/src/e2e-server.ts +81 -0
  267. package/remote-computer-agent/src/pairing-page.ts +35 -0
  268. package/remote-computer-agent/src/protocol.ts +25 -25
  269. package/remote-computer-agent/src/secure-pairing.ts +105 -0
  270. package/remote-computer-agent/src/store.ts +20 -5
  271. package/remote-computer-agent/src/transport.ts +39 -15
@@ -0,0 +1 @@
1
+ 5471a0070ddbb69dca34dee6da550de4581e8e1a40e22e0f97873d539793bf4e v1.json
@@ -0,0 +1,335 @@
1
+ // The yamux profile carried inside the records: one frame per record, phone-opened streams only, 2 MiB windows.
2
+ // Wire spec: companion/src/e2e/README.md, "Multiplexing". This module does no I/O, so every rule is testable.
3
+
4
+ export const VERSION = 0;
5
+ export const TYPE = { data: 0, windowUpdate: 1, ping: 2, goAway: 3 } as const;
6
+ export const FLAG = { syn: 0x1, ack: 0x2, fin: 0x4, rst: 0x8 } as const;
7
+ const KNOWN_FLAGS = 0xf;
8
+ /** `removed` goes only from the computer to the phone: this device was removed, and the close that follows is 4001. It
9
+ * is inside the encryption, so unlike a bare 4001 close code a relay can't forge it. */
10
+ export const GO_AWAY = { normal: 0, protocol: 1, internal: 2, removed: 3 } as const;
11
+
12
+ export const HEADER_BYTES = 12;
13
+ export const MAX_DATA_PAYLOAD = 65_507;
14
+ export const INITIAL_WINDOW = 256 * 1024;
15
+ export const STREAM_WINDOW = 2 * 1024 * 1024;
16
+ export const OPEN_WINDOW_DELTA = STREAM_WINDOW - INITIAL_WINDOW;
17
+ export const WINDOW_UPDATE_AFTER = 1024 * 1024;
18
+ export const MAX_STREAMS = 64;
19
+ export const MAX_STREAM_ID = 2 ** 31 - 1;
20
+ export const PING_INTERVAL_MS = 20_000;
21
+ export const PING_TIMEOUT_MS = 10_000;
22
+
23
+ export type FrameType = (typeof TYPE)[keyof typeof TYPE];
24
+
25
+ export interface Frame {
26
+ type: FrameType;
27
+ flags: number;
28
+ streamId: number;
29
+ /** Data: the payload size. WindowUpdate: the window delta. Ping: the opaque value. GoAway: the code. */
30
+ length: number;
31
+ data?: Uint8Array;
32
+ }
33
+
34
+ export class YamuxError extends Error {
35
+ readonly reason: string;
36
+ constructor(reason: string) {
37
+ super(`yamux: ${reason}`);
38
+ this.reason = reason;
39
+ }
40
+ }
41
+
42
+ export function encodeFrame(frame: Frame): Buffer {
43
+ const payload = frame.type === TYPE.data ? frame.data ?? new Uint8Array(0) : new Uint8Array(0);
44
+ if (frame.type === TYPE.data && payload.length !== frame.length) throw new YamuxError("data_length");
45
+ if (payload.length > MAX_DATA_PAYLOAD) throw new YamuxError("data_too_large");
46
+ const out = Buffer.alloc(HEADER_BYTES + payload.length);
47
+ out.writeUInt8(VERSION, 0);
48
+ out.writeUInt8(frame.type, 1);
49
+ out.writeUInt16BE(frame.flags, 2);
50
+ out.writeUInt32BE(frame.streamId, 4);
51
+ out.writeUInt32BE(frame.length, 8);
52
+ out.set(payload, HEADER_BYTES);
53
+ return out;
54
+ }
55
+
56
+ /** One record's plaintext is exactly one frame. Everything that can be checked without stream state is
57
+ * checked here. */
58
+ export function decodeFrame(bytes: Uint8Array): Frame {
59
+ if (bytes.length < HEADER_BYTES) throw new YamuxError("short_frame");
60
+ const view = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
61
+ if (view.readUInt8(0) !== VERSION) throw new YamuxError("version");
62
+ const type = view.readUInt8(1);
63
+ if (type > TYPE.goAway) throw new YamuxError("type");
64
+ const flags = view.readUInt16BE(2);
65
+ if ((flags & ~KNOWN_FLAGS) !== 0) throw new YamuxError("flags");
66
+ const streamId = view.readUInt32BE(4);
67
+ const length = view.readUInt32BE(8);
68
+ if (type === TYPE.data) {
69
+ if (length > MAX_DATA_PAYLOAD) throw new YamuxError("data_too_large");
70
+ if (bytes.length !== HEADER_BYTES + length) throw new YamuxError("data_length");
71
+ if (streamId === 0) throw new YamuxError("stream_zero");
72
+ return { type, flags, streamId, length, data: view.subarray(HEADER_BYTES) };
73
+ }
74
+ if (bytes.length !== HEADER_BYTES) throw new YamuxError("trailing_bytes");
75
+ if (type === TYPE.windowUpdate) {
76
+ if (streamId === 0) throw new YamuxError("stream_zero");
77
+ } else {
78
+ if (streamId !== 0) throw new YamuxError("session_frame_stream");
79
+ if (type === TYPE.ping && flags !== FLAG.syn && flags !== FLAG.ack) throw new YamuxError("ping_flags");
80
+ if (type === TYPE.goAway && flags !== 0) throw new YamuxError("goaway_flags");
81
+ }
82
+ return { type: type as FrameType, flags, streamId, length };
83
+ }
84
+
85
+ export const openFrame = (streamId: number): Frame => ({ type: TYPE.windowUpdate, flags: FLAG.syn, streamId, length: OPEN_WINDOW_DELTA });
86
+ export const acceptFrame = (streamId: number): Frame => ({ type: TYPE.windowUpdate, flags: FLAG.ack, streamId, length: OPEN_WINDOW_DELTA });
87
+ export const resetFrame = (streamId: number): Frame => ({ type: TYPE.windowUpdate, flags: FLAG.rst, streamId, length: 0 });
88
+ export const finFrame = (streamId: number): Frame => ({ type: TYPE.windowUpdate, flags: FLAG.fin, streamId, length: 0 });
89
+ export const windowFrame = (streamId: number, delta: number): Frame => ({ type: TYPE.windowUpdate, flags: 0, streamId, length: delta });
90
+ export const dataFrame = (streamId: number, data: Uint8Array, flags = 0): Frame => ({ type: TYPE.data, flags, streamId, length: data.length, data });
91
+ export const pingFrame = (value: number, flags: number = FLAG.syn): Frame => ({ type: TYPE.ping, flags, streamId: 0, length: value >>> 0 });
92
+ export const goAwayFrame = (code: number): Frame => ({ type: TYPE.goAway, flags: 0, streamId: 0, length: code });
93
+
94
+ /** The phone opens every stream; the computer only accepts. */
95
+ export type Role = "phone" | "computer";
96
+
97
+ interface StreamState {
98
+ /** The phone side: false until the computer's ACK arrives. The computer side: false until accept(). */
99
+ established: boolean;
100
+ /** What the peer may still send before this side grants more. */
101
+ receiveWindow: number;
102
+ /** What this side may still send. */
103
+ sendWindow: number;
104
+ /** Consumed but not yet granted back. */
105
+ uncredited: number;
106
+ localFin: boolean;
107
+ remoteFin: boolean;
108
+ }
109
+
110
+ export interface MuxEvents {
111
+ /** Every frame to seal and send, in order. */
112
+ send(frame: Frame): void;
113
+ /** The computer side: a new stream. Answer with accept() or reset(); data may arrive first. */
114
+ opened?(streamId: number): void;
115
+ /** The phone side: the computer accepted. */
116
+ accepted?(streamId: number): void;
117
+ data(streamId: number, data: Uint8Array): void;
118
+ /** The peer may send no more (FIN). */
119
+ ended(streamId: number): void;
120
+ /** Ended by RST from either side, or the session closing. */
121
+ reset(streamId: number): void;
122
+ /** The peer granted more send window. */
123
+ writable(streamId: number): void;
124
+ goAway?(code: number): void;
125
+ pong?(value: number): void;
126
+ }
127
+
128
+ /** A frame that breaks the profile throws YamuxError. The caller then sends GoAway(1) through
129
+ * `protocolError()` and closes the WebSocket with 4000. */
130
+ export class Mux {
131
+ readonly role: Role;
132
+ readonly #events: MuxEvents;
133
+ readonly #streams = new Map<number, StreamState>();
134
+ /** The highest ID the phone has opened. */
135
+ #highestId = 0;
136
+ #peerGoneAway = false;
137
+ #localGoneAway = false;
138
+
139
+ constructor(role: Role, events: MuxEvents) {
140
+ this.role = role;
141
+ this.#events = events;
142
+ }
143
+
144
+ get activeStreams(): number {
145
+ return this.#streams.size;
146
+ }
147
+
148
+ get peerGoneAway(): boolean {
149
+ return this.#peerGoneAway;
150
+ }
151
+
152
+ has(streamId: number): boolean {
153
+ return this.#streams.has(streamId);
154
+ }
155
+
156
+ sendWindow(streamId: number): number {
157
+ return this.#streams.get(streamId)?.sendWindow ?? 0;
158
+ }
159
+
160
+ /** The phone side. Data may follow at once, within the first 256 KiB. */
161
+ open(): number {
162
+ if (this.role !== "phone") throw new YamuxError("computer_open");
163
+ if (this.#localGoneAway || this.#peerGoneAway) throw new YamuxError("gone_away");
164
+ if (this.#streams.size >= MAX_STREAMS) throw new YamuxError("too_many_streams");
165
+ const streamId = this.#highestId === 0 ? 1 : this.#highestId + 2;
166
+ if (streamId > MAX_STREAM_ID) throw new YamuxError("ids_exhausted");
167
+ this.#highestId = streamId;
168
+ this.#streams.set(streamId, {
169
+ established: false,
170
+ receiveWindow: INITIAL_WINDOW + OPEN_WINDOW_DELTA,
171
+ sendWindow: INITIAL_WINDOW,
172
+ uncredited: 0,
173
+ localFin: false,
174
+ remoteFin: false,
175
+ });
176
+ this.#events.send(openFrame(streamId));
177
+ return streamId;
178
+ }
179
+
180
+ /** The computer side, once the origin socket is connected. */
181
+ accept(streamId: number): void {
182
+ const stream = this.#streams.get(streamId);
183
+ if (!stream || stream.established || this.role !== "computer") return;
184
+ stream.established = true;
185
+ stream.receiveWindow += OPEN_WINDOW_DELTA;
186
+ this.#events.send(acceptFrame(streamId));
187
+ }
188
+
189
+ /** Sends up to the window and the frame cap; returns the bytes taken. */
190
+ write(streamId: number, data: Uint8Array): number {
191
+ const stream = this.#streams.get(streamId);
192
+ if (!stream || stream.localFin || (this.role === "computer" && !stream.established)) return 0;
193
+ const size = Math.min(data.length, stream.sendWindow, MAX_DATA_PAYLOAD);
194
+ if (size === 0) return 0;
195
+ stream.sendWindow -= size;
196
+ this.#events.send(dataFrame(streamId, data.subarray(0, size)));
197
+ return size;
198
+ }
199
+
200
+ finish(streamId: number): void {
201
+ const stream = this.#streams.get(streamId);
202
+ if (!stream || stream.localFin) return;
203
+ stream.localFin = true;
204
+ this.#events.send(finFrame(streamId));
205
+ this.#closeIfDone(streamId, stream);
206
+ }
207
+
208
+ reset(streamId: number): void {
209
+ if (!this.#streams.delete(streamId)) return;
210
+ this.#events.send(resetFrame(streamId));
211
+ this.#events.reset(streamId);
212
+ }
213
+
214
+ /** The receiver handed `bytes` on (to the origin socket, or the HTTP stack). Grants them back in 1 MiB steps. */
215
+ consumed(streamId: number, bytes: number): void {
216
+ const stream = this.#streams.get(streamId);
217
+ if (!stream || stream.remoteFin) return;
218
+ stream.uncredited += bytes;
219
+ if (stream.uncredited < WINDOW_UPDATE_AFTER) return;
220
+ const delta = stream.uncredited;
221
+ stream.uncredited = 0;
222
+ stream.receiveWindow += delta;
223
+ this.#events.send(windowFrame(streamId, delta));
224
+ }
225
+
226
+ ping(value: number): void {
227
+ this.#events.send(pingFrame(value));
228
+ }
229
+
230
+ goAway(code: number = GO_AWAY.normal): void {
231
+ this.#localGoneAway = true;
232
+ this.#events.send(goAwayFrame(code));
233
+ }
234
+
235
+ /** Sends GoAway(1); the caller closes with 4000. */
236
+ protocolError(): void {
237
+ this.goAway(GO_AWAY.protocol);
238
+ this.closeAll();
239
+ }
240
+
241
+ closeAll(): void {
242
+ const ids = [...this.#streams.keys()];
243
+ this.#streams.clear();
244
+ for (const id of ids) this.#events.reset(id);
245
+ }
246
+
247
+ receive(frame: Frame): void {
248
+ if (frame.type === TYPE.ping) {
249
+ if (frame.flags === FLAG.syn) this.#events.send(pingFrame(frame.length, FLAG.ack));
250
+ else this.#events.pong?.(frame.length);
251
+ return;
252
+ }
253
+ if (frame.type === TYPE.goAway) {
254
+ this.#peerGoneAway = true;
255
+ this.#events.goAway?.(frame.length);
256
+ return;
257
+ }
258
+ if (frame.flags & FLAG.syn) {
259
+ this.#receiveOpen(frame);
260
+ return;
261
+ }
262
+ if (frame.flags & FLAG.ack) {
263
+ this.#receiveAck(frame);
264
+ return;
265
+ }
266
+ const stream = this.#known(frame.streamId);
267
+ if (!stream) return;
268
+ this.#receiveOnStream(frame.streamId, stream, frame);
269
+ }
270
+
271
+ /** A stream that ended is ignored: an RST and the peer's last frames cross. An ID never opened is an error. */
272
+ #known(streamId: number): StreamState | null {
273
+ if (streamId % 2 === 0 || streamId > this.#highestId) throw new YamuxError("unknown_stream");
274
+ return this.#streams.get(streamId) ?? null;
275
+ }
276
+
277
+ #receiveOpen(frame: Frame): void {
278
+ if (this.role === "phone") throw new YamuxError("computer_opened_stream");
279
+ if (frame.type !== TYPE.windowUpdate || frame.flags !== FLAG.syn) throw new YamuxError("open_flags");
280
+ if (frame.streamId % 2 === 0) throw new YamuxError("even_stream");
281
+ if (frame.streamId <= this.#highestId) throw new YamuxError("stream_reused");
282
+ this.#highestId = frame.streamId;
283
+ if (this.#localGoneAway || this.#streams.size >= MAX_STREAMS) {
284
+ this.#events.send(resetFrame(frame.streamId));
285
+ return;
286
+ }
287
+ this.#streams.set(frame.streamId, {
288
+ established: false,
289
+ receiveWindow: INITIAL_WINDOW,
290
+ sendWindow: INITIAL_WINDOW + frame.length,
291
+ uncredited: 0,
292
+ localFin: false,
293
+ remoteFin: false,
294
+ });
295
+ this.#events.opened?.(frame.streamId);
296
+ }
297
+
298
+ #receiveAck(frame: Frame): void {
299
+ if (this.role === "computer") throw new YamuxError("phone_ack");
300
+ if (frame.type !== TYPE.windowUpdate || (frame.flags & ~(FLAG.ack | FLAG.fin | FLAG.rst)) !== 0) throw new YamuxError("ack_flags");
301
+ const stream = this.#known(frame.streamId);
302
+ if (!stream) return;
303
+ if (stream.established) throw new YamuxError("duplicate_ack");
304
+ stream.established = true;
305
+ this.#events.accepted?.(frame.streamId);
306
+ this.#receiveOnStream(frame.streamId, stream, { ...frame, flags: frame.flags & ~FLAG.ack });
307
+ }
308
+
309
+ #receiveOnStream(streamId: number, stream: StreamState, frame: Frame): void {
310
+ if (frame.flags & FLAG.rst) {
311
+ this.#streams.delete(streamId);
312
+ this.#events.reset(streamId);
313
+ return;
314
+ }
315
+ if (frame.type === TYPE.data) {
316
+ if (stream.remoteFin && (frame.length > 0 || frame.flags & FLAG.fin)) throw new YamuxError("data_after_fin");
317
+ if (frame.length > stream.receiveWindow) throw new YamuxError("window_exceeded");
318
+ stream.receiveWindow -= frame.length;
319
+ if (frame.length > 0) this.#events.data(streamId, frame.data as Uint8Array);
320
+ } else if (frame.length > 0) {
321
+ stream.sendWindow += frame.length;
322
+ this.#events.writable(streamId);
323
+ }
324
+ if (frame.flags & FLAG.fin) {
325
+ if (stream.remoteFin) throw new YamuxError("data_after_fin");
326
+ stream.remoteFin = true;
327
+ this.#events.ended(streamId);
328
+ this.#closeIfDone(streamId, stream);
329
+ }
330
+ }
331
+
332
+ #closeIfDone(streamId: number, stream: StreamState): void {
333
+ if (stream.localFin && stream.remoteFin) this.#streams.delete(streamId);
334
+ }
335
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ljwei-stak/dsh-model-route-bot",
3
- "version": "0.66.4",
3
+ "version": "0.67.0",
4
4
  "type": "module",
5
5
  "description": "DSH Bot workspace with integrated model routing, official tools and remote computers",
6
6
  "repository": {
@@ -51,6 +51,7 @@
51
51
  "modules/bot-runtime/vendor/ds-bot/office.js",
52
52
  "modules/bot-runtime/vendor/ds-bot/test/harness-resolve.mjs",
53
53
  "modules/bot-runtime/web-search.mjs",
54
+ "modules/openmaus-protocol/",
54
55
  "packages/orchestration/src/bot-runtime-config.mjs",
55
56
  ".dsh-plugin/index.mjs",
56
57
  ".dsh-plugin/official-tools-remote-service.mjs",
@@ -256,6 +257,7 @@
256
257
  "packages/orchestration/src/sqlite-router-state.mjs",
257
258
  "packages/orchestration/src/task-contract.mjs",
258
259
  "packages/orchestration/src/bot-settings.mjs",
260
+ "packages/orchestration/src/main-model-routes.mjs",
259
261
  "packages/orchestration/src/funding-accounts.mjs",
260
262
  "packages/orchestration/src/model-accounting.mjs",
261
263
  "packages/capability-broker/src/cli-bootstrap.mjs",
@@ -323,6 +325,7 @@
323
325
  "packages/orchestration/src/machine-heartbeat.mjs",
324
326
  "remote-computer-agent/start.cmd",
325
327
  "remote-computer-agent/setup.ps1",
328
+ "remote-computer-agent/pair-device.ps1",
326
329
  "remote-computer-agent/launch-agent.ps1",
327
330
  "native/windows-desktop/",
328
331
  "remote-computer-agent/build.mjs",
@@ -338,11 +341,13 @@
338
341
  "check:client": "node scripts/build-client.mjs --check",
339
342
  "check:contracts": "node scripts/check-contracts.mjs",
340
343
  "contracts:write": "node scripts/check-contracts.mjs --write",
341
- "build:agent": "node remote-computer-agent/build.mjs",
344
+ "build:agent": "node scripts/build-openmaus-protocol.mjs && node remote-computer-agent/build.mjs",
342
345
  "check:source": "node scripts/integration-product-source.mjs",
343
346
  "pretest": "node scripts/prepare-product-tests.mjs",
344
347
  "test": "node scripts/run-tests.mjs",
345
- "prepack": "node remote-computer-agent/build.mjs && node scripts/prepack.mjs",
348
+ "verify:independent": "node scripts/verify-bot-independent.mjs",
349
+ "verify:independent-chat": "node scripts/verify-independent-chat-browser.mjs && node scripts/verify-independent-chat-rich-browser.mjs && node scripts/verify-independent-chat-native-browser.mjs",
350
+ "prepack": "node scripts/build-openmaus-protocol.mjs && node remote-computer-agent/build.mjs && node scripts/prepack.mjs",
346
351
  "prepare:native": "node scripts/prepare-native-driver.mjs",
347
352
  "check:github-math": "node scripts/check-github-math.mjs"
348
353
  },
@@ -356,12 +361,12 @@
356
361
  "@deepseek-ai/dsh-commands": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.4 || 0.2.1-rc.5 || 0.2.2-rc.1 || 0.2.2-rc.2 || 0.2.2-rc.3",
357
362
  "@deepseek-ai/dsh-llm": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.4 || 0.2.1-rc.5 || 0.2.2-rc.1 || 0.2.2-rc.2 || 0.2.2-rc.3",
358
363
  "@deepseek-ai/dsh-sandbox": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.4 || 0.2.1-rc.5 || 0.2.2-rc.1 || 0.2.2-rc.2 || 0.2.2-rc.3",
364
+ "@deepseek-ai/dsh-scope": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.4 || 0.2.1-rc.5 || 0.2.2-rc.1 || 0.2.2-rc.2 || 0.2.2-rc.3",
359
365
  "@deepseek-ai/dsh-settings": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.4 || 0.2.1-rc.5 || 0.2.2-rc.1 || 0.2.2-rc.2 || 0.2.2-rc.3",
360
366
  "@deepseek-ai/dsh-tools": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.4 || 0.2.1-rc.5 || 0.2.2-rc.1 || 0.2.2-rc.2 || 0.2.2-rc.3",
361
367
  "@deepseek-ai/dsh-typert-protocol": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.4 || 0.2.1-rc.5 || 0.2.2-rc.1 || 0.2.2-rc.2 || 0.2.2-rc.3",
362
368
  "@deepseek-ai/dsh-web": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.4 || 0.2.1-rc.5 || 0.2.2-rc.1 || 0.2.2-rc.2 || 0.2.2-rc.3",
363
- "@deepseek-ai/schemastery": "~3.18.4",
364
- "@deepseek-ai/dsh-scope": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.4 || 0.2.1-rc.5 || 0.2.2-rc.1 || 0.2.2-rc.2 || 0.2.2-rc.3"
369
+ "@deepseek-ai/schemastery": "~3.18.4"
365
370
  },
366
371
  "optionalDependencies": {
367
372
  "@lydell/node-pty": "1.2.0-beta.15",
@@ -375,6 +380,7 @@
375
380
  "@deepseek-ai/cordis-plugin-timer": "1.1.7",
376
381
  "@deepseek-ai/dsh-agent": "0.2.1-rc.3 || 0.2.1-rc.4",
377
382
  "@deepseek-ai/dsh-agent-default-model": "0.2.1-rc.3 || 0.2.1-rc.4",
383
+ "@deepseek-ai/dsh-agent-loop": "0.2.1-rc.4",
378
384
  "@deepseek-ai/dsh-agent-preset-registry": "0.2.1-rc.3 || 0.2.1-rc.4",
379
385
  "@deepseek-ai/dsh-api-gateway": "0.2.1-rc.3 || 0.2.1-rc.4",
380
386
  "@deepseek-ai/dsh-api-remotes": "0.2.1-rc.3 || 0.2.1-rc.4",
@@ -439,6 +445,12 @@
439
445
  "dependencies": {
440
446
  "@modelcontextprotocol/server": "2.4.0",
441
447
  "better-sqlite3": "13.0.3",
442
- "deep-view": "0.1.0"
448
+ "deep-view": "0.1.0",
449
+ "jsqr": "1.4.0",
450
+ "qrcode": "1.5.4",
451
+ "ws": "8.22.1"
452
+ },
453
+ "overrides": {
454
+ "zod": "4.6.6"
443
455
  }
444
456
  }
@@ -0,0 +1,156 @@
1
+ # packages/bot-acp
2
+
3
+ The generic ACP (Agent Client Protocol) session core for the Bot runtime: a strictly
4
+ bounded JSON-RPC 2.0 stdio transport, multi-turn session management, fail-closed
5
+ permissions, and a Host-bound adapter the product composition can wire.
6
+
7
+ Ported and re-grounded from OpenMausBot `server/drivers/acp/*` at pin
8
+ `1bc4749aa8307d7d6998bf86de4ef6478460dd93` (Apache-2.0); see `licenses/NOTICE.md`
9
+ for the exact upstream paths and the preserved license text.
10
+
11
+ > **Status.** This module is a core, not a product feature. It is **not wired into
12
+ > product composition**, and no ACP CLI is claimed to be verified end-to-end here.
13
+ > Presence in the tree is not availability: the parent composition still has to
14
+ > register a pinned entry, a grant scope and the funding/ledger hooks.
15
+
16
+ ## Modules
17
+
18
+ | File | Purpose |
19
+ | --- | --- |
20
+ | `src/rpc-codec.mjs` | Pure codec: newline framing with UTF-8 reassembly and a hard frame cap, message classification, id validation, wire-error mapping, client/agent capability schema |
21
+ | `src/stdio-rpc.mjs` | Bounded transport: `createChildPipe` (real stdio) and `createStdioRpc` (id-mapped requests, notifications, responses, pending/deadline/idle/backlog limits, duplicate/unknown/late response accounting, accurate pending rejection on real process exit) |
22
+ | `src/acp-host.mjs` | One agent process and the sessions in it: sessionId routing, single-flight `initialize`/`authenticate`, session/lifetime/draining bounds, fail-closed unrouted handler |
23
+ | `src/session-core.mjs` | Multi-turn session: handshake, `session/new`, capability-gated `session/load`, serial turns with explicit busy, text-only prompts, `session/update` merging, stopReason classification, advertised-only model/config/mode control, an `onDispatched` dispatch marker and `acpNotDispatched`-tagged pre-dispatch refusals |
24
+ | `src/permission-policy.mjs` | `session/request_permission` decided by committed Tool/Task/operation authority; every unknown, duplicate, expired, revoked, timed-out or closing case denies. `createClientMethodGuard` refuses `fs/*` and `terminal/*` unless the Host enables them with a submitted root and IO callbacks |
25
+ | `src/native-launch.mjs` | `pinAcpEntry` (absolute path + digest + real version proof), `createSupervisorPipe` (native supervisor stdio relay), `launchAcpProcess` — all reusing `packages/cli-runtime/src/native-process.mjs` |
26
+ | `src/acp-adapter.mjs` | `createAcpAdapter` — the public surface: identity, Host hooks, the one-slot turn fence, settlement ordering, cancellation, usage/cost evidence, stop evidence |
27
+ ## Public API
28
+
29
+ ```js
30
+ import { createAcpAdapter } from './packages/bot-acp/src/acp-adapter.mjs'
31
+
32
+ const adapter = await createAcpAdapter({
33
+ authority, // capability-broker authority: { describe, assertModel, assertTool }
34
+ subject, // registered authority subject
35
+ binding, // { taskId, attemptId, machineId, workspaceId, location }
36
+ operationId, // durable operation identity
37
+ entry, // { executable, sha256, version, script?, scriptSha256? } — Host-pinned
38
+ spawnArgs, // Host-constructed argv after the executable (e.g. ['acp'])
39
+ runtimeRoot, // isolated absolute runtime root (private HOME)
40
+ workspace, // agent cwd
41
+ model, // { catalogProvider, catalogModel }
42
+ signal, // AbortSignal
43
+ hooks, // { onBeforeRequest, onObserve, onSettle, onClose } — the only ledger recipients
44
+ onPermissionRequest, // Host permission callback; absent denies every ask
45
+ operationTool, // (identity) => committed tool name | null
46
+ resumeSessionId, // only honoured when the agent advertises loadSession
47
+ })
48
+
49
+ await adapter.start() // initialize → authenticate → session/new (or load)
50
+ const turn = await adapter.submitTurn({ text }) // text only
51
+ adapter.cancel() // a request, never a stop confirmation
52
+ await adapter.stop('reason') // real process stop + evidence
53
+ adapter.snapshot() // evidence snapshot
54
+ ```
55
+
56
+ A turn result carries `{ ok, outcome, stopReason, sessionId, text, usage, costUsd,
57
+ costStatus, elapsedMs, cancelRequestedAt, updates, errorText, dispatched }`.
58
+
59
+ `outcome` is one of `succeeded`, `cancelled`, `failed`, `not-run` or `unknown`.
60
+ `not-run` means the prompt provably never reached the agent (a refused admission, a
61
+ failed launch or session handshake, a pre-dispatch refusal, a cancelled
62
+ reservation); `unknown` means the prompt was issued and the agent may have acted
63
+ on it — an unknown outcome is reported verbatim and is never replayed
64
+ automatically. `dispatched` records whether the prompt was really issued, and
65
+ `snapshot().turnsDispatched` counts dispatches, never completions.
66
+
67
+ `submitTurn` rejects only for a request that never became a turn (a malformed
68
+ payload, a Host admission refusal, a denied grant). Once a turn exists it always
69
+ ends in exactly one settlement, delivered to `hooks.onSettle` before the returned
70
+ promise resolves.
71
+
72
+ ## Policy invariants
73
+
74
+ - **Authority is Host state.** Model routes, tools, executable paths, digests,
75
+ environment and credentials never arrive through a turn payload: `submitTurn`
76
+ accepts `{ text }` and nothing else. A driver request naming an executable,
77
+ endpoint or credential is an error (`HOST_OWNED_ENGINE_INPUT` upstream, and the
78
+ same discipline here). Request JSON that claims a `botId`, `owner` or `config`
79
+ is never authority.
80
+ - **One turn slot, reserved before the first await.** `submitTurn` takes its slot
81
+ synchronously, so a second call in the same tick is refused with
82
+ `ACP_TURN_IN_PROGRESS` and performs no admission, no launch and no prompt.
83
+ Admission, session start, dispatch, the peer's result and the Host settlement
84
+ all belong to that one slot, and the Host grant is re-judged after every
85
+ asynchronous boundary. A turn settled underneath a pending call (cancel, stop,
86
+ revocation, peer exit) is never resumed: the late continuation refuses with
87
+ `ACP_TURN_NOT_DISPATCHED`.
88
+ - **Settlement is durable before it is public.** `hooks.onSettle` is the only
89
+ settlement path, and it runs before the success is published and before the
90
+ slot is released; a slow hook keeps the turn observably pending
91
+ (`busy === true`, `snapshot().settlement.inFlight`). A hook that refuses
92
+ refuses the success: `submitTurn` rejects with `ACP_SETTLEMENT_FAILED` carrying
93
+ the original native result and its unknown funding evidence, and the adapter
94
+ retires instead of letting a new request run on an unrecorded turn. Nothing is
95
+ caught and swallowed into a success, no cost or usage figure is invented, and
96
+ no second ledger is opened.
97
+ - **Not-run and unknown are different answers.** A refused admission releases the
98
+ slot so the caller may call again and records no settlement. After admission,
99
+ a failure before the prompt is issued settles once as `not-run`; a prompt whose
100
+ write may have reached the agent settles as `unknown`. `turnsDispatched` counts
101
+ the dispatch moment, not completions.
102
+ - **Session and model identity are fenced by a pending turn.** `start`,
103
+ `startFresh`, `selectModel` and `setMode` refuse while a turn is pending. The
104
+ model a turn runs under is the Host-committed `catalogModel`: a peer advertising
105
+ another model is not authority, and selecting one anyway would make the turn
106
+ evidence and the funded grant disagree. A different binding needs a rebuilt,
107
+ re-authorized adapter (`ACP_MODEL_BINDING_CHANGED`), never owner or permission
108
+ data smuggled through request JSON.
109
+ - **Stop and cancel work at every stage.** `stop` settles a pending turn first
110
+ (not-run before dispatch, unknown afterwards), awaits a launch that raced it so
111
+ no late spawn outlives the stop, really stops the native tree, and only then
112
+ waits for in-flight settlements before reporting `onClose`. `cancel` during a
113
+ pending admission settles the reservation as not-run; after dispatch it sends
114
+ `session/cancel`, and an unanswered cancel ends in a real process stop. Only the
115
+ launcher's own accounting (Windows job object) confirms a tree stop; a cancel
116
+ request is never a stop confirmation, and a stop without one is reported
117
+ `unconfirmed`, not `confirmed`.- **Fail-closed permissions.** Only an `allow`-kind option the agent itself offered
118
+ can approve, and only after the Host handler answers against a committed grant.
119
+ Missing handler, unknown option, duplicate ask, timeout, close, revocation and
120
+ abort all deny. Session-scoped "allow always" is deliberately not implemented:
121
+ every ask is decided by the Host.
122
+ - **Client file and terminal access is off by default.** `fs/read_text_file`,
123
+ `fs/write_text_file` and `terminal/*` are advertised `false` and refused with
124
+ method-not-found. Enabling them requires a Host-submitted root plus Host IO
125
+ callbacks; a hand-written instruction in a prompt is never the boundary.
126
+ - **Unknown is not success.** An unknown `stopReason` is reported verbatim;
127
+ `end_turn` with no output is a lost turn; a refused resume never opens a new
128
+ session and replays the old turn.
129
+ - **Cancel is a request, not a confirmation.** `session/cancel` and the process
130
+ tree-stop are recorded separately; only the native supervisor's own accounting
131
+ (Windows job object) confirms a tree stop.
132
+ - **No second ledger.** ACP reports token/context usage and no dollar figure, so
133
+ `costUsd` stays `null` and `costStatus` stays `unknown`; evidence flows to the
134
+ Host hooks only.
135
+ - **`onObserve` is diagnostic, `onSettle` is the record.** `onObserve` reports
136
+ that a session exists; it is never a settlement and never carries money. Only
137
+ `onSettle` records a turn, and only `onClose` reports the stop evidence.
138
+
139
+ ## Tests
140
+
141
+ | File | Runs |
142
+ | --- | --- |
143
+ | `tests/bot-acp-codec.test.mjs` | fast, offline: framing, UTF-8 splits, oversize, malformed, duplicate/unknown ids, exit rejection, real-child pipe |
144
+ | `tests/bot-acp-permissions.test.mjs` | fast, offline: every deny path, client-method guard, path containment |
145
+ | `tests/bot-acp-session-state.test.mjs` | fast, in-process: session lifecycle, turn rules, model/mode gating, permission routing |
146
+ | `tests/bot-acp-turn-fence.test.mjs` | fast, in-process: the turn fence — same-tick submits, slow/refused admission, slow/refused settlement, stop during a launch, pin/launch/dispatch failures, late and retired-process traffic, cancel/revocation at every stage, identity fences — against a controlled launcher seam |
147
+ | `tests/bot-acp-session.native.mjs` | **serial** (`node --test --test-concurrency=1`): real offline child through the real native supervisor, including the turn-fence suite |
148
+
149
+ `scripts/verify-bot-acp.mjs` runs all five in an isolated `tmp/integration/bot-acp/`
150
+ directory and writes `report.json`. No agent CLI, network, user profile, credential
151
+ or desktop is touched.
152
+
153
+ The seam suite proves the *timing* of the fence deterministically; the native
154
+ suite proves the same rules against a real offline Node child and a real native
155
+ supervisor, which is the only real-machine evidence. Neither is a substitute for
156
+ the other.