@tt-a1i/hive 1.7.0 → 2.0.2

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 (251) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.en.md +73 -11
  3. package/README.md +41 -8
  4. package/dist/src/cli/hive-remote.d.ts +46 -0
  5. package/dist/src/cli/hive-remote.js +257 -0
  6. package/dist/src/cli/hive-update.js +7 -2
  7. package/dist/src/cli/hive.d.ts +6 -0
  8. package/dist/src/cli/hive.js +64 -0
  9. package/dist/src/cli/team.d.ts +22 -0
  10. package/dist/src/cli/team.js +255 -5
  11. package/dist/src/server/agent-command-resolver.js +10 -3
  12. package/dist/src/server/agent-exit-classification.d.ts +6 -0
  13. package/dist/src/server/agent-exit-classification.js +6 -0
  14. package/dist/src/server/agent-manager-support.d.ts +2 -1
  15. package/dist/src/server/agent-manager-support.js +59 -15
  16. package/dist/src/server/agent-manager.d.ts +3 -0
  17. package/dist/src/server/agent-manager.js +22 -7
  18. package/dist/src/server/agent-run-bootstrap.d.ts +14 -0
  19. package/dist/src/server/agent-run-bootstrap.js +11 -4
  20. package/dist/src/server/agent-run-exit-handler.js +14 -8
  21. package/dist/src/server/agent-run-starter.d.ts +3 -1
  22. package/dist/src/server/agent-run-starter.js +22 -5
  23. package/dist/src/server/agent-run-sync.js +13 -5
  24. package/dist/src/server/agent-runtime-types.d.ts +1 -0
  25. package/dist/src/server/agent-runtime.d.ts +2 -1
  26. package/dist/src/server/agent-runtime.js +9 -2
  27. package/dist/src/server/agent-startup-instructions.d.ts +2 -1
  28. package/dist/src/server/agent-startup-instructions.js +8 -4
  29. package/dist/src/server/agent-stdin-dispatcher.d.ts +4 -2
  30. package/dist/src/server/agent-stdin-dispatcher.js +35 -3
  31. package/dist/src/server/command-preset-defaults.d.ts +6 -1
  32. package/dist/src/server/command-preset-defaults.js +56 -0
  33. package/dist/src/server/fs-browse.d.ts +2 -0
  34. package/dist/src/server/fs-browse.js +165 -31
  35. package/dist/src/server/fs-pick-folder.js +6 -69
  36. package/dist/src/server/fs-sandbox.d.ts +5 -3
  37. package/dist/src/server/fs-sandbox.js +5 -3
  38. package/dist/src/server/hive-team-guidance.js +18 -6
  39. package/dist/src/server/machine-name.d.ts +2 -0
  40. package/dist/src/server/machine-name.js +13 -0
  41. package/dist/src/server/open-target-commands.d.ts +1 -0
  42. package/dist/src/server/open-target-commands.js +4 -1
  43. package/dist/src/server/orchestrator-autostart.js +1 -1
  44. package/dist/src/server/platform-path.d.ts +1 -0
  45. package/dist/src/server/platform-path.js +14 -1
  46. package/dist/src/server/post-start-input-writer.js +50 -13
  47. package/dist/src/server/preset-launch-support.js +1 -0
  48. package/dist/src/server/recovery-summary.d.ts +2 -1
  49. package/dist/src/server/recovery-summary.js +2 -1
  50. package/dist/src/server/remote-audit-store.d.ts +51 -0
  51. package/dist/src/server/remote-audit-store.js +108 -0
  52. package/dist/src/server/remote-config-keys.d.ts +17 -0
  53. package/dist/src/server/remote-config-keys.js +27 -0
  54. package/dist/src/server/remote-control-constants.d.ts +30 -0
  55. package/dist/src/server/remote-control-constants.js +29 -0
  56. package/dist/src/server/remote-device-session.d.ts +40 -0
  57. package/dist/src/server/remote-device-session.js +22 -0
  58. package/dist/src/server/remote-device-store.d.ts +36 -0
  59. package/dist/src/server/remote-device-store.js +67 -0
  60. package/dist/src/server/remote-frame-bridge.d.ts +102 -0
  61. package/dist/src/server/remote-frame-bridge.js +791 -0
  62. package/dist/src/server/remote-gateway-client.d.ts +14 -0
  63. package/dist/src/server/remote-gateway-client.js +36 -0
  64. package/dist/src/server/remote-loopback-auth.d.ts +6 -0
  65. package/dist/src/server/remote-loopback-auth.js +112 -0
  66. package/dist/src/server/remote-pairing-tunnel.d.ts +59 -0
  67. package/dist/src/server/remote-pairing-tunnel.js +146 -0
  68. package/dist/src/server/remote-pairing.d.ts +58 -0
  69. package/dist/src/server/remote-pairing.js +237 -0
  70. package/dist/src/server/remote-tunnel.d.ts +113 -0
  71. package/dist/src/server/remote-tunnel.js +514 -0
  72. package/dist/src/server/restart-policy-support.d.ts +4 -1
  73. package/dist/src/server/restart-policy-support.js +3 -1
  74. package/dist/src/server/restart-policy.d.ts +1 -1
  75. package/dist/src/server/restart-policy.js +19 -3
  76. package/dist/src/server/route-types.d.ts +1 -1
  77. package/dist/src/server/routes-dispatches.js +1 -1
  78. package/dist/src/server/routes-fs.js +3 -3
  79. package/dist/src/server/routes-marketplace.js +2 -2
  80. package/dist/src/server/routes-open-workspace.js +1 -1
  81. package/dist/src/server/routes-remote.d.ts +2 -0
  82. package/dist/src/server/routes-remote.js +166 -0
  83. package/dist/src/server/routes-runtime.js +6 -6
  84. package/dist/src/server/routes-settings.js +16 -16
  85. package/dist/src/server/routes-tasks.js +2 -2
  86. package/dist/src/server/routes-team-memory.d.ts +2 -0
  87. package/dist/src/server/routes-team-memory.js +154 -0
  88. package/dist/src/server/routes-team-recall.d.ts +2 -0
  89. package/dist/src/server/routes-team-recall.js +119 -0
  90. package/dist/src/server/routes-team.js +31 -9
  91. package/dist/src/server/routes-ui.js +11 -1
  92. package/dist/src/server/routes-workflow-schedules.js +3 -3
  93. package/dist/src/server/routes-workflows.js +5 -5
  94. package/dist/src/server/routes-workspace-memory-dreams.d.ts +2 -0
  95. package/dist/src/server/routes-workspace-memory-dreams.js +105 -0
  96. package/dist/src/server/routes-workspace-memory.d.ts +2 -0
  97. package/dist/src/server/routes-workspace-memory.js +215 -0
  98. package/dist/src/server/routes-workspaces.js +9 -9
  99. package/dist/src/server/routes.js +10 -0
  100. package/dist/src/server/runtime-database.d.ts +1 -0
  101. package/dist/src/server/runtime-database.js +27 -2
  102. package/dist/src/server/runtime-restart-policy.d.ts +3 -1
  103. package/dist/src/server/runtime-restart-policy.js +2 -1
  104. package/dist/src/server/runtime-store-contract.d.ts +37 -0
  105. package/dist/src/server/runtime-store-dream.d.ts +23 -0
  106. package/dist/src/server/runtime-store-dream.js +16 -0
  107. package/dist/src/server/runtime-store-helpers.d.ts +20 -0
  108. package/dist/src/server/runtime-store-helpers.js +81 -7
  109. package/dist/src/server/runtime-store-memory.d.ts +33 -0
  110. package/dist/src/server/runtime-store-memory.js +37 -0
  111. package/dist/src/server/runtime-store-remote.d.ts +5 -0
  112. package/dist/src/server/runtime-store-remote.js +45 -0
  113. package/dist/src/server/runtime-store-workflows.js +2 -0
  114. package/dist/src/server/runtime-store.js +14 -3
  115. package/dist/src/server/session-capture-claude.d.ts +1 -1
  116. package/dist/src/server/session-capture-claude.js +7 -4
  117. package/dist/src/server/session-capture-codex.js +4 -5
  118. package/dist/src/server/session-capture-gemini.js +4 -5
  119. package/dist/src/server/session-capture-opencode.d.ts +4 -4
  120. package/dist/src/server/session-capture-opencode.js +20 -12
  121. package/dist/src/server/session-capture-qwen.d.ts +5 -0
  122. package/dist/src/server/session-capture-qwen.js +104 -0
  123. package/dist/src/server/session-capture.d.ts +17 -0
  124. package/dist/src/server/session-capture.js +16 -0
  125. package/dist/src/server/sqlite-schema-v23.d.ts +2 -0
  126. package/dist/src/server/sqlite-schema-v23.js +43 -0
  127. package/dist/src/server/sqlite-schema-v24.d.ts +2 -0
  128. package/dist/src/server/sqlite-schema-v24.js +34 -0
  129. package/dist/src/server/sqlite-schema-v25.d.ts +2 -0
  130. package/dist/src/server/sqlite-schema-v25.js +127 -0
  131. package/dist/src/server/sqlite-schema-v26.d.ts +2 -0
  132. package/dist/src/server/sqlite-schema-v26.js +56 -0
  133. package/dist/src/server/sqlite-schema-v27.d.ts +6 -0
  134. package/dist/src/server/sqlite-schema-v27.js +92 -0
  135. package/dist/src/server/sqlite-schema-v28.d.ts +2 -0
  136. package/dist/src/server/sqlite-schema-v28.js +19 -0
  137. package/dist/src/server/sqlite-schema-v29.d.ts +2 -0
  138. package/dist/src/server/sqlite-schema-v29.js +27 -0
  139. package/dist/src/server/sqlite-schema-v30.d.ts +2 -0
  140. package/dist/src/server/sqlite-schema-v30.js +27 -0
  141. package/dist/src/server/sqlite-schema-v31.d.ts +2 -0
  142. package/dist/src/server/sqlite-schema-v31.js +30 -0
  143. package/dist/src/server/sqlite-schema.d.ts +1 -1
  144. package/dist/src/server/sqlite-schema.js +49 -1
  145. package/dist/src/server/startup-command-parser.js +5 -1
  146. package/dist/src/server/tasks-file-watcher.d.ts +2 -0
  147. package/dist/src/server/tasks-file-watcher.js +15 -6
  148. package/dist/src/server/tasks-file.js +30 -5
  149. package/dist/src/server/tasks-websocket-server.js +4 -0
  150. package/dist/src/server/team-authz.d.ts +1 -1
  151. package/dist/src/server/team-authz.js +13 -1
  152. package/dist/src/server/team-list-enrichment.js +3 -1
  153. package/dist/src/server/team-memory-digest.d.ts +52 -0
  154. package/dist/src/server/team-memory-digest.js +200 -0
  155. package/dist/src/server/team-memory-dream-applier.d.ts +5 -0
  156. package/dist/src/server/team-memory-dream-applier.js +234 -0
  157. package/dist/src/server/team-memory-dream-http-serializers.d.ts +13 -0
  158. package/dist/src/server/team-memory-dream-http-serializers.js +12 -0
  159. package/dist/src/server/team-memory-dream-ops.d.ts +40 -0
  160. package/dist/src/server/team-memory-dream-ops.js +153 -0
  161. package/dist/src/server/team-memory-dream-reverter.d.ts +22 -0
  162. package/dist/src/server/team-memory-dream-reverter.js +221 -0
  163. package/dist/src/server/team-memory-dream-run-store.d.ts +23 -0
  164. package/dist/src/server/team-memory-dream-run-store.js +211 -0
  165. package/dist/src/server/team-memory-dream-runner.d.ts +37 -0
  166. package/dist/src/server/team-memory-dream-runner.js +178 -0
  167. package/dist/src/server/team-memory-dream-scheduler.d.ts +32 -0
  168. package/dist/src/server/team-memory-dream-scheduler.js +115 -0
  169. package/dist/src/server/team-memory-dream-store.d.ts +19 -0
  170. package/dist/src/server/team-memory-dream-store.js +16 -0
  171. package/dist/src/server/team-memory-dream-types.d.ts +104 -0
  172. package/dist/src/server/team-memory-dream-types.js +23 -0
  173. package/dist/src/server/team-memory-export.d.ts +22 -0
  174. package/dist/src/server/team-memory-export.js +220 -0
  175. package/dist/src/server/team-memory-feature.d.ts +12 -0
  176. package/dist/src/server/team-memory-feature.js +12 -0
  177. package/dist/src/server/team-memory-http-serializers.d.ts +102 -0
  178. package/dist/src/server/team-memory-http-serializers.js +46 -0
  179. package/dist/src/server/team-memory-injection.d.ts +31 -0
  180. package/dist/src/server/team-memory-injection.js +49 -0
  181. package/dist/src/server/team-memory-store.d.ts +116 -0
  182. package/dist/src/server/team-memory-store.js +513 -0
  183. package/dist/src/server/team-operations.d.ts +5 -1
  184. package/dist/src/server/team-operations.js +46 -16
  185. package/dist/src/server/team-recall-store.d.ts +38 -0
  186. package/dist/src/server/team-recall-store.js +205 -0
  187. package/dist/src/server/terminal-input-profile.d.ts +1 -1
  188. package/dist/src/server/terminal-input-profile.js +18 -0
  189. package/dist/src/server/terminal-ws-server.js +6 -0
  190. package/dist/src/server/ui-auth-helpers.d.ts +1 -1
  191. package/dist/src/server/ui-auth-helpers.js +7 -1
  192. package/dist/src/server/ui-auth.d.ts +3 -0
  193. package/dist/src/server/ui-auth.js +21 -1
  194. package/dist/src/server/workflow-cli-policy.d.ts +2 -3
  195. package/dist/src/server/workflow-cli-policy.js +3 -3
  196. package/dist/src/server/workflow-runner.d.ts +1 -0
  197. package/dist/src/server/workflow-runner.js +9 -4
  198. package/dist/src/server/workspace-path-validation.js +6 -2
  199. package/dist/src/server/workspace-store.d.ts +1 -1
  200. package/dist/src/server/workspace-store.js +35 -9
  201. package/dist/src/shared/fs-browse.d.ts +1 -0
  202. package/dist/src/shared/fs-browse.js +1 -0
  203. package/dist/src/shared/path-input.d.ts +12 -0
  204. package/dist/src/shared/path-input.js +22 -0
  205. package/dist/src/shared/remote-bridge-routing.d.ts +19 -0
  206. package/dist/src/shared/remote-bridge-routing.js +141 -0
  207. package/dist/src/shared/remote-crypto.d.ts +138 -0
  208. package/dist/src/shared/remote-crypto.js +427 -0
  209. package/dist/src/shared/remote-pairing-code.d.ts +7 -0
  210. package/dist/src/shared/remote-pairing-code.js +47 -0
  211. package/dist/src/shared/remote-protocol.d.ts +160 -0
  212. package/dist/src/shared/remote-protocol.js +526 -0
  213. package/dist/src/shared/team-memory.d.ts +11 -0
  214. package/dist/src/shared/team-memory.js +10 -0
  215. package/dist/src/shared/team-recall.d.ts +1 -0
  216. package/dist/src/shared/team-recall.js +1 -0
  217. package/dist/src/shared/types.d.ts +4 -5
  218. package/package.json +12 -5
  219. package/scripts/postinstall-native-artifacts.mjs +113 -0
  220. package/web/dist/assets/AddWorkerDialog-CbV75qUX.js +2 -0
  221. package/web/dist/assets/AddWorkspaceFlow-CwV-7wPx.js +1 -0
  222. package/web/dist/assets/FirstRunWizard-a6PWIK3x.js +1 -0
  223. package/web/dist/assets/MarketplaceDrawer-Dd8WIA8T.js +67 -0
  224. package/web/dist/assets/TaskGraphDrawer-Bk5WFIk_.js +1 -0
  225. package/web/dist/assets/{WhatsNewDialog-CHkZeINH.js → WhatsNewDialog-C2VZaip0.js} +1 -1
  226. package/web/dist/assets/WorkerModal-DucW-9YT.js +1 -0
  227. package/web/dist/assets/WorkflowsDrawer-Bjf4olbR.js +1 -0
  228. package/web/dist/assets/WorkspaceMemoryDrawer-DglCy_5f.js +1 -0
  229. package/web/dist/assets/WorkspaceTaskDrawer-BIWwISvA.js +1 -0
  230. package/web/dist/assets/index-BAiLYajK.css +1 -0
  231. package/web/dist/assets/index-BV2k9Dts.js +73 -0
  232. package/web/dist/assets/search-Bk2HQvO7.js +1 -0
  233. package/web/dist/assets/square-terminal-D93m9hfY.js +1 -0
  234. package/web/dist/cli-icons/agy.png +0 -0
  235. package/web/dist/cli-icons/cursor.ico +0 -0
  236. package/web/dist/cli-icons/grok.ico +0 -0
  237. package/web/dist/cli-icons/qwen.png +0 -0
  238. package/web/dist/index.html +8 -3
  239. package/web/dist/sw.js +1 -1
  240. package/scripts/fix-runtime-artifacts.mjs +0 -33
  241. package/web/dist/assets/AddWorkerDialog-BRUxpa3f.js +0 -2
  242. package/web/dist/assets/AddWorkspaceDialog-D56x5JCb.js +0 -1
  243. package/web/dist/assets/FirstRunWizard-BFVaMIsE.js +0 -1
  244. package/web/dist/assets/MarketplaceDrawer-DeEZ35dN.js +0 -76
  245. package/web/dist/assets/WorkerModal-BBCuMLIa.js +0 -1
  246. package/web/dist/assets/WorkspaceTaskDrawer-CpZHAcj1.js +0 -1
  247. package/web/dist/assets/WorkspaceTerminalPanels-7If2mDyp.js +0 -1
  248. package/web/dist/assets/WorkspaceTerminalPanels-DDGTF8rc.css +0 -1
  249. package/web/dist/assets/index-5zh61jMg.css +0 -1
  250. package/web/dist/assets/index-CxNL0O-C.js +0 -73
  251. package/web/dist/assets/path-join-7MR1s7b1.js +0 -1
@@ -0,0 +1,427 @@
1
+ // E2E pairing handshake + directional session key schedule + AEAD frame seal/open + SAS.
2
+ //
3
+ // Runs in BOTH node and browser: no Buffer, no node-only APIs. Bytes are Uint8Array.
4
+ //
5
+ // Crypto invariants enforced here (each is exercised by tests/unit/remote-crypto.test.ts):
6
+ // 1. The 12-byte frame header travels in the clear but is authenticated as AEAD AAD.
7
+ // 2. HKDF derives DIRECTIONAL keys (daemon->device vs device->daemon are distinct, never shared).
8
+ // 3. The nonce binds (direction, streamId, seq): seq is per-direction monotonic (replay/reorder
9
+ // guard) and streamId is folded in separately so the same key never reuses a nonce across
10
+ // concurrent streams.
11
+ // 4. The handshake transcript binds daemonId + deviceId + protocolVersion (+ a fresh per-session
12
+ // salt) so a downgrade / misbind / reroute / reconnect produces different keys.
13
+ //
14
+ // HARDEN: a fresh 32-byte `sessionSalt` is mandatory at PAIRING time. It is mixed into both the
15
+ // HKDF salt and the transcript, so the persisted directional keys (d2p/p2d) are fresh per pairing.
16
+ //
17
+ // M6.1 — those persisted directional keys are ROOTS, never used as an AEAD key directly. On EVERY
18
+ // (re)connect (page reload, daemon reconnect, peer-online) both sides exchange a fresh bilateral
19
+ // connection salt and call `deriveConnectionKeys` to derive the per-connection AEAD keys. That is
20
+ // what now makes resetting `seq` to 0 per FrameSealer safe: the AEAD key is guaranteed fresh per
21
+ // connection (a reload draws a fresh phone salt, a reconnect a fresh daemon salt, and neither side
22
+ // can force the other's contribution to repeat), so (key, nonce) reuse is structurally impossible.
23
+ // The root is never an AEAD key, so resetting seq under a reused root no longer collides.
24
+ import { xchacha20poly1305 } from '@noble/ciphers/chacha.js';
25
+ import { randomBytes } from '@noble/ciphers/utils.js';
26
+ import { x25519 } from '@noble/curves/ed25519.js';
27
+ import { hkdf } from '@noble/hashes/hkdf.js';
28
+ import { sha256 } from '@noble/hashes/sha2.js';
29
+ export const REMOTE_CRYPTO_VERSION = 2; // was 1 — wire handshake changed (mandatory bilateral salt exchange before any sealed frame)
30
+ export const X25519_KEY_LEN = 32;
31
+ export const SHARED_SECRET_LEN = 32;
32
+ export const SESSION_KEY_LEN = 32;
33
+ export const NONCE_LEN = 24;
34
+ export const AEAD_TAG_LEN = 16;
35
+ export const PAIRING_SECRET_LEN = 32;
36
+ export const SESSION_SALT_LEN = 32;
37
+ export const SAS_DIGITS = 6;
38
+ // M6.1 — per-connection rekey. A fresh bilateral salt every (re)connect derives the AEAD keys from
39
+ // the persisted ROOT keys, so seq can reset to 0 per connection without ever reusing (key, nonce).
40
+ export const CONN_SALT_LEN = 32;
41
+ // HKDF labels — ASCII. These ARE the wire contract; changing any string requires bumping the version.
42
+ const HKDF_SALT = 'hive/remote/v1/pair';
43
+ const INFO_KEY_D2P = 'hive/remote/v1/key/daemon->device';
44
+ const INFO_KEY_P2D = 'hive/remote/v1/key/device->daemon';
45
+ const INFO_SAS = 'hive/remote/v1/sas';
46
+ const TRANSCRIPT_TAG = 'hive/remote/v1/transcript';
47
+ // Per-connection key labels. The `conn/*` namespace + ikm=root keeps these disjoint from the
48
+ // pairing `v1/*` labels (ikm=pairingSecret||ss), so no label can collide across the two schedules.
49
+ const INFO_CONN_D2P = 'hive/remote/v2/conn/daemon->device';
50
+ const INFO_CONN_P2D = 'hive/remote/v2/conn/device->daemon';
51
+ const CONN_SALT_PREFIX = 'hive/remote/v2/conn-salt';
52
+ // Nonce direction tag (nonce[0]); also documents the key->direction mapping.
53
+ const DIR_BYTE_D2P = 0x01;
54
+ const DIR_BYTE_P2D = 0x02;
55
+ // Header field offsets — crypto reads streamId/seq out of the header bytes (R5 single source of
56
+ // truth). MUST match remote-protocol.ts encodeHeader: streamId@4 (u32be), seq@8 (u32be).
57
+ const HEADER_OFF_STREAM_ID = 4;
58
+ const HEADER_OFF_SEQ = 8;
59
+ const HEADER_MIN_LEN = 12;
60
+ // ── private helpers ──────────────────────────────────────────────────────────
61
+ const te = new TextEncoder();
62
+ function utf8(s) {
63
+ return te.encode(s);
64
+ }
65
+ function concat(...parts) {
66
+ let total = 0;
67
+ for (const p of parts)
68
+ total += p.length;
69
+ const out = new Uint8Array(total);
70
+ let off = 0;
71
+ for (const p of parts) {
72
+ out.set(p, off);
73
+ off += p.length;
74
+ }
75
+ return out;
76
+ }
77
+ function u32be(n) {
78
+ if (!Number.isInteger(n) || n < 0 || n > 0xffffffff) {
79
+ throw new RangeError(`u32be out of range: ${n}`);
80
+ }
81
+ const out = new Uint8Array(4);
82
+ new DataView(out.buffer).setUint32(0, n);
83
+ return out;
84
+ }
85
+ function u32beRead(b, off = 0) {
86
+ if (b.length < off + 4)
87
+ throw new RangeError('u32beRead: buffer too short');
88
+ return new DataView(b.buffer, b.byteOffset, b.byteLength).getUint32(off);
89
+ }
90
+ function u64be(n) {
91
+ if (!Number.isSafeInteger(n) || n < 0) {
92
+ throw new RangeError(`u64be out of range: ${n}`);
93
+ }
94
+ const out = new Uint8Array(8);
95
+ // JS bitwise ops are 32-bit; split into hi/lo 32-bit words.
96
+ const hi = Math.floor(n / 0x1_0000_0000);
97
+ const lo = n >>> 0;
98
+ const dv = new DataView(out.buffer);
99
+ dv.setUint32(0, hi);
100
+ dv.setUint32(4, lo);
101
+ return out;
102
+ }
103
+ function lp(b) {
104
+ if (b.length > 0xffff)
105
+ throw new RangeError('lp: value too long for u16 length prefix');
106
+ const out = new Uint8Array(2 + b.length);
107
+ new DataView(out.buffer).setUint16(0, b.length);
108
+ out.set(b, 2);
109
+ return out;
110
+ }
111
+ function assertLen(b, len, name) {
112
+ if (b.length !== len) {
113
+ throw new RangeError(`${name} must be ${len} bytes, got ${b.length}`);
114
+ }
115
+ }
116
+ // ── base64url (no btoa/atob/Buffer) ──────────────────────────────────────────
117
+ const B64_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
118
+ const B64_REVERSE = (() => {
119
+ const r = new Int16Array(128).fill(-1);
120
+ for (let i = 0; i < B64_ALPHABET.length; i++) {
121
+ r[B64_ALPHABET.charCodeAt(i)] = i;
122
+ }
123
+ return r;
124
+ })();
125
+ export function toBase64Url(bytes) {
126
+ let out = '';
127
+ let i = 0;
128
+ for (; i + 3 <= bytes.length; i += 3) {
129
+ const b0 = bytes[i];
130
+ const b1 = bytes[i + 1];
131
+ const b2 = bytes[i + 2];
132
+ out += B64_ALPHABET[b0 >> 2];
133
+ out += B64_ALPHABET[((b0 & 0x03) << 4) | (b1 >> 4)];
134
+ out += B64_ALPHABET[((b1 & 0x0f) << 2) | (b2 >> 6)];
135
+ out += B64_ALPHABET[b2 & 0x3f];
136
+ }
137
+ const rem = bytes.length - i;
138
+ if (rem === 1) {
139
+ const b0 = bytes[i];
140
+ out += B64_ALPHABET[b0 >> 2];
141
+ out += B64_ALPHABET[(b0 & 0x03) << 4];
142
+ }
143
+ else if (rem === 2) {
144
+ const b0 = bytes[i];
145
+ const b1 = bytes[i + 1];
146
+ out += B64_ALPHABET[b0 >> 2];
147
+ out += B64_ALPHABET[((b0 & 0x03) << 4) | (b1 >> 4)];
148
+ out += B64_ALPHABET[(b1 & 0x0f) << 2];
149
+ }
150
+ return out;
151
+ }
152
+ export function fromBase64Url(s) {
153
+ if (s.length % 4 === 1)
154
+ throw new RangeError('invalid base64url');
155
+ const fullGroups = s.length >> 2;
156
+ const rem = s.length & 3;
157
+ const outLen = fullGroups * 3 + (rem === 0 ? 0 : rem - 1);
158
+ const out = new Uint8Array(outLen);
159
+ const val = (ch) => {
160
+ if (ch >= 128)
161
+ throw new RangeError('invalid base64url');
162
+ const v = B64_REVERSE[ch];
163
+ if (v < 0)
164
+ throw new RangeError('invalid base64url');
165
+ return v;
166
+ };
167
+ let o = 0;
168
+ let i = 0;
169
+ for (; i + 4 <= s.length; i += 4) {
170
+ const c0 = val(s.charCodeAt(i));
171
+ const c1 = val(s.charCodeAt(i + 1));
172
+ const c2 = val(s.charCodeAt(i + 2));
173
+ const c3 = val(s.charCodeAt(i + 3));
174
+ out[o++] = (c0 << 2) | (c1 >> 4);
175
+ out[o++] = ((c1 & 0x0f) << 4) | (c2 >> 2);
176
+ out[o++] = ((c2 & 0x03) << 6) | c3;
177
+ }
178
+ if (rem === 2) {
179
+ const c0 = val(s.charCodeAt(i));
180
+ const c1 = val(s.charCodeAt(i + 1));
181
+ out[o++] = (c0 << 2) | (c1 >> 4);
182
+ }
183
+ else if (rem === 3) {
184
+ const c0 = val(s.charCodeAt(i));
185
+ const c1 = val(s.charCodeAt(i + 1));
186
+ const c2 = val(s.charCodeAt(i + 2));
187
+ out[o++] = (c0 << 2) | (c1 >> 4);
188
+ out[o++] = ((c1 & 0x0f) << 4) | (c2 >> 2);
189
+ }
190
+ return out;
191
+ }
192
+ // ── pairing payload (QR) ──────────────────────────────────────────────────────
193
+ export function encodePairingPayload(p) {
194
+ return JSON.stringify(p);
195
+ }
196
+ export function decodePairingPayload(s) {
197
+ let raw;
198
+ try {
199
+ raw = JSON.parse(s);
200
+ }
201
+ catch {
202
+ throw new RangeError('invalid pairing payload: not JSON');
203
+ }
204
+ if (typeof raw !== 'object' || raw === null) {
205
+ throw new RangeError('invalid pairing payload: not an object');
206
+ }
207
+ const o = raw;
208
+ if (o.v !== REMOTE_CRYPTO_VERSION) {
209
+ throw new RangeError(`unsupported pairing version: ${String(o.v)}`);
210
+ }
211
+ if (typeof o.gatewayUrl !== 'string' || o.gatewayUrl.length === 0) {
212
+ throw new RangeError('invalid pairing payload: gatewayUrl');
213
+ }
214
+ if (typeof o.daemonId !== 'string' || o.daemonId.length === 0) {
215
+ throw new RangeError('invalid pairing payload: daemonId');
216
+ }
217
+ if (typeof o.pairingSecret !== 'string') {
218
+ throw new RangeError('invalid pairing payload: pairingSecret');
219
+ }
220
+ const secret = fromBase64Url(o.pairingSecret);
221
+ if (secret.length !== PAIRING_SECRET_LEN) {
222
+ throw new RangeError(`pairingSecret must be ${PAIRING_SECRET_LEN} bytes`);
223
+ }
224
+ return {
225
+ v: o.v,
226
+ gatewayUrl: o.gatewayUrl,
227
+ daemonId: o.daemonId,
228
+ pairingSecret: o.pairingSecret,
229
+ };
230
+ }
231
+ // ── device keypair ─────────────────────────────────────────────────────────────
232
+ export function generateDeviceKeyPair() {
233
+ const secretKey = x25519.utils.randomSecretKey();
234
+ const publicKey = x25519.getPublicKey(secretKey);
235
+ return { secretKey, publicKey };
236
+ }
237
+ export function serializeDeviceKeyPair(kp) {
238
+ return {
239
+ secretKey: toBase64Url(kp.secretKey),
240
+ publicKey: toBase64Url(kp.publicKey),
241
+ };
242
+ }
243
+ export function deserializeDeviceKeyPair(s) {
244
+ const secretKey = fromBase64Url(s.secretKey);
245
+ const publicKey = fromBase64Url(s.publicKey);
246
+ if (secretKey.length !== X25519_KEY_LEN) {
247
+ throw new RangeError(`secretKey must be ${X25519_KEY_LEN} bytes`);
248
+ }
249
+ if (publicKey.length !== X25519_KEY_LEN) {
250
+ throw new RangeError(`publicKey must be ${X25519_KEY_LEN} bytes`);
251
+ }
252
+ return { secretKey, publicKey };
253
+ }
254
+ /** A fresh per-session salt. MUST be drawn anew on every (re)connect — see header comment. */
255
+ export function generateSessionSalt() {
256
+ return randomBytes(SESSION_SALT_LEN);
257
+ }
258
+ // ── key schedule ───────────────────────────────────────────────────────────────
259
+ function deriveSession(args) {
260
+ // Length validation up front so IKM/transcript byte boundaries are unambiguous (harden minor).
261
+ assertLen(args.pairingSecret, PAIRING_SECRET_LEN, 'pairingSecret');
262
+ assertLen(args.sessionSalt, SESSION_SALT_LEN, 'sessionSalt');
263
+ assertLen(args.daemonPublicKey, X25519_KEY_LEN, 'daemonPublicKey');
264
+ assertLen(args.devicePublicKey, X25519_KEY_LEN, 'devicePublicKey');
265
+ const ss = x25519.getSharedSecret(args.localSecretKey, args.peerPublicKey);
266
+ assertLen(ss, SHARED_SECRET_LEN, 'sharedSecret');
267
+ // pairingSecret is load-bearing: a passive relay with both pubkeys still can't derive keys.
268
+ const ikm = concat(args.pairingSecret, ss);
269
+ // Fixed daemon/device slots (not self/peer) so both sides hash identical bytes.
270
+ const transcript = concat(utf8(TRANSCRIPT_TAG), Uint8Array.of(0x00), u32be(args.ids.protocolVersion), lp(utf8(args.ids.daemonId)), lp(utf8(args.ids.deviceId)), args.daemonPublicKey, args.devicePublicKey, args.sessionSalt // fresh per-session randomness → distinct keys every (re)connect
271
+ );
272
+ const transcriptHash = sha256(transcript);
273
+ // HKDF salt also mixes the session salt so a reused (pairing, ss) still gives fresh keys.
274
+ const salt = concat(utf8(HKDF_SALT), args.sessionSalt);
275
+ const d2p = hkdf(sha256, ikm, salt, concat(utf8(INFO_KEY_D2P), Uint8Array.of(0x00), transcriptHash), SESSION_KEY_LEN);
276
+ const p2d = hkdf(sha256, ikm, salt, concat(utf8(INFO_KEY_P2D), Uint8Array.of(0x00), transcriptHash), SESSION_KEY_LEN);
277
+ const sas = deriveSas(transcriptHash, args.ids);
278
+ return { d2p, p2d, sas, transcriptHash };
279
+ }
280
+ export function deriveDaemonSession(args) {
281
+ return deriveSession({
282
+ localSecretKey: args.daemonSecretKey,
283
+ peerPublicKey: args.devicePublicKey,
284
+ daemonPublicKey: args.daemonPublicKey,
285
+ devicePublicKey: args.devicePublicKey,
286
+ pairingSecret: args.pairingSecret,
287
+ sessionSalt: args.sessionSalt,
288
+ ids: args.ids,
289
+ });
290
+ }
291
+ export function deriveDeviceSession(args) {
292
+ return deriveSession({
293
+ localSecretKey: args.deviceSecretKey,
294
+ peerPublicKey: args.daemonPublicKey,
295
+ daemonPublicKey: args.daemonPublicKey,
296
+ devicePublicKey: args.devicePublicKey,
297
+ pairingSecret: args.pairingSecret,
298
+ sessionSalt: args.sessionSalt,
299
+ ids: args.ids,
300
+ });
301
+ }
302
+ /**
303
+ * A fresh 32-byte connection salt. Drawn anew on EVERY (re)connect (page reload, daemon reconnect,
304
+ * peer-online). Both sides contribute one (bilateral) so neither can force the result to repeat.
305
+ */
306
+ export function generateConnSalt() {
307
+ return randomBytes(CONN_SALT_LEN);
308
+ }
309
+ /**
310
+ * Derive the two per-connection AEAD keys from the persisted ROOT keys + the bilateral connection
311
+ * salts. Called identically on both sides — same inputs, same byte order ⇒ same ConnectionKeys, so
312
+ * d2p/p2d still match across the wire.
313
+ *
314
+ * The persisted keys (DeviceSession.keys / StoredDeviceSession.rootKeys) are ROOTS: they are never
315
+ * passed to sealFrame/openFrame. This is the ONLY consumer of the root for AEAD purposes.
316
+ *
317
+ * ikm = the PER-DIRECTION root (rootD2p→d2p, rootP2d→p2d) — no cross-direction mixing.
318
+ * salt = CONN_SALT_PREFIX || phoneConnSalt(32) || daemonConnSalt(32) (bilateral, fixed order).
319
+ * info = per-direction label || 0x00 || ctx, where ctx binds protocolVersion + ids + both salts.
320
+ *
321
+ * NOTE: the salts appear in BOTH the HKDF salt arg and the info ctx. This is deliberate
322
+ * defense-in-depth, not a leftover — the salt slot makes them the extract entropy, the info slot
323
+ * binds them into the per-direction expand context so the two directions can never coincide and a
324
+ * future label edit can't silently drop the salt binding. Redundant but harmless (HKDF salt and
325
+ * info are independent inputs). A root reused across (deviceId, daemonId, version) yields different
326
+ * connKeys, and the two directions stay distinct (invariant 2).
327
+ */
328
+ export function deriveConnectionKeys(args) {
329
+ assertLen(args.rootD2p, SESSION_KEY_LEN, 'rootD2p');
330
+ assertLen(args.rootP2d, SESSION_KEY_LEN, 'rootP2d');
331
+ assertLen(args.phoneConnSalt, CONN_SALT_LEN, 'phoneConnSalt');
332
+ assertLen(args.daemonConnSalt, CONN_SALT_LEN, 'daemonConnSalt');
333
+ const salt = concat(utf8(CONN_SALT_PREFIX), args.phoneConnSalt, args.daemonConnSalt);
334
+ const ctx = concat(Uint8Array.of(0x00), u32be(args.ids.protocolVersion), lp(utf8(args.ids.daemonId)), lp(utf8(args.ids.deviceId)), args.phoneConnSalt, args.daemonConnSalt);
335
+ const d2p = hkdf(sha256, args.rootD2p, salt, concat(utf8(INFO_CONN_D2P), ctx), SESSION_KEY_LEN);
336
+ const p2d = hkdf(sha256, args.rootP2d, salt, concat(utf8(INFO_CONN_P2D), ctx), SESSION_KEY_LEN);
337
+ return { d2p, p2d };
338
+ }
339
+ export function deriveSas(transcriptHash, ids) {
340
+ assertLen(transcriptHash, 32, 'transcriptHash');
341
+ const sasBytes = hkdf(sha256, transcriptHash, utf8(HKDF_SALT), concat(utf8(INFO_SAS), Uint8Array.of(0x00), u32be(ids.protocolVersion)), 4);
342
+ const n = u32beRead(sasBytes) % 1_000_000;
343
+ return n.toString().padStart(SAS_DIGITS, '0');
344
+ }
345
+ // ── nonce (invariant 3 — structural uniqueness) ────────────────────────────────
346
+ export function buildNonce(direction, streamId, seq) {
347
+ if (!Number.isInteger(streamId) || streamId < 0 || streamId > 0xffffffff) {
348
+ throw new RangeError(`streamId out of range: ${streamId}`);
349
+ }
350
+ const nonce = new Uint8Array(NONCE_LEN);
351
+ nonce[0] = direction === 'd2p' ? DIR_BYTE_D2P : DIR_BYTE_P2D;
352
+ nonce.set(u32be(streamId), 1); // bytes 1..4
353
+ // bytes 5..7 stay zero
354
+ nonce.set(u64be(seq), 8); // bytes 8..15 (high 4 zero in M1; seq is u32 on the wire)
355
+ // bytes 16..23 stay zero
356
+ return nonce;
357
+ }
358
+ // ── header field reads (single source of truth — R5) ───────────────────────────
359
+ function readStreamId(headerBytes) {
360
+ if (headerBytes.length < HEADER_MIN_LEN) {
361
+ throw new RangeError('headerBytes too short');
362
+ }
363
+ return u32beRead(headerBytes, HEADER_OFF_STREAM_ID);
364
+ }
365
+ function readSeq(headerBytes) {
366
+ if (headerBytes.length < HEADER_MIN_LEN) {
367
+ throw new RangeError('headerBytes too short');
368
+ }
369
+ return u32beRead(headerBytes, HEADER_OFF_SEQ);
370
+ }
371
+ // ── frame seal/open (invariant 1 — header as AAD) ───────────────────────────────
372
+ export function sealFrame(args) {
373
+ assertLen(args.key, SESSION_KEY_LEN, 'key');
374
+ const streamId = readStreamId(args.headerBytes);
375
+ const seq = readSeq(args.headerBytes);
376
+ const nonce = buildNonce(args.direction, streamId, seq);
377
+ const cipher = xchacha20poly1305(args.key, nonce, args.headerBytes);
378
+ return cipher.encrypt(args.payload);
379
+ }
380
+ export function openFrame(args) {
381
+ assertLen(args.key, SESSION_KEY_LEN, 'key');
382
+ const streamId = readStreamId(args.headerBytes);
383
+ const seq = readSeq(args.headerBytes);
384
+ const nonce = buildNonce(args.direction, streamId, seq);
385
+ const cipher = xchacha20poly1305(args.key, nonce, args.headerBytes);
386
+ return cipher.decrypt(args.ciphertext);
387
+ }
388
+ // ── stateful replay/reorder guard (invariant 3, sequencing half) — per-direction ─
389
+ export function createSealer(direction) {
390
+ return { direction, nextSeq: 0 };
391
+ }
392
+ export function createOpener(direction) {
393
+ return { direction, lastSeq: -1 };
394
+ }
395
+ export function sealNext(sealer, args) {
396
+ // The header is the source of truth for streamId/seq; verify the caller's seq matches.
397
+ const headerSeq = readSeq(args.headerBytes);
398
+ if (headerSeq !== sealer.nextSeq) {
399
+ throw new RangeError(`header seq ${headerSeq} does not match sealer.nextSeq ${sealer.nextSeq}`);
400
+ }
401
+ const ciphertext = sealFrame({
402
+ key: args.key,
403
+ direction: sealer.direction,
404
+ headerBytes: args.headerBytes,
405
+ payload: args.payload,
406
+ });
407
+ const seq = sealer.nextSeq;
408
+ sealer.nextSeq += 1;
409
+ return { seq, ciphertext };
410
+ }
411
+ export function openNext(opener, args) {
412
+ if (args.seq !== opener.lastSeq + 1) {
413
+ throw new RangeError('out-of-order or replayed frame');
414
+ }
415
+ const headerSeq = readSeq(args.headerBytes);
416
+ if (headerSeq !== args.seq) {
417
+ throw new RangeError('out-of-order or replayed frame');
418
+ }
419
+ const plaintext = openFrame({
420
+ key: args.key,
421
+ direction: opener.direction,
422
+ headerBytes: args.headerBytes,
423
+ ciphertext: args.ciphertext,
424
+ });
425
+ opener.lastSeq = args.seq;
426
+ return plaintext;
427
+ }
@@ -0,0 +1,7 @@
1
+ export declare const PAIRING_CODE_ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
2
+ export declare const PAIRING_CODE_CHARS = 12;
3
+ export declare const PAIRING_CODE_RANDOM_BYTES = 8;
4
+ export declare const PAIRING_CODE_SECRET_CONTEXT = "hive-remote-pairing-code-v1:";
5
+ export declare const normalizePairingCode: (input: string) => string | null;
6
+ export declare const formatPairingCode: (input: string) => string;
7
+ export declare const generatePairingCode: (randomBytes: (length: number) => Uint8Array) => string;
@@ -0,0 +1,47 @@
1
+ export const PAIRING_CODE_ALPHABET = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
2
+ export const PAIRING_CODE_CHARS = 12;
3
+ export const PAIRING_CODE_RANDOM_BYTES = 8;
4
+ export const PAIRING_CODE_SECRET_CONTEXT = 'hive-remote-pairing-code-v1:';
5
+ export const normalizePairingCode = (input) => {
6
+ const normalized = input
7
+ .trim()
8
+ .toUpperCase()
9
+ .replace(/[\s-]+/g, '')
10
+ .replace(/O/g, '0')
11
+ .replace(/[IL]/g, '1');
12
+ if (normalized.length !== PAIRING_CODE_CHARS)
13
+ return null;
14
+ for (const ch of normalized) {
15
+ if (!PAIRING_CODE_ALPHABET.includes(ch))
16
+ return null;
17
+ }
18
+ return normalized;
19
+ };
20
+ export const formatPairingCode = (input) => {
21
+ const normalized = normalizePairingCode(input) ?? input.replace(/[\s-]+/g, '').toUpperCase();
22
+ const groups = [];
23
+ for (let i = 0; i < normalized.length; i += 4) {
24
+ groups.push(normalized.slice(i, i + 4));
25
+ }
26
+ return groups.join('-');
27
+ };
28
+ export const generatePairingCode = (randomBytes) => {
29
+ const bytes = randomBytes(PAIRING_CODE_RANDOM_BYTES);
30
+ if (bytes.length < PAIRING_CODE_RANDOM_BYTES) {
31
+ throw new RangeError(`pairing code requires ${PAIRING_CODE_RANDOM_BYTES} random bytes`);
32
+ }
33
+ let acc = 0;
34
+ let bits = 0;
35
+ let out = '';
36
+ for (const byte of bytes) {
37
+ acc = (acc << 8) | byte;
38
+ bits += 8;
39
+ while (bits >= 5 && out.length < PAIRING_CODE_CHARS) {
40
+ bits -= 5;
41
+ out += PAIRING_CODE_ALPHABET[(acc >> bits) & 31];
42
+ }
43
+ if (out.length === PAIRING_CODE_CHARS)
44
+ break;
45
+ }
46
+ return out;
47
+ };
@@ -0,0 +1,160 @@
1
+ export { REMOTE_CRYPTO_VERSION as PROTOCOL_VERSION } from './remote-crypto.js';
2
+ export declare const FrameKind: {
3
+ readonly Open: 1;
4
+ readonly Data: 2;
5
+ readonly End: 3;
6
+ readonly Reset: 4;
7
+ readonly Ping: 5;
8
+ readonly Ack: 6;
9
+ };
10
+ export type FrameKind = (typeof FrameKind)[keyof typeof FrameKind];
11
+ export declare const StreamTransport: {
12
+ readonly Http: 1;
13
+ readonly Ws: 2;
14
+ };
15
+ export type StreamTransport = (typeof StreamTransport)[keyof typeof StreamTransport];
16
+ export declare const ResetCode: {
17
+ readonly Normal: 0;
18
+ readonly ProtocolError: 1;
19
+ readonly FlowViolation: 2;
20
+ readonly StreamRefused: 3;
21
+ readonly VersionMismatch: 4;
22
+ readonly InternalError: 5;
23
+ };
24
+ export type ResetCode = (typeof ResetCode)[keyof typeof ResetCode];
25
+ export declare const FLOW: {
26
+ readonly INITIAL_WINDOW: number;
27
+ readonly ACK_THRESHOLD: number;
28
+ };
29
+ export declare const HEADER_BYTES = 12;
30
+ export declare const CHANNEL_STREAM_ID = 0;
31
+ export declare const CONN_SALT_STREAM_ID = 4294967295;
32
+ export declare class ProtocolError extends Error {
33
+ code: ResetCode;
34
+ constructor(message: string, code: ResetCode);
35
+ }
36
+ export interface FrameHeader {
37
+ version: number;
38
+ kind: FrameKind;
39
+ flags: number;
40
+ streamId: number;
41
+ seq: number;
42
+ }
43
+ /**
44
+ * bit0 (FIN) is a permitted Data flag; every other bit is reserved-MUST-be-0. So this returns true
45
+ * for 0x0000 and 0x0001 only. The non-Data canonicalization (where even bit0 is reserved) is
46
+ * enforced in decodeHeader, which knows the kind.
47
+ */
48
+ export declare function isReservedFlagsClear(flags: number): boolean;
49
+ export declare function encodeHeader(h: FrameHeader): Uint8Array;
50
+ export declare function decodeHeader(bytes: Uint8Array): FrameHeader;
51
+ export interface StreamMeta {
52
+ transport: StreamTransport;
53
+ http?: {
54
+ method: string;
55
+ path: string;
56
+ headers: [string, string][];
57
+ hasBody: boolean;
58
+ };
59
+ ws?: {
60
+ path: string;
61
+ query?: [string, string][];
62
+ subprotocol?: string;
63
+ };
64
+ }
65
+ export interface HelloMeta {
66
+ protocolVersion: number;
67
+ role: 'daemon' | 'device';
68
+ daemonId: string;
69
+ deviceId: string;
70
+ }
71
+ export interface HttpResponseHead {
72
+ status: number;
73
+ headers: [string, string][];
74
+ }
75
+ export declare function encodeOpenPayload(m: StreamMeta): Uint8Array;
76
+ export declare function decodeOpenPayload(p: Uint8Array): StreamMeta;
77
+ export declare const CHANNEL_DISC: {
78
+ readonly ConnSalt: 1;
79
+ readonly Hello: 2;
80
+ };
81
+ export type ChannelDisc = (typeof CHANNEL_DISC)[keyof typeof CHANNEL_DISC];
82
+ export interface ConnSaltMsg {
83
+ role: 'daemon' | 'device';
84
+ salt: Uint8Array;
85
+ }
86
+ export declare function encodeConnSalt(m: ConnSaltMsg): Uint8Array;
87
+ export declare function decodeConnSalt(p: Uint8Array): ConnSaltMsg;
88
+ /**
89
+ * True iff a frame's payload self-describes as the unsealed ConnSalt disc. Only consulted for frames
90
+ * that already arrived on CONN_SALT_STREAM_ID — a secondary self-check, NOT the primary demux. The
91
+ * primary demux is the cleartext streamId (a sealed Hello on CHANNEL_STREAM_ID has uniform-random
92
+ * byte 0, so this byte alone is never trusted to route a streamId-0 frame).
93
+ */
94
+ export declare function isConnSaltPayload(payload: Uint8Array): boolean;
95
+ export declare function encodeHello(m: HelloMeta): Uint8Array;
96
+ export declare function decodeHello(p: Uint8Array): HelloMeta;
97
+ export declare function encodeAckPayload(cumulativeBytes: number): Uint8Array;
98
+ export declare function decodeAckPayload(p: Uint8Array): number;
99
+ export declare function encodeResetPayload(code: ResetCode): Uint8Array;
100
+ export declare function decodeResetPayload(p: Uint8Array): ResetCode;
101
+ export declare function encodeWsMessage(data: Uint8Array, isText: boolean): Uint8Array;
102
+ export declare function decodeWsMessage(p: Uint8Array): {
103
+ data: Uint8Array;
104
+ isText: boolean;
105
+ };
106
+ export declare function encodeHttpHead(h: HttpResponseHead): Uint8Array;
107
+ export declare function encodeHttpBodyChunk(b: Uint8Array): Uint8Array;
108
+ export declare function decodeHttpData(p: Uint8Array): {
109
+ kind: 'head';
110
+ head: HttpResponseHead;
111
+ } | {
112
+ kind: 'body';
113
+ data: Uint8Array;
114
+ };
115
+ export declare function createStreamIdAllocator(side: 'daemon' | 'device'): () => number;
116
+ export type StreamState = 'idle' | 'open' | 'localClosed' | 'remoteClosed' | 'closed';
117
+ export interface StreamMachine {
118
+ state(): StreamState;
119
+ onRecv(kind: FrameKind): {
120
+ ok: true;
121
+ } | {
122
+ ok: false;
123
+ reset: ResetCode;
124
+ };
125
+ onSendData(): {
126
+ ok: true;
127
+ } | {
128
+ ok: false;
129
+ reset: ResetCode;
130
+ };
131
+ onLocalEnd(): void;
132
+ onReset(): void;
133
+ }
134
+ export declare function createStreamMachine(): StreamMachine;
135
+ export interface FlowController {
136
+ trySend(n: number): {
137
+ ok: true;
138
+ } | {
139
+ ok: false;
140
+ reason: 'WindowExhausted';
141
+ };
142
+ applyAck(cumulativeBytes: number): {
143
+ resumed: boolean;
144
+ };
145
+ onConsume(n: number): {
146
+ ackCumulative: number;
147
+ } | null;
148
+ flushAck(): {
149
+ ackCumulative: number;
150
+ };
151
+ isPaused(): boolean;
152
+ }
153
+ export declare function createFlowController(window?: number, ackThreshold?: number): FlowController;
154
+ export declare function negotiateVersion(_localHello: HelloMeta, peerHello: HelloMeta): {
155
+ ok: true;
156
+ version: number;
157
+ } | {
158
+ ok: false;
159
+ reset: ResetCode;
160
+ };