toolroll 0.5.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 (626) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1113 -0
  3. package/THIRD_PARTY_NOTICES.md +449 -0
  4. package/dist/accent-colors.d.ts +48 -0
  5. package/dist/accent-colors.js +122 -0
  6. package/dist/action-ledger.d.ts +26 -0
  7. package/dist/action-ledger.js +75 -0
  8. package/dist/agent-fence.d.ts +42 -0
  9. package/dist/agent-fence.js +183 -0
  10. package/dist/agentconfig.d.ts +216 -0
  11. package/dist/agentconfig.js +454 -0
  12. package/dist/api-tokens.d.ts +16 -0
  13. package/dist/api-tokens.js +27 -0
  14. package/dist/approval-policy.d.ts +69 -0
  15. package/dist/approval-policy.js +137 -0
  16. package/dist/approval-rules-ui.d.ts +25 -0
  17. package/dist/approval-rules-ui.js +39 -0
  18. package/dist/assignment-adapters.d.ts +180 -0
  19. package/dist/assignment-adapters.js +239 -0
  20. package/dist/assignment-brief.d.ts +71 -0
  21. package/dist/assignment-brief.js +129 -0
  22. package/dist/assignment-delivery.d.ts +56 -0
  23. package/dist/assignment-delivery.js +160 -0
  24. package/dist/assignment-presentation.d.ts +26 -0
  25. package/dist/assignment-presentation.js +80 -0
  26. package/dist/assignment-status.d.ts +62 -0
  27. package/dist/assignment-status.js +152 -0
  28. package/dist/assignment-ui.d.ts +71 -0
  29. package/dist/assignment-ui.js +103 -0
  30. package/dist/assignment.d.ts +222 -0
  31. package/dist/assignment.js +399 -0
  32. package/dist/attest.d.ts +56 -0
  33. package/dist/attest.js +153 -0
  34. package/dist/backend.d.ts +97 -0
  35. package/dist/backend.js +166 -0
  36. package/dist/backup-ui.d.ts +22 -0
  37. package/dist/backup-ui.js +59 -0
  38. package/dist/backup.d.ts +84 -0
  39. package/dist/backup.js +421 -0
  40. package/dist/beads.d.ts +34 -0
  41. package/dist/beads.js +135 -0
  42. package/dist/bin.d.ts +2 -0
  43. package/dist/bin.js +22 -0
  44. package/dist/board.d.ts +150 -0
  45. package/dist/board.js +210 -0
  46. package/dist/boot-identity.d.ts +63 -0
  47. package/dist/boot-identity.js +99 -0
  48. package/dist/browser/THIRD_PARTY_NOTICES.txt +2295 -0
  49. package/dist/browser/workspace.css +4 -0
  50. package/dist/browser/workspace.js +225 -0
  51. package/dist/browser-crew.d.ts +11 -0
  52. package/dist/browser-crew.js +50 -0
  53. package/dist/browser-shell.d.ts +8 -0
  54. package/dist/browser-shell.js +60 -0
  55. package/dist/browser-workspace.d.ts +780 -0
  56. package/dist/browser-workspace.js +47 -0
  57. package/dist/budget-alerts.d.ts +19 -0
  58. package/dist/budget-alerts.js +46 -0
  59. package/dist/builder.d.ts +370 -0
  60. package/dist/builder.js +3262 -0
  61. package/dist/capscan.d.ts +35 -0
  62. package/dist/capscan.js +113 -0
  63. package/dist/chat-acceptance.d.ts +71 -0
  64. package/dist/chat-acceptance.js +120 -0
  65. package/dist/chat-actions.d.ts +265 -0
  66. package/dist/chat-actions.js +1273 -0
  67. package/dist/chat-channel.d.ts +109 -0
  68. package/dist/chat-channel.js +652 -0
  69. package/dist/chat-continuity.d.ts +32 -0
  70. package/dist/chat-continuity.js +317 -0
  71. package/dist/chat-controls.d.ts +113 -0
  72. package/dist/chat-controls.js +53 -0
  73. package/dist/chat-delivery-state.d.ts +159 -0
  74. package/dist/chat-delivery-state.js +309 -0
  75. package/dist/chat-delivery.d.ts +38 -0
  76. package/dist/chat-delivery.js +693 -0
  77. package/dist/chat-display.d.ts +1 -0
  78. package/dist/chat-display.js +18 -0
  79. package/dist/chat-evidence.d.ts +123 -0
  80. package/dist/chat-evidence.js +207 -0
  81. package/dist/chat-flow.d.ts +44 -0
  82. package/dist/chat-flow.js +215 -0
  83. package/dist/chat-inbox.d.ts +57 -0
  84. package/dist/chat-inbox.js +123 -0
  85. package/dist/chat-polish.d.ts +16 -0
  86. package/dist/chat-polish.js +135 -0
  87. package/dist/chat-review.d.ts +46 -0
  88. package/dist/chat-review.js +98 -0
  89. package/dist/chat-rooms.d.ts +123 -0
  90. package/dist/chat-rooms.js +216 -0
  91. package/dist/chat-task-actions.d.ts +44 -0
  92. package/dist/chat-task-actions.js +79 -0
  93. package/dist/check-progress.d.ts +51 -0
  94. package/dist/check-progress.js +290 -0
  95. package/dist/child-database.d.ts +12 -0
  96. package/dist/child-database.js +30 -0
  97. package/dist/claim.d.ts +688 -0
  98. package/dist/claim.js +1740 -0
  99. package/dist/cli.d.ts +137 -0
  100. package/dist/cli.js +1461 -0
  101. package/dist/codex-limits.d.ts +15 -0
  102. package/dist/codex-limits.js +145 -0
  103. package/dist/coding-context.d.ts +55 -0
  104. package/dist/coding-context.js +92 -0
  105. package/dist/coding-handoff.d.ts +57 -0
  106. package/dist/coding-handoff.js +277 -0
  107. package/dist/coding-provider.d.ts +72 -0
  108. package/dist/coding-provider.js +424 -0
  109. package/dist/coding-shipping-ui.d.ts +4 -0
  110. package/dist/coding-shipping-ui.js +12 -0
  111. package/dist/coding-types.d.ts +62 -0
  112. package/dist/coding-types.js +1 -0
  113. package/dist/coding-ui.d.ts +17 -0
  114. package/dist/coding-ui.js +364 -0
  115. package/dist/coding-update.d.ts +15 -0
  116. package/dist/coding-update.js +149 -0
  117. package/dist/coding-workspace.d.ts +124 -0
  118. package/dist/coding-workspace.js +768 -0
  119. package/dist/container-state.d.ts +5 -0
  120. package/dist/container-state.js +62 -0
  121. package/dist/containment.d.ts +197 -0
  122. package/dist/containment.js +559 -0
  123. package/dist/contest.d.ts +318 -0
  124. package/dist/contest.js +753 -0
  125. package/dist/control-setup.d.ts +35 -0
  126. package/dist/control-setup.js +40 -0
  127. package/dist/control-ui.d.ts +29 -0
  128. package/dist/control-ui.js +53 -0
  129. package/dist/controller-service.d.ts +1 -0
  130. package/dist/controller-service.js +19 -0
  131. package/dist/controller-supervisor.d.ts +30 -0
  132. package/dist/controller-supervisor.js +104 -0
  133. package/dist/converse.d.ts +304 -0
  134. package/dist/converse.js +849 -0
  135. package/dist/coordinator-proposals.d.ts +29 -0
  136. package/dist/coordinator-proposals.js +137 -0
  137. package/dist/coordinator.d.ts +186 -0
  138. package/dist/coordinator.js +447 -0
  139. package/dist/credentials-ui.d.ts +25 -0
  140. package/dist/credentials-ui.js +48 -0
  141. package/dist/daemon.d.ts +209 -0
  142. package/dist/daemon.js +604 -0
  143. package/dist/decision.d.ts +120 -0
  144. package/dist/decision.js +388 -0
  145. package/dist/demo.d.ts +55 -0
  146. package/dist/demo.js +992 -0
  147. package/dist/desktop-access.d.ts +28 -0
  148. package/dist/desktop-access.js +88 -0
  149. package/dist/desktop-bundle.d.ts +16 -0
  150. package/dist/desktop-bundle.js +74 -0
  151. package/dist/desktop-host.d.ts +54 -0
  152. package/dist/desktop-host.js +508 -0
  153. package/dist/desktop-update-gate.d.ts +12 -0
  154. package/dist/desktop-update-gate.js +91 -0
  155. package/dist/desktop-update-recovery.d.ts +14 -0
  156. package/dist/desktop-update-recovery.js +243 -0
  157. package/dist/desktop-update.d.ts +110 -0
  158. package/dist/desktop-update.js +740 -0
  159. package/dist/discord-api.d.ts +20 -0
  160. package/dist/discord-api.js +138 -0
  161. package/dist/discord-chat.d.ts +16 -0
  162. package/dist/discord-chat.js +381 -0
  163. package/dist/discord-settings.d.ts +7 -0
  164. package/dist/discord-settings.js +75 -0
  165. package/dist/discord.d.ts +10 -0
  166. package/dist/discord.js +169 -0
  167. package/dist/discover.d.ts +75 -0
  168. package/dist/discover.js +150 -0
  169. package/dist/dispatch.d.ts +100 -0
  170. package/dist/dispatch.js +311 -0
  171. package/dist/dispose.d.ts +161 -0
  172. package/dist/dispose.js +803 -0
  173. package/dist/email-settings.d.ts +34 -0
  174. package/dist/email-settings.js +50 -0
  175. package/dist/envelope.d.ts +47 -0
  176. package/dist/envelope.js +105 -0
  177. package/dist/evidence-pack.d.ts +185 -0
  178. package/dist/evidence-pack.js +258 -0
  179. package/dist/evidence.d.ts +345 -0
  180. package/dist/evidence.js +782 -0
  181. package/dist/exec.d.ts +285 -0
  182. package/dist/exec.js +1841 -0
  183. package/dist/exhaustion.d.ts +85 -0
  184. package/dist/exhaustion.js +141 -0
  185. package/dist/export-ui.d.ts +4 -0
  186. package/dist/export-ui.js +17 -0
  187. package/dist/export.d.ts +48 -0
  188. package/dist/export.js +305 -0
  189. package/dist/flow-actions.d.ts +77 -0
  190. package/dist/flow-actions.js +203 -0
  191. package/dist/flow-code.d.ts +50 -0
  192. package/dist/flow-code.js +159 -0
  193. package/dist/flow-draft.d.ts +38 -0
  194. package/dist/flow-draft.js +80 -0
  195. package/dist/flow-engine.d.ts +66 -0
  196. package/dist/flow-engine.js +416 -0
  197. package/dist/flow-insights.d.ts +95 -0
  198. package/dist/flow-insights.js +149 -0
  199. package/dist/flow-live.d.ts +24 -0
  200. package/dist/flow-live.js +150 -0
  201. package/dist/flow-people.d.ts +24 -0
  202. package/dist/flow-people.js +91 -0
  203. package/dist/flow-replies.d.ts +48 -0
  204. package/dist/flow-replies.js +113 -0
  205. package/dist/flow-scripts.d.ts +38 -0
  206. package/dist/flow-scripts.js +73 -0
  207. package/dist/flow-secrets.d.ts +13 -0
  208. package/dist/flow-secrets.js +45 -0
  209. package/dist/flow-sort.d.ts +82 -0
  210. package/dist/flow-sort.js +153 -0
  211. package/dist/flow-steps.d.ts +40 -0
  212. package/dist/flow-steps.js +408 -0
  213. package/dist/flow-triggers.d.ts +244 -0
  214. package/dist/flow-triggers.js +959 -0
  215. package/dist/flows-ui.d.ts +20 -0
  216. package/dist/flows-ui.js +175 -0
  217. package/dist/flows.d.ts +229 -0
  218. package/dist/flows.js +968 -0
  219. package/dist/fonts.d.ts +19 -0
  220. package/dist/fonts.js +19 -0
  221. package/dist/gaps.d.ts +35 -0
  222. package/dist/gaps.js +101 -0
  223. package/dist/gate-failure.d.ts +40 -0
  224. package/dist/gate-failure.js +66 -0
  225. package/dist/git.d.ts +54 -0
  226. package/dist/git.js +94 -0
  227. package/dist/google-mail.d.ts +58 -0
  228. package/dist/google-mail.js +151 -0
  229. package/dist/grant.d.ts +136 -0
  230. package/dist/grant.js +238 -0
  231. package/dist/graph.d.ts +164 -0
  232. package/dist/graph.js +383 -0
  233. package/dist/guides.d.ts +22 -0
  234. package/dist/guides.js +363 -0
  235. package/dist/held.d.ts +150 -0
  236. package/dist/held.js +799 -0
  237. package/dist/invoke.d.ts +112 -0
  238. package/dist/invoke.js +780 -0
  239. package/dist/issues.d.ts +41 -0
  240. package/dist/issues.js +131 -0
  241. package/dist/job-object-helper.ps1 +252 -0
  242. package/dist/jsonl-discriminants.d.ts +11 -0
  243. package/dist/jsonl-discriminants.js +93 -0
  244. package/dist/keys.d.ts +104 -0
  245. package/dist/keys.js +229 -0
  246. package/dist/kits-ui.d.ts +17 -0
  247. package/dist/kits-ui.js +46 -0
  248. package/dist/kits.d.ts +86 -0
  249. package/dist/kits.js +215 -0
  250. package/dist/knowledge-cli.d.ts +101 -0
  251. package/dist/knowledge-cli.js +92 -0
  252. package/dist/knowledge-ui.d.ts +24 -0
  253. package/dist/knowledge-ui.js +40 -0
  254. package/dist/lead-context.d.ts +8 -0
  255. package/dist/lead-context.js +56 -0
  256. package/dist/lead-follow.d.ts +22 -0
  257. package/dist/lead-follow.js +167 -0
  258. package/dist/lead-status.d.ts +75 -0
  259. package/dist/lead-status.js +285 -0
  260. package/dist/ledger-chain.d.ts +46 -0
  261. package/dist/ledger-chain.js +187 -0
  262. package/dist/ledger-csv.d.ts +4 -0
  263. package/dist/ledger-csv.js +11 -0
  264. package/dist/ledger-view.d.ts +24 -0
  265. package/dist/ledger-view.js +58 -0
  266. package/dist/limits-ui.d.ts +26 -0
  267. package/dist/limits-ui.js +67 -0
  268. package/dist/link.d.ts +72 -0
  269. package/dist/link.js +217 -0
  270. package/dist/live.d.ts +96 -0
  271. package/dist/live.js +366 -0
  272. package/dist/liveness.d.ts +30 -0
  273. package/dist/liveness.js +42 -0
  274. package/dist/log.d.ts +7 -0
  275. package/dist/log.js +25 -0
  276. package/dist/mailbox.d.ts +57 -0
  277. package/dist/mailbox.js +150 -0
  278. package/dist/maintenance.d.ts +11 -0
  279. package/dist/maintenance.js +35 -0
  280. package/dist/mate-cli.d.ts +52 -0
  281. package/dist/mate-cli.js +345 -0
  282. package/dist/mate-contract.d.ts +10 -0
  283. package/dist/mate-contract.js +30 -0
  284. package/dist/mate-doors.d.ts +69 -0
  285. package/dist/mate-doors.js +548 -0
  286. package/dist/mate-progress.d.ts +29 -0
  287. package/dist/mate-progress.js +121 -0
  288. package/dist/mate-tools.d.ts +94 -0
  289. package/dist/mate-tools.js +1878 -0
  290. package/dist/mate.d.ts +92 -0
  291. package/dist/mate.js +435 -0
  292. package/dist/mcp-connect.d.ts +103 -0
  293. package/dist/mcp-connect.js +252 -0
  294. package/dist/mcp.d.ts +27 -0
  295. package/dist/mcp.js +651 -0
  296. package/dist/memory-cli.d.ts +466 -0
  297. package/dist/memory-cli.js +111 -0
  298. package/dist/memory-pass.d.ts +160 -0
  299. package/dist/memory-pass.js +409 -0
  300. package/dist/metrics.d.ts +3 -0
  301. package/dist/metrics.js +56 -0
  302. package/dist/mobile-viewport.d.ts +3 -0
  303. package/dist/mobile-viewport.js +37 -0
  304. package/dist/model-catalog.d.ts +118 -0
  305. package/dist/model-catalog.js +376 -0
  306. package/dist/models-cli.d.ts +102 -0
  307. package/dist/models-cli.js +66 -0
  308. package/dist/models-ui.d.ts +34 -0
  309. package/dist/models-ui.js +68 -0
  310. package/dist/modes.d.ts +80 -0
  311. package/dist/modes.js +182 -0
  312. package/dist/monitoring-settings.d.ts +44 -0
  313. package/dist/monitoring-settings.js +173 -0
  314. package/dist/monitoring-ui.d.ts +20 -0
  315. package/dist/monitoring-ui.js +51 -0
  316. package/dist/monitoring.d.ts +34 -0
  317. package/dist/monitoring.js +221 -0
  318. package/dist/names.d.ts +67 -0
  319. package/dist/names.js +102 -0
  320. package/dist/observations.d.ts +40 -0
  321. package/dist/observations.js +211 -0
  322. package/dist/oidc.d.ts +71 -0
  323. package/dist/oidc.js +182 -0
  324. package/dist/onboard.d.ts +108 -0
  325. package/dist/onboard.js +325 -0
  326. package/dist/openrouter-models.d.ts +30 -0
  327. package/dist/openrouter-models.js +120 -0
  328. package/dist/operate.d.ts +118 -0
  329. package/dist/operate.js +11321 -0
  330. package/dist/peek-cli.d.ts +97 -0
  331. package/dist/peek-cli.js +215 -0
  332. package/dist/peek.d.ts +134 -0
  333. package/dist/peek.js +578 -0
  334. package/dist/phase-routing.d.ts +307 -0
  335. package/dist/phase-routing.js +658 -0
  336. package/dist/plan-auto.d.ts +14 -0
  337. package/dist/plan-auto.js +92 -0
  338. package/dist/plan.d.ts +186 -0
  339. package/dist/plan.js +401 -0
  340. package/dist/planner-source.d.ts +251 -0
  341. package/dist/planner-source.js +460 -0
  342. package/dist/planner.d.ts +75 -0
  343. package/dist/planner.js +992 -0
  344. package/dist/policy-ui.d.ts +20 -0
  345. package/dist/policy-ui.js +41 -0
  346. package/dist/policy.d.ts +111 -0
  347. package/dist/policy.js +231 -0
  348. package/dist/prepared-evidence.d.ts +16 -0
  349. package/dist/prepared-evidence.js +96 -0
  350. package/dist/pricing.d.ts +45 -0
  351. package/dist/pricing.js +77 -0
  352. package/dist/principal.d.ts +42 -0
  353. package/dist/principal.js +82 -0
  354. package/dist/probe.d.ts +46 -0
  355. package/dist/probe.js +79 -0
  356. package/dist/process-custody.d.ts +17 -0
  357. package/dist/process-custody.js +91 -0
  358. package/dist/process-liveness.d.ts +10 -0
  359. package/dist/process-liveness.js +81 -0
  360. package/dist/process-recovery-anchor.d.ts +75 -0
  361. package/dist/process-recovery-anchor.js +188 -0
  362. package/dist/process-recovery-coalition.d.ts +25 -0
  363. package/dist/process-recovery-coalition.js +105 -0
  364. package/dist/process-recovery-eligibility.d.ts +178 -0
  365. package/dist/process-recovery-eligibility.js +170 -0
  366. package/dist/process-recovery-native.d.ts +172 -0
  367. package/dist/process-recovery-native.js +592 -0
  368. package/dist/process-recovery-provenance.d.ts +163 -0
  369. package/dist/process-recovery-provenance.js +292 -0
  370. package/dist/process-recovery-services.d.ts +56 -0
  371. package/dist/process-recovery-services.js +191 -0
  372. package/dist/process-recovery-settlement.d.ts +32 -0
  373. package/dist/process-recovery-settlement.js +132 -0
  374. package/dist/process-recovery.d.ts +41 -0
  375. package/dist/process-recovery.js +91 -0
  376. package/dist/process-tree.d.ts +41 -0
  377. package/dist/process-tree.js +264 -0
  378. package/dist/project-access.d.ts +14 -0
  379. package/dist/project-access.js +34 -0
  380. package/dist/project-cli.d.ts +18 -0
  381. package/dist/project-cli.js +104 -0
  382. package/dist/project-delete-ui.d.ts +22 -0
  383. package/dist/project-delete-ui.js +48 -0
  384. package/dist/project-delete.d.ts +55 -0
  385. package/dist/project-delete.js +413 -0
  386. package/dist/project-knowledge.d.ts +111 -0
  387. package/dist/project-knowledge.js +241 -0
  388. package/dist/project-learning.d.ts +100 -0
  389. package/dist/project-learning.js +438 -0
  390. package/dist/project-memory.d.ts +81 -0
  391. package/dist/project-memory.js +264 -0
  392. package/dist/project-skills.d.ts +152 -0
  393. package/dist/project-skills.js +660 -0
  394. package/dist/project-tools.d.ts +219 -0
  395. package/dist/project-tools.js +796 -0
  396. package/dist/project.d.ts +61 -0
  397. package/dist/project.js +125 -0
  398. package/dist/prompt.d.ts +22 -0
  399. package/dist/prompt.js +72 -0
  400. package/dist/proof.d.ts +513 -0
  401. package/dist/proof.js +1140 -0
  402. package/dist/proposal.d.ts +82 -0
  403. package/dist/proposal.js +210 -0
  404. package/dist/provider-connection.d.ts +22 -0
  405. package/dist/provider-connection.js +98 -0
  406. package/dist/provider-limits.d.ts +35 -0
  407. package/dist/provider-limits.js +95 -0
  408. package/dist/provider.d.ts +279 -0
  409. package/dist/provider.js +935 -0
  410. package/dist/publish.d.ts +107 -0
  411. package/dist/publish.js +650 -0
  412. package/dist/pulls.d.ts +118 -0
  413. package/dist/pulls.js +240 -0
  414. package/dist/push.d.ts +95 -0
  415. package/dist/push.js +353 -0
  416. package/dist/quality.d.ts +8 -0
  417. package/dist/quality.js +6 -0
  418. package/dist/recipe-ui.d.ts +18 -0
  419. package/dist/recipe-ui.js +152 -0
  420. package/dist/recipes.d.ts +81 -0
  421. package/dist/recipes.js +332 -0
  422. package/dist/remote.d.ts +67 -0
  423. package/dist/remote.js +122 -0
  424. package/dist/render.d.ts +64 -0
  425. package/dist/render.js +587 -0
  426. package/dist/report-summary.d.ts +13 -0
  427. package/dist/report-summary.js +15 -0
  428. package/dist/repos.d.ts +65 -0
  429. package/dist/repos.js +277 -0
  430. package/dist/repository-context-ui.d.ts +2 -0
  431. package/dist/repository-context-ui.js +10 -0
  432. package/dist/repository-context.d.ts +67 -0
  433. package/dist/repository-context.js +376 -0
  434. package/dist/restart-certification.d.ts +134 -0
  435. package/dist/restart-certification.js +226 -0
  436. package/dist/result-actions.d.ts +28 -0
  437. package/dist/result-actions.js +270 -0
  438. package/dist/result-completion.d.ts +18 -0
  439. package/dist/result-completion.js +66 -0
  440. package/dist/result-review.d.ts +213 -0
  441. package/dist/result-review.js +430 -0
  442. package/dist/retention-ui.d.ts +12 -0
  443. package/dist/retention-ui.js +39 -0
  444. package/dist/retention.d.ts +72 -0
  445. package/dist/retention.js +288 -0
  446. package/dist/review-context.d.ts +243 -0
  447. package/dist/review-context.js +1043 -0
  448. package/dist/review-evidence.d.ts +33 -0
  449. package/dist/review-evidence.js +56 -0
  450. package/dist/reviewer.d.ts +97 -0
  451. package/dist/reviewer.js +202 -0
  452. package/dist/routine.d.ts +228 -0
  453. package/dist/routine.js +764 -0
  454. package/dist/runner.d.ts +266 -0
  455. package/dist/runner.js +399 -0
  456. package/dist/scan.d.ts +32 -0
  457. package/dist/scan.js +92 -0
  458. package/dist/scope.d.ts +695 -0
  459. package/dist/scope.js +1176 -0
  460. package/dist/scout-report.d.ts +42 -0
  461. package/dist/scout-report.js +108 -0
  462. package/dist/scout.d.ts +69 -0
  463. package/dist/scout.js +351 -0
  464. package/dist/serve.d.ts +313 -0
  465. package/dist/serve.js +21776 -0
  466. package/dist/session-brief.d.ts +8 -0
  467. package/dist/session-brief.js +24 -0
  468. package/dist/session-cli.d.ts +13 -0
  469. package/dist/session-cli.js +284 -0
  470. package/dist/session-contract.d.ts +183 -0
  471. package/dist/session-contract.js +122 -0
  472. package/dist/session-http.d.ts +19 -0
  473. package/dist/session-http.js +154 -0
  474. package/dist/session-server.d.ts +12 -0
  475. package/dist/session-server.js +37 -0
  476. package/dist/session-service.d.ts +22 -0
  477. package/dist/session-service.js +157 -0
  478. package/dist/setup-guide.d.ts +39 -0
  479. package/dist/setup-guide.js +93 -0
  480. package/dist/sign-in-guard.d.ts +45 -0
  481. package/dist/sign-in-guard.js +74 -0
  482. package/dist/skills-ui.d.ts +11 -0
  483. package/dist/skills-ui.js +58 -0
  484. package/dist/skills.d.ts +97 -0
  485. package/dist/skills.js +276 -0
  486. package/dist/slack-api.d.ts +55 -0
  487. package/dist/slack-api.js +229 -0
  488. package/dist/slack-chat.d.ts +28 -0
  489. package/dist/slack-chat.js +413 -0
  490. package/dist/slack-settings.d.ts +7 -0
  491. package/dist/slack-settings.js +75 -0
  492. package/dist/slack-state.d.ts +13 -0
  493. package/dist/slack-state.js +9 -0
  494. package/dist/slack.d.ts +13 -0
  495. package/dist/slack.js +167 -0
  496. package/dist/spend-ui.d.ts +28 -0
  497. package/dist/spend-ui.js +95 -0
  498. package/dist/spend.d.ts +142 -0
  499. package/dist/spend.js +313 -0
  500. package/dist/sqlite-runtime.d.ts +3 -0
  501. package/dist/sqlite-runtime.js +6 -0
  502. package/dist/sso-settings.d.ts +33 -0
  503. package/dist/sso-settings.js +103 -0
  504. package/dist/sso-ui.d.ts +22 -0
  505. package/dist/sso-ui.js +39 -0
  506. package/dist/storage.d.ts +15 -0
  507. package/dist/storage.js +84 -0
  508. package/dist/store.d.ts +7754 -0
  509. package/dist/store.js +23279 -0
  510. package/dist/structured-output.d.ts +58 -0
  511. package/dist/structured-output.js +103 -0
  512. package/dist/style-asset.d.ts +7 -0
  513. package/dist/style-asset.js +41 -0
  514. package/dist/subscription-chat.d.ts +46 -0
  515. package/dist/subscription-chat.js +275 -0
  516. package/dist/summary.d.ts +33 -0
  517. package/dist/summary.js +71 -0
  518. package/dist/supervisor.mjs +297 -0
  519. package/dist/surface.d.ts +63 -0
  520. package/dist/surface.js +352 -0
  521. package/dist/sync.d.ts +94 -0
  522. package/dist/sync.js +279 -0
  523. package/dist/task-composer.d.ts +13 -0
  524. package/dist/task-composer.js +52 -0
  525. package/dist/task-control.d.ts +165 -0
  526. package/dist/task-control.js +214 -0
  527. package/dist/task-outcome-cli.d.ts +10 -0
  528. package/dist/task-outcome-cli.js +92 -0
  529. package/dist/task-text.d.ts +30 -0
  530. package/dist/task-text.js +41 -0
  531. package/dist/team-cli.d.ts +15 -0
  532. package/dist/team-cli.js +618 -0
  533. package/dist/team-contract.d.ts +92 -0
  534. package/dist/team-contract.js +1 -0
  535. package/dist/team-http.d.ts +17 -0
  536. package/dist/team-http.js +144 -0
  537. package/dist/team-leads.d.ts +81 -0
  538. package/dist/team-leads.js +565 -0
  539. package/dist/team-runtime.d.ts +27 -0
  540. package/dist/team-runtime.js +264 -0
  541. package/dist/team-ui.d.ts +3 -0
  542. package/dist/team-ui.js +9 -0
  543. package/dist/team-updates.d.ts +8 -0
  544. package/dist/team-updates.js +57 -0
  545. package/dist/teammate-admin.d.ts +53 -0
  546. package/dist/teammate-admin.js +111 -0
  547. package/dist/teammate-desk.d.ts +60 -0
  548. package/dist/teammate-desk.js +154 -0
  549. package/dist/teammate-memory.d.ts +64 -0
  550. package/dist/teammate-memory.js +196 -0
  551. package/dist/teammate-question.d.ts +59 -0
  552. package/dist/teammate-question.js +175 -0
  553. package/dist/teammate-tools.d.ts +108 -0
  554. package/dist/teammate-tools.js +271 -0
  555. package/dist/teammate-week.d.ts +68 -0
  556. package/dist/teammate-week.js +132 -0
  557. package/dist/teammate-work.d.ts +76 -0
  558. package/dist/teammate-work.js +280 -0
  559. package/dist/teammates-ui.d.ts +20 -0
  560. package/dist/teammates-ui.js +166 -0
  561. package/dist/teammates.d.ts +180 -0
  562. package/dist/teammates.js +253 -0
  563. package/dist/teams-api.d.ts +33 -0
  564. package/dist/teams-api.js +204 -0
  565. package/dist/teams-chat.d.ts +26 -0
  566. package/dist/teams-chat.js +215 -0
  567. package/dist/teams-settings.d.ts +12 -0
  568. package/dist/teams-settings.js +57 -0
  569. package/dist/teams.d.ts +21 -0
  570. package/dist/teams.js +124 -0
  571. package/dist/telegram-flow.d.ts +49 -0
  572. package/dist/telegram-flow.js +110 -0
  573. package/dist/telegram-mate.d.ts +137 -0
  574. package/dist/telegram-mate.js +707 -0
  575. package/dist/telegram-progress.d.ts +14 -0
  576. package/dist/telegram-progress.js +129 -0
  577. package/dist/telegram-settings.d.ts +9 -0
  578. package/dist/telegram-settings.js +36 -0
  579. package/dist/telegram-status.d.ts +75 -0
  580. package/dist/telegram-status.js +265 -0
  581. package/dist/telegram-team.d.ts +85 -0
  582. package/dist/telegram-team.js +359 -0
  583. package/dist/telegram.d.ts +230 -0
  584. package/dist/telegram.js +1931 -0
  585. package/dist/templates.d.ts +65 -0
  586. package/dist/templates.js +118 -0
  587. package/dist/tool-launcher.d.ts +1 -0
  588. package/dist/tool-launcher.js +101 -0
  589. package/dist/tools-ui.d.ts +30 -0
  590. package/dist/tools-ui.js +40 -0
  591. package/dist/transitions-recipes.d.ts +1 -0
  592. package/dist/transitions-recipes.js +203 -0
  593. package/dist/tree-proof.d.ts +34 -0
  594. package/dist/tree-proof.js +58 -0
  595. package/dist/verification-evidence.d.ts +44 -0
  596. package/dist/verification-evidence.js +238 -0
  597. package/dist/version.d.ts +2 -0
  598. package/dist/version.js +10 -0
  599. package/dist/webhooks.d.ts +91 -0
  600. package/dist/webhooks.js +283 -0
  601. package/dist/work-index.d.ts +68 -0
  602. package/dist/work-index.js +384 -0
  603. package/dist/work-summary.d.ts +57 -0
  604. package/dist/work-summary.js +116 -0
  605. package/dist/workspace-motion.d.ts +4 -0
  606. package/dist/workspace-motion.js +133 -0
  607. package/dist/workspace-revision.d.ts +19 -0
  608. package/dist/workspace-revision.js +92 -0
  609. package/dist/workspace-ui.d.ts +204 -0
  610. package/dist/workspace-ui.js +411 -0
  611. package/dist/worktree-notices.d.ts +13 -0
  612. package/dist/worktree-notices.js +17 -0
  613. package/dist/worktree.d.ts +249 -0
  614. package/dist/worktree.js +720 -0
  615. package/package.json +122 -0
  616. package/scripts/canary-assertions.mjs +42 -0
  617. package/scripts/crash-canary.mjs +253 -0
  618. package/scripts/fixtures/crash-process.mjs +74 -0
  619. package/scripts/fixtures/pilot-scenarios.mjs +85 -0
  620. package/scripts/fixtures/restart-service.mjs +27 -0
  621. package/scripts/launchd-certification.mjs +143 -0
  622. package/scripts/pilot.mjs +121 -0
  623. package/scripts/proof-preflight.mjs +140 -0
  624. package/scripts/provider-canary.mjs +379 -0
  625. package/scripts/recovery-canary.mjs +135 -0
  626. package/scripts/restart-certification.mjs +98 -0
@@ -0,0 +1,720 @@
1
+ /**
2
+ * The pool of working copies, and the leases over them.
3
+ *
4
+ * Semantics copied from treehouse rather than reinvented, including the two
5
+ * rules that are easy to get wrong and expensive to get wrong quietly:
6
+ *
7
+ * **Untracked files count as dirty.** A repository whose `.gitignore` hides
8
+ * build output is still a repository with somebody's work in it, and a pool
9
+ * that recycles a directory because `git status` looked clean under the
10
+ * operator's config will delete something they wanted. So the check is run
11
+ * with ignored files excluded but untracked files included, which is the
12
+ * conservative reading of "is anyone using this".
13
+ *
14
+ * **Reconstructed state is leased-until-verified.** A worktree found on disk
15
+ * after a crash describes what a dead process was doing, not what is true now.
16
+ * It comes back into the pool marked unverified, and something has to look at
17
+ * it before it is handed to anybody.
18
+ *
19
+ * Where the pool lives is a decision, not a detail. Worktrees are created
20
+ * outside the repository — never inside it, where they would appear as
21
+ * untracked directories in the operator's own `git status`, and never inside a
22
+ * synced folder like OneDrive or Dropbox, where a sync client racing an agent
23
+ * over `.git` internals corrupts both.
24
+ */
25
+ import { createHash, randomBytes } from "node:crypto";
26
+ import { existsSync, readFileSync, writeFileSync, rmSync, realpathSync, mkdirSync, readdirSync } from "node:fs";
27
+ import { hostname } from "node:os";
28
+ import { basename, join } from "node:path";
29
+ import { run } from "./exec.js";
30
+ import { currentBootId, provenDeadByBootChange } from "./boot-identity.js";
31
+ import { HANDOFF_PREFIX, MAILBOX_SUFFIX, readMailbox } from "./evidence.js";
32
+ import { parseHandoff } from "./decision.js";
33
+ /**
34
+ * The note a lease leaves in the checkout, naming the process holding it.
35
+ *
36
+ * Deliberately inside the worktree rather than beside it: it travels with the
37
+ * directory, and `git status` will show it as untracked, which is honest —
38
+ * something is in there.
39
+ */
40
+ export const MARKER = ".standing-orders-lease";
41
+ /** Checkouts this process is leasing or removing right now: a lease and storage retention never overlap on one. */
42
+ const busy = new Map();
43
+ /** Creating a worktree copies a tree; it is local work but not instant. */
44
+ export const WORKTREE_TIMEOUT_MS = 60_000;
45
+ const GIT = "git";
46
+ const READ_ONLY = ["--no-optional-locks"];
47
+ /**
48
+ * A path for this repository and branch that is stable across runs.
49
+ *
50
+ * Stable so that a second attempt at the same task reuses the checkout it
51
+ * already paid for, and flattened so a branch called `feat/a/b` cannot escape
52
+ * the pool root through its own slashes.
53
+ */
54
+ export function worktreePath(root, repo, branch) {
55
+ // The readable part is for a person reading `ls`; the digest is what makes
56
+ // it correct. Flattening alone collides in two ways that both end with one
57
+ // task's work landing in another's checkout: `feat/a` and `feat-a` reduce to
58
+ // the same name, and `/x/api` and `/y/api` share a basename. The digest is
59
+ // taken over the full repository path and the exact branch, so neither can.
60
+ const digest = createHash("sha256")
61
+ .update(`${normalisePath(repo)}\u0000${branch}`, "utf8")
62
+ .digest("hex")
63
+ .slice(0, 8);
64
+ return join(root, safeSegment(basenameOf(repo)), `${safeSegment(branch)}-${digest}`);
65
+ }
66
+ function normalisePath(path) {
67
+ return path.replace(/\\/g, "/").replace(/\/+$/, "");
68
+ }
69
+ function basenameOf(repo) {
70
+ const parts = repo.replace(/\\/g, "/").replace(/\/+$/, "").split("/");
71
+ return parts[parts.length - 1] ?? "repo";
72
+ }
73
+ /** Anything that is not a plain name becomes one; no separators survive. */
74
+ function safeSegment(text) {
75
+ const cleaned = text.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^[.-]+/, "");
76
+ return cleaned === "" ? "x" : cleaned;
77
+ }
78
+ /** Record a potentially writing subprocess against the original checkout lease. */
79
+ export function recordWorktreeProcess(store, path, runner, pid, leaseEpoch) {
80
+ const leased = store.getWorktree(path);
81
+ if (!Number.isInteger(pid) || pid <= 0 || leased === null || leased.releasedAt !== null || leased.runner !== runner ||
82
+ (leaseEpoch !== undefined && leased.leaseEpoch !== leaseEpoch)) {
83
+ throw new Error(`${path}: subprocess custody no longer matches its worktree lease`);
84
+ }
85
+ // The boot the holder was born under rides the note (v53): after a
86
+ // verified boot change nothing of it survives, and the checkout is free
87
+ // without a PID probe that a reused pid could answer wrongly.
88
+ const bootId = currentBootId();
89
+ writeFileSync(join(path, MARKER), `${pid} ${runner} group ${bootId ?? "unknown"} ${hostname()}\n`, "utf8");
90
+ }
91
+ /** Read-only occupancy witness shared with stop settlement. */
92
+ export function worktreeProcessOccupancy(path) {
93
+ const note = join(path, MARKER);
94
+ if (!existsSync(note))
95
+ return { held: false };
96
+ const parts = readFileSync(note, "utf8").trim().split(/\s+/);
97
+ const pid = Number(parts[0]);
98
+ if (!Number.isInteger(pid) || pid <= 0)
99
+ return { held: false };
100
+ const noteHost = parts[4];
101
+ if (noteHost !== undefined && noteHost !== hostname())
102
+ return { held: true, by: pid };
103
+ if (pid === process.pid && parts[2] !== "group")
104
+ return { held: false };
105
+ // A note from a previous, VERIFIED boot of this host names a process
106
+ // that cannot exist any more; a note without a boot id (legacy) or a
107
+ // boot this host cannot verify keeps the probe below.
108
+ const noteBoot = parts[3];
109
+ if (noteBoot !== undefined && noteHost !== undefined && provenDeadByBootChange({ host: noteHost, bootId: noteBoot }))
110
+ return { held: false };
111
+ // A shell/provider can exit before its descendants. A recorded POSIX
112
+ // process group remains an owner until that whole group has gone.
113
+ if (parts[2] === "group" && process.platform !== "win32") {
114
+ try {
115
+ process.kill(-pid, 0);
116
+ return { held: true, by: pid };
117
+ }
118
+ catch (error) {
119
+ if (error.code !== "ESRCH")
120
+ return { held: true, by: pid };
121
+ }
122
+ }
123
+ try {
124
+ process.kill(pid, 0);
125
+ return { held: true, by: pid };
126
+ }
127
+ catch (error) {
128
+ // Two different answers hide behind one throw. ESRCH means the process
129
+ // is gone and the note is what it left behind. EPERM means it is very
130
+ // much alive and simply not ours to signal — running as another user, or
131
+ // elevated — and reading that as "gone" would hand somebody's live
132
+ // checkout to another runner. Only ESRCH frees it.
133
+ const code = error.code;
134
+ return code === "ESRCH" ? { held: false } : { held: true, by: pid };
135
+ }
136
+ }
137
+ export class WorktreePool {
138
+ store;
139
+ options;
140
+ runner;
141
+ constructor(store, options) {
142
+ this.store = store;
143
+ this.options = options;
144
+ this.runner = options.runner ?? run;
145
+ }
146
+ /**
147
+ * Take a working copy for this branch.
148
+ *
149
+ * Refused when somebody else holds it — losing is ordinary, and a caller
150
+ * that is told who holds it can go and do something else rather than poll.
151
+ */
152
+ /**
153
+ * Whether a live process is working in there, independently of what the
154
+ * database believes.
155
+ *
156
+ * treehouse's rule, and it exists because the database is the thing most
157
+ * likely to be wrong: a row saying "released" written by a process that then
158
+ * kept running, or a stale row from a crash, are both cases where the
159
+ * checkout is genuinely occupied and only the machine can say so. So each
160
+ * lease drops a note naming the process holding it, and `process.kill(pid, 0)`
161
+ * — which signals nothing and only asks whether the pid exists — is what
162
+ * answers the question afterwards.
163
+ *
164
+ * A pid can be recycled by the operating system, so this can say "in use"
165
+ * about a stranger. That is the safe direction: refusing a checkout somebody
166
+ * may be in costs a retry, and taking one they are in costs their work.
167
+ */
168
+ inUse(path) {
169
+ return worktreeProcessOccupancy(path);
170
+ }
171
+ /**
172
+ * Once a provider exists it, not the supervising console, is the process
173
+ * whose liveness protects this checkout. Rewriting the occupancy marker at
174
+ * spawn means a replacement control plane waits for an orphaned provider
175
+ * to finish instead of starting a second writer in the same worktree.
176
+ */
177
+ markProviderOccupancy(path, runner, providerPid, leaseEpoch) {
178
+ if (!Number.isInteger(providerPid) || providerPid <= 0)
179
+ return false;
180
+ const leased = this.store.getWorktree(path);
181
+ if (leased === null || leased.releasedAt !== null || leased.runner !== runner)
182
+ return false;
183
+ if (leaseEpoch !== undefined && leased.leaseEpoch !== leaseEpoch)
184
+ return false;
185
+ try {
186
+ recordWorktreeProcess(this.store, path, runner, providerPid, leaseEpoch);
187
+ return true;
188
+ }
189
+ catch {
190
+ return false;
191
+ }
192
+ }
193
+ /** Spawn callbacks must fail if custody cannot be recorded. The transport
194
+ * kills and reaps its child before returning that callback failure. */
195
+ recordProviderOccupancy(path, runner, providerPid, leaseEpoch) {
196
+ if (!this.markProviderOccupancy(path, runner, providerPid, leaseEpoch)) {
197
+ throw new Error(`${path}: the provider's worktree custody could not be recorded`);
198
+ }
199
+ }
200
+ async lease(request) {
201
+ const target = worktreePath(this.options.root, request.repo, request.branch);
202
+ if (busy.get(target) === "removing")
203
+ return { ok: false, reason: "in-use", message: `${target} is being removed; try again in a moment` };
204
+ const mine = !busy.has(target);
205
+ if (mine)
206
+ busy.set(target, "leasing");
207
+ try {
208
+ return await this.leaseNow(request);
209
+ }
210
+ finally {
211
+ if (mine)
212
+ busy.delete(target);
213
+ }
214
+ }
215
+ async leaseNow(request) {
216
+ // A worktree leased to a runner nobody registered cannot be heartbeated
217
+ // and cannot be recovered — it would be a checkout that never comes back.
218
+ // The database enforces this too; catching it here is what turns a foreign
219
+ // key error into a sentence somebody can act on.
220
+ if (this.store.getRunner(request.runner) === null) {
221
+ return {
222
+ ok: false,
223
+ reason: "unknown-runner",
224
+ message: `no runner \`${request.runner}\` — register it before giving it work`,
225
+ };
226
+ }
227
+ const path = worktreePath(this.options.root, request.repo, request.branch);
228
+ const existing = this.store.getWorktree(path);
229
+ // The machine outranks the database here. A row can say "released" while a
230
+ // process that never got to write its own ending is still working in the
231
+ // directory, and that is exactly when taking it away costs somebody a
232
+ // night's work.
233
+ const occupied = this.inUse(path);
234
+ if (occupied.held) {
235
+ return {
236
+ ok: false,
237
+ reason: "in-use",
238
+ message: `${path} is being used by process ${occupied.by} — leaving it alone`,
239
+ };
240
+ }
241
+ if (existing !== null && existing.releasedAt === null && existing.runner !== request.runner) {
242
+ return {
243
+ ok: false,
244
+ reason: "held",
245
+ message: `${path} is leased to ${existing.runner ?? "someone"}`,
246
+ };
247
+ }
248
+ // Re-inspect every released checkout at the lease boundary. `verified`
249
+ // says it was clean when released, not that no orphan/provider/person
250
+ // wrote to it afterwards; trusting that old bit is how a restored safety
251
+ // patch can be handed out without recovery lineage or silently reset.
252
+ let reclaimed;
253
+ let resumedFromRun;
254
+ let recoveryKind;
255
+ if (existing !== null && existing.releasedAt !== null) {
256
+ const dirty = await this.isDirty(path);
257
+ if (dirty === null) {
258
+ return { ok: false, reason: "git", message: `${path} could not be inspected` };
259
+ }
260
+ if (dirty) {
261
+ // The task's own leftover, from an attempt that finished and said
262
+ // so: kept as evidence, then cleared, so the retry starts from the
263
+ // branch and not from a half-edit. Anything else stays for a person.
264
+ const own = request.reclaim !== undefined && request.taskRef !== undefined && existing.taskRef === request.taskRef;
265
+ if (!own) {
266
+ return {
267
+ ok: false,
268
+ reason: "dirty",
269
+ message: `${path} has uncommitted or untracked work from a previous run — look before reusing it`,
270
+ };
271
+ }
272
+ const previousRun = this.store.latestRunInWorktree(path);
273
+ const recoveryRun = this.store.latestRecoverableRunInWorktree(path);
274
+ const recovery = recoveryRun === null ? null : this.recoverableDraft(path, recoveryRun);
275
+ const kept = await this.keepLeftover(path, request.reclaim.evidenceRoot, request.now);
276
+ if (!kept.ok)
277
+ return { ok: false, reason: "git", message: kept.message };
278
+ reclaimed = kept.file;
279
+ if (recovery !== null) {
280
+ // The predecessor reached a structurally valid completed handoff
281
+ // before its runner disappeared. Keep its draft in place under the
282
+ // fresh lease so the successor reviews and verifies it. build()
283
+ // quarantines the old nonce-bound handoff before the new provider
284
+ // starts, so only the source draft crosses attempts — never the old
285
+ // attempt's authority.
286
+ resumedFromRun = recovery.runId;
287
+ recoveryKind = recovery.kind;
288
+ }
289
+ else {
290
+ const reset = await this.resetTree(path);
291
+ if (!reset.ok)
292
+ return { ok: false, reason: "git", message: reset.message };
293
+ }
294
+ }
295
+ }
296
+ const created = existing === null;
297
+ if (created) {
298
+ // A branch that already exists (its checkout removed by storage retention) is checked out as it stands, just
299
+ // as its kept checkout would have been reused; `base` only starts a branch that doesn't exist yet.
300
+ const branchExists = request.base !== undefined && request.reuseBranch === true &&
301
+ (await this.git(request.repo, ["rev-parse", "--verify", "--quiet", `refs/heads/${request.branch}`], READ_ONLY)).code === 0;
302
+ const fromBase = request.base !== undefined && !branchExists;
303
+ const add = await this.git(request.repo, [
304
+ "worktree",
305
+ "add",
306
+ ...(fromBase ? ["-b", request.branch] : []),
307
+ path,
308
+ ...(fromBase ? [request.base] : [request.branch]),
309
+ ]);
310
+ if (add.code !== 0) {
311
+ return { ok: false, reason: "git", message: firstLine(add.stderr) };
312
+ }
313
+ }
314
+ const row = {
315
+ path,
316
+ repo: request.repo,
317
+ branch: request.branch,
318
+ runner: request.runner,
319
+ taskRef: request.taskRef ?? null,
320
+ createdAt: existing?.createdAt ?? request.now.toISOString(),
321
+ leasedAt: request.now.toISOString(),
322
+ releasedAt: null,
323
+ // Freshly created, or inspected just above — either way, checked.
324
+ verified: true,
325
+ // The occupancy epoch (live-peek findings 16/28): fresh randomness,
326
+ // written by the SAME row write that grants the lease — it can never
327
+ // repeat and never lag the occupancy it names.
328
+ leaseEpoch: randomBytes(12).toString("hex"),
329
+ };
330
+ // The note goes down before the lease is granted. A checkout we cannot
331
+ // mark is one whose next holder cannot tell it is occupied — the in-use
332
+ // check would report it free while a process worked in it — so failing to
333
+ // write the note fails the lease rather than quietly leaving the gap.
334
+ if (!this.mark(path, request.runner)) {
335
+ return {
336
+ ok: false,
337
+ reason: "git",
338
+ message: `${path} could not be marked as in use, so it is not safe to hand out`,
339
+ };
340
+ }
341
+ this.store.saveWorktree(row);
342
+ return {
343
+ ok: true,
344
+ worktree: row,
345
+ created,
346
+ ...(reclaimed === undefined ? {} : { reclaimed }),
347
+ ...(resumedFromRun === undefined ? {} : { resumedFromRun }),
348
+ ...(recoveryKind === undefined ? {} : { recoveryKind }),
349
+ };
350
+ }
351
+ /**
352
+ * A crash can strand either a finished handoff or a partial source draft.
353
+ * The expired lease can no longer authorize a commit, so this never ingests
354
+ * the old result. It classifies the recovery context for the next freshly
355
+ * fenced attempt, which must review and prove the inherited work itself.
356
+ */
357
+ recoverableDraft(path, runId) {
358
+ const prior = this.store.getRun(runId);
359
+ if (prior === null)
360
+ return null;
361
+ // Once a fresh attempt has explicitly inherited a validated draft, keep
362
+ // that lineage through an infrastructure/provider failure too. A warm
363
+ // park resume also uses parentRun, but its parent is `parked`, never the
364
+ // failed/interrupted source required here.
365
+ if (prior.parentRun !== null) {
366
+ const source = this.store.getRun(prior.parentRun);
367
+ if (source?.outcome === "failed" && source.reason === "interrupted") {
368
+ return { runId: source.id, kind: "partial" };
369
+ }
370
+ }
371
+ if (prior.outcome !== null && (prior.outcome !== "failed" || prior.reason !== "interrupted"))
372
+ return null;
373
+ let names;
374
+ try {
375
+ names = readdirSync(path).filter(name => name.startsWith(HANDOFF_PREFIX) && name.endsWith(MAILBOX_SUFFIX));
376
+ }
377
+ catch {
378
+ return { runId, kind: "partial" };
379
+ }
380
+ if (names.length !== 1)
381
+ return { runId, kind: "partial" };
382
+ const read = readMailbox(join(path, names[0]));
383
+ if (!read.ok)
384
+ return { runId, kind: "partial" };
385
+ const parsed = parseHandoff(read.raw.toString("utf8"));
386
+ return {
387
+ runId,
388
+ kind: parsed.ok && parsed.handoff.status === "completed" ? "completed" : "partial",
389
+ };
390
+ }
391
+ /**
392
+ * Preserve a dirty tree's work as one patch — tracked changes against
393
+ * HEAD, then each untracked file against nothing — under the evidence of
394
+ * the last run that lived in this checkout (or a leftover folder when no
395
+ * run is on record). Nothing is deleted here.
396
+ */
397
+ async keepLeftover(path, evidenceRoot, now) {
398
+ const status = await this.runner(GIT, [...READ_ONLY, "status", "--porcelain", "--untracked-files=all"], { cwd: path, timeoutMs: WORKTREE_TIMEOUT_MS });
399
+ if (status.code !== 0)
400
+ return { ok: false, message: `${path} could not be inspected before reclaiming it` };
401
+ const untracked = status.stdout
402
+ .split("\n")
403
+ .filter(line => line.startsWith("?? ") && !line.trimEnd().endsWith(MARKER))
404
+ .map(line => line.slice(3).trim());
405
+ const tracked = await this.runner(GIT, [...READ_ONLY, "diff", "--binary", "HEAD"], { cwd: path, timeoutMs: WORKTREE_TIMEOUT_MS });
406
+ if (tracked.code !== 0)
407
+ return { ok: false, message: `${path}: git diff failed while reclaiming (${firstLine(tracked.stderr)})` };
408
+ const parts = [tracked.stdout];
409
+ for (const file of untracked) {
410
+ // --no-index exits 1 when the sides differ; that is the expected answer.
411
+ const one = await this.runner(GIT, [...READ_ONLY, "diff", "--binary", "--no-index", "--", "/dev/null", file], { cwd: path, timeoutMs: WORKTREE_TIMEOUT_MS });
412
+ if (one.code > 1)
413
+ return { ok: false, message: `${path}: ${file} could not be captured (${firstLine(one.stderr)})` };
414
+ parts.push(one.stdout);
415
+ }
416
+ const lastRun = this.store.latestRunInWorktree(path);
417
+ const dir = lastRun === null ? join(evidenceRoot, "leftover") : join(evidenceRoot, String(lastRun));
418
+ const file = join(dir, lastRun === null ? `${now.toISOString().replace(/[:.]/g, "-")}.patch` : "leftover.patch");
419
+ try {
420
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
421
+ writeFileSync(file, `# leftover from ${path}\n# kept ${now.toISOString()} before the tree was reset for the next attempt\n` + parts.join(""), { mode: 0o600 });
422
+ }
423
+ catch (error) {
424
+ return { ok: false, message: `${path}: the leftover could not be written to ${file} (${error instanceof Error ? error.message : String(error)})` };
425
+ }
426
+ return { ok: true, file };
427
+ }
428
+ /** Back to HEAD, untracked gone, our lease note kept; proven clean after. */
429
+ async resetTree(path) {
430
+ const checkout = await this.runner(GIT, ["checkout", "--", "."], { cwd: path, timeoutMs: WORKTREE_TIMEOUT_MS });
431
+ if (checkout.code !== 0)
432
+ return { ok: false, message: `${path}: git checkout failed while resetting (${firstLine(checkout.stderr)})` };
433
+ const clean = await this.runner(GIT, ["clean", "-fd", "-e", MARKER], { cwd: path, timeoutMs: WORKTREE_TIMEOUT_MS });
434
+ if (clean.code !== 0)
435
+ return { ok: false, message: `${path}: git clean failed while resetting (${firstLine(clean.stderr)})` };
436
+ const dirty = await this.isDirty(path);
437
+ if (dirty !== false)
438
+ return { ok: false, message: `${path} is still not clean after the reset — leaving it for a person` };
439
+ return { ok: true };
440
+ }
441
+ /** Leave a note naming the process holding this checkout. */
442
+ mark(path, runner, pid = process.pid) {
443
+ try {
444
+ writeFileSync(join(path, MARKER), `${pid} ${runner} process ${currentBootId() ?? "unknown"} ${hostname()}\n`, "utf8");
445
+ return true;
446
+ }
447
+ catch {
448
+ return false;
449
+ }
450
+ }
451
+ /**
452
+ * Hand a working copy back.
453
+ *
454
+ * The tree is inspected first, and dirt is reported rather than cleaned.
455
+ * `git reset --hard` leaves untracked files behind and destroys work that
456
+ * might have been repairable, which is why the failure taxonomy this project
457
+ * borrows says to preserve a failed commit rather than blanket-reset.
458
+ */
459
+ async release(path, now) {
460
+ const existing = this.store.getWorktree(path);
461
+ if (existing === null) {
462
+ return { ok: false, reason: "git", message: `${path} is not a worktree we made` };
463
+ }
464
+ const dirty = await this.isDirty(path);
465
+ // The note goes before the row is written: a crash between the two should
466
+ // leave a checkout that looks free rather than one held by a pid that has
467
+ // gone, which is the direction that needs no human to unstick it.
468
+ try {
469
+ rmSync(join(path, MARKER), { force: true });
470
+ }
471
+ catch {
472
+ // Nothing to do about it, and the pid check handles a note left behind.
473
+ }
474
+ const row = {
475
+ ...existing,
476
+ runner: null,
477
+ releasedAt: now.toISOString(),
478
+ // Unknown counts as unverified. Saying "clean" about a tree we could not
479
+ // read would hand the next runner a surprise.
480
+ verified: dirty === false,
481
+ // Occupancy ended: the epoch rotates HERE too, so a peek proved
482
+ // against the tenancy that just ended can only discard (finding 28).
483
+ leaseEpoch: randomBytes(12).toString("hex"),
484
+ };
485
+ this.store.saveWorktree(row);
486
+ if (dirty === null) {
487
+ return { ok: false, reason: "git", message: `${path} could not be inspected on release` };
488
+ }
489
+ if (dirty) {
490
+ return {
491
+ ok: false,
492
+ reason: "dirty",
493
+ message: `${path} still has uncommitted or untracked work — it is kept, not cleaned`,
494
+ };
495
+ }
496
+ return { ok: true, worktree: row, created: false };
497
+ }
498
+ /**
499
+ * Worktrees git knows about that this pool does not, and the reverse.
500
+ *
501
+ * The first is somebody else's business and is left alone. The second is
502
+ * ours: a row for a directory that is no longer there, which happens when a
503
+ * machine is reimaged or a pool root is deleted between runs.
504
+ */
505
+ /**
506
+ * Remove a released checkout and its branch (v4 review, finding 3): the
507
+ * read-only roles' workspaces are disposable, so the next attempt starts
508
+ * from the requested base instead of whatever the last one saw. Refused
509
+ * while a process holds the directory; a failure leaves the checkout
510
+ * where `worktrees` can see it rather than pretending it is gone.
511
+ */
512
+ async discard(path, now) {
513
+ const existing = this.store.getWorktree(path);
514
+ if (existing === null)
515
+ return { ok: false, message: `${path} is not a worktree we made` };
516
+ if (existing.releasedAt === null)
517
+ return { ok: false, message: `${path} is still leased — release it first` };
518
+ const occupied = this.inUse(path);
519
+ if (occupied.held)
520
+ return { ok: false, message: `${path} is being used by process ${occupied.by} — leaving it alone` };
521
+ const removed = await this.git(existing.repo, ["worktree", "remove", "--force", path]);
522
+ if (removed.code !== 0)
523
+ return { ok: false, message: `git worktree remove: ${removed.stderr.trim() || `exit ${removed.code}`}` };
524
+ // The branch is disposable too; a failure here is bookkeeping, not custody.
525
+ await this.git(existing.repo, ["branch", "-D", existing.branch]);
526
+ this.store.forgetWorktree(path);
527
+ void now;
528
+ return { ok: true };
529
+ }
530
+ async orphans(repo) {
531
+ const listed = await this.git(repo, ["worktree", "list", "--porcelain"], READ_ONLY);
532
+ // A listing that failed is not a listing that came back empty. Treating
533
+ // it as empty would report every stored row as missing — and anything
534
+ // acting on that answer would erase real bookkeeping over a git hiccup.
535
+ if (listed.code !== 0) {
536
+ return {
537
+ ok: false,
538
+ message: `git could not list ${repo}'s worktrees — ${listed.stderr.trim() || `exit ${listed.code}`}`,
539
+ };
540
+ }
541
+ const onDisk = new Set(listed.stdout
542
+ .split("\n")
543
+ .filter(line => line.startsWith("worktree "))
544
+ .map(line => line.slice("worktree ".length).trim()));
545
+ const ours = this.store.listWorktrees().filter(row => row.repo === repo);
546
+ const known = new Set(ours.map(row => row.path));
547
+ return {
548
+ ok: true,
549
+ untracked: [...onDisk].filter(path => !known.has(path) && path !== repo),
550
+ missing: ours.filter(row => !onDisk.has(row.path)).map(row => row.path),
551
+ };
552
+ }
553
+ /**
554
+ * Take responsibility for worktrees that exist but nothing recorded.
555
+ *
556
+ * These turn up after a crash between `git worktree add` and the row that
557
+ * should have followed it, or when a database is replaced while checkouts
558
+ * survive on disk. Left alone they are invisible: git knows about them,
559
+ * every pool query does not, and the same branch gets checked out again
560
+ * beside them.
561
+ *
562
+ * Adopted **released and unverified**, never leased. Nobody watched them
563
+ * being made, so they are a claim about the past — the same rule that
564
+ * governs a dead runner's worktrees, for the same reason.
565
+ *
566
+ * Rows whose directory is gone are dropped: a lease over a path that does
567
+ * not exist can only refuse work that could otherwise have run.
568
+ */
569
+ async adopt(repo, now) {
570
+ const found = await this.orphans(repo);
571
+ // Fail closed, before anything is written or forgotten: an answer built
572
+ // on a failed listing would adopt nothing and erase everything.
573
+ if (!found.ok)
574
+ return found;
575
+ // Only what lives under this pool's root is ours to adopt. A worktree the
576
+ // operator made by hand, wherever they made it, is somebody's business —
577
+ // it is named by `orphans()` and left exactly where it is. Compared as
578
+ // real paths, because git reports where a directory actually is while the
579
+ // configured root may reach it through a symlink (macOS's /var, for one).
580
+ const real = (path) => {
581
+ try {
582
+ return realpathSync(path);
583
+ }
584
+ catch {
585
+ return path;
586
+ }
587
+ };
588
+ const root = normalisePath(real(this.options.root)) + "/";
589
+ const adoptable = found.untracked.filter(path => normalisePath(real(path)).startsWith(root));
590
+ // Git's listing is a survey, not permission to overwrite custody. A
591
+ // builder may create and record a worktree while the listing is in
592
+ // flight. Recheck under the writer transaction, and never forget an
593
+ // owned checkout or a directory that appeared after the listing.
594
+ return this.store.transact(() => {
595
+ const adopted = [];
596
+ const forgotten = [];
597
+ for (const path of adoptable) {
598
+ if (this.store.getWorktree(path) !== null || this.inUse(path).held)
599
+ continue;
600
+ this.store.saveWorktree({
601
+ path,
602
+ repo,
603
+ // The branch is not knowable from the listing alone, and inventing one
604
+ // would be worse than admitting it: whoever verifies this will look.
605
+ branch: "unknown",
606
+ runner: null,
607
+ taskRef: null,
608
+ createdAt: now.toISOString(),
609
+ leasedAt: null,
610
+ releasedAt: now.toISOString(),
611
+ verified: false,
612
+ });
613
+ adopted.push(path);
614
+ }
615
+ for (const path of found.missing) {
616
+ const current = this.store.getWorktree(path);
617
+ if (current === null || current.repo !== repo || (current.runner !== null && current.releasedAt === null) || existsSync(path))
618
+ continue;
619
+ this.store.forgetWorktree(path);
620
+ forgotten.push(path);
621
+ }
622
+ return { ok: true, adopted, forgotten };
623
+ });
624
+ }
625
+ /**
626
+ * Keep storage in check: remove the working copies nobody needs any more.
627
+ * A checkout goes when it was released `keepMs` ago or longer, nothing
628
+ * holds it, it is clean (build output that .gitignore hides is not work),
629
+ * everything its HEAD reaches is on a branch, tag or remote, and it isn't the
630
+ * checkout of one of `keep()`'s branches (those unfinished tasks work on or
631
+ * would come back to, read afresh before each removal), by its branch or by
632
+ * the name a lease of that branch would give it under any root. Only the
633
+ * working copy goes: its branch, and so every commit, stays, and a later
634
+ * lease of the same branch makes a new one. At most `max` go per pass.
635
+ */
636
+ async prune(repo, now, keepMs, keep, max = 20) {
637
+ const removed = [];
638
+ const kept = [];
639
+ const idle = (row, releasedAt) => row !== null && row.runner === null && row.releasedAt === releasedAt && !this.inUse(row.path).held;
640
+ const kept_ = (branches, row) => branches.includes(row.branch) || branches.some(branch => basename(worktreePath("/", repo, branch)) === basename(row.path));
641
+ for (const row of this.store.listWorktrees().filter(one => one.repo === repo)) {
642
+ if (removed.length >= max)
643
+ break;
644
+ if (row.releasedAt === null || row.runner !== null || busy.has(row.path) || kept_(keep(), row))
645
+ continue;
646
+ if (now.getTime() - Date.parse(row.releasedAt) < keepMs || !existsSync(row.path) || !idle(row, row.releasedAt))
647
+ continue;
648
+ const dirty = await this.isDirty(row.path);
649
+ if (dirty !== false) {
650
+ kept.push({ path: row.path, why: dirty === null ? "unreadable" : "has changes" });
651
+ continue;
652
+ }
653
+ // Commits only this checkout's HEAD reaches (a detached HEAD moved on) exist nowhere else.
654
+ const own = await this.runner(GIT, [...READ_ONLY, "rev-list", "-1", "HEAD", "--not", "--branches", "--tags", "--remotes"], { cwd: row.path, timeoutMs: WORKTREE_TIMEOUT_MS });
655
+ if (own.code !== 0 || own.stdout.trim() !== "") {
656
+ kept.push({ path: row.path, why: own.code !== 0 ? "unreadable" : "has commits" });
657
+ continue;
658
+ }
659
+ if (busy.has(row.path))
660
+ continue;
661
+ busy.set(row.path, "removing");
662
+ try {
663
+ // A lease, or a task coming back, since the listing wins: look again right before removing.
664
+ if (!idle(this.store.getWorktree(row.path), row.releasedAt) || kept_(keep(), row))
665
+ continue;
666
+ // Held while it goes: another process's lease sees this process in it (the busy map is this process's only).
667
+ const note = join(row.path, MARKER);
668
+ try {
669
+ writeFileSync(note, `${process.pid} storage-retention removing ${currentBootId() ?? "unknown"} ${hostname()}\n`, "utf8");
670
+ }
671
+ catch {
672
+ continue;
673
+ }
674
+ const gone = await this.git(repo, ["worktree", "remove", "--force", row.path]);
675
+ if (gone.code !== 0) {
676
+ rmSync(note, { force: true });
677
+ kept.push({ path: row.path, why: "git refused" });
678
+ continue;
679
+ }
680
+ this.store.transact(() => {
681
+ const still = this.store.getWorktree(row.path);
682
+ if (still !== null && still.runner === null && still.releasedAt === row.releasedAt)
683
+ this.store.forgetWorktree(row.path);
684
+ });
685
+ removed.push(row);
686
+ }
687
+ finally {
688
+ busy.delete(row.path);
689
+ }
690
+ }
691
+ return { removed, kept };
692
+ }
693
+ /**
694
+ * Whether anybody's work is in there. null means we could not tell, which is
695
+ * neither clean nor dirty and must not be rounded to either.
696
+ *
697
+ * `--untracked-files=all` and no `--ignored`: build output that .gitignore
698
+ * hides is not work, but a file somebody dropped in and never staged is.
699
+ */
700
+ async isDirty(path) {
701
+ const status = await this.runner(GIT, [...READ_ONLY, "status", "--porcelain", "--untracked-files=all"], { cwd: path, timeoutMs: WORKTREE_TIMEOUT_MS });
702
+ if (status.code !== 0)
703
+ return null;
704
+ // Our own lease note is not the operator's work. Without this the marker
705
+ // makes every checkout permanently dirty, which would jam the pool shut on
706
+ // the first lease — untracked files counting as dirty is the right rule,
707
+ // and this is the one file it must not apply to.
708
+ return status.stdout
709
+ .split("\n")
710
+ .filter(line => line.trim() !== "")
711
+ .some(line => !line.trimEnd().endsWith(MARKER));
712
+ }
713
+ git(cwd, args, prefix = []) {
714
+ return this.runner(GIT, [...prefix, ...args], { cwd, timeoutMs: WORKTREE_TIMEOUT_MS });
715
+ }
716
+ }
717
+ function firstLine(text) {
718
+ const [line = ""] = text.trim().split("\n");
719
+ return line;
720
+ }