toolroll 0.9.93 → 0.9.95

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 (436) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/dist/activity-line.d.ts +7 -4
  3. package/dist/activity-line.js +9 -7
  4. package/dist/agent-fence.d.ts +10 -0
  5. package/dist/agent-fence.js +6 -3
  6. package/dist/approval-authority.d.ts +7 -1
  7. package/dist/approval-authority.js +11 -2
  8. package/dist/approval-password.d.ts +31 -0
  9. package/dist/approval-password.js +26 -0
  10. package/dist/assignment-status.d.ts +1 -1
  11. package/dist/assignment-ui.js +3 -1
  12. package/dist/assignment.js +5 -0
  13. package/dist/attest.d.ts +3 -0
  14. package/dist/attest.js +10 -0
  15. package/dist/boot-identity.d.ts +2 -1
  16. package/dist/boot-identity.js +6 -4
  17. package/dist/bounded-spawn.d.ts +57 -0
  18. package/dist/bounded-spawn.js +220 -0
  19. package/dist/browser/terminal.js +42 -42
  20. package/dist/browser/workspace.css +1 -1
  21. package/dist/browser/workspace.js +32 -32
  22. package/dist/browser-crew.js +2 -0
  23. package/dist/browser-shell.d.ts +1 -1
  24. package/dist/browser-shell.js +2 -2
  25. package/dist/browser-workspace.d.ts +50 -2
  26. package/dist/browser-workspace.js +3 -3
  27. package/dist/build-review.js +2 -0
  28. package/dist/builder-restart.d.ts +21 -0
  29. package/dist/builder-restart.js +50 -0
  30. package/dist/builder.d.ts +11 -2
  31. package/dist/builder.js +234 -101
  32. package/dist/card-catalog.d.ts +209 -0
  33. package/dist/card-catalog.js +133 -0
  34. package/dist/card-cli.d.ts +43 -0
  35. package/dist/card-cli.js +44 -0
  36. package/dist/card-render.d.ts +26 -0
  37. package/dist/card-render.js +223 -0
  38. package/dist/card-sources.d.ts +68 -0
  39. package/dist/card-sources.js +234 -0
  40. package/dist/chat-acceptance.js +1 -1
  41. package/dist/chat-actions.d.ts +106 -9
  42. package/dist/chat-actions.js +473 -46
  43. package/dist/chat-approval.d.ts +13 -0
  44. package/dist/chat-approval.js +39 -9
  45. package/dist/chat-card-part.d.ts +11 -0
  46. package/dist/chat-card-part.js +18 -0
  47. package/dist/chat-card.d.ts +46 -0
  48. package/dist/chat-card.js +99 -0
  49. package/dist/chat-channel.d.ts +2 -0
  50. package/dist/chat-channel.js +37 -14
  51. package/dist/chat-controls.d.ts +4 -0
  52. package/dist/chat-controls.js +4 -0
  53. package/dist/chat-decide.d.ts +10 -16
  54. package/dist/chat-decide.js +79 -62
  55. package/dist/chat-delivery.js +13 -4
  56. package/dist/chat-flow.d.ts +0 -27
  57. package/dist/chat-flow.js +31 -2
  58. package/dist/chat-quiet.js +4 -4
  59. package/dist/chat-task-actions.d.ts +3 -2
  60. package/dist/chat-task-actions.js +19 -5
  61. package/dist/chat-terms.d.ts +14 -2
  62. package/dist/chat-terms.js +35 -0
  63. package/dist/chat-voice.d.ts +1 -1
  64. package/dist/chat-voice.js +2 -2
  65. package/dist/check-facts.d.ts +3 -1
  66. package/dist/check-facts.js +57 -11
  67. package/dist/check-failure.d.ts +24 -3
  68. package/dist/check-failure.js +89 -13
  69. package/dist/check-levels-ui.d.ts +4 -0
  70. package/dist/check-levels-ui.js +10 -1
  71. package/dist/check-load.d.ts +85 -0
  72. package/dist/check-load.js +147 -0
  73. package/dist/check-only-standing.d.ts +36 -0
  74. package/dist/check-only-standing.js +39 -0
  75. package/dist/check-only.d.ts +15 -0
  76. package/dist/check-only.js +99 -0
  77. package/dist/check-repair.d.ts +47 -0
  78. package/dist/check-repair.js +240 -0
  79. package/dist/check-report.d.ts +2 -0
  80. package/dist/check-report.js +1 -1
  81. package/dist/check-stamp.d.ts +46 -0
  82. package/dist/check-stamp.js +93 -0
  83. package/dist/checkout-cleanup.d.ts +36 -5
  84. package/dist/checkout-cleanup.js +166 -14
  85. package/dist/claim.d.ts +6 -0
  86. package/dist/claim.js +13 -2
  87. package/dist/cli-help.d.ts +4 -1
  88. package/dist/cli-help.js +49 -30
  89. package/dist/cli.js +15 -1
  90. package/dist/code-extract.d.ts +44 -0
  91. package/dist/code-extract.js +88 -0
  92. package/dist/code-map-acts.d.ts +24 -0
  93. package/dist/code-map-acts.js +53 -0
  94. package/dist/code-map-admission.d.ts +52 -0
  95. package/dist/code-map-admission.js +295 -0
  96. package/dist/code-map-build.d.ts +63 -0
  97. package/dist/code-map-build.js +415 -0
  98. package/dist/code-map-cli.d.ts +153 -0
  99. package/dist/code-map-cli.js +103 -0
  100. package/dist/code-map-main.d.ts +1 -0
  101. package/dist/code-map-main.js +48 -0
  102. package/dist/code-map-read.d.ts +106 -0
  103. package/dist/code-map-read.js +171 -0
  104. package/dist/code-map-report.d.ts +37 -0
  105. package/dist/code-map-report.js +95 -0
  106. package/dist/code-map-shape.d.ts +172 -0
  107. package/dist/code-map-shape.js +327 -0
  108. package/dist/code-map-store.d.ts +136 -0
  109. package/dist/code-map-store.js +233 -0
  110. package/dist/coding-provider.d.ts +3 -1
  111. package/dist/coding-provider.js +12 -7
  112. package/dist/coding-update.js +3 -2
  113. package/dist/coding-workspace.d.ts +1 -1
  114. package/dist/coding-workspace.js +8 -8
  115. package/dist/console-chrome.js +2 -2
  116. package/dist/containment.d.ts +5 -0
  117. package/dist/containment.js +14 -1
  118. package/dist/contracts/chat-actions.d.ts +92 -0
  119. package/dist/contracts/chat-actions.js +20 -0
  120. package/dist/contracts/chat-card.d.ts +91 -0
  121. package/dist/contracts/chat-card.js +54 -0
  122. package/dist/contracts/chat-content.d.ts +191 -0
  123. package/dist/contracts/chat-content.js +4 -0
  124. package/dist/contracts/cli.d.ts +52 -0
  125. package/dist/contracts/cli.js +4 -0
  126. package/dist/contracts/console-api.d.ts +12 -3
  127. package/dist/contracts/console-api.js +12 -3
  128. package/dist/contracts/flow-propose.d.ts +8 -1
  129. package/dist/contracts/flow-propose.js +3 -1
  130. package/dist/contracts/flow-send.d.ts +126 -0
  131. package/dist/contracts/flow-send.js +3 -0
  132. package/dist/contracts/gateway-tools.d.ts +17 -0
  133. package/dist/contracts/gateway-tools.js +3 -1
  134. package/dist/contracts/lead-tools.d.ts +234 -0
  135. package/dist/contracts/lead-tools.js +25 -1
  136. package/dist/contracts/project-knowledge.d.ts +38 -5
  137. package/dist/contracts/project-knowledge.js +11 -5
  138. package/dist/contracts/project-memory.d.ts +3 -1
  139. package/dist/contracts/project-memory.js +3 -2
  140. package/dist/contracts/storage.d.ts +14 -0
  141. package/dist/contracts/storage.js +3 -1
  142. package/dist/contracts/store-json.d.ts +18 -0
  143. package/dist/contracts/youiyoux-request.d.ts +1 -1
  144. package/dist/contracts/youiyoux-request.js +1 -1
  145. package/dist/desktop-access.js +6 -2
  146. package/dist/discord-chat.js +14 -0
  147. package/dist/dispatch.d.ts +1 -1
  148. package/dist/dispatch.js +25 -16
  149. package/dist/dispose.d.ts +19 -0
  150. package/dist/dispose.js +55 -9
  151. package/dist/easy-start.js +2 -3
  152. package/dist/eval-oracle.js +4 -1
  153. package/dist/evals.js +2 -1
  154. package/dist/evidence-pair.d.ts +53 -0
  155. package/dist/evidence-pair.js +128 -0
  156. package/dist/exhaustion.d.ts +17 -4
  157. package/dist/exhaustion.js +44 -4
  158. package/dist/export.js +1 -1
  159. package/dist/failing-first.d.ts +31 -0
  160. package/dist/failing-first.js +44 -0
  161. package/dist/fallback-schema.d.ts +8 -0
  162. package/dist/fallback-schema.js +40 -0
  163. package/dist/fallback.d.ts +138 -0
  164. package/dist/fallback.js +605 -0
  165. package/dist/fast-start.d.ts +136 -0
  166. package/dist/fast-start.js +431 -0
  167. package/dist/fast-terms.d.ts +9 -0
  168. package/dist/fast-terms.js +23 -0
  169. package/dist/fenced-check-coverage.d.ts +62 -0
  170. package/dist/fenced-check-coverage.js +54 -0
  171. package/dist/findings.d.ts +224 -0
  172. package/dist/findings.js +449 -0
  173. package/dist/first-run.js +1 -1
  174. package/dist/flaky-ci.d.ts +31 -0
  175. package/dist/flaky-ci.js +149 -0
  176. package/dist/flaky-cli.d.ts +60 -0
  177. package/dist/flaky-cli.js +60 -0
  178. package/dist/flaky.d.ts +75 -9
  179. package/dist/flaky.js +255 -0
  180. package/dist/fleet-schema.d.ts +17 -0
  181. package/dist/fleet-schema.js +135 -0
  182. package/dist/flow-browser.js +2 -2
  183. package/dist/flow-card-task.js +3 -0
  184. package/dist/flow-code.d.ts +2 -2
  185. package/dist/flow-code.js +9 -5
  186. package/dist/flow-result-card.d.ts +22 -0
  187. package/dist/flow-result-card.js +55 -0
  188. package/dist/flow-send.js +12 -9
  189. package/dist/flow-steps.js +1 -1
  190. package/dist/flow-triggers.d.ts +1 -1
  191. package/dist/flow-triggers.js +2 -2
  192. package/dist/flow-unattended.d.ts +26 -0
  193. package/dist/flow-unattended.js +12 -3
  194. package/dist/flows-ui.js +10 -2
  195. package/dist/follow-through-brief.d.ts +12 -0
  196. package/dist/follow-through-brief.js +31 -0
  197. package/dist/follow-through-cli.d.ts +23 -0
  198. package/dist/follow-through-cli.js +153 -0
  199. package/dist/git-incident.d.ts +16 -0
  200. package/dist/git-incident.js +69 -6
  201. package/dist/github-import.d.ts +62 -0
  202. package/dist/github-import.js +146 -0
  203. package/dist/guides.js +15 -10
  204. package/dist/handover-note.d.ts +33 -0
  205. package/dist/handover-note.js +106 -0
  206. package/dist/invoke.d.ts +25 -2
  207. package/dist/invoke.js +157 -58
  208. package/dist/job-object-helper.ps1 +10 -3
  209. package/dist/keymap.d.ts +41 -0
  210. package/dist/keymap.js +51 -0
  211. package/dist/knowledge-cli.js +1 -1
  212. package/dist/knowledge-ui.d.ts +21 -4
  213. package/dist/knowledge-ui.js +75 -7
  214. package/dist/laptop-background.d.ts +16 -0
  215. package/dist/laptop-background.js +108 -2
  216. package/dist/lead-cli.js +26 -24
  217. package/dist/lead-context.js +1 -1
  218. package/dist/lead-contract.d.ts +1 -1
  219. package/dist/lead-contract.js +3 -2
  220. package/dist/lead-doors.d.ts +4 -0
  221. package/dist/lead-doors.js +2 -2
  222. package/dist/lead-progress.js +5 -0
  223. package/dist/lead-status.d.ts +2 -0
  224. package/dist/lead-status.js +6 -2
  225. package/dist/lead-tools.js +162 -34
  226. package/dist/lead.js +2 -2
  227. package/dist/lesson-notes.d.ts +81 -0
  228. package/dist/lesson-notes.js +336 -0
  229. package/dist/limits-ui.js +1 -1
  230. package/dist/local-owner.d.ts +1 -1
  231. package/dist/local-owner.js +16 -0
  232. package/dist/machine-check.d.ts +16 -0
  233. package/dist/machine-check.js +26 -0
  234. package/dist/machine-identity.d.ts +75 -0
  235. package/dist/machine-identity.js +170 -0
  236. package/dist/mcp-core.js +11 -1
  237. package/dist/memory-cli.d.ts +300 -1
  238. package/dist/memory-cli.js +62 -9
  239. package/dist/memory-pass.d.ts +16 -0
  240. package/dist/memory-pass.js +67 -3
  241. package/dist/memory-tidy.d.ts +181 -0
  242. package/dist/memory-tidy.js +523 -0
  243. package/dist/memory-use.d.ts +35 -0
  244. package/dist/memory-use.js +109 -0
  245. package/dist/merge-checklist.d.ts +55 -0
  246. package/dist/merge-checklist.js +182 -0
  247. package/dist/modes.d.ts +6 -6
  248. package/dist/modes.js +3 -1
  249. package/dist/needs-you.d.ts +31 -9
  250. package/dist/needs-you.js +110 -41
  251. package/dist/nightly-github.d.ts +2 -0
  252. package/dist/nightly-github.js +4 -4
  253. package/dist/operate-remote-arguments.js +7 -1
  254. package/dist/operate-remote.js +3 -1
  255. package/dist/operate.d.ts +6 -1
  256. package/dist/operate.js +565 -64
  257. package/dist/phase-routing.js +2 -2
  258. package/dist/plain-schema.d.ts +23 -0
  259. package/dist/plain-schema.js +94 -0
  260. package/dist/plain-words.d.ts +20 -0
  261. package/dist/plain-words.js +66 -0
  262. package/dist/plan-login.d.ts +14 -0
  263. package/dist/plan-login.js +58 -0
  264. package/dist/planner.d.ts +1 -1
  265. package/dist/planner.js +4 -2
  266. package/dist/process-liveness.d.ts +2 -0
  267. package/dist/process-liveness.js +61 -17
  268. package/dist/project-delete-ui.d.ts +19 -2
  269. package/dist/project-delete-ui.js +29 -1
  270. package/dist/project-delete.js +14 -1
  271. package/dist/project-knowledge.d.ts +18 -11
  272. package/dist/project-knowledge.js +39 -9
  273. package/dist/project-learning.d.ts +2 -1
  274. package/dist/project-learning.js +15 -45
  275. package/dist/project-memory.d.ts +5 -0
  276. package/dist/project-memory.js +22 -2
  277. package/dist/project-tools.d.ts +4 -4
  278. package/dist/project-tools.js +23 -7
  279. package/dist/proof.js +30 -5
  280. package/dist/protected-storage-look.d.ts +1 -0
  281. package/dist/protected-storage-look.js +13 -0
  282. package/dist/protected-storage.d.ts +70 -39
  283. package/dist/protected-storage.js +196 -147
  284. package/dist/provider-auth.js +1 -1
  285. package/dist/provider-capture.d.ts +34 -0
  286. package/dist/provider-capture.js +53 -0
  287. package/dist/publish.js +9 -1
  288. package/dist/pull-request-flow.js +5 -0
  289. package/dist/push.d.ts +18 -2
  290. package/dist/push.js +40 -16
  291. package/dist/release-adapters.d.ts +9 -0
  292. package/dist/release-adapters.js +76 -0
  293. package/dist/release-auto.js +13 -13
  294. package/dist/release-card.d.ts +1 -2
  295. package/dist/release-card.js +11 -0
  296. package/dist/release-carry.d.ts +7 -2
  297. package/dist/release-carry.js +49 -10
  298. package/dist/release-cli.d.ts +4 -0
  299. package/dist/release-cli.js +29 -22
  300. package/dist/release-coverage.js +1 -1
  301. package/dist/release-next.d.ts +48 -0
  302. package/dist/release-next.js +161 -0
  303. package/dist/release.d.ts +20 -1
  304. package/dist/release.js +85 -9
  305. package/dist/remote-destination.d.ts +36 -0
  306. package/dist/remote-destination.js +217 -0
  307. package/dist/remote-main.d.ts +48 -0
  308. package/dist/remote-main.js +84 -5
  309. package/dist/repository-context.d.ts +3 -2
  310. package/dist/repository-context.js +112 -70
  311. package/dist/restart-certification.d.ts +1 -0
  312. package/dist/restart-certification.js +8 -6
  313. package/dist/result-acts.d.ts +27 -19
  314. package/dist/result-acts.js +36 -25
  315. package/dist/result-follow-ups.d.ts +2 -2
  316. package/dist/result-follow-ups.js +18 -4
  317. package/dist/result-review.d.ts +1 -1
  318. package/dist/route-change-check.d.ts +4 -0
  319. package/dist/route-change-check.js +13 -0
  320. package/dist/route-change.d.ts +22 -0
  321. package/dist/route-change.js +89 -0
  322. package/dist/route-hooks.js +3 -2
  323. package/dist/route-results.d.ts +6 -1
  324. package/dist/route-results.js +14 -5
  325. package/dist/run-closeout.d.ts +1 -1
  326. package/dist/run-closeout.js +7 -5
  327. package/dist/runner-process.js +3 -2
  328. package/dist/runner.js +2 -1
  329. package/dist/scope.d.ts +28 -4
  330. package/dist/scope.js +92 -28
  331. package/dist/scout.d.ts +1 -1
  332. package/dist/scout.js +2 -1
  333. package/dist/serve.d.ts +1 -1
  334. package/dist/serve.js +26 -18
  335. package/dist/server/chat.js +16 -14
  336. package/dist/server/chrome.d.ts +1 -1
  337. package/dist/server/chrome.js +26 -35
  338. package/dist/server/console.css +73 -0
  339. package/dist/server/guards.js +3 -0
  340. package/dist/server/home.d.ts +6 -0
  341. package/dist/server/home.js +21 -13
  342. package/dist/server/http.js +1 -1
  343. package/dist/server/kit.js +1 -1
  344. package/dist/server/merge-ready-view.d.ts +10 -0
  345. package/dist/server/merge-ready-view.js +30 -0
  346. package/dist/server/pages-assets.d.ts +1 -0
  347. package/dist/server/pages-assets.js +8 -5
  348. package/dist/server/pages.js +103 -82
  349. package/dist/server/render-chat.js +16 -3
  350. package/dist/server/render-code-map.d.ts +30 -0
  351. package/dist/server/render-code-map.js +137 -0
  352. package/dist/server/render-flaky.d.ts +20 -0
  353. package/dist/server/render-flaky.js +59 -0
  354. package/dist/server/render-follow-through.d.ts +11 -0
  355. package/dist/server/render-follow-through.js +68 -0
  356. package/dist/server/render-history.js +1 -1
  357. package/dist/server/render-new-task.d.ts +26 -1
  358. package/dist/server/render-new-task.js +30 -5
  359. package/dist/server/render-pages.d.ts +1 -175
  360. package/dist/server/render-pages.js +38 -313
  361. package/dist/server/render-projects.d.ts +2 -1
  362. package/dist/server/render-projects.js +9 -7
  363. package/dist/server/render-release.d.ts +2 -0
  364. package/dist/server/render-release.js +3 -3
  365. package/dist/server/render-results.d.ts +2 -0
  366. package/dist/server/render-results.js +68 -68
  367. package/dist/server/render-tasks.d.ts +16 -2
  368. package/dist/server/render-tasks.js +177 -141
  369. package/dist/server/retired-routes.d.ts +17 -0
  370. package/dist/server/retired-routes.js +53 -0
  371. package/dist/server/route-table.d.ts +3 -0
  372. package/dist/server/route-table.js +48 -12
  373. package/dist/server/settings.js +166 -34
  374. package/dist/server/task-views.js +66 -26
  375. package/dist/server/tasks.d.ts +2 -6
  376. package/dist/server/tasks.js +250 -321
  377. package/dist/server/welcome.js +1 -1
  378. package/dist/settings-groups-ui.d.ts +1 -0
  379. package/dist/settings-groups-ui.js +4 -2
  380. package/dist/settings-kit.js +3 -2
  381. package/dist/settings-proofs.js +3 -1
  382. package/dist/slack-chat.js +14 -1
  383. package/dist/steer-mailbox.d.ts +8 -1
  384. package/dist/steer-mailbox.js +164 -6
  385. package/dist/stop-proof.js +6 -4
  386. package/dist/storage-sweep-process.js +1 -1
  387. package/dist/storage-sweep.d.ts +5 -3
  388. package/dist/storage-sweep.js +30 -9
  389. package/dist/storage-ui.js +28 -17
  390. package/dist/storage.d.ts +27 -2
  391. package/dist/storage.js +39 -2
  392. package/dist/store.d.ts +89 -7
  393. package/dist/store.js +386 -114
  394. package/dist/surface.js +67 -6
  395. package/dist/sweep-quarantine.d.ts +1 -0
  396. package/dist/sweep-quarantine.js +6 -4
  397. package/dist/tamper-scan.d.ts +4 -0
  398. package/dist/tamper-scan.js +90 -0
  399. package/dist/task-aliases.d.ts +5 -0
  400. package/dist/task-aliases.js +9 -0
  401. package/dist/task-control.d.ts +2 -1
  402. package/dist/task-control.js +4 -1
  403. package/dist/task-outcome-cli.d.ts +2 -0
  404. package/dist/task-outcome-cli.js +17 -17
  405. package/dist/task-sizing.d.ts +5 -0
  406. package/dist/task-sizing.js +2 -0
  407. package/dist/task-source.d.ts +7 -0
  408. package/dist/task-source.js +14 -0
  409. package/dist/task-status.d.ts +9 -0
  410. package/dist/task-status.js +45 -49
  411. package/dist/teardown-git.d.ts +18 -0
  412. package/dist/teardown-git.js +105 -21
  413. package/dist/telegram-lead.js +23 -6
  414. package/dist/telegram-progress.js +5 -3
  415. package/dist/telegram-status.js +6 -4
  416. package/dist/telegram.js +12 -1
  417. package/dist/test-infra.d.ts +8 -0
  418. package/dist/test-infra.js +18 -0
  419. package/dist/tidy.js +1 -1
  420. package/dist/toolroll-update.js +19 -5
  421. package/dist/tui-contract.d.ts +24 -0
  422. package/dist/tui-source.js +67 -8
  423. package/dist/tui.js +51 -51
  424. package/dist/vanished-retry.d.ts +30 -0
  425. package/dist/vanished-retry.js +174 -0
  426. package/dist/work-index.d.ts +2 -0
  427. package/dist/work-index.js +95 -66
  428. package/dist/work-summary.d.ts +1 -1
  429. package/dist/work-summary.js +7 -2
  430. package/dist/workspace-ui.d.ts +7 -3
  431. package/dist/workspace-ui.js +38 -23
  432. package/dist/worktree.d.ts +12 -3
  433. package/dist/worktree.js +91 -14
  434. package/package.json +2 -1
  435. package/dist/server/render-next.d.ts +0 -63
  436. package/dist/server/render-next.js +0 -109
