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/dist/dispose.js CHANGED
@@ -1,4 +1,8 @@
1
1
  // Assignment tracking grants no repair authority or attempts.
2
+ import { STORAGE_HOLD_PREFIX } from "./needs-you.js";
3
+ import { blockAfterSignedOut } from "./fallback.js";
4
+ import { authorizeCheckOnly } from "./check-only.js";
5
+ import { proposeNewRoute } from "./route-change.js";
2
6
  import { originalTaskBase, focusedTestCommandSupported } from "./observations.js";
3
7
  /**
4
8
  * The build disposition service (Parity II Phase 2, v4 Q2 / v6 W1): the
@@ -18,6 +22,7 @@ import { bodyHashOf, publicationBody } from "./publish.js";
18
22
  import { modeTermsFromJson } from "./modes.js";
19
23
  import { evidenceRoot, readVerifiedArtifact, writeEvidenceFile } from "./evidence.js";
20
24
  import { queueBuildReview } from "./build-review.js";
25
+ import { followBuiltRun } from "./check-repair.js";
21
26
  import { homedir } from "node:os";
22
27
  import { nightlyTaskRef, releaseTaskRef } from "./store.js";
23
28
  import {} from "./proof.js";
@@ -158,6 +163,8 @@ function disposeBuildOutcomeLocked(context, result) {
158
163
  });
159
164
  // A stated no-change publishes nothing.
160
165
  if (result.noChange !== true && result.committed) {
166
+ // The approval that built this commit allows one automatic recheck of it, should its verdict go missing.
167
+ authorizeCheckOnly(store, runId, clock());
161
168
  const grant = store.publicationGrantFor(repo);
162
169
  const headSha = store.getRun(runId)?.headRevision ?? null;
163
170
  if (grant !== null &&
@@ -198,6 +205,7 @@ function disposeBuildOutcomeLocked(context, result) {
198
205
  store.resetStrikes(taskRef);
199
206
  store.clearQuota(runner, provider, model ?? "");
200
207
  maybeRequestAutoReview(store, repo, runId, result.committed, result.noChange === true, clock(), context.evidenceRoot);
208
+ followBuiltRun(store, repo, context.evidenceRoot ?? evidenceRoot(homedir()), runId, worktreePath, clock());
201
209
  return { kind: "built", committed: result.committed, noChange: result.noChange === true };
202
210
  }
203
211
  store.transact(() => {
@@ -327,11 +335,26 @@ function disposeBuildOutcomeLocked(context, result) {
327
335
  sealed: sealed.ok,
328
336
  };
329
337
  }
338
+ if (result.reason === "protected-storage") {
339
+ // The storage check couldn't finish (protected-storage.ts): nothing spent,
340
+ // no strike. The words are kept on the run, the task holds and tries
341
+ // again on its own, and a person reads why and which program to allow.
342
+ if (leaseId !== undefined)
343
+ release(store, leaseId, clock());
344
+ if (result.message.trim() !== "")
345
+ store.recordOutcomeFacts(runId, { handoff: result.message });
346
+ store.finishRun(runId, { outcome: "refused", reason: "protected-storage", now: clock() });
347
+ holdForStorage(store, { taskRef, taskId, message: result.message }, clock());
348
+ return { kind: "skipped", reason: "protected-storage" };
349
+ }
330
350
  // no-claim, not-yours, not-leased, protected-branch, wrong-branch:
331
351
  // invariants the dispatcher was supposed to uphold.
332
352
  if (leaseId !== undefined)
333
353
  release(store, leaseId, clock());
334
354
  store.finishRun(runId, { outcome: "refused", reason: result.reason, now: clock() });
355
+ // A fallback agent not signed in to its plan: the task waits for a person's retry, never loops (fallback.ts).
356
+ if (result.reason === "auth-mode" && blockAfterSignedOut(store, runId, clock()))
357
+ return { kind: "skipped", reason: "fallback-signed-out" };
335
358
  if (result.reason === "stale-approval") {
336
359
  // A deterministic refusal (setup review): the approval no longer
337
360
  // matches the routing, and nothing about the next pass changes that.
@@ -347,6 +370,27 @@ function disposeBuildOutcomeLocked(context, result) {
347
370
  }
348
371
  return { kind: "invariant", reason: result.reason };
349
372
  }
373
+ /** How long a task waits after the storage check couldn't finish before it tries again on its own. */
374
+ export const STORAGE_RETRY_MS = 10 * 60_000;
375
+ export { STORAGE_HOLD_PREFIX };
376
+ /**
377
+ * The storage HOLD: the check couldn't finish, so no launch can be fenced
378
+ * now. The task waits STORAGE_RETRY_MS and tries again on its own (a person
379
+ * who allowed access in Privacy & Security needn't do more), and "Try again"
380
+ * (unhold) lifts it at once. One message per task an hour, with the words.
381
+ */
382
+ export function holdForStorage(store, args, now) {
383
+ store.holdOwned({ taskRef: args.taskRef, ownerKind: "backoff", ownerId: `storage:${args.taskRef}`, reason: `${STORAGE_HOLD_PREFIX}${args.message}`, until: new Date(now.getTime() + STORAGE_RETRY_MS) }, now);
384
+ store.enqueueNotification({
385
+ source: { taskRef: args.taskRef },
386
+ dedupeKey: `protected-storage:${args.taskRef}:${now.toISOString().slice(0, 13)}`,
387
+ kind: "protected-storage",
388
+ pushClass: "attention",
389
+ link: `/t/${encodeURIComponent(args.taskId)}`,
390
+ subject: `${args.taskId}: waiting for macOS permission`,
391
+ body: `${args.message}\nIt tries again on its own in ${STORAGE_RETRY_MS / 60_000} minutes.`,
392
+ }, now);
393
+ }
350
394
  /**
351
395
  * The stale-approval HOLD, shared by the post-build disposition above and
352
396
  * the tick's pre-admission refusal (v48 integrity): a pre-routing row
@@ -357,6 +401,8 @@ function disposeBuildOutcomeLocked(context, result) {
357
401
  */
358
402
  export function holdStaleApproval(store, args, now) {
359
403
  const scope = store.getScope(args.taskId);
404
+ // Toolroll proposes the new exact route itself (route-change.ts): the person's one act is Approve, on one line.
405
+ const proposed = proposeNewRoute(store, args.taskRef, args.taskId, now);
360
406
  store.holdOwned({
361
407
  taskRef: args.taskRef,
362
408
  ownerKind: "backoff",
@@ -370,8 +416,8 @@ export function holdStaleApproval(store, args, now) {
370
416
  kind: "stale-approval",
371
417
  pushClass: "attention",
372
418
  link: `/t/${encodeURIComponent(args.taskId)}`,
373
- subject: `${args.taskId}: its approval no longer matches the build routing — approve it again`,
374
- body: `${args.message}\nNothing runs on it until the scope is approved again; the hold lifts with the yes.`,
419
+ subject: `${args.taskId}: its agents changed — approve it again`,
420
+ body: `${proposed?.words ?? args.message}\nNothing runs on it until it's approved again.`,
375
421
  }, now);
376
422
  }
377
423
  // ---- the bounded repair loop (v40, evidence-review-v1) --------------------
@@ -538,9 +584,9 @@ export function regateTask(store, taskId, now, approval) {
538
584
  export function maybeTriggerRepair(store, repo, evidenceRoot, sourceRunId, verdict, now, cause = "review", automatic = false) {
539
585
  if (verdict !== "short" && verdict !== "refuted")
540
586
  return { kind: "none" };
541
- // Findings go to the lead. Explicit repair/revision actions remain available.
542
- // Historical mode signatures remain intact; no automatic follow-up spends.
543
- if (automatic)
587
+ // A review's findings go to the lead, and a person asks for their repair. A failed check is the one automatic road
588
+ // (check-repair.ts), and only under a mode a person signed for it, checked below.
589
+ if (automatic && cause !== "verification")
544
590
  return { kind: "none" };
545
591
  // One attempt per source run, ever (source_run UNIQUE) — checked first so
546
592
  // a re-fired trigger is a silent no-op, never a duplicate.
@@ -598,9 +644,9 @@ export function maybeTriggerRepair(store, repo, evidenceRoot, sourceRunId, verdi
598
644
  const terms = mode === null ? null : modeTermsFromJson(mode.termsJson);
599
645
  let verification;
600
646
  if (cause === "verification") {
601
- // A signed repair mode is required for this automatic producer. The
602
- // existing review/manual road continues to offer unapproved drafts.
603
- if (terms?.repairAuto !== true || run.finishedAt === null || !run.headRevision ||
647
+ // A signed repair mode is required for this automatic producer. A person
648
+ // asking (check-repair.ts startCheckRepair) gets an unapproved draft instead.
649
+ if ((automatic && terms?.repairAuto !== true) || run.finishedAt === null || !run.headRevision ||
604
650
  (run.outcome !== "built" && run.outcome !== "no-change"))
605
651
  return { kind: "none" };
606
652
  const owner = store.refForId(run.taskRef);
@@ -633,7 +679,7 @@ export function maybeTriggerRepair(store, repo, evidenceRoot, sourceRunId, verdi
633
679
  subject: unrelated.kind === "gate-timed-out" ? "The project check ran out of time; running it once more" : "A slow test outside this change failed the check; running it once more",
634
680
  body: `${gateFailureSummary(unrelated)} The check runs again once on the same commit ${run.headRevision.slice(0, 7)}, with no agent. If it fails again, a person decides.`,
635
681
  link: `/t/${encodeURIComponent(taskId)}` }, now);
636
- return { kind: "none" };
682
+ return { kind: "rerun" };
637
683
  }
638
684
  }
639
685
  store.enqueueNotification({ source: { run: sourceRunId }, dedupeKey: `repair-unrelated:${sourceRunId}`, kind: "repair-evidence", pushClass: "attention",
@@ -284,9 +284,8 @@ export function defaultEasyStartSeams(options) {
284
284
  }
285
285
  },
286
286
  findRepo: async (dir) => {
287
- const { run } = await import("./exec.js");
288
- const top = await run("git", ["rev-parse", "--show-toplevel"], { cwd: dir, timeoutMs: 10_000 });
289
- return top.code === 0 ? top.stdout.trim() : null;
287
+ const { checkoutTop } = await import("./teardown-git.js");
288
+ return checkoutTop(dir);
290
289
  },
291
290
  enrolled: async (repo) => {
292
291
  const { loadRepos } = await import("./repos.js");
@@ -19,6 +19,7 @@
19
19
  * The build's code never runs in the oracle's own process, and nothing the build wrote (a result file, a verdict) is
20
20
  * ever read as one: the verdict travels from the oracle to the runner over the oracle's own pipe.
21
21
  */
22
+ import { prepareProtectedStorage } from "./protected-storage.js";
22
23
  import { spawn } from "node:child_process";
23
24
  import { createHash, randomBytes } from "node:crypto";
24
25
  import { closeSync, constants, lstatSync, mkdirSync, mkdtempSync, openSync, realpathSync, rmSync, writeSync } from "node:fs";
@@ -55,10 +56,12 @@ export function canonical(value) {
55
56
  export const fencedGradeRunner = async ({ cwd, stdin, timeoutMs, deny }) => {
56
57
  if (!macosFenceAvailable() && !linuxFenceAvailable())
57
58
  throw new Error("this machine can't fence the graded program");
59
+ // The storage check, awaited off this thread (protected-storage.ts); a refusal keeps its words.
60
+ const storage = await prepareProtectedStorage();
58
61
  const egress = await startEgressProxy({ hosts: [] });
59
62
  try {
60
63
  const network = { mode: "allowlist", proxyPort: egress.port, brokerPorts: [], loopback: "proxy-only", socketRoots: [cwd], token: egress.secret };
61
- const launch = fenceFor("check", { databaseFile: null, worktree: cwd, boundary: null, binding: null, runFolders: runFolders(null), poolRoots: [], extra: [...deny],
64
+ const launch = fenceFor("check", { databaseFile: null, worktree: cwd, boundary: null, binding: null, runFolders: runFolders(null), poolRoots: [], storage, extra: [...deny],
62
65
  writes: { writes: { subpaths: [cwd], prefixes: [] }, readOnly: [] }, network });
63
66
  const result = await run(process.execPath, ["probe.mjs"], { cwd, stdin, timeoutMs, maxBuffer: ORACLE_OUTPUT_BYTES, processGroup: true, envAllowlist: ["PATH"],
64
67
  env: { ...proxyEnv(egress, "proxy-only"), HOME: cwd, TMPDIR: cwd }, fence: launch.fence, fenceGrants: launch.fenceGrants, confinement: "required" });
package/dist/evals.js CHANGED
@@ -27,6 +27,7 @@ import { createHash, randomBytes } from "node:crypto";
27
27
  import { closeSync, constants, lstatSync, mkdirSync, mkdtempSync, openSync, readdirSync, realpathSync, rmSync, writeSync } from "node:fs";
28
28
  import { dirname, join, relative, sep } from "node:path";
29
29
  import { homedir, hostname } from "node:os";
30
+ import { sameMachineHost } from "./machine-identity.js";
30
31
  import { fileURLToPath } from "node:url";
31
32
  import { createRequire } from "node:module";
32
33
  import { readVerifiedCommit, verifiedBlobs } from "./commit-extract.js";
@@ -840,7 +841,7 @@ export function recoverEvalCampaigns(store, now, options = {}) {
840
841
  const out = [];
841
842
  for (const campaign of store.handle.prepare("SELECT id, runner_host, runner_pid, runner_started FROM eval_campaign WHERE state = 'running' ORDER BY admitted_at").all()) {
842
843
  const id = String(campaign["id"]);
843
- if (String(campaign["runner_host"]) !== host) {
844
+ if (!sameMachineHost(String(campaign["runner_host"]), host)) {
844
845
  out.push({ id, recovered: false, why: `its runner is on ${String(campaign["runner_host"])}` });
845
846
  continue;
846
847
  }
@@ -0,0 +1,53 @@
1
+ import type { Store } from "./store.js";
2
+ export type EvidenceReading = "failing" | "fixed" | "still-failing" | "check-changed" | "out-of-date";
3
+ export type EvidenceSide = {
4
+ run: number;
5
+ head: string;
6
+ status: "passed" | "failed";
7
+ via: "build" | "follow-up";
8
+ screenshots: number[];
9
+ };
10
+ export type EvidencePair = {
11
+ id: number;
12
+ familyRoot: string;
13
+ before: EvidenceSide;
14
+ receiptDigest: string;
15
+ commandDigest: string | null;
16
+ savedBy: string;
17
+ savedAt: string;
18
+ after: EvidenceSide | null;
19
+ latestHead: string | null;
20
+ latestRun: number | null;
21
+ reading: EvidenceReading;
22
+ };
23
+ export declare function evidencePairsOf(store: Store, root: string, taskId: string, now: Date): EvidencePair[];
24
+ export type SavePair = {
25
+ ok: true;
26
+ id: number;
27
+ existing: boolean;
28
+ } | {
29
+ ok: false;
30
+ reason: "unknown" | "not-on-task" | "no-failed-check";
31
+ message: string;
32
+ };
33
+ /** Save a run's failed check as the before side. Only a sealed failure can be saved: there's nothing else to compare. */
34
+ export declare function saveEvidencePair(store: Store, root: string, input: {
35
+ taskId: string;
36
+ runId?: number;
37
+ by: string;
38
+ }, now: Date): SavePair;
39
+ export type RerunPair = {
40
+ ok: true;
41
+ run: number;
42
+ head: string;
43
+ request: number;
44
+ existing: boolean;
45
+ } | {
46
+ ok: false;
47
+ message: string;
48
+ };
49
+ /** Ask for the same check on the latest commit. Its level is the one the saved failure ran at. The caller proves the person. */
50
+ export declare function rerunEvidence(store: Store, taskId: string, actor: string, now: Date): RerunPair;
51
+ /** How a pair reads, in one sentence. */
52
+ export declare function evidenceWords(pair: EvidencePair): string;
53
+ export { askFailingFirst, dropFailingFirst, FAILING_FIRST_FILE, failingFirstOf, failingFirstReportSchema, failingFirstWords, saveFailingFirstReport, type FailingFirst } from "./failing-first.js";
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Before-and-after evidence (docs/plans/fleet-follow-through.md, part 4), and a bug fix's optional failing test first.
3
+ *
4
+ * The before side is saved from a sealed failed check: its run, commit, receipt digest and command digest. The after
5
+ * side is never stored. It is read each time from the family's newest result, so a newer commit reads as out of date
6
+ * without anything having to notice. Rerunning uses the follow-up check road (result-follow-ups.ts): the same approved
7
+ * command, in a fresh checkout, on the latest commit, with no agent.
8
+ *
9
+ * A failing-test-first report is the agent's own words, shown as that. It is not a check Toolroll ran.
10
+ */
11
+ import { readVerificationView } from "./contracts/verification-view.js";
12
+ import { followUpChecksOf, requestFollowUpChecks } from "./result-follow-ups.js";
13
+ import { readingFleet } from "./fleet-schema.js";
14
+ import { cleanFindingText, familyRootOf, NOT_ON_TASK, runOnTask } from "./findings.js";
15
+ import { runCheckLevel } from "./check-levels.js";
16
+ import { failedVerificationEvidence, verificationEvidence } from "./verification-evidence.js";
17
+ /** The family's builder runs that finished with a commit, newest first. */
18
+ function familyRuns(store, taskId) {
19
+ const family = store.taskFamilyOf(taskId, null, true);
20
+ const ids = family === null || family.problem !== null ? [taskId] : family.versions.map(one => one.id);
21
+ return ids.flatMap(id => {
22
+ const ref = store.lookupRef(id);
23
+ return ref === null ? [] : store.runsFor(ref.id).filter(one => one.role === "builder" && one.finishedAt !== null && one.headRevision !== null);
24
+ }).sort((a, b) => b.id - a.id);
25
+ }
26
+ const screenshotsOf = (store, runId) => store.artifactsFor(runId).filter(one => one.kind === "screenshot").map(one => one.id);
27
+ /** The command a run's sealed receipt ran, and whether it passed. Null when it has no readable receipt. */
28
+ function sealedCheck(store, root, runId) {
29
+ const gate = verificationEvidence(store, root, runId);
30
+ if (!gate.ok || gate.bytes === null)
31
+ return null;
32
+ const view = readVerificationView(gate.bytes);
33
+ if (!view.ok)
34
+ return null;
35
+ const result = view.value.result;
36
+ return { digest: view.value.command.digest, passed: result.configured && result.ran === true && result.exitCode === 0 };
37
+ }
38
+ /** The newest result of the family that ran `digest`, by the build's own check or a follow-up check on its commit. */
39
+ function afterSide(store, root, runs, beforeRun, digest, now) {
40
+ let otherCommand = false;
41
+ for (const run of runs) {
42
+ if (run.id < beforeRun)
43
+ break;
44
+ const follow = followUpChecksOf(store, run.id, now).filter(one => (one.state === "passed" || one.state === "failed") && one.head === run.headRevision).at(-1);
45
+ if (follow !== undefined && (digest === null || follow.digest === digest))
46
+ return { run: run.id, head: follow.head, status: follow.state, via: "follow-up", screenshots: screenshotsOf(store, run.id) };
47
+ if (follow !== undefined)
48
+ otherCommand = true;
49
+ if (run.id === beforeRun)
50
+ continue;
51
+ const own = sealedCheck(store, root, run.id);
52
+ if (own === null)
53
+ continue;
54
+ if (digest !== null && own.digest !== digest) {
55
+ otherCommand = true;
56
+ continue;
57
+ }
58
+ return { run: run.id, head: run.headRevision, status: own.passed ? "passed" : "failed", via: "build", screenshots: screenshotsOf(store, run.id) };
59
+ }
60
+ return otherCommand ? "check-changed" : null;
61
+ }
62
+ function pairOf(store, root, row, taskId, now) {
63
+ const runs = familyRuns(store, taskId);
64
+ const beforeRun = Number(row["before_run"]);
65
+ const commandDigest = row["command_digest"] === null ? null : String(row["command_digest"]);
66
+ const found = afterSide(store, root, runs, beforeRun, commandDigest, now);
67
+ const latest = runs[0] ?? null;
68
+ const after = found === "check-changed" ? null : found;
69
+ const reading = found === "check-changed" ? "check-changed"
70
+ : after === null ? latest !== null && latest.headRevision !== String(row["before_head"]) ? "out-of-date" : "failing"
71
+ : after.head !== latest?.headRevision ? "out-of-date"
72
+ : after.status === "passed" ? "fixed" : "still-failing";
73
+ return {
74
+ id: Number(row["id"]), familyRoot: String(row["family_root"]),
75
+ before: { run: beforeRun, head: String(row["before_head"]), status: "failed", via: "build", screenshots: screenshotsOf(store, beforeRun) },
76
+ receiptDigest: String(row["receipt_digest"]), commandDigest, savedBy: String(row["saved_by"]), savedAt: String(row["saved_at"]),
77
+ after, latestHead: latest?.headRevision ?? null, latestRun: latest?.id ?? null, reading,
78
+ };
79
+ }
80
+ export function evidencePairsOf(store, root, taskId, now) {
81
+ return readingFleet(() => store.handle.prepare("SELECT * FROM evidence_pair WHERE family_root = ? ORDER BY id DESC").all(familyRootOf(store, taskId)).map(row => pairOf(store, root, row, taskId, now)), []);
82
+ }
83
+ /** Save a run's failed check as the before side. Only a sealed failure can be saved: there's nothing else to compare. */
84
+ export function saveEvidencePair(store, root, input, now) {
85
+ const ref = store.lookupRef(input.taskId);
86
+ if (ref === null)
87
+ return { ok: false, reason: "unknown", message: `no task \`${input.taskId}\`` };
88
+ // A run named by a caller is checked first, and said plainly: every road (the CLI's --run, the console's form) comes here.
89
+ if (input.runId !== undefined && !runOnTask(store, input.runId, input.taskId))
90
+ return { ok: false, reason: "not-on-task", message: NOT_ON_TASK };
91
+ const runId = input.runId ?? familyRuns(store, input.taskId)[0]?.id;
92
+ const run = runId === undefined ? null : store.getRun(runId);
93
+ const root1 = familyRootOf(store, input.taskId);
94
+ if (run === null || run.headRevision === null)
95
+ return { ok: false, reason: "no-failed-check", message: `${input.taskId} has no finished attempt to save.` };
96
+ const failure = failedVerificationEvidence(store, root, run.id);
97
+ if (failure.kind !== "failed")
98
+ return { ok: false, reason: "no-failed-check", message: `Attempt #${run.id} has no failed check to save.` };
99
+ const view = readVerificationView(failure.receipt);
100
+ const made = store.handle.prepare(`INSERT INTO evidence_pair (family_root, repo, before_run, before_head, receipt_digest, command_digest, saved_by, saved_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
101
+ ON CONFLICT (family_root, before_run) DO NOTHING`).run(root1, ref.repo, run.id, run.headRevision, failure.digest, view.ok ? view.value.command.digest : null, input.by, now.toISOString());
102
+ if (Number(made.changes) === 1)
103
+ return { ok: true, id: Number(made.lastInsertRowid), existing: false };
104
+ const row = store.handle.prepare("SELECT id FROM evidence_pair WHERE family_root = ? AND before_run = ?").get(root1, run.id);
105
+ return { ok: true, id: Number(row?.["id"]), existing: true };
106
+ }
107
+ /** Ask for the same check on the latest commit. Its level is the one the saved failure ran at. The caller proves the person. */
108
+ export function rerunEvidence(store, taskId, actor, now) {
109
+ const latest = familyRuns(store, taskId)[0];
110
+ if (latest === undefined)
111
+ return { ok: false, message: `${taskId} has no finished attempt to check.` };
112
+ const level = runCheckLevel(store, latest.id);
113
+ const asked = requestFollowUpChecks(store, { runId: latest.id, level: (level === "off" || level === null ? "full" : level), actor }, now);
114
+ return asked.ok ? { ok: true, run: latest.id, head: latest.headRevision, request: asked.request, existing: asked.existing } : { ok: false, message: asked.message };
115
+ }
116
+ const short = (head) => head === null ? "no commit" : head.slice(0, 7);
117
+ /** How a pair reads, in one sentence. */
118
+ export function evidenceWords(pair) {
119
+ const before = `Failed on ${short(pair.before.head)} (build #${pair.before.run})`;
120
+ switch (pair.reading) {
121
+ case "failing": return `${before}. Not checked since.`;
122
+ case "fixed": return `${before}, passes on ${short(pair.after.head)} (build #${pair.after.run}).`;
123
+ case "still-failing": return `${before}, still fails on ${short(pair.after.head)} (build #${pair.after.run}).`;
124
+ case "check-changed": return `${before}. The project's check changed since, so the two can't be compared.`;
125
+ case "out-of-date": return `${before}. Out of date: the code is now at ${short(pair.latestHead)}${pair.after === null ? "" : `, and the last check ran on ${short(pair.after.head)}`}.`;
126
+ }
127
+ }
128
+ export { askFailingFirst, dropFailingFirst, FAILING_FIRST_FILE, failingFirstOf, failingFirstReportSchema, failingFirstWords, saveFailingFirstReport } from "./failing-first.js";
@@ -6,9 +6,11 @@
6
6
  * someone signs in, instead of burning attempts. Everything else is the
7
7
  * ordinary failure road.
8
8
  *
9
- * `usage-exhausted`, `credits-depleted` and `transient-throttle` are only
10
- * ever read from older runs' records (they were stamped by a removed
11
- * feature); this build never produces them.
9
+ * `usage-exhausted` moves the work to the next agent a person approved
10
+ * (fallback.ts), so it comes only from a recogniser below, and a recogniser
11
+ * comes only from a real run (docs/plans/fallback-chains.md). A provider with
12
+ * none never reads as exhausted. `credits-depleted` and `transient-throttle`
13
+ * are only ever read from older runs' records.
12
14
  */
13
15
  export type TerminalClass = "usage-exhausted" | "credits-depleted" | "transient-throttle"
14
16
  /** The provider's sign-in or API key no longer works — no retry:
@@ -31,15 +33,26 @@ export type StructuralTerminal = {
31
33
  code: string | null;
32
34
  };
33
35
  /** Classify a finished attempt: a sign-in that no longer works first, for
34
- * any provider or version; then a structural failure terminal is
36
+ * any provider or version; then a recognised plan limit, on the exact provider,
37
+ * auth mode and CLI line it was seen on; then a structural failure terminal is
35
38
  * `not-exhausted`, and anything else `unknown`. */
36
39
  export declare function classifyTerminal(args: {
37
40
  terminal: StructuralTerminal | null;
41
+ /** The run's own provider, its auth mode, and the version the installed CLI reports to the gateway: never the checkout's word. */
42
+ run?: {
43
+ provider: string;
44
+ authMode: string;
45
+ version: string | null;
46
+ };
38
47
  /** What a run that failed within seconds, having produced nothing, said
39
48
  * on its way out (stderr and unstructured stdout) — read ONLY for the
40
49
  * sign-in signal: a CLI that is not logged in often exits before its
41
50
  * structured stream begins. */
42
51
  earlyExit?: string | null;
43
52
  }): TerminalClass;
53
+ /** Whether a terminal reads as a limit for this provider and auth mode, before its CLI version is asked. */
54
+ export declare function terminalMatchesLimit(provider: string, authMode: string, terminal: StructuralTerminal | null): boolean;
55
+ /** Whether Toolroll can recognise this agent running out at all: the words every surface shows beside an entry. */
56
+ export declare function limitRecognised(provider: string, authMode: string): boolean;
44
57
  /** Whether a provider's own words say its sign-in or key no longer works. */
45
58
  export declare function isAuthFailure(text: string): boolean;
@@ -6,12 +6,15 @@
6
6
  * someone signs in, instead of burning attempts. Everything else is the
7
7
  * ordinary failure road.
8
8
  *
9
- * `usage-exhausted`, `credits-depleted` and `transient-throttle` are only
10
- * ever read from older runs' records (they were stamped by a removed
11
- * feature); this build never produces them.
9
+ * `usage-exhausted` moves the work to the next agent a person approved
10
+ * (fallback.ts), so it comes only from a recogniser below, and a recogniser
11
+ * comes only from a real run (docs/plans/fallback-chains.md). A provider with
12
+ * none never reads as exhausted. `credits-depleted` and `transient-throttle`
13
+ * are only ever read from older runs' records.
12
14
  */
13
15
  /** Classify a finished attempt: a sign-in that no longer works first, for
14
- * any provider or version; then a structural failure terminal is
16
+ * any provider or version; then a recognised plan limit, on the exact provider,
17
+ * auth mode and CLI line it was seen on; then a structural failure terminal is
15
18
  * `not-exhausted`, and anything else `unknown`. */
16
19
  export function classifyTerminal(args) {
17
20
  const { terminal } = args;
@@ -21,8 +24,45 @@ export function classifyTerminal(args) {
21
24
  return "auth-expired";
22
25
  if (terminal === null || terminal.failed !== true)
23
26
  return "unknown";
27
+ if (args.run !== undefined && terminal.text !== null && planLimitReached(args.run, terminal.text))
28
+ return "usage-exhausted";
24
29
  return "not-exhausted";
25
30
  }
31
+ const RECOGNISERS = Object.freeze([
32
+ {
33
+ provider: "claude", authMode: "subscription", from: [2, 1, 290], below: [2, 2, 0],
34
+ terminal: /^You've hit your (?:session|weekly|Opus|Sonnet) limit · resets [^\n]{1,64}$/,
35
+ },
36
+ ]);
37
+ function versionOf(version) {
38
+ const parts = version === null ? null : /^(\d{1,6})\.(\d{1,6})\.(\d{1,6})(?:\s|$)/.exec(version);
39
+ return parts === null ? null : [Number(parts[1]), Number(parts[2]), Number(parts[3])];
40
+ }
41
+ function before(a, b) {
42
+ for (let at = 0; at < 3; at++)
43
+ if (a[at] !== b[at])
44
+ return a[at] < b[at];
45
+ return false;
46
+ }
47
+ function planLimitReached(run, text) {
48
+ const version = versionOf(run.version);
49
+ if (version === null)
50
+ return false;
51
+ const words = text.trim();
52
+ return RECOGNISERS.some(one => one.provider === run.provider && one.authMode === run.authMode
53
+ && !before(version, one.from) && before(version, one.below) && one.terminal.test(words));
54
+ }
55
+ /** Whether a terminal reads as a limit for this provider and auth mode, before its CLI version is asked. */
56
+ export function terminalMatchesLimit(provider, authMode, terminal) {
57
+ if (terminal === null || terminal.failed !== true || terminal.text === null)
58
+ return false;
59
+ const words = terminal.text.trim();
60
+ return RECOGNISERS.some(one => one.provider === provider && one.authMode === authMode && one.terminal.test(words));
61
+ }
62
+ /** Whether Toolroll can recognise this agent running out at all: the words every surface shows beside an entry. */
63
+ export function limitRecognised(provider, authMode) {
64
+ return RECOGNISERS.some(one => one.provider === provider && one.authMode === authMode);
65
+ }
26
66
  /**
27
67
  * The sign-in signals, provider-neutral: Claude's expired or unrefreshable
28
68
  * OAuth session and its "Please run /login", an API key the provider calls
package/dist/export.js CHANGED
@@ -57,7 +57,7 @@ export function categoryOf(table) {
57
57
  [/^(flow|workflow_)/, "flows"],
58
58
  [/^(chat_|lead_|team_|notification|push_)/, "chats"],
59
59
  [/^(approver|credential$|invite|sso_identity|coordinator_|oauth_)/, "people"],
60
- [/^(run|artifact|claim|worktree$|execution_slot|criterion_review|diff_comment|proof_|review_request|repair_chain|incident|publication$|merge_|side_spend|knowledge_snapshot|learning_snapshot|skill_snapshot)/, "runs"],
60
+ [/^(run|artifact|claim|worktree$|execution_slot|criterion_review|diff_comment|proof_|review_request|repair_chain|check_repair|evidence_pair|failing_first|finding|provider_capture|incident|publication$|merge_|side_spend|knowledge_snapshot|learning_snapshot|skill_snapshot)/, "runs"],
61
61
  [/^(task|hold$|plan_|scope_|tool_seal|decision|external_|skill_test)/, "tasks"],
62
62
  [/^(project|approval_policy|capability|verify_command|worktree_setup|backend_grant|intake_grant|publication_grant|operating_mode|knowledge_|learning_|memory_|skill_)/, "projects"],
63
63
  [/(_config|_default|_defaults)$|^(budget|model_|provider_|installation_fact|runtime_check|monitoring_status|integration_check|quota|mode_rail|schema_version)/, "settings"],
@@ -0,0 +1,31 @@
1
+ /** Failing test first (docs/plans/fleet-follow-through.md), on its own so the steering mailbox can read it. Evidence pairs
2
+ * reach the builder through follow-up checks, and the mailbox loads with the provider table: importing them from there
3
+ * is a cycle that fails at load in every built command. */
4
+ import { z } from "zod";
5
+ import type { Store } from "./store.js";
6
+ export declare const FAILING_FIRST_OUTPUT_LIMIT = 2000;
7
+ /** Where the agent writes its report, in the attempt's own mailbox folder. */
8
+ export declare const FAILING_FIRST_FILE = "failing-first.json";
9
+ export declare const failingFirstReportSchema: z.ZodObject<{
10
+ key: z.ZodString;
11
+ test: z.ZodString;
12
+ output: z.ZodString;
13
+ }, z.core.$strict>;
14
+ export type FailingFirst = {
15
+ askedBy: string;
16
+ askedAt: string;
17
+ reportRun: number | null;
18
+ test: string | null;
19
+ output: string | null;
20
+ reportedAt: string | null;
21
+ };
22
+ export declare function failingFirstOf(store: Store, taskId: string): FailingFirst | null;
23
+ export declare function askFailingFirst(store: Store, taskId: string, by: string, now: Date): boolean;
24
+ export declare function dropFailingFirst(store: Store, taskId: string): void;
25
+ /** The agent's report, as data. Only a task that asked takes one. */
26
+ export declare function saveFailingFirstReport(store: Store, taskRef: number, runId: number, report: {
27
+ test: string;
28
+ output: string;
29
+ }, now: Date): boolean;
30
+ /** How the step stands, or null when this task didn't ask for it. */
31
+ export declare function failingFirstWords(step: FailingFirst | null): string | null;
@@ -0,0 +1,44 @@
1
+ /** Failing test first (docs/plans/fleet-follow-through.md), on its own so the steering mailbox can read it. Evidence pairs
2
+ * reach the builder through follow-up checks, and the mailbox loads with the provider table: importing them from there
3
+ * is a cycle that fails at load in every built command. */
4
+ import { z } from "zod";
5
+ import { readingFleet } from "./fleet-schema.js";
6
+ import { cleanFindingText, familyRootOf } from "./findings.js";
7
+ export const FAILING_FIRST_OUTPUT_LIMIT = 2_000;
8
+ /** Where the agent writes its report, in the attempt's own mailbox folder. */
9
+ export const FAILING_FIRST_FILE = "failing-first.json";
10
+ export const failingFirstReportSchema = z.strictObject({ key: z.string().min(1).max(64), test: z.string().min(1).max(300), output: z.string().min(1).max(FAILING_FIRST_OUTPUT_LIMIT) });
11
+ export function failingFirstOf(store, taskId) {
12
+ return readingFleet(() => {
13
+ const row = store.handle.prepare("SELECT * FROM failing_first WHERE family_root = ?").get(familyRootOf(store, taskId));
14
+ if (row === undefined)
15
+ return null;
16
+ const text = (key) => row[key] === null ? null : String(row[key]);
17
+ return { askedBy: String(row["asked_by"]), askedAt: String(row["asked_at"]), reportRun: row["report_run"] === null ? null : Number(row["report_run"]), test: text("test"), output: text("output"), reportedAt: text("reported_at") };
18
+ }, null);
19
+ }
20
+ export function askFailingFirst(store, taskId, by, now) {
21
+ const ref = store.lookupRef(taskId);
22
+ if (ref === null)
23
+ return false;
24
+ store.handle.prepare("INSERT INTO failing_first (family_root, repo, asked_by, asked_at) VALUES (?, ?, ?, ?) ON CONFLICT (family_root) DO NOTHING").run(familyRootOf(store, taskId), ref.repo, by, now.toISOString());
25
+ return true;
26
+ }
27
+ export function dropFailingFirst(store, taskId) {
28
+ store.handle.prepare("DELETE FROM failing_first WHERE family_root = ?").run(familyRootOf(store, taskId));
29
+ }
30
+ /** The agent's report, as data. Only a task that asked takes one. */
31
+ export function saveFailingFirstReport(store, taskRef, runId, report, now) {
32
+ const taskId = store.refById(taskRef)?.externalId;
33
+ if (taskId === undefined)
34
+ return false;
35
+ const saved = store.handle.prepare("UPDATE failing_first SET report_run = ?, test = ?, output = ?, reported_at = ? WHERE family_root = ?")
36
+ .run(runId, cleanFindingText(report.test, 300), cleanFindingText(report.output, FAILING_FIRST_OUTPUT_LIMIT), now.toISOString(), familyRootOf(store, taskId));
37
+ return Number(saved.changes) === 1;
38
+ }
39
+ /** How the step stands, or null when this task didn't ask for it. */
40
+ export function failingFirstWords(step) {
41
+ if (step === null)
42
+ return null;
43
+ return step.reportedAt === null ? "Failing test first was asked for. No report yet." : `The agent reports it wrote ${step.test ?? "a test"} first and saw it fail. Toolroll didn't run this itself.`;
44
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * v156: fallback chains (docs/plans/fallback-chains.md). New tables only, so a saved row reads as it did. Their names
3
+ * differ from the tables v115 dropped: nothing old comes back under an old name. Kept beside nothing else so the update
4
+ * road can read the list without loading the store.
5
+ */
6
+ export declare const V156_SCHEMA = "\nCREATE TABLE IF NOT EXISTS fallback_setting (\n repo TEXT PRIMARY KEY,\n entries_json TEXT NOT NULL,\n paid_granted_by TEXT,\n paid_granted_at TEXT,\n updated_by TEXT NOT NULL,\n updated_at TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS fallback_walk (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n task_ref INTEGER NOT NULL,\n scope_digest TEXT NOT NULL,\n chain_digest TEXT NOT NULL,\n cursor INTEGER NOT NULL DEFAULT 0,\n tail_run INTEGER NOT NULL,\n state TEXT NOT NULL DEFAULT 'open' CHECK (state IN ('open','closed')),\n closed_reason TEXT,\n opened_at TEXT NOT NULL,\n closed_at TEXT\n);\nCREATE UNIQUE INDEX IF NOT EXISTS fallback_walk_one_open ON fallback_walk (task_ref) WHERE state = 'open';\nCREATE TABLE IF NOT EXISTS fallback_move (\n walk INTEGER NOT NULL REFERENCES fallback_walk(id),\n to_index INTEGER NOT NULL,\n from_run INTEGER NOT NULL UNIQUE,\n to_run INTEGER UNIQUE,\n terminal_class TEXT NOT NULL,\n paid INTEGER NOT NULL DEFAULT 0,\n moved_at TEXT NOT NULL,\n PRIMARY KEY (walk, to_index)\n);\n";
7
+ export declare const V156_TABLES: readonly ["fallback_setting", "fallback_walk", "fallback_move"];
8
+ export declare const V156_STEPS: readonly ["fallback-tables"];
@@ -0,0 +1,40 @@
1
+ /**
2
+ * v156: fallback chains (docs/plans/fallback-chains.md). New tables only, so a saved row reads as it did. Their names
3
+ * differ from the tables v115 dropped: nothing old comes back under an old name. Kept beside nothing else so the update
4
+ * road can read the list without loading the store.
5
+ */
6
+ export const V156_SCHEMA = `
7
+ CREATE TABLE IF NOT EXISTS fallback_setting (
8
+ repo TEXT PRIMARY KEY,
9
+ entries_json TEXT NOT NULL,
10
+ paid_granted_by TEXT,
11
+ paid_granted_at TEXT,
12
+ updated_by TEXT NOT NULL,
13
+ updated_at TEXT NOT NULL
14
+ );
15
+ CREATE TABLE IF NOT EXISTS fallback_walk (
16
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
17
+ task_ref INTEGER NOT NULL,
18
+ scope_digest TEXT NOT NULL,
19
+ chain_digest TEXT NOT NULL,
20
+ cursor INTEGER NOT NULL DEFAULT 0,
21
+ tail_run INTEGER NOT NULL,
22
+ state TEXT NOT NULL DEFAULT 'open' CHECK (state IN ('open','closed')),
23
+ closed_reason TEXT,
24
+ opened_at TEXT NOT NULL,
25
+ closed_at TEXT
26
+ );
27
+ CREATE UNIQUE INDEX IF NOT EXISTS fallback_walk_one_open ON fallback_walk (task_ref) WHERE state = 'open';
28
+ CREATE TABLE IF NOT EXISTS fallback_move (
29
+ walk INTEGER NOT NULL REFERENCES fallback_walk(id),
30
+ to_index INTEGER NOT NULL,
31
+ from_run INTEGER NOT NULL UNIQUE,
32
+ to_run INTEGER UNIQUE,
33
+ terminal_class TEXT NOT NULL,
34
+ paid INTEGER NOT NULL DEFAULT 0,
35
+ moved_at TEXT NOT NULL,
36
+ PRIMARY KEY (walk, to_index)
37
+ );
38
+ `;
39
+ export const V156_TABLES = Object.freeze(["fallback_setting", "fallback_walk", "fallback_move"]);
40
+ export const V156_STEPS = Object.freeze(["fallback-tables"]);