@skrr-ai/cli 0.1.119 → 0.1.121

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 (336) hide show
  1. package/dist/base-command.d.ts +31 -0
  2. package/dist/base-command.js +117 -6
  3. package/dist/commands/agent/message/context.d.ts +1 -0
  4. package/dist/commands/agent/message/context.js +3 -2
  5. package/dist/commands/commands.js +11 -0
  6. package/dist/commands/commitments/action-proposal.d.ts +13 -0
  7. package/dist/commands/commitments/action-proposal.js +39 -0
  8. package/dist/commands/commitments/action-proposals/decide.js +8 -1
  9. package/dist/commands/commitments/action-proposals/execute.js +11 -1
  10. package/dist/commands/commitments/create.js +2 -2
  11. package/dist/commands/commitments/doctor.d.ts +8 -0
  12. package/dist/commands/commitments/doctor.js +22 -0
  13. package/dist/commands/commitments/execute-remediation.js +12 -1
  14. package/dist/commands/commitments/observations.js +2 -2
  15. package/dist/commands/commitments/pack/bind.d.ts +18 -1
  16. package/dist/commands/commitments/pack/bind.js +66 -8
  17. package/dist/commands/commitments/pack/plan.d.ts +4 -1
  18. package/dist/commands/commitments/pack/plan.js +30 -2
  19. package/dist/commands/commitments/update.js +8 -2
  20. package/dist/commands/completion/index.js +1 -1
  21. package/dist/commands/completion/values.js +1 -1
  22. package/dist/commands/computer-use/audio.d.ts +1 -0
  23. package/dist/commands/computer-use/audio.js +1 -0
  24. package/dist/commands/computer-use/browser.d.ts +1 -0
  25. package/dist/commands/computer-use/browser.js +1 -0
  26. package/dist/commands/computer-use/disable.d.ts +1 -0
  27. package/dist/commands/computer-use/disable.js +1 -0
  28. package/dist/commands/computer-use/enable.d.ts +1 -0
  29. package/dist/commands/computer-use/enable.js +1 -0
  30. package/dist/commands/computer-use/index.d.ts +1 -0
  31. package/dist/commands/computer-use/index.js +1 -0
  32. package/dist/commands/computer-use/install.d.ts +1 -0
  33. package/dist/commands/computer-use/install.js +1 -0
  34. package/dist/commands/computer-use/recover.d.ts +1 -0
  35. package/dist/commands/computer-use/recover.js +1 -0
  36. package/dist/commands/computer-use/status.d.ts +1 -0
  37. package/dist/commands/computer-use/status.js +1 -0
  38. package/dist/commands/computer-use/stop.d.ts +1 -0
  39. package/dist/commands/computer-use/stop.js +1 -0
  40. package/dist/commands/context.js +16 -11
  41. package/dist/commands/convos/recovery.js +1 -1
  42. package/dist/commands/daemon/diagnostics.d.ts +9 -0
  43. package/dist/commands/daemon/diagnostics.js +53 -0
  44. package/dist/commands/daemon/index.js +2 -0
  45. package/dist/commands/daemon/maintenance/attest.d.ts +15 -0
  46. package/dist/commands/daemon/maintenance/attest.js +52 -0
  47. package/dist/commands/daemon/maintenance/hold.d.ts +17 -0
  48. package/dist/commands/daemon/maintenance/hold.js +70 -0
  49. package/dist/commands/daemon/maintenance/index.d.ts +12 -0
  50. package/dist/commands/daemon/maintenance/index.js +30 -0
  51. package/dist/commands/daemon/maintenance/resume.d.ts +13 -0
  52. package/dist/commands/daemon/maintenance/resume.js +36 -0
  53. package/dist/commands/daemon/maintenance/status.d.ts +13 -0
  54. package/dist/commands/daemon/maintenance/status.js +36 -0
  55. package/dist/commands/doctor.d.ts +4 -1
  56. package/dist/commands/doctor.js +50 -6
  57. package/dist/commands/followups/plans/cancel-batch.js +6 -3
  58. package/dist/commands/followups/plans/capacity.js +7 -3
  59. package/dist/commands/followups/plans/observability.js +11 -3
  60. package/dist/commands/followups/plans/preferences/set.js +26 -8
  61. package/dist/commands/followups/plans/preferences.js +1 -1
  62. package/dist/commands/followups/plans/show.js +1 -1
  63. package/dist/commands/goals/key-results/assess.d.ts +36 -0
  64. package/dist/commands/goals/key-results/assess.js +110 -0
  65. package/dist/commands/goals/key-results/history.d.ts +30 -0
  66. package/dist/commands/goals/key-results/history.js +104 -0
  67. package/dist/commands/goals/key-results/list.d.ts +5 -4
  68. package/dist/commands/goals/key-results/list.js +12 -6
  69. package/dist/commands/goals/key-results/show.d.ts +4 -2
  70. package/dist/commands/goals/key-results/show.js +25 -4
  71. package/dist/commands/goals/key-results/update.d.ts +4 -0
  72. package/dist/commands/goals/key-results/update.js +18 -24
  73. package/dist/commands/goals/plan-now.js +12 -1
  74. package/dist/commands/goals/planner-compare.d.ts +30 -0
  75. package/dist/commands/goals/planner-compare.js +136 -0
  76. package/dist/commands/machines/dedicated/service/exec.d.ts +3 -0
  77. package/dist/commands/machines/dedicated/service/exec.js +70 -11
  78. package/dist/commands/machines/hosted/destroy.d.ts +1 -0
  79. package/dist/commands/machines/hosted/destroy.js +11 -1
  80. package/dist/commands/machines/hosted/exec.d.ts +1 -0
  81. package/dist/commands/machines/hosted/exec.js +12 -1
  82. package/dist/commands/machines/hosted/index.js +1 -0
  83. package/dist/commands/machines/hosted/operation.d.ts +14 -0
  84. package/dist/commands/machines/hosted/operation.js +51 -0
  85. package/dist/commands/machines/hosted/pull.js +1 -1
  86. package/dist/commands/machines/hosted/start.js +6 -2
  87. package/dist/commands/search/agents.js +5 -1
  88. package/dist/commands/search/all.js +74 -22
  89. package/dist/commands/search/skills.d.ts +1 -0
  90. package/dist/commands/search/skills.js +12 -1
  91. package/dist/commands/search/spaces.d.ts +8 -1
  92. package/dist/commands/search/spaces.js +50 -12
  93. package/dist/commands/search/tasks.d.ts +13 -2
  94. package/dist/commands/search/tasks.js +78 -14
  95. package/dist/commands/search/teams.js +5 -1
  96. package/dist/commands/search/workspace.d.ts +19 -0
  97. package/dist/commands/search/workspace.js +126 -0
  98. package/dist/commands/skills/browse.d.ts +1 -0
  99. package/dist/commands/skills/browse.js +8 -1
  100. package/dist/commands/skills/search.d.ts +1 -0
  101. package/dist/commands/skills/search.js +12 -1
  102. package/dist/commands/spaces/auto-executor/compare.d.ts +18 -0
  103. package/dist/commands/spaces/auto-executor/compare.js +139 -0
  104. package/dist/commands/spaces/auto-executor/runs.d.ts +3 -0
  105. package/dist/commands/spaces/auto-executor/runs.js +13 -1
  106. package/dist/commands/spaces/charter/create-from-description.d.ts +21 -0
  107. package/dist/commands/spaces/charter/create-from-description.js +77 -0
  108. package/dist/commands/spaces/charter/show.d.ts +22 -0
  109. package/dist/commands/spaces/charter/show.js +61 -0
  110. package/dist/commands/spaces/heartbeats/list.d.ts +1 -16
  111. package/dist/commands/spaces/heartbeats/list.js +13 -0
  112. package/dist/commands/spaces/install-template.js +3 -0
  113. package/dist/commands/spaces/list.d.ts +1 -0
  114. package/dist/commands/spaces/list.js +12 -3
  115. package/dist/commands/spaces/loops/migrate.d.ts +32 -0
  116. package/dist/commands/spaces/loops/migrate.js +103 -0
  117. package/dist/commands/spaces/loops/release.d.ts +14 -0
  118. package/dist/commands/spaces/loops/release.js +59 -0
  119. package/dist/commands/spaces/notebook.d.ts +25 -0
  120. package/dist/commands/spaces/notebook.js +75 -0
  121. package/dist/commands/spaces/proposals/discard.d.ts +17 -0
  122. package/dist/commands/spaces/proposals/discard.js +17 -0
  123. package/dist/commands/spaces/proposals/keep.d.ts +17 -0
  124. package/dist/commands/spaces/proposals/keep.js +17 -0
  125. package/dist/commands/spaces/proposals/list.d.ts +24 -0
  126. package/dist/commands/spaces/proposals/list.js +89 -0
  127. package/dist/commands/spaces/search.d.ts +1 -0
  128. package/dist/commands/spaces/search.js +9 -0
  129. package/dist/commands/spaces/skills/propose.d.ts +29 -0
  130. package/dist/commands/spaces/skills/propose.js +82 -0
  131. package/dist/commands/spaces/templates/fork.js +3 -0
  132. package/dist/commands/spaces/triggers/show.js +6 -0
  133. package/dist/commands/spaces/workflows/retry.d.ts +1 -0
  134. package/dist/commands/spaces/workflows/retry.js +8 -1
  135. package/dist/commands/spaces/workflows/triggers/create.js +3 -2
  136. package/dist/commands/spaces/workflows/triggers/fire.js +4 -0
  137. package/dist/commands/spaces/workflows/triggers/update.js +2 -1
  138. package/dist/commands/spaces/write-policy/set.d.ts +22 -0
  139. package/dist/commands/spaces/write-policy/set.js +82 -0
  140. package/dist/commands/spaces/write-policy/show.d.ts +19 -0
  141. package/dist/commands/spaces/write-policy/show.js +49 -0
  142. package/dist/commands/store/artifacts/receipt.d.ts +14 -0
  143. package/dist/commands/store/artifacts/receipt.js +34 -0
  144. package/dist/commands/store/artifacts/release.d.ts +2 -0
  145. package/dist/commands/store/artifacts/release.js +11 -0
  146. package/dist/commands/store/governance/publish-release.d.ts +2 -0
  147. package/dist/commands/store/governance/publish-release.js +30 -1
  148. package/dist/commands/store/governance/release-receipt.d.ts +12 -0
  149. package/dist/commands/store/governance/release-receipt.js +32 -0
  150. package/dist/commands/tasks/approvals/list.d.ts +11 -5
  151. package/dist/commands/tasks/approvals/list.js +13 -6
  152. package/dist/commands/tasks/attachments/facts.d.ts +12 -0
  153. package/dist/commands/tasks/attachments/facts.js +29 -0
  154. package/dist/commands/tasks/attachments/upload.d.ts +1 -0
  155. package/dist/commands/tasks/attachments/upload.js +16 -0
  156. package/dist/commands/tasks/bulk-update.js +35 -1
  157. package/dist/commands/tasks/context.d.ts +15 -0
  158. package/dist/commands/tasks/context.js +69 -0
  159. package/dist/commands/tasks/copy.d.ts +18 -0
  160. package/dist/commands/tasks/copy.js +85 -0
  161. package/dist/commands/tasks/create-tree.js +97 -23
  162. package/dist/commands/tasks/create.js +9 -1
  163. package/dist/commands/tasks/deliverable/add.js +20 -10
  164. package/dist/commands/tasks/move.js +8 -1
  165. package/dist/commands/tasks/operations/show.d.ts +16 -0
  166. package/dist/commands/tasks/operations/show.js +98 -0
  167. package/dist/commands/tasks/operations/wait.d.ts +22 -0
  168. package/dist/commands/tasks/operations/wait.js +114 -0
  169. package/dist/commands/tasks/preflight.d.ts +1 -0
  170. package/dist/commands/tasks/preflight.js +11 -1
  171. package/dist/commands/tasks/rearm-recurrence.js +4 -0
  172. package/dist/commands/tasks/result/show.js +22 -0
  173. package/dist/commands/tasks/result/submit.d.ts +1 -0
  174. package/dist/commands/tasks/result/submit.js +3 -0
  175. package/dist/commands/tasks/retry-receipt.d.ts +15 -0
  176. package/dist/commands/tasks/retry-receipt.js +89 -0
  177. package/dist/commands/tasks/retry-review.d.ts +16 -0
  178. package/dist/commands/tasks/retry-review.js +66 -0
  179. package/dist/commands/tasks/retry.d.ts +4 -8
  180. package/dist/commands/tasks/retry.js +34 -68
  181. package/dist/commands/tasks/runs.d.ts +21 -0
  182. package/dist/commands/tasks/runs.js +95 -0
  183. package/dist/commands/tasks/search.d.ts +1 -0
  184. package/dist/commands/tasks/search.js +9 -0
  185. package/dist/commands/tasks/show.d.ts +2 -0
  186. package/dist/commands/tasks/show.js +15 -1
  187. package/dist/commands/tasks/start-receipt.d.ts +15 -0
  188. package/dist/commands/tasks/start-receipt.js +95 -0
  189. package/dist/commands/tasks/start.d.ts +4 -0
  190. package/dist/commands/tasks/start.js +49 -3
  191. package/dist/commands/tasks/update.js +3 -0
  192. package/dist/commands/tasks/upsert.js +17 -3
  193. package/dist/commands/tasks/watch.d.ts +3 -0
  194. package/dist/commands/tasks/watch.js +85 -20
  195. package/dist/commands/wiki/history.d.ts +33 -0
  196. package/dist/commands/wiki/history.js +134 -0
  197. package/dist/commands/wiki/ls.d.ts +2 -0
  198. package/dist/commands/wiki/ls.js +8 -0
  199. package/dist/commands/wiki/update.d.ts +3 -1
  200. package/dist/commands/wiki/update.js +24 -2
  201. package/dist/commands/wiki/write.d.ts +22 -0
  202. package/dist/commands/wiki/write.js +144 -6
  203. package/dist/generated/agent-guides.json +81 -81
  204. package/dist/generated/managed-instruction-guides.json +1 -1
  205. package/dist/lib/agent-guides.d.ts +9 -0
  206. package/dist/lib/agent-guides.js +23 -1
  207. package/dist/lib/agentic-stream.d.ts +5 -0
  208. package/dist/lib/agentic-stream.js +122 -3
  209. package/dist/lib/api-error-reason.d.ts +2 -0
  210. package/dist/lib/api-error-reason.js +14 -0
  211. package/dist/lib/api-fetch.js +3 -0
  212. package/dist/lib/auth-diagnostics.d.ts +39 -0
  213. package/dist/lib/auth-diagnostics.js +71 -0
  214. package/dist/lib/cli-client-identity.d.ts +6 -0
  215. package/dist/lib/cli-client-identity.js +16 -0
  216. package/dist/lib/cli-installation-diagnostics.d.ts +52 -0
  217. package/dist/lib/cli-installation-diagnostics.js +254 -0
  218. package/dist/lib/command-hygiene.d.ts +45 -0
  219. package/dist/lib/command-hygiene.js +413 -0
  220. package/dist/lib/commitments.d.ts +64 -3
  221. package/dist/lib/commitments.js +34 -6
  222. package/dist/lib/completion-index.d.ts +16 -4
  223. package/dist/lib/completion-index.js +50 -14
  224. package/dist/lib/config.js +7 -1
  225. package/dist/lib/daemon-maintenance-command.d.ts +22 -0
  226. package/dist/lib/daemon-maintenance-command.js +39 -0
  227. package/dist/lib/daemon-maintenance.d.ts +275 -0
  228. package/dist/lib/daemon-maintenance.js +1035 -0
  229. package/dist/lib/dedicated-service.d.ts +3 -2
  230. package/dist/lib/dedicated-service.js +11 -3
  231. package/dist/lib/governed-store.d.ts +8 -0
  232. package/dist/lib/governed-store.js +5 -0
  233. package/dist/lib/hosted-machines.d.ts +33 -2
  234. package/dist/lib/hosted-machines.js +19 -1
  235. package/dist/lib/json-output.d.ts +1 -1
  236. package/dist/lib/machines.d.ts +9 -1
  237. package/dist/lib/machines.js +15 -1
  238. package/dist/lib/node-adapter.js +13 -0
  239. package/dist/lib/record-output.d.ts +22 -0
  240. package/dist/lib/record-output.js +78 -0
  241. package/dist/lib/roles.d.ts +3 -3
  242. package/dist/lib/scoped-memory-command.d.ts +18 -0
  243. package/dist/lib/scoped-memory-command.js +53 -2
  244. package/dist/lib/search-display.d.ts +7 -0
  245. package/dist/lib/search-display.js +31 -0
  246. package/dist/lib/skill-store-pagination.d.ts +8 -0
  247. package/dist/lib/skill-store-pagination.js +35 -0
  248. package/dist/lib/space-charter.d.ts +49 -0
  249. package/dist/lib/space-charter.js +78 -0
  250. package/dist/lib/space-loop-migration.d.ts +18 -0
  251. package/dist/lib/space-loop-migration.js +128 -0
  252. package/dist/lib/space-notebook.d.ts +22 -0
  253. package/dist/lib/space-notebook.js +140 -0
  254. package/dist/lib/space-proposal-decision-command.d.ts +36 -0
  255. package/dist/lib/space-proposal-decision-command.js +108 -0
  256. package/dist/lib/space-proposals.d.ts +44 -0
  257. package/dist/lib/space-proposals.js +186 -0
  258. package/dist/lib/space-templates.d.ts +8 -0
  259. package/dist/lib/space-templates.js +17 -0
  260. package/dist/lib/space-write-policy.d.ts +24 -0
  261. package/dist/lib/space-write-policy.js +103 -0
  262. package/dist/lib/task-agent-context-capabilities.d.ts +10 -0
  263. package/dist/lib/task-agent-context-capabilities.js +22 -0
  264. package/dist/lib/task-agent-context-compatibility.d.ts +9 -0
  265. package/dist/lib/task-agent-context-compatibility.js +30 -0
  266. package/dist/lib/task-asset-upload.d.ts +22 -0
  267. package/dist/lib/task-asset-upload.js +257 -29
  268. package/dist/lib/task-batch.d.ts +15 -1
  269. package/dist/lib/task-batch.js +35 -2
  270. package/dist/lib/task-operation-watch.d.ts +66 -0
  271. package/dist/lib/task-operation-watch.js +159 -0
  272. package/dist/lib/task-resolver.d.ts +1 -1
  273. package/dist/lib/task-resolver.js +4 -3
  274. package/dist/lib/task-work.d.ts +1 -1
  275. package/dist/lib/task-work.js +35 -1
  276. package/dist/lib/task-write-attribution.d.ts +14 -0
  277. package/dist/lib/task-write-attribution.js +35 -2
  278. package/dist/lib/triggers.d.ts +11 -0
  279. package/dist/lib/triggers.js +7 -0
  280. package/dist/lib/wiki-update.d.ts +1 -0
  281. package/dist/lib/wiki-update.js +10 -1
  282. package/dist/lib/wiki.js +4 -1
  283. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/computerWire.d.ts +9 -1
  284. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/computerWire.js +31 -15
  285. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.d.ts +10 -0
  286. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.js +147 -0
  287. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonConsentProof.d.ts +20 -0
  288. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonConsentProof.js +72 -0
  289. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.js +1 -0
  290. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonWire.js +13 -7
  291. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/desktopDaemonProof.d.ts +21 -0
  292. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/desktopDaemonProof.js +96 -0
  293. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.d.ts +2 -3
  294. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +102 -20
  295. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/guidedUpdate.d.ts +370 -0
  296. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/guidedUpdate.js +315 -0
  297. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/independentMachineReservation.d.ts +27 -0
  298. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/independentMachineReservation.js +161 -0
  299. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/independentMachineScopes.d.ts +26 -0
  300. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/independentMachineScopes.js +41 -0
  301. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +2 -1
  302. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +15 -0
  303. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/macos.js +20 -4
  304. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/maintenanceReportWire.d.ts +178 -0
  305. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/maintenanceReportWire.js +262 -0
  306. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/toolApprovalSignature.d.ts +51 -0
  307. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/toolApprovalSignature.js +106 -0
  308. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/computerWire.d.ts +9 -1
  309. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/computerWire.js +32 -5
  310. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.d.ts +10 -0
  311. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.js +172 -1
  312. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonConsentProof.d.ts +20 -0
  313. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonConsentProof.js +43 -0
  314. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.js +1 -0
  315. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonWire.js +33 -14
  316. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/desktopDaemonProof.d.ts +21 -0
  317. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/desktopDaemonProof.js +62 -0
  318. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.d.ts +2 -3
  319. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +133 -25
  320. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/guidedUpdate.d.ts +370 -0
  321. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/guidedUpdate.js +404 -0
  322. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/independentMachineReservation.d.ts +27 -0
  323. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/independentMachineReservation.js +132 -0
  324. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/independentMachineScopes.d.ts +26 -0
  325. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/independentMachineScopes.js +15 -0
  326. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +2 -1
  327. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +2 -1
  328. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/macos.js +28 -15
  329. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/maintenanceReportWire.d.ts +178 -0
  330. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/maintenanceReportWire.js +342 -0
  331. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/toolApprovalSignature.d.ts +51 -0
  332. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/toolApprovalSignature.js +138 -0
  333. package/dist/node_modules/@skrr-ai/auth-core/package.json +51 -1
  334. package/dist/node_modules/@skrr-ai/data-provider/index.js +27181 -20293
  335. package/oclif.manifest.json +5745 -1813
  336. package/package.json +20 -2
