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/invoke.js CHANGED
@@ -21,13 +21,16 @@ import { adapterFor, auditOf, claudePluginEnv, isolationUnsupported, ALL_CREDENT
21
21
  import { readProviderKey, readAuthModeStrict, PROVIDER_KEY_ENV, OWN_KEY_ENV } from "./keys.js";
22
22
  import { ACP_AGENTS, isAcpProvider, isKeyedProvider } from "./acp-agents.js";
23
23
  import { launchFromJson, verifyLaunch } from "./acp-attest.js";
24
- import { classifyTerminal } from "./exhaustion.js";
24
+ import { classifyTerminal, terminalMatchesLimit } from "./exhaustion.js";
25
+ import { captureProviderFailure } from "./provider-capture.js";
26
+ import { chainCustodyProblem, closeWalkSignedOut, pinnedEntryOf } from "./fallback.js";
27
+ import { codexLoginBilling, livePlanLogin, PLAN_LOGIN_PROVABLE } from "./plan-login.js";
25
28
  import { liftAuthPause } from "./provider-auth.js";
26
- import { attestProvider } from "./attest.js";
29
+ import { attestProvider, reportedVersion } from "./attest.js";
27
30
  import { run as runCommand, runOwnerTag, streamsInput } from "./exec.js";
28
31
  import { spawnSync } from "node:child_process";
29
32
  import { currentContainment } from "./containment.js";
30
- import { recordRunBillingBasis, recordRunCostUnknown, recordRunModelReported } from "./store.js";
33
+ import { recordCheckStamp, recordRunBillingBasis, recordRunCostUnknown, recordRunModelReported } from "./store.js";
31
34
  import { witnessedRunner } from "./process-custody.js";
32
35
  import { underStopWatch } from "./task-control.js";
33
36
  import { childDatabaseEnv, isolatedChildDatabase, removeChildDatabase as removeAgentDatabase } from "./child-database.js";
@@ -37,12 +40,15 @@ import { existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, realpathSy
37
40
  import { homedir, tmpdir } from "node:os";
38
41
  import { dirname, join } from "node:path";
39
42
  import { poolRootOf } from "./worktree.js";
43
+ import { prepareProtectedStorage, storageRefusalOf } from "./protected-storage.js";
40
44
  import { checkLaunchRoot, signInGrants, signInIsItself } from "./agent-fence.js";
41
45
  import { localModeOn } from "./local-owner.js";
46
+ import { CHECK_STAMP_VERSION, EVERYWHERE, unfencedStamp } from "./check-stamp.js";
42
47
  import { CHECK_ACCESS_DEFAULT, checkAccessHere, localModeCap, narrowestAccess, narrowestNetwork, signInsAvailable } from "./check-access.js";
43
48
  import { readDenyPath } from "./project-sandbox.js";
44
49
  import { sandboxRuntimeWords } from "./sandbox-runtime.js";
45
50
  import { canonicalJson } from "./canonical-json.js";
51
+ import { deliveredFile } from "./code-map-admission.js";
46
52
  import { buildCacheEnv, buildCacheFolder, grantFolder, writeAllowlist, WRITES_DEFAULT, CheckoutBindingError, trustedWorktreeBinding, checkoutGitEnv, HARDENED_GIT_ARGS, codexFenceArgv, fenceFor, fencedGitEnv, LEGACY_SANDBOX, launchSandboxOf, linuxFenceAvailable, macosFenceAvailable, NO_GRANTS, sandboxedSpawn, serverGrantsOf, runFolders, taskBoundary } from "./agent-fence.js";
47
53
  import { proxyEnv, providerOnlyNetwork, repositoryEcosystems, resolveNetwork, startEgressProxy } from "./egress.js";
48
54
  import { claudeConfigDir, claudeDefaults, codexDefaults, codexHomeDir, isolatedCodexHome } from "./provider-home.js";
@@ -271,7 +277,12 @@ export async function invokeAgent(store, runId, spec, invocation, options) {
271
277
  diagnostic: strict.problem,
272
278
  };
273
279
  }
274
- const authMode = strict.mode;
280
+ // A run on an approved chain spends under the auth mode that entry was pinned to, never the provider's current setting.
281
+ const pinned = invocation.phase === "review" ? null : pinnedEntryOf(store, runId);
282
+ if (pinned !== null && "problem" in pinned) {
283
+ return { kind: "refused", reason: "route-authority", providerVersion: attested === null ? null : attested.version, diagnostic: `the fallback agent's authority lapsed before spawn — ${pinned.problem}` };
284
+ }
285
+ const authMode = pinned === null ? strict.mode : pinned.entry.authMode;
275
286
  if (spec.provider === "claude" && invocation.phase !== "review") {
276
287
  const problem = claudeSettingsAuthProblem(options.env?.["CLAUDE_CONFIG_DIR"] ?? process.env["CLAUDE_CONFIG_DIR"] ?? join(keyHome ?? homedir(), ".claude"));
277
288
  if (problem !== null)
@@ -284,6 +295,27 @@ export async function invokeAgent(store, runId, spec, invocation, options) {
284
295
  const managedKey = authMode === "api-key" && keyed !== null && keyEnvName !== null
285
296
  ? readProviderKey(keyed, keyHome) ?? (process.env[keyEnvName] || null)
286
297
  : null;
298
+ // A pinned API key that isn't there never falls through to some other credential.
299
+ if (pinned !== null && authMode === "api-key" && managedKey === null) {
300
+ return { kind: "refused", reason: "auth-mode", providerVersion: attested === null ? null : attested.version, diagnostic: `this fallback agent is approved on an API key, and ${spec.provider} has none here` };
301
+ }
302
+ // What decides the child's credential, computed once: the environment it starts from and every variable it sheds. The
303
+ // live plan proof below reads with exactly these, and the child launches with exactly these (childOmitEnv).
304
+ const childAuth = childAuthEnv({ provider: spec.provider, phase: invocation.phase, authMode, keyEnvName, ownKeyEnv,
305
+ extraOmitEnv: adapter.extraOmitEnv, runEnv: runOptions.env, runOmitEnv: runOptions.omitEnv });
306
+ // A fallback agent approved on its plan runs only on a login its CLI proves, live, is that plan: read with the run's
307
+ // own environment, before anything is started for it (no proxy, tools or homes to leave behind). A provider with no
308
+ // live proof never gets here (filing refuses it); if one does, it's refused too. A key is never read as paid.
309
+ if (pinned !== null && pinned.index > 0 && authMode === "subscription") {
310
+ const live = PLAN_LOGIN_PROVABLE.has(spec.provider)
311
+ ? await livePlanLogin(spec.provider, attested !== null ? attested.executable : adapter.binary, childAuth.omitEnv, childAuth.env)
312
+ : null;
313
+ if (live !== "subscription") {
314
+ closeWalkSignedOut(store, runId, clock());
315
+ return { kind: "refused", reason: "auth-mode", providerVersion: attested === null ? null : attested.version,
316
+ diagnostic: `this fallback agent is approved on its plan, and ${spec.provider} isn't signed in to one here${live === "api-key" ? " (it's signed in another way)" : ""}` };
317
+ }
318
+ }
287
319
  // Resume XOR mint, enforced at the GATEWAY (Codex gemini verify round 2,
288
320
  // finding 3): a resume and a minted start id are mutually exclusive —
289
321
  // geminiArgv silently prefers --resume, so a minted id that rides
@@ -362,12 +394,12 @@ export async function invokeAgent(store, runId, spec, invocation, options) {
362
394
  // tool servers outside its command sandbox, so its own launch folder is
363
395
  // fenced too; Claude reads its config from there, inside the sandbox.
364
396
  const ownTools = tools?.privateDir === undefined ? null : realpathSync(tools.privateDir);
365
- const fenceOf = invocation.phase === "review" ? { fence: [], fileTools: [], grants: NO_GRANTS, gitPaths: [], cache: undefined, problem: undefined } : runFence(store, runId, spec.provider, options.keyHome, codexHarness ? null : ownTools, launchWrites ?? WRITES_DEFAULT, launchSandbox ?? LEGACY_SANDBOX);
397
+ const fenceOf = invocation.phase === "review" ? { fence: [], fileTools: [], grants: NO_GRANTS, gitPaths: [], cache: undefined, problem: undefined } : await runFence(store, runId, spec.provider, options.keyHome, codexHarness ? null : ownTools, launchWrites ?? WRITES_DEFAULT, launchSandbox ?? LEGACY_SANDBOX);
366
398
  if (fenceOf.problem !== undefined) {
367
399
  tools?.cleanup();
368
400
  codexHome?.cleanup();
369
401
  removeAgentDatabase(isolatedDb.dir);
370
- return { kind: "refused", reason: "containment", providerVersion: attested?.version ?? null, diagnostic: fenceOf.problem };
402
+ return { kind: "refused", reason: "storageRefused" in fenceOf && fenceOf.storageRefused === true ? "protected-storage" : "containment", providerVersion: attested?.version ?? null, diagnostic: fenceOf.problem };
371
403
  }
372
404
  const fence = [...fenceOf.fence, ...(fenceOf.fence.length > 0 && codexHarness ? [ownTools, codexHome === null ? null : realpathSync(codexHome.dir)].filter((path) => path !== null) : [])];
373
405
  const grants = { ...fenceOf.grants };
@@ -497,20 +529,8 @@ export async function invokeAgent(store, runId, spec, invocation, options) {
497
529
  // the live control database through a generic RunOptions override.
498
530
  ...childDatabaseEnv(isolatedDb.file),
499
531
  };
500
- const childOmitEnv = [
501
- ...(runOptions.omitEnv ?? []),
502
- ...adapter.extraOmitEnv,
503
- // A Claude build loads no plugin, from settings or the environment (a review's safe mode already loads none).
504
- ...(spec.provider === "claude" && invocation.phase !== "review" ? claudePluginEnv({ ...process.env, ...(runOptions.env ?? {}) }) : []),
505
- // Subscription mode: shed this provider's OWN key too, so an ambient
506
- // one cannot force API billing over the login the operator prefers.
507
- // Api-key mode: shed every OTHER own-key alias (finding 1) — gemini
508
- // reads GOOGLE_API_KEY as well as GEMINI_API_KEY, and a stray alias
509
- // must not override the ONE canonical key this mode injected.
510
- ...(authMode === "subscription"
511
- ? ownKeyEnv
512
- : ownKeyEnv.filter(name => name !== keyEnvName)),
513
- ];
532
+ // The one omit list (childAuthEnv): the live plan proof read with this same list.
533
+ const childOmitEnv = childAuth.omitEnv;
514
534
  const launchOptions = {
515
535
  ...(fenced.wrap.length === 0 ? {} : { fence: fenced.wrap, fenceGrants: grants, confinement: "required" }),
516
536
  ...runOptions,
@@ -678,12 +698,23 @@ export async function invokeAgent(store, runId, spec, invocation, options) {
678
698
  return { kind: "refused", reason: "provider-unattested", providerVersion: acpAttestation.agent.version, diagnostic: verified.problem };
679
699
  }
680
700
  }
681
- if (attested !== null)
682
- store.stampProviderStart(runId, clock(), attested.version);
683
- else if (acpAttestation !== null)
684
- store.stampProviderStart(runId, clock(), acpAttestation.agent.version);
685
- else
686
- store.stampProviderStart(runId, clock());
701
+ // THE CHAIN'S LAST LOOK (fallback.ts): the walk's place, the approval, and for a paid entry the live grant, read in
702
+ // the one transaction that stamps the spend, after every await.
703
+ const chainRefusal = store.transact(() => {
704
+ const custody = invocation.phase === "review" ? null : chainCustodyProblem(store, runId);
705
+ if (custody !== null)
706
+ return custody;
707
+ if (attested !== null)
708
+ store.stampProviderStart(runId, clock(), attested.version);
709
+ else if (acpAttestation !== null)
710
+ store.stampProviderStart(runId, clock(), acpAttestation.agent.version);
711
+ else
712
+ store.stampProviderStart(runId, clock());
713
+ return null;
714
+ });
715
+ if (chainRefusal !== null) {
716
+ return { kind: "refused", reason: "route-authority", providerVersion: attested === null ? null : attested.version, diagnostic: `the fallback chain's authority lapsed before spawn — ${chainRefusal}` };
717
+ }
687
718
  // The brief is queued only once the checkout has passed the fence check.
688
719
  liveInput?.begin(invocation.brief);
689
720
  spawnedAt = Date.now();
@@ -788,13 +819,28 @@ export async function invokeAgent(store, runId, spec, invocation, options) {
788
819
  const quietEarlyExit = result.code !== 0 && !result.timedOut && !result.notFound &&
789
820
  Date.now() - spawnedAt <= AUTH_EARLY_EXIT_MS &&
790
821
  envelope.promptConsumed !== true && (envelope.tokensOut ?? 0) === 0;
822
+ // Claude has no attested range (attest.ts), so a terminal that reads as a plan limit asks the installed CLI its version.
823
+ const limitVersion = !terminalMatchesLimit(spec.provider, authMode, envelope.structuralTerminal) ? null
824
+ : attested !== null ? attested.version : await reportedVersion(adapter.binary, options.versionProbe);
791
825
  const terminalClass = classifyTerminal({
792
826
  terminal: envelope.structuralTerminal,
827
+ run: { provider: spec.provider, authMode, version: limitVersion },
793
828
  // stderr and plain stdout only: a JSON event line can carry the agent's
794
829
  // own command output (codex aggregated_output), never its sign-in.
795
830
  earlyExit: quietEarlyExit ? `${result.stderr.slice(-4096)}\n${plainLines(result.stdout).slice(-4096)}` : null,
796
831
  });
797
- store.stampTerminalClass(runId, authMode, terminalClass);
832
+ // A plan limit counts only from a run whose protocol and identity hold (fallback.ts moves work on it): a terminal from a
833
+ // conflicting session, a minted id it didn't echo, or a failure on exit 0 is refused below and is never exhaustion.
834
+ const protocolClean = envelope.protocolError === null
835
+ && !(startSessionId !== undefined && envelope.initObserved === true && envelope.sessionId !== startSessionId)
836
+ && sessionIdentityProblem === null
837
+ && !(result.code === 0 && envelope.structuralTerminal?.failed === true);
838
+ store.stampTerminalClass(runId, authMode, terminalClass === "usage-exhausted" && !protocolClean ? "not-exhausted" : terminalClass);
839
+ // What the provider said on its way out, saved for a person (provider-capture.ts). Nothing routes on it.
840
+ if (terminalClass !== "auth-expired" && !result.timedOut && !result.notFound && (result.code !== 0 || envelope.structuralTerminal?.failed === true)) {
841
+ captureProviderFailure(store, { run: runId, provider: spec.provider, model: spec.model ?? null, authMode, exitCode: result.code, code: envelope.structuralTerminal?.code ?? null,
842
+ texts: [envelope.structuralTerminal?.text, envelope.finalMessage?.slice(-1_000), result.stderr.slice(-2_000), plainLines(result.stdout).slice(-1_000)] }, new Date());
843
+ }
798
844
  // Contradictory structural identity is a provider protocol failure, not
799
845
  // a session selection problem. Usage above remains recorded because the
800
846
  // process ran and spent, but no downstream handoff or repair may trust
@@ -936,7 +982,7 @@ function linkedFor(store, worktree) {
936
982
  /** The paths this run's agent may not reach: the state folder around the live database (but not its own worktree),
937
983
  * ~/.toolroll (or its older name), its task boundary and other launches' private folders; what the fence gives back
938
984
  * (FenceGrants); and what only Claude's file tools refuse. */
939
- function runFence(store, runId, provider, keyHome, ownTools, writes = WRITES_DEFAULT, sandbox = LEGACY_SANDBOX) {
985
+ async function runFence(store, runId, provider, keyHome, ownTools, writes = WRITES_DEFAULT, sandbox = LEGACY_SANDBOX) {
940
986
  try {
941
987
  // A plane always has a database file; an in-memory store (a test) has nothing to fence.
942
988
  const databaseFile = store.databaseFile();
@@ -949,7 +995,9 @@ function runFence(store, runId, provider, keyHome, ownTools, writes = WRITES_DEF
949
995
  throw new CheckoutBindingError("The checkout's Git pointer was changed or its trusted binding could not be verified.");
950
996
  const poolRoots = [join(dirname(databaseFile), "worktrees"), ...store.listWorktrees().map(record => poolRootOf(record.path, record.repo))];
951
997
  const binding = worktree === null || row === null ? null : trustedWorktreeBinding(row.repo, worktree, poolRoots);
952
- const boundary = worktree === null ? null : taskBoundary({ worktree, provider, ...home, binding, policy: sandbox.policy });
998
+ // The storage check, awaited off this thread (protected-storage.ts): a privacy prompt holds up only its child.
999
+ const storage = await prepareProtectedStorage(keyHome ?? homedir());
1000
+ const boundary = worktree === null ? null : taskBoundary({ worktree, provider, ...home, binding, policy: sandbox.policy, storage });
953
1001
  const folders = runFolders(ownTools);
954
1002
  // A build with a checkout writes only its workspace unless its approval said anywhere (arc 2): its checkout and
955
1003
  // Git data, its launch folder, its project's caches and the provider state its CLI must write.
@@ -958,8 +1006,10 @@ function runFence(store, runId, provider, keyHome, ownTools, writes = WRITES_DEF
958
1006
  // The shared install is granted only as the plane linked it: this checkout's project and stamped setup.
959
1007
  // Every plane checkout pool is denied around this one: the plane's own beside the database, and the one this
960
1008
  // checkout was recorded in (a --pool elsewhere), so another project's worktree, present at launch, is out of reach. Native wrappers also cover later creations.
961
- const launch = fenceFor(provider === "command" ? "command" : "build", { databaseFile, worktree, ...home, boundary, binding, runFolders: folders, poolRoots,
1009
+ const launch = fenceFor(provider === "command" ? "command" : "build", { databaseFile, worktree, ...home, boundary, binding, runFolders: folders, poolRoots, storage,
962
1010
  extra: allowed?.deny ?? [], linked: linkedFor(store, worktree), writes: allowed === null ? null : { writes: allowed.writes, readOnly: allowed.readOnly }, policy: sandbox.policy, provider,
1011
+ // This launch's code map file: exactly that file, read-only, inside the denied state folder. Commands don't get it.
1012
+ ...(provider === "command" ? {} : { exactFile: deliveredFile(store, runId) }),
963
1013
  // The sealed read denies, and whatever the project lists now too (adding a deny only narrows).
964
1014
  readDeny: [...new Set([...sandbox.readDeny, ...(row === null ? [] : store.projectSandbox(row.repo).readDeny)])].map(one => readDenyPath(one, keyHome ?? homedir())), machLookup: sandbox.machLookup });
965
1015
  return {
@@ -972,6 +1022,10 @@ function runFence(store, runId, provider, keyHome, ownTools, writes = WRITES_DEF
972
1022
  };
973
1023
  }
974
1024
  catch (error) {
1025
+ // The storage check's own words: what didn't finish, and which program macOS may need to allow.
1026
+ const refused = storageRefusalOf(error);
1027
+ if (refused !== null)
1028
+ return { fence: [], fileTools: [], grants: NO_GRANTS, problem: refused.message, storageRefused: true };
975
1029
  return { fence: [], fileTools: [], grants: NO_GRANTS, problem: error instanceof CheckoutBindingError ? error.message : "The build fence could not be prepared." };
976
1030
  }
977
1031
  }
@@ -992,6 +1046,7 @@ export function fencedCommands(store, runId, writes, inner, available = () => ma
992
1046
  if (!available()) {
993
1047
  if (store.unfencedAgentsAllowed()) {
994
1048
  store.stampRunCheckAccess(runId, "unsandboxed");
1049
+ stampLaunch(store, [runId], unfencedStamp());
995
1050
  return inner(file, args, options);
996
1051
  }
997
1052
  return { code: 1, stdout: "", stderr: "This machine can't confine the build's commands (no macOS sandbox, no working bubblewrap), so they didn't run. Install bubblewrap, socat and ripgrep (Linux), or an instance operator allows unconfined agents with `toolroll config unconfined-agents allow`.\n", timedOut: false, notFound: false };
@@ -1002,13 +1057,17 @@ export function fencedCommands(store, runId, writes, inner, available = () => ma
1002
1057
  mkdirSync(temp, { mode: 0o700 });
1003
1058
  // Not sandboxed (check-access.ts): the project's checks are trusted by the whole instance, as before 0.9.66. Never
1004
1059
  // while local mode is on (easy-start C5): then they run in the fence, sandboxed.
1005
- if (access === "unsandboxed" && !localModeOn(store))
1060
+ if (access === "unsandboxed" && !localModeOn(store)) {
1061
+ stampLaunch(store, [runId], unfencedStamp());
1006
1062
  return await inner(file, args, { ...options, env: { ...(options?.env ?? {}), TMPDIR: temp, TMP: temp, TEMP: temp } });
1007
- const fenced = runFence(store, runId, "command", undefined, own, writes, sandbox);
1063
+ }
1064
+ const fenced = await runFence(store, runId, "command", undefined, own, writes, sandbox);
1008
1065
  if (fenced.problem !== undefined)
1009
1066
  return { code: 1, stdout: "", stderr: `${fenced.problem}\n`, timedOut: false, notFound: false };
1010
- if (fenced.fence.length === 0)
1067
+ if (fenced.fence.length === 0) {
1068
+ stampLaunch(store, [runId], unfencedStamp());
1011
1069
  return inner(file, args, options);
1070
+ }
1012
1071
  // A self-hosted check's own database (child-database.ts) is the command's to write too.
1013
1072
  const database = options?.env?.["TOOLROLL_DB"];
1014
1073
  const checkData = database === undefined || fenced.grants.writes === undefined ? null : grantFolder(dirname(database), false);
@@ -1020,12 +1079,14 @@ export function fencedCommands(store, runId, writes, inner, available = () => ma
1020
1079
  if (signIns !== null && "problem" in signIns)
1021
1080
  return { code: 1, stdout: "", stderr: `${signIns.problem}\n`, timedOut: false, notFound: false };
1022
1081
  try {
1082
+ // Each sign-in file looked at again right before the launch: one no longer itself isn't granted.
1083
+ // Never the keychain: the sign-ins kept there come in the environment, read outside (keychainSignIns).
1084
+ const given = signIns === null ? grants : { ...grants, signIns: { ...signIns.grants, read: signIns.grants.read.filter(path => signInIsItself(realpathSync(homedir()), path)) }, network: signIns.network };
1085
+ stampLaunch(store, [runId], fencedStamp(signIns === null ? "sandboxed" : "sign-ins", fenced.fence, given, signIns === null ? undefined : network));
1023
1086
  return await inner(file, args, {
1024
1087
  ...options,
1025
1088
  fence: fenced.fence,
1026
- // Each sign-in file looked at again right before the launch: one no longer itself isn't granted.
1027
- // Never the keychain: the sign-ins kept there come in the environment, read outside (keychainSignIns).
1028
- fenceGrants: signIns === null ? grants : { ...grants, signIns: { ...signIns.grants, read: signIns.grants.read.filter(path => signInIsItself(realpathSync(homedir()), path)) }, network: signIns.network },
1089
+ fenceGrants: given,
1029
1090
  confinement: "required",
1030
1091
  env: { ...(options?.env ?? {}), TMPDIR: temp, TMP: temp, TEMP: temp, ...(fenced.cache === undefined || fenced.grants.writes === undefined ? {} : buildCacheEnv(fenced.cache)), ...(signIns?.env ?? {}) },
1031
1092
  ...(signIns === null ? {} : { omitEnv: [...(options?.omitEnv ?? []), ...SIGN_INS_OMITTED_ENV] }),
@@ -1046,26 +1107,32 @@ export function fencedCommands(store, runId, writes, inner, available = () => ma
1046
1107
  * project's writes setting, only that checkout, its Git data, its own temp folder, the project's caches and its check
1047
1108
  * database writable. Where nothing can confine them, they run only when an instance operator allowed unconfined agents.
1048
1109
  */
1049
- export function fencedCheckoutCommands(store, repo, checkout, inner, available = () => macosFenceAvailable() || linuxFenceAvailable(), access = CHECK_ACCESS_DEFAULT, network, sandbox = LEGACY_SANDBOX) {
1110
+ export function fencedCheckoutCommands(store, repo, checkout, inner, available = () => macosFenceAvailable() || linuxFenceAvailable(), access = CHECK_ACCESS_DEFAULT, network, sandbox = LEGACY_SANDBOX, stampRuns = []) {
1050
1111
  if (inner !== runCommand)
1051
1112
  return inner;
1052
1113
  return async (file, args, options) => {
1053
1114
  if (!available()) {
1054
- if (store.unfencedAgentsAllowed())
1115
+ if (store.unfencedAgentsAllowed()) {
1116
+ stampLaunch(store, stampRuns, unfencedStamp());
1055
1117
  return inner(file, args, options);
1118
+ }
1056
1119
  return { code: 1, stdout: "", stderr: "This machine can't confine the project's checks (no macOS sandbox, no working bubblewrap), so they didn't run. Install bubblewrap, socat and ripgrep (Linux), or an instance operator allows unconfined agents with `toolroll config unconfined-agents allow`.\n", timedOut: false, notFound: false };
1057
1120
  }
1058
1121
  const databaseFile = store.databaseFile();
1059
- if (databaseFile === null)
1122
+ if (databaseFile === null) {
1123
+ stampLaunch(store, stampRuns, unfencedStamp());
1060
1124
  return inner(file, args, options);
1125
+ }
1061
1126
  const own = realpathSync(mkdtempSync(join(checkLaunchRoot(), "so-cmd-")));
1062
1127
  try {
1063
1128
  const temp = join(own, "tmp");
1064
1129
  mkdirSync(temp, { mode: 0o700 });
1065
1130
  // Not sandboxed (check-access.ts): the project's checks are trusted by the whole instance, as before 0.9.66. Never
1066
1131
  // while local mode is on (easy-start C5): then they run in the fence, sandboxed.
1067
- if (access === "unsandboxed" && !localModeOn(store))
1132
+ if (access === "unsandboxed" && !localModeOn(store)) {
1133
+ stampLaunch(store, stampRuns, unfencedStamp());
1068
1134
  return await inner(file, args, { ...options, env: { ...(options?.env ?? {}), TMPDIR: temp, TMP: temp, TEMP: temp } });
1135
+ }
1069
1136
  let binding;
1070
1137
  try {
1071
1138
  binding = trustedWorktreeBinding(repo, checkout);
@@ -1074,13 +1141,20 @@ export function fencedCheckoutCommands(store, repo, checkout, inner, available =
1074
1141
  return { code: 1, stdout: "", stderr: "The check's checkout couldn't be bound to its project, so nothing ran.\n", timedOut: false, notFound: false };
1075
1142
  }
1076
1143
  const poolRoots = [join(dirname(databaseFile), "worktrees"), ...store.listWorktrees().map(record => poolRootOf(record.path, record.repo))];
1077
- const boundary = taskBoundary({ worktree: checkout, provider: "command", binding, policy: sandbox.policy });
1144
+ let storage;
1145
+ try {
1146
+ storage = await prepareProtectedStorage(homedir());
1147
+ }
1148
+ catch (error) {
1149
+ return { code: 1, stdout: "", stderr: `${storageRefusalOf(error)?.message ?? "The check's fence could not be prepared, so nothing ran."}\n`, timedOut: false, notFound: false };
1150
+ }
1151
+ const boundary = taskBoundary({ worktree: checkout, provider: "command", binding, policy: sandbox.policy, storage });
1078
1152
  const folders = runFolders(own);
1079
1153
  const cache = buildCacheFolder(repo);
1080
1154
  const allowed = store.projectWrites(repo) !== "workspace" ? null : writeAllowlist({ provider: "command", home: homedir(), worktree: checkout, binding, launchFolder: own, cache });
1081
1155
  const database = options?.env?.["TOOLROLL_DB"];
1082
1156
  const checkData = allowed === null || database === undefined ? null : grantFolder(dirname(database), false);
1083
- const launch = fenceFor("check", { databaseFile, worktree: checkout, boundary, binding, runFolders: folders, poolRoots, extra: allowed?.deny ?? [], linked: linkedFor(store, checkout),
1157
+ const launch = fenceFor("check", { databaseFile, worktree: checkout, boundary, binding, runFolders: folders, poolRoots, storage, extra: allowed?.deny ?? [], linked: linkedFor(store, checkout),
1084
1158
  readDeny: sandbox.readDeny.map(one => readDenyPath(one, homedir())), machLookup: sandbox.machLookup, policy: sandbox.policy,
1085
1159
  writes: allowed === null ? null : { writes: { ...allowed.writes, subpaths: [...allowed.writes.subpaths, ...(checkData === null ? [] : [checkData])] }, readOnly: allowed.readOnly } });
1086
1160
  const grants = launch.fenceGrants;
@@ -1091,12 +1165,14 @@ export function fencedCheckoutCommands(store, repo, checkout, inner, available =
1091
1165
  if (signIns !== null && "problem" in signIns)
1092
1166
  return { code: 1, stdout: "", stderr: `${signIns.problem}\n`, timedOut: false, notFound: false };
1093
1167
  try {
1168
+ // Each sign-in file looked at again right before the launch: one no longer itself isn't granted.
1169
+ // Never the keychain: the sign-ins kept there come in the environment, read outside (keychainSignIns).
1170
+ const given = signIns === null ? grants : { ...grants, signIns: { ...signIns.grants, read: signIns.grants.read.filter(path => signInIsItself(realpathSync(homedir()), path)) }, network: signIns.network };
1171
+ stampLaunch(store, stampRuns, fencedStamp(signIns === null ? "sandboxed" : "sign-ins", launch.fence, given, signIns === null ? undefined : network));
1094
1172
  return await inner(file, args, {
1095
1173
  ...options,
1096
1174
  fence: launch.fence,
1097
- // Each sign-in file looked at again right before the launch: one no longer itself isn't granted.
1098
- // Never the keychain: the sign-ins kept there come in the environment, read outside (keychainSignIns).
1099
- fenceGrants: signIns === null ? grants : { ...grants, signIns: { ...signIns.grants, read: signIns.grants.read.filter(path => signInIsItself(realpathSync(homedir()), path)) }, network: signIns.network },
1175
+ fenceGrants: given,
1100
1176
  confinement: launch.confinement,
1101
1177
  env: { ...(options?.env ?? {}), TMPDIR: temp, TMP: temp, TEMP: temp, ...(allowed === null ? {} : buildCacheEnv(cache)), ...(signIns?.env ?? {}) },
1102
1178
  ...(signIns === null ? {} : { omitEnv: [...(options?.omitEnv ?? []), ...SIGN_INS_OMITTED_ENV] }),
@@ -1157,18 +1233,6 @@ async function runTools(store, runId, spec, keyHome, clock, readOnly, withheld,
1157
1233
  return noToolsArgs(spec.provider);
1158
1234
  }
1159
1235
  }
1160
- /** How Codex says it's signed in (`codex login status`): a ChatGPT sign-in is its plan; a key or Bedrock is a key;
1161
- * null when it says neither (not signed in, or it didn't answer). */
1162
- async function codexLoginBilling(binary, omitEnv) {
1163
- try {
1164
- const status = await runCommand(binary, ["login", "status"], { timeoutMs: 15_000, omitEnv, processGroup: true });
1165
- const said = `${status.stdout}\n${status.stderr}`;
1166
- return /using chatgpt/i.test(said) ? "subscription" : /api key|bedrock/i.test(said) ? "api-key" : null;
1167
- }
1168
- catch {
1169
- return null;
1170
- }
1171
- }
1172
1236
  /** The lines of a harness's stdout that are not JSON events. */
1173
1237
  function plainLines(stdout) {
1174
1238
  return stdout.split("\n").filter(line => !line.trimStart().startsWith("{")).join("\n");
@@ -1284,7 +1348,7 @@ export function checkoutRunner(store, repo, checkout, inner, runIds, ran, platfo
1284
1348
  return (file, args, options) => {
1285
1349
  const launch = checkoutLaunchOf(store, repo, runIds, platform);
1286
1350
  ran(actualCheckAccess(store, launch.access));
1287
- return fencedCheckoutCommands(store, repo, checkout, inner, undefined, launch.access, launch.network, checkoutSandboxOf(store, repo, runIds))(file, args, options);
1351
+ return fencedCheckoutCommands(store, repo, checkout, inner, undefined, launch.access, launch.network, checkoutSandboxOf(store, repo, runIds), runIds)(file, args, options);
1288
1352
  };
1289
1353
  }
1290
1354
  /**
@@ -1308,6 +1372,22 @@ export function checkoutSandboxOf(store, repo, runIds) {
1308
1372
  machLookup: common(one => one.machLookup, live.machLookup),
1309
1373
  };
1310
1374
  }
1375
+ /** A fenced launch's stamp: the level it runs at and exactly what it was given (check-stamp.ts), read from the grants the
1376
+ * launch itself takes, never from what was asked for. */
1377
+ export function fencedStamp(level, fence, grants, network) {
1378
+ const writes = grants.writes;
1379
+ return {
1380
+ version: CHECK_STAMP_VERSION, level, policyDigest: fenceDigest(fence, grants),
1381
+ writable: writes === undefined ? [EVERYWHERE] : [...new Set([...writes.subpaths, ...writes.prefixes.map(one => `${one.path}${one.suffix}`)])].sort(),
1382
+ readable: [...new Set([...grants.readable, ...(grants.signIns?.read ?? [])])].sort(),
1383
+ network: network === undefined ? { mode: "machine" } : { mode: network.mode, hosts: [...network.hosts].sort() },
1384
+ };
1385
+ }
1386
+ function stampLaunch(store, runs, stamp) {
1387
+ const now = new Date();
1388
+ for (const runId of runs)
1389
+ recordCheckStamp(store, runId, stamp, now);
1390
+ }
1311
1391
  /** The digest of a launch's whole fence (its denied paths and grants, canonical), as stamped on its run (C11). */
1312
1392
  export function fenceDigest(fence, grants) {
1313
1393
  return createHash("sha256").update(`toolroll:fence:${canonicalJson({ fence: [...fence], grants: grants })}`).digest("hex").slice(0, 32);
@@ -1331,3 +1411,22 @@ export function observeClaudeBilling(seen, event) {
1331
1411
  seen.answered = true;
1332
1412
  }
1333
1413
  }
1414
+ /**
1415
+ * What decides a child's credential: the environment it starts from (the plane's, then the caller's) and every variable
1416
+ * it sheds. Subscription: this provider's own keys too, so an ambient key can't force API billing over the login.
1417
+ * API key: every other own-key alias, so a stray one can't override the one key injected. Foreign keys and (for a
1418
+ * Claude build) plugin variables always. The live plan proof and the launch both use this one answer.
1419
+ */
1420
+ export function childAuthEnv(args) {
1421
+ const env = { ...process.env, ...(args.runEnv ?? {}) };
1422
+ return {
1423
+ env,
1424
+ omitEnv: [
1425
+ ...(args.runOmitEnv ?? []),
1426
+ ...args.extraOmitEnv,
1427
+ // A Claude build loads no plugin, from settings or the environment (a review's safe mode already loads none).
1428
+ ...(args.provider === "claude" && args.phase !== "review" ? claudePluginEnv(env) : []),
1429
+ ...(args.authMode === "subscription" ? args.ownKeyEnv : args.ownKeyEnv.filter(name => name !== args.keyEnvName)),
1430
+ ],
1431
+ };
1432
+ }
@@ -25,7 +25,9 @@ param(
25
25
  # a space or a percent sign the controller passed.
26
26
  [Parameter(Position = 1)] [string] $Encoded,
27
27
  [string] $JobName,
28
- [string] $QueryName
28
+ [string] $QueryName,
29
+ # A whole-job memory limit in bytes (the code map's bounded reads); 0 is none.
30
+ [long] $MemoryLimit = 0
29
31
  )
30
32
 
31
33
  $ErrorActionPreference = "Stop"
@@ -68,6 +70,7 @@ public static class SoJob {
68
70
  public const int JobObjectBasicAccountingInformation = 1;
69
71
  public const int JobObjectExtendedLimitInformation = 9;
70
72
  public const uint JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000;
73
+ public const uint JOB_OBJECT_LIMIT_JOB_MEMORY = 0x200;
71
74
  public const uint CREATE_SUSPENDED = 0x4;
72
75
  public const uint CREATE_UNICODE_ENVIRONMENT = 0x400;
73
76
  public const int STARTF_USESTDHANDLES = 0x100;
@@ -111,7 +114,7 @@ public static class SoJob {
111
114
  catch { return "unknown"; }
112
115
  finally { CloseHandle(job); }
113
116
  }
114
- public static IntPtr MakeJob(string name) {
117
+ public static IntPtr MakeJob(string name, long memoryLimit) {
115
118
  IntPtr job = CreateJobObjectW(IntPtr.Zero, name);
116
119
  int createdError = Marshal.GetLastWin32Error();
117
120
  if (job != IntPtr.Zero && createdError == 183) { CloseHandle(job); throw new Exception("job name already exists"); }
@@ -119,6 +122,10 @@ public static class SoJob {
119
122
  var info = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
120
123
  // Kill on close; NO breakaway flags, so nothing inside may leave the job.
121
124
  info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
125
+ if (memoryLimit > 0) {
126
+ info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_JOB_MEMORY;
127
+ info.JobMemoryLimit = new UIntPtr((ulong)memoryLimit);
128
+ }
122
129
  int size = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
123
130
  IntPtr buffer = Marshal.AllocHGlobal(size);
124
131
  try {
@@ -177,7 +184,7 @@ try {
177
184
  [string[]]$targetArguments = ConvertFrom-Json -InputObject ([System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($Encoded)))
178
185
  if ($targetArguments.Count -lt 1) { throw "the target argv is empty" }
179
186
  # Admission arrives only after the controller durably records this helper.
180
- $job = [SoJob]::MakeJob($JobName)
187
+ $job = [SoJob]::MakeJob($JobName, $MemoryLimit)
181
188
  Send "ready"
182
189
  $admission = $reader.ReadLineAsync()
183
190
  if (-not $admission.Wait(15000) -or $admission.Result -cne "go") { throw "job admission refused" }
@@ -0,0 +1,41 @@
1
+ /** One keymap for the console and the terminal UI (docs/plans/plain-toolroll.md P1.7). A shortcut only ever presses a
2
+ * control the page already rendered, the same press a person's click makes, so any password field or confirm it opens
3
+ * still appears; with no such control on the page the key does nothing. Shortcuts sleep in text fields, with a
4
+ * modifier held, and while the shortcuts list is open. Navigation keys go to a page and post nothing. */
5
+ /** What a key does in the console. `press`: the first visible, enabled control matching these, in order. */
6
+ export type WebShortcut = {
7
+ kind: "go";
8
+ href: string;
9
+ } | {
10
+ kind: "press";
11
+ selectors: readonly string[];
12
+ } | {
13
+ kind: "search";
14
+ } | {
15
+ kind: "help";
16
+ };
17
+ /** What a key does in the terminal UI, or null with the reason it has none there (a named gap). */
18
+ export type TuiShortcut = "new" | "ink" | "send-back" | "retry" | "list" | "help";
19
+ export type Shortcut = {
20
+ keys: string;
21
+ says: string;
22
+ web: WebShortcut;
23
+ tui: TuiShortcut | {
24
+ none: string;
25
+ };
26
+ };
27
+ /** The page's ink act: Approve (the approve control itself, password popover and all) or Accept. */
28
+ export declare const INK_ACT_SELECTORS: readonly ["[data-ink-approve]", '[data-ink-act="accept"]', "[data-accept-result][data-primary-action]"];
29
+ /** Send back: the control that opens the Send back form (never its submit: the note comes first). */
30
+ export declare const SEND_BACK_SELECTORS: readonly ['[data-act="request-changes"]', '[data-act="revise"]', 'a.result-feedback-link[href="#request-changes"]'];
31
+ /** Retry: the result's Retry, or the failed box's own Retry form. */
32
+ export declare const RETRY_SELECTORS: readonly ['[data-act="retry"]', 'form[data-retry] button[type="submit"]', 'form[data-rebuild] button[type="submit"]'];
33
+ export declare const SHORTCUTS: readonly Shortcut[];
34
+ /** The keys typed, as the help list shows them: "g h" reads "g then h". */
35
+ export declare const keyWords: (keys: string) => string;
36
+ /**
37
+ * The console's key routing, as an inline script (it runs inside chromeScript's closure, which defines `show` for the
38
+ * jump-to palette, `toggleHelp`, `helpOpen`, `close` and `go`). Generated from SHORTCUTS, so the table is the only
39
+ * place a key is defined.
40
+ */
41
+ export declare function webKeymapScript(): string;
package/dist/keymap.js ADDED
@@ -0,0 +1,51 @@
1
+ /** One keymap for the console and the terminal UI (docs/plans/plain-toolroll.md P1.7). A shortcut only ever presses a
2
+ * control the page already rendered, the same press a person's click makes, so any password field or confirm it opens
3
+ * still appears; with no such control on the page the key does nothing. Shortcuts sleep in text fields, with a
4
+ * modifier held, and while the shortcuts list is open. Navigation keys go to a page and post nothing. */
5
+ /** The page's ink act: Approve (the approve control itself, password popover and all) or Accept. */
6
+ export const INK_ACT_SELECTORS = ["[data-ink-approve]", '[data-ink-act="accept"]', "[data-accept-result][data-primary-action]"];
7
+ /** Send back: the control that opens the Send back form (never its submit: the note comes first). */
8
+ export const SEND_BACK_SELECTORS = ['[data-act="request-changes"]', '[data-act="revise"]', 'a.result-feedback-link[href="#request-changes"]'];
9
+ /** Retry: the result's Retry, or the failed box's own Retry form. */
10
+ export const RETRY_SELECTORS = ['[data-act="retry"]', 'form[data-retry] button[type="submit"]', 'form[data-rebuild] button[type="submit"]'];
11
+ export const SHORTCUTS = [
12
+ { keys: "n", says: "new task", web: { kind: "go", href: "/tasks/new" }, tui: "new" },
13
+ { keys: "a", says: "Approve or Accept (presses the button on the page)", web: { kind: "press", selectors: INK_ACT_SELECTORS }, tui: "ink" },
14
+ { keys: "b", says: "Send back (opens its note)", web: { kind: "press", selectors: SEND_BACK_SELECTORS }, tui: "send-back" },
15
+ { keys: "r", says: "Retry (presses the button on the page)", web: { kind: "press", selectors: RETRY_SELECTORS }, tui: "retry" },
16
+ { keys: "/", says: "search", web: { kind: "search" }, tui: { none: "The terminal has no search; press w to open the console." } },
17
+ { keys: "g h", says: "go Home", web: { kind: "go", href: "/" }, tui: "list" },
18
+ { keys: "g t", says: "go to Tasks", web: { kind: "go", href: "/tasks" }, tui: "list" },
19
+ { keys: "?", says: "these shortcuts", web: { kind: "help" }, tui: "help" },
20
+ ];
21
+ /** The keys typed, as the help list shows them: "g h" reads "g then h". */
22
+ export const keyWords = (keys) => keys.split(" ").join(" then ");
23
+ /**
24
+ * The console's key routing, as an inline script (it runs inside chromeScript's closure, which defines `show` for the
25
+ * jump-to palette, `toggleHelp`, `helpOpen`, `close` and `go`). Generated from SHORTCUTS, so the table is the only
26
+ * place a key is defined.
27
+ */
28
+ export function webKeymapScript() {
29
+ const single = {}, after = {};
30
+ for (const one of SHORTCUTS) {
31
+ const parts = one.keys.split(" ");
32
+ if (parts.length === 1)
33
+ single[parts[0]] = one.web;
34
+ else if (parts[0] === "g")
35
+ after[parts[1]] = one.web;
36
+ }
37
+ return `var KEYS=${JSON.stringify(single)},GKEYS=${JSON.stringify(after)};` +
38
+ // A control a person could press right now: rendered, shown, and not disabled.
39
+ `function pressable(el){if(!el||el.disabled||el.getAttribute("aria-disabled")==="true")return false;` +
40
+ `if(el.closest("[hidden],[inert]"))return false;var r=el.getClientRects();return r.length>0;}` +
41
+ // Press it exactly as a click would; a submit whose form isn't complete (an empty password, a required reason)
42
+ // shows the browser's own prompt for it instead of posting.
43
+ `function press(selectors){for(var s=0;s<selectors.length;s++){var all=document.querySelectorAll(selectors[s]);` +
44
+ `for(var i=0;i<all.length;i++){var el=all[i];if(!pressable(el))continue;` +
45
+ `var form=el.form||null;var submits=el.tagName.toLowerCase()==="button"&&(el.type==="submit"||el.getAttribute("type")===null)&&form;` +
46
+ `if(submits&&!form.checkValidity()){form.reportValidity();return true;}` +
47
+ `el.click();return true;}}return false;}` +
48
+ `function search(){if(document.querySelector("[data-workspace-shell]")){window.dispatchEvent(new CustomEvent("so:open-search",{detail:document.activeElement}));return;}show();}` +
49
+ `function run(act){if(act.kind==="go"){go(act.href);return true;}if(act.kind==="press")return press(act.selectors);` +
50
+ `if(act.kind==="search"){search();return true;}if(act.kind==="help"){toggleHelp();return true;}return false;}`;
51
+ }
@@ -112,7 +112,7 @@ export async function runKnowledgeCommand(positional, options, context) {
112
112
  admittedToken = token;
113
113
  }
114
114
  const cacheRoot = context.evidenceRoot ? join(dirname(context.evidenceRoot), 'repository-context') : namedPath(join(homedir(), '.cache'), ['repository-context']);
115
- const result = await repositoryContext({ repo: resolve(repo), query, mode: spec.action === 'impact' ? 'impact' : 'search', refresh: spec.action === 'refresh', cacheRoot, ...(typeof base === 'string' ? { baseRevision: base } : {}) });
115
+ const result = await repositoryContext({ repo: resolve(repo), ...(admittedToken ? { project: resolve(repo) } : {}), query, mode: spec.action === 'impact' ? 'impact' : 'search', refresh: spec.action === 'refresh', cacheRoot, ...(typeof base === 'string' ? { baseRevision: base } : {}) });
116
116
  if (admittedToken) {
117
117
  const current = authenticateCoordinator(context.store, admittedToken);
118
118
  if (!current.ok)
@@ -1,17 +1,18 @@
1
1
  import { type KnowledgeDraft, type KnowledgeSelection, type KnowledgeView } from './project-knowledge.js';
2
2
  import { type Html } from './html.js';
3
+ import { type MemoryUse } from './memory-use.js';
3
4
  /** A section as a frame (after ReUI's Frame, MIT): its heading on the raised well, its body one paper panel. The
4
5
  * cards inside become hairline-divided rows of that panel, never cards in a card. */
5
6
  export declare function framedSection(heading: string, body: Html | string, extra?: string): Html;
6
7
  export declare const KNOWLEDGE_CSS = ".knowledge{max-width:780px;min-width:0;overflow-wrap:anywhere}.knowledge .card{padding:20px;min-width:0;margin:16px 0}.knowledge h2{margin:0 0 12px}.knowledge form{display:grid;gap:12px;margin:0}.knowledge label{display:grid;gap:6px}.knowledge textarea,.knowledge input,.knowledge select{box-sizing:border-box;width:100%;min-width:0;max-width:100%}.knowledge textarea{resize:vertical;min-height:110px}.knowledge button,.knowledge summary,.knowledge .button-link{min-height:44px}.knowledge button{justify-self:start;white-space:nowrap}.knowledge summary{padding:12px 0;cursor:pointer}.knowledge pre{white-space:pre-wrap;overflow-wrap:anywhere;font:inherit}.knowledge .knowledge-actions{display:flex;gap:12px;flex-wrap:wrap}.knowledge .meta{font-size:.86rem}.knowledge .knowledge-source{display:block}.knowledge details+details{border-top:1px solid var(--so-line)}.memory-stats{display:flex;flex-wrap:wrap;gap:.4rem}.memory-stats span+span::before{content:\"\u00B7\";margin-right:.4rem}.knowledge.frame{margin:0 0 16px}.knowledge.frame>.frame-head h2,.knowledge.frame>summary.frame-head{margin:0;font-size:13px;font-weight:600;line-height:1.4}.knowledge.frame>summary.frame-head{min-height:44px;padding:4px 10px}.knowledge.frame>.frame-panel{min-width:0}.knowledge.frame>.frame-panel>:first-child{margin-top:0}.knowledge.frame>.frame-panel>:last-child{margin-bottom:0}.knowledge .frame-panel>details.card,.knowledge .frame-panel .learning .card{margin:0;padding:0;border:0;border-radius:0;background:none;box-shadow:none}.knowledge .frame-panel>details.card+details.card,.knowledge .frame-panel .learning .card+.card{border-top:1px solid var(--so-line);padding-top:4px}.knowledge.frame>summary.frame-head{justify-content:flex-start}.knowledge form.field-row{grid-template-columns:minmax(0,1fr) auto;align-items:end}.knowledge form.field-row>label,.knowledge form.field-row :is(input,select,button){margin:0}@media(max-width:600px){.knowledge .card{padding:16px}.knowledge h2{font-size:1.1rem}}";
7
- export declare function knowledgeHtml(view: KnowledgeView, csrf: string, canManage: boolean, draft?: KnowledgeDraft, error?: string): Html;
8
+ export declare function knowledgeHtml(view: KnowledgeView, csrf: string, canManage: boolean, draft?: KnowledgeDraft, error?: string, use?: ReadonlyMap<string, MemoryUse>): Html;
8
9
  export declare function knowledgeContextHtml(snapshot: KnowledgeSelection | null): Html | '';
9
10
  import type { Decision, MemoryHit } from './project-memory.js';
10
- export declare const MEMORY_INTRO = "What the lead and crew read before they work. Every change keeps its history.";
11
+ export declare const MEMORY_INTRO = "What the lead and crew read before they work. Every change keeps its history. Last included is counted in runs.";
11
12
  export declare function decisionsHtml(repo: string, decisions: Decision[], csrf: string, canManage: boolean, draft?: {
12
13
  claim?: string;
13
14
  why?: string;
14
- }, error?: string): Html;
15
+ }, error?: string, use?: ReadonlyMap<string, MemoryUse>, afterwards?: (decision: Decision) => string | null): Html;
15
16
  export declare function memorySearchHtml(repo: string, query: string, hits: MemoryHit[] | null): Html;
16
17
  import type { MemoryProposal } from './memory-pass.js';
17
18
  export declare function proposalsHtml(repo: string, proposals: MemoryProposal[], status: {
@@ -25,4 +26,20 @@ export declare function proposalsHtml(repo: string, proposals: MemoryProposal[],
25
26
  };
26
27
  openGaps: number;
27
28
  pending: number;
28
- } | null, csrf: string, canManage: boolean): Html;
29
+ } | null, csrf: string, canManage: boolean, who?: string): Html;
30
+ import type { TidyProposal } from './memory-tidy.js';
31
+ export declare function cleanUpHtml(repo: string, proposals: readonly TidyProposal[], manage: boolean): Html;
32
+ /** On a saved version's page: the references that aren't in current knowledge, each back as a new version. */
33
+ export declare function referenceRestoreHtml(repo: string, revision: number, references: readonly {
34
+ id: string;
35
+ title: string;
36
+ }[], manage: boolean): Html | '';
37
+ /** Retired decisions, undoable merges and disabled lessons, each with its way back. */
38
+ export declare function restoreHtml(repo: string, retired: readonly Decision[], merges: readonly {
39
+ merge: number;
40
+ successor: number;
41
+ predecessors: number[];
42
+ }[], lessons: readonly {
43
+ id: number;
44
+ observation: string;
45
+ }[], manage: boolean): Html | '';