toolroll 0.9.92 → 0.9.94

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 (341) hide show
  1. package/CHANGELOG.md +87 -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 +4 -0
  5. package/dist/agent-fence.js +4 -1
  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 -3
  12. package/dist/assignment.js +16 -3
  13. package/dist/boot-identity.d.ts +2 -1
  14. package/dist/boot-identity.js +6 -4
  15. package/dist/bounded-spawn.d.ts +57 -0
  16. package/dist/bounded-spawn.js +220 -0
  17. package/dist/browser/terminal.js +42 -42
  18. package/dist/browser/workspace.css +2 -2
  19. package/dist/browser/workspace.js +26 -26
  20. package/dist/browser-crew.js +2 -2
  21. package/dist/browser-shell.d.ts +1 -1
  22. package/dist/browser-shell.js +2 -2
  23. package/dist/browser-workspace.d.ts +48 -7
  24. package/dist/browser-workspace.js +3 -3
  25. package/dist/builder-restart.d.ts +21 -0
  26. package/dist/builder-restart.js +50 -0
  27. package/dist/builder.d.ts +4 -0
  28. package/dist/builder.js +73 -25
  29. package/dist/chat-acceptance.js +1 -1
  30. package/dist/chat-actions.d.ts +12 -2
  31. package/dist/chat-actions.js +25 -2
  32. package/dist/chat-channel.js +11 -0
  33. package/dist/chat-decide.d.ts +7 -2
  34. package/dist/chat-decide.js +37 -9
  35. package/dist/chat-quiet.js +3 -3
  36. package/dist/chat-voice.d.ts +1 -1
  37. package/dist/chat-voice.js +2 -2
  38. package/dist/check-facts.d.ts +126 -0
  39. package/dist/check-facts.js +318 -0
  40. package/dist/check-failure.d.ts +22 -3
  41. package/dist/check-failure.js +57 -13
  42. package/dist/check-levels-ui.d.ts +4 -0
  43. package/dist/check-levels-ui.js +10 -1
  44. package/dist/check-load.d.ts +85 -0
  45. package/dist/check-load.js +147 -0
  46. package/dist/check-only-standing.d.ts +36 -0
  47. package/dist/check-only-standing.js +39 -0
  48. package/dist/check-only.d.ts +15 -0
  49. package/dist/check-only.js +99 -0
  50. package/dist/check-report.d.ts +7 -0
  51. package/dist/check-report.js +4 -3
  52. package/dist/check-stamp.d.ts +46 -0
  53. package/dist/check-stamp.js +93 -0
  54. package/dist/checkout-cleanup.d.ts +36 -5
  55. package/dist/checkout-cleanup.js +166 -14
  56. package/dist/cli-help.d.ts +4 -1
  57. package/dist/cli-help.js +47 -30
  58. package/dist/cli.js +16 -1
  59. package/dist/code-extract.d.ts +44 -0
  60. package/dist/code-extract.js +88 -0
  61. package/dist/code-map-acts.d.ts +24 -0
  62. package/dist/code-map-acts.js +53 -0
  63. package/dist/code-map-admission.d.ts +52 -0
  64. package/dist/code-map-admission.js +295 -0
  65. package/dist/code-map-build.d.ts +63 -0
  66. package/dist/code-map-build.js +415 -0
  67. package/dist/code-map-cli.d.ts +153 -0
  68. package/dist/code-map-cli.js +103 -0
  69. package/dist/code-map-main.d.ts +1 -0
  70. package/dist/code-map-main.js +48 -0
  71. package/dist/code-map-read.d.ts +106 -0
  72. package/dist/code-map-read.js +171 -0
  73. package/dist/code-map-report.d.ts +37 -0
  74. package/dist/code-map-report.js +95 -0
  75. package/dist/code-map-shape.d.ts +172 -0
  76. package/dist/code-map-shape.js +327 -0
  77. package/dist/code-map-store.d.ts +136 -0
  78. package/dist/code-map-store.js +233 -0
  79. package/dist/coding-provider.d.ts +2 -0
  80. package/dist/coding-provider.js +3 -2
  81. package/dist/coding-update.js +3 -2
  82. package/dist/coding-workspace.js +4 -4
  83. package/dist/console-chrome.js +2 -2
  84. package/dist/containment.d.ts +5 -0
  85. package/dist/containment.js +14 -1
  86. package/dist/contracts/chat-actions.d.ts +9 -0
  87. package/dist/contracts/chat-actions.js +2 -0
  88. package/dist/contracts/cli.d.ts +52 -0
  89. package/dist/contracts/cli.js +4 -0
  90. package/dist/contracts/console-api.d.ts +12 -5
  91. package/dist/contracts/console-api.js +12 -5
  92. package/dist/contracts/gateway-tools.d.ts +17 -0
  93. package/dist/contracts/gateway-tools.js +3 -1
  94. package/dist/contracts/lead-tools.d.ts +61 -0
  95. package/dist/contracts/lead-tools.js +10 -1
  96. package/dist/contracts/project-knowledge.d.ts +38 -5
  97. package/dist/contracts/project-knowledge.js +11 -5
  98. package/dist/contracts/project-memory.d.ts +3 -1
  99. package/dist/contracts/project-memory.js +3 -2
  100. package/dist/contracts/release-check.d.ts +25 -1
  101. package/dist/contracts/release-check.js +19 -0
  102. package/dist/contracts/storage.d.ts +14 -0
  103. package/dist/contracts/storage.js +3 -1
  104. package/dist/contracts/store-json.d.ts +2 -0
  105. package/dist/contracts/youiyoux-request.d.ts +1 -1
  106. package/dist/contracts/youiyoux-request.js +1 -1
  107. package/dist/desktop-access.js +6 -2
  108. package/dist/desktop-update.d.ts +2 -2
  109. package/dist/desktop-update.js +13 -9
  110. package/dist/dispatch.js +18 -15
  111. package/dist/dispose.js +8 -2
  112. package/dist/easy-start.js +2 -3
  113. package/dist/eval-hooks.d.ts +55 -0
  114. package/dist/eval-hooks.js +227 -4
  115. package/dist/evals.js +2 -1
  116. package/dist/fast-start.d.ts +136 -0
  117. package/dist/fast-start.js +431 -0
  118. package/dist/fast-terms.d.ts +9 -0
  119. package/dist/fast-terms.js +23 -0
  120. package/dist/fenced-check-coverage.d.ts +62 -0
  121. package/dist/fenced-check-coverage.js +54 -0
  122. package/dist/first-run.js +1 -1
  123. package/dist/flaky-ci.d.ts +31 -0
  124. package/dist/flaky-ci.js +149 -0
  125. package/dist/flaky-cli.d.ts +60 -0
  126. package/dist/flaky-cli.js +60 -0
  127. package/dist/flaky.d.ts +83 -0
  128. package/dist/flaky.js +264 -0
  129. package/dist/flow-card-task.js +3 -0
  130. package/dist/flow-triggers.d.ts +1 -1
  131. package/dist/flow-triggers.js +1 -1
  132. package/dist/github-import.d.ts +62 -0
  133. package/dist/github-import.js +146 -0
  134. package/dist/guides.js +15 -10
  135. package/dist/invoke.d.ts +5 -1
  136. package/dist/invoke.js +49 -14
  137. package/dist/job-object-helper.ps1 +10 -3
  138. package/dist/keymap.d.ts +41 -0
  139. package/dist/keymap.js +51 -0
  140. package/dist/knowledge-cli.js +1 -1
  141. package/dist/knowledge-ui.d.ts +21 -4
  142. package/dist/knowledge-ui.js +75 -7
  143. package/dist/laptop-background.d.ts +16 -0
  144. package/dist/laptop-background.js +108 -2
  145. package/dist/lead-context.js +1 -1
  146. package/dist/lead-contract.d.ts +1 -1
  147. package/dist/lead-contract.js +2 -2
  148. package/dist/lead-progress.js +3 -0
  149. package/dist/lead-status.d.ts +2 -0
  150. package/dist/lead-status.js +6 -2
  151. package/dist/lead-tools.js +51 -2
  152. package/dist/lead.js +1 -1
  153. package/dist/lesson-notes.d.ts +81 -0
  154. package/dist/lesson-notes.js +336 -0
  155. package/dist/limits-ui.js +1 -1
  156. package/dist/local-owner.d.ts +1 -1
  157. package/dist/local-owner.js +11 -0
  158. package/dist/machine-check.d.ts +16 -0
  159. package/dist/machine-check.js +26 -0
  160. package/dist/machine-identity.d.ts +75 -0
  161. package/dist/machine-identity.js +170 -0
  162. package/dist/mcp-core.js +11 -1
  163. package/dist/memory-cli.d.ts +300 -1
  164. package/dist/memory-cli.js +62 -9
  165. package/dist/memory-pass.d.ts +16 -0
  166. package/dist/memory-pass.js +67 -3
  167. package/dist/memory-tidy.d.ts +181 -0
  168. package/dist/memory-tidy.js +523 -0
  169. package/dist/memory-use.d.ts +35 -0
  170. package/dist/memory-use.js +109 -0
  171. package/dist/merge-checklist.d.ts +55 -0
  172. package/dist/merge-checklist.js +182 -0
  173. package/dist/needs-you.d.ts +32 -13
  174. package/dist/needs-you.js +108 -49
  175. package/dist/nightly-github.d.ts +2 -0
  176. package/dist/nightly-github.js +4 -4
  177. package/dist/operate-remote-arguments.js +6 -1
  178. package/dist/operate-remote.js +3 -1
  179. package/dist/operate.d.ts +6 -1
  180. package/dist/operate.js +540 -70
  181. package/dist/phase-routing.js +2 -2
  182. package/dist/plain-schema.d.ts +23 -0
  183. package/dist/plain-schema.js +94 -0
  184. package/dist/plain-words.d.ts +20 -0
  185. package/dist/plain-words.js +66 -0
  186. package/dist/planner.js +2 -1
  187. package/dist/process-liveness.d.ts +2 -0
  188. package/dist/process-liveness.js +61 -17
  189. package/dist/project-delete-ui.d.ts +7 -2
  190. package/dist/project-delete-ui.js +12 -1
  191. package/dist/project-delete.js +13 -2
  192. package/dist/project-knowledge.d.ts +18 -11
  193. package/dist/project-knowledge.js +39 -9
  194. package/dist/project-learning.d.ts +2 -1
  195. package/dist/project-learning.js +15 -45
  196. package/dist/project-memory.d.ts +5 -0
  197. package/dist/project-memory.js +22 -2
  198. package/dist/proof.js +30 -5
  199. package/dist/provider-auth.js +1 -1
  200. package/dist/publish.js +9 -1
  201. package/dist/pull-request-flow.js +5 -0
  202. package/dist/push.d.ts +26 -3
  203. package/dist/push.js +55 -15
  204. package/dist/release-adapters.d.ts +9 -0
  205. package/dist/release-adapters.js +87 -1
  206. package/dist/release-approval.d.ts +191 -0
  207. package/dist/release-approval.js +382 -0
  208. package/dist/release-auto.js +26 -17
  209. package/dist/release-card.d.ts +74 -6
  210. package/dist/release-card.js +179 -23
  211. package/dist/release-carry.d.ts +7 -2
  212. package/dist/release-carry.js +39 -9
  213. package/dist/release-cli.d.ts +4 -0
  214. package/dist/release-cli.js +38 -23
  215. package/dist/release-coverage.js +1 -1
  216. package/dist/release-next.d.ts +48 -0
  217. package/dist/release-next.js +161 -0
  218. package/dist/release.d.ts +50 -1
  219. package/dist/release.js +118 -11
  220. package/dist/remote-destination.d.ts +36 -0
  221. package/dist/remote-destination.js +217 -0
  222. package/dist/remote-main.d.ts +48 -0
  223. package/dist/remote-main.js +84 -5
  224. package/dist/repository-context.d.ts +3 -2
  225. package/dist/repository-context.js +112 -70
  226. package/dist/restart-certification.d.ts +1 -0
  227. package/dist/restart-certification.js +8 -6
  228. package/dist/result-acts.d.ts +29 -21
  229. package/dist/result-acts.js +36 -25
  230. package/dist/result-follow-ups.d.ts +2 -2
  231. package/dist/result-follow-ups.js +18 -4
  232. package/dist/result-review.d.ts +1 -1
  233. package/dist/route-change-check.d.ts +4 -0
  234. package/dist/route-change-check.js +13 -0
  235. package/dist/route-change.d.ts +22 -0
  236. package/dist/route-change.js +89 -0
  237. package/dist/route-hooks.js +3 -2
  238. package/dist/route-results.d.ts +6 -1
  239. package/dist/route-results.js +14 -5
  240. package/dist/run-closeout.d.ts +1 -1
  241. package/dist/run-closeout.js +7 -5
  242. package/dist/runner-process.js +3 -2
  243. package/dist/runner.js +2 -1
  244. package/dist/scope.d.ts +6 -1
  245. package/dist/scope.js +22 -9
  246. package/dist/serve.d.ts +1 -1
  247. package/dist/serve.js +24 -18
  248. package/dist/server/chat.js +14 -12
  249. package/dist/server/chrome.d.ts +1 -1
  250. package/dist/server/chrome.js +26 -35
  251. package/dist/server/console.css +23 -0
  252. package/dist/server/guards.js +4 -0
  253. package/dist/server/home.d.ts +6 -0
  254. package/dist/server/home.js +21 -13
  255. package/dist/server/http.js +1 -1
  256. package/dist/server/kit.js +1 -1
  257. package/dist/server/merge-ready-view.d.ts +10 -0
  258. package/dist/server/merge-ready-view.js +30 -0
  259. package/dist/server/pages-assets.d.ts +1 -0
  260. package/dist/server/pages-assets.js +8 -5
  261. package/dist/server/pages.js +103 -82
  262. package/dist/server/render-chat.js +2 -2
  263. package/dist/server/render-code-map.d.ts +30 -0
  264. package/dist/server/render-code-map.js +137 -0
  265. package/dist/server/render-flaky.d.ts +20 -0
  266. package/dist/server/render-flaky.js +59 -0
  267. package/dist/server/render-history.js +1 -1
  268. package/dist/server/render-new-task.d.ts +26 -1
  269. package/dist/server/render-new-task.js +30 -5
  270. package/dist/server/render-pages.d.ts +1 -175
  271. package/dist/server/render-pages.js +38 -313
  272. package/dist/server/render-projects.d.ts +2 -1
  273. package/dist/server/render-projects.js +9 -7
  274. package/dist/server/render-release.d.ts +19 -0
  275. package/dist/server/render-release.js +84 -0
  276. package/dist/server/render-results.d.ts +2 -2
  277. package/dist/server/render-results.js +71 -73
  278. package/dist/server/render-tasks.d.ts +13 -2
  279. package/dist/server/render-tasks.js +156 -139
  280. package/dist/server/retired-routes.d.ts +17 -0
  281. package/dist/server/retired-routes.js +53 -0
  282. package/dist/server/route-table.d.ts +4 -1
  283. package/dist/server/route-table.js +52 -13
  284. package/dist/server/settings.js +129 -22
  285. package/dist/server/task-views.js +61 -26
  286. package/dist/server/tasks.d.ts +17 -6
  287. package/dist/server/tasks.js +273 -338
  288. package/dist/server/welcome.js +1 -1
  289. package/dist/settings-groups-ui.js +1 -1
  290. package/dist/setup-guide.d.ts +2 -2
  291. package/dist/stop-proof.d.ts +67 -0
  292. package/dist/stop-proof.js +295 -0
  293. package/dist/storage-sweep-process.js +1 -1
  294. package/dist/storage-sweep.d.ts +5 -3
  295. package/dist/storage-sweep.js +30 -9
  296. package/dist/storage-ui.js +28 -17
  297. package/dist/storage.d.ts +27 -2
  298. package/dist/storage.js +39 -2
  299. package/dist/store.d.ts +148 -34
  300. package/dist/store.js +626 -177
  301. package/dist/surface.js +46 -5
  302. package/dist/sweep-quarantine.d.ts +1 -0
  303. package/dist/sweep-quarantine.js +6 -4
  304. package/dist/tamper-scan.d.ts +4 -0
  305. package/dist/tamper-scan.js +90 -0
  306. package/dist/task-aliases.d.ts +5 -0
  307. package/dist/task-aliases.js +9 -0
  308. package/dist/task-control.js +4 -1
  309. package/dist/task-outcome-cli.d.ts +2 -0
  310. package/dist/task-outcome-cli.js +36 -17
  311. package/dist/task-sizing.d.ts +5 -0
  312. package/dist/task-sizing.js +2 -0
  313. package/dist/task-source.d.ts +7 -0
  314. package/dist/task-source.js +14 -0
  315. package/dist/task-status.d.ts +9 -0
  316. package/dist/task-status.js +40 -49
  317. package/dist/teardown-git.d.ts +16 -0
  318. package/dist/teardown-git.js +63 -5
  319. package/dist/telegram-progress.js +5 -3
  320. package/dist/telegram-status.js +6 -4
  321. package/dist/test-infra.d.ts +8 -0
  322. package/dist/test-infra.js +18 -0
  323. package/dist/tidy.js +1 -1
  324. package/dist/toolroll-update-ui.js +2 -2
  325. package/dist/toolroll-update.js +18 -5
  326. package/dist/tui-contract.d.ts +24 -0
  327. package/dist/tui-source.js +67 -8
  328. package/dist/tui.js +51 -51
  329. package/dist/vanished-retry.d.ts +30 -0
  330. package/dist/vanished-retry.js +174 -0
  331. package/dist/work-index.d.ts +2 -0
  332. package/dist/work-index.js +82 -70
  333. package/dist/work-summary.d.ts +2 -2
  334. package/dist/work-summary.js +2 -0
  335. package/dist/workspace-ui.d.ts +4 -2
  336. package/dist/workspace-ui.js +28 -22
  337. package/dist/worktree.d.ts +12 -3
  338. package/dist/worktree.js +68 -13
  339. package/package.json +1 -1
  340. package/dist/server/render-next.d.ts +0 -63
  341. package/dist/server/render-next.js +0 -109