@@ -0,0 +1,404 @@
1
+ /**
2
+ * The pure core of a GUIDED update (OSK-19107, Space OSK-S-524, tasks M51 to M53):
3
+ * hold the machine, wait for it to be quiet, update, verify, and give the machine
4
+ * back. The Desktop app and a standalone `skrr` install both run it, so what they
5
+ * decide on, and the words they use for what is in the way, cannot drift apart.
6
+ *
7
+ * This file is DECISIONS ONLY. It performs no I/O, reads no clock and holds no
8
+ * state, so every rule below is a table test. The orchestration (a persisted flow
9
+ * record, polling, the installer, the supervisor) lives with each caller, because
10
+ * that is the part that differs between a Desktop that quits mid-flow and a CLI that
11
+ * runs in the foreground.
12
+ *
13
+ * ## Why these rules (each names the failure it prevents)
14
+ *
15
+ * - **`prepare` is a probe that briefly refuses admitted work.** While it runs the
16
+ * daemon refuses admitted tool calls with an infra-class error, and a tool-proxy Run
17
+ * that sees one drops its tools to the Hosted Machine for good. So a guided flow
18
+ * calls it ONLY when the server's reading says there is nothing admitted to refuse
19
+ * ({@link prepareBasis}), never to find out whether there is.
20
+ * - **`unknown` is an answer.** A source nobody could read is never zero and never
21
+ * ready. {@link noServerVeto} is deliberately strict: every server source must have
22
+ * been read, and none may list active or unproven work.
23
+ * - **A server source can only veto.** Only the daemon's own positively announced
24
+ * report grants `ready`; the owner's attestation and the daemon's own `prepare`
25
+ * (labelled `no_veto`, D38) are the two other bases, and each is labelled as what
26
+ * it is wherever it is shown.
27
+ * - **Blockers are shown in the words of whoever knows them.** The daemon's blocker
28
+ * `reason` and the server's holder `label` are the vocabulary; nothing here
29
+ * re-spells them. They are bounded and never a raw path, because a diagnostic can
30
+ * carry one.
31
+ * - **A new PID, an exit code or a cloud connection is not a verified update.**
32
+ * {@link verifyReplacement} requires the daemon's own `verified`, from a process
33
+ * other than the one that was replaced, for the build that was asked for, and the
34
+ * same machine identity afterwards. A different identity is the reinstall case: the
35
+ * hold is left in place and shown, never resumed.
36
+ *
37
+ * The shapes are STRUCTURAL SUBSETS of `DaemonMaintenanceView` in
38
+ * `@skrr-ai/data-provider` (`daemonMaintenance.ts`), which is the one vocabulary.
39
+ * This package cannot import it (it sits below it), so `guidedUpdate.test.ts` reads
40
+ * that file and fails when a list mirrored here drifts from it.
41
+ */
42
+ /** The server sources that can only veto. `daemon_report` is the only one that grants. */
43
+ export const GUIDED_SERVER_SOURCES = [
44
+ 'claims',
45
+ 'receipts',
46
+ 'sessions',
47
+ 'runs',
48
+ 'leases',
49
+ 'custody',
50
+ ];
51
+ /** The refusals the owner API names, mirrored from `DAEMON_MAINTENANCE_REFUSALS`. */
52
+ export const GUIDED_REFUSALS = [
53
+ 'managed_machine',
54
+ 'no_stable_identity',
55
+ 'not_found',
56
+ 'ambiguous',
57
+ 'generation_conflict',
58
+ 'not_held',
59
+ 'already_held',
60
+ 'attestation_not_applicable',
61
+ 'not_owner',
62
+ 'held_by_other',
63
+ ];
64
+ export function isGuidedRefusal(value) {
65
+ return typeof value === 'string' && GUIDED_REFUSALS.includes(value);
66
+ }
67
+ /** The daemon handoff phases, mirrored from `daemon/src/update/handoff.ts`. */
68
+ export const GUIDED_HANDOFF_PHASES = [
69
+ 'ready',
70
+ 'deferred',
71
+ 'reconnecting',
72
+ 'verified',
73
+ 'failed',
74
+ 'rolled-back',
75
+ ];
76
+ /**
77
+ * Whether the server holds a reading that nothing vetoes, for a daemon that cannot
78
+ * report (D38). Strict on purpose: a source that is absent from the list, unreadable,
79
+ * truncated, or that names active or unproven work, is a veto. `daemon_report` must
80
+ * be `not_announced`: any other answer means the reading is not the
81
+ * `no_inventory_capability` case this is for.
82
+ */
83
+ export function noServerVeto(sources) {
84
+ // `Array.isArray` does not narrow a readonly array, so the list is named explicitly.
85
+ if (!Array.isArray(sources))
86
+ return false;
87
+ const list = sources;
88
+ for (const name of GUIDED_SERVER_SOURCES) {
89
+ const source = list.find((s) => s && s.source === name);
90
+ if (!source || source.read !== 'ok' || source.truncated === true)
91
+ return false;
92
+ const holders = Array.isArray(source.holders)
93
+ ? source.holders
94
+ : null;
95
+ if (!holders)
96
+ return false;
97
+ if (holders.some((h) => h && (h.class === 'active' || h.class === 'unproven')))
98
+ return false;
99
+ }
100
+ const report = list.find((s) => s && s.source === 'daemon_report');
101
+ return report !== undefined && report.read === 'not_announced';
102
+ }
103
+ /**
104
+ * Read the owner API's view for a flow that took `generation`.
105
+ *
106
+ * Order matters and is the rule set (each is a test):
107
+ * 1. No live hold: `not_held`. A different live generation: `generation_changed`.
108
+ * 2. The fence is not confirmed (`pausing`): nothing below it means anything.
109
+ * 3. The reading is `ready` (the daemon's own report): `ready` by evidence.
110
+ * 4. `draining`: work is running. Evidence, never overridden.
111
+ * 5. `unknown`: ready by attestation if the Journey says the owner vouched for this
112
+ * cause; `no_report` if the daemon cannot report and no source vetoes; else
113
+ * `unknown`. No reading at all is `unknown (source_unreadable)`, never ready.
114
+ */
115
+ export function evaluateReadiness(view, expectedGeneration) {
116
+ const hold = view?.hold;
117
+ if (!view || !hold || hold.lifecycle !== 'held')
118
+ return { kind: 'not_held' };
119
+ if (expectedGeneration !== null && hold.generation !== expectedGeneration) {
120
+ return { kind: 'generation_changed', currentGeneration: hold.generation };
121
+ }
122
+ if (view.journey?.phase === 'pausing')
123
+ return { kind: 'pausing' };
124
+ const reading = view.reading;
125
+ if (!reading)
126
+ return { kind: 'unknown', cause: 'source_unreadable' };
127
+ if (reading.state === 'ready')
128
+ return { kind: 'ready', basis: 'evidence' };
129
+ if (reading.state === 'draining')
130
+ return { kind: 'draining' };
131
+ if (reading.state !== 'unknown')
132
+ return { kind: 'unknown', cause: 'source_unreadable' };
133
+ if (view.journey?.phase === 'ready' && view.journey.modifiers?.includes('attested')) {
134
+ return { kind: 'ready', basis: 'attested' };
135
+ }
136
+ if (reading.cause === 'no_inventory_capability' && noServerVeto(reading.sources)) {
137
+ return { kind: 'no_report' };
138
+ }
139
+ return { kind: 'unknown', cause: reading.cause ?? null };
140
+ }
141
+ /**
142
+ * Whether a guided flow may call the daemon's `prepare`, and on what basis. `null`
143
+ * means NO: `prepare` is never a way to find out. A legacy daemon never gets one:
144
+ * it has no endpoint, and its restart is the owner's attested decision, not a probe.
145
+ */
146
+ export function prepareBasis(readiness, opts) {
147
+ if (opts.legacy)
148
+ return null;
149
+ if (readiness.kind === 'ready')
150
+ return readiness.basis;
151
+ if (readiness.kind === 'no_report')
152
+ return 'no_veto';
153
+ return null;
154
+ }
155
+ /**
156
+ * Whether a legacy daemon (no handoff endpoint) may be restarted by the supervisor.
157
+ * Only on the owner's attestation: the daemon cannot report what it is doing and a
158
+ * SIGTERM ends its wrappers, so nothing but the owner's word can vouch for it.
159
+ */
160
+ export function legacyRestartAllowed(readiness) {
161
+ return readiness.kind === 'ready' && readiness.basis === 'attested';
162
+ }
163
+ export const MAX_OWNER_BLOCKERS = 20;
164
+ const BLOCKER_TEXT_MAX = 200;
165
+ const ID_PATTERN = /^[A-Za-z0-9_-]{1,120}$/;
166
+ // A diagnostic can carry a local path or a URL. Showing one would leak the layout of
167
+ // someone's machine into a notice, so a reason that looks like one is dropped whole.
168
+ const PATHLIKE = /(?:^|[\s("'=])(?:~|\.{1,2})?[\\/](?:[\w.-]+[\\/])+|[A-Za-z]:[\\/]|\\\\|file:\/\/|\/(?:Users|home|var|tmp|private|opt|etc|usr)\//i;
169
+ /** A sentence safe to put in front of the owner, or `undefined` when it is not. */
170
+ export function sanitizeBlockerText(value, max = BLOCKER_TEXT_MAX) {
171
+ if (typeof value !== 'string')
172
+ return undefined;
173
+ const cleaned = Array.from(value, (ch) => {
174
+ const code = ch.charCodeAt(0);
175
+ return code < 0x20 || code === 0x7f ? ' ' : ch;
176
+ })
177
+ .join('')
178
+ .replace(/\s+/g, ' ')
179
+ .trim();
180
+ if (!cleaned || PATHLIKE.test(cleaned))
181
+ return undefined;
182
+ return cleaned.length > max ? `${cleaned.slice(0, max - 1)}…` : cleaned;
183
+ }
184
+ function boundedId(value) {
185
+ return typeof value === 'string' && ID_PATTERN.test(value) ? value : undefined;
186
+ }
187
+ /**
188
+ * Everything in the way, from the three places that know: the server's holders
189
+ * (active and unproven work: `pending_delivery` and `forensic` never block), the
190
+ * daemon's own refusal from `prepare`, and the work the Desktop app itself hosts and
191
+ * ends when it quits (`LocalSessionService` processes and agent browser windows,
192
+ * which the daemon's inventory cannot see).
193
+ *
194
+ * A daemon blocker whose id equals a server holder's ref is the same work seen twice:
195
+ * it is listed once, with the daemon's reason and the server's label. Whether the two
196
+ * ids are in fact comparable was never established (review §6.2 item 2), so a match
197
+ * only MERGES the wording and never hides a blocker that has no match.
198
+ */
199
+ export function describeBlockers(input) {
200
+ const out = [];
201
+ const serverByRef = new Map();
202
+ const serverHolders = [];
203
+ for (const source of input.view?.reading?.sources ?? []) {
204
+ for (const holder of source?.holders ?? []) {
205
+ if (!holder || (holder.class !== 'active' && holder.class !== 'unproven'))
206
+ continue;
207
+ serverHolders.push(holder);
208
+ if (typeof holder.ref === 'string')
209
+ serverByRef.set(holder.ref, holder);
210
+ }
211
+ }
212
+ const merged = new Set();
213
+ for (const blocker of input.daemon ?? []) {
214
+ if (!blocker || typeof blocker.kind !== 'string')
215
+ continue;
216
+ const id = boundedId(blocker.id);
217
+ const holder = blocker.id ? serverByRef.get(blocker.id) : undefined;
218
+ if (holder)
219
+ merged.add(holder);
220
+ const label = holder ? sanitizeBlockerText(holder.label, 120) : undefined;
221
+ out.push({
222
+ source: 'daemon',
223
+ kind: blocker.kind.slice(0, 64),
224
+ text: sanitizeBlockerText(blocker.reason),
225
+ ...(label ? { label } : {}),
226
+ ...(id ? { id } : {}),
227
+ ...(holder?.since !== undefined ? { since: holder.since } : {}),
228
+ });
229
+ }
230
+ for (const holder of serverHolders) {
231
+ if (merged.has(holder))
232
+ continue;
233
+ const id = boundedId(holder.ref);
234
+ out.push({
235
+ source: 'server',
236
+ kind: String(holder.kind || 'work').slice(0, 64),
237
+ text: sanitizeBlockerText(holder.label, 120),
238
+ ...(id ? { id } : {}),
239
+ ...(holder.since !== undefined ? { since: holder.since } : {}),
240
+ });
241
+ }
242
+ const desktop = input.desktop;
243
+ if (desktop && desktop.localSessions > 0) {
244
+ out.push({ source: 'desktop', kind: 'desktop_local_sessions', count: desktop.localSessions });
245
+ }
246
+ if (desktop && desktop.agentBrowserWindows > 0) {
247
+ out.push({
248
+ source: 'desktop',
249
+ kind: 'desktop_agent_browser',
250
+ count: desktop.agentBrowserWindows,
251
+ });
252
+ }
253
+ return out.slice(0, MAX_OWNER_BLOCKERS);
254
+ }
255
+ /**
256
+ * A string that changes whenever the set of things in the way changes. The "update
257
+ * now anyway" confirmation carries the one it was shown, and the action is refused
258
+ * when it no longer matches: a confirmation that names what WAS running is not a
259
+ * confirmation of what is.
260
+ */
261
+ export function blockersFingerprint(blockers) {
262
+ return blockers
263
+ .map((b) => [b.source, b.kind, b.id ?? '', b.count ?? ''].join('|'))
264
+ .sort()
265
+ .join(';');
266
+ }
267
+ /**
268
+ * The labels the owner is shown, as the server records them in the outcome
269
+ * (`acknowledged`): each bounded to the server's 160-character limit.
270
+ */
271
+ export function acknowledgedLabels(blockers, max = 20) {
272
+ const labels = [];
273
+ for (const b of blockers) {
274
+ const detail = b.label ?? b.text;
275
+ const text = b.count ? `${b.kind} (${b.count})` : detail ? `${b.kind}: ${detail}` : b.kind;
276
+ labels.push(text.slice(0, 160));
277
+ if (labels.length >= max)
278
+ break;
279
+ }
280
+ return labels;
281
+ }
282
+ function isHandoffStatus(value) {
283
+ if (!value || typeof value !== 'object')
284
+ return false;
285
+ const v = value;
286
+ return (v.protocolVersion === 1 &&
287
+ typeof v.handoffId === 'string' &&
288
+ typeof v.phase === 'string' &&
289
+ GUIDED_HANDOFF_PHASES.includes(v.phase) &&
290
+ typeof v.sourceProcessId === 'string' &&
291
+ !!v.target &&
292
+ typeof v.target.sha256 === 'string' &&
293
+ Array.isArray(v.blockers) &&
294
+ Array.isArray(v.expectedSessions));
295
+ }
296
+ export function classifyHandoffReply(input) {
297
+ const { statusCode, body } = input;
298
+ if (statusCode === null)
299
+ return { kind: 'unreachable' };
300
+ if (statusCode === 200) {
301
+ return isHandoffStatus(body) ? { kind: 'ok', status: body } : { kind: 'unreachable' };
302
+ }
303
+ if (statusCode === 404)
304
+ return { kind: 'absent' };
305
+ if (statusCode === 401 || statusCode === 403)
306
+ return { kind: 'unauthorized' };
307
+ if (statusCode === 400 || statusCode === 409 || statusCode === 413 || statusCode === 405) {
308
+ const code = body && typeof body === 'object' && typeof body.error === 'string'
309
+ ? String(body.error).slice(0, 64)
310
+ : undefined;
311
+ return { kind: 'refused', ...(code ? { code } : {}) };
312
+ }
313
+ return { kind: 'unreachable' };
314
+ }
315
+ /**
316
+ * Decide whether a committed replacement is complete. All of (1) and (2) must hold
317
+ * before anything else is asked of the server (3, {@link serverSawNewProcess}), and
318
+ * nothing here resumes: the verdict only says what was seen.
319
+ *
320
+ * An identity that is merely UNREADABLE (the bootstrap is mid-rotation, the machine
321
+ * payload could not be fetched) is `not_yet`, never a reinstall and never verified.
322
+ */
323
+ export function verifyReplacement(evidence) {
324
+ const { handoff, expect, identity } = evidence;
325
+ if (!handoff)
326
+ return { kind: 'not_yet', missing: 'daemon_status' };
327
+ if (handoff.phase === 'failed' || handoff.phase === 'rolled-back') {
328
+ return {
329
+ kind: 'failed',
330
+ phase: handoff.phase,
331
+ reason: String(handoff.reason || handoff.phase).slice(0, 64),
332
+ // A failed handoff observed by the SAME process is a replacement that never
333
+ // happened: the old daemon is still the one running, and a retry is meaningful.
334
+ sameProcess: handoff.observer?.processId === expect.sourceProcessId,
335
+ };
336
+ }
337
+ if (handoff.phase !== 'verified')
338
+ return { kind: 'not_yet', missing: 'verified' };
339
+ const by = handoff.verifiedBy;
340
+ if (!by ||
341
+ by.processId === expect.sourceProcessId ||
342
+ handoff.target.sha256 !== expect.targetSha256 ||
343
+ (expect.version !== null && by.version !== expect.version)) {
344
+ return { kind: 'not_yet', missing: 'verified' };
345
+ }
346
+ if (identity.bootstrapDaemonId === null || identity.runtimeKeyNow === null) {
347
+ return { kind: 'not_yet', missing: 'identity_unreadable' };
348
+ }
349
+ if (identity.bootstrapDaemonId !== identity.recordedDaemonId) {
350
+ return { kind: 'reinstall', what: 'daemon_id' };
351
+ }
352
+ if (identity.runtimeKeyNow !== identity.recordedRuntimeKey) {
353
+ return { kind: 'reinstall', what: 'runtime_key' };
354
+ }
355
+ return { kind: 'verified' };
356
+ }
357
+ /**
358
+ * The legacy counterpart: a daemon with no handoff cannot say `verified`, so the copy
359
+ * claims only what was seen ("restarted on version X"), never "adopted N sessions".
360
+ * A new process (a different start time and, where known, a different PID), the same
361
+ * identity, and the version it announces equal to the bundled one.
362
+ */
363
+ export function verifyLegacyRestart(evidence) {
364
+ const { before, after, expect } = evidence;
365
+ if (after.daemonId === null || after.startedAt === null || evidence.runtimeKeyNow === null) {
366
+ return { kind: 'not_yet', missing: 'identity_unreadable' };
367
+ }
368
+ const newProcess = before.startedAt === null
369
+ ? after.processId !== null && after.processId !== before.processId
370
+ : after.startedAt > before.startedAt &&
371
+ (after.processId === null ||
372
+ before.processId === null ||
373
+ after.processId !== before.processId);
374
+ if (!newProcess)
375
+ return { kind: 'not_yet', missing: 'verified' };
376
+ if (after.daemonId !== expect.daemonId)
377
+ return { kind: 'reinstall', what: 'daemon_id' };
378
+ if (evidence.runtimeKeyNow !== evidence.recordedRuntimeKey) {
379
+ return { kind: 'reinstall', what: 'runtime_key' };
380
+ }
381
+ if (expect.version !== null && after.version !== expect.version) {
382
+ return { kind: 'not_yet', missing: 'verified' };
383
+ }
384
+ return { kind: 'verified' };
385
+ }
386
+ /**
387
+ * Whether the server independently saw a new process register under the held key
388
+ * (condition 3). The Journey says `back_held` for ANY process that registered since
389
+ * the fence, so it can only corroborate THIS replacement when it did not already say
390
+ * so before the commit. When it did, the answer is `not_independent`: the flow rests
391
+ * on (1) and (2) and says so.
392
+ */
393
+ export function serverSawNewProcess(input) {
394
+ if (input.backHeldAtCommit)
395
+ return 'not_independent';
396
+ return input.journeyPhase === 'back_held' ? 'seen' : 'not_yet';
397
+ }
398
+ /** Whether an observed hold is the one this flow took, and still live. */
399
+ export function isOurLiveHold(view, generation) {
400
+ return (generation !== null &&
401
+ !!view?.hold &&
402
+ view.hold.lifecycle === 'held' &&
403
+ view.hold.generation === generation);
404
+ }
@@ -0,0 +1,27 @@
1
+ /** The desktop reserves a new profile for every background authorization. */
2
+ export declare const INDEPENDENT_MACHINE_PROFILE_RE: RegExp;
3
+ export declare const INDEPENDENT_MACHINE_RESERVATION_FILE = "independent-machine-attempt.json";
4
+ export interface IndependentMachineReservation {
5
+ version: 2;
6
+ profile: string;
7
+ ownerId: string;
8
+ origin: string;
9
+ secretHash: string;
10
+ encryptedSecret: string;
11
+ createdAt: string;
12
+ }
13
+ /** Reserve a previously absent profile and persist the encrypted attempt secret.
14
+ * `encrypt` must use the platform secure store and throw if it is unavailable. */
15
+ export declare function reserveIndependentMachineProfile(options: {
16
+ root: string;
17
+ ownerId: string;
18
+ origin: string;
19
+ encrypt: (secret: string) => Buffer;
20
+ }): {
21
+ reservation: IndependentMachineReservation;
22
+ secret: string;
23
+ };
24
+ /** A saved reservation is a selection hint, never proof of machine ownership. */
25
+ export declare function readIndependentMachineReservation(root: string, profile: string): IndependentMachineReservation | null;
26
+ /** Confirm a one-shot signer is acting on the desktop's reserved attempt. */
27
+ export declare function verifyIndependentMachineReservation(root: string, profile: string, secret: unknown): boolean;
@@ -0,0 +1,132 @@
1
+ import crypto from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ /** The desktop reserves a new profile for every background authorization. */
5
+ export const INDEPENDENT_MACHINE_PROFILE_RE = /^desktop-bg-[a-f0-9]{32}$/;
6
+ export const INDEPENDENT_MACHINE_RESERVATION_FILE = 'independent-machine-attempt.json';
7
+ function canonicalOrigin(input) {
8
+ try {
9
+ const url = new URL(input);
10
+ const local = ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname);
11
+ return !url.username && !url.password && url.origin === input &&
12
+ (url.protocol === 'https:' || (url.protocol === 'http:' && local));
13
+ }
14
+ catch {
15
+ return false;
16
+ }
17
+ }
18
+ function validSecret(secret) {
19
+ return typeof secret === 'string' && /^[A-Za-z0-9_-]{43}$/.test(secret) &&
20
+ Buffer.from(secret, 'base64url').length === 32 &&
21
+ Buffer.from(secret, 'base64url').toString('base64url') === secret;
22
+ }
23
+ function hashSecret(profile, ownerId, origin, secret) {
24
+ return crypto.createHash('sha256').update('independent-machine-reservation:v2\0')
25
+ .update(profile).update('\0').update(ownerId).update('\0').update(origin).update('\0').update(secret).digest();
26
+ }
27
+ function reservationPath(root, profile) {
28
+ if (!path.isAbsolute(root) || !INDEPENDENT_MACHINE_PROFILE_RE.test(profile)) {
29
+ throw new Error('Invalid independent machine profile');
30
+ }
31
+ return path.join(root, 'profiles', profile, INDEPENDENT_MACHINE_RESERVATION_FILE);
32
+ }
33
+ /** Reserve a previously absent profile and persist the encrypted attempt secret.
34
+ * `encrypt` must use the platform secure store and throw if it is unavailable. */
35
+ export function reserveIndependentMachineProfile(options) {
36
+ if (!path.isAbsolute(options.root) || !/^[a-fA-F0-9]{24}$/.test(options.ownerId) ||
37
+ !canonicalOrigin(options.origin))
38
+ throw new Error('Invalid background authorization context');
39
+ const secret = crypto.randomBytes(32).toString('base64url');
40
+ const encrypted = options.encrypt(secret);
41
+ if (!Buffer.isBuffer(encrypted) || encrypted.length === 0 || encrypted.length > 4096) {
42
+ throw new Error('Background authorization secure storage is unavailable');
43
+ }
44
+ const profiles = path.join(options.root, 'profiles');
45
+ const rootExisted = fs.existsSync(options.root);
46
+ const profilesExisted = fs.existsSync(profiles);
47
+ fs.mkdirSync(profiles, { recursive: true, mode: 0o700 });
48
+ for (let i = 0; i < 5; i += 1) {
49
+ const profile = `desktop-bg-${crypto.randomBytes(16).toString('hex')}`;
50
+ const dir = path.join(profiles, profile);
51
+ try {
52
+ fs.mkdirSync(dir, { mode: 0o700 });
53
+ }
54
+ catch (error) {
55
+ if (error.code === 'EEXIST')
56
+ continue;
57
+ throw error;
58
+ }
59
+ const reservation = {
60
+ version: 2, profile, ownerId: options.ownerId.toLowerCase(), origin: options.origin,
61
+ secretHash: hashSecret(profile, options.ownerId.toLowerCase(), options.origin, secret).toString('hex'),
62
+ encryptedSecret: encrypted.toString('base64'),
63
+ createdAt: new Date().toISOString(),
64
+ };
65
+ const file = reservationPath(options.root, profile);
66
+ const fd = fs.openSync(file, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL |
67
+ (fs.constants.O_NOFOLLOW ?? 0), 0o600);
68
+ try {
69
+ fs.writeFileSync(fd, `${JSON.stringify(reservation)}\n`, 'utf8');
70
+ fs.fsyncSync(fd);
71
+ }
72
+ finally {
73
+ fs.closeSync(fd);
74
+ }
75
+ const directories = [dir, profiles, ...(!profilesExisted ? [options.root] : []),
76
+ ...(!rootExisted ? [path.dirname(options.root)] : [])];
77
+ for (const directory of directories) {
78
+ const directoryFd = fs.openSync(directory, 'r');
79
+ try {
80
+ fs.fsyncSync(directoryFd);
81
+ }
82
+ finally {
83
+ fs.closeSync(directoryFd);
84
+ }
85
+ }
86
+ // A profile whose reservation cannot be written is never used. Keep any
87
+ // uncertain directory for recovery rather than deleting other state.
88
+ return { reservation, secret };
89
+ }
90
+ throw new Error('Could not reserve a new background machine profile');
91
+ }
92
+ /** A saved reservation is a selection hint, never proof of machine ownership. */
93
+ export function readIndependentMachineReservation(root, profile) {
94
+ try {
95
+ const file = reservationPath(root, profile);
96
+ const directory = fs.lstatSync(path.dirname(file));
97
+ if (!directory.isDirectory() || directory.isSymbolicLink())
98
+ return null;
99
+ const fd = fs.openSync(file, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0));
100
+ let raw;
101
+ try {
102
+ const stat = fs.fstatSync(fd);
103
+ if (!stat.isFile() || stat.nlink !== 1 || stat.size > 8192 || (stat.mode & 0o077) !== 0)
104
+ return null;
105
+ raw = fs.readFileSync(fd, 'utf8');
106
+ }
107
+ finally {
108
+ fs.closeSync(fd);
109
+ }
110
+ const value = JSON.parse(raw);
111
+ if (value.version !== 2 || value.profile !== profile ||
112
+ typeof value.ownerId !== 'string' || !/^[a-f0-9]{24}$/.test(value.ownerId) ||
113
+ typeof value.origin !== 'string' || !canonicalOrigin(value.origin) ||
114
+ typeof value.secretHash !== 'string' || !/^[a-f0-9]{64}$/.test(value.secretHash) ||
115
+ typeof value.encryptedSecret !== 'string' || !/^[A-Za-z0-9+/]+={0,2}$/.test(value.encryptedSecret) ||
116
+ typeof value.createdAt !== 'string' || !Number.isFinite(Date.parse(value.createdAt)))
117
+ return null;
118
+ return value;
119
+ }
120
+ catch {
121
+ return null;
122
+ }
123
+ }
124
+ /** Confirm a one-shot signer is acting on the desktop's reserved attempt. */
125
+ export function verifyIndependentMachineReservation(root, profile, secret) {
126
+ if (!validSecret(secret))
127
+ return false;
128
+ const saved = readIndependentMachineReservation(root, profile);
129
+ if (!saved)
130
+ return false;
131
+ return crypto.timingSafeEqual(Buffer.from(saved.secretHash, 'hex'), hashSecret(profile, saved.ownerId, saved.origin, secret));
132
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The capabilities explained to a person before a separate background
3
+ * machine is authorized. This version is an immutable wire contract: adding
4
+ * access needs a new contract and a new authorization, not an edit to v1.
5
+ */
6
+ export declare const INDEPENDENT_MACHINE_SCOPE_CONTRACT_V1: "desktop-background-v1";
7
+ export declare const INDEPENDENT_MACHINE_PERMISSIONS_V1: readonly [Readonly<{
8
+ scope: "daemon:tools:bash";
9
+ description: "Run terminal commands and local agents";
10
+ }>, Readonly<{
11
+ scope: "daemon:tools:fs:read";
12
+ description: "Read files in allowed folders";
13
+ }>, Readonly<{
14
+ scope: "daemon:tools:fs:write";
15
+ description: "Create and edit files in allowed folders";
16
+ }>, Readonly<{
17
+ scope: "daemon:browser:cdp";
18
+ description: "Control connected browser tabs";
19
+ }>, Readonly<{
20
+ scope: "daemon:session:replay";
21
+ description: "Replay results recorded while offline";
22
+ }>, Readonly<{
23
+ scope: "daemon:offline-queue";
24
+ description: "Send queued results after reconnecting";
25
+ }>];
26
+ export declare const INDEPENDENT_MACHINE_SCOPES_V1: readonly string[];
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The capabilities explained to a person before a separate background
3
+ * machine is authorized. This version is an immutable wire contract: adding
4
+ * access needs a new contract and a new authorization, not an edit to v1.
5
+ */
6
+ export const INDEPENDENT_MACHINE_SCOPE_CONTRACT_V1 = 'desktop-background-v1';
7
+ export const INDEPENDENT_MACHINE_PERMISSIONS_V1 = Object.freeze([
8
+ Object.freeze({ scope: 'daemon:tools:bash', description: 'Run terminal commands and local agents' }),
9
+ Object.freeze({ scope: 'daemon:tools:fs:read', description: 'Read files in allowed folders' }),
10
+ Object.freeze({ scope: 'daemon:tools:fs:write', description: 'Create and edit files in allowed folders' }),
11
+ Object.freeze({ scope: 'daemon:browser:cdp', description: 'Control connected browser tabs' }),
12
+ Object.freeze({ scope: 'daemon:session:replay', description: 'Replay results recorded while offline' }),
13
+ Object.freeze({ scope: 'daemon:offline-queue', description: 'Send queued results after reconnecting' }),
14
+ ]);
15
+ export const INDEPENDENT_MACHINE_SCOPES_V1 = Object.freeze(INDEPENDENT_MACHINE_PERMISSIONS_V1.map((permission) => permission.scope));
@@ -28,7 +28,7 @@ export { buildCredentialAad, decrypt as credEnvelopeDecrypt, deserialize as cred
28
28
  export { __setKekStrategyForTest, getKekStrategy, InMemoryKek, UnavailableKek, KekUnavailableError, LinuxKek, MacosKek, registerZeroizeHook, WindowsKek, zeroizeKekCaches, type KekStrategy, } from './kek/index.js';
29
29
  export { buildProof as buildDeviceProof, generateDeviceKeyPair, jwkThumbprint, normalizeHtu, publicJwkFromX, tokenHash, verifyProof as verifyDeviceProof, type DeviceKeyPair, type DeviceProofClaims, type DeviceProofHeader, type Ed25519PrivateJwk, type Ed25519PublicJwk, type VerifyFailureReason as DeviceProofVerifyFailureReason, type VerifyFailure as DeviceProofVerifyFailure, type VerifyOptions as DeviceProofVerifyOptions, type VerifySuccess as DeviceProofVerifySuccess, } from './deviceKey.js';
30
30
  export { DPOP_HEADER, DPOP_MAX_AGE_SECONDS, DPOP_PROOF_INVALID, DPOP_PROOF_REQUIRED, DPOP_PROOF_TYP, DPOP_PROOF_VERIFY_UNAVAILABLE, accessTokenHash, createDpopKey, isAth, isJwkThumbprint, normalizeDpopHtu, verifyDpopProof, type DpopKey, type DpopProofClaims, type DpopProofRequest, type DpopVerifyFailureReason, type DpopVerifyOptions, type DpopVerifyResult, } from './dpop.js';
31
- export { initCredEnvelope, resetCredEnvelope, shutdownCredEnvelope, isCredEnvelopeActive, hasCredEnvelopePathConflict, describeCredEnvelopeState, splitKeyConvergencePolicy, maybeEncryptForWrite, maybeDecryptOnRead, DEFAULT_UNREADABLE_CREDENTIAL_HINT, type DecryptOnReadOptions, __wrappedDekFilePathForTest, __setStateForTest as __setCredEnvelopeStateForTest, __getStateForTest as __getCredEnvelopeStateForTest, __resetShutdownGuardForTest, type ReadTransformResult, } from './credentialEnvelopeBridge.js';
31
+ export { initCredEnvelope, initExistingCredEnvelope, initNewIndependentCredEnvelope, resetCredEnvelope, shutdownCredEnvelope, isCredEnvelopeActive, hasCredEnvelopePathConflict, describeCredEnvelopeState, splitKeyConvergencePolicy, maybeEncryptForWrite, maybeDecryptOnRead, DEFAULT_UNREADABLE_CREDENTIAL_HINT, type DecryptOnReadOptions, __wrappedDekFilePathForTest, __setStateForTest as __setCredEnvelopeStateForTest, __getStateForTest as __getCredEnvelopeStateForTest, __resetShutdownGuardForTest, type ReadTransformResult, } from './credentialEnvelopeBridge.js';
32
32
  export { initDeviceIdentity, resetDeviceIdentity, signServerRequest, enrollWithServer, clearEnrollmentMarker, isDeviceIdentityActive, getDevicePublicKey, describeDeviceIdentityState, describeDeviceKeySplit, __publicKeyPathForTest, __privateKeyPathForTest, __enrollmentMarkerPathForTest, __enrollmentBackoffPathForTest, __setStateForTest as __setDeviceIdentityStateForTest, __getStateForTest as __getDeviceIdentityStateForTest, type EnrollmentResult, } from './deviceIdentityBridge.js';
33
33
  export { getMachineUuid, getMachineUuidForProfile, describeMachineIdentity, type MachineIdentityDescription, type MachineIdentityReason, getHardwareMachineUuid, setMachineUuidProfile, getMachineUuidProfile, qualifyMachineUuid, MACHINE_IDENTITY_FILENAME, hostedRuntimeIdOverride, HOSTED_RUNTIME_MACHINE_SOURCES, __resetMachineUuidCacheForTest, } from './machineUuid.js';
34
34
  export { ensureMachineDaemonId, legacyDaemonUuids, __resetCachedDaemonIdForTest, } from './machineId.js';
@@ -50,3 +50,4 @@ export * from './harnessPermissionControls.js';
50
50
  export { compareReleaseVersions } from './releaseVersions.js';
51
51
  export * from './computerUse.js';
52
52
  export * from './desktopWire.js';
53
+ export { INDEPENDENT_MACHINE_PROFILE_RE, INDEPENDENT_MACHINE_RESERVATION_FILE, readIndependentMachineReservation, reserveIndependentMachineProfile, verifyIndependentMachineReservation, type IndependentMachineReservation, } from './independentMachineReservation.js';
@@ -112,7 +112,7 @@ export { DPOP_HEADER, DPOP_MAX_AGE_SECONDS, DPOP_PROOF_INVALID, DPOP_PROOF_REQUI
112
112
  // Daemon and CLI consume these; the underlying primitives live above.
113
113
  export {
114
114
  // credEnvelope bridge
115
- initCredEnvelope, resetCredEnvelope, shutdownCredEnvelope, isCredEnvelopeActive, hasCredEnvelopePathConflict, describeCredEnvelopeState, splitKeyConvergencePolicy, maybeEncryptForWrite, maybeDecryptOnRead, DEFAULT_UNREADABLE_CREDENTIAL_HINT,
115
+ initCredEnvelope, initExistingCredEnvelope, initNewIndependentCredEnvelope, resetCredEnvelope, shutdownCredEnvelope, isCredEnvelopeActive, hasCredEnvelopePathConflict, describeCredEnvelopeState, splitKeyConvergencePolicy, maybeEncryptForWrite, maybeDecryptOnRead, DEFAULT_UNREADABLE_CREDENTIAL_HINT,
116
116
  // Test seams — `__`-prefixed so consumers don't import them in
117
117
  // production code paths. Re-exported here so CLI/daemon spec files
118
118
  // can resolve the wrapped-DEK path against the same module instance.
@@ -186,3 +186,4 @@ export * from './harnessPermissionControls.js';
186
186
  export { compareReleaseVersions } from './releaseVersions.js';
187
187
  export * from './computerUse.js';
188
188
  export * from './desktopWire.js';
189
+ export { INDEPENDENT_MACHINE_PROFILE_RE, INDEPENDENT_MACHINE_RESERVATION_FILE, readIndependentMachineReservation, reserveIndependentMachineProfile, verifyIndependentMachineReservation, } from './independentMachineReservation.js';