@@ -1,11 +1,13 @@
1
- import { type CheckoutCleanup } from "./storage.js";
2
- import type { Store, WorktreeRow } from "./store.js";
1
+ import { type CheckoutCleanup, type CheckoutIdle } from "./storage.js";
2
+ import { type Store, type WorktreeRow } from "./store.js";
3
3
  import { type WorktreePool } from "./worktree.js";
4
4
  /** A release candidate's checkout holds the build a deploy installs: the newest complete one waits at most a week for it. */
5
5
  export declare const CANDIDATE_KEEP_MS: number;
6
6
  /** The worker's pass removes at most this many; a clean-up by hand has no cap. */
7
7
  export declare const AUTO_MAX = 20;
8
- export type KeepWhy = "in use" | "task not finished" | "waiting for review" | "release candidate" | "has changes" | "has commits" | "unreadable" | "git refused" | "not due yet" | "cleanup is off" | "no task";
8
+ export type KeepWhy = "in use" | "task not finished" | "waiting for review" | "release candidate" | "has changes" | "has commits" | "not on its branch" | "unreadable" | "git refused" | "not due yet" | "cleanup is off" | "no task"
9
+ /** A clean-up by hand leaves it; the next sweep takes it. */
10
+ | "idle";
9
11
  export type CheckoutItem = {
10
12
  path: string;
11
13
  repo: string;
@@ -17,10 +19,13 @@ export type CheckoutItem = {
17
19
  why: KeepWhy | null;
18
20
  /** With "not due yet" or "release candidate": when it may go. */
19
21
  dueAt?: string;
22
+ /** Past the idle limit: when its task last saw any activity. */
23
+ idleSince?: string;
20
24
  };
21
25
  export type CheckoutPlan = {
22
26
  at: string;
23
27
  cleanup: CheckoutCleanup;
28
+ idle: CheckoutIdle;
24
29
  manual: boolean;
25
30
  go: CheckoutItem[];
26
31
  stay: CheckoutItem[];
@@ -30,6 +35,9 @@ export type CheckoutPlan = {
30
35
  freeBytes: number;
31
36
  waitingReview: number;
32
37
  withChanges: number;
38
+ /** What the next sweep's idle cleanup takes. */
39
+ idleCount: number;
40
+ idleBytes: number;
33
41
  };
34
42
  type Status = {
35
43
  keep: KeepWhy | null;
@@ -64,6 +72,21 @@ export declare function finishedCheckouts(store: Store, now: Date, repo?: string
64
72
  taskId: string | null;
65
73
  finishedAt: string;
66
74
  }[];
75
+ /** The newest activity of a task's whole family, and whether something rules idle cleanup out for it. */
76
+ type IdleFact = {
77
+ activeAt: string;
78
+ blocked: boolean;
79
+ };
80
+ export type IdleFacts = {
81
+ byRef: Map<number, IdleFact>;
82
+ byBranch: Map<string, IdleFact>;
83
+ };
84
+ /**
85
+ * When each task family last saw a run, a person's act or a lease, read in one pass. A revision builds on what it
86
+ * revises, so a family is idle together. `blocked`: an open run, an unreleased claim, a release gate, or a task only an
87
+ * external tracker knows, in the family.
88
+ */
89
+ export declare function idleFacts(store: Store): IdleFacts;
67
90
  /** Why a checkout stays as a clean-up by hand reads it (in use, or its task not done with it), before its files; null: it may go. */
68
91
  export declare function checkoutKeepWhy(pool: WorktreePool, row: WorktreeRow, statuses: ReturnType<typeof taskStatuses>, now: Date): KeepWhy | null;
69
92
  /**
@@ -83,11 +106,15 @@ export type CleanResult = {
83
106
  why: KeepWhy;
84
107
  }[];
85
108
  freed: number;
109
+ /** The worker's pass stopped at AUTO_MAX: more may be due, and the sweep comes back an hour later. */
110
+ more?: boolean;
86
111
  };
87
112
  /**
88
113
  * Remove what may go: the worker's pass (`manual` false: under the setting, at most AUTO_MAX, one repository) or a
89
114
  * clean-up by hand (no cap, every repository). Each checkout is asked again right before it goes; each removal is in
90
- * the ledger.
115
+ * the ledger. Idle checkouts go only with `idle`, the storage sweep's child (never the service's own pass, whose git
116
+ * and du must not hold up its heartbeats): each is judged again from a fresh read of its task and its family's
117
+ * activity, `spare` (live work), the OS's list of what runs in the folder, and `proveRecreatable`, inside its reservation.
91
118
  */
92
119
  export declare function cleanCheckouts(store: Store, pool: WorktreePool, now: () => Date, options: {
93
120
  manual: boolean;
@@ -95,6 +122,10 @@ export declare function cleanCheckouts(store: Store, pool: WorktreePool, now: ()
95
122
  repo?: string;
96
123
  only?: ReadonlySet<string>;
97
124
  spare?: (path: string) => boolean;
125
+ /** The storage sweep's child only: idle checkouts go too. `occupied` asks the OS whether any process works in a folder. */
126
+ idle?: {
127
+ occupied: (path: string) => boolean;
128
+ };
98
129
  }): Promise<CleanResult>;
99
130
  export type SlimResult = {
100
131
  slimmed: {
@@ -122,5 +153,5 @@ export declare function discardCheckout(store: Store, pool: WorktreePool, path:
122
153
  message: string;
123
154
  }>;
124
155
  /** "Waiting for review", "Has changes" … as a sentence fragment for a person. */
125
- export declare function whyWords(item: Pick<CheckoutItem, "why" | "dueAt">): string;
156
+ export declare function whyWords(item: Pick<CheckoutItem, "why" | "dueAt" | "idleSince">): string;
126
157
  export {};
@@ -5,14 +5,22 @@
5
5
  * 2 days or a week after that, or never (only a clean-up by hand). A clean-up by hand doesn't wait for the setting.
6
6
  * Only the working copy goes: its branch, and so every commit, stays, and a later lease of the branch makes a new one.
7
7
  *
8
- * Always kept: a checkout in use, one whose task isn't finished (on hold, failed, a revision under way), a result
9
- * still waiting for review, a checkout with changes (files Toolroll wrote itself aside), and one with commits on no
10
- * branch. A release check's checkout (a deploy installs from it) goes once its task is complete, with its dependency
8
+ * Kept until idle (below): one whose task isn't finished (on hold, failed, a revision under way) and a result still
9
+ * waiting for review. Always kept: a checkout in use, one with changes (files Toolroll wrote itself aside), and one
10
+ * with commits on no branch. A release check's checkout (a deploy installs from it) goes once its task is complete, with its dependency
11
11
  * install and journey output, except the one deployed now and the project's newest complete one still waiting for its
12
12
  * deploy (no deploy since it was complete; at most a week). A superseded or cancelled gate goes at once, whatever its
13
13
  * task's state (staged releases keep the rollback copy). A checkout no task names (adopted after a crash) goes only
14
14
  * in a clean-up by hand. Every removal is in the ledger.
15
15
  *
16
+ * Idle checkouts go too (the idle limit, 14 days by default): one whose whole task family has been idle that long (no
17
+ * run, no person's act, no open lease) goes even with its result waiting for review or its task unfinished, in the
18
+ * storage sweep's child only, never in a clean-up by hand, and only when `WorktreePool.proveRecreatable` says nothing is lost.
19
+ * Nothing needs that working copy: a result page, Accept, Send back and Run checks read saved evidence and the branch,
20
+ * and a retry, revision or resume leases the branch, which makes a new one. Excluded, because that can't be proven: a
21
+ * release gate (a deploy installs from its checkout), work from an external tracker, and a checkout no task names. A
22
+ * checkout with uncommitted changes is never removed by this; it is listed for a person.
23
+ *
16
24
  * A kept checkout waiting for review or with changes drops its dependencies and build output once its run ends
17
25
  * (`slimKeptCheckouts`); the next run's setup restores them.
18
26
  */
@@ -21,7 +29,8 @@ import { existsSync, readFileSync, readdirSync } from "node:fs";
21
29
  import { basename, dirname, join } from "node:path";
22
30
  import { taskBranches } from "./names.js";
23
31
  import { COMPLETION_ACTION } from "./result-completion.js";
24
- import { CLEANUP_CHOICES, bytesWords, diskBytes, diskBytesAsync } from "./storage.js";
32
+ import { CLEANUP_CHOICES, IDLE_CHOICES, bytesWords, diskBytes, diskBytesAsync } from "./storage.js";
33
+ import { checkoutIdle } from "./store.js";
25
34
  import { worktreePath } from "./worktree.js";
26
35
  const DAY_MS = 86_400_000;
27
36
  /** A release candidate's checkout holds the build a deploy installs: the newest complete one waits at most a week for it. */
@@ -218,6 +227,114 @@ export function finishedCheckouts(store, now, repo) {
218
227
  return [{ row, taskId: status.taskId, finishedAt: status.finishedAt }];
219
228
  });
220
229
  }
230
+ /** Toolroll's own bookkeeping about a task: none of it is anyone using the task. Every other ledger entry counts as activity. */
231
+ const NOT_ACTIVITY = ["assignment status baseline", "assignment status observed", "task archived", "checkout removed", "checkout slimmed", "checkout processes stopped",
232
+ "run processes stopped", "orphan process stopped", "process observation failed", "process witness settled", "branch deleted", "landed work cleaned up"];
233
+ /**
234
+ * When each task family last saw a run, a person's act or a lease, read in one pass. A revision builds on what it
235
+ * revises, so a family is idle together. `blocked`: an open run, an unreleased claim, a release gate, or a task only an
236
+ * external tracker knows, in the family.
237
+ */
238
+ export function idleFacts(store) {
239
+ const db = store.handle;
240
+ const refs = db.prepare(`SELECT r.id AS ref, r.external_id AS id, r.revision_of AS revision_of, t.state AS state, t.created_at AS created_at, t.updated_at AS updated_at,
241
+ (SELECT MAX(s.candidate) FROM task_scope s WHERE s.task_id = r.external_id) AS candidate
242
+ FROM task_ref r LEFT JOIN task t ON t.id = r.external_id AND r.backend = 'built-in'`).all();
243
+ const parentOf = new Map(refs.filter(row => row["revision_of"] !== null).map(row => [String(row["id"]), String(row["revision_of"])]));
244
+ const rootOf = (id) => {
245
+ const seen = new Set([id]);
246
+ let root = id;
247
+ for (let parent = parentOf.get(root); parent !== undefined && !seen.has(parent); parent = parentOf.get(root)) {
248
+ seen.add(parent);
249
+ root = parent;
250
+ }
251
+ return root;
252
+ };
253
+ const families = new Map();
254
+ const rootByRef = new Map();
255
+ const rootsById = new Map();
256
+ const note = (root, at, blocked = false) => {
257
+ if (root === undefined)
258
+ return;
259
+ const fact = families.get(root) ?? { activeAt: "", blocked: false };
260
+ if (typeof at === "string" && at > fact.activeAt)
261
+ fact.activeAt = at;
262
+ if (blocked)
263
+ fact.blocked = true;
264
+ families.set(root, fact);
265
+ };
266
+ for (const row of refs) {
267
+ const id = String(row["id"]), root = rootOf(id);
268
+ rootByRef.set(Number(row["ref"]), root);
269
+ rootsById.set(id, root);
270
+ note(root, row["created_at"]);
271
+ note(root, row["updated_at"], row["state"] === null || row["candidate"] !== null);
272
+ }
273
+ const byTaskRef = (at, ref, blocked = false) => note(rootByRef.get(Number(ref)), at, blocked);
274
+ const runRoot = new Map();
275
+ const branches = [];
276
+ for (const row of db.prepare("SELECT id, task_ref, branch, started_at, finished_at FROM run").all()) {
277
+ const root = rootByRef.get(Number(row["task_ref"]));
278
+ runRoot.set(Number(row["id"]), root);
279
+ note(root, row["started_at"], row["finished_at"] === null);
280
+ note(root, row["finished_at"]);
281
+ if (root !== undefined && row["branch"] !== null)
282
+ branches.push([String(row["branch"]), root]);
283
+ }
284
+ for (const row of db.prepare("SELECT task_ref, acquired_at, heartbeat_at, released_at FROM claim").all()) {
285
+ for (const at of [row["acquired_at"], row["heartbeat_at"], row["released_at"]])
286
+ byTaskRef(at, row["task_ref"]);
287
+ byTaskRef(null, row["task_ref"], row["released_at"] === null);
288
+ }
289
+ for (const row of db.prepare("SELECT task_ref, held_at AS at FROM hold UNION ALL SELECT task_ref, created_at FROM task_steer UNION ALL SELECT task_ref, at FROM task_act").all())
290
+ byTaskRef(row["at"], row["task_ref"]);
291
+ for (const row of db.prepare("SELECT run, created_at AS at FROM run_note UNION ALL SELECT run, created_at FROM diff_comment UNION ALL SELECT run, created_at FROM decision UNION ALL SELECT run, answered_at FROM decision").all()) {
292
+ note(runRoot.get(Number(row["run"])), row["at"]);
293
+ }
294
+ for (const row of db.prepare(`SELECT task_id, run_id, MAX(at) AS at FROM action_ledger WHERE (task_id IS NOT NULL OR run_id IS NOT NULL)
295
+ AND action NOT IN (SELECT value FROM json_each(?)) GROUP BY task_id, run_id`).all(JSON.stringify(NOT_ACTIVITY))) {
296
+ if (row["task_id"] !== null)
297
+ note(rootsById.get(String(row["task_id"])), row["at"]);
298
+ if (row["run_id"] !== null)
299
+ note(runRoot.get(Number(row["run_id"])), row["at"]);
300
+ }
301
+ const byRef = new Map();
302
+ for (const [ref, root] of rootByRef)
303
+ byRef.set(ref, families.get(root));
304
+ // A branch answers for every family that used it: the newest activity and any block among them.
305
+ const byBranch = new Map();
306
+ const share = (branch, root) => {
307
+ const one = families.get(root), seen = byBranch.get(branch);
308
+ byBranch.set(branch, seen === undefined ? { ...one } : { activeAt: seen.activeAt > one.activeAt ? seen.activeAt : one.activeAt, blocked: seen.blocked || one.blocked });
309
+ };
310
+ for (const row of refs)
311
+ for (const branch of taskBranches(String(row["id"])))
312
+ share(branch, rootOf(String(row["id"])));
313
+ for (const [branch, root] of branches)
314
+ share(branch, root);
315
+ return { byRef, byBranch };
316
+ }
317
+ /** The idle limit in days as it stands: null when idle cleanup is off (the limit, or automatic cleanup altogether). */
318
+ function idleDays(store) {
319
+ return store.checkoutCleanup() === "never" ? null : IDLE_CHOICES.find(one => one.value === checkoutIdle(store)).days;
320
+ }
321
+ /** When this checkout's task last saw activity, when that is past the limit and only its task's standing keeps it; else null. */
322
+ function idleSince(pool, row, statuses, facts, days, now) {
323
+ if (days === null || row.taskRef === null || row.branch === "unknown" || row.releasedAt === null || row.runner !== null || pool.inUse(row.path).held)
324
+ return null;
325
+ const keep = statusOf(row, statuses)?.keep;
326
+ if (keep !== "waiting for review" && keep !== "task not finished")
327
+ return null;
328
+ const own = facts.byRef.get(row.taskRef), shared = facts.byBranch.get(row.branch);
329
+ if (own === undefined || own.blocked || shared?.blocked === true)
330
+ return null;
331
+ const since = [own.activeAt, shared?.activeAt, row.createdAt, row.leasedAt, row.releasedAt].filter((one) => typeof one === "string").sort().at(-1);
332
+ return Date.parse(since) + days * DAY_MS <= now.getTime() ? since : null;
333
+ }
334
+ /** Every commit a task's runs recorded on this checkout or its branch: what `proveRecreatable` must find on a branch. */
335
+ function recordedHeads(store, row) {
336
+ return store.handle.prepare("SELECT DISTINCT head_revision AS head FROM run WHERE (branch = ? OR worktree = ?) AND head_revision IS NOT NULL").all(row.branch, row.path).map(one => String(one["head"]));
337
+ }
221
338
  /** Why a checkout stays before anyone looks at its files, or null: it may go (as far as its task and the setting say). */
222
339
  function keptFor(pool, row, statuses, now, cleanup) {
223
340
  const status = statusOf(row, statuses);
@@ -266,6 +383,8 @@ export async function checkoutPlan(store, pool, now, options) {
266
383
  const rows = store.listWorktrees().filter(row => existsSync(row.path) && (options.repo === undefined || row.repo === options.repo));
267
384
  const sizes = await diskBytesAsync(rows.map(row => row.path));
268
385
  const statuses = taskStatuses(store, now);
386
+ const days = idleDays(store);
387
+ const facts = idleFacts(store);
269
388
  const items = await eachAtMost(rows, INSPECT_AT_ONCE, async (row) => {
270
389
  const kept = keptFor(pool, row, statuses, now, options.manual ? "manual" : cleanup);
271
390
  let why = kept.why;
@@ -273,14 +392,23 @@ export async function checkoutPlan(store, pool, now, options) {
273
392
  const found = await pool.inspect(row.path);
274
393
  why = found === "clean" ? null : found;
275
394
  }
276
- return { path: row.path, repo: row.repo, branch: row.branch, taskId: kept.taskId, bytes: sizes.get(row.path) ?? 0, releasedAt: row.releasedAt, why, ...(kept.dueAt === undefined ? {} : { dueAt: kept.dueAt }) };
395
+ const since = kept.why === null ? null : idleSince(pool, row, statuses, facts, days, now);
396
+ if (since !== null) {
397
+ const found = await pool.inspect(row.path);
398
+ const proven = found === "clean" ? await pool.proveRecreatable(row, recordedHeads(store, row)) : found;
399
+ why = proven !== "clean" ? proven : options.manual ? "idle" : null;
400
+ }
401
+ return { path: row.path, repo: row.repo, branch: row.branch, taskId: kept.taskId, bytes: sizes.get(row.path) ?? 0, releasedAt: row.releasedAt, why,
402
+ ...(kept.dueAt === undefined ? {} : { dueAt: kept.dueAt }), ...(since === null ? {} : { idleSince: since }) };
277
403
  });
278
404
  const go = items.filter(one => one.why === null).sort((a, b) => b.bytes - a.bytes);
279
405
  const stay = items.filter(one => one.why !== null).sort((a, b) => (a.why ?? "").localeCompare(b.why ?? "") || b.bytes - a.bytes);
406
+ const idle = items.filter(one => one.idleSince !== undefined && (one.why === null || one.why === "idle"));
280
407
  return {
281
- at: now.toISOString(), cleanup, manual: options.manual, go, stay,
408
+ at: now.toISOString(), cleanup, idle: checkoutIdle(store), manual: options.manual, go, stay,
282
409
  count: items.length, totalBytes: items.reduce((sum, one) => sum + one.bytes, 0), freeBytes: go.reduce((sum, one) => sum + one.bytes, 0),
283
410
  waitingReview: stay.filter(one => one.why === "waiting for review").length, withChanges: stay.filter(one => one.why === "has changes").length,
411
+ idleCount: idle.length, idleBytes: idle.reduce((sum, one) => sum + one.bytes, 0),
284
412
  };
285
413
  }
286
414
  /** Names exactly what a preview would remove, so a confirmation removes that and nothing else. */
@@ -290,7 +418,9 @@ export function previewDigest(plan) {
290
418
  /**
291
419
  * Remove what may go: the worker's pass (`manual` false: under the setting, at most AUTO_MAX, one repository) or a
292
420
  * clean-up by hand (no cap, every repository). Each checkout is asked again right before it goes; each removal is in
293
- * the ledger.
421
+ * the ledger. Idle checkouts go only with `idle`, the storage sweep's child (never the service's own pass, whose git
422
+ * and du must not hold up its heartbeats): each is judged again from a fresh read of its task and its family's
423
+ * activity, `spare` (live work), the OS's list of what runs in the folder, and `proveRecreatable`, inside its reservation.
294
424
  */
295
425
  export async function cleanCheckouts(store, pool, now, options) {
296
426
  const cleanup = store.checkoutCleanup();
@@ -299,31 +429,51 @@ export async function cleanCheckouts(store, pool, now, options) {
299
429
  // `only`: the checkouts a person saw in the preview; nothing they didn't see goes.
300
430
  const rows = store.listWorktrees().filter(row => existsSync(row.path) && (options.repo === undefined || row.repo === options.repo) && (options.only === undefined || options.only.has(row.path)));
301
431
  let statuses = taskStatuses(store, now());
432
+ const days = options.idle === undefined ? null : idleDays(store);
433
+ let facts = days === null ? { byRef: new Map(), byBranch: new Map() } : idleFacts(store);
434
+ // The checkouts going for being idle, and since when: judged at every look, so a task coming back drops out.
435
+ const idle = new Map();
436
+ const mayGo = (row) => {
437
+ idle.delete(row.path);
438
+ if (keptFor(pool, row, statuses, now(), options.manual ? "manual" : cleanup).why === null)
439
+ return options.spare?.(row.path) !== true;
440
+ const since = idleSince(pool, row, statuses, facts, days, now());
441
+ if (since === null)
442
+ return false;
443
+ idle.set(row.path, since);
444
+ return options.spare?.(row.path) !== true;
445
+ };
446
+ const refresh = () => { statuses = taskStatuses(store, now()); if (days !== null)
447
+ facts = idleFacts(store); };
302
448
  const sizes = new Map();
303
449
  const wanted = (row, fresh) => {
304
450
  // Right before a checkout goes, where its task stands is read afresh, and its size taken for the ledger.
305
451
  // `spare`: live work uses it (storage-sweep.ts), asked at both looks.
306
452
  if (fresh)
307
- statuses = taskStatuses(store, now());
308
- const go = keptFor(pool, row, statuses, now(), options.manual ? "manual" : cleanup).why === null && options.spare?.(row.path) !== true;
453
+ refresh();
454
+ const go = mayGo(row);
309
455
  if (go && fresh) {
310
456
  sizes.set(row.path, diskBytes([row.path]).get(row.path) ?? 0);
311
457
  // du can be slow. The reservation excludes a concurrent checkout lease, and this last read also catches
312
458
  // an open run/claim or task change that arrived while size was measured in the sweep child.
313
- return keptFor(pool, row, taskStatuses(store, now()), now(), options.manual ? "manual" : cleanup).why === null && options.spare?.(row.path) !== true;
459
+ refresh();
460
+ return mayGo(row) && !(idle.has(row.path) && options.idle?.occupied(row.path) === true);
314
461
  }
315
462
  return go;
316
463
  };
317
- const pruned = await pool.prune(rows, wanted, options.manual ? Infinity : AUTO_MAX);
464
+ const proof = async (row) => idle.has(row.path) ? pool.proveRecreatable(row, recordedHeads(store, row)) : "clean";
465
+ const max = options.manual ? Infinity : AUTO_MAX;
466
+ const pruned = await pool.prune(rows, wanted, max, undefined, proof);
318
467
  const removed = [];
319
468
  for (const row of pruned.removed) {
320
469
  const taskId = row.taskRef === null ? null : store.externalIdFor(row.taskRef);
321
470
  const bytes = sizes.get(row.path) ?? 0;
322
- removed.push({ path: row.path, repo: row.repo, branch: row.branch, taskId, bytes, releasedAt: row.releasedAt, why: null });
471
+ const since = idle.get(row.path);
472
+ removed.push({ path: row.path, repo: row.repo, branch: row.branch, taskId, bytes, releasedAt: row.releasedAt, why: null, ...(since === undefined ? {} : { idleSince: since }) });
323
473
  store.recordAction({ at: now().toISOString(), actor: options.actor, repo: row.repo, taskId, runId: null, action: "checkout removed", outcome: "removed",
324
- source: options.manual ? "request" : "work", detail: `${basename(row.path)} (released ${row.releasedAt?.slice(0, 10) ?? "?"}, about ${bytesWords(bytes)}); branch ${row.branch} kept` });
474
+ source: options.manual ? "request" : "work", detail: `${basename(row.path)} (${since === undefined ? "" : `idle since ${since.slice(0, 10)}, `}released ${row.releasedAt?.slice(0, 10) ?? "?"}, about ${bytesWords(bytes)}); branch ${row.branch} kept` });
325
475
  }
326
- return { removed, kept: pruned.kept, freed: removed.reduce((sum, one) => sum + one.bytes, 0) };
476
+ return { removed, kept: pruned.kept, freed: removed.reduce((sum, one) => sum + one.bytes, 0), ...(pruned.removed.length >= max ? { more: true } : {}) };
327
477
  }
328
478
  /** Checkouts this process already looked at since they were let go (path and release time): looked at once. */
329
479
  const looked = new Set();
@@ -381,6 +531,8 @@ export function whyWords(item) {
381
531
  case "release candidate": return item.dueAt === undefined ? "the deployed release candidate" : `newest release candidate, kept for its deploy until ${item.dueAt.slice(0, 10)}`;
382
532
  case "has changes": return "has changes";
383
533
  case "has commits": return "has commits on no branch";
534
+ case "not on its branch": return "isn't on its task's branch";
535
+ case "idle": return `idle since ${item.idleSince?.slice(0, 10) ?? "a while"}; the next sweep removes it`;
384
536
  case "unreadable": return "git couldn't read it";
385
537
  case "git refused": return "git wouldn't remove it";
386
538
  case "not due yet": return `due ${item.dueAt?.slice(0, 10) ?? "later"}`;
package/dist/claim.d.ts CHANGED
@@ -214,6 +214,8 @@ export declare function acquireIfReady(store: Store, taskRef: number, runner: st
214
214
  provider?: string;
215
215
  /** The model this dispatch would run — the quota scope. */
216
216
  model?: string;
217
+ /** A fallback entry's pinned auth mode: its quota is that credential's, never the base's. */
218
+ authMode?: "subscription" | "api-key";
217
219
  /** What this dispatch is FOR. Re-proved inside the transaction — the
218
220
  * caller's survey is not trusted (Codex planning review, finding 2). */
219
221
  dispatchRole?: "builder" | "planner" | "scout";
@@ -348,6 +350,10 @@ export type FailureDisposition = {
348
350
  ok: true;
349
351
  disposition: "auth-expired";
350
352
  provider: string;
353
+ } | {
354
+ ok: true;
355
+ disposition: "fallback";
356
+ provider: string;
351
357
  } | {
352
358
  ok: false;
353
359
  reason: "fenced" | "unknown" | "stopped";
package/dist/claim.js CHANGED
@@ -38,6 +38,7 @@ import { sealUnchangedPlanUnderMode } from "./plan-auto.js";
38
38
  import { routeFromJson } from "./phase-routing.js";
39
39
  import { contractChangesOf, decodePlannerSource, encodePlannerSource, canonicalContractJson, PLANNER_SOURCE_LIMITS, describeContractChanges, encodePlanContractRecord, plannerContractOf, plannerSourceDigest, } from "./planner-source.js";
40
40
  import { storeEvidence, readVerifiedArtifact, EVIDENCE_CAPS } from "./evidence.js";
41
+ import { moveOnIfExhausted } from "./fallback.js";
41
42
  /** How stale a mirror's last complete sync may be before admission refuses. */
42
43
  export const SYNC_MAX_AGE_MS = 15 * 60_000;
43
44
  /**
@@ -298,7 +299,7 @@ export function acquireIfReady(store, taskRef, runner, options) {
298
299
  // Quota is keyed to the credential that actually exhausts: the RESOLVED
299
300
  // provider, never a fixed binary name — codex quota must not collide
300
301
  // with claude's, nor bypass it (Codex provider review, high finding 5).
301
- const quota = store.quotaState(runner, options.provider ?? "claude", scope, options.now);
302
+ const quota = store.quotaState(runner, options.provider ?? "claude", scope, options.now, options.authMode ?? "subscription");
302
303
  if (quota !== null && quota.state === "exhausted") {
303
304
  return {
304
305
  ok: false,
@@ -349,7 +350,7 @@ export function acquireIfReady(store, taskRef, runner, options) {
349
350
  // exhausted with no probe in flight (finding 3). Same transaction, so
350
351
  // consume-with-claim is all-or-nothing.
351
352
  if (taken.ok && quota !== null) {
352
- store.consumeHalfOpen(runner, options.provider ?? "claude", scope, options.now);
353
+ store.consumeHalfOpen(runner, options.provider ?? "claude", scope, options.now, options.authMode ?? "subscription");
353
354
  }
354
355
  return taken;
355
356
  });
@@ -880,6 +881,16 @@ function sealFailure(store, leaseId, args) {
880
881
  if (failureClass !== "commit-failure" && run.terminalClass === "auth-expired") {
881
882
  return { ok: true, ...sealAuthExpired(store, run, taskId, now) };
882
883
  }
884
+ // Out of plan on an approved chain: the walk moves on, no strike, and the next agent runs through the ordinary claim.
885
+ // Anything else about this ending (fallback.ts says what closed the walk) stays the ordinary road below.
886
+ // Only the agent's own exit moves: a lease, branch or HEAD law that failed, or a commit that failed, is that ending.
887
+ if (failureClass === "unknown") {
888
+ const moved = moveOnIfExhausted(store, runId, taskId, now);
889
+ if (moved !== null) {
890
+ store.finishRun(runId, { outcome: "failed", reason: "usage-exhausted", now });
891
+ return { ok: true, disposition: "fallback", provider: moved.to.provider };
892
+ }
893
+ }
883
894
  store.finishRun(runId, { outcome: "failed", reason: failureClass, now });
884
895
  if (failureClass === "commit-failure") {
885
896
  // The work exists and is uncommitted; nothing times its way out of
@@ -2,4 +2,7 @@
2
2
  * The top-level usage text, on its own so `toolroll --help` can print it
3
3
  * without loading every command family (see cli-fast.ts).
4
4
  */
5
- export declare const HELP = "toolroll \u2014 a control plane for unattended coding agents\n\nUsage\n toolroll at your terminal: start Toolroll here (or attach) and open it in the terminal\n (--web: the browser; --project-root <dir>: where projects live)\n in a repository without a terminal, under an agent or with --json: only says what it would do\n toolroll open [--web] open this computer's Toolroll again, signed in\n toolroll welcome the three steps to a first result (--json: the model)\n toolroll password set add a password (typed twice, at your terminal)\n toolroll scan [path...] report what is in flight (also bare `toolroll` outside a repository)\n toolroll [path...] the same report for the paths given\n toolroll pulls report what is waiting on a person\n toolroll graph report which work graph is already here\n toolroll repos list connected repositories, and how to adjust\n toolroll repos add <path> add a project; it is in the console at once (no path: the repo you are in)\n toolroll repos remove <path>\n take it out of the console; its tasks and results stay\n toolroll repos add-from-github <owner/name> --root <dir>\n preview, then clone and connect (--yes)\n toolroll link put `toolroll` on your PATH\n toolroll unlink take it off again\n toolroll update update to the latest release: verified, drained, undoable\n (preview first; --yes, --now, --at HH:MM, --rollback)\n toolroll release <branch> release Toolroll itself: gate, your approval, deploy, merge, tag, publish\n (resumable: rerun to continue; --help)\n toolroll contract the machine contract: envelope version + capabilities\n (--commands dumps the declared command guide)\n toolroll skills install --claude-code [--dir <path>]\n install this binary's guides for Claude Code (preview first)\n toolroll skills install teach a repo's agents this queue exists (preview first)\n toolroll skills list the guides this exact binary serves\n toolroll skills get <name> print one guide (version-matched, never stale)\n toolroll demo a seeded throwaway sandbox \u2014 see it working in 90 seconds\n toolroll up app + builder for every saved project \u2014 the normal start\n toolroll onboard make your agent Toolroll's lead: add this repo, install its skill (--yes), offer starter flows (--starter)\n toolroll status running, queued, ready results, release check and plan windows\n toolroll health recent server latency, write waits and live streams\n toolroll integrations which integrations work, and what to do about the ones that don't (--json)\n toolroll lead token mint your lead agent's credential: its own work pings nobody\n toolroll lead say \"<text>\" (the lead) one short message in its person's chat\n\nOperating the queue \u2014 `toolroll task` prints the whole surface,\nand any queue command + --help prints it too\n toolroll approver add <name>\n mint the credential that lets a person say yes\n toolroll ready what could be dispatched right now\n toolroll check-progress <run> current or final approved check progress\n toolroll task wait <id> [--timeout <seconds>]\n wait for this attempt to finish or need a person\n toolroll task add <title> queue work\n toolroll task scope <id> --goal <text>\n state what success is; approval binds to it\n toolroll task approve <id>\n the yes \u2014 nothing builds without one\n toolroll claim <id> --runner <name>\n toolroll heartbeat <lease> / worker release <lease> / reap\n toolroll tick --runner <name> --token <t> --repo <path>\n one unattended pass over the ready set\n toolroll flows list | show <id> | create | edit | trigger | script | card | archive\n set up and inspect flows (writes preview until --yes)\n toolroll chat --as <you> --token <t>\n talk to your lead: one conversation across every\n project; it proposes, you confirm (--say \"\u2026\" for one turn)\n toolroll serve --repo <path> advanced: run only the console\n (--editor vscode with --runner: file links\n open in VS Code on the device you browse from)\n toolroll watch / daemon install advanced: run only the builder\n toolroll doctor check that Toolroll's service holds no key a build could read\n toolroll reconcile recover what the last stretch left behind\n\nWith nothing connected it reports everything it can find below the working\ndirectory. Once you connect repositories it reports those instead.\n\nOptions\n --all report everything discoverable, ignoring connected repos\n --depth <n> directory levels to descend (default: 6)\n --hidden include dot-directories, which are skipped by default\n --dirty also read each working tree for uncommitted files\n --local branches only: no network, no pull requests, no issues\n --json emit a machine-readable envelope instead of a report\n -o <file> also write that envelope to a file (requires --json)\n -h, --help show this\n\nNothing is written, installed, or configured. Every repository is read\nthrough git with --no-optional-locks, so a scan never contends for the\nindex lock with an editor you have open.\n\n--dirty is off by default because reading a working tree is unbounded\nwork: on a large or cloud-synced repo `git status` can take minutes,\nwhere listing branches takes milliseconds.\n\nlink shows what it would do and changes nothing; add --yes to apply it,\nor --to <dir> to choose where. It never uses sudo, never leaves your\nhome directory, and never touches a file it did not create.";
5
+ export declare const HELP = "toolroll \u2014 hand coding work to agents, walk away, come back to pull requests\n\nUsage\n toolroll at your terminal: start Toolroll here (or attach) and open it in the terminal\n (--web: the browser; --project-root <dir>: where projects live)\n in a repository without a terminal, under an agent or with --json: only says what it would do\n toolroll open [--web] open this computer's Toolroll again, signed in\n toolroll welcome the three steps to a first result (--json: the model)\n toolroll password set add a password (typed twice, at your terminal)\n toolroll scan [path...] report what is in flight (also bare `toolroll` outside a repository)\n toolroll [path...] the same report for the paths given\n toolroll pulls report what is waiting on a person\n toolroll graph report which work graph is already here\n toolroll repos list connected repositories, and how to adjust\n toolroll repos add <path> add a project; it is in the console at once (no path: the repo you are in)\n toolroll repos remove <path>\n take it out of the console; its tasks and results stay\n toolroll repos add-from-github <owner/name> --root <dir>\n preview, then clone and connect (--yes)\n toolroll link put `toolroll` on your PATH\n toolroll unlink take it off again\n toolroll update update to the latest release: verified, drained, undoable\n (preview first; --yes, --now, --at HH:MM, --rollback)\n toolroll release <branch> release Toolroll itself: checks, your approval, deploy, merge, tag, publish\n (rerun the same command to continue; --help)\n toolroll skills install --claude-code [--dir <path>]\n install this binary's guides for Claude Code (preview first)\n toolroll skills install teach a repo's agents this queue exists (preview first)\n toolroll demo a seeded throwaway sandbox \u2014 see it working in 90 seconds\n toolroll up app + builder for every saved project \u2014 the normal start\n toolroll onboard make your agent Toolroll's lead: add this repo, install its skill (--yes), offer starter flows (--starter)\n toolroll status running, queued, ready results, release check and plan windows\n toolroll integrations which integrations work, and what to do about the ones that don't (--json)\n toolroll lead token mint your lead agent's sign-in: its own work pings nobody\n toolroll lead say \"<text>\" (the lead) one short message in its person's chat\n\nTasks \u2014 `toolroll task` prints the whole list,\nand any task command + --help prints it too\n toolroll approver add <name>\n add a person who can approve work\n toolroll task add <title> queue work\n toolroll task show <id> one task: its plan, builds and what it waits for\n toolroll task approve <id>\n the yes \u2014 nothing builds without one\n toolroll task accept <id> --finish\n accept the result you read (--despite \"<why>\": accept it with a reason, without finishing)\n toolroll task send-back <id> --feedback \"<what to change>\"\n send the result back for changes\n toolroll task retry <id> build it again\n toolroll task ready <id> can I merge it? each check, and what clears a blocker\n toolroll chat --as <you> --token <t>\n talk to your lead: one conversation across every\n project; it proposes, you confirm (--say \"\u2026\" for one turn)\n toolroll card <source> the data chat shows as a card: tasks, results, flows, spend,\n projects, builders, mode, integrations, providers, findings, handover\n toolroll flows list | show <id> | create | edit | trigger | script | card | archive\n set up and inspect flows (writes preview until --yes)\n\nMachine and agent commands (the builder, agent tools, recovery):\n toolroll internals lists them; each keeps working as before\n\nWith nothing connected it reports everything it can find below the working\ndirectory. Once you connect repositories it reports those instead.\n\nOptions\n --all report everything discoverable, ignoring connected repos\n --depth <n> directory levels to descend (default: 6)\n --hidden include dot-directories, which are skipped by default\n --dirty also read each working tree for uncommitted files\n --local branches only: no network, no pull requests, no issues\n --json emit a machine-readable envelope instead of a report\n -o <file> also write that envelope to a file (requires --json)\n -h, --help show this\n\nNothing is written, installed, or configured. Every repository is read\nthrough git with --no-optional-locks, so a scan never contends for the\nindex lock with an editor you have open.\n\n--dirty is off by default because reading a working tree is unbounded\nwork: on a large or cloud-synced repo `git status` can take minutes,\nwhere listing branches takes milliseconds.\n\nlink shows what it would do and changes nothing; add --yes to apply it,\nor --to <dir> to choose where. It never uses sudo, never leaves your\nhome directory, and never touches a file it did not create.";
6
+ /** The commands people rarely type: the builder's own loop, the machine contract and recovery. They all keep working;
7
+ * `toolroll internals` (or `toolroll internals --help`) lists them so top-level help stays about the work. */
8
+ export declare const INTERNALS_HELP = "toolroll internals \u2014 machine and agent commands\n\n toolroll contract the machine contract: envelope version + capabilities\n (--commands dumps the declared command guide)\n toolroll skills list the guides this exact binary serves\n toolroll skills get <name> print one guide (version-matched, never stale)\n toolroll health recent server latency, write waits and live streams\n toolroll ready what could be started right now\n toolroll check-progress <run> current or final approved check progress\n toolroll task wait <id> [--timeout <seconds>]\n wait for this attempt to finish or need a person\n toolroll task scope <id> --goal <text>\n write a task's plan from the command line\n toolroll claim <id> --runner <name>\n toolroll heartbeat <lease> / worker release <lease> / reap\n toolroll tick --runner <name> --token <t> --repo <path>\n one unattended pass over the ready set\n toolroll serve --repo <path> run only the console\n (--editor vscode with --runner: file links\n open in VS Code on the device you browse from)\n toolroll watch / daemon install run only the builder\n toolroll doctor check that Toolroll's service holds no key a build could read\n toolroll reconcile recover what the last stretch left behind\n toolroll release --recover-deploy [--stage <dir>] [--yes]\n toolroll release --release-gate <id> [--yes]\n the release tool's recovery steps (toolroll release <branch> runs them for you)";
package/dist/cli-help.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * without loading every command family (see cli-fast.ts).
4
4
  */
5
5
  import { DEFAULT_MAX_DEPTH } from "./scan.js";
6
- export const HELP = `toolroll — a control plane for unattended coding agents
6
+ export const HELP = `toolroll — hand coding work to agents, walk away, come back to pull requests
7
7
 
8
8
  Usage
9
9
  toolroll at your terminal: start Toolroll here (or attach) and open it in the terminal
@@ -26,52 +26,43 @@ Usage
26
26
  toolroll unlink take it off again
27
27
  toolroll update update to the latest release: verified, drained, undoable
28
28
  (preview first; --yes, --now, --at HH:MM, --rollback)
29
- toolroll release <branch> release Toolroll itself: gate, your approval, deploy, merge, tag, publish
30
- (resumable: rerun to continue; --help)
31
- toolroll contract the machine contract: envelope version + capabilities
32
- (--commands dumps the declared command guide)
29
+ toolroll release <branch> release Toolroll itself: checks, your approval, deploy, merge, tag, publish
30
+ (rerun the same command to continue; --help)
33
31
  toolroll skills install --claude-code [--dir <path>]
34
32
  install this binary's guides for Claude Code (preview first)
35
33
  toolroll skills install teach a repo's agents this queue exists (preview first)
36
- toolroll skills list the guides this exact binary serves
37
- toolroll skills get <name> print one guide (version-matched, never stale)
38
34
  toolroll demo a seeded throwaway sandbox — see it working in 90 seconds
39
35
  toolroll up app + builder for every saved project — the normal start
40
36
  toolroll onboard make your agent Toolroll's lead: add this repo, install its skill (--yes), offer starter flows (--starter)
41
37
  toolroll status running, queued, ready results, release check and plan windows
42
- toolroll health recent server latency, write waits and live streams
43
38
  toolroll integrations which integrations work, and what to do about the ones that don't (--json)
44
- toolroll lead token mint your lead agent's credential: its own work pings nobody
39
+ toolroll lead token mint your lead agent's sign-in: its own work pings nobody
45
40
  toolroll lead say "<text>" (the lead) one short message in its person's chat
46
41
 
47
- Operating the queue — \`toolroll task\` prints the whole surface,
48
- and any queue command + --help prints it too
42
+ Tasks — \`toolroll task\` prints the whole list,
43
+ and any task command + --help prints it too
49
44
  toolroll approver add <name>
50
- mint the credential that lets a person say yes
51
- toolroll ready what could be dispatched right now
52
- toolroll check-progress <run> current or final approved check progress
53
- toolroll task wait <id> [--timeout <seconds>]
54
- wait for this attempt to finish or need a person
45
+ add a person who can approve work
55
46
  toolroll task add <title> queue work
56
- toolroll task scope <id> --goal <text>
57
- state what success is; approval binds to it
47
+ toolroll task show <id> one task: its plan, builds and what it waits for
58
48
  toolroll task approve <id>
59
49
  the yes — nothing builds without one
60
- toolroll claim <id> --runner <name>
61
- toolroll heartbeat <lease> / worker release <lease> / reap
62
- toolroll tick --runner <name> --token <t> --repo <path>
63
- one unattended pass over the ready set
64
- toolroll flows list | show <id> | create | edit | trigger | script | card | archive
65
- set up and inspect flows (writes preview until --yes)
50
+ toolroll task accept <id> --finish
51
+ accept the result you read (--despite "<why>": accept it with a reason, without finishing)
52
+ toolroll task send-back <id> --feedback "<what to change>"
53
+ send the result back for changes
54
+ toolroll task retry <id> build it again
55
+ toolroll task ready <id> can I merge it? each check, and what clears a blocker
66
56
  toolroll chat --as <you> --token <t>
67
57
  talk to your lead: one conversation across every
68
58
  project; it proposes, you confirm (--say "…" for one turn)
69
- toolroll serve --repo <path> advanced: run only the console
70
- (--editor vscode with --runner: file links
71
- open in VS Code on the device you browse from)
72
- toolroll watch / daemon install advanced: run only the builder
73
- toolroll doctor check that Toolroll's service holds no key a build could read
74
- toolroll reconcile recover what the last stretch left behind
59
+ toolroll card <source> the data chat shows as a card: tasks, results, flows, spend,
60
+ projects, builders, mode, integrations, providers, findings, handover
61
+ toolroll flows list | show <id> | create | edit | trigger | script | card | archive
62
+ set up and inspect flows (writes preview until --yes)
63
+
64
+ Machine and agent commands (the builder, agent tools, recovery):
65
+ toolroll internals lists them; each keeps working as before
75
66
 
76
67
  With nothing connected it reports everything it can find below the working
77
68
  directory. Once you connect repositories it reports those instead.
@@ -97,3 +88,31 @@ where listing branches takes milliseconds.
97
88
  link shows what it would do and changes nothing; add --yes to apply it,
98
89
  or --to <dir> to choose where. It never uses sudo, never leaves your
99
90
  home directory, and never touches a file it did not create.`;
91
+ /** The commands people rarely type: the builder's own loop, the machine contract and recovery. They all keep working;
92
+ * `toolroll internals` (or `toolroll internals --help`) lists them so top-level help stays about the work. */
93
+ export const INTERNALS_HELP = `toolroll internals — machine and agent commands
94
+
95
+ toolroll contract the machine contract: envelope version + capabilities
96
+ (--commands dumps the declared command guide)
97
+ toolroll skills list the guides this exact binary serves
98
+ toolroll skills get <name> print one guide (version-matched, never stale)
99
+ toolroll health recent server latency, write waits and live streams
100
+ toolroll ready what could be started right now
101
+ toolroll check-progress <run> current or final approved check progress
102
+ toolroll task wait <id> [--timeout <seconds>]
103
+ wait for this attempt to finish or need a person
104
+ toolroll task scope <id> --goal <text>
105
+ write a task's plan from the command line
106
+ toolroll claim <id> --runner <name>
107
+ toolroll heartbeat <lease> / worker release <lease> / reap
108
+ toolroll tick --runner <name> --token <t> --repo <path>
109
+ one unattended pass over the ready set
110
+ toolroll serve --repo <path> run only the console
111
+ (--editor vscode with --runner: file links
112
+ open in VS Code on the device you browse from)
113
+ toolroll watch / daemon install run only the builder
114
+ toolroll doctor check that Toolroll's service holds no key a build could read
115
+ toolroll reconcile recover what the last stretch left behind
116
+ toolroll release --recover-deploy [--stage <dir>] [--yes]
117
+ toolroll release --release-gate <id> [--yes]
118
+ the release tool's recovery steps (toolroll release <branch> runs them for you)`;
package/dist/cli.js CHANGED
@@ -37,7 +37,8 @@ import { detectGraphs, chooseBackend, setupOptions, LABELS as GRAPH_LABELS, } fr
37
37
  import { openStore, openStoreReadOnly, databasePath } from "./store.js";
38
38
  import { renderReport, renderPulls, renderGraph } from "./render.js";
39
39
  import { DEFAULT_MAX_DEPTH } from "./scan.js";
40
- import { HELP } from "./cli-help.js";
40
+ import { HELP, INTERNALS_HELP } from "./cli-help.js";
41
+ import { withTaskAlias } from "./task-aliases.js";
41
42
  import { quickAnswer } from "./cli-fast.js";
42
43
  import { BIN_NAME, chooseTarget, planLink, applyLink, planUnlink, applyUnlink, } from "./link.js";
43
44
  const USAGE_EXIT = 2;
@@ -137,7 +138,13 @@ export const OPERATE_COMMANDS = new Set([
137
138
  "review",
138
139
  "assignment",
139
140
  "knowledge",
141
+ "map",
140
142
  "memory",
143
+ "flaky",
144
+ "card",
145
+ // Follow-through (follow-through-cli.ts): findings and saved provider failures.
146
+ "finding",
147
+ "captures",
141
148
  "models",
142
149
  "claim",
143
150
  "heartbeat",
@@ -305,6 +312,8 @@ function routinesMoved(argv, write) {
305
312
  return 0;
306
313
  }
307
314
  async function dispatch(argv, write, mainOptions) {
315
+ // People's names for task commands (task send-back, retry, recheck) are the commands they name, before anything else.
316
+ argv = withTaskAlias(argv);
308
317
  // v115: routines became scheduled flows. Every old `routine …` invocation answers with one line pointing there —
309
318
  // hidden from help and the contract, before anything remote or local opens, changing nothing.
310
319
  if (argv[0] === "routine")
@@ -321,6 +330,11 @@ async function dispatch(argv, write, mainOptions) {
321
330
  // Command families below load when their command runs, so one command never pays for the others.
322
331
  if (argv[0] === 'health')
323
332
  return (await import("./health-cli.js")).runHealthCommand(argv.slice(1), write, mainOptions.connect ?? mainOptions.remote);
333
+ // The machine and agent commands, listed apart so top-level help stays about the work (each still runs by name).
334
+ if (argv[0] === 'internals' && argv.slice(1).every(arg => arg === '--help' || arg === '-h')) {
335
+ write(INTERNALS_HELP);
336
+ return 0;
337
+ }
324
338
  // This computer's own service, read here and never on a central server.
325
339
  if (argv[0] === 'doctor')
326
340
  return (await import("./doctor.js")).runDoctorCommand(argv.slice(1), write);