cloving 1.1.0 → 1.3.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 (568) hide show
  1. package/README.md +584 -144
  2. package/assets/watch-fonts/fira-code-OFL.md +93 -0
  3. package/assets/watch-fonts/fira-code-latin-400-normal.woff2 +0 -0
  4. package/assets/watch-fonts/fira-code-latin-500-normal.woff2 +0 -0
  5. package/assets/watch-fonts/heebo-OFL.md +93 -0
  6. package/assets/watch-fonts/heebo-latin-500-normal.woff2 +0 -0
  7. package/assets/watch-fonts/heebo-latin-700-normal.woff2 +0 -0
  8. package/assets/watch-fonts/heebo-latin-800-normal.woff2 +0 -0
  9. package/assets/watch-fonts/manifest.json +112 -0
  10. package/assets/watch-fonts/space-grotesk-OFL.md +93 -0
  11. package/assets/watch-fonts/space-grotesk-latin-400-normal.woff2 +0 -0
  12. package/assets/watch-fonts/space-grotesk-latin-500-normal.woff2 +0 -0
  13. package/assets/watch-fonts/space-grotesk-latin-600-normal.woff2 +0 -0
  14. package/assets/watch-fonts/space-grotesk-latin-700-normal.woff2 +0 -0
  15. package/dist/src/agent-harness-shell.d.ts +12 -0
  16. package/dist/src/agent-harness-shell.js +90 -0
  17. package/dist/src/agent-harness-versions.d.ts +8 -0
  18. package/dist/src/agent-harness-versions.js +49 -0
  19. package/dist/src/agent-harnesses.d.ts +4 -0
  20. package/dist/src/agent-harnesses.js +13 -0
  21. package/dist/src/authority/commands.d.ts +2 -0
  22. package/dist/src/authority/commands.js +49 -8
  23. package/dist/src/authority/dispatch-decision.d.ts +1 -0
  24. package/dist/src/authority/dispatch-decision.js +7 -4
  25. package/dist/src/authority/events.js +2 -2
  26. package/dist/src/authority/gate-reduction.d.ts +1 -0
  27. package/dist/src/authority/gate-reduction.js +18 -3
  28. package/dist/src/authority/gates.d.ts +2 -1
  29. package/dist/src/authority/gates.js +5 -1
  30. package/dist/src/authority/handoff-chain.d.ts +24 -0
  31. package/dist/src/authority/handoff-chain.js +46 -0
  32. package/dist/src/authority/operations.js +1 -1
  33. package/dist/src/authority/reconciliation.d.ts +1 -0
  34. package/dist/src/authority/reconciliation.js +18 -11
  35. package/dist/src/authority/reduction.d.ts +1 -1
  36. package/dist/src/authority/reduction.js +6 -1
  37. package/dist/src/authority/status-input.d.ts +1 -1
  38. package/dist/src/authority/status.d.ts +1 -0
  39. package/dist/src/authority/status.js +27 -3
  40. package/dist/src/authority/transitions.d.ts +5 -5
  41. package/dist/src/authority/transitions.js +9 -0
  42. package/dist/src/broker/actor/authority-delivery.js +1 -0
  43. package/dist/src/broker/actor/authority-queries.d.ts +11 -1
  44. package/dist/src/broker/actor/authority-queries.js +67 -3
  45. package/dist/src/broker/actor/authority-receipts.js +11 -1
  46. package/dist/src/broker/actor/authority-session.js +2 -2
  47. package/dist/src/broker/actor/authority-settings.js +4 -0
  48. package/dist/src/broker/actor/delivery-intents.d.ts +3 -1
  49. package/dist/src/broker/actor/delivery-intents.js +4 -4
  50. package/dist/src/broker/actor/effect-delivery.js +13 -1
  51. package/dist/src/broker/actor/effect-queries.d.ts +1 -0
  52. package/dist/src/broker/actor/effect-queries.js +7 -0
  53. package/dist/src/broker/actor/pin-check.js +1 -1
  54. package/dist/src/broker/actor/stop-plan.js +1 -1
  55. package/dist/src/broker/actor/turn-state.js +1 -1
  56. package/dist/src/broker/authority-admission.js +26 -5
  57. package/dist/src/broker/authority-restore.js +1 -0
  58. package/dist/src/broker/decision-restore.js +4 -4
  59. package/dist/src/broker/socket-address.d.ts +3 -1
  60. package/dist/src/broker/socket-address.js +2 -2
  61. package/dist/src/cli/arguments.d.ts +11 -13
  62. package/dist/src/cli/arguments.js +37 -107
  63. package/dist/src/cli/command-effects.d.ts +7 -0
  64. package/dist/src/cli/command-effects.js +39 -0
  65. package/dist/src/cli/commands.js +11 -4
  66. package/dist/src/cli/config-arguments.d.ts +11 -0
  67. package/dist/src/cli/config-arguments.js +59 -0
  68. package/dist/src/cli/config-command.d.ts +2 -0
  69. package/dist/src/cli/config-command.js +105 -0
  70. package/dist/src/cli/config-file.d.ts +3 -0
  71. package/dist/src/cli/config-file.js +50 -0
  72. package/dist/src/cli/config-lock.d.ts +7 -0
  73. package/dist/src/cli/config-lock.js +61 -0
  74. package/dist/src/cli/config-notice.d.ts +5 -0
  75. package/dist/src/cli/config-notice.js +46 -0
  76. package/dist/src/cli/doctor-github.d.ts +9 -0
  77. package/dist/src/cli/doctor-github.js +138 -0
  78. package/dist/src/cli/doctor-harness.d.ts +13 -0
  79. package/dist/src/cli/doctor-harness.js +242 -0
  80. package/dist/src/cli/doctor-local.d.ts +18 -0
  81. package/dist/src/cli/doctor-local.js +77 -0
  82. package/dist/src/cli/doctor-runtime.d.ts +15 -0
  83. package/dist/src/cli/doctor-runtime.js +176 -0
  84. package/dist/src/cli/doctor-service.d.ts +1 -0
  85. package/dist/src/cli/doctor-service.js +11 -0
  86. package/dist/src/cli/doctor-workflows.d.ts +3 -0
  87. package/dist/src/cli/doctor-workflows.js +45 -0
  88. package/dist/src/cli/doctor.d.ts +15 -0
  89. package/dist/src/cli/doctor.js +190 -0
  90. package/dist/src/cli/github-ci-progress.d.ts +8 -0
  91. package/dist/src/cli/github-ci-progress.js +83 -0
  92. package/dist/src/cli/github-retry.d.ts +6 -0
  93. package/dist/src/cli/github-retry.js +47 -0
  94. package/dist/src/cli/harness-probes.d.ts +22 -0
  95. package/dist/src/cli/harness-probes.js +183 -0
  96. package/dist/src/cli/main.d.ts +7 -1
  97. package/dist/src/cli/main.js +39 -61
  98. package/dist/src/cli/phone-arguments.d.ts +14 -0
  99. package/dist/src/cli/phone-arguments.js +37 -0
  100. package/dist/src/cli/phone-client.d.ts +2 -0
  101. package/dist/src/cli/phone-client.js +32 -0
  102. package/dist/src/cli/phone-command.d.ts +13 -0
  103. package/dist/src/cli/phone-command.js +101 -0
  104. package/dist/src/cli/probe-command.d.ts +36 -0
  105. package/dist/src/cli/probe-command.js +59 -0
  106. package/dist/src/cli/project-settings.d.ts +4 -0
  107. package/dist/src/cli/project-settings.js +33 -0
  108. package/dist/src/cli/render.d.ts +3 -1
  109. package/dist/src/cli/render.js +102 -39
  110. package/dist/src/cli/report-github.d.ts +18 -0
  111. package/dist/src/cli/report-github.js +200 -0
  112. package/dist/src/cli/report.d.ts +17 -0
  113. package/dist/src/cli/report.js +192 -0
  114. package/dist/src/cli/repository-arguments.d.ts +5 -0
  115. package/dist/src/cli/repository-arguments.js +34 -0
  116. package/dist/src/cli/runtime-arguments.d.ts +4 -0
  117. package/dist/src/cli/runtime-arguments.js +15 -0
  118. package/dist/src/cli/runtime-directory.d.ts +6 -2
  119. package/dist/src/cli/runtime-directory.js +72 -14
  120. package/dist/src/cli/toolchain.d.ts +2 -0
  121. package/dist/src/cli/toolchain.js +22 -2
  122. package/dist/src/cli/usage-render.d.ts +5 -0
  123. package/dist/src/cli/usage-render.js +40 -1
  124. package/dist/src/cli/usage.d.ts +3 -3
  125. package/dist/src/cli/usage.js +74 -45
  126. package/dist/src/cli/watch-actions.d.ts +2 -0
  127. package/dist/src/cli/watch-actions.js +4 -4
  128. package/dist/src/cli/watch-board-view.d.ts +12 -1
  129. package/dist/src/cli/watch-board-view.js +64 -12
  130. package/dist/src/cli/watch-board.d.ts +7 -1
  131. package/dist/src/cli/watch-board.js +61 -16
  132. package/dist/src/cli/watch-card-forms.js +2 -2
  133. package/dist/src/cli/watch-client.d.ts +1 -1
  134. package/dist/src/cli/watch-client.js +12 -3
  135. package/dist/src/cli/watch-header-view.d.ts +27 -0
  136. package/dist/src/cli/watch-header-view.js +116 -0
  137. package/dist/src/cli/watch-model.d.ts +10 -4
  138. package/dist/src/cli/watch-model.js +49 -59
  139. package/dist/src/cli/watch-page.js +67 -14
  140. package/dist/src/cli/watch-phases.js +3 -3
  141. package/dist/src/cli/watch-phone-client.d.ts +1 -0
  142. package/dist/src/cli/watch-phone-client.js +84 -0
  143. package/dist/src/cli/watch-phone-view.d.ts +56 -0
  144. package/dist/src/cli/watch-phone-view.js +113 -0
  145. package/dist/src/cli/watch-phone.d.ts +5 -0
  146. package/dist/src/cli/watch-phone.js +53 -0
  147. package/dist/src/cli/watch-pr-command.d.ts +5 -2
  148. package/dist/src/cli/watch-pr-command.js +41 -4
  149. package/dist/src/cli/watch-pr-identity.d.ts +14 -2
  150. package/dist/src/cli/watch-pr-identity.js +25 -0
  151. package/dist/src/cli/watch-pr-observer.d.ts +1 -0
  152. package/dist/src/cli/watch-pr-observer.js +48 -6
  153. package/dist/src/cli/watch-pull-request.d.ts +12 -0
  154. package/dist/src/cli/watch-pull-request.js +134 -0
  155. package/dist/src/cli/watch-reads.d.ts +7 -1
  156. package/dist/src/cli/watch-reads.js +42 -20
  157. package/dist/src/cli/watch-render.d.ts +23 -2
  158. package/dist/src/cli/watch-render.js +2 -1
  159. package/dist/src/cli/watch-server.js +56 -7
  160. package/dist/src/cli/watch-turn.d.ts +4 -1
  161. package/dist/src/cli/watch-turn.js +38 -2
  162. package/dist/src/cli/watch-usage.js +5 -1
  163. package/dist/src/cli/watch-view.d.ts +10 -17
  164. package/dist/src/cli/watch-view.js +33 -77
  165. package/dist/src/cli.js +106 -23
  166. package/dist/src/command-settings.d.ts +16 -0
  167. package/dist/src/command-settings.js +42 -0
  168. package/dist/src/configuration-change.d.ts +11 -0
  169. package/dist/src/configuration-change.js +62 -0
  170. package/dist/src/configuration-display.d.ts +21 -0
  171. package/dist/src/configuration-display.js +69 -0
  172. package/dist/src/configuration-schema.d.ts +49 -0
  173. package/dist/src/configuration-schema.js +159 -0
  174. package/dist/src/daemon/claude-turn.d.ts +20 -5
  175. package/dist/src/daemon/claude-turn.js +116 -53
  176. package/dist/src/daemon/claude-worker.d.ts +3 -5
  177. package/dist/src/daemon/claude-worker.js +64 -80
  178. package/dist/src/daemon/command-journal.d.ts +5 -0
  179. package/dist/src/daemon/command-journal.js +50 -0
  180. package/dist/src/daemon/command-process.d.ts +28 -0
  181. package/dist/src/daemon/command-process.js +164 -0
  182. package/dist/src/daemon/command-worker.d.ts +19 -0
  183. package/dist/src/daemon/command-worker.js +182 -0
  184. package/dist/src/daemon/cursor-stream.d.ts +4 -0
  185. package/dist/src/daemon/cursor-stream.js +154 -0
  186. package/dist/src/daemon/daemon-adapters.d.ts +58 -0
  187. package/dist/src/daemon/daemon-adapters.js +102 -0
  188. package/dist/src/daemon/daemon-loops.d.ts +14 -0
  189. package/dist/src/daemon/daemon-loops.js +39 -0
  190. package/dist/src/daemon/daemon.d.ts +3 -30
  191. package/dist/src/daemon/daemon.js +132 -110
  192. package/dist/src/daemon/declared-input.d.ts +3 -0
  193. package/dist/src/daemon/declared-input.js +9 -0
  194. package/dist/src/daemon/delivery-router.js +7 -1
  195. package/dist/src/daemon/gemini-stream.d.ts +4 -0
  196. package/dist/src/daemon/gemini-stream.js +129 -0
  197. package/dist/src/daemon/github-calls.d.ts +4 -0
  198. package/dist/src/daemon/github-calls.js +16 -7
  199. package/dist/src/daemon/github-ci-rerun.d.ts +26 -0
  200. package/dist/src/daemon/github-ci-rerun.js +92 -0
  201. package/dist/src/daemon/github-ci.d.ts +6 -0
  202. package/dist/src/daemon/github-ci.js +251 -0
  203. package/dist/src/daemon/github-merge-call.d.ts +16 -0
  204. package/dist/src/daemon/github-merge-call.js +73 -0
  205. package/dist/src/daemon/github-merge.d.ts +3 -5
  206. package/dist/src/daemon/github-merge.js +59 -98
  207. package/dist/src/daemon/github-methods.d.ts +28 -0
  208. package/dist/src/daemon/github-methods.js +90 -16
  209. package/dist/src/daemon/github-operation.d.ts +3 -0
  210. package/dist/src/daemon/github-retry.d.ts +6 -0
  211. package/dist/src/daemon/github-retry.js +13 -0
  212. package/dist/src/daemon/github-ticket-status.js +27 -1
  213. package/dist/src/daemon/github-worker.js +2 -1
  214. package/dist/src/daemon/handoff-worker.d.ts +10 -0
  215. package/dist/src/daemon/handoff-worker.js +232 -0
  216. package/dist/src/daemon/issue-queue.d.ts +2 -0
  217. package/dist/src/daemon/issue-queue.js +92 -63
  218. package/dist/src/daemon/notify-journal.d.ts +18 -0
  219. package/dist/src/daemon/notify-journal.js +108 -0
  220. package/dist/src/daemon/notify-message.js +2 -1
  221. package/dist/src/daemon/notify.js +177 -37
  222. package/dist/src/daemon/opencode-stream.d.ts +5 -0
  223. package/dist/src/daemon/opencode-stream.js +87 -0
  224. package/dist/src/daemon/queue-status.d.ts +4 -0
  225. package/dist/src/daemon/queue-status.js +38 -0
  226. package/dist/src/daemon/retry-worktree.d.ts +18 -0
  227. package/dist/src/daemon/retry-worktree.js +139 -0
  228. package/dist/src/daemon/retrying-turn.d.ts +5 -0
  229. package/dist/src/daemon/retrying-turn.js +145 -21
  230. package/dist/src/daemon/schedule-journal.d.ts +19 -0
  231. package/dist/src/daemon/schedule-journal.js +57 -0
  232. package/dist/src/daemon/text-output.d.ts +18 -0
  233. package/dist/src/daemon/text-output.js +44 -0
  234. package/dist/src/daemon/ticket-dependency-waits.d.ts +18 -0
  235. package/dist/src/daemon/ticket-dependency-waits.js +60 -0
  236. package/dist/src/daemon/ticket-registration.d.ts +10 -8
  237. package/dist/src/daemon/ticket-registration.js +146 -47
  238. package/dist/src/daemon/ticket-source.d.ts +4 -1
  239. package/dist/src/daemon/ticket-source.js +8 -2
  240. package/dist/src/daemon/turn-command.d.ts +10 -0
  241. package/dist/src/daemon/turn-command.js +33 -0
  242. package/dist/src/daemon/turn-report.d.ts +18 -0
  243. package/dist/src/daemon/turn-report.js +79 -0
  244. package/dist/src/daemon/work-tree.d.ts +2 -1
  245. package/dist/src/daemon/work-tree.js +15 -12
  246. package/dist/src/daemon/workflow-generations.d.ts +32 -0
  247. package/dist/src/daemon/workflow-generations.js +57 -0
  248. package/dist/src/daemon/workflow-replay.d.ts +16 -0
  249. package/dist/src/daemon/workflow-replay.js +58 -0
  250. package/dist/src/daemon/workflow-schedules.d.ts +27 -0
  251. package/dist/src/daemon/workflow-schedules.js +185 -0
  252. package/dist/src/decision-visibility.d.ts +2 -0
  253. package/dist/src/decision-visibility.js +10 -0
  254. package/dist/src/decision-wait.d.ts +1 -0
  255. package/dist/src/decision-wait.js +3 -0
  256. package/dist/src/delivery-backoff.d.ts +1 -0
  257. package/dist/src/delivery-backoff.js +3 -0
  258. package/dist/src/doctor-report.d.ts +24 -0
  259. package/dist/src/doctor-report.js +70 -0
  260. package/dist/src/execution-pin.d.ts +9 -0
  261. package/dist/src/execution-pin.js +86 -2
  262. package/dist/src/grok-result.d.ts +12 -0
  263. package/dist/src/grok-result.js +61 -0
  264. package/dist/src/harness-defaults.d.ts +35 -0
  265. package/dist/src/harness-defaults.js +121 -0
  266. package/dist/src/host/decision-resume.d.ts +35 -0
  267. package/dist/src/host/decision-resume.js +86 -0
  268. package/dist/src/host/gate-bridge.d.ts +48 -0
  269. package/dist/src/host/gate-bridge.js +124 -0
  270. package/dist/src/host/run-wait.d.ts +26 -0
  271. package/dist/src/host/run-wait.js +37 -0
  272. package/dist/src/host/start-delivery.d.ts +3 -2
  273. package/dist/src/host/start-delivery.js +11 -4
  274. package/dist/src/host/step-bridge-host.d.ts +6 -19
  275. package/dist/src/host/step-bridge-host.js +57 -55
  276. package/dist/src/host/world-reconciliation.d.ts +1 -0
  277. package/dist/src/host/world-reconciliation.js +3 -0
  278. package/dist/src/host/world-runs.js +5 -0
  279. package/dist/src/issue-identity.d.ts +6 -0
  280. package/dist/src/issue-identity.js +23 -0
  281. package/dist/src/merge-request.d.ts +19 -0
  282. package/dist/src/merge-request.js +32 -0
  283. package/dist/src/phone/canonical-json.d.ts +1 -0
  284. package/dist/src/phone/canonical-json.js +16 -0
  285. package/dist/src/phone/phone-link.d.ts +7 -0
  286. package/dist/src/phone/phone-link.js +21 -0
  287. package/dist/src/phone/qr-data.d.ts +8 -0
  288. package/dist/src/phone/qr-data.js +133 -0
  289. package/dist/src/phone/qr-matrix.d.ts +6 -0
  290. package/dist/src/phone/qr-matrix.js +175 -0
  291. package/dist/src/phone/qr-penalty.d.ts +3 -0
  292. package/dist/src/phone/qr-penalty.js +67 -0
  293. package/dist/src/phone/qr-picture.d.ts +5 -0
  294. package/dist/src/phone/qr-picture.js +24 -0
  295. package/dist/src/private-config.schema.json +263 -0
  296. package/dist/src/project-config.schema.json +65 -0
  297. package/dist/src/project-settings.d.ts +19 -0
  298. package/dist/src/project-settings.js +131 -0
  299. package/dist/src/relay/answer-check.d.ts +24 -0
  300. package/dist/src/relay/answer-check.js +145 -0
  301. package/dist/src/relay/answer-inbox.d.ts +15 -0
  302. package/dist/src/relay/answer-inbox.js +173 -0
  303. package/dist/src/relay/daemon-keys.d.ts +16 -0
  304. package/dist/src/relay/daemon-keys.js +75 -0
  305. package/dist/src/relay/decision-check.d.ts +3 -0
  306. package/dist/src/relay/decision-check.js +77 -0
  307. package/dist/src/relay/decision-close.d.ts +6 -0
  308. package/dist/src/relay/decision-close.js +72 -0
  309. package/dist/src/relay/decision-envelopes.d.ts +29 -0
  310. package/dist/src/relay/decision-envelopes.js +93 -0
  311. package/dist/src/relay/decision-watch.d.ts +14 -0
  312. package/dist/src/relay/decision-watch.js +68 -0
  313. package/dist/src/relay/envelope-post.d.ts +8 -0
  314. package/dist/src/relay/envelope-post.js +71 -0
  315. package/dist/src/relay/envelope.d.ts +28 -0
  316. package/dist/src/relay/envelope.js +53 -0
  317. package/dist/src/relay/hpke.d.ts +25 -0
  318. package/dist/src/relay/hpke.js +108 -0
  319. package/dist/src/relay/pairing-checks.d.ts +42 -0
  320. package/dist/src/relay/pairing-checks.js +172 -0
  321. package/dist/src/relay/pairing-follow.d.ts +8 -0
  322. package/dist/src/relay/pairing-follow.js +95 -0
  323. package/dist/src/relay/pairing-session.d.ts +29 -0
  324. package/dist/src/relay/pairing-session.js +87 -0
  325. package/dist/src/relay/phone-delivery.d.ts +51 -0
  326. package/dist/src/relay/phone-delivery.js +139 -0
  327. package/dist/src/relay/phone-devices.d.ts +36 -0
  328. package/dist/src/relay/phone-devices.js +46 -0
  329. package/dist/src/relay/phone-inbox.d.ts +18 -0
  330. package/dist/src/relay/phone-inbox.js +17 -0
  331. package/dist/src/relay/phone-operations.d.ts +18 -0
  332. package/dist/src/relay/phone-operations.js +121 -0
  333. package/dist/src/relay/phone-outbox.d.ts +21 -0
  334. package/dist/src/relay/phone-outbox.js +25 -0
  335. package/dist/src/relay/phone-socket.d.ts +6 -0
  336. package/dist/src/relay/phone-socket.js +63 -0
  337. package/dist/src/relay/relay-control.d.ts +7 -0
  338. package/dist/src/relay/relay-control.js +24 -0
  339. package/dist/src/relay/relay-files.d.ts +2 -0
  340. package/dist/src/relay/relay-files.js +23 -0
  341. package/dist/src/relay/relay-heartbeat.d.ts +17 -0
  342. package/dist/src/relay/relay-heartbeat.js +81 -0
  343. package/dist/src/relay/relay-http.d.ts +14 -0
  344. package/dist/src/relay/relay-http.js +63 -0
  345. package/dist/src/relay/relay-registration.d.ts +15 -0
  346. package/dist/src/relay/relay-registration.js +63 -0
  347. package/dist/src/relay/relay-service.d.ts +25 -0
  348. package/dist/src/relay/relay-service.js +216 -0
  349. package/dist/src/relay/relay-store.d.ts +48 -0
  350. package/dist/src/relay/relay-store.js +131 -0
  351. package/dist/src/relay/sealed-post.d.ts +25 -0
  352. package/dist/src/relay/sealed-post.js +52 -0
  353. package/dist/src/relay.d.ts +1 -0
  354. package/dist/src/relay.js +8 -0
  355. package/dist/src/report-statistics.d.ts +50 -0
  356. package/dist/src/report-statistics.js +70 -0
  357. package/dist/src/report.d.ts +171 -0
  358. package/dist/src/report.js +119 -0
  359. package/dist/src/repository-identity.d.ts +4 -0
  360. package/dist/src/repository-identity.js +4 -0
  361. package/dist/src/review-subject.d.ts +41 -0
  362. package/dist/src/review-subject.js +91 -0
  363. package/dist/src/run-wait.d.ts +23 -0
  364. package/dist/src/run-wait.js +20 -0
  365. package/dist/src/service/active-tickets.d.ts +4 -0
  366. package/dist/src/service/active-tickets.js +49 -0
  367. package/dist/src/service/command-effects.d.ts +4 -0
  368. package/dist/src/service/command-effects.js +16 -0
  369. package/dist/src/service/decision-handler.d.ts +4 -0
  370. package/dist/src/service/decision-handler.js +39 -4
  371. package/dist/src/service/gateway.js +18 -3
  372. package/dist/src/service/queue-runs.d.ts +3 -0
  373. package/dist/src/service/queue-runs.js +39 -0
  374. package/dist/src/service/registry.d.ts +22 -3
  375. package/dist/src/service/registry.js +51 -8
  376. package/dist/src/service/replies.d.ts +10 -0
  377. package/dist/src/service/replies.js +12 -0
  378. package/dist/src/service/run-handlers.js +45 -54
  379. package/dist/src/service/run-metadata.d.ts +8 -0
  380. package/dist/src/service/run-metadata.js +53 -0
  381. package/dist/src/service/run-waits.d.ts +3 -0
  382. package/dist/src/service/run-waits.js +41 -0
  383. package/dist/src/service/service.d.ts +21 -2
  384. package/dist/src/service/service.js +23 -5
  385. package/dist/src/service/submission-handler.js +70 -19
  386. package/dist/src/service/worker-port.d.ts +1 -0
  387. package/dist/src/service/worker-totals.js +10 -1
  388. package/dist/src/step-bridge.d.ts +32 -0
  389. package/dist/src/steps/decision-gate.d.ts +2 -1
  390. package/dist/src/steps/decision-gate.js +7 -0
  391. package/dist/src/steps/handoff.d.ts +2 -0
  392. package/dist/src/steps/handoff.js +13 -0
  393. package/dist/src/steps/merge-by-person.d.ts +2 -0
  394. package/dist/src/steps/merge-by-person.js +18 -0
  395. package/dist/src/steps/merge-when-green.d.ts +2 -0
  396. package/dist/src/steps/merge-when-green.js +62 -0
  397. package/dist/src/steps/review-by-agent.d.ts +5 -0
  398. package/dist/src/steps/review-by-agent.js +124 -0
  399. package/dist/src/steps/review-by-person.d.ts +4 -0
  400. package/dist/src/steps/review-by-person.js +16 -0
  401. package/dist/src/steps/review-subject.d.ts +2 -0
  402. package/dist/src/steps/review-subject.js +11 -0
  403. package/dist/src/steps/run-command.d.ts +2 -0
  404. package/dist/src/steps/run-command.js +15 -0
  405. package/dist/src/steps/sleep.d.ts +3 -0
  406. package/dist/src/steps/sleep.js +19 -0
  407. package/dist/src/steps/wait-for-decision.d.ts +2 -0
  408. package/dist/src/steps/wait-for-decision.js +71 -0
  409. package/dist/src/steps/wait-for-run.d.ts +5 -0
  410. package/dist/src/steps/wait-for-run.js +9 -0
  411. package/dist/src/storage/decision-snapshots.d.ts +1 -0
  412. package/dist/src/storage/decision-snapshots.js +7 -1
  413. package/dist/src/submission.d.ts +4 -0
  414. package/dist/src/submission.js +6 -2
  415. package/dist/src/testing.d.ts +29 -0
  416. package/dist/src/testing.js +264 -0
  417. package/dist/src/ticket-dependencies.d.ts +1 -0
  418. package/dist/src/ticket-dependencies.js +15 -0
  419. package/dist/src/ticket-status-stream.d.ts +9 -0
  420. package/dist/src/ticket-status-stream.js +94 -0
  421. package/dist/src/ticket-status.js +7 -4
  422. package/dist/src/token-prices.d.ts +14 -4
  423. package/dist/src/token-prices.js +56 -11
  424. package/dist/src/turn-profile.d.ts +1 -0
  425. package/dist/src/turn-profile.js +8 -1
  426. package/dist/src/worker-usage.d.ts +25 -0
  427. package/dist/src/worker-usage.js +58 -1
  428. package/dist/src/workflow-admission.d.ts +6 -0
  429. package/dist/src/workflow-candidate.d.ts +7 -2
  430. package/dist/src/workflow-candidate.js +48 -5
  431. package/dist/src/workflow-contracts.d.ts +2 -0
  432. package/dist/src/workflow-contracts.js +8 -3
  433. package/dist/src/workflow-declaration.d.ts +31 -2
  434. package/dist/src/workflow-declaration.js +140 -16
  435. package/dist/src/workflow-discovery.d.ts +19 -0
  436. package/dist/src/workflow-discovery.js +36 -0
  437. package/dist/src/workflow-refusals.d.ts +6 -0
  438. package/dist/src/workflow-refusals.js +42 -0
  439. package/dist/src/workflow-schedule.d.ts +22 -0
  440. package/dist/src/workflow-schedule.js +53 -0
  441. package/dist/src/workflows.d.ts +12 -1
  442. package/dist/src/workflows.js +9 -0
  443. package/dist/tools/admit-workflow.d.ts +14 -0
  444. package/dist/tools/admit-workflow.js +107 -0
  445. package/dist/tools/doctor-adoption.d.ts +1 -0
  446. package/dist/tools/doctor-adoption.js +11 -0
  447. package/dist/tools/project-configuration.d.ts +4 -0
  448. package/dist/tools/project-configuration.js +47 -0
  449. package/dist/tools/self-host-processes.d.ts +1 -0
  450. package/dist/tools/self-host-processes.js +33 -22
  451. package/dist/tools/self-host-project.d.ts +21 -0
  452. package/dist/tools/self-host-project.js +224 -0
  453. package/dist/tools/self-host-runtime.d.ts +16 -0
  454. package/dist/tools/self-host-runtime.js +60 -0
  455. package/dist/tools/self-host-workflows.d.ts +1 -0
  456. package/dist/tools/self-host-workflows.js +36 -1
  457. package/dist/tools/self-host.js +106 -135
  458. package/dist/tools/workflow-bundle-dependencies.d.ts +5 -0
  459. package/dist/tools/workflow-bundle-dependencies.js +93 -0
  460. package/dist/tools/workflow-bundles.d.ts +20 -0
  461. package/dist/tools/workflow-bundles.js +173 -0
  462. package/dist/tools/workflow-candidate-counts.d.ts +12 -0
  463. package/dist/tools/workflow-candidate-counts.js +266 -0
  464. package/dist/tools/workflow-candidate-fields.d.ts +5 -0
  465. package/dist/tools/workflow-candidate-fields.js +24 -0
  466. package/dist/tools/workflow-candidate-helper-counts.d.ts +10 -0
  467. package/dist/tools/workflow-candidate-helper-counts.js +129 -0
  468. package/dist/tools/workflow-candidate-program.d.ts +13 -0
  469. package/dist/tools/workflow-candidate-program.js +106 -0
  470. package/dist/tools/workflow-candidate-source.d.ts +6 -0
  471. package/dist/tools/workflow-candidate-source.js +169 -0
  472. package/dist/tools/workflow-candidate-syntax.d.ts +21 -0
  473. package/dist/tools/workflow-candidate-syntax.js +263 -0
  474. package/dist/tools/workflow-command.d.ts +1 -0
  475. package/dist/tools/workflow-command.js +143 -0
  476. package/dist/tools/workflow-folder-capture.d.ts +18 -0
  477. package/dist/tools/workflow-folder-capture.js +261 -0
  478. package/dist/tools/workflow-folders.d.ts +49 -0
  479. package/dist/tools/workflow-folders.js +203 -0
  480. package/dist/tools/workflow-source-effects.d.ts +10 -0
  481. package/dist/tools/workflow-source-effects.js +213 -0
  482. package/dist/tools/workflow-test-hook.d.ts +7 -0
  483. package/dist/tools/workflow-test-hook.js +16 -0
  484. package/dist/tools/workflow-test-loader.d.ts +1 -0
  485. package/dist/tools/workflow-test-loader.js +10 -0
  486. package/dist/workflows/develop-prd.js +71 -3
  487. package/dist/workflows/develop-ticket.js +6 -1
  488. package/dist/workflows/ticket-prompts.js +17 -3
  489. package/package.json +66 -6
  490. package/templates/develop/README.md +78 -0
  491. package/templates/develop/develop/accept.ts +11 -0
  492. package/templates/develop/develop/flow-tests.ts +357 -0
  493. package/templates/develop/develop/local.ts +40 -0
  494. package/templates/develop/develop/option-tests.ts +174 -0
  495. package/templates/develop/develop/partial-choice.ts +29 -0
  496. package/templates/develop/develop/partial.ts +28 -0
  497. package/templates/develop/develop/pull.ts +152 -0
  498. package/templates/develop/develop/queue-partial.ts +39 -0
  499. package/templates/develop/develop/refusal-tests.ts +174 -0
  500. package/templates/develop/develop/report.ts +156 -0
  501. package/templates/develop/develop/round.ts +40 -0
  502. package/templates/develop/develop/scenarios.ts +87 -0
  503. package/templates/develop/develop/turn.ts +67 -0
  504. package/templates/develop/develop.declaration.json +24 -0
  505. package/templates/develop/develop.test.ts +287 -0
  506. package/templates/develop/develop.ts +30 -0
  507. package/templates/develop/template.json +153 -0
  508. package/dist/src/chats/analysis-batches.d.ts +0 -24
  509. package/dist/src/chats/analysis-batches.js +0 -83
  510. package/dist/src/chats/analysis-contract.d.ts +0 -80
  511. package/dist/src/chats/analysis-contract.js +0 -16
  512. package/dist/src/chats/analysis-data.d.ts +0 -3
  513. package/dist/src/chats/analysis-data.js +0 -85
  514. package/dist/src/chats/analysis-input.d.ts +0 -12
  515. package/dist/src/chats/analysis-input.js +0 -83
  516. package/dist/src/chats/analysis-links.d.ts +0 -2
  517. package/dist/src/chats/analysis-links.js +0 -25
  518. package/dist/src/chats/analysis-prompt.d.ts +0 -10
  519. package/dist/src/chats/analysis-prompt.js +0 -16
  520. package/dist/src/chats/analysis-proposal.d.ts +0 -2
  521. package/dist/src/chats/analysis-proposal.js +0 -32
  522. package/dist/src/chats/analysis-provenance.d.ts +0 -4
  523. package/dist/src/chats/analysis-provenance.js +0 -99
  524. package/dist/src/chats/analysis-shape.d.ts +0 -3
  525. package/dist/src/chats/analysis-shape.js +0 -228
  526. package/dist/src/chats/analysis.d.ts +0 -3
  527. package/dist/src/chats/analysis.js +0 -42
  528. package/dist/src/chats/corpus-input.d.ts +0 -5
  529. package/dist/src/chats/corpus-input.js +0 -288
  530. package/dist/src/chats/destination.d.ts +0 -4
  531. package/dist/src/chats/destination.js +0 -96
  532. package/dist/src/chats/import.d.ts +0 -15
  533. package/dist/src/chats/import.js +0 -243
  534. package/dist/src/chats/input-file.d.ts +0 -3
  535. package/dist/src/chats/input-file.js +0 -32
  536. package/dist/src/chats/normalize.d.ts +0 -29
  537. package/dist/src/chats/normalize.js +0 -155
  538. package/dist/src/chats/previous-import.d.ts +0 -5
  539. package/dist/src/chats/previous-import.js +0 -34
  540. package/dist/src/chats/reader.d.ts +0 -21
  541. package/dist/src/chats/reader.js +0 -118
  542. package/dist/src/chats/redact-import.d.ts +0 -14
  543. package/dist/src/chats/redact-import.js +0 -141
  544. package/dist/src/chats/redaction.d.ts +0 -41
  545. package/dist/src/chats/redaction.js +0 -161
  546. package/dist/src/chats/revision-input.d.ts +0 -3
  547. package/dist/src/chats/revision-input.js +0 -180
  548. package/dist/src/chats/revision.d.ts +0 -48
  549. package/dist/src/chats/revision.js +0 -94
  550. package/dist/src/chats/selection.d.ts +0 -25
  551. package/dist/src/chats/selection.js +0 -109
  552. package/dist/src/chats/source.d.ts +0 -23
  553. package/dist/src/chats/source.js +0 -135
  554. package/dist/src/chats/structure.d.ts +0 -57
  555. package/dist/src/chats/structure.js +0 -216
  556. package/dist/src/daemon/analysis-launch.d.ts +0 -5
  557. package/dist/src/daemon/analysis-launch.js +0 -81
  558. package/dist/src/daemon/analysis-token.d.ts +0 -6
  559. package/dist/src/daemon/analysis-token.js +0 -88
  560. package/dist/src/daemon/analysis-worker.d.ts +0 -22
  561. package/dist/src/daemon/analysis-worker.js +0 -219
  562. package/dist/src/daemon/chats-files.d.ts +0 -15
  563. package/dist/src/daemon/chats-files.js +0 -113
  564. package/dist/src/daemon/chats-worker.d.ts +0 -12
  565. package/dist/src/daemon/chats-worker.js +0 -200
  566. package/dist/workflows/analyze-chats.d.ts +0 -10
  567. package/dist/workflows/analyze-chats.declaration.json +0 -9
  568. package/dist/workflows/analyze-chats.js +0 -73
@@ -0,0 +1,93 @@
1
+ import { createHash } from "node:crypto";
2
+ import { operationalDecision } from "../decision-visibility.js";
3
+ import { plaintextBytes, } from "./envelope.js";
4
+ const previewBytes = 2048;
5
+ const fullBytes = 65_535;
6
+ export function promptDigest(prompt) {
7
+ return `sha256:${createHash("sha256").update(prompt).digest("hex")}`;
8
+ }
9
+ function prefix(text, bytes) {
10
+ let used = 0;
11
+ let end = 0;
12
+ for (const character of text) {
13
+ const size = Buffer.byteLength(character);
14
+ if (used + size > bytes)
15
+ break;
16
+ used += size;
17
+ end += character.length;
18
+ }
19
+ return text.slice(0, end);
20
+ }
21
+ function fitted(prompt, sizing) {
22
+ let shown = prompt;
23
+ let envelope = sizing.build(shown);
24
+ while (plaintextBytes(envelope) > sizing.limit && shown !== "") {
25
+ const excess = plaintextBytes(envelope) - sizing.limit;
26
+ shown = prefix(shown, Buffer.byteLength(shown) - excess);
27
+ envelope = sizing.build(shown);
28
+ }
29
+ return envelope;
30
+ }
31
+ function envelopeOf(source, content) {
32
+ return { version: 1, ...source.target, ...content };
33
+ }
34
+ export function receiptEnvelope(source) {
35
+ return {
36
+ version: 1,
37
+ ...source.target,
38
+ kind: "receipt",
39
+ body: source.receipt,
40
+ };
41
+ }
42
+ export function closedEnvelope(source) {
43
+ return {
44
+ version: 1,
45
+ ...source.target,
46
+ kind: "closed",
47
+ body: source.ending,
48
+ };
49
+ }
50
+ export function previewEnvelope(source) {
51
+ const { decision, workflowId } = source;
52
+ const prompt = String(operationalDecision(decision, workflowId)["prompt"]);
53
+ return fitted(prompt, {
54
+ limit: previewBytes,
55
+ build: (shown) => envelopeOf(source, {
56
+ kind: "preview",
57
+ body: {
58
+ decisionId: decision["decisionId"],
59
+ label: decision["label"] ?? null,
60
+ type: decision["type"],
61
+ workflowId,
62
+ expiresAt: decision["expiresAt"] ?? null,
63
+ prompt: shown,
64
+ truncated: shown.length < prompt.length,
65
+ },
66
+ }),
67
+ });
68
+ }
69
+ export function fullEnvelope(source) {
70
+ const { decision, workflowId } = source;
71
+ const prompt = String(decision["prompt"]);
72
+ return fitted(prompt, {
73
+ limit: fullBytes,
74
+ build: (shown) => envelopeOf(source, {
75
+ kind: "full",
76
+ body: {
77
+ decisionId: decision["decisionId"],
78
+ label: decision["label"] ?? null,
79
+ type: decision["type"],
80
+ runId: decision["runId"],
81
+ workflowId,
82
+ prompt: shown,
83
+ allowed: decision["allowed"],
84
+ revision: decision["revision"],
85
+ generation: decision["generation"],
86
+ subjectDigest: decision["subjectDigest"],
87
+ promptDigest: promptDigest(prompt),
88
+ expiresAt: decision["expiresAt"] ?? null,
89
+ truncated: shown.length < prompt.length,
90
+ },
91
+ }),
92
+ });
93
+ }
@@ -0,0 +1,14 @@
1
+ import { type CheckContext } from "./decision-check.ts";
2
+ export interface WatchTiming {
3
+ readonly pollMilliseconds: number;
4
+ readonly reconnectMilliseconds: number;
5
+ }
6
+ export declare const watchTiming: WatchTiming;
7
+ export interface WatchContext extends CheckContext {
8
+ readonly runtimeDirectory: string;
9
+ }
10
+ export interface DecisionWatch {
11
+ readonly wake: () => void;
12
+ readonly close: () => Promise<void>;
13
+ }
14
+ export declare function watchDecisions(context: WatchContext, timing: WatchTiming): DecisionWatch;
@@ -0,0 +1,68 @@
1
+ import { setTimeout as delay } from "node:timers/promises";
2
+ import { checkDecisions } from "./decision-check.js";
3
+ import { relayEvents } from "./relay-control.js";
4
+ export const watchTiming = {
5
+ pollMilliseconds: 60_000,
6
+ reconnectMilliseconds: 5000,
7
+ };
8
+ const gateEvents = new Set([
9
+ "gate.opened",
10
+ "gate.decided",
11
+ "gate.expired",
12
+ ]);
13
+ function pause(milliseconds, signal) {
14
+ return delay(milliseconds, undefined, { signal }).catch(() => undefined);
15
+ }
16
+ async function followGates(context, stream) {
17
+ const page = await context.control({
18
+ operation: "events",
19
+ parameters: { after: 0, limit: 1 },
20
+ });
21
+ const latest = page.kind === "result" ? page.result["latestSequence"] : null;
22
+ if (typeof latest !== "number")
23
+ return;
24
+ stream.wake();
25
+ await relayEvents(context.runtimeDirectory, {
26
+ after: latest,
27
+ onEvent: (event) => {
28
+ if (gateEvents.has(String(event["type"])))
29
+ stream.wake();
30
+ },
31
+ stopped: stream.stopped,
32
+ });
33
+ }
34
+ export function watchDecisions(context, timing) {
35
+ const stop = new AbortController();
36
+ const stopped = new Promise((settle) => {
37
+ stop.signal.addEventListener("abort", () => {
38
+ settle();
39
+ });
40
+ });
41
+ const waking = { controller: new AbortController() };
42
+ const wake = () => {
43
+ waking.controller.abort();
44
+ };
45
+ const checking = (async () => {
46
+ while (!stop.signal.aborted) {
47
+ const current = waking.controller;
48
+ await checkDecisions(context).catch(() => undefined);
49
+ await pause(timing.pollMilliseconds, AbortSignal.any([stop.signal, current.signal]));
50
+ if (current.signal.aborted)
51
+ waking.controller = new AbortController();
52
+ }
53
+ })();
54
+ const following = (async () => {
55
+ while (!stop.signal.aborted) {
56
+ await followGates(context, { wake, stopped }).catch(() => undefined);
57
+ wake();
58
+ await pause(timing.reconnectMilliseconds, stop.signal);
59
+ }
60
+ })();
61
+ return {
62
+ wake,
63
+ close: async () => {
64
+ stop.abort();
65
+ await Promise.all([checking, following]);
66
+ },
67
+ };
68
+ }
@@ -0,0 +1,8 @@
1
+ import type { JsonObject } from "solid-objects";
2
+ import type { PostedEnvelope, PostOutcome } from "./phone-delivery.ts";
3
+ export interface PostContext {
4
+ readonly phoneDirectory: string;
5
+ readonly now: () => number;
6
+ }
7
+ export declare function envelopesBody(deviceId: string, envelopes: readonly PostedEnvelope[]): Readonly<Record<string, unknown>>;
8
+ export declare function postEnvelopes(context: PostContext, request: JsonObject): Promise<PostOutcome>;
@@ -0,0 +1,71 @@
1
+ import { record } from "../protocol.js";
2
+ import { relayRequest } from "./relay-http.js";
3
+ import { readLink } from "./relay-registration.js";
4
+ const textFields = [
5
+ "envelopeId",
6
+ "kind",
7
+ "ciphertext",
8
+ "expiresAt",
9
+ "push",
10
+ ];
11
+ function postedEnvelope(value) {
12
+ const fields = record(value);
13
+ if (fields === undefined)
14
+ return undefined;
15
+ if (!textFields.every((name) => typeof fields[name] === "string"))
16
+ return undefined;
17
+ const collapseId = fields["collapseId"] ?? null;
18
+ if (collapseId !== null && typeof collapseId !== "string")
19
+ return undefined;
20
+ return {
21
+ envelopeId: String(fields["envelopeId"]),
22
+ kind: String(fields["kind"]),
23
+ ciphertext: String(fields["ciphertext"]),
24
+ expiresAt: String(fields["expiresAt"]),
25
+ push: String(fields["push"]),
26
+ collapseId,
27
+ };
28
+ }
29
+ function postedEnvelopes(value) {
30
+ if (!Array.isArray(value))
31
+ return undefined;
32
+ const parsed = value.flatMap((item) => {
33
+ const envelope = postedEnvelope(item);
34
+ return envelope === undefined ? [] : [envelope];
35
+ });
36
+ return parsed.length === value.length ? parsed : undefined;
37
+ }
38
+ export function envelopesBody(deviceId, envelopes) {
39
+ return {
40
+ envelopes: envelopes.map((envelope) => ({
41
+ device_id: deviceId,
42
+ envelope_id: envelope.envelopeId,
43
+ kind: envelope.kind,
44
+ ciphertext: envelope.ciphertext,
45
+ expires_at: envelope.expiresAt,
46
+ push: envelope.push,
47
+ collapse_id: envelope.collapseId,
48
+ })),
49
+ };
50
+ }
51
+ export async function postEnvelopes(context, request) {
52
+ const at = context.now();
53
+ const deviceId = request["deviceId"];
54
+ const envelopes = postedEnvelopes(request["envelopes"]);
55
+ if (typeof deviceId !== "string" || envelopes === undefined)
56
+ return { at, failure: "relay-request-invalid" };
57
+ const link = await readLink(context.phoneDirectory).catch(() => undefined);
58
+ if (link === undefined)
59
+ return { at, failure: "relay-unregistered" };
60
+ const reply = await relayRequest(link.origin, {
61
+ method: "POST",
62
+ path: "/api/v1/relay/envelopes",
63
+ token: link.token,
64
+ body: envelopesBody(deviceId, envelopes),
65
+ }).catch(() => undefined);
66
+ if (reply === undefined)
67
+ return { at, failure: "relay-unreachable" };
68
+ if (reply.status !== 201)
69
+ return { at, failure: `relay-status-${String(reply.status)}` };
70
+ return { at, failure: null };
71
+ }
@@ -0,0 +1,28 @@
1
+ import type { Fields } from "../protocol.ts";
2
+ import type { DaemonKeys } from "./daemon-keys.ts";
3
+ export type EnvelopeKind = "preview" | "full" | "closed" | "receipt";
4
+ export interface Envelope {
5
+ readonly version: 1;
6
+ readonly kind: EnvelopeKind;
7
+ readonly daemonId: string;
8
+ readonly deviceId: string;
9
+ readonly sequence: number;
10
+ readonly issuedAt: string;
11
+ readonly expiresAt: string;
12
+ readonly body: Fields;
13
+ }
14
+ export declare const relayInfo: Buffer<ArrayBuffer>;
15
+ export declare function envelopeAad(binding: {
16
+ readonly daemonId: string;
17
+ readonly deviceId: string;
18
+ readonly kind: string;
19
+ }): Buffer;
20
+ export declare function collapseId(keys: DaemonKeys, decisionId: string): string;
21
+ export declare function plaintextBytes(envelope: Envelope): number;
22
+ export declare function signedPlaintext(keys: DaemonKeys, envelope: Envelope): Buffer;
23
+ export declare function padPlaintext(plaintext: Buffer): Buffer;
24
+ export declare function unpadPlaintext(padded: Buffer): Buffer;
25
+ export declare function sealEnvelope(keys: DaemonKeys, sealing: {
26
+ readonly envelope: Envelope;
27
+ readonly agreementPublicKey: string;
28
+ }): Buffer;
@@ -0,0 +1,53 @@
1
+ import { createHmac } from "node:crypto";
2
+ import { canonicalJson } from "../phone/canonical-json.js";
3
+ import { sealTo } from "./hpke.js";
4
+ import { signedBy } from "./pairing-checks.js";
5
+ export const relayInfo = Buffer.from("cloving-relay-v1");
6
+ const paddedSizes = [1024, 4096, 16_384, 65_536];
7
+ const marker = 0x80;
8
+ const signatureCharacters = 86;
9
+ export function envelopeAad(binding) {
10
+ return Buffer.from(`cloving-relay-v1|${binding.daemonId}|${binding.deviceId}|${binding.kind}`);
11
+ }
12
+ export function collapseId(keys, decisionId) {
13
+ const key = createHmac("sha256", Buffer.from(keys.signingPrivateKey, "base64url"))
14
+ .update("cloving-collapse-key")
15
+ .digest();
16
+ return createHmac("sha256", key)
17
+ .update(decisionId)
18
+ .digest()
19
+ .subarray(0, 16)
20
+ .toString("base64url");
21
+ }
22
+ export function plaintextBytes(envelope) {
23
+ return Buffer.byteLength(canonicalJson({
24
+ payload: canonicalJson(envelope),
25
+ signature: "A".repeat(signatureCharacters),
26
+ }));
27
+ }
28
+ export function signedPlaintext(keys, envelope) {
29
+ return Buffer.from(canonicalJson(signedBy(keys, canonicalJson(envelope))));
30
+ }
31
+ export function padPlaintext(plaintext) {
32
+ const size = paddedSizes.find((bytes) => plaintext.length < bytes);
33
+ if (size === undefined)
34
+ throw new Error(`envelope-too-large: A sealed envelope has at most ${String(paddedSizes.at(-1))} bytes.`);
35
+ const padded = Buffer.alloc(size);
36
+ plaintext.copy(padded);
37
+ padded[plaintext.length] = marker;
38
+ return padded;
39
+ }
40
+ export function unpadPlaintext(padded) {
41
+ const end = padded.findLastIndex((byte) => byte !== 0);
42
+ if (end === -1 || padded[end] !== marker)
43
+ throw new Error("envelope-padding-invalid: The envelope has no end mark.");
44
+ return padded.subarray(0, end);
45
+ }
46
+ export function sealEnvelope(keys, sealing) {
47
+ const { envelope } = sealing;
48
+ return sealTo(sealing.agreementPublicKey, {
49
+ info: relayInfo,
50
+ aad: envelopeAad(envelope),
51
+ plaintext: padPlaintext(signedPlaintext(keys, envelope)),
52
+ });
53
+ }
@@ -0,0 +1,25 @@
1
+ export interface HpkeSchedule {
2
+ readonly key: Buffer;
3
+ readonly baseNonce: Buffer;
4
+ }
5
+ export interface SealedMessage {
6
+ readonly info: Buffer;
7
+ readonly aad: Buffer;
8
+ }
9
+ export declare function keySchedule(sharedSecret: Buffer, info: Buffer): HpkeSchedule;
10
+ export declare function sealAt(schedule: HpkeSchedule, message: {
11
+ readonly sequence: number;
12
+ readonly aad: Buffer;
13
+ readonly plaintext: Buffer;
14
+ }): Buffer;
15
+ export declare function openAt(schedule: HpkeSchedule, message: {
16
+ readonly sequence: number;
17
+ readonly aad: Buffer;
18
+ readonly ciphertext: Buffer;
19
+ }): Buffer;
20
+ export declare function sealTo(publicKey: string, message: SealedMessage & {
21
+ readonly plaintext: Buffer;
22
+ }): Buffer;
23
+ export declare function openFrom(privateKey: string, message: SealedMessage & {
24
+ readonly sealed: Buffer;
25
+ }): Buffer;
@@ -0,0 +1,108 @@
1
+ import { createCipheriv, createDecipheriv, createHmac, createPrivateKey, decapsulate, encapsulate, } from "node:crypto";
2
+ import { publicKeyObject } from "./pairing-checks.js";
3
+ const suite = Buffer.concat([
4
+ Buffer.from("HPKE"),
5
+ Buffer.from([0x00, 0x10, 0x00, 0x01, 0x00, 0x02]),
6
+ ]);
7
+ const version = Buffer.from("HPKE-v1");
8
+ const encapsulatedBytes = 65;
9
+ const tagBytes = 16;
10
+ function labeledExtract(salt, input) {
11
+ return createHmac("sha256", salt.length === 0 ? Buffer.alloc(32) : salt)
12
+ .update(Buffer.concat([version, suite, Buffer.from(input.label), input.material]))
13
+ .digest();
14
+ }
15
+ function labeledExpand(secret, input) {
16
+ const length = Buffer.alloc(2);
17
+ length.writeUInt16BE(input.length);
18
+ return createHmac("sha256", secret)
19
+ .update(Buffer.concat([
20
+ length,
21
+ version,
22
+ suite,
23
+ Buffer.from(input.label),
24
+ input.context,
25
+ Buffer.from([1]),
26
+ ]))
27
+ .digest()
28
+ .subarray(0, input.length);
29
+ }
30
+ export function keySchedule(sharedSecret, info) {
31
+ const empty = Buffer.alloc(0);
32
+ const context = Buffer.concat([
33
+ Buffer.from([0]),
34
+ labeledExtract(empty, { label: "psk_id_hash", material: empty }),
35
+ labeledExtract(empty, { label: "info_hash", material: info }),
36
+ ]);
37
+ const secret = labeledExtract(sharedSecret, {
38
+ label: "secret",
39
+ material: empty,
40
+ });
41
+ return {
42
+ key: labeledExpand(secret, { label: "key", context, length: 32 }),
43
+ baseNonce: labeledExpand(secret, {
44
+ label: "base_nonce",
45
+ context,
46
+ length: 12,
47
+ }),
48
+ };
49
+ }
50
+ function nonce(schedule, sequence) {
51
+ const value = Buffer.alloc(12);
52
+ value.writeUIntBE(sequence, 6, 6);
53
+ for (const [index, byte] of schedule.baseNonce.entries())
54
+ value.writeUInt8(byte ^ value.readUInt8(index), index);
55
+ return value;
56
+ }
57
+ export function sealAt(schedule, message) {
58
+ const cipher = createCipheriv("aes-256-gcm", schedule.key, nonce(schedule, message.sequence));
59
+ cipher.setAAD(message.aad);
60
+ return Buffer.concat([
61
+ cipher.update(message.plaintext),
62
+ cipher.final(),
63
+ cipher.getAuthTag(),
64
+ ]);
65
+ }
66
+ export function openAt(schedule, message) {
67
+ const { ciphertext } = message;
68
+ if (ciphertext.length < tagBytes)
69
+ throw new Error("hpke-open-failed: The sealed text is too short.");
70
+ const decipher = createDecipheriv("aes-256-gcm", schedule.key, nonce(schedule, message.sequence));
71
+ decipher.setAAD(message.aad);
72
+ decipher.setAuthTag(ciphertext.subarray(ciphertext.length - tagBytes));
73
+ return Buffer.concat([
74
+ decipher.update(ciphertext.subarray(0, ciphertext.length - tagBytes)),
75
+ decipher.final(),
76
+ ]);
77
+ }
78
+ export function sealTo(publicKey, message) {
79
+ const recipient = publicKeyObject(publicKey);
80
+ if (recipient === undefined)
81
+ throw new Error("hpke-seal-failed: The public key is not a P-256 point.");
82
+ const { sharedKey, ciphertext } = encapsulate(recipient);
83
+ const schedule = keySchedule(sharedKey, message.info);
84
+ return Buffer.concat([
85
+ ciphertext,
86
+ sealAt(schedule, {
87
+ sequence: 0,
88
+ aad: message.aad,
89
+ plaintext: message.plaintext,
90
+ }),
91
+ ]);
92
+ }
93
+ export function openFrom(privateKey, message) {
94
+ const { sealed } = message;
95
+ if (sealed.length < encapsulatedBytes + tagBytes)
96
+ throw new Error("hpke-open-failed: The sealed text is too short.");
97
+ const key = createPrivateKey({
98
+ key: Buffer.from(privateKey, "base64url"),
99
+ format: "der",
100
+ type: "pkcs8",
101
+ });
102
+ const sharedSecret = decapsulate(key, sealed.subarray(0, encapsulatedBytes));
103
+ return openAt(keySchedule(sharedSecret, message.info), {
104
+ sequence: 0,
105
+ aad: message.aad,
106
+ ciphertext: sealed.subarray(encapsulatedBytes),
107
+ });
108
+ }
@@ -0,0 +1,42 @@
1
+ import { type KeyObject } from "node:crypto";
2
+ import type { DaemonKeys } from "./daemon-keys.ts";
3
+ export interface PairingRequest {
4
+ readonly deviceId: string;
5
+ readonly name: string;
6
+ readonly platform: string;
7
+ readonly agreementPublicKey: string;
8
+ readonly answerPublicKey: string;
9
+ readonly approvalPublicKey: string;
10
+ readonly code: string;
11
+ }
12
+ export type RequestCheck = {
13
+ readonly kind: "valid";
14
+ readonly request: PairingRequest;
15
+ } | {
16
+ readonly kind: "refused";
17
+ readonly code: string;
18
+ };
19
+ export interface SignedMessage {
20
+ readonly payload: string;
21
+ readonly signature: string;
22
+ }
23
+ export declare function publicKeyObject(point: string): KeyObject | undefined;
24
+ export declare function pairingCode(secret: Buffer, canonical: string): string;
25
+ export declare function checkedRequest(secret: Buffer, body: unknown): RequestCheck;
26
+ export declare function signedReceipt(keys: DaemonKeys, values: {
27
+ readonly daemonId: string;
28
+ readonly deviceId: string;
29
+ readonly pairedAt: string;
30
+ }): {
31
+ readonly receipt: SignedMessage;
32
+ readonly nonce: string;
33
+ };
34
+ export declare function signedBy(keys: DaemonKeys, payload: string): SignedMessage;
35
+ export interface ExpectedCheck {
36
+ readonly daemonId: string;
37
+ readonly deviceId: string;
38
+ readonly nonce: string;
39
+ readonly answerPublicKey: string;
40
+ readonly now: number;
41
+ }
42
+ export declare function checkValid(check: unknown, expected: ExpectedCheck): boolean;
@@ -0,0 +1,172 @@
1
+ import { createHash, createHmac, createPrivateKey, createPublicKey, randomBytes, sign, timingSafeEqual, verify, } from "node:crypto";
2
+ import { canonicalJson } from "../phone/canonical-json.js";
3
+ const requestFields = [
4
+ "agreement_public_key",
5
+ "answer_public_key",
6
+ "approval_public_key",
7
+ "device_id",
8
+ "name",
9
+ "platform",
10
+ ];
11
+ function text(value, name) {
12
+ const field = typeof value === "object" && value !== null
13
+ ? Reflect.get(value, name)
14
+ : undefined;
15
+ return typeof field === "string" ? field : undefined;
16
+ }
17
+ export function publicKeyObject(point) {
18
+ const raw = Buffer.from(point, "base64url");
19
+ if (raw.length !== 65 || raw[0] !== 4 || raw.toString("base64url") !== point)
20
+ return undefined;
21
+ try {
22
+ return createPublicKey({
23
+ key: {
24
+ kty: "EC",
25
+ crv: "P-256",
26
+ x: raw.subarray(1, 33).toString("base64url"),
27
+ y: raw.subarray(33).toString("base64url"),
28
+ },
29
+ format: "jwk",
30
+ });
31
+ }
32
+ catch {
33
+ return undefined;
34
+ }
35
+ }
36
+ function requestFieldsOf(body) {
37
+ const agreement = text(body, "agreement_public_key");
38
+ const answer = text(body, "answer_public_key");
39
+ const approval = text(body, "approval_public_key");
40
+ const deviceId = text(body, "device_id");
41
+ const name = text(body, "name");
42
+ const platform = text(body, "platform");
43
+ const mac = text(body, "mac");
44
+ if (agreement === undefined ||
45
+ answer === undefined ||
46
+ approval === undefined ||
47
+ deviceId === undefined ||
48
+ name === undefined ||
49
+ platform === undefined ||
50
+ mac === undefined)
51
+ return undefined;
52
+ return {
53
+ agreement_public_key: agreement,
54
+ answer_public_key: answer,
55
+ approval_public_key: approval,
56
+ device_id: deviceId,
57
+ name,
58
+ platform,
59
+ mac,
60
+ };
61
+ }
62
+ export function pairingCode(secret, canonical) {
63
+ const digest = createHash("sha256").update(canonical).digest();
64
+ const tag = createHmac("sha256", secret)
65
+ .update("cloving-pair-code|")
66
+ .update(digest)
67
+ .digest();
68
+ const number = BigInt(`0x${tag.toString("hex")}`) % 1000000n;
69
+ return String(number).padStart(6, "0");
70
+ }
71
+ function macValid(secret, given) {
72
+ const canonical = canonicalJson(Object.fromEntries(requestFields.map((name) => [name, given[name]])));
73
+ const expected = createHmac("sha256", secret).update(canonical).digest();
74
+ const mac = Buffer.from(given.mac, "base64url");
75
+ const valid = mac.length === expected.length && timingSafeEqual(mac, expected);
76
+ return valid ? canonical : "";
77
+ }
78
+ function wellFormed(given) {
79
+ return (/^[A-Za-z0-9_-]{1,64}$/.test(given.device_id) &&
80
+ Array.from(given.name).length <= 64 &&
81
+ ["ios", "android"].includes(given.platform));
82
+ }
83
+ export function checkedRequest(secret, body) {
84
+ const given = requestFieldsOf(body);
85
+ if (given === undefined)
86
+ return { kind: "refused", code: "pairing-request-invalid" };
87
+ const canonical = macValid(secret, given);
88
+ if (canonical === "")
89
+ return { kind: "refused", code: "pairing-mac-invalid" };
90
+ const keys = [
91
+ given.agreement_public_key,
92
+ given.answer_public_key,
93
+ given.approval_public_key,
94
+ ];
95
+ if (keys.some((key) => publicKeyObject(key) === undefined))
96
+ return { kind: "refused", code: "pairing-key-invalid" };
97
+ if (!wellFormed(given))
98
+ return { kind: "refused", code: "pairing-request-invalid" };
99
+ return {
100
+ kind: "valid",
101
+ request: {
102
+ deviceId: given.device_id,
103
+ name: given.name,
104
+ platform: given.platform,
105
+ agreementPublicKey: given.agreement_public_key,
106
+ answerPublicKey: given.answer_public_key,
107
+ approvalPublicKey: given.approval_public_key,
108
+ code: pairingCode(secret, canonical),
109
+ },
110
+ };
111
+ }
112
+ export function signedReceipt(keys, values) {
113
+ const nonce = randomBytes(16).toString("base64url");
114
+ const payload = canonicalJson({
115
+ daemonId: values.daemonId,
116
+ deviceId: values.deviceId,
117
+ kind: "pairing-receipt",
118
+ nonce,
119
+ pairedAt: values.pairedAt,
120
+ version: 1,
121
+ });
122
+ return { receipt: signedBy(keys, payload), nonce };
123
+ }
124
+ export function signedBy(keys, payload) {
125
+ const key = createPrivateKey({
126
+ key: Buffer.from(keys.signingPrivateKey, "base64url"),
127
+ format: "der",
128
+ type: "pkcs8",
129
+ });
130
+ const signature = sign("sha256", Buffer.from(payload), {
131
+ key,
132
+ dsaEncoding: "ieee-p1363",
133
+ });
134
+ return { payload, signature: signature.toString("base64url") };
135
+ }
136
+ function signatureValid(message, publicKey) {
137
+ const key = publicKeyObject(publicKey);
138
+ return (key !== undefined &&
139
+ verify("sha256", Buffer.from(message.payload), { key, dsaEncoding: "ieee-p1363" }, Buffer.from(message.signature, "base64url")));
140
+ }
141
+ function checkFields(payload) {
142
+ try {
143
+ const value = JSON.parse(payload);
144
+ if (typeof value !== "object" || value === null)
145
+ return {};
146
+ return canonicalJson(value) === payload ? { ...value } : {};
147
+ }
148
+ catch {
149
+ return {};
150
+ }
151
+ }
152
+ export function checkValid(check, expected) {
153
+ const payload = text(check, "payload");
154
+ const signature = text(check, "signature");
155
+ if (payload === undefined || signature === undefined)
156
+ return false;
157
+ if (!signatureValid({ payload, signature }, expected.answerPublicKey))
158
+ return false;
159
+ const fields = checkFields(payload);
160
+ const wanted = {
161
+ daemonId: expected.daemonId,
162
+ deviceId: expected.deviceId,
163
+ kind: "pairing-check",
164
+ nonce: expected.nonce,
165
+ signedAt: fields["signedAt"],
166
+ version: 1,
167
+ };
168
+ const signedAt = Date.parse(String(fields["signedAt"]));
169
+ return (typeof fields["signedAt"] === "string" &&
170
+ canonicalJson(wanted) === payload &&
171
+ Math.abs(signedAt - expected.now) <= 600_000);
172
+ }