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
@@ -2,6 +2,8 @@
2
2
  * State, decisions, approval, process custody and proof still belong to
3
3
  * their existing owners. Action hints are navigation/proposal guidance,
4
4
  * never credentials or permission to bypass an owning operation. */
5
+ import { heldFindingCount } from "./findings.js";
6
+ import { machineChecked } from "./machine-check.js";
5
7
  import { resultCheckLevel } from "./result-follow-ups.js";
6
8
  import { diagnoseTaskDispatch } from "./dispatch.js";
7
9
  import { isAlive } from "./runner.js";
@@ -27,7 +29,7 @@ export function workDecisionAction(taskId, decision, principal) {
27
29
  access: principal === "operator" ? "operator-control" : "proposal-only", retry: "refresh-before-acting" };
28
30
  }
29
31
  const PROPOSABLE = new Set(["answer-decision", "write-scope", "unhold"]);
30
- const READ_ACTIONS = new Set(["open-result", "open-pr", "inspect-task", "inspect-run", "inspect-stop", "inspect-decisions"]);
32
+ const READ_ACTIONS = new Set(["open-result", "open-pr", "inspect-task", "inspect-findings", "inspect-run", "inspect-stop", "inspect-decisions"]);
31
33
  /** Pure counterpart for callers already holding the same facts. */
32
34
  export function workSummaryOf(facts, principal, resultDisplay) {
33
35
  const status = workStatusOf(facts, resultDisplay);
@@ -47,6 +49,8 @@ export function workSummaryOf(facts, principal, resultDisplay) {
47
49
  primary = make("resume-run", status.action.label, { runId: facts.control.run });
48
50
  else if (status.token === "waiting-decision" && questionAction !== null)
49
51
  primary = questionAction;
52
+ else if (status.token === "finding-held")
53
+ primary = make("inspect-findings", status.action.label);
50
54
  else if (facts.dispatch?.code === "vanished-run")
51
55
  primary = make("reconcile-run", "Check the unfinished attempt", { runId: facts.unfinishedRunId ?? null });
52
56
  else if (status.action.kind === "open-result" || status.action.kind === "open-review")
@@ -94,9 +98,10 @@ export function taskWorkSummaryOf(store, taskId, now, access) {
94
98
  const publication = result === null ? null : store.publicationForRun(result.id);
95
99
  const facts = {
96
100
  id: task.id, title: task.title, repo: ref.repo, state: task.state, updatedAt: task.updatedAt,
101
+ ...(machineChecked(store, task.id) ? { machine: true } : {}),
97
102
  dispatch: diagnoseTaskDispatch(store, taskId, now, access.dependencyRepos ?? null), liveRunId: live?.id ?? null,
98
103
  unfinishedRunId: runs.find(one => one.outcome === null && one.role !== "reviewer")?.id ?? null,
99
- control: taskControlOf(store, ref.id, now), openDecision: openWorkDecisionOf(store, ref.id, now),
104
+ control: taskControlOf(store, ref.id, now), openDecision: openWorkDecisionOf(store, ref.id, now), heldFindings: heldFindingCount(store, taskId),
100
105
  result: result === null ? null : {
101
106
  runId: result.id, role: result.role, outcome: result.outcome,
102
107
  verdict: proof?.verdict ?? null, reasons: proof?.reasons ?? [], accepted: store.proofAcceptance(result.id) !== null,
@@ -26,7 +26,7 @@ import { type ChecksFact } from "./task-status.js";
26
26
  import { type Html } from "./html.js";
27
27
  /** The Work destination's views — shortcuts over the same rows, never a
28
28
  * persisted state. All is the default. */
29
- export type WorkView = "all" | "needs-you" | "running" | "queued" | "completed" | "archived";
29
+ export type WorkView = "all" | "needs-you" | "ready" | "running" | "queued" | "completed" | "archived";
30
30
  export declare const WORK_VIEWS: readonly {
31
31
  key: WorkView;
32
32
  label: string;
@@ -106,7 +106,7 @@ export type NextAction = {
106
106
  label: string;
107
107
  /** Where the act lives — the page, not a URL, so every surface can
108
108
  * build its own link (a task page anchors, a row links out). */
109
- kind: "open-result" | "open-review" | "open-task" | "open-run" | "open-pr";
109
+ kind: "open-result" | "open-review" | "open-task" | "open-findings" | "open-run" | "open-pr";
110
110
  };
111
111
  export type DisplayStatus = {
112
112
  /** The stable `data-work-status` token pages and tests key off. */
@@ -154,6 +154,8 @@ export declare function receiptPublicationWords(publication: PublicationFacts):
154
154
  * already reads elsewhere. */
155
155
  export type WorkFacts = {
156
156
  id: string;
157
+ /** The machine runs this task's check on a prepared candidate; no agent runs (machine-check.ts). */
158
+ machine?: boolean;
157
159
  title: string;
158
160
  repo: string | null;
159
161
  state: TaskState;
@@ -173,6 +175,8 @@ export type WorkFacts = {
173
175
  question: string;
174
176
  overdue: boolean;
175
177
  } | null;
178
+ /** Findings of this task's family that used their rounds and wait on a person (findings.ts). */
179
+ heldFindings?: number;
176
180
  };
177
181
  export type WorkStatus = DisplayStatus & {
178
182
  /** Which shortcut views list this row; `all` always does. */
@@ -233,7 +237,7 @@ export declare function lastErrorLineOf(log: string): {
233
237
  text: string;
234
238
  } | null;
235
239
  /** The result page's own sentence when the decision is on it. */
236
- export declare const RESULT_DECISION_SENTENCE = "Review the change, then accept it or ask for changes.";
240
+ export declare const RESULT_DECISION_SENTENCE = "Review the change, then accept it or send it back.";
237
241
  /** What the result page's Accept says, from facts it already shows: the
238
242
  * recorded checks, the requirements not met, whether the saved proof reads,
239
243
  * and what the button posts. "Accept and finish" only when everything is met
@@ -16,22 +16,25 @@
16
16
  * observed" name observed publication records, and deployment is never
17
17
  * claimed without one.
18
18
  */
19
+ import { lastIncludedWords } from './memory-use.js';
19
20
  import { ACCEPT_NEEDS_REASON, FAILED_CHECK, cantAcceptYetOf, evidenceProblemOf } from "./result-acts.js";
20
21
  export { ACCEPT_NEEDS_REASON, cantAcceptYetOf, evidenceProblemOf };
21
22
  import { manualReviewOnly, plainReasonWords } from "./proof.js";
22
23
  import { CHECK_COMMAND_CHANGED, CHECK_UNREADABLE, OPEN_RESULT, approvedCheckOf, approvedCheckStateOf, plainReasonOf, stageOfCode, taskStatusOf, workToneOf } from "./task-status.js";
23
24
  import { whenUtc } from "./when-html.js";
25
+ import { NEEDS } from "./needs-you.js";
24
26
  import { html, postForm } from "./html.js";
25
27
  export const WORK_VIEWS = [
26
28
  { key: "all", label: "All", hint: "Every task in view, most urgent first.", empty: "Nothing is in progress. Describe work in chat or add a task, and it appears here." },
27
29
  { key: "needs-you", label: "Needs you", hint: "Tasks waiting on an answer, approval, or your inspection.", empty: "Nothing needs you right now. Queued and running work continues on its own." },
30
+ { key: "ready", label: "Ready", hint: "Results waiting for you to accept or send back.", empty: "Nothing is ready for review." },
28
31
  { key: "running", label: "Running", hint: "Attempts a builder owns right now.", empty: "Nothing is running right now. Approved tasks start when a builder with capacity is connected." },
29
32
  { key: "queued", label: "Queued", hint: "Tasks that start on their own: waiting for a builder, a dependency or a retry.", empty: "Nothing is queued." },
30
- { key: "completed", label: "Finished", hint: "Tasks the lead or user has marked complete. Recorded check results remain available.", empty: "Nothing has finished in this view yet." },
31
- { key: "archived", label: "Archived", hint: "Tasks put away by you or by tidy. Their history and results are kept.", empty: "Nothing is put away." },
33
+ { key: "completed", label: "Finished", hint: "Tasks the lead or you accepted. Recorded check results stay available.", empty: "Nothing has finished in this view yet." },
34
+ { key: "archived", label: "Archived", hint: "Tasks archived by you or automatically. Their history and results are kept.", empty: "Nothing is archived." },
32
35
  ];
33
36
  export function parseWorkView(raw) {
34
- return raw === "needs-you" || raw === "running" || raw === "queued" || raw === "completed" || raw === "archived" ? raw : "all";
37
+ return raw === "needs-you" || raw === "ready" || raw === "running" || raw === "queued" || raw === "completed" || raw === "archived" ? raw : "all";
35
38
  }
36
39
  /** The review facts from the store's bounded retry projection — the same
37
40
  * reading `diagnoseTaskDispatch` makes, so every surface starts from one
@@ -141,8 +144,8 @@ function machineVerdictWords(result, check) {
141
144
  return `the approved check failed against it${exit === null ? "" : ` (exit ${exit})`}`;
142
145
  }
143
146
  if (problem === "mismatched")
144
- return "its evidence did not match the sealed record";
145
- return "its required evidence was missing";
147
+ return "its saved files didn't match the record";
148
+ return "its required files were missing";
146
149
  }
147
150
  /** The approved check passed (task-status.ts approvedCheckStateOf): its own pass, or checks Off covered by a passing
148
151
  * release check. A re-run underway keeps the pass it had. */
@@ -180,7 +183,7 @@ function storedResultStatusOf(result, publication) {
180
183
  // Acceptance records a person's decision; it neither proves nor
181
184
  // disproves anything about the checks. The machine's own verdict is
182
185
  // restated as recorded.
183
- detail: withPublication(`An approver accepted this result by hand, and the recorded exception says why. The machine's verdict is unchanged: ${machineVerdictWords(result, check)}.`),
186
+ detail: withPublication(`An approver accepted this result by hand, and their reason says why. The checks are unchanged: ${machineVerdictWords(result, check)}.`),
184
187
  tone: "done",
185
188
  action: { label: "Open the recorded exception", kind: "open-review" },
186
189
  };
@@ -228,16 +231,16 @@ function storedResultStatusOf(result, publication) {
228
231
  // at adjudication; only a verdict-less no-change reads its two
229
232
  // presence facts here.
230
233
  return result.recordComplete === false && result.verdict === null
231
- ? { token: "record-incomplete", label: "No-change result, record incomplete", detail: "The build concluded nothing needed to change, but its handoff or sealed diff is missing.", tone: "problem", action: { label: "Open the record", kind: "open-run" } }
232
- : { token: "no-change", label: "No changes were needed", detail: withPublication("The build concluded nothing needed to change; its handoff and sealed diff are on record."), tone: "done", action: { label: OPEN_RESULT, kind: "open-result" } };
234
+ ? { token: "record-incomplete", label: "No-change result, record incomplete", detail: "The build concluded nothing needed to change, but its handoff or saved diff is missing.", tone: "problem", action: { label: "Open the record", kind: "open-run" } }
235
+ : { token: "no-change", label: "No changes were needed", detail: withPublication("The build concluded nothing needed to change; its handoff and saved diff are on record."), tone: "done", action: { label: OPEN_RESULT, kind: "open-result" } };
233
236
  }
234
237
  if (result.verdict === null) {
235
238
  return {
236
239
  token: "verification-needed",
237
- label: "Result saved — verification needed",
238
- detail: withPublication("No verification result is recorded for this build."),
240
+ label: "No check result for this build yet",
241
+ detail: withPublication("No check result for this build yet. Run checks on it."),
239
242
  tone: "problem",
240
- action: { label: "See what is missing", kind: "open-review" },
243
+ action: { label: "Run checks", kind: "open-review" },
241
244
  };
242
245
  }
243
246
  // Any verdict short of a pass the check confirms: never "passed" when the check can't be read now.
@@ -250,8 +253,8 @@ function storedResultStatusOf(result, publication) {
250
253
  if (result.verdict === "attested" && !passedNow(check)) {
251
254
  return {
252
255
  token: "agent-attested",
253
- label: "Result saved — checks reported by the agent",
254
- detail: withPublication("No independent project check ran; the checks listed are the agent's own report."),
256
+ label: "Ready · no project check set up",
257
+ detail: withPublication("No project check is set up, so the checks listed are the agent's own report."),
255
258
  tone: "neutral",
256
259
  action: { label: OPEN_RESULT, kind: "open-result" },
257
260
  };
@@ -322,11 +325,14 @@ const DISPATCH_ACTION_LABELS = {
322
325
  "inspect-hold": "Review hold",
323
326
  "repair-dependency": "Review required task",
324
327
  "repair-capability": "Review missing requirement",
325
- "start-worker": "Check connection",
328
+ "start-worker": "Start the builder",
326
329
  "retry-review": "Review retry options",
327
330
  "resume-run": "Review pause",
328
331
  };
329
332
  export function dispatchActionLabel(dispatch) {
333
+ // One label everywhere for the builder (needs-you.ts NEEDS): Connect a builder, or Start the builder.
334
+ if (dispatch?.action === "start-worker")
335
+ return dispatch.code === "no-worker-registered" ? NEEDS["connect-builder"].action.label : NEEDS["start-builder"].action.label;
330
336
  return dispatch?.action == null ? "View task details" : DISPATCH_ACTION_LABELS[dispatch.action];
331
337
  }
332
338
  /** Result tokens a done task can wear, as the shared stage they mean. A
@@ -376,7 +382,7 @@ export function reportMismatchesOf(reasons, criteria, changes) {
376
382
  return [{ text: plainReasonWords(reason), ...place(review[1]), note: null, reason }];
377
383
  const unsigned = UNSIGNED_NOTE.exec(reason);
378
384
  if (unsigned !== null)
379
- return [{ text: `The report's note ${unsigned[1]} is about ${unsigned[2]}, which isn't one of the signed requirements: ${unsigned[3]}`, path: null, lines: null, inChanges: null, note: Number(unsigned[1]), reason }];
385
+ return [{ text: `The report's note ${unsigned[1]} is about ${unsigned[2]}, which isn't one of the approved requirements: ${unsigned[3]}`, path: null, lines: null, inChanges: null, note: Number(unsigned[1]), reason }];
380
386
  if (FAILED_CHECK.test(reason))
381
387
  return [];
382
388
  return [{ text: sentenceOf(plainReasonWords(reason)), path: null, lines: null, inChanges: null, note: note === null ? null : Number(note), reason }];
@@ -419,7 +425,7 @@ export function lastErrorLineOf(log) {
419
425
  return (stderr >= 0 ? pick(stderr + 1, lines.length) : null) ?? pick(0, stderr >= 0 ? stderr : lines.length);
420
426
  }
421
427
  /** The result page's own sentence when the decision is on it. */
422
- export const RESULT_DECISION_SENTENCE = "Review the change, then accept it or ask for changes.";
428
+ export const RESULT_DECISION_SENTENCE = "Review the change, then accept it or send it back.";
423
429
  export function acceptWordsOf(facts) {
424
430
  const plural = (n, one, many) => `${n} ${n === 1 ? one : many}`;
425
431
  const parts = [
@@ -435,7 +441,7 @@ export function acceptWordsOf(facts) {
435
441
  : facts.publishing === "pull-request" ? "Finishes the task. No pull request opens; you can open one from the task after."
436
442
  : facts.publishing === "off" ? "Finishes the task. The branch stays; publishing isn't set up."
437
443
  : "Finishes the task. Nothing is published.";
438
- return { label: ready ? "Accept and finish" : "Accept without checks", ready, why: ready ? null : `${joined.charAt(0).toUpperCase()}${joined.slice(1)}.`, effect };
444
+ return { label: ready ? "Accept" : "Accept without checks", ready, why: ready ? null : `${joined.charAt(0).toUpperCase()}${joined.slice(1)}.`, effect };
439
445
  }
440
446
  export function resultHeadlineOf(status) {
441
447
  const read = RESULT_NEEDS_YOU.has(status.token) ? { stage: "needs-you", need: "other" }
@@ -454,8 +460,8 @@ export function workStatusOf(facts, resultDisplay) {
454
460
  : stageOfCode(status.token, { needsPerson: status.views.includes("needs-you"), planning: facts.dispatch?.code === "running" && facts.dispatch.role === "planner", operatorHold: facts.dispatch?.action === "unhold" });
455
461
  const stage = done && read.stage !== "needs-you" && read.stage !== "failed" ? "finished" : read.stage;
456
462
  const level = facts.result?.checkLevel ?? null;
457
- const shared = taskStatusOf({ stage, ...(read.need === undefined ? {} : { need: read.need }), reason: plainReasonOf(stage, status.token, status.detail),
458
- report: facts.result?.role === "scout", checks: stage !== "finished" ? null : VERIFIED_RESULT.has(status.token) ? { status: "passed", exitCode: null, head: null, level }
463
+ const shared = taskStatusOf({ stage, ...(read.need === undefined ? {} : { need: read.need }), ...(read.wait === undefined ? {} : { wait: read.wait }), reason: plainReasonOf(stage, status.token, status.detail),
464
+ report: facts.result?.role === "scout", ...(facts.machine === true ? { machine: true } : {}), checks: stage !== "finished" ? null : VERIFIED_RESULT.has(status.token) ? { status: "passed", exitCode: null, head: null, level }
459
465
  : level === "off" ? { status: "not-run", exitCode: null, head: null, level } : null });
460
466
  return { ...status, label: shared.headline, tone: workToneOf(shared.headline), detail: stage === "finished" && level !== "off" ? status.detail : shared.sentence };
461
467
  }
@@ -464,7 +470,8 @@ function workStatusWordsOf(facts, resultDisplay) {
464
470
  const result = facts.state === "done" ? resultDisplay ?? resultStatusOf(facts.result, facts.publication) : null;
465
471
  const running = facts.liveRunId !== null || (dispatch?.condition === "running" && !REVIEW_TOKENS.has(dispatch.code));
466
472
  const question = (facts.state === "queued" || facts.state === "running" || facts.state === "done") ? facts.openDecision ?? null : null;
467
- const needs = (dispatch !== null && !REVIEW_TOKENS.has(dispatch.code) && needsPerson(dispatch)) || question !== null;
473
+ const held = (facts.heldFindings ?? 0) > 0 && facts.state !== "cancelled";
474
+ const needs = (dispatch !== null && !REVIEW_TOKENS.has(dispatch.code) && needsPerson(dispatch)) || question !== null || held;
468
475
  const views = ["all"];
469
476
  if (needs)
470
477
  views.push("needs-you");
@@ -474,6 +481,10 @@ function workStatusWordsOf(facts, resultDisplay) {
474
481
  views.push("queued");
475
482
  if (facts.state === "done")
476
483
  views.push("completed");
484
+ const findingWords = (behind) => ({
485
+ token: "finding-held", label: "Needs you", detail: NEEDS.finding.sentence({}), tone: "attention", action: { label: NEEDS.finding.action.label, kind: "open-findings" }, views, rank: 0,
486
+ ...(behind === null ? {} : { diagnostics: [behind] }),
487
+ });
477
488
  const dispatchWords = (tone, rank, action) => ({
478
489
  token: dispatch?.code ?? "unknown",
479
490
  label: dispatch?.summary ?? "Status unknown",
@@ -486,7 +497,7 @@ function workStatusWordsOf(facts, resultDisplay) {
486
497
  // The control projection knows whether a stop has actually settled.
487
498
  // A still-live claim alone cannot distinguish Running from Stopping.
488
499
  if (facts.control?.kind === "stopping") {
489
- return { token: "stopping", label: "Stopping…", detail: facts.control.detail ?? "The attempt is ending. Its work is preserved; resume becomes available after its processes exit.", tone: "attention", action: { label: "View stop details", kind: "open-task" }, views, rank: 0 };
500
+ return { token: "stopping", label: "Stopping…", detail: facts.control.detail ?? "The attempt is ending. Its work is preserved; resume becomes available after its processes exit.", tone: "attention", action: { label: "View stop details", kind: "open-task" }, views, rank: 1 };
490
501
  }
491
502
  if (facts.control?.kind === "paused") {
492
503
  return { token: "stopped", label: "Paused", detail: "The stopped attempt's work is preserved. Resuming requires confirmation and checks the current approval again.", tone: "attention", action: { label: "Review pause", kind: "open-task" }, views, rank: 0 };
@@ -499,6 +510,8 @@ function workStatusWordsOf(facts, resultDisplay) {
499
510
  tone: "attention", action: { label: "Answer question", kind: "open-task" }, views, rank: 0,
500
511
  diagnostics: [{ token: result.token, label: result.label, detail: result.detail, tone: result.tone }] };
501
512
  }
513
+ if (held && result.tone !== "problem")
514
+ return findingWords({ token: result.token, label: result.label, detail: result.detail, tone: result.tone });
502
515
  return { ...result, views, rank: needs ? 0 : 3 };
503
516
  }
504
517
  if (facts.state === "cancelled") {
@@ -513,6 +526,8 @@ function workStatusWordsOf(facts, resultDisplay) {
513
526
  }),
514
527
  };
515
528
  }
529
+ if (held && facts.state !== "failed")
530
+ return findingWords(dispatch === null ? null : { token: dispatch.code, label: dispatch.summary, detail: dispatch.detail, tone: running ? "live" : "attention" });
516
531
  if (running) {
517
532
  return dispatchWords("live", 1, { label: "Watch the build", kind: facts.liveRunId === null ? "open-task" : "open-run" });
518
533
  }
@@ -529,7 +544,7 @@ function workStatusWordsOf(facts, resultDisplay) {
529
544
  }
530
545
  /** The counts each view tab wears — from the same rows the page lists. */
531
546
  export function workCounts(rows) {
532
- const counts = { all: 0, "needs-you": 0, running: 0, queued: 0, completed: 0, archived: 0 };
547
+ const counts = { all: 0, "needs-you": 0, ready: 0, running: 0, queued: 0, completed: 0, archived: 0 };
533
548
  for (const row of rows)
534
549
  for (const view of row.views)
535
550
  counts[view] += 1;
@@ -557,7 +572,7 @@ export function learningHtml(view, csrf, canManage, source) {
557
572
  const act = (action, label, lesson) => canManage && csrf ? postForm('/settings/learning/change', html `<button type="submit">${label}</button>`, { hidden: { ...base, action, ...(lesson ? { lesson: lesson.id, version: lesson.version, sha: lesson.sha } : {}) } }) : '';
558
573
  const evidence = (list, run) => list.map(one => html `<li><a href="/r/${String(one.runId ?? run)}/evidence/${one.artifactId}">Source #${one.artifactId}</a><blockquote>${one.excerpt}</blockquote><code>${one.sha256}</code></li>`);
559
574
  const lessons = view.lessons.filter(l => source === undefined || l.source === source);
560
- const cards = lessons.map(l => html `<article class="card" data-lesson="${l.id}"><p><strong>${l.payload.observation}</strong></p><p>${l.payload.action}</p><p class="meta">${l.payload.kind === 'system' ? 'System suggestion · No change applied' : l.status === 'adopted' ? 'Adopted advice' : l.status === 'disabled' ? 'Disabled' : 'Proposed lesson'}</p><details><summary>Source and use</summary><p><a href="/r/${l.source}">Result #${l.source}</a> · <a href="/r/${l.reviewer}">Review #${l.reviewer}</a></p><p>${l.payload.paths.join(', ')} · ${l.payload.phases.join(', ')} · ${{ darwin: "macOS", win32: "Windows", linux: "Linux" }[l.payload.platform] ?? l.payload.platform}</p><ul>${evidence(l.payload.evidence, l.source)}</ul>${l.payload.kind !== 'project' ? '' : html `<p class="meta">${l.status === 'proposed' ? 'Save permits advisory reuse when enabled and applicable. It does not prove a remedy works.' : 'Disabling affects future runs. Active snapshots, code and approvals stay unchanged.'}</p>${l.status === 'proposed' ? act('adopt', 'Save lesson', l) : ''}`}</details>${l.payload.kind === 'project' && l.status === 'adopted' ? act('disable', 'Disable lesson', l) : ''}</article>`);
575
+ const cards = lessons.map(l => html `<article class="card" data-lesson="${l.id}"><p><strong>${l.payload.observation}</strong></p><p>${l.payload.action}</p><p class="meta">${lastIncludedWords(view.use.get(`lesson:${l.id}`))}</p><p class="meta">${l.payload.kind === 'system' ? 'System suggestion · No change applied' : l.status === 'adopted' ? 'Adopted advice' : l.status === 'disabled' ? 'Disabled' : 'Proposed lesson'}</p><details><summary>Source and use</summary><p><a href="/r/${l.source}">Result #${l.source}</a> · <a href="/r/${l.reviewer}">Review #${l.reviewer}</a></p><p>${l.payload.paths.join(', ')} · ${l.payload.phases.join(', ')} · ${{ darwin: "macOS", win32: "Windows", linux: "Linux" }[l.payload.platform] ?? l.payload.platform}</p><ul>${evidence(l.payload.evidence, l.source)}</ul>${l.payload.kind !== 'project' ? '' : html `<p class="meta">${l.status === 'proposed' ? 'Save permits advisory reuse when enabled and applicable. It does not prove a remedy works.' : 'Disabling affects future runs. Active snapshots, code and approvals stay unchanged.'}</p>${l.status === 'proposed' ? act('adopt', 'Save lesson', l) : ''}`}</details>${l.payload.kind === 'project' && l.status === 'adopted' ? act('disable', 'Disable lesson', l) : ''}</article>`);
561
576
  if (source !== undefined)
562
577
  return lessons.length ? html `<details class="learning result-learning"><summary>Learned from this task</summary>${cards}<a href="/settings/learning?repo=${encodeURIComponent(view.repo)}">Learning settings</a></details>` : html ``;
563
578
  const history = view.events.map(ev => html `<article class="card" data-learning-event="${ev.action}"><p><strong>${{ assessment: { propose: 'Learning suggested', none: 'No lesson needed', unassessed: 'Learning not assessed', invalid: 'Learning assessment invalid' }[ev.after] ?? 'Learning not assessed', proposal: 'Suggestion recorded', adopt: 'Lesson adopted', disable: 'Lesson disabled', reset: 'Learning reset', enable: 'Reuse enabled', pause: 'Reuse paused', reuse: 'Run context saved', failure: 'Learning issue', capture: 'Capture finished' }[ev.action] ?? ev.action}</strong>${ev.action === "reuse" ? ` · ${ev.after}` : ""}</p><p class="meta">${whenUtc(ev.at)} · ${ev.actor}${ev.run === null ? '' : html ` · <a href="/r/${ev.run}">Run #${ev.run}</a>${ev.outcome === null ? "" : ` · ${ev.outcome}`}`}</p><details><summary>Details</summary><p>${ev.before} → ${ev.after}</p><p>${ev.reason}</p>${ev.lesson === null ? '' : html `<p>Lesson #${ev.lesson}</p>`}<ul>${evidence(ev.evidence, ev.run)}</ul>${ev.snapshot === null ? "" : html `<details><summary>Exact context</summary><pre>${ev.snapshot}</pre></details>`}</details></article>`);
@@ -96,7 +96,7 @@ export type LeaseResult = {
96
96
  };
97
97
  export type LeaseFailure = "held" | "dirty" | "git" | "unverified" | "unknown-runner" | "in-use";
98
98
  /** Why storage retention left a checkout in place. */
99
- export type KeptWhy = "has changes" | "has commits" | "unreadable" | "git refused";
99
+ export type KeptWhy = "has changes" | "has commits" | "not on its branch" | "unreadable" | "git refused";
100
100
  /**
101
101
  * The note a lease leaves in the checkout, naming the process holding it.
102
102
  *
@@ -291,15 +291,24 @@ export declare class WorktreePool {
291
291
  * or remote. Reads only, through `git` when given (teardown's hardened git).
292
292
  */
293
293
  inspect(path: string, git?: Git): Promise<"clean" | KeptWhy>;
294
+ /**
295
+ * Whether a checkout a task may still come back to can go, beyond `inspect`: a later lease of its branch must make the
296
+ * same checkout, and every commit its task recorded must outlive it (git's garbage collection counts a worktree's HEAD
297
+ * and reflog; once they go only a branch holds a commit). So: the folder is the pool's own, the checkout belongs to its
298
+ * project by the plane's record (`boundDirs`, not its `.git` file), its HEAD is its recorded branch, every recorded
299
+ * `heads` commit is on a branch, and no ref lives only in this worktree. Reads only; anything unreadable keeps it.
300
+ */
301
+ proveRecreatable(row: WorktreeRow, heads: readonly string[]): Promise<"clean" | KeptWhy>;
294
302
  /**
295
303
  * Keep storage in check: remove the working copies nobody needs any more. Of `rows`, a checkout goes when it is
296
304
  * let go, nothing holds it, `wanted` says it may go (asked again right before it goes, so a task coming back or a
297
305
  * lease since keeps it) and `inspect` finds it clean, then clean still right before it goes (git refuses an edit
298
306
  * made after that). Only the working copy goes: its branch, and so every commit,
299
307
  * stays, and a later lease of the same branch makes a new one. At most `max` go per pass. Every git command goes
300
- * through `git` when given: landed cleanup passes its hardened teardown git (teardown-git.ts).
308
+ * through `git` when given: landed cleanup passes its hardened teardown git (teardown-git.ts). `proof` is asked last,
309
+ * inside the reservation, for what `inspect` doesn't cover (idle cleanup: `proveRecreatable`).
301
310
  */
302
- prune(rows: readonly WorktreeRow[], wanted: (row: WorktreeRow, fresh: boolean) => boolean | Promise<boolean>, max?: number, git?: Git): Promise<{
311
+ prune(rows: readonly WorktreeRow[], wanted: (row: WorktreeRow, fresh: boolean) => boolean | Promise<boolean>, max?: number, git?: Git, proof?: (row: WorktreeRow) => Promise<"clean" | KeptWhy>): Promise<{
303
312
  removed: WorktreeRow[];
304
313
  kept: {
305
314
  path: string;
package/dist/worktree.js CHANGED
@@ -22,17 +22,19 @@
22
22
  * synced folder like OneDrive or Dropbox, where a sync client racing an agent
23
23
  * over `.git` internals corrupts both.
24
24
  */
25
+ import { INCIDENT_QUIET_MS, recentGitIncident } from "./git-incident.js";
25
26
  import { createHash, randomBytes } from "node:crypto";
26
27
  import { existsSync, lstatSync, readFileSync, writeFileSync, rmSync, realpathSync, mkdirSync, readdirSync, renameSync } from "node:fs";
27
28
  import { hostname } from "node:os";
28
29
  import { basename, dirname, join, relative, resolve, sep } from "node:path";
29
30
  import { run } from "./exec.js";
30
31
  import { currentBootId, provenDeadByBootChange } from "./boot-identity.js";
32
+ import { currentMachineId, isThisMachine } from "./machine-identity.js";
31
33
  import { HANDOFF_PREFIX, MAILBOX_SUFFIX, looksLikeProtocolFile, readMailbox } from "./evidence.js";
32
34
  import { parseHandoff } from "./decision.js";
33
35
  import { diskBytes } from "./storage.js";
34
36
  import { markNeverIndex } from "./never-index.js";
35
- import { checkoutGit, teardownGit } from "./teardown-git.js";
37
+ import { checkoutGit, gitIncidentRecord, teardownGit } from "./teardown-git.js";
36
38
  /**
37
39
  * The note a lease leaves in the checkout, naming the process holding it.
38
40
  *
@@ -55,7 +57,8 @@ const reservationPath = (path) => `${path}.occupancy`;
55
57
  function reservation(path) {
56
58
  try {
57
59
  const note = JSON.parse(readFileSync(reservationPath(path), "utf8"));
58
- if (Number.isSafeInteger(note.pid) && note.pid > 0 && typeof note.host === "string" && typeof note.token === "string")
60
+ if (Number.isSafeInteger(note.pid) && note.pid > 0 && typeof note.host === "string" && typeof note.token === "string"
61
+ && (note.machineId === undefined || note.machineId === null || typeof note.machineId === "string"))
59
62
  return note;
60
63
  }
61
64
  catch (error) {
@@ -66,9 +69,9 @@ function reservation(path) {
66
69
  return { pid: 0, host: "unknown", bootId: null, token: "unknown" };
67
70
  }
68
71
  function reservationAlive(note) {
69
- if (note.pid === 0 || note.host !== hostname())
72
+ if (note.pid === 0 || !isThisMachine(note))
70
73
  return true;
71
- if (note.bootId !== null && provenDeadByBootChange(note))
74
+ if (note.bootId !== null && provenDeadByBootChange(note, { host: note.host, bootId: currentBootId() }))
72
75
  return false;
73
76
  try {
74
77
  process.kill(note.pid, 0);
@@ -81,7 +84,7 @@ function reservationAlive(note) {
81
84
  /** A short store transaction arbitrates marker creation/recovery, never git, a scan or deletion.
82
85
  * Atomic rename publishes a complete owner; a crash cannot leave a half-written reservation. */
83
86
  function reserveCheckout(store, path) {
84
- const note = { pid: process.pid, host: hostname(), bootId: currentBootId(), token: randomBytes(12).toString("hex") };
87
+ const note = { pid: process.pid, host: hostname(), bootId: currentBootId(), token: randomBytes(12).toString("hex"), machineId: currentMachineId() };
85
88
  const file = reservationPath(path), temp = `${file}.${note.token}`;
86
89
  const held = store.transact(() => {
87
90
  const prior = reservation(path);
@@ -155,6 +158,18 @@ function safePoolPath(root, target) {
155
158
  }
156
159
  return true;
157
160
  }
161
+ /** The checkout itself, strictly inside the pool as real paths, reached by real folders only: never a link out of it. */
162
+ function insidePool(root, path) {
163
+ if (!safePoolPath(root, path))
164
+ return false;
165
+ try {
166
+ const real = realpathSync(path), base = realpathSync(root);
167
+ return real !== base && real.startsWith(base + sep) && lstatSync(path).isDirectory();
168
+ }
169
+ catch {
170
+ return false;
171
+ }
172
+ }
158
173
  function normalisePath(path) {
159
174
  return path.replace(/\\/g, "/").replace(/\/+$/, "");
160
175
  }
@@ -178,12 +193,12 @@ export function recordWorktreeProcess(store, path, runner, pid, leaseEpoch) {
178
193
  // verified boot change nothing of it survives, and the checkout is free
179
194
  // without a PID probe that a reused pid could answer wrongly.
180
195
  const bootId = currentBootId();
181
- writeFileSync(join(path, MARKER), `${pid} ${runner} group ${bootId ?? "unknown"} ${hostname()}\n`, "utf8");
196
+ writeFileSync(join(path, MARKER), `${pid} ${runner} group ${bootId ?? "unknown"} ${hostname()} ${currentMachineId() ?? "unknown"}\n`, "utf8");
182
197
  }
183
198
  /** Read-only occupancy witness shared with stop settlement. */
184
199
  export function worktreeProcessOccupancy(path) {
185
200
  const reserved = reservation(path);
186
- if (reserved !== null && (reserved.pid !== process.pid || reserved.host !== hostname()) && reservationAlive(reserved))
201
+ if (reserved !== null && (reserved.pid !== process.pid || !isThisMachine(reserved)) && reservationAlive(reserved))
187
202
  return { held: true, by: reserved.pid };
188
203
  const note = join(path, MARKER);
189
204
  if (!existsSync(note))
@@ -193,7 +208,9 @@ export function worktreeProcessOccupancy(path) {
193
208
  if (!Number.isInteger(pid) || pid <= 0)
194
209
  return { held: false };
195
210
  const noteHost = parts[4];
196
- if (noteHost !== undefined && noteHost !== hostname())
211
+ // The sixth word (v150) is the writer's machine id; "unknown", or a note from before it, is matched by name.
212
+ const noteMachine = parts[5] === undefined || parts[5] === "unknown" ? null : parts[5];
213
+ if (noteHost !== undefined && !isThisMachine({ host: noteHost, machineId: noteMachine }))
197
214
  return { held: true, by: pid };
198
215
  if (pid === process.pid && parts[2] !== "group")
199
216
  return { held: false };
@@ -201,7 +218,7 @@ export function worktreeProcessOccupancy(path) {
201
218
  // that cannot exist any more; a note without a boot id (legacy) or a
202
219
  // boot this host cannot verify keeps the probe below.
203
220
  const noteBoot = parts[3];
204
- if (noteBoot !== undefined && noteHost !== undefined && provenDeadByBootChange({ host: noteHost, bootId: noteBoot }))
221
+ if (noteBoot !== undefined && noteHost !== undefined && provenDeadByBootChange({ host: noteHost, bootId: noteBoot }, { host: noteHost, bootId: currentBootId() }))
205
222
  return { held: false };
206
223
  // A shell/provider can exit before its descendants. A recorded POSIX
207
224
  // process group remains an owner until that whole group has gone.
@@ -650,7 +667,7 @@ export class WorktreePool {
650
667
  /** Leave a note naming the process holding this checkout. */
651
668
  mark(path, runner, pid = process.pid) {
652
669
  try {
653
- writeFileSync(join(path, MARKER), `${pid} ${runner} process ${currentBootId() ?? "unknown"} ${hostname()}\n`, "utf8");
670
+ writeFileSync(join(path, MARKER), `${pid} ${runner} process ${currentBootId() ?? "unknown"} ${hostname()} ${currentMachineId() ?? "unknown"}\n`, "utf8");
654
671
  return true;
655
672
  }
656
673
  catch {
@@ -837,6 +854,9 @@ export class WorktreePool {
837
854
  * or remote. Reads only, through `git` when given (teardown's hardened git).
838
855
  */
839
856
  async inspect(path, git = this.teardown) {
857
+ // A checkout that had a git incident lately is left alone (git-incident.ts): nothing re-runs git there each pass.
858
+ if (quietAfterIncident(path))
859
+ return "unreadable";
840
860
  const dirty = await this.isDirty(path, true, git);
841
861
  if (dirty !== false)
842
862
  return dirty === null ? "unreadable" : "has changes";
@@ -846,15 +866,51 @@ export class WorktreePool {
846
866
  return "unreadable";
847
867
  return own.stdout.trim() === "" ? "clean" : "has commits";
848
868
  }
869
+ /**
870
+ * Whether a checkout a task may still come back to can go, beyond `inspect`: a later lease of its branch must make the
871
+ * same checkout, and every commit its task recorded must outlive it (git's garbage collection counts a worktree's HEAD
872
+ * and reflog; once they go only a branch holds a commit). So: the folder is the pool's own, the checkout belongs to its
873
+ * project by the plane's record (`boundDirs`, not its `.git` file), its HEAD is its recorded branch, every recorded
874
+ * `heads` commit is on a branch, and no ref lives only in this worktree. Reads only; anything unreadable keeps it.
875
+ */
876
+ async proveRecreatable(row, heads) {
877
+ if (quietAfterIncident(row.path))
878
+ return "unreadable";
879
+ if (row.branch === "unknown" || !insidePool(this.options.root, row.path) || heads.some(one => !/^[0-9a-f]{40,64}$/.test(one)))
880
+ return "unreadable";
881
+ const git = teardownGit(this.runner, { repo: row.repo });
882
+ const head = await git(row.path, [...READ_ONLY, "symbolic-ref", "-q", "HEAD"]);
883
+ if (head.code === 1)
884
+ return "not on its branch";
885
+ if (head.code !== 0)
886
+ return "unreadable";
887
+ if (head.stdout.trim() !== `refs/heads/${row.branch}`)
888
+ return "not on its branch";
889
+ const branch = await git(row.repo, [...READ_ONLY, "rev-parse", "--verify", "--quiet", `refs/heads/${row.branch}^{commit}`]);
890
+ if (branch.code !== 0)
891
+ return "unreadable";
892
+ if (heads.length > 0) {
893
+ const unheld = await git(row.repo, [...READ_ONLY, "rev-list", "-1", ...heads, "--not", "--branches"]);
894
+ if (unheld.code !== 0)
895
+ return "unreadable";
896
+ if (unheld.stdout.trim() !== "")
897
+ return "has commits";
898
+ }
899
+ const own = await git(row.path, [...READ_ONLY, "for-each-ref", "--count=1", "refs/worktree", "refs/bisect", "refs/rewritten"]);
900
+ if (own.code !== 0)
901
+ return "unreadable";
902
+ return own.stdout.trim() === "" ? "clean" : "has commits";
903
+ }
849
904
  /**
850
905
  * Keep storage in check: remove the working copies nobody needs any more. Of `rows`, a checkout goes when it is
851
906
  * let go, nothing holds it, `wanted` says it may go (asked again right before it goes, so a task coming back or a
852
907
  * lease since keeps it) and `inspect` finds it clean, then clean still right before it goes (git refuses an edit
853
908
  * made after that). Only the working copy goes: its branch, and so every commit,
854
909
  * stays, and a later lease of the same branch makes a new one. At most `max` go per pass. Every git command goes
855
- * through `git` when given: landed cleanup passes its hardened teardown git (teardown-git.ts).
910
+ * through `git` when given: landed cleanup passes its hardened teardown git (teardown-git.ts). `proof` is asked last,
911
+ * inside the reservation, for what `inspect` doesn't cover (idle cleanup: `proveRecreatable`).
856
912
  */
857
- async prune(rows, wanted, max = Infinity, git = this.teardown) {
913
+ async prune(rows, wanted, max = Infinity, git = this.teardown, proof) {
858
914
  const removed = [];
859
915
  const kept = [];
860
916
  const idle = (row, releasedAt) => row !== null && row.runner === null && row.releasedAt === releasedAt && !this.inUse(row.path).held;
@@ -888,6 +944,11 @@ export class WorktreePool {
888
944
  const allowed = wanted(now, true);
889
945
  if (!(typeof allowed === "boolean" ? allowed : await allowed))
890
946
  continue;
947
+ const proven = proof === undefined ? "clean" : await proof(now);
948
+ if (proven !== "clean") {
949
+ kept.push({ path: row.path, why: proven });
950
+ continue;
951
+ }
891
952
  const gone = await this.removeCleanCopy(row, git);
892
953
  if (gone !== "removed") {
893
954
  kept.push({ path: row.path, why: gone });
@@ -931,7 +992,7 @@ export class WorktreePool {
931
992
  return { ok: false, message: `${row.path} is in use` };
932
993
  // Held while it slims: another process's lease sees this process in it.
933
994
  try {
934
- writeFileSync(note, `${process.pid} storage-retention slimming ${currentBootId() ?? "unknown"} ${hostname()}\n`, "utf8");
995
+ writeFileSync(note, `${process.pid} storage-retention slimming ${currentBootId() ?? "unknown"} ${hostname()} ${currentMachineId() ?? "unknown"}\n`, "utf8");
935
996
  held = true;
936
997
  }
937
998
  catch {
@@ -989,7 +1050,7 @@ export class WorktreePool {
989
1050
  // Held while it goes: another process's lease sees this process in it (the busy map is this process's only).
990
1051
  const note = join(row.path, MARKER);
991
1052
  try {
992
- writeFileSync(note, `${process.pid} storage-retention removing ${currentBootId() ?? "unknown"} ${hostname()}\n`, "utf8");
1053
+ writeFileSync(note, `${process.pid} storage-retention removing ${currentBootId() ?? "unknown"} ${hostname()} ${currentMachineId() ?? "unknown"}\n`, "utf8");
993
1054
  }
994
1055
  catch {
995
1056
  return false;
@@ -1065,3 +1126,19 @@ function firstLine(text) {
1065
1126
  const [line = ""] = text.trim().split("\n");
1066
1127
  return line;
1067
1128
  }
1129
+ /** Whether a checkout had a git incident within INCIDENT_QUIET_MS (by its real path, as incidents record it). */
1130
+ function quietAfterIncident(path) {
1131
+ let real;
1132
+ try {
1133
+ real = realpathSync(path);
1134
+ }
1135
+ catch {
1136
+ return false;
1137
+ }
1138
+ try {
1139
+ return recentGitIncident(gitIncidentRecord(), real, new Date(Date.now() - INCIDENT_QUIET_MS));
1140
+ }
1141
+ catch {
1142
+ return false;
1143
+ }
1144
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolroll",
3
- "version": "0.9.93",
3
+ "version": "0.9.95",
4
4
  "description": "A control plane for unattended coding agents — queue tasks, walk away, come back to pull requests. Agents build in leased worktrees, never touch your default branch, and interrupt you only for decisions that need a human.",
5
5
  "directories": {
6
6
  "doc": "docs"
@@ -133,6 +133,7 @@
133
133
  },
134
134
  "dependencies": {
135
135
  "@anthropic-ai/sandbox-runtime": "0.0.79",
136
+ "@json-render/core": "0.22.0",
136
137
  "@slack/socket-mode": "3.1.1",
137
138
  "@xterm/addon-serialize": "0.14.0",
138
139
  "@xterm/headless": "6.0.0",