toolroll 0.9.93 → 0.9.95

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (436) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/dist/activity-line.d.ts +7 -4
  3. package/dist/activity-line.js +9 -7
  4. package/dist/agent-fence.d.ts +10 -0
  5. package/dist/agent-fence.js +6 -3
  6. package/dist/approval-authority.d.ts +7 -1
  7. package/dist/approval-authority.js +11 -2
  8. package/dist/approval-password.d.ts +31 -0
  9. package/dist/approval-password.js +26 -0
  10. package/dist/assignment-status.d.ts +1 -1
  11. package/dist/assignment-ui.js +3 -1
  12. package/dist/assignment.js +5 -0
  13. package/dist/attest.d.ts +3 -0
  14. package/dist/attest.js +10 -0
  15. package/dist/boot-identity.d.ts +2 -1
  16. package/dist/boot-identity.js +6 -4
  17. package/dist/bounded-spawn.d.ts +57 -0
  18. package/dist/bounded-spawn.js +220 -0
  19. package/dist/browser/terminal.js +42 -42
  20. package/dist/browser/workspace.css +1 -1
  21. package/dist/browser/workspace.js +32 -32
  22. package/dist/browser-crew.js +2 -0
  23. package/dist/browser-shell.d.ts +1 -1
  24. package/dist/browser-shell.js +2 -2
  25. package/dist/browser-workspace.d.ts +50 -2
  26. package/dist/browser-workspace.js +3 -3
  27. package/dist/build-review.js +2 -0
  28. package/dist/builder-restart.d.ts +21 -0
  29. package/dist/builder-restart.js +50 -0
  30. package/dist/builder.d.ts +11 -2
  31. package/dist/builder.js +234 -101
  32. package/dist/card-catalog.d.ts +209 -0
  33. package/dist/card-catalog.js +133 -0
  34. package/dist/card-cli.d.ts +43 -0
  35. package/dist/card-cli.js +44 -0
  36. package/dist/card-render.d.ts +26 -0
  37. package/dist/card-render.js +223 -0
  38. package/dist/card-sources.d.ts +68 -0
  39. package/dist/card-sources.js +234 -0
  40. package/dist/chat-acceptance.js +1 -1
  41. package/dist/chat-actions.d.ts +106 -9
  42. package/dist/chat-actions.js +473 -46
  43. package/dist/chat-approval.d.ts +13 -0
  44. package/dist/chat-approval.js +39 -9
  45. package/dist/chat-card-part.d.ts +11 -0
  46. package/dist/chat-card-part.js +18 -0
  47. package/dist/chat-card.d.ts +46 -0
  48. package/dist/chat-card.js +99 -0
  49. package/dist/chat-channel.d.ts +2 -0
  50. package/dist/chat-channel.js +37 -14
  51. package/dist/chat-controls.d.ts +4 -0
  52. package/dist/chat-controls.js +4 -0
  53. package/dist/chat-decide.d.ts +10 -16
  54. package/dist/chat-decide.js +79 -62
  55. package/dist/chat-delivery.js +13 -4
  56. package/dist/chat-flow.d.ts +0 -27
  57. package/dist/chat-flow.js +31 -2
  58. package/dist/chat-quiet.js +4 -4
  59. package/dist/chat-task-actions.d.ts +3 -2
  60. package/dist/chat-task-actions.js +19 -5
  61. package/dist/chat-terms.d.ts +14 -2
  62. package/dist/chat-terms.js +35 -0
  63. package/dist/chat-voice.d.ts +1 -1
  64. package/dist/chat-voice.js +2 -2
  65. package/dist/check-facts.d.ts +3 -1
  66. package/dist/check-facts.js +57 -11
  67. package/dist/check-failure.d.ts +24 -3
  68. package/dist/check-failure.js +89 -13
  69. package/dist/check-levels-ui.d.ts +4 -0
  70. package/dist/check-levels-ui.js +10 -1
  71. package/dist/check-load.d.ts +85 -0
  72. package/dist/check-load.js +147 -0
  73. package/dist/check-only-standing.d.ts +36 -0
  74. package/dist/check-only-standing.js +39 -0
  75. package/dist/check-only.d.ts +15 -0
  76. package/dist/check-only.js +99 -0
  77. package/dist/check-repair.d.ts +47 -0
  78. package/dist/check-repair.js +240 -0
  79. package/dist/check-report.d.ts +2 -0
  80. package/dist/check-report.js +1 -1
  81. package/dist/check-stamp.d.ts +46 -0
  82. package/dist/check-stamp.js +93 -0
  83. package/dist/checkout-cleanup.d.ts +36 -5
  84. package/dist/checkout-cleanup.js +166 -14
  85. package/dist/claim.d.ts +6 -0
  86. package/dist/claim.js +13 -2
  87. package/dist/cli-help.d.ts +4 -1
  88. package/dist/cli-help.js +49 -30
  89. package/dist/cli.js +15 -1
  90. package/dist/code-extract.d.ts +44 -0
  91. package/dist/code-extract.js +88 -0
  92. package/dist/code-map-acts.d.ts +24 -0
  93. package/dist/code-map-acts.js +53 -0
  94. package/dist/code-map-admission.d.ts +52 -0
  95. package/dist/code-map-admission.js +295 -0
  96. package/dist/code-map-build.d.ts +63 -0
  97. package/dist/code-map-build.js +415 -0
  98. package/dist/code-map-cli.d.ts +153 -0
  99. package/dist/code-map-cli.js +103 -0
  100. package/dist/code-map-main.d.ts +1 -0
  101. package/dist/code-map-main.js +48 -0
  102. package/dist/code-map-read.d.ts +106 -0
  103. package/dist/code-map-read.js +171 -0
  104. package/dist/code-map-report.d.ts +37 -0
  105. package/dist/code-map-report.js +95 -0
  106. package/dist/code-map-shape.d.ts +172 -0
  107. package/dist/code-map-shape.js +327 -0
  108. package/dist/code-map-store.d.ts +136 -0
  109. package/dist/code-map-store.js +233 -0
  110. package/dist/coding-provider.d.ts +3 -1
  111. package/dist/coding-provider.js +12 -7
  112. package/dist/coding-update.js +3 -2
  113. package/dist/coding-workspace.d.ts +1 -1
  114. package/dist/coding-workspace.js +8 -8
  115. package/dist/console-chrome.js +2 -2
  116. package/dist/containment.d.ts +5 -0
  117. package/dist/containment.js +14 -1
  118. package/dist/contracts/chat-actions.d.ts +92 -0
  119. package/dist/contracts/chat-actions.js +20 -0
  120. package/dist/contracts/chat-card.d.ts +91 -0
  121. package/dist/contracts/chat-card.js +54 -0
  122. package/dist/contracts/chat-content.d.ts +191 -0
  123. package/dist/contracts/chat-content.js +4 -0
  124. package/dist/contracts/cli.d.ts +52 -0
  125. package/dist/contracts/cli.js +4 -0
  126. package/dist/contracts/console-api.d.ts +12 -3
  127. package/dist/contracts/console-api.js +12 -3
  128. package/dist/contracts/flow-propose.d.ts +8 -1
  129. package/dist/contracts/flow-propose.js +3 -1
  130. package/dist/contracts/flow-send.d.ts +126 -0
  131. package/dist/contracts/flow-send.js +3 -0
  132. package/dist/contracts/gateway-tools.d.ts +17 -0
  133. package/dist/contracts/gateway-tools.js +3 -1
  134. package/dist/contracts/lead-tools.d.ts +234 -0
  135. package/dist/contracts/lead-tools.js +25 -1
  136. package/dist/contracts/project-knowledge.d.ts +38 -5
  137. package/dist/contracts/project-knowledge.js +11 -5
  138. package/dist/contracts/project-memory.d.ts +3 -1
  139. package/dist/contracts/project-memory.js +3 -2
  140. package/dist/contracts/storage.d.ts +14 -0
  141. package/dist/contracts/storage.js +3 -1
  142. package/dist/contracts/store-json.d.ts +18 -0
  143. package/dist/contracts/youiyoux-request.d.ts +1 -1
  144. package/dist/contracts/youiyoux-request.js +1 -1
  145. package/dist/desktop-access.js +6 -2
  146. package/dist/discord-chat.js +14 -0
  147. package/dist/dispatch.d.ts +1 -1
  148. package/dist/dispatch.js +25 -16
  149. package/dist/dispose.d.ts +19 -0
  150. package/dist/dispose.js +55 -9
  151. package/dist/easy-start.js +2 -3
  152. package/dist/eval-oracle.js +4 -1
  153. package/dist/evals.js +2 -1
  154. package/dist/evidence-pair.d.ts +53 -0
  155. package/dist/evidence-pair.js +128 -0
  156. package/dist/exhaustion.d.ts +17 -4
  157. package/dist/exhaustion.js +44 -4
  158. package/dist/export.js +1 -1
  159. package/dist/failing-first.d.ts +31 -0
  160. package/dist/failing-first.js +44 -0
  161. package/dist/fallback-schema.d.ts +8 -0
  162. package/dist/fallback-schema.js +40 -0
  163. package/dist/fallback.d.ts +138 -0
  164. package/dist/fallback.js +605 -0
  165. package/dist/fast-start.d.ts +136 -0
  166. package/dist/fast-start.js +431 -0
  167. package/dist/fast-terms.d.ts +9 -0
  168. package/dist/fast-terms.js +23 -0
  169. package/dist/fenced-check-coverage.d.ts +62 -0
  170. package/dist/fenced-check-coverage.js +54 -0
  171. package/dist/findings.d.ts +224 -0
  172. package/dist/findings.js +449 -0
  173. package/dist/first-run.js +1 -1
  174. package/dist/flaky-ci.d.ts +31 -0
  175. package/dist/flaky-ci.js +149 -0
  176. package/dist/flaky-cli.d.ts +60 -0
  177. package/dist/flaky-cli.js +60 -0
  178. package/dist/flaky.d.ts +75 -9
  179. package/dist/flaky.js +255 -0
  180. package/dist/fleet-schema.d.ts +17 -0
  181. package/dist/fleet-schema.js +135 -0
  182. package/dist/flow-browser.js +2 -2
  183. package/dist/flow-card-task.js +3 -0
  184. package/dist/flow-code.d.ts +2 -2
  185. package/dist/flow-code.js +9 -5
  186. package/dist/flow-result-card.d.ts +22 -0
  187. package/dist/flow-result-card.js +55 -0
  188. package/dist/flow-send.js +12 -9
  189. package/dist/flow-steps.js +1 -1
  190. package/dist/flow-triggers.d.ts +1 -1
  191. package/dist/flow-triggers.js +2 -2
  192. package/dist/flow-unattended.d.ts +26 -0
  193. package/dist/flow-unattended.js +12 -3
  194. package/dist/flows-ui.js +10 -2
  195. package/dist/follow-through-brief.d.ts +12 -0
  196. package/dist/follow-through-brief.js +31 -0
  197. package/dist/follow-through-cli.d.ts +23 -0
  198. package/dist/follow-through-cli.js +153 -0
  199. package/dist/git-incident.d.ts +16 -0
  200. package/dist/git-incident.js +69 -6
  201. package/dist/github-import.d.ts +62 -0
  202. package/dist/github-import.js +146 -0
  203. package/dist/guides.js +15 -10
  204. package/dist/handover-note.d.ts +33 -0
  205. package/dist/handover-note.js +106 -0
  206. package/dist/invoke.d.ts +25 -2
  207. package/dist/invoke.js +157 -58
  208. package/dist/job-object-helper.ps1 +10 -3
  209. package/dist/keymap.d.ts +41 -0
  210. package/dist/keymap.js +51 -0
  211. package/dist/knowledge-cli.js +1 -1
  212. package/dist/knowledge-ui.d.ts +21 -4
  213. package/dist/knowledge-ui.js +75 -7
  214. package/dist/laptop-background.d.ts +16 -0
  215. package/dist/laptop-background.js +108 -2
  216. package/dist/lead-cli.js +26 -24
  217. package/dist/lead-context.js +1 -1
  218. package/dist/lead-contract.d.ts +1 -1
  219. package/dist/lead-contract.js +3 -2
  220. package/dist/lead-doors.d.ts +4 -0
  221. package/dist/lead-doors.js +2 -2
  222. package/dist/lead-progress.js +5 -0
  223. package/dist/lead-status.d.ts +2 -0
  224. package/dist/lead-status.js +6 -2
  225. package/dist/lead-tools.js +162 -34
  226. package/dist/lead.js +2 -2
  227. package/dist/lesson-notes.d.ts +81 -0
  228. package/dist/lesson-notes.js +336 -0
  229. package/dist/limits-ui.js +1 -1
  230. package/dist/local-owner.d.ts +1 -1
  231. package/dist/local-owner.js +16 -0
  232. package/dist/machine-check.d.ts +16 -0
  233. package/dist/machine-check.js +26 -0
  234. package/dist/machine-identity.d.ts +75 -0
  235. package/dist/machine-identity.js +170 -0
  236. package/dist/mcp-core.js +11 -1
  237. package/dist/memory-cli.d.ts +300 -1
  238. package/dist/memory-cli.js +62 -9
  239. package/dist/memory-pass.d.ts +16 -0
  240. package/dist/memory-pass.js +67 -3
  241. package/dist/memory-tidy.d.ts +181 -0
  242. package/dist/memory-tidy.js +523 -0
  243. package/dist/memory-use.d.ts +35 -0
  244. package/dist/memory-use.js +109 -0
  245. package/dist/merge-checklist.d.ts +55 -0
  246. package/dist/merge-checklist.js +182 -0
  247. package/dist/modes.d.ts +6 -6
  248. package/dist/modes.js +3 -1
  249. package/dist/needs-you.d.ts +31 -9
  250. package/dist/needs-you.js +110 -41
  251. package/dist/nightly-github.d.ts +2 -0
  252. package/dist/nightly-github.js +4 -4
  253. package/dist/operate-remote-arguments.js +7 -1
  254. package/dist/operate-remote.js +3 -1
  255. package/dist/operate.d.ts +6 -1
  256. package/dist/operate.js +565 -64
  257. package/dist/phase-routing.js +2 -2
  258. package/dist/plain-schema.d.ts +23 -0
  259. package/dist/plain-schema.js +94 -0
  260. package/dist/plain-words.d.ts +20 -0
  261. package/dist/plain-words.js +66 -0
  262. package/dist/plan-login.d.ts +14 -0
  263. package/dist/plan-login.js +58 -0
  264. package/dist/planner.d.ts +1 -1
  265. package/dist/planner.js +4 -2
  266. package/dist/process-liveness.d.ts +2 -0
  267. package/dist/process-liveness.js +61 -17
  268. package/dist/project-delete-ui.d.ts +19 -2
  269. package/dist/project-delete-ui.js +29 -1
  270. package/dist/project-delete.js +14 -1
  271. package/dist/project-knowledge.d.ts +18 -11
  272. package/dist/project-knowledge.js +39 -9
  273. package/dist/project-learning.d.ts +2 -1
  274. package/dist/project-learning.js +15 -45
  275. package/dist/project-memory.d.ts +5 -0
  276. package/dist/project-memory.js +22 -2
  277. package/dist/project-tools.d.ts +4 -4
  278. package/dist/project-tools.js +23 -7
  279. package/dist/proof.js +30 -5
  280. package/dist/protected-storage-look.d.ts +1 -0
  281. package/dist/protected-storage-look.js +13 -0
  282. package/dist/protected-storage.d.ts +70 -39
  283. package/dist/protected-storage.js +196 -147
  284. package/dist/provider-auth.js +1 -1
  285. package/dist/provider-capture.d.ts +34 -0
  286. package/dist/provider-capture.js +53 -0
  287. package/dist/publish.js +9 -1
  288. package/dist/pull-request-flow.js +5 -0
  289. package/dist/push.d.ts +18 -2
  290. package/dist/push.js +40 -16
  291. package/dist/release-adapters.d.ts +9 -0
  292. package/dist/release-adapters.js +76 -0
  293. package/dist/release-auto.js +13 -13
  294. package/dist/release-card.d.ts +1 -2
  295. package/dist/release-card.js +11 -0
  296. package/dist/release-carry.d.ts +7 -2
  297. package/dist/release-carry.js +49 -10
  298. package/dist/release-cli.d.ts +4 -0
  299. package/dist/release-cli.js +29 -22
  300. package/dist/release-coverage.js +1 -1
  301. package/dist/release-next.d.ts +48 -0
  302. package/dist/release-next.js +161 -0
  303. package/dist/release.d.ts +20 -1
  304. package/dist/release.js +85 -9
  305. package/dist/remote-destination.d.ts +36 -0
  306. package/dist/remote-destination.js +217 -0
  307. package/dist/remote-main.d.ts +48 -0
  308. package/dist/remote-main.js +84 -5
  309. package/dist/repository-context.d.ts +3 -2
  310. package/dist/repository-context.js +112 -70
  311. package/dist/restart-certification.d.ts +1 -0
  312. package/dist/restart-certification.js +8 -6
  313. package/dist/result-acts.d.ts +27 -19
  314. package/dist/result-acts.js +36 -25
  315. package/dist/result-follow-ups.d.ts +2 -2
  316. package/dist/result-follow-ups.js +18 -4
  317. package/dist/result-review.d.ts +1 -1
  318. package/dist/route-change-check.d.ts +4 -0
  319. package/dist/route-change-check.js +13 -0
  320. package/dist/route-change.d.ts +22 -0
  321. package/dist/route-change.js +89 -0
  322. package/dist/route-hooks.js +3 -2
  323. package/dist/route-results.d.ts +6 -1
  324. package/dist/route-results.js +14 -5
  325. package/dist/run-closeout.d.ts +1 -1
  326. package/dist/run-closeout.js +7 -5
  327. package/dist/runner-process.js +3 -2
  328. package/dist/runner.js +2 -1
  329. package/dist/scope.d.ts +28 -4
  330. package/dist/scope.js +92 -28
  331. package/dist/scout.d.ts +1 -1
  332. package/dist/scout.js +2 -1
  333. package/dist/serve.d.ts +1 -1
  334. package/dist/serve.js +26 -18
  335. package/dist/server/chat.js +16 -14
  336. package/dist/server/chrome.d.ts +1 -1
  337. package/dist/server/chrome.js +26 -35
  338. package/dist/server/console.css +73 -0
  339. package/dist/server/guards.js +3 -0
  340. package/dist/server/home.d.ts +6 -0
  341. package/dist/server/home.js +21 -13
  342. package/dist/server/http.js +1 -1
  343. package/dist/server/kit.js +1 -1
  344. package/dist/server/merge-ready-view.d.ts +10 -0
  345. package/dist/server/merge-ready-view.js +30 -0
  346. package/dist/server/pages-assets.d.ts +1 -0
  347. package/dist/server/pages-assets.js +8 -5
  348. package/dist/server/pages.js +103 -82
  349. package/dist/server/render-chat.js +16 -3
  350. package/dist/server/render-code-map.d.ts +30 -0
  351. package/dist/server/render-code-map.js +137 -0
  352. package/dist/server/render-flaky.d.ts +20 -0
  353. package/dist/server/render-flaky.js +59 -0
  354. package/dist/server/render-follow-through.d.ts +11 -0
  355. package/dist/server/render-follow-through.js +68 -0
  356. package/dist/server/render-history.js +1 -1
  357. package/dist/server/render-new-task.d.ts +26 -1
  358. package/dist/server/render-new-task.js +30 -5
  359. package/dist/server/render-pages.d.ts +1 -175
  360. package/dist/server/render-pages.js +38 -313
  361. package/dist/server/render-projects.d.ts +2 -1
  362. package/dist/server/render-projects.js +9 -7
  363. package/dist/server/render-release.d.ts +2 -0
  364. package/dist/server/render-release.js +3 -3
  365. package/dist/server/render-results.d.ts +2 -0
  366. package/dist/server/render-results.js +68 -68
  367. package/dist/server/render-tasks.d.ts +16 -2
  368. package/dist/server/render-tasks.js +177 -141
  369. package/dist/server/retired-routes.d.ts +17 -0
  370. package/dist/server/retired-routes.js +53 -0
  371. package/dist/server/route-table.d.ts +3 -0
  372. package/dist/server/route-table.js +48 -12
  373. package/dist/server/settings.js +166 -34
  374. package/dist/server/task-views.js +66 -26
  375. package/dist/server/tasks.d.ts +2 -6
  376. package/dist/server/tasks.js +250 -321
  377. package/dist/server/welcome.js +1 -1
  378. package/dist/settings-groups-ui.d.ts +1 -0
  379. package/dist/settings-groups-ui.js +4 -2
  380. package/dist/settings-kit.js +3 -2
  381. package/dist/settings-proofs.js +3 -1
  382. package/dist/slack-chat.js +14 -1
  383. package/dist/steer-mailbox.d.ts +8 -1
  384. package/dist/steer-mailbox.js +164 -6
  385. package/dist/stop-proof.js +6 -4
  386. package/dist/storage-sweep-process.js +1 -1
  387. package/dist/storage-sweep.d.ts +5 -3
  388. package/dist/storage-sweep.js +30 -9
  389. package/dist/storage-ui.js +28 -17
  390. package/dist/storage.d.ts +27 -2
  391. package/dist/storage.js +39 -2
  392. package/dist/store.d.ts +89 -7
  393. package/dist/store.js +386 -114
  394. package/dist/surface.js +67 -6
  395. package/dist/sweep-quarantine.d.ts +1 -0
  396. package/dist/sweep-quarantine.js +6 -4
  397. package/dist/tamper-scan.d.ts +4 -0
  398. package/dist/tamper-scan.js +90 -0
  399. package/dist/task-aliases.d.ts +5 -0
  400. package/dist/task-aliases.js +9 -0
  401. package/dist/task-control.d.ts +2 -1
  402. package/dist/task-control.js +4 -1
  403. package/dist/task-outcome-cli.d.ts +2 -0
  404. package/dist/task-outcome-cli.js +17 -17
  405. package/dist/task-sizing.d.ts +5 -0
  406. package/dist/task-sizing.js +2 -0
  407. package/dist/task-source.d.ts +7 -0
  408. package/dist/task-source.js +14 -0
  409. package/dist/task-status.d.ts +9 -0
  410. package/dist/task-status.js +45 -49
  411. package/dist/teardown-git.d.ts +18 -0
  412. package/dist/teardown-git.js +105 -21
  413. package/dist/telegram-lead.js +23 -6
  414. package/dist/telegram-progress.js +5 -3
  415. package/dist/telegram-status.js +6 -4
  416. package/dist/telegram.js +12 -1
  417. package/dist/test-infra.d.ts +8 -0
  418. package/dist/test-infra.js +18 -0
  419. package/dist/tidy.js +1 -1
  420. package/dist/toolroll-update.js +19 -5
  421. package/dist/tui-contract.d.ts +24 -0
  422. package/dist/tui-source.js +67 -8
  423. package/dist/tui.js +51 -51
  424. package/dist/vanished-retry.d.ts +30 -0
  425. package/dist/vanished-retry.js +174 -0
  426. package/dist/work-index.d.ts +2 -0
  427. package/dist/work-index.js +95 -66
  428. package/dist/work-summary.d.ts +1 -1
  429. package/dist/work-summary.js +7 -2
  430. package/dist/workspace-ui.d.ts +7 -3
  431. package/dist/workspace-ui.js +38 -23
  432. package/dist/worktree.d.ts +12 -3
  433. package/dist/worktree.js +91 -14
  434. package/package.json +2 -1
  435. package/dist/server/render-next.d.ts +0 -63
  436. package/dist/server/render-next.js +0 -109
@@ -2,11 +2,15 @@ import { PASSWORDLESS_TRADE, passwordlessSetting, setPasswordless } from "../pas
2
2
  import { ACP_AGENTS, isKeyedProvider } from "../acp-agents.js";
3
3
  import { LIVE_VIEW_CONSENT, liveViewConsentDigest, setLiveCommands } from "../live-detail.js";
4
4
  import { CONSOLE_FORMS, readForm } from "../contracts/console-api.js";
5
+ import { clearFallbackChain, parseEntry, setFallbackChain, setPaidGrant } from "../fallback.js";
5
6
  import { galleryTemplateOf } from "../flow-gallery.js";
6
7
  import { finishGoogleConsent, GOOGLE_CALLBACK } from "../google-mail.js";
7
8
  import { kitInstalled, kitOf } from "../kits.js";
8
9
  import { CONNECT_CALLBACK, connectionsOf, finishConnect, localConnectOf, oneClickOf } from "../mcp-connect.js";
9
10
  import { learningView } from "../project-learning.js";
11
+ import { memoryUseOf } from "../memory-use.js";
12
+ import { afterwardsWords, lessonNoteOf, noteAfterwards } from "../lesson-notes.js";
13
+ import { decideTidy, disabledLessons, listTidy, proposeTidy, restoreDecision, restoreLesson, restoreReference, undoableMerges, unmergeDecisions } from "../memory-tidy.js";
10
14
  import { skillTestResult } from "../project-skills.js";
11
15
  import { discoverTools, projectToolsOf } from "../project-tools.js";
12
16
  import { lookProjectMcp } from "../folder-look.js";
@@ -48,6 +52,7 @@ import { releaseApprovalSetting, setReleaseApproval } from "../release-auto.js";
48
52
  import { backupHtml } from "../backup-ui.js";
49
53
  import { BACKUP_EVERY_HOURS, backupFolderOf, backupNow, checkBackupFolder, defaultBackupFolder, MAX_KEEP } from "../backup.js";
50
54
  import { projectBatchChecks, setProjectBatchChecks } from '../batch-policy.js';
55
+ import { fastStartOn, setFastStart } from '../fast-start.js';
51
56
  import { ALL_PROJECTS as CHAT_APPROVAL_ALL, chatApprovalWords, setChatApproval } from "../chat-approval.js";
52
57
  import { ChatState } from "../chat-delivery-state.js";
53
58
  import { checkSettingsHtml } from '../check-levels-ui.js';
@@ -67,20 +72,20 @@ import { disconnectGoogle, googleConnected, googleConsent, readGoogleMail, saveG
67
72
  import { integrationsHtml } from "../integrations-ui.js";
68
73
  import { setYouiyouxIntake } from "../youiyoux-intake.js";
69
74
  import { clearProviderKey, keyStatus, plausibleKey, PROVIDER_KEY_ENV, readAuthMode, readProviderKey, saveProviderKey, setAuthMode, SUBSCRIPTION_CAPABLE, verdictWords, verifyProviderKey } from "../keys.js";
70
- import { decisionsHtml, framedSection, knowledgeHtml, MEMORY_INTRO, memorySearchHtml, proposalsHtml } from "../knowledge-ui.js";
75
+ import { cleanUpHtml, decisionsHtml, framedSection, knowledgeHtml, MEMORY_INTRO, memorySearchHtml, proposalsHtml, referenceRestoreHtml, restoreHtml } from "../knowledge-ui.js";
71
76
  import { aboutYouOf, checkAboutYou, saveAboutYou } from "../lead-about.js";
72
77
  import { cancelCommitment, conditionWords, openCommitments } from '../lead-commitments.js';
73
78
  import { checkLeadIdentity, leadIdentityOf } from "../lead-identity.js";
74
79
  import { mailboxAccess, readThroughImap } from "../mailbox.js";
75
80
  import { startConnect } from "../mcp-connect.js";
76
- import { decideProposal, listProposals, memoryStatus } from "../memory-pass.js";
81
+ import { decideProposal, defaultTidyAnalyzer, listProposals, memoryStatus } from "../memory-pass.js";
77
82
  import { checkModels, isNewModel, modelOptions, modelWords, RUNTIME_TOOLS, runtimeStates, seenModels, setWatch, updateRuntime, watchState } from "../model-catalog.js";
78
83
  import { modelsHtml, modelsScript } from "../models-ui.js";
79
84
  import { acceptSuggestion, dismissSuggestion, grantWords, keepRule, modelName, ruleWords, rulesOf, setSuggestModels, startTrial, stopTrial, suggestionsFor, suggestionWords, suggestModelsOn, trialOffersFor, trialOfferWords, trialsOf, trialWords, turnOffRule } from "../route-rules.js";
80
85
  import { ALL_SLOTS, applyModelChoices, browserWords, checkScope, DEFAULT_PRESET, defaultAgentOf, fillDefaultRouting, isPresetName, newerModels, PRESETS, presetChange, presetInUse, previewModelChange, recentRuns, rowAt, slotKey, SLOTS, slotWords, stateDigest, switchChange } from "../model-choices.js";
81
86
  import { HEADROOM_FIVE_HOUR_LIMIT, HEADROOM_WEEKLY_LIMIT, exactModelId } from "../phase-routing.js";
82
87
  import { RISKY_MEANS, SIZE_MEANS, SIZING_CLAUDE_MODEL } from "../task-sizing.js";
83
- import { CHAT_APPROVE_ALL, MODE_MAX_DAYS, modeDigestOf, modeTermsJson, modeWords, presetTerms } from "../modes.js";
88
+ import { CHAT_APPROVE_ALL, CHECK_REPAIR_LIMIT, MODE_MAX_DAYS, modeDigestOf, modeTermsJson, modeWords, presetTerms } from "../modes.js";
84
89
  import { monitoringChange, readMonitoring, saveMonitoring } from "../monitoring-settings.js";
85
90
  import { monitoringHtml, signingSecretHtml } from "../monitoring-ui.js";
86
91
  import { openRouterPickerScript } from "../openrouter-models.js";
@@ -93,6 +98,7 @@ import { isCheckAccess, signInsUnavailableWords, widerThan } from "../check-acce
93
98
  import { LOCAL_MODE_CONFINES, localModeOn } from "../local-owner.js";
94
99
  import { parseMachLookup, parseReadDeny } from "../project-sandbox.js";
95
100
  import { projectDeleteConfirmHtml, projectSettingsHtml } from "../project-delete-ui.js";
101
+ import { endHoldout, setCodeMapSwitch } from "../code-map-acts.js";
96
102
  import { deleteProject, holdingsWords } from "../project-delete.js";
97
103
  import { applySavedKnowledge, changeKnowledge, knowledgeVersion, knowledgeView } from "../project-knowledge.js";
98
104
  import { changeLearning } from "../project-learning.js";
@@ -128,8 +134,8 @@ import { lastSweep, saveSweep, storageSweepOff } from "../storage-sweep.js";
128
134
  import { sweepSchedule } from "../storage-sweep-schedule.js";
129
135
  import { storageHtml } from "../storage-ui.js";
130
136
  import { landedSummary } from "../landed-cleanup.js";
131
- import { bytesWords, parseCleanup } from "../storage.js";
132
- import { isDigestTime, liveBackupState, localModeOf, RESULT_SCREENSHOTS, setLiveBackupSettings, setLocalModeOff } from "../store.js";
137
+ import { bytesWords, parseCleanup, parseIdle } from "../storage.js";
138
+ import { checkoutIdle, isDigestTime, liveBackupState, setCheckoutIdle, localModeOf, RESULT_SCREENSHOTS, setLiveBackupSettings, setLocalModeOff } from "../store.js";
133
139
  import { defaultLiveFolder, liveFolderOf, liveFolderProblem } from "../live-backup.js";
134
140
  import { LIVE_EVERY } from "../backup-ui.js";
135
141
  import { checkTeamsCredentials, clearTeamsCredentials, loadTeamsCredentials, saveTeamsCredentials, TeamsError } from "../teams-api.js";
@@ -353,9 +359,34 @@ ${connectionHtml(asked, connection, task)}${command === null ? "" : html `<p>On
353
359
  return refuse(response, who, 403, "That project is outside your access.", "/settings/project");
354
360
  const selector = projectPicker("/settings/project", projects.map(repo => ({ repo, name: projectName(repo) })), chosen);
355
361
  const notice = { said: url.searchParams.get("said"), problem: url.searchParams.get("problem") };
356
- const content = chosen === "" ? html `${notice.said ? infoLine(notice.said) : ""}${empty({ title: "No projects yet.", action: btn("Add a project", { sm: true, href: "/projects" }) })}` : (projectSettingsHtml({ ...projectViewOf(chosen, who), remoteUrl: store.projectRemote(chosen) }, notice));
362
+ const content = chosen === "" ? html `${notice.said ? infoLine(notice.said) : ""}${empty({ title: "No projects yet.", action: btn("Add a project", { sm: true, href: "/projects" }) })}` : (projectSettingsHtml({ ...projectViewOf(chosen, who), remoteUrl: store.projectRemote(chosen), codeMap: codeMapView(chosen, who, ctx.now) }, notice));
357
363
  return sendScreen(response, 200, screen("Project", settingsSubPage("projects", "Project", html `${selector}${content}`), { chrome: chromeFor(chosen || null, "settings") }));
358
364
  }
365
+ /** The project's code map section: the switch, and the holdout while new builds are still being enrolled. */
366
+ function codeMapView(repo, who, now) {
367
+ const row = store.handle.prepare("SELECT code_map, code_map_enroll_until FROM project WHERE path = ?").get(repo);
368
+ const until = typeof row?.["code_map_enroll_until"] === "string" ? row["code_map_enroll_until"] : null;
369
+ return { on: row === undefined || Number(row["code_map"]) === 1, enrollUntil: until !== null && Date.parse(until) > now.getTime() ? until : null,
370
+ canEndHoldout: who.via === "cookie" && who.authority !== "local-owner", href: `/projects/${encodeURIComponent(projectName(repo))}/map` };
371
+ }
372
+ function codeMapAct(kind) {
373
+ return async (ctx) => {
374
+ const { response, now, posted } = ctx;
375
+ const who = browserCaller(ctx.who);
376
+ const repo = posted.get("repo") ?? "";
377
+ const back = (key, words) => redirect(response, `/settings/project?repo=${encodeURIComponent(repo)}&${key}=${encodeURIComponent(words)}#code-map`);
378
+ if (posted.getAll("repo").length !== 1 || posted.getAll("on").length > 1)
379
+ return back("problem", "That form was sent twice over. Nothing changed.");
380
+ if (!consoleProjects().includes(repo) || !store.accountCanAccess(who.name, repo))
381
+ return refuse(response, who, 403, "That project is outside your access.", "/settings/project");
382
+ const door = who.authority === "local-owner" ? { kind: "local-owner", actor: who.name } : { kind: "person", person: modelsAuthority(who, "session") };
383
+ const on = posted.get("on") === "1";
384
+ const done = kind === "switch" ? setCodeMapSwitch(store, door, repo, on, now) : endHoldout(store, door, repo, now);
385
+ if (!done.ok)
386
+ return back("problem", done.problem);
387
+ return back("said", kind === "holdout" ? "The holdout has ended. Builds already enrolled keep their arm." : on ? "The code map is on." : "The code map is off.");
388
+ };
389
+ }
359
390
  // v102: Settings → Approvals and safety: the defaults for new tasks, a project's separation of duties and release
360
391
  // approval, and the way to its operating mode. Anyone who sees the project reads them; each form's own route decides
361
392
  // who may change it (an approver for the defaults and releases, an instance operator for the rules).
@@ -467,9 +498,9 @@ ${connectionHtml(asked, connection, task)}${command === null ? "" : html `<p>On
467
498
  const measured = lastChecks.known("storage");
468
499
  const check = postForm("/settings/storage/check", btn("Check now", { sm: true, type: "submit" }));
469
500
  if (measured === null) {
470
- return sendScreen(response, 200, screen("Storage", settingsSubPage("system", "Storage", settingsSection({ id: "storage", title: "Checkouts not measured yet", lead: "Toolroll measures the checkouts in the background. Check now measures them at once.", body: check })), { chrome: chromeFor(project, "settings") }));
501
+ return sendScreen(response, 200, screen("Storage", settingsSubPage("system", "Storage", settingsSection({ id: "storage", title: "Task folders not measured yet", lead: "Toolroll measures the task folders in the background. Check now measures them at once.", body: check })), { chrome: chromeFor(project, "settings") }));
471
502
  }
472
- const plan = { ...measured.value, cleanup: store.checkoutCleanup() };
503
+ const plan = { ...measured.value, cleanup: store.checkoutCleanup(), idle: checkoutIdle(store) };
473
504
  const sweep = { last: lastSweep(store), off: storageSweepOff(), schedule: sweepSchedule(store, now) };
474
505
  return sendScreen(response, 200, screen("Storage", settingsSubPage("system", "Storage", html `${(storageHtml({ plan, csrf: who.session.csrf, sweep, landed: landedSummary(store), measured: html `<div class="k-acts"><span class="k-note">Measured ${new Date(measured.at).toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit" })}</span>${check}</div>` }, { said: url.searchParams.get("said"), problem: url.searchParams.get("problem") }))}`), { chrome: chromeFor(project, "settings") }));
475
506
  }
@@ -499,7 +530,7 @@ ${connectionHtml(asked, connection, task)}${command === null ? "" : html `<p>On
499
530
  const quick = liveQuickCommand(store, repo);
500
531
  const body = (checkSettingsHtml({ repo, name: projectName(repo), csrf: who.session.csrf, canChange: who.role === "approver" && !store.isDemo(),
501
532
  level: projectCheckLevel(store, repo).level, full: store.liveVerifyCommand(repo), quick, suggestion: quick === null ? suggestQuickCommand(repo) : null,
502
- review: (({ on, source }) => ({ on, source }))(store.reviewSwitch(repo, clock())), batch: { on: projectBatchChecks(store, repo).on },
533
+ review: (({ on, source }) => ({ on, source }))(store.reviewSwitch(repo, clock())), batch: { on: projectBatchChecks(store, repo).on }, fast: { on: fastStartOn(store, repo) },
503
534
  said: url.searchParams.get("said"), problem: url.searchParams.get("problem") }));
504
535
  return sendScreen(response, 200, screen("Checks", settingsSubPage("projects", "Checks", html `${body}`), { chrome: chromeFor(project, "projects") }));
505
536
  }
@@ -577,21 +608,25 @@ ${connectionHtml(asked, connection, task)}${command === null ? "" : html `<p>On
577
608
  const revision = Number(url.searchParams.get('version'));
578
609
  const previous = knowledgeVersion(store, chosen, who.name, revision);
579
610
  const restore = csrf && who.role === 'approver' && revision !== view.revision ? postForm('/settings/knowledge/change', html `<p>Replaces knowledge for future tasks. Existing runs keep their saved context.</p><button>Restore version ${revision}</button>`, { attrs: { class: 'knowledge' }, hidden: { repo: chosen, identity: view.identity, revision: String(view.revision), action: 'restore', restore: String(revision) } }) : '';
580
- return sendScreen(response, 200, screen('Knowledge history', settingsSubPage('agents', `Knowledge · version ${revision}`, html `<p class="k-note"><a class="k-link" href="/settings/knowledge?repo=${encodeURIComponent(chosen)}">Current knowledge</a></p>${knowledgeHtml({ ...view, knowledge: previous, history: [], stale: null }, '', false)}${restore}`), { chrome: chromeFor(chosen, 'settings') }));
611
+ return sendScreen(response, 200, screen('Knowledge history', settingsSubPage('agents', `Knowledge · version ${revision}`, html `<p class="k-note"><a class="k-link" href="/settings/knowledge?repo=${encodeURIComponent(chosen)}">Current knowledge</a></p>${knowledgeHtml({ ...view, knowledge: previous, history: [], stale: null }, '', false)}${restore}${referenceRestoreHtml(chosen, revision, previous.references.filter(ref => !view.knowledge.references.some(now => now.id === ref.id)), !!csrf && who.role === 'approver')}`), { chrome: chromeFor(chosen, 'settings') }));
581
612
  }
582
613
  const query = (url.searchParams.get('q') ?? '').slice(0, 1000);
583
- const result = query.trim() ? repositoryContextRead({ repo: chosen, query, mode: url.searchParams.get('mode') === 'impact' ? 'impact' : 'search', cacheRoot: join(dirname(evidenceRoot), 'repository-context') }) : null;
614
+ const result = query.trim() ? repositoryContextRead({ repo: chosen, project: chosen, query, mode: url.searchParams.get('mode') === 'impact' ? 'impact' : 'search', cacheRoot: join(dirname(evidenceRoot), 'repository-context') }) : null;
584
615
  const memoryQuery = (url.searchParams.get('memory') ?? '').slice(0, 300);
585
616
  const hits = memoryQuery.trim() ? searchMemory(store, { actor: who.name, repos: [chosen], query: memoryQuery, limit: 20 }) : null;
586
617
  let proposals = html ``;
587
618
  try {
588
- proposals = (proposalsHtml(chosen, listProposals(store, chosen), memoryStatus(store, chosen, who.name), csrf, who.role === 'approver'));
619
+ proposals = (proposalsHtml(chosen, listProposals(store, chosen), memoryStatus(store, chosen, who.name), csrf, who.role === 'approver', who.name));
589
620
  }
590
621
  catch {
591
622
  proposals = html ``;
592
623
  }
593
- content = html `<p class="meta">${MEMORY_INTRO}</p>${memorySearchHtml(chosen, memoryQuery, hits)}${proposals}${(decisionsHtml(chosen, listDecisions(store, chosen, who.name, { limit: 50 }), csrf, who.role === 'approver'))}\
594
- ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf : '')}${knowledgeHtml(view, csrf, who.role === 'approver')}\
624
+ const use = memoryUseOf(store, chosen);
625
+ const manageMemory = who.role === 'approver' && csrf !== '';
626
+ const retired = listDecisions(store, chosen, who.name, { status: 'all', limit: 100 }).filter(d => d.status !== 'active');
627
+ content = html `<p class="meta">${MEMORY_INTRO}</p>${memorySearchHtml(chosen, memoryQuery, hits)}${proposals}${cleanUpHtml(chosen, listTidy(store, chosen), manageMemory)}${(decisionsHtml(chosen, listDecisions(store, chosen, who.name, { limit: 50 }), csrf, who.role === 'approver', undefined, undefined, use, d => { const after = noteAfterwards(store, d); return after === null ? null : afterwardsWords(after); }))}\
628
+ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf : '')}${knowledgeHtml(view, csrf, who.role === 'approver', undefined, undefined, use)}\
629
+ ${restoreHtml(chosen, retired, undoableMerges(store, chosen), disabledLessons(store, chosen), manageMemory)}\
595
630
  <details class="knowledge frame"><summary class="frame-head">Learned lessons</summary><div class="frame-panel">${(learningHtml(learningView(store, evidenceRoot, chosen, who.name), csrf, who.role === 'approver'))}</div></details>`;
596
631
  }
597
632
  catch {
@@ -803,11 +838,11 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
803
838
  const { url, who, response } = ctx;
804
839
  const provider = url.pathname.split("/")[2];
805
840
  if (!isProviderId(provider))
806
- return refuse(response, who, 400, "unknown provider", "/work");
841
+ return refuse(response, who, 400, "unknown provider", "/tasks");
807
842
  if (who.role !== "approver")
808
- return refuse(response, who, 403, "Only an operator can resume an agent's work.", "/work");
843
+ return refuse(response, who, 403, "Only an operator can resume an agent's work.", "/tasks");
809
844
  liftAuthPause(store, provider, "person", who.name, clock());
810
- return redirect(response, "/work");
845
+ return redirect(response, "/tasks");
811
846
  }
812
847
  // The update notice is dismissed per browser and per version: a newer release shows it again.
813
848
  // Cockpit B2: Forget — nothing verified on this session before now counts as fresh (a CSRF-checked post, like every act).
@@ -1235,16 +1270,20 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
1235
1270
  // What the page shows next is measured again after the change.
1236
1271
  const said = sayTo(response, "/settings/storage");
1237
1272
  const back = async (kind, words) => { await measureStorage(databaseFile).catch(() => undefined); return said(kind, words); };
1238
- const what = op === "clean" ? "clean up" : op === "discard" ? "discard a checkout" : "change checkout cleanup";
1273
+ const what = op === "clean" ? "clean up" : op === "discard" ? "discard a task folder" : "change automatic cleanup";
1239
1274
  if (!authenticateApprover(who, body.get("password") ?? "").ok)
1240
1275
  return back("problem", `Enter your Toolroll password to ${what}.`);
1241
1276
  if (op === "cleanup") {
1242
1277
  const cleanup = parseCleanup(body.get("cleanup") ?? "");
1243
1278
  if (cleanup === undefined)
1244
- return back("problem", "Choose when a finished task's checkout is removed.");
1245
- const before = store.checkoutCleanup();
1279
+ return back("problem", "Choose when a finished task's folder is removed.");
1280
+ const idle = body.get("idle") === null ? checkoutIdle(store) : parseIdle(body.get("idle") ?? "");
1281
+ if (idle === undefined)
1282
+ return back("problem", "Choose when an idle task's folder is removed.");
1283
+ const same = cleanup === store.checkoutCleanup() && idle === checkoutIdle(store);
1246
1284
  store.setCheckoutCleanup(cleanup, who.name, now);
1247
- return back("said", cleanup === before ? "Nothing changed." : "Saved.");
1285
+ setCheckoutIdle(store, idle, who.name, now);
1286
+ return back("said", same ? "Nothing changed." : "Saved.");
1248
1287
  }
1249
1288
  const pool = storagePool(databaseFile);
1250
1289
  if (op === "clean") {
@@ -1256,7 +1295,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
1256
1295
  return back("problem", "What a clean-up would remove changed since you looked. Check the list again.");
1257
1296
  const done = await cleanCheckouts(store, pool, clock, { manual: true, actor: who.name, only: new Set(plan.go.map(one => one.path)) });
1258
1297
  saveSweep(store, { at: clock().toISOString(), source: "manual", actor: who.name, parts: [{ kind: "checkouts", count: done.removed.length, bytes: done.freed, failed: done.kept.filter(one => one.why === "git refused").length, items: done.removed.slice(0, 20).map(one => one.path) }] });
1259
- return back("said", done.removed.length === 0 ? "Nothing was removed: those checkouts are no longer ready to go." : `Removed ${done.removed.length} checkout${done.removed.length === 1 ? "" : "s"}, about ${bytesWords(done.freed)}. Their branches stay.`);
1298
+ return back("said", done.removed.length === 0 ? "Nothing was removed: those task folders are no longer ready to go." : `Removed ${done.removed.length} task folder${done.removed.length === 1 ? "" : "s"}, about ${bytesWords(done.freed)}. Their branches stay.`);
1260
1299
  }
1261
1300
  const done = await discardCheckout(store, pool, body.get("path") ?? "", now, who.name);
1262
1301
  return done.ok ? back("said", `Discarded, about ${bytesWords(done.bytes)}. Its branch stays.`) : back("problem", done.message);
@@ -1331,6 +1370,13 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
1331
1370
  const changed = setProjectBatchChecks(store, repo, on, who.name, now);
1332
1371
  return back("said", !changed.changed ? "Saved." : on ? "Batch checks are on." : "Batch checks are off.");
1333
1372
  }
1373
+ // Small tasks start right away: a person's own logged settings act, behind this form's password.
1374
+ if (act === "small-tasks") {
1375
+ const on = body.get("on") === "1";
1376
+ store.upsertProject(repo, projectName(repo), now);
1377
+ setFastStart(store, repo, on, who.name, now);
1378
+ return back("said", on ? "Small tasks start right away." : "Small tasks wait for a plan.");
1379
+ }
1334
1380
  if (act === "level") {
1335
1381
  const level = body.get("level");
1336
1382
  if (!isCheckLevel(level))
@@ -1736,7 +1782,7 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
1736
1782
  const repo = body.get('repo') ?? '';
1737
1783
  if (!visible(repo) || !store.accountCanAccess(who.name, repo) || !consoleProjects().includes(repo))
1738
1784
  return refuse(response, who, 403, 'That project is outside your access.', '/settings/knowledge');
1739
- const refreshed = await repositoryContext({ repo, query: '', refresh: true, cacheRoot: join(dirname(evidenceRoot), 'repository-context') });
1785
+ const refreshed = await repositoryContext({ repo, project: repo, query: '', refresh: true, cacheRoot: join(dirname(evidenceRoot), 'repository-context') });
1740
1786
  return sendScreen(response, refreshed.index.status === 'ready' ? 200 : 503, screen('Project context', settingsSubPage("agents", "Project context", html `<p>${refreshed.index.status === 'ready' ? 'Code index refreshed.' : 'The index is unavailable. Source search still works.'}</p><a class="button-link" href="/settings/knowledge?repo=${encodeURIComponent(repo)}">Open knowledge</a>`), { chrome: chromeFor(repo, 'settings') }));
1741
1787
  }
1742
1788
  async function knowledgeProposal(ctx) {
@@ -1755,6 +1801,64 @@ ${repositoryContextHtml(chosen, query, result, who.role === 'approver' ? csrf :
1755
1801
  }
1756
1802
  return redirect(response, `/settings/knowledge?repo=${encodeURIComponent(repo)}&saved=1`);
1757
1803
  }
1804
+ /** A lesson note: a person's own words, accepted as a decision through the one proposal writer, or rejected. */
1805
+ async function knowledgeNote(ctx) {
1806
+ const { who, response, now, posted } = ctx;
1807
+ const body = readForm(posted, CONSOLE_FORMS.knowledgeNote);
1808
+ const repo = body.get('repo') ?? '', decision = body.get('decision') ?? '';
1809
+ if (!visible(repo) || !consoleProjects().includes(repo))
1810
+ return refuse(response, who, 403, 'That project is outside your access.', '/settings/knowledge');
1811
+ if (!['accept', 'reject'].includes(decision) || !/^[0-9]{1,12}$/.test(body.get('proposal') ?? ''))
1812
+ return refuse(response, who, 400, 'Choose accept or reject for one note.', '/settings/knowledge');
1813
+ const id = Number(body.get('proposal'));
1814
+ try {
1815
+ if (lessonNoteOf(store, id) === null)
1816
+ throw Error('That note is unavailable.');
1817
+ decideProposal(store, { repo, actor: who.name, id, decision: decision, claim: body.get('claim') ?? '' }, now);
1818
+ }
1819
+ catch (error) {
1820
+ return sendScreen(response, 409, screen('Knowledge', settingsSubPage("agents", "Knowledge", html `<p role="alert">${error instanceof Error ? error.message : 'That note could not be decided.'}</p><p><a class="k-link" href="/settings/knowledge?repo=${encodeURIComponent(repo)}">Back to Knowledge</a></p>`), { chrome: chromeFor(repo, 'settings') }));
1821
+ }
1822
+ return redirect(response, `/settings/knowledge?repo=${encodeURIComponent(repo)}&saved=1`);
1823
+ }
1824
+ /** Project memory's clean-up: a person's request, accepting or rejecting one proposal, and restores. Every writer
1825
+ * proves the person inside its own transaction. */
1826
+ async function knowledgeTidy(ctx) {
1827
+ const { who, response, now, posted } = ctx;
1828
+ const body = readForm(posted, CONSOLE_FORMS.knowledgeTidy);
1829
+ const repo = body.get('repo') ?? '', action = body.get('action') ?? '', target = body.get('target') ?? '';
1830
+ if (!visible(repo) || !consoleProjects().includes(repo))
1831
+ return refuse(response, who, 403, 'That project is outside your access.', '/settings/knowledge');
1832
+ const back = `/settings/knowledge?repo=${encodeURIComponent(repo)}`;
1833
+ const said = (id) => /^[0-9]{1,12}$/.test(id) ? Number(id) : null;
1834
+ // Only a browser session is a person's proof here; the writers refuse anything else.
1835
+ const proof = who.via === 'cookie' ? 'session' : 'remote-token';
1836
+ try {
1837
+ if (action === 'propose')
1838
+ await proposeTidy(store, { repo, actor: who.name, analyzer: defaultTidyAnalyzer(store, store.databaseFile() === null ? {} : { configDir: dirname(store.databaseFile()) }) }, now);
1839
+ else if (action === 'accept' || action === 'reject') {
1840
+ const id = said(body.get('proposal') ?? '');
1841
+ if (id === null)
1842
+ throw Error('Choose one proposal.');
1843
+ const choice = body.get('choice'), claim = body.get('claim'), why = body.get('why');
1844
+ decideTidy(store, evidenceRoot, { repo, actor: who.name, id, decision: action, proof, ...(choice === null ? {} : { choice }), ...(claim === null ? {} : { claim }), ...(why === null ? {} : { why }) }, now);
1845
+ }
1846
+ else if (action === 'restore' && said(target) !== null)
1847
+ restoreDecision(store, { repo, actor: who.name, id: said(target), proof }, now);
1848
+ else if (action === 'unmerge' && said(target) !== null)
1849
+ unmergeDecisions(store, { repo, actor: who.name, merge: said(target), proof }, now);
1850
+ else if (action === 'readopt' && said(target) !== null)
1851
+ restoreLesson(store, evidenceRoot, { repo, actor: who.name, id: said(target), proof }, now);
1852
+ else if (action === 'reference' && /^[a-f0-9]{20}$/.test(target) && said(body.get('revision') ?? '') !== null)
1853
+ restoreReference(store, { repo, actor: who.name, id: target, fromRevision: said(body.get('revision') ?? ''), proof }, now);
1854
+ else
1855
+ return refuse(response, who, 400, 'Choose one clean-up action.', back);
1856
+ }
1857
+ catch (error) {
1858
+ return sendScreen(response, 409, screen('Knowledge', settingsSubPage("agents", "Knowledge", html `<p role="alert">${error instanceof Error ? error.message : 'That could not be done.'}</p><p><a class="k-link" href="${back}">Back to Knowledge</a></p>`), { chrome: chromeFor(repo, 'settings') }));
1859
+ }
1860
+ return redirect(response, `${back}&saved=1`);
1861
+ }
1758
1862
  async function knowledgeDecision(ctx) {
1759
1863
  const { response, now, posted } = ctx;
1760
1864
  const who = browserCaller(ctx.who);
@@ -2358,19 +2462,17 @@ ${postForm("/settings/chat-approval/save", html `\
2358
2462
  planAuto: body.get("plan-auto") === "1",
2359
2463
  reviewAuto: false,
2360
2464
  reviewRetryAuto: false,
2361
- // The repair-auto grant is the SAME rule (v40): unchecked stays
2362
- // false on every preset — only the explicit box grants it, and the
2363
- // attempt cap it carries is meaningless without it.
2364
- repairAuto: false,
2365
- repairMaxAttempts: 0,
2465
+ // Repairing failed checks: only the explicit choice grants it, with the limit it signs (check-repair.ts).
2466
+ repairAuto: body.get("repair-auto") === "1",
2467
+ repairMaxAttempts: body.get("repair-auto") === "1" ? CHECK_REPAIR_LIMIT : 0,
2366
2468
  // Approving from the paired chat: only the explicit choice grants it, naming every chat app it covers (a grant
2367
2469
  // signed before it named them stays Telegram only).
2368
2470
  chatApprove: body.get("chat-approve") === "1",
2369
2471
  ...(body.get("chat-approve") === "1" ? { chatApproveChats: CHAT_APPROVE_ALL } : {}),
2370
2472
  publication: body.get("publication") === "automerge" ? "automerge" : "notify",
2371
2473
  };
2372
- if (["review-auto", "review-retry-auto", "repair-auto"].some(field => body.get(field) === "1")) {
2373
- return refuse(response, who, 400, "Review scheduling and automatic revisions are no longer available. Reload the mode form.", "/mode");
2474
+ if (["review-auto", "review-retry-auto"].some(field => body.get(field) === "1")) {
2475
+ return refuse(response, who, 400, "Review scheduling is no longer available. Reload the mode form.", "/mode");
2374
2476
  }
2375
2477
  if (terms.planAuto && !terms.autoApproveFiling) {
2376
2478
  return refuse(response, who, 400, "Automatic planner approval requires automatic filing approval.", "/mode");
@@ -2383,9 +2485,9 @@ ${postForm("/settings/chat-approval/save", html `\
2383
2485
  // THE CEREMONY (M1): every resolved term in words, and the password
2384
2486
  // signs exactly this digest — a drifted form refuses at /mode/sign.
2385
2487
  const nonce = mintApprovalNonce(who.name, "mode-sign", `${project}:${digest}`);
2386
- const signed = Object.fromEntries(["name", "days", "publication", "auto-approve", "plan-auto", "chat-approve"]
2488
+ const signed = Object.fromEntries(["name", "days", "publication", "auto-approve", "plan-auto", "chat-approve", "repair-auto"]
2387
2489
  .map(field => [field, body.get(field) ?? ""]));
2388
- const bodyHtml = modeCeremonyHtml({ name, project: projectName(project), words: modeWords(terms), chatApprove: signed["chat-approve"] === "1", hidden: { nonce, digest, ...signed, expiry } });
2490
+ const bodyHtml = modeCeremonyHtml({ name, project: projectName(project), words: modeWords(terms), chatApprove: signed["chat-approve"] === "1", repairAuto: signed["repair-auto"] === "1", hidden: { nonce, digest, ...signed, expiry } });
2389
2491
  return sendScreen(response, 200, screen("Sign the mode", bodyHtml, { chrome: chromeFor(project, "mode") }));
2390
2492
  }
2391
2493
  // /mode/sign: the fixed expiry rides the form; the digest is
@@ -2401,8 +2503,8 @@ ${postForm("/settings/chat-approval/save", html `\
2401
2503
  return refuse(response, who, 409, "the terms moved while you were reading — read them again", "/mode");
2402
2504
  }
2403
2505
  const token = body.get("token") ?? "";
2404
- // A mode that lets a chat app approve opens a way to act: a credential act. Any other mode is an ordinary one.
2405
- if (!(body.get("chat-approve") === "1" ? authenticateCredential(who, token) : authenticateApprover(who, token)).ok) {
2506
+ // A mode that lets a chat app approve, or repairs start by themselves, opens a way to act: a credential act. Any other mode is an ordinary one.
2507
+ if (!(body.get("chat-approve") === "1" || body.get("repair-auto") === "1" ? authenticateCredential(who, token) : authenticateApprover(who, token)).ok) {
2406
2508
  return refuse(response, who, 403, "signing a mode takes your password, typed again", "/mode");
2407
2509
  }
2408
2510
  store.signMode({
@@ -2711,6 +2813,7 @@ ${postForm("/settings/chat-approval/save", html `\
2711
2813
  "settings.project-concurrency": projectConcurrency,
2712
2814
  "settings.project-live-view": projectLiveView,
2713
2815
  "settings.project-network": projectNetwork,
2816
+ "settings.project-fallback": projectFallback,
2714
2817
  "settings.project-writes": projectWrites,
2715
2818
  "settings.project-check-access": projectCheckAccess,
2716
2819
  "settings.project-sandbox": projectSandbox,
@@ -2754,6 +2857,8 @@ ${postForm("/settings/chat-approval/save", html `\
2754
2857
  "settings.models-stop": modelsRuleAct("stop"),
2755
2858
  "settings.knowledge-refresh": knowledgeRefresh,
2756
2859
  "settings.knowledge-proposal": knowledgeProposal,
2860
+ "settings.knowledge-note": knowledgeNote,
2861
+ "settings.knowledge-tidy": knowledgeTidy,
2757
2862
  "settings.knowledge-decision": knowledgeDecision,
2758
2863
  "settings.knowledge-change": knowledgeChange,
2759
2864
  "settings.learning-change": learningChange,
@@ -2803,6 +2908,8 @@ ${postForm("/settings/chat-approval/save", html `\
2803
2908
  "settings.lead-about": leadAbout,
2804
2909
  "settings.lead-promise-cancel": leadPromiseCancel,
2805
2910
  "settings.lead-on": leadOn,
2911
+ "settings.code-map": codeMapAct("switch"),
2912
+ "settings.code-map-holdout": codeMapAct("holdout"),
2806
2913
  }, {
2807
2914
  "edge.google-callback": googleCallback,
2808
2915
  "edge.connect-callback": connectCallback,
@@ -3091,5 +3198,30 @@ ${postForm("/settings/chat-approval/save", html `\
3091
3198
  : kind === "suggest-on" ? "Suggestions are on for this project." : "Suggestions are off for this project.");
3092
3199
  };
3093
3200
  }
3201
+ // Settings → Project → Fallback agents (fallback.ts): who takes over when an agent runs out of its plan. An approver for
3202
+ // the project, with their password typed now; the ledger keeps every change. New tasks seal it; approved ones keep theirs.
3203
+ async function projectFallback(ctx) {
3204
+ const { who, response, now, posted } = ctx;
3205
+ const body = readForm(posted, CONSOLE_FORMS.projectFallback);
3206
+ const repo = body.get("repo") ?? "";
3207
+ if (!consoleProjects().includes(repo) || !visible(repo))
3208
+ return refuse(response, who, 403, "That project is outside your access.", "/settings/project");
3209
+ if (who.via !== "cookie" || who.role !== "approver" || store.isDemo() || !store.accountCanAccess(who.name, repo))
3210
+ return refuse(response, who, 403, "An approver for this project changes its fallback agents.", "/settings/project");
3211
+ const back = sayTo(response, `/settings/project?repo=${encodeURIComponent(repo)}`, "#fallback");
3212
+ const parsed = (body.get("agents") ?? "").split(/[\n,]+/).map(one => one.trim()).filter(Boolean).map(parseEntry);
3213
+ const bad = parsed.find((one) => typeof one === "string");
3214
+ if (bad !== undefined)
3215
+ return back("problem", `Nothing changed: ${bad}.`);
3216
+ if (!authenticateCredential(who, body.get("password") ?? "").ok)
3217
+ return back("problem", "Enter your Toolroll password to change fallback agents.");
3218
+ const done = store.transact(() => {
3219
+ const set = parsed.length === 0 ? clearFallbackChain(store, repo, who.name, now) : setFallbackChain(store, repo, parsed, who.name, now);
3220
+ if (!set.ok || parsed.length === 0)
3221
+ return set;
3222
+ return setPaidGrant(store, repo, body.get("paid") === "on", who.name, now);
3223
+ });
3224
+ return done.ok ? back("said", "Saved. Tasks scoped from now on use it; tasks already approved keep the chain they were approved with.") : back("problem", `Nothing changed: ${done.problem}`);
3225
+ }
3094
3226
  return { registrations, modelsView };
3095
3227
  }
@@ -1,3 +1,6 @@
1
+ import { heldFindingCount } from "../findings.js";
2
+ import { familyInWebReach } from "../findings.js";
3
+ import { existsSync } from "node:fs";
1
4
  import { resultRecordWordsOf } from "../route-results.js";
2
5
  import { selectionEvidenceWords } from "../route-rules.js";
3
6
  import { feedbackImageProblem } from "../result-actions.js";
@@ -7,8 +10,10 @@ import { RELEASE_STEPS } from "../release.js";
7
10
  import { releaseProgressFor, shipChecksForTask } from "../store.js";
8
11
  import { releaseCiRunningOf } from "../release-ci-hold.js";
9
12
  import { releaseObligationsOf } from "../release-recheck.js";
10
- import { approvalProofOf, freshlyApprovable, freshnessFor, localApprovalEligible, withFreshness } from "../scope.js";
11
- import { localOwnerRefusal } from "../local-owner.js";
13
+ import { approvalProofOf, freshnessFor, withFreshness } from "../scope.js";
14
+ import { approvalPasswordField } from "../approval-password.js";
15
+ import { fastChangeProblem, fastStarted } from "../fast-start.js";
16
+ import { routeChangeOf } from "../route-change.js";
12
17
  import { liveAttemptOf, steerControlsFor } from "../steer-controls.js";
13
18
  import { originOf } from "../task-source.js";
14
19
  import { recentGitReads, taskBaseOf } from "../task-base.js";
@@ -25,6 +30,7 @@ import { readSkillsSnapshot, skillTestResult } from "../project-skills.js";
25
30
  import { newestPullRequestOf, publishingOf, pullRequestBlocker } from '../pull-request-flow.js';
26
31
  import { structuredHandoffView, terminalDiffView } from "../result-evidence-readers.js";
27
32
  import { resultCheckLevel } from '../result-follow-ups.js';
33
+ import { followThroughView } from "./render-follow-through.js";
28
34
  import { skillsSnapshotHtml, skillTestFeedbackHtml } from "../skills-ui.js";
29
35
  import { archiveOf } from "../store.js";
30
36
  import { runActivityOf } from "../task-activity.js";
@@ -55,6 +61,8 @@ import {} from "./render-pages.js";
55
61
  import { completionReceiptView, diffFileAnchor, evidenceLinksFor, followUpsFor, orderChangedFiles, parseReviewDiff, proofBundleView, receiptStatusOf, withinSignedTouches } from "./render-results.js";
56
62
  import { approvalFormDigest, consentDoorOf, nonceHashOf, publicationFactsOf, resumeCeremonyPage, resumeDigestOf, taskBody, taskPage } from "./render-tasks.js";
57
63
  import {} from "./session.js";
64
+ import { mergeReadyViewOf } from "./merge-ready-view.js";
65
+ import { machineChecked, machineCheckWords } from "../machine-check.js";
58
66
  import { RESULT_REVIEW_SCRIPT } from "../result-review.js";
59
67
  import { reviewEvidenceScript } from "./render-results.js";
60
68
  import { budgetHoldWords, budgetLabel, monthOf } from "../spend.js";
@@ -138,6 +146,7 @@ export function createTaskViews(runtime) {
138
146
  const live = runs.find(one => runIsLive(one)) ?? null;
139
147
  const facts = {
140
148
  id: task.id,
149
+ heldFindings: heldFindingCount(runtime.store, task.id),
141
150
  title: task.title,
142
151
  repo: task.repo,
143
152
  state: task.state,
@@ -162,6 +171,7 @@ export function createTaskViews(runtime) {
162
171
  },
163
172
  publication: latest === null ? null : publicationFactsOf(runtime.store.publicationForRun(latest.id)),
164
173
  liveRunId: live === null ? null : live.id,
174
+ ...(machineChecked(runtime.store, task.id) ? { machine: true } : {}),
165
175
  openDecision: ref === null ? null : openWorkDecisionOf(runtime.store, ref.id, now),
166
176
  unfinishedRunId: runs.find(one => one.outcome === null && one.role !== "reviewer")?.id ?? null,
167
177
  control: ref === null ? { kind: "none" } : taskControlOf(runtime.store, ref.id, now),
@@ -521,23 +531,26 @@ export function createTaskViews(runtime) {
521
531
  })(),
522
532
  // Cockpit B2: whether this person's remembered password may approve these exact terms (the same rule approve()
523
533
  // applies), and since when.
524
- approvalFresh: (() => {
534
+ // The one password rule every approve form reads (approval-password.ts): remembered since when, or a local
535
+ // owner's no-password approval (eligible, or why not and where to go).
536
+ ...(() => {
525
537
  const scope = ref === null ? null : runtime.store.getScope(ref.externalId);
526
- if (scope === null || who?.via !== "cookie")
527
- return null;
538
+ if (scope === null || ref === null || who?.via !== "cookie")
539
+ return { approvalFresh: null, approvalLocal: null };
528
540
  const fresh = withFreshness("approve-standard", () => freshnessFor(who.name, Date.now(), true));
529
- if (fresh === null)
530
- return null;
531
- const gate = runtime.store.approvalGate(ref.externalId, who.name, "person");
532
- return freshlyApprovable(runtime.store, ref.externalId, scope, gate.verdict === "seal" && !gate.protectedWork) ? new Date(fresh.at).toISOString() : null;
541
+ const field = approvalPasswordField(runtime.store, { name: who.name, taskId: ref.externalId, scope, passwordless: false,
542
+ localOwner: who.authority === "local-owner", freshAt: fresh?.at ?? null, returnTo: taskHref(ref.externalId) });
543
+ return { approvalFresh: field.field === "remembered" ? field.since : null,
544
+ approvalLocal: field.field === "none" && field.why === "local-owner" ? { eligible: true }
545
+ : field.field === "local-refused" ? { eligible: false, message: field.message, href: field.href, action: field.action } : null };
533
546
  })(),
534
- // Easy-start C5: a laptop's local owner approves eligible work with no password (the seal applies the same rule).
535
- approvalLocal: (() => {
536
- if (ref === null || who?.via !== "cookie" || who.authority !== "local-owner")
547
+ routeChange: ref === null ? null : routeChangeOf(runtime.store, ref.externalId),
548
+ followThrough: who === undefined || familyInWebReach(runtime.store, who, taskId, runtime.visible) === null ? null : followThroughView(runtime.store, runtime.evidenceRoot, taskId, taskHref(taskId), who.role === "approver" && who.via === "cookie" && !runtime.store.isDemo(), runtime.clock()),
549
+ fastGrown: (() => {
550
+ if (ref === null || runtime.store.getTask(ref.externalId)?.state !== "done" || !fastStarted(runtime.store, ref.externalId))
537
551
  return null;
538
- if (localApprovalEligible(runtime.store, who.name, ref.externalId).ok)
539
- return { eligible: true };
540
- return { eligible: false, ...localOwnerRefusal(runtime.store.accountOf(who.name)?.passwordSet === true, taskHref(ref.externalId)) };
552
+ const last = runtime.store.lastBuilderRun(ref.id);
553
+ return last === null ? null : fastChangeProblem(runtime.store, ref.externalId, last.id);
541
554
  })(),
542
555
  steerRecent: ref === null ? [] : runtime.store.runsFor(ref.id).filter(one => one.outcome !== null).map(one => one.id).sort((a, b) => b - a).slice(0, 5),
543
556
  // The running attempt's composer, for an approver on a browser session (steer-controls.ts).
@@ -805,7 +818,7 @@ export function createTaskViews(runtime) {
805
818
  return runtime.sendScreen(response, status, screen(admittedFamily.root.title, html `<h1>${admittedFamily.root.title}</h1><p>${admittedFamily.current.state}</p><p class="problem" data-history-problem>${admittedFamily.problem}</p>`, { chrome: runtime.chromeFor(admittedFamily.root.repo, "tasks") }));
806
819
  const data = taskViewData(taskId, who, problem);
807
820
  if (data === null)
808
- return refuse(response, who, 404, "no such task", "/work");
821
+ return refuse(response, who, 404, "no such task", "/tasks");
809
822
  const family = runtime.familyOf(taskId);
810
823
  const presentedData = { ...data, rootId: family?.root.id ?? taskId, rootTitle: family?.root.title ?? data.task.title,
811
824
  ...(family === null || data.assignment != null ? {} : { history: familyHistory(family) }),
@@ -858,7 +871,21 @@ export function createTaskViews(runtime) {
858
871
  // Ship (its pull request's checks, each on one time axis) and a release's own steps, read from the store now.
859
872
  const rootId = family?.root.id ?? taskId;
860
873
  const oneTaskView = onePage?.kind === "task" ? { ...onePage, ship: shipViewOf(shipChecksForTask(runtime.store, rootId), runtime.clock()),
861
- release: releaseStepsViewOf(releaseProgressFor(runtime.store, rootId), runtime.clock()) } : onePage;
874
+ release: releaseStepsViewOf(releaseProgressFor(runtime.store, rootId), runtime.clock()),
875
+ // "Can I merge?" (P1.6): the checklist from the same assignment this page read, each blocker with its own form.
876
+ mergeReady: mergeReadyViewOf(runtime.store, taskId, data.assignment, who, presentedData.runChecks ?? null, taskHref(taskId)) } : onePage;
877
+ // A check the machine runs (a prepared candidate: a release's check) is said as the check it is, with no agent's
878
+ // activity, no notes to read, and for a release its own steps instead of the generic Plan → Build stepper.
879
+ if (oneTaskView?.kind === "task" && machineChecked(runtime.store, taskId)) {
880
+ const words = machineCheckWords(runtime.store, taskId, data.liveRunId ?? null, oneTaskView.release ?? null, runtime.clock());
881
+ const card = oneTaskView.status;
882
+ if (words !== null && card !== null && card.status.headline === "Building")
883
+ oneTaskView.status = { ...card, status: { ...card.status, sentence: words } };
884
+ oneTaskView.activity = null;
885
+ oneTaskView.steering = null;
886
+ if (oneTaskView.release != null)
887
+ oneTaskView.journey = null;
888
+ }
862
889
  // The Result tab's no-script page, under the task's own (the same HTML its result page serves), and its script.
863
890
  if (extras.result != null) {
864
891
  page.body = html `${page.body}<section class="task-result-tab" id="task-result" aria-label="Result">${extras.result.html}</section>`;
@@ -895,14 +922,14 @@ export function createTaskViews(runtime) {
895
922
  const stop = runtime.store.stopOf(runId);
896
923
  const run = runtime.store.getRun(runId);
897
924
  if (stop === null || run === null || stop.taskRef !== ref.id)
898
- return taskScreen(response, who, taskId, `run #${runId} is not a stopped attempt of this task`, 409);
925
+ return taskScreen(response, who, taskId, `build #${runId} is not a stopped attempt of this task`, 409);
899
926
  if (run.role === "reviewer")
900
- return taskScreen(response, who, taskId, `run #${runId} is a historical review run — nothing reruns it; open the saved result instead`, 409);
927
+ return taskScreen(response, who, taskId, `build #${runId} was an old review. Nothing reruns it; open the saved result instead`, 409);
901
928
  if (stop.resumedAt !== null)
902
- return taskScreen(response, who, taskId, `run #${runId} was already resumed by ${stop.resumedBy ?? "?"}`, 409);
929
+ return taskScreen(response, who, taskId, `build #${runId} was already resumed by ${stop.resumedBy ?? "?"}`, 409);
903
930
  const control = taskControlOf(runtime.store, ref.id, now);
904
931
  if (control.kind !== "paused" || control.run !== runId) {
905
- return taskScreen(response, who, taskId, control.kind === "stopping" ? `run #${runId} is still stopping — its processes are not yet established gone` : `run #${runId} is no longer the attempt a resume can name — reload and decide against the current state`, 409);
932
+ return taskScreen(response, who, taskId, control.kind === "stopping" ? `build #${runId} is still stopping. Toolroll is making sure it has ended` : `build #${runId} is no longer the one to resume. Reload and decide again`, 409);
906
933
  }
907
934
  const digest = resumeDigestOf(taskId, runId, stop.settledAt ?? "", runtime.store.getScope(taskId));
908
935
  const nonceValue = randomBytes(18).toString("base64url");
@@ -1168,7 +1195,7 @@ export function createTaskViews(runtime) {
1168
1195
  // Editor links (arc 6): the deployment capability, THIS machine's
1169
1196
  // runner owning the run, the session's own device-side yes, and a
1170
1197
  // checkout to open — or nothing renders.
1171
- editor: runtime.options.editorLinks !== undefined && runtime.options.localRunner !== undefined && run.runner === runtime.options.localRunner && who.via === "cookie" && who.session.editorLinks === true && run.worktree !== null
1198
+ editor: runtime.options.editorLinks !== undefined && runtime.options.localRunner !== undefined && run.runner === runtime.options.localRunner && who.via === "cookie" && who.session.editorLinks === true && run.worktree !== null && existsSync(run.worktree)
1172
1199
  ? { worktree: run.worktree }
1173
1200
  : null,
1174
1201
  signedCriteria: scope?.acceptance.length ?? 0,
@@ -1231,18 +1258,28 @@ export function releaseStepsViewOf(progress, now) {
1231
1258
  const all = RELEASE_STEPS.filter(step => step !== "done");
1232
1259
  // A release that started part way (one carried under an earlier approval, or resumed from an older journal) shows
1233
1260
  // the steps from where its own record starts; a step missing between two recorded ones is a write that was lost.
1234
- const firstAt = all.findIndex(step => recorded.has(step));
1261
+ // A build running since the stop is the release's check, wherever the release last stopped; what follows it is to come.
1262
+ const building = progress.building, checkAt = all.indexOf("check");
1263
+ const firstAt = building === null ? all.findIndex(step => recorded.has(step)) : Math.min(all.findIndex(step => recorded.has(step)), checkAt);
1235
1264
  const lastAt = all.reduce((at, step, index) => recorded.has(step) ? index : at, -1);
1236
1265
  const steps = firstAt <= 0 ? all : all.slice(firstAt);
1237
1266
  const rows = steps.map(step => {
1238
1267
  const one = recorded.get(step);
1268
+ if (building !== null && step === "check") {
1269
+ const start = Date.parse(building.startedAt);
1270
+ return { name: RELEASE_STEP_WORDS[step] ?? step, st: "run", start: Number.isFinite(start) && Number.isFinite(origin) ? Math.max(0, (start - origin) / 1000) : 0, dur: Number.isFinite(start) ? Math.max(0, (now.getTime() - start) / 1000) : 0 };
1271
+ }
1272
+ if (building !== null && all.indexOf(step) > checkAt)
1273
+ return { name: RELEASE_STEP_WORDS[step] ?? step, st: "q", start: 0, dur: 0 };
1239
1274
  // A step with no record is never painted as done: between recorded ones it says so, after them it is queued.
1240
1275
  if (one === undefined)
1241
1276
  return all.indexOf(step) < lastAt ? { name: RELEASE_STEP_WORDS[step] ?? step, sub: "not recorded", st: "q", start: 0, dur: 0 }
1242
1277
  : { name: RELEASE_STEP_WORDS[step] ?? step, st: "q", start: 0, dur: 0 };
1243
1278
  const start = Date.parse(one.startedAt), end = one.endedAt === null ? now.getTime() : Date.parse(one.endedAt);
1244
- const st = one.state === "done" ? "ok" : one.state === "failed" ? "bad" : one.state === "waiting" ? "wait" : "run";
1245
- return { name: RELEASE_STEP_WORDS[step] ?? step, ...(one.detail === null || one.state === "done" ? {} : { sub: one.detail.slice(0, 160) }), st,
1279
+ // A failure the release has gone on from (resumed past it, or a build since) is history, not where it stands.
1280
+ const passed = one.state === "failed" && progress.stopped === null;
1281
+ const st = one.state === "done" || passed ? "ok" : one.state === "failed" ? "bad" : one.state === "waiting" ? "wait" : "run";
1282
+ return { name: RELEASE_STEP_WORDS[step] ?? step, ...(passed ? { sub: "Stopped here, then went on" } : one.detail === null || one.state === "done" ? {} : { sub: one.detail.slice(0, 160) }), st,
1246
1283
  start: Number.isFinite(start) && Number.isFinite(origin) ? Math.max(0, (start - origin) / 1000) : 0, dur: Number.isFinite(start) && Number.isFinite(end) ? Math.max(0, (end - start) / 1000) : 0 };
1247
1284
  });
1248
1285
  const current = rows.findIndex(row => row.st === "run" || row.st === "wait" || row.st === "bad");
@@ -1254,7 +1291,10 @@ export function releaseStepsViewOf(progress, now) {
1254
1291
  return {
1255
1292
  tag: progress.tag, version: progress.version, pr: progress.pr,
1256
1293
  rows,
1257
- state: progress.stopped !== null ? "failed" : done ? "done" : at?.st === "wait" ? "waiting" : "running",
1294
+ // Waiting is a person's act only at Approval; a step waiting on a machine (its check, GitHub's checks, quiet) is the
1295
+ // release running, never "Needs you".
1296
+ state: progress.stopped !== null ? "failed" : done ? "done" : at?.st === "wait" && steps[current] === "approval" ? "waiting" : "running",
1297
+ at: current === -1 ? null : { step: steps[current], name: at.name, index: current + 1, of: rows.length },
1258
1298
  headline: done ? `Released ${progress.version}` : progress.stopped !== null ? `Stopped at ${RELEASE_STEP_WORDS[progress.stopped.step] ?? progress.stopped.step}`
1259
1299
  : at !== null ? `${at.name} · Step ${current + 1} of ${rows.length}` : last === null ? "Starting" : `${last} done · the next step starts on its own`,
1260
1300
  stopped: progress.stopped === null ? null : { step: RELEASE_STEP_WORDS[progress.stopped.step] ?? progress.stopped.step, message: progress.stopped.message, at: progress.stopped.at },