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
package/CHANGELOG.md CHANGED
@@ -1,5 +1,84 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.95 — 2026-11-07
4
+
5
+ - **Cards in chat, and approving from chat.** Slack, Discord and Telegram show tasks, plans and results as proper cards. Anything that needs approval can be approved there by a person with authority, the same as on the web; long terms link to the review page.
6
+ - **Results keep moving after an agent finishes.** Send a finding to the task that owns the code: the running build gets it, or the next attempt. The owner must accept it, saying what changed, or reject it with a reason that names a file. Reading it closes nothing, and after two rounds it comes to you.
7
+ - **A failed check gets one owner and a limited repair.** The same failure on the same commit never starts a second repair. Sign a mode with repairs on and it runs by itself, up to its limit, inside the plan you approved. Without that, the failure waits for you to start a repair.
8
+ - **Handover note.** Every task has a note built from Toolroll's records: current commit, decisions, rejected approaches, open findings, checks and the next action. A fresh agent starts from it. Read it on the task page or with `toolroll task handover`.
9
+ - **Before and after.** Save a failing result and rerun the same check on the latest commit. If the code moves on, the evidence says it is out of date. A bug fix can also ask for a failing test first.
10
+ - **What a provider said when an attempt failed is saved** (`toolroll captures`), so a real used-up plan can be recognised later. Nothing routes on it.
11
+ - **Pull requests that can affect upgrades now run the upgrade test in CI**, so a broken upgrade shows up before a release.
12
+ - **Fallback agents.** A project can name up to three agents to switch to when the first one runs out of its plan. A plan entry is used only after Toolroll proves that login live; paid entries need your permission in the mode you sign.
13
+ - **Clearer storage permission.** When macOS needs Full Disk Access before a build can start, Toolroll names the program that needs it, holds the work, and shows it under Needs you with Try again.
14
+ - **Git incident alerts are accurate and quiet.** False alerts while Toolroll tidied old checkouts are gone. A real one names what Toolroll was doing, that checkout is left alone for a day, and alerts are limited to a few per checkout per day.
15
+
16
+ ## 0.9.94 — 2026-11-07
17
+
18
+ - **Small tasks start in one step.** For small work you start yourself, New
19
+ task shows the exact terms and **Build** is your approval. If the change
20
+ grows past small, Toolroll stops and asks you to approve it to keep it.
21
+ In the terminal: `toolroll task new "<what should change>" --repo <project>`.
22
+ - **Start a task from a GitHub issue or pull request.** Paste the link in New
23
+ task, or use `toolroll task new --from <url>`. Toolroll quotes the text as
24
+ written outside Toolroll, and you read it before filing it.
25
+ - **Needs you now holds only what needs you.** Things that are simply waiting
26
+ say what they wait for, and the task page, chat, the terminal and pings all
27
+ say the same sentence. Pings are one line with one button ("#41 is ready to
28
+ review").
29
+ - **Fewer pages and fewer words.** Home has Needs you, Running and Waiting;
30
+ Tasks is the one list. Old links still land in the right place. A result has
31
+ Accept, Send back and Retry; a plan has Approve, Request changes and Reject.
32
+ One set of keyboard shortcuts works in the console and the terminal.
33
+ - **"Can I merge?"** One checklist on each task: plan, build, checks,
34
+ requirements, pull request, review comments and release check.
35
+ - **A code map for each project.** `/projects/<name>/map` and `toolroll map`
36
+ show the areas, files and exports of your main branch, generated by Toolroll
37
+ itself with no agent or model. Builds and the lead can read it, and the page
38
+ reports how builds with the map compare to builds without it. Turn it off
39
+ per project in its settings.
40
+ - **Flaky tests**: each project lists tests with mixed outcomes under recorded
41
+ conditions, from local checks and GitHub CI re-runs, with history and one
42
+ action, "Make a task to fix it". `toolroll flaky list|show`. Read only:
43
+ nothing is rerun and no check changes.
44
+ - **Lesson notes**: when a revision passes a test that failed, Toolroll
45
+ proposes a note with what was recorded. You put it in your own words and
46
+ accept it as a decision, then see how often that test fails afterwards.
47
+ - **Last included in a run** on lessons, references and decisions, and
48
+ decisions now reach runs in projects with no other knowledge.
49
+ - **Clean up** in Settings → Knowledge proposes retiring, merging or settling
50
+ memory entries. Nothing changes until you accept, and restore and undo merge
51
+ are there when you change your mind. `toolroll memory tidy|restore|unmerge`.
52
+ - **Toolroll cleans up task folders nobody is using.** A task idle for 14
53
+ days now loses its working folder even if its result is still waiting for
54
+ review or the task never finished, as long as nothing would be lost: no
55
+ uncommitted changes, every commit on its branch, nothing running in it.
56
+ Reviewing, Accept, Send back, Run checks, Retry and Resume all keep working;
57
+ the next run makes the folder again from its branch. Choose 7, 14 or 30
58
+ days or never in Settings → Storage, or with
59
+ `toolroll storage cleanup idle <days>`. `toolroll storage` shows what the
60
+ next sweep would free.
61
+ - **Folders with uncommitted changes are listed, never removed.** Settings →
62
+ Storage and `toolroll storage` now show them so you can decide. Storage
63
+ calls these "task folders" now.
64
+ - **Builds that vanish recover on their own, within limits.** A lost build
65
+ counts once and retries up to three times. Past that, or with no budget left,
66
+ the task stops and says why.
67
+ - **A missing check verdict gets one automatic recheck** of that exact commit,
68
+ with no agent, instead of waiting for you.
69
+ - **The builder comes back after a crash.** Toolroll restarts it only if you
70
+ left it on and it can prove the old process is gone. If you turned it off, it
71
+ stays off.
72
+ - **A changed route is one Approve.** When a task's model route changed since
73
+ you approved, you see one line saying what changed and approve once.
74
+ - **One release command.** `toolroll release <branch>` works out the next step,
75
+ picks up what a stopped deploy left, and updates a branch that is behind main.
76
+ - **Toolroll knows which computer it is on, even if the network renames it.**
77
+ A release no longer waits forever on "the computer that ran build #N".
78
+ - **Checks say how they ran.** Each check launch records its sandbox, and the
79
+ result page and release card show it. A check with no record reads unknown and
80
+ is never carried forward as sandboxed.
81
+
3
82
  ## 0.9.93 — 2026-11-06
4
83
 
5
84
  - **Approve a release with one tap.** When a release is ready for your yes,
@@ -3,9 +3,10 @@
3
3
  * 40 s ago". Pure and shared: the server sends the facts, and each page words
4
4
  * them against its own clock, so the age keeps counting between reads.
5
5
  *
6
- * After five minutes with no word the line turns amber and says so; a worker
7
- * whose heartbeat stopped says that instead, because a quiet agent and a
8
- * machine that went away need different help.
6
+ * After five minutes with no word the line says when it last heard, in the
7
+ * same quiet secondary text (a quiet agent isn't something a person must do;
8
+ * amber is for what needs you). A worker whose heartbeat stopped says that
9
+ * instead, and that one is marked: the builder going away needs a person.
9
10
  */
10
11
  /** The facts behind the line: our own words for the last thing done (never a tool's arguments), when, and whether
11
12
  * the worker running it still answers. */
@@ -19,8 +20,10 @@ export declare const QUIET_AFTER_MS: number;
19
20
  export declare const ACTIVITY_WORDS: Readonly<Record<string, string>>;
20
21
  /** "just now", "40 s ago", "3 min ago", "2 h ago". */
21
22
  export declare function agoWords(ms: number): string;
22
- /** The line itself, and whether it is quiet (amber): silent five minutes or more, or the worker is offline. */
23
+ /** The line itself. `quiet` (marked): the worker is offline, which needs a person. `stale`: silent five minutes or
24
+ * more, said in the same neutral words as any other update. */
23
25
  export declare function activityLine(activity: RunActivity, now: number): {
24
26
  text: string;
25
27
  quiet: boolean;
28
+ stale: boolean;
26
29
  };
@@ -3,9 +3,10 @@
3
3
  * 40 s ago". Pure and shared: the server sends the facts, and each page words
4
4
  * them against its own clock, so the age keeps counting between reads.
5
5
  *
6
- * After five minutes with no word the line turns amber and says so; a worker
7
- * whose heartbeat stopped says that instead, because a quiet agent and a
8
- * machine that went away need different help.
6
+ * After five minutes with no word the line says when it last heard, in the
7
+ * same quiet secondary text (a quiet agent isn't something a person must do;
8
+ * amber is for what needs you). A worker whose heartbeat stopped says that
9
+ * instead, and that one is marked: the builder going away needs a person.
9
10
  */
10
11
  export const QUIET_AFTER_MS = 5 * 60_000;
11
12
  /** The words for each fixed activity kind (live.ts's vocabulary) and for a progress checkpoint. */
@@ -35,13 +36,14 @@ export function agoWords(ms) {
35
36
  return `${minutes} min ago`;
36
37
  return `${Math.floor(minutes / 60)} h ago`;
37
38
  }
38
- /** The line itself, and whether it is quiet (amber): silent five minutes or more, or the worker is offline. */
39
+ /** The line itself. `quiet` (marked): the worker is offline, which needs a person. `stale`: silent five minutes or
40
+ * more, said in the same neutral words as any other update. */
39
41
  export function activityLine(activity, now) {
40
42
  const at = Date.parse(activity.at);
41
43
  const since = Number.isNaN(at) ? 0 : Math.max(0, now - at);
42
44
  if (activity.worker === "offline")
43
- return { text: `Worker offline · last word ${agoWords(since)}`, quiet: true };
45
+ return { text: `Worker offline · last word ${agoWords(since)}`, quiet: true, stale: false };
44
46
  if (since >= QUIET_AFTER_MS)
45
- return { text: `No word for ${Math.floor(since / 60_000)} min`, quiet: true };
46
- return { text: `${activity.what} · ${agoWords(since)}`, quiet: false };
47
+ return { text: `Last update ${agoWords(since)}`, quiet: false, stale: true };
48
+ return { text: `${activity.what} · ${agoWords(since)}`, quiet: false, stale: false };
47
49
  }
@@ -1,6 +1,7 @@
1
1
  import type { LoopbackMode } from "./egress.js";
2
2
  import type { ExecutionProfile } from "./scope.js";
3
3
  import { type SrtTerms } from "./sandbox-runtime.js";
4
+ import { type ProtectedStorage } from "./protected-storage.js";
4
5
  /** How a launch was fenced, as recorded on the run and said on the task. */
5
6
  export type FenceMethod = "codex-profile" | "macos-sandbox" | "linux-bubblewrap" | "claude-rules" | "none";
6
7
  /** Only the plane's recorded repository may supply an own-Git exemption. */
@@ -58,6 +59,7 @@ export declare function taskBoundary(options: {
58
59
  home?: string;
59
60
  binding?: WorktreeBinding | null;
60
61
  policy?: FencePolicy;
62
+ storage?: ProtectedStorage;
61
63
  }): TaskBoundary;
62
64
  /** Where launches keep their private folders: `root` (the system temp folder) and, on a Mac, `also` the short shared one
63
65
  * a check's launch folder is made in (/private/tmp, so its unix sockets fit); `own` is this launch's. */
@@ -90,6 +92,8 @@ export type FenceGrants = {
90
92
  pool?: CheckoutPool;
91
93
  network?: NetworkFence;
92
94
  writes?: WriteAllowlist;
95
+ /** Which `readable` entries are one exact file: read with no listing of its folder, so a sibling stays denied. */
96
+ exactFiles?: string[];
93
97
  /** Policy v1: the home configuration names denied by name, present or not (TaskBoundary.homeNames). */
94
98
  homeNames?: string[];
95
99
  /** macOS: the project's sealed mach services beyond the runtime's baseline (its `allowMachLookup`). */
@@ -359,6 +363,12 @@ export type FenceKind = "build" | "command" | "check" | "flow" | "tool-server";
359
363
  * with no checkout), so no caller forgets one; every path comes from Toolroll's own records, never from the checkout.
360
364
  */
361
365
  export type FenceContext = {
366
+ /** This launch's storage check, awaited before the fence is made (protected-storage.ts prepareProtectedStorage). Every
367
+ * launch road passes it (an architecture test holds them to it); without one the check runs right here, which only a
368
+ * test does. */
369
+ storage?: ProtectedStorage;
370
+ /** One exact file read back inside the fence (a build's code map), or null. */
371
+ exactFile?: string | null;
362
372
  databaseFile: string | null;
363
373
  worktree: string | null;
364
374
  boundary: TaskBoundary | null;
@@ -306,7 +306,7 @@ export function taskBoundary(options) {
306
306
  // Policy v2 (docs/plans/sandbox-parity.md §1.2): the home folder, the repositories beside this one and the project's
307
307
  // primary checkout are read as Anthropic's sandbox runtime reads them; only writes to the primary checkout stay denied.
308
308
  // Protected storage by name, under both policies (protected-storage.ts): never part of the v1-only home configuration.
309
- const storage = protectedStorageNames(home);
309
+ const storage = options.storage?.names ?? protectedStorageNames(home);
310
310
  if ((options.policy ?? 1) === 2)
311
311
  return v2Boundary(worktree, main, gitdir, storage);
312
312
  const shell = new Set(present(HOME_CONFIGURATION.filter(name => !own.has(name))));
@@ -1006,7 +1006,7 @@ export function srtTermsOf(fence, grants = NO_GRANTS) {
1006
1006
  const denyRead = new Set(fence.map(real));
1007
1007
  // A readable grant's own folder is read too: bundlers (esbuild) find node_modules by listing it. The runtime can't
1008
1008
  // grant a listing without the folder's files, so the shared copy's folder is read whole (its ready note among them).
1009
- const allowRead = new Set(grants.readable.flatMap(path => [real(path), real(dirname(path))]));
1009
+ const allowRead = new Set(grants.readable.flatMap(path => grants.exactFiles?.includes(path) === true ? [real(path)] : [real(path), real(dirname(path))]));
1010
1010
  const denyWrite = new Set([...fence, ...grants.readOnly].map(real));
1011
1011
  for (const path of grants.writeDenies ?? [])
1012
1012
  denyWrite.add(path);
@@ -1173,16 +1173,19 @@ export function fenceFor(kind, ctx) {
1173
1173
  const home = ctx.home === undefined ? {} : { home: ctx.home };
1174
1174
  // The project's read denies join the fence by name, there or not (the runtime denies a path that isn't there yet).
1175
1175
  // Protected storage, resolved once for this launch, from the plane's own environment (protected-storage.ts).
1176
- const storage = protectedStorageDenies(ctx.home ?? homedir());
1176
+ const storage = ctx.storage?.denies ?? protectedStorageDenies(ctx.home ?? homedir());
1177
1177
  const fence = agentFence({ databaseFile: ctx.databaseFile, worktree: ctx.worktree, ...home, boundary: ctx.boundary, binding: ctx.binding, runFolders: ctx.runFolders, extra: [...ctx.extra ?? [], ...ctx.readDeny ?? []], protectedStorage: storage });
1178
1178
  const found = fenceGrants({ databaseFile: ctx.databaseFile, worktree: ctx.worktree, boundary: ctx.boundary, runFolders: ctx.runFolders, linked: ctx.linked ?? null, binding: ctx.binding, ...home, poolRoots: ctx.poolRoots });
1179
1179
  // Toolroll's own install, the sandbox runtime's package and the Node running them, never written by anything it
1180
1180
  // launches: rewriting the code that fences a build would switch the fence off.
1181
1181
  const made = { ...found, readOnly: [...new Set([...found.readOnly, ...toolrollInstall(ctx.worktree, ctx.home ?? homedir(), ctx.binding?.main ?? null)])].sort() };
1182
1182
  const written = ctx.writes ?? null;
1183
+ const exact = ctx.exactFile ?? null;
1183
1184
  const grants = {
1184
1185
  ...made,
1185
1186
  ...(written === null ? {} : { writes: written.writes, readOnly: [...new Set([...made.readOnly, ...written.readOnly])].sort() }),
1187
+ // One exact file, granted as the shared dependencies are, and never written (Claude's Edit rules read `readOnly`).
1188
+ ...(exact === null ? {} : { readable: [...made.readable, exact], exactFiles: [exact], readOnly: [...new Set([...made.readOnly, ...(written?.readOnly ?? []), exact])].sort() }),
1186
1189
  ...(ctx.network === undefined ? {} : { network: ctx.network }),
1187
1190
  ...(ctx.machLookup === undefined || ctx.machLookup.length === 0 ? {} : { machLookup: [...ctx.machLookup] }),
1188
1191
  // A project's checks may drive a browser (Playwright's Chromium), and so may a flow's script zone whose browser use
@@ -1,7 +1,13 @@
1
1
  /** SQL over a `task_scope` alias and a repo expression: whether that row's approval authority stands at `now` (an SQL
2
- * expression, used exactly three times: the mode and grant clauses, and the release's expiry). It never asks whether the row is approved.
2
+ * expression, used exactly four times: the mode and grant clauses, the release's expiry and a fast start's window). It never asks whether the row is approved.
3
3
  * COALESCE: a NULL basis would make the OR chain NULL, and NOT NULL reads as "not dead"; NULL stands on nothing. */
4
4
  export declare function authorityStandsSql(scope: string, repo: string, now: string): string;
5
+ /**
6
+ * SQL, conjunctive with every alternative above (plain-toolroll.md P2.4): narrow only. A fast start's seal dispatches only
7
+ * inside its start window, and only as what it claims to be: a person's own ceremony yes to these exact bytes, proved at
8
+ * the seal's instant. Losing the marker leaves an ordinary person approval; nothing here grants anything.
9
+ */
10
+ export declare function fastStartStandsSql(scope: string, now: string): string;
5
11
  /**
6
12
  * SQL, conjunctive with every alternative above (results routing slice 3): a task whose sealed route tries a cheaper
7
13
  * model (its build leg's selection is a trial) stands only on a PERSON's own yes to these exact bytes — the ceremony
@@ -18,7 +18,7 @@ import { autoSealSql } from "./release-authority.js";
18
18
  /** The proofs a person gives (src/person-proof.ts), as an SQL list. */
19
19
  const PERSON_PROOFS = "('password-now', 'sso-now', 'fresh-password', 'fresh-sso', 'session', 'cli-login', 'chat', 'passkey', 'local-owner')";
20
20
  /** SQL over a `task_scope` alias and a repo expression: whether that row's approval authority stands at `now` (an SQL
21
- * expression, used exactly three times: the mode and grant clauses, and the release's expiry). It never asks whether the row is approved.
21
+ * expression, used exactly four times: the mode and grant clauses, the release's expiry and a fast start's window). It never asks whether the row is approved.
22
22
  * COALESCE: a NULL basis would make the OR chain NULL, and NOT NULL reads as "not dead"; NULL stands on nothing. */
23
23
  export function authorityStandsSql(scope, repo, now) {
24
24
  return `COALESCE((((COALESCE(${scope}.approval_basis, '') IN ('password', 'chat') AND NOT ${machineSealSql(scope)}
@@ -31,9 +31,18 @@ export function authorityStandsSql(scope, repo, now) {
31
31
  OR (${scope}.approval_basis = 'grant' AND ${grantStandsSql(`${scope}.mode_digest`, now, repo)})
32
32
  OR (${scope}.approval_basis = 'schedule' AND ${approverStandsSql(scope, repo)})
33
33
  OR (${scope}.approval_basis IN ('auto-check', 'auto-green') AND ${autoSealSql(scope, repo)} AND ${approverStandsSql(scope, repo)}))
34
- AND ${releaseStandsSql(scope, now)} AND ${localSealStandsSql(scope, repo)}
34
+ AND ${releaseStandsSql(scope, now)} AND ${localSealStandsSql(scope, repo)} AND ${fastStartStandsSql(scope, now)}
35
35
  AND (instr(COALESCE(${scope}.approved_route_json, ''), '"trial"') = 0 OR ${trialSealStandsSql(scope)})), 0)`;
36
36
  }
37
+ /**
38
+ * SQL, conjunctive with every alternative above (plain-toolroll.md P2.4): narrow only. A fast start's seal dispatches only
39
+ * inside its start window, and only as what it claims to be: a person's own ceremony yes to these exact bytes, proved at
40
+ * the seal's instant. Losing the marker leaves an ordinary person approval; nothing here grants anything.
41
+ */
42
+ export function fastStartStandsSql(scope, now) {
43
+ return `NOT EXISTS (SELECT 1 FROM task_ref fsr WHERE fsr.backend = 'built-in' AND fsr.external_id = ${scope}.task_id AND fsr.fast_start_until IS NOT NULL
44
+ AND (fsr.fast_start_until <= ${now} OR COALESCE(${scope}.approval_basis, '') <> 'password' OR ${machineSealSql(scope)} OR NOT ${personSealSql(scope)}))`;
45
+ }
37
46
  /**
38
47
  * SQL, conjunctive with every alternative above (results routing slice 3): a task whose sealed route tries a cheaper
39
48
  * model (its build leg's selection is a trial) stands only on a PERSON's own yes to these exact bytes — the ceremony
@@ -0,0 +1,31 @@
1
+ import type { Scope } from "./scope.js";
2
+ import type { Store } from "./store.js";
3
+ export type ApprovalPasswordField = {
4
+ field: "none";
5
+ why: "passwordless" | "local-owner";
6
+ } | {
7
+ field: "remembered";
8
+ since: string;
9
+ } | {
10
+ field: "required";
11
+ } | {
12
+ field: "local-refused";
13
+ message: string;
14
+ href: string;
15
+ action: string;
16
+ };
17
+ export type ApprovalPasswordFacts = {
18
+ /** The approver. */
19
+ name: string;
20
+ taskId: string;
21
+ scope: Scope;
22
+ /** Passwordless is on for this browser session (html.ts passwordlessRendering). */
23
+ passwordless: boolean;
24
+ /** The request is this laptop's local owner (easy-start C5). */
25
+ localOwner: boolean;
26
+ /** When a password was last typed on this session, if it may still stand (scope.ts freshnessFor), else null. */
27
+ freshAt: number | null;
28
+ /** Where a refused local owner goes back to. */
29
+ returnTo: string;
30
+ };
31
+ export declare function approvalPasswordField(store: Store, facts: ApprovalPasswordFacts): ApprovalPasswordField;
@@ -0,0 +1,26 @@
1
+ /** When an approval form asks for a password (docs/plans/plain-toolroll.md P1.3): one function every approve form reads,
2
+ * so the task page, the focused chat and the shared-action review never disagree. It only decides what a form shows;
3
+ * approve() proves the person, the freshness, the local owner and the digest again inside its own transaction, with
4
+ * the same two rules this reads (freshlyApprovable, localApprovalEligible).
5
+ *
6
+ * - `none`: Passwordless is on for this session, or this computer's local owner may approve this exact work.
7
+ * - `remembered`: a password typed on this session in the last fifteen minutes may stand for these terms; the field
8
+ * shows, and may be left empty.
9
+ * - `required`: a password typed now: protected or escalated work, terms an agent wrote, or nothing remembered.
10
+ * - `local-refused`: a local owner who can't approve this here, with where to go instead. */
11
+ import { freshlyApprovable, localApprovalEligible } from "./scope.js";
12
+ import { localOwnerRefusal } from "./local-owner.js";
13
+ export function approvalPasswordField(store, facts) {
14
+ if (facts.localOwner) {
15
+ if (localApprovalEligible(store, facts.name, facts.taskId).ok)
16
+ return { field: "none", why: "local-owner" };
17
+ return { field: "local-refused", ...localOwnerRefusal(store.accountOf(facts.name)?.passwordSet === true, facts.returnTo) };
18
+ }
19
+ if (facts.passwordless)
20
+ return { field: "none", why: "passwordless" };
21
+ if (facts.freshAt === null)
22
+ return { field: "required" };
23
+ const gate = store.approvalGate(facts.taskId, facts.name, "person");
24
+ return freshlyApprovable(store, facts.taskId, facts.scope, gate.verdict === "seal" && !gate.protectedWork)
25
+ ? { field: "remembered", since: new Date(facts.freshAt).toISOString() } : { field: "required" };
26
+ }
@@ -23,7 +23,7 @@ export declare function assignmentStatusBrief(assignment: AssignmentSnapshot): {
23
23
  } | null;
24
24
  runId: number | null;
25
25
  primaryAction: {
26
- code: "answer-agent" | "inspect-decisions" | "inspect-run" | "inspect-stop" | "inspect-task" | "open-pr" | "recheck-stopped" | "reconcile-run" | "sign-in" | import("./dispatch.js").DispatchAction;
26
+ code: "answer-agent" | "inspect-decisions" | "inspect-findings" | "inspect-run" | "inspect-stop" | "inspect-task" | "open-pr" | "recheck-stopped" | "reconcile-run" | "sign-in" | import("./dispatch.js").DispatchAction;
27
27
  label: string;
28
28
  target: import("./work-summary.js").WorkTarget;
29
29
  } | null;
@@ -24,6 +24,8 @@ export function assignmentActionHref(assignment) {
24
24
  if (action === null)
25
25
  return null;
26
26
  const { taskId, runId, decisionId } = action.target;
27
+ if (action.code === 'inspect-findings')
28
+ return `/t/${encodeURIComponent(taskId)}#findings`;
27
29
  if (decisionId !== null)
28
30
  return `/d/${decisionId}`;
29
31
  if (action.code === 'inspect-decisions')
@@ -63,7 +65,7 @@ export function assignmentAttemptsOf(assignment) {
63
65
  export function assignmentAttemptsHtml(assignment) {
64
66
  if (assignment.attempts.length === 0)
65
67
  return html ``;
66
- return html `<details class="assignment-attempts"><summary>Attempts <span class="meta">${assignment.attempts.length}</span></summary><ol>${assignmentAttemptsOf(assignment).map(attempt => html `<li data-attempt-task="${attempt.taskId}" data-history-version="${attempt.taskId}"><a href="${attempt.href}">${attempt.label}</a>${attempt.runId === null ? '' : html ` <a href="/r/${attempt.runId}">Run #${attempt.runId}</a>`}${attempt.detail === null ? '' : html `<p class="meta">${attempt.detail}</p>`}</li>`)}</ol>${assignment.owner === null ? '' : html `<p class="meta">Lead: ${assignment.owner.label}${assignment.owner.active ? '' : ' · access ended'}</p>`}<p class="work-meta work-id">Task <span class="mono">${assignment.rootId}</span></p></details>`;
68
+ return html `<details class="assignment-attempts"><summary>Attempts <span class="meta">${assignment.attempts.length}</span></summary><ol>${assignmentAttemptsOf(assignment).map(attempt => html `<li data-attempt-task="${attempt.taskId}" data-history-version="${attempt.taskId}"><a href="${attempt.href}">${attempt.label}</a>${attempt.runId === null ? '' : html ` <a href="/r/${attempt.runId}">Build #${attempt.runId}</a>`}${attempt.detail === null ? '' : html `<p class="meta">${attempt.detail}</p>`}</li>`)}</ol>${assignment.owner === null ? '' : html `<p class="meta">Lead: ${assignment.owner.label}${assignment.owner.active ? '' : ' · access ended'}</p>`}<p class="work-meta work-id">Task <span class="mono">${assignment.rootId}</span></p></details>`;
67
69
  }
68
70
  /** Only recognized storage limits and explicitly identified older records are
69
71
  * secondary. Unknown findings and damaged current material stay in view. */
@@ -1,6 +1,7 @@
1
1
  /** One assignment follows the existing task family. It owns no execution,
2
2
  * approval or proof state. Lead ownership and exact receipt acknowledgments
3
3
  * are append-only actions; handoffs use the existing durable outbox. */
4
+ import { fastCompletionProblem } from "./fast-start.js";
4
5
  import { createHash } from "node:crypto";
5
6
  import { currentActor, withActor } from "./actor.js";
6
7
  import { replacedWords } from "./task-status.js";
@@ -496,6 +497,10 @@ function acknowledgeCurrent(store, current, receiptDigest, actor, now, root, acc
496
497
  // token, a remote API run) that may complete ordinary work but is never the second person on protected work.
497
498
  const person = actor.startsWith("operator:") && !machineActing() ? actor.slice("operator:".length) : null;
498
499
  const marked = actor.startsWith("operator:") ? (person === null ? COMPLETION_BY_MACHINE : COMPLETION_BY_PERSON) : null;
500
+ // A fast start's Accept backstop (fast-start.ts): a person only, and only a change that stayed small.
501
+ const grown = fastCompletionProblem(store, receipt.taskId, receipt.runId, person);
502
+ if (grown !== null)
503
+ return { ok: false, reason: "not-ready", message: grown };
499
504
  if (current.state !== "complete") {
500
505
  // The one readiness guard: a failed or missing required check, an unresolved requirement or HIGH finding refuses.
501
506
  const blockers = current.readiness?.blockers ?? [];
package/dist/attest.d.ts CHANGED
@@ -54,3 +54,6 @@ export declare function resetAttestationCache(): void;
54
54
  * throw, never a silent pass. Returns null for tier-1 providers.
55
55
  */
56
56
  export declare function attestProvider(provider: ProviderId, binary: string, probe?: VersionProbe): Promise<AttestOutcome | null>;
57
+ /** What the installed CLI says its version is, with no range attached: a recogniser's own check (exhaustion.ts), asked of
58
+ * the exact executable PATH resolves on this runner, only when a terminal already reads as a plan limit. Null: no answer. */
59
+ export declare function reportedVersion(binary: string, probe?: VersionProbe): Promise<string | null>;
package/dist/attest.js CHANGED
@@ -158,3 +158,13 @@ export async function attestProvider(provider, binary, probe = defaultProbe) {
158
158
  }
159
159
  return { ok: true, executable: resolved, version };
160
160
  }
161
+ /** What the installed CLI says its version is, with no range attached: a recogniser's own check (exhaustion.ts), asked of
162
+ * the exact executable PATH resolves on this runner, only when a terminal already reads as a plan limit. Null: no answer. */
163
+ export async function reportedVersion(binary, probe = defaultProbe) {
164
+ const resolved = resolveExecutable(binary);
165
+ if (resolved === null)
166
+ return null;
167
+ const answer = await probe(resolved, ["--version"], PROBE_TIMEOUT_MS);
168
+ const raw = answer.stdout.trim().slice(0, 100);
169
+ return answer.code !== 0 || raw === "" ? null : raw;
170
+ }
@@ -48,10 +48,11 @@ export declare function injectBootIdentity(identity: BootIdentity | null): void;
48
48
  export type BootWitness = {
49
49
  host: string;
50
50
  bootId: string | null;
51
+ machineId?: string | null;
51
52
  };
52
53
  /**
53
54
  * THE ONE RULE (plan §login/reboot recovery): a witnessed process is
54
- * proven dead by boot change only when the witness names THIS host, the
55
+ * proven dead by boot change only when the witness names THIS machine (isThisMachine), the
55
56
  * witness carries a well-formed boot id, this host's current boot id is
56
57
  * well-formed and known, and the two differ. Same boot, a legacy witness
57
58
  * without an id, a foreign host, an unknown or malformed current id — each
@@ -21,7 +21,7 @@
21
21
  */
22
22
  import { execFileSync } from "node:child_process";
23
23
  import { readFileSync } from "node:fs";
24
- import { hostname } from "node:os";
24
+ import { isThisMachine } from "./machine-identity.js";
25
25
  /** RFC 4122 shape, any case; the kernel tokens are exactly this. */
26
26
  const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
27
27
  /** Normalize a candidate token: exactly one UUID, lower-cased; anything else is malformed. */
@@ -79,15 +79,17 @@ export function injectBootIdentity(identity) {
79
79
  }
80
80
  /**
81
81
  * THE ONE RULE (plan §login/reboot recovery): a witnessed process is
82
- * proven dead by boot change only when the witness names THIS host, the
82
+ * proven dead by boot change only when the witness names THIS machine (isThisMachine), the
83
83
  * witness carries a well-formed boot id, this host's current boot id is
84
84
  * well-formed and known, and the two differ. Same boot, a legacy witness
85
85
  * without an id, a foreign host, an unknown or malformed current id — each
86
86
  * answers false, and the caller keeps its PID probe or its refusal.
87
87
  */
88
88
  export function provenDeadByBootChange(witness, current = { bootId: currentBootId() }) {
89
- const host = current.host ?? hostname();
90
- if (witness.host !== host)
89
+ // A caller naming the host compares exactly (restart certification, tests); otherwise THIS machine is decided by
90
+ // its stable id, or for a witness without one by this machine's own names (machine-identity.ts), never by the
91
+ // network hostname alone: a boot id from another machine always differs from ours.
92
+ if (current.host !== undefined ? witness.host !== current.host : !isThisMachine(witness))
91
93
  return false;
92
94
  if (witness.bootId === null || current.bootId === null)
93
95
  return false;
@@ -0,0 +1,57 @@
1
+ export type BoundedStart = {
2
+ pid: number;
3
+ pgid: number;
4
+ startedAt: string | null;
5
+ };
6
+ export type BoundedOptions = {
7
+ cwd: string;
8
+ /** The child's whole environment. */
9
+ env: Record<string, string>;
10
+ label: string;
11
+ stdin?: Buffer;
12
+ timeoutMs: number;
13
+ /** Past this the tree is killed (`killed: "output"`). */
14
+ maxStdout: number;
15
+ /** Kept to this many bytes; more is dropped. */
16
+ maxStderr: number;
17
+ memoryBytes?: number;
18
+ diskBytes?: number;
19
+ /** The largest file any process may write (RLIMIT_FSIZE). */
20
+ fileBytes?: number;
21
+ /** The folder whose total `diskBytes` bounds. */
22
+ folder?: string;
23
+ onStart?: (started: BoundedStart) => void;
24
+ sampleMs?: number;
25
+ };
26
+ export type BoundedResult = {
27
+ code: number | null;
28
+ signal: NodeJS.Signals | null;
29
+ stdout: Buffer;
30
+ stderr: string;
31
+ killed: "time" | "memory" | "disk" | "output" | null;
32
+ enforced: "system" | "checked";
33
+ peakBytes: number;
34
+ };
35
+ export type BoundedRunner = (file: string, args: readonly string[], options: BoundedOptions) => Promise<BoundedResult>;
36
+ /** Tests: forget the probed machine. */
37
+ export declare function resetBoundedContainmentForTests(): void;
38
+ /** A process's own start time, as `ps` reports it: what tells a live process from a reused pid. */
39
+ export declare function processStartedAt(pid: number): string | null;
40
+ /** The folder's total size, without following a link. */
41
+ export declare function folderBytes(folder: string): number;
42
+ export declare const boundedRun: BoundedRunner;
43
+ export type SpawnRecord = {
44
+ pid: number | null;
45
+ pgid: number | null;
46
+ startedAt: string | null;
47
+ folder: string | null;
48
+ };
49
+ /**
50
+ * What a crashed build left: its group is killed only if the leader is still the process recorded (the pid and its start
51
+ * time, so a reused pid isn't killed), and its folder goes if it sits under `root`. On Linux the cgroup janitor has
52
+ * done this already.
53
+ */
54
+ export declare function cleanupLeftover(record: SpawnRecord, root: string): {
55
+ killed: boolean;
56
+ removed: boolean;
57
+ };