package/CHANGELOG.md CHANGED
@@ -1,5 +1,92 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 0.9.94 — 2026-11-07
6
+
7
+ - **Small tasks start in one step.** For small work you start yourself, New
8
+ task shows the exact terms and **Build** is your approval. If the change
9
+ grows past small, Toolroll stops and asks you to approve it to keep it.
10
+ In the terminal: `toolroll task new "<what should change>" --repo <project>`.
11
+ - **Start a task from a GitHub issue or pull request.** Paste the link in New
12
+ task, or use `toolroll task new --from <url>`. Toolroll quotes the text as
13
+ written outside Toolroll, and you read it before filing it.
14
+ - **Needs you now holds only what needs you.** Things that are simply waiting
15
+ say what they wait for, and the task page, chat, the terminal and pings all
16
+ say the same sentence. Pings are one line with one button ("#41 is ready to
17
+ review").
18
+ - **Fewer pages and fewer words.** Home has Needs you, Running and Waiting;
19
+ Tasks is the one list. Old links still land in the right place. A result has
20
+ Accept, Send back and Retry; a plan has Approve, Request changes and Reject.
21
+ One set of keyboard shortcuts works in the console and the terminal.
22
+ - **"Can I merge?"** One checklist on each task: plan, build, checks,
23
+ requirements, pull request, review comments and release check.
24
+ - **A code map for each project.** `/projects/<name>/map` and `toolroll map`
25
+ show the areas, files and exports of your main branch, generated by Toolroll
26
+ itself with no agent or model. Builds and the lead can read it, and the page
27
+ reports how builds with the map compare to builds without it. Turn it off
28
+ per project in its settings.
29
+ - **Flaky tests**: each project lists tests with mixed outcomes under recorded
30
+ conditions, from local checks and GitHub CI re-runs, with history and one
31
+ action, "Make a task to fix it". `toolroll flaky list|show`. Read only:
32
+ nothing is rerun and no check changes.
33
+ - **Lesson notes**: when a revision passes a test that failed, Toolroll
34
+ proposes a note with what was recorded. You put it in your own words and
35
+ accept it as a decision, then see how often that test fails afterwards.
36
+ - **Last included in a run** on lessons, references and decisions, and
37
+ decisions now reach runs in projects with no other knowledge.
38
+ - **Clean up** in Settings → Knowledge proposes retiring, merging or settling
39
+ memory entries. Nothing changes until you accept, and restore and undo merge
40
+ are there when you change your mind. `toolroll memory tidy|restore|unmerge`.
41
+ - **Toolroll cleans up task folders nobody is using.** A task idle for 14
42
+ days now loses its working folder even if its result is still waiting for
43
+ review or the task never finished, as long as nothing would be lost: no
44
+ uncommitted changes, every commit on its branch, nothing running in it.
45
+ Reviewing, Accept, Send back, Run checks, Retry and Resume all keep working;
46
+ the next run makes the folder again from its branch. Choose 7, 14 or 30
47
+ days or never in Settings → Storage, or with
48
+ `toolroll storage cleanup idle <days>`. `toolroll storage` shows what the
49
+ next sweep would free.
50
+ - **Folders with uncommitted changes are listed, never removed.** Settings →
51
+ Storage and `toolroll storage` now show them so you can decide. Storage
52
+ calls these "task folders" now.
53
+ - **Builds that vanish recover on their own, within limits.** A lost build
54
+ counts once and retries up to three times. Past that, or with no budget left,
55
+ the task stops and says why.
56
+ - **A missing check verdict gets one automatic recheck** of that exact commit,
57
+ with no agent, instead of waiting for you.
58
+ - **The builder comes back after a crash.** Toolroll restarts it only if you
59
+ left it on and it can prove the old process is gone. If you turned it off, it
60
+ stays off.
61
+ - **A changed route is one Approve.** When a task's model route changed since
62
+ you approved, you see one line saying what changed and approve once.
63
+ - **One release command.** `toolroll release <branch>` works out the next step,
64
+ picks up what a stopped deploy left, and updates a branch that is behind main.
65
+ - **Toolroll knows which computer it is on, even if the network renames it.**
66
+ A release no longer waits forever on "the computer that ran build #N".
67
+ - **Checks say how they ran.** Each check launch records its sandbox, and the
68
+ result page and release card show it. A check with no record reads unknown and
69
+ is never carried forward as sandboxed.
70
+
71
+ ## 0.9.93 — 2026-11-06
72
+
73
+ - **Approve a release with one tap.** When a release is ready for your yes,
74
+ your phone gets one notification ("A release is ready for your approval")
75
+ that opens a single screen: what changed since the last release, the
76
+ GitHub checks, the full terms, and one **Approve release** button. Approve
77
+ and walk away: it deploys only after everything passes, and you hear back
78
+ only if something fails. In the terminal, `toolroll release approve` finds
79
+ the release itself — no task names or codes to copy.
80
+ - **No more "Confirm it stopped".** When a finished build's process record
81
+ is incomplete, Toolroll now checks the computer itself, stops anything left
82
+ over that it can prove is that build's, and carries on. The task says
83
+ "Making sure build #N stopped. This clears on its own." Releases wait
84
+ through it instead of stopping.
85
+ - **Checks record what they saw.** Each finished check now keeps an advisory
86
+ record of the exact code, conditions and failures, groundwork for a list
87
+ of flaky tests and for lessons from fixed failures. It never changes a
88
+ check's result.
89
+
3
90
  ## 0.9.92 — 2026-11-05
4
91
 
5
92
  - **Toolroll learns which cheaper models do the job.** Every task now gets a
@@ -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
  }
@@ -90,6 +90,8 @@ export type FenceGrants = {
90
90
  pool?: CheckoutPool;
91
91
  network?: NetworkFence;
92
92
  writes?: WriteAllowlist;
93
+ /** Which `readable` entries are one exact file: read with no listing of its folder, so a sibling stays denied. */
94
+ exactFiles?: string[];
93
95
  /** Policy v1: the home configuration names denied by name, present or not (TaskBoundary.homeNames). */
94
96
  homeNames?: string[];
95
97
  /** macOS: the project's sealed mach services beyond the runtime's baseline (its `allowMachLookup`). */
@@ -359,6 +361,8 @@ export type FenceKind = "build" | "command" | "check" | "flow" | "tool-server";
359
361
  * with no checkout), so no caller forgets one; every path comes from Toolroll's own records, never from the checkout.
360
362
  */
361
363
  export type FenceContext = {
364
+ /** One exact file read back inside the fence (a build's code map), or null. */
365
+ exactFile?: string | null;
362
366
  databaseFile: string | null;
363
367
  worktree: string | null;
364
368
  boundary: TaskBoundary | null;
@@ -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);
@@ -1180,9 +1180,12 @@ export function fenceFor(kind, ctx) {
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" | "confirm-stopped" | "inspect-decisions" | "inspect-run" | "inspect-stop" | "inspect-task" | "open-pr" | "reconcile-run" | "sign-in" | import("./dispatch.js").DispatchAction;
26
+ code: "answer-agent" | "inspect-decisions" | "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;
@@ -31,8 +31,8 @@ export function assignmentActionHref(assignment) {
31
31
  if (action.code === 'sign-in')
32
32
  return '/settings/agents#providers';
33
33
  // The confirmation sits on the task page, behind the password.
34
- if (action.code === 'confirm-stopped')
35
- return `/t/${encodeURIComponent(assignment.rootId)}#confirm-stopped`;
34
+ if (action.code === 'recheck-stopped')
35
+ return `/t/${encodeURIComponent(assignment.rootId)}#recheck-stopped`;
36
36
  if (action.code === 'open-pr' && /^https:\/\/github\.com\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+\/pull\/\d+$/.test(assignment.publication?.prUrl ?? ''))
37
37
  return assignment.publication.prUrl;
38
38
  if (runId !== null && (action.code === 'open-result' || action.code === 'open-pr' || action.code === 'retry-review')) {
@@ -63,7 +63,7 @@ export function assignmentAttemptsOf(assignment) {
63
63
  export function assignmentAttemptsHtml(assignment) {
64
64
  if (assignment.attempts.length === 0)
65
65
  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>`;
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}">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
67
  }
68
68
  /** Only recognized storage limits and explicitly identified older records are
69
69
  * 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";
@@ -252,13 +253,14 @@ export function assignmentOf(store, taskId, now, access, root, memo) {
252
253
  : { code: "inspect-task", label: `Open ${successor}`, target: { taskId: successor, runId: null, decisionId: null }, access: "read", retry: "read-again" };
253
254
  }
254
255
  else if (!live && processFact !== null && result !== null) {
255
- // Toolroll can't confirm the build stopped. A person confirms it when
256
- // nothing of it may still run; otherwise the work waits, needing no one.
256
+ // Toolroll hasn't yet proven the build stopped. It proves that itself
257
+ // (stop-proof.ts), so the work waits, needing no one; only when this
258
+ // computer can't look is it held, offering Check again — never a vouch.
257
259
  const process = processNeedOf(processFact);
258
260
  if ("need" in process) {
259
261
  state = "needs-decision";
260
262
  detail = NEEDS[process.need].sentence({ build: process.build });
261
- primaryAction = { code: "confirm-stopped", label: NEEDS[process.need].action.label, target: { taskId: current.id, runId: process.build, decisionId: null },
263
+ primaryAction = { code: "recheck-stopped", label: NEEDS[process.need].action.label, target: { taskId: current.id, runId: process.build, decisionId: null },
262
264
  access: access.principal === "operator" ? "operator-control" : "operator-handoff", retry: "refresh-before-acting" };
263
265
  need = { key: process.need, build: process.build };
264
266
  }
@@ -480,6 +482,11 @@ function acknowledgeCurrent(store, current, receiptDigest, actor, now, root, acc
480
482
  // The nightly check of main is a check only (release-once.md, part 3): nobody completes it.
481
483
  if (store.handle.prepare("SELECT 1 AS hit FROM nightly_check WHERE task = ?").get(current.receipt.taskId) !== undefined)
482
484
  return { ok: false, reason: "check-only", message: "The nightly check of main is only a check; it isn't completed." };
485
+ // Toolroll is still proving its build stopped (stop-proof.ts): a wait that clears on its own, said as one, so a
486
+ // release waits for it instead of stopping.
487
+ if (current.state !== "ready-to-check" && current.state !== "complete" && current.need != null && "wait" in current.need && STOP_WAITS.has(current.need.wait)) {
488
+ return { ok: false, reason: "stopping", message: `Toolroll is still making sure ${current.need.build === null ? "the build" : `build #${current.need.build}`} stopped. This clears on its own.` };
489
+ }
483
490
  if (current.state !== "ready-to-check" && current.state !== "complete")
484
491
  return { ok: false, reason: "not-ready", message: "This assignment still has unresolved execution, scope or decisions." };
485
492
  // A release task completes only while its release approval stands for it (release-once.md, liveReleaseOf).
@@ -490,6 +497,10 @@ function acknowledgeCurrent(store, current, receiptDigest, actor, now, root, acc
490
497
  // token, a remote API run) that may complete ordinary work but is never the second person on protected work.
491
498
  const person = actor.startsWith("operator:") && !machineActing() ? actor.slice("operator:".length) : null;
492
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 };
493
504
  if (current.state !== "complete") {
494
505
  // The one readiness guard: a failed or missing required check, an unresolved requirement or HIGH finding refuses.
495
506
  const blockers = current.readiness?.blockers ?? [];
@@ -674,3 +685,5 @@ export function assignmentUpdates(store, now, access, query, root) {
674
685
  });
675
686
  return { events, nextCursor: page.length === 0 ? after : Number(page.at(-1)["id"]), hasMore: rows.length > limit };
676
687
  }
688
+ /** The waits of a build Toolroll is still proving stopped by itself (needs-you.ts processNeedOf). */
689
+ const STOP_WAITS = new Set(["build-finishing", "build-checking", "build-stopping"]);
@@ -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
+ };