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
@@ -22,7 +22,7 @@ export function homeSentence(needs, perProject, scope, any) {
22
22
  const waiting = perProject.filter(one => one.needs > 0);
23
23
  return waiting.length === 1 ? `${needs} ${verb} you in ${waiting[0].name}.` : `${needs} ${verb} you across ${waiting.length} projects.`;
24
24
  }
25
- function rowOf(store, item, now, multi, canAnswer) {
25
+ function rowOf(store, item, now, multi, canAnswer, closedDoor) {
26
26
  const action = item.primaryAction;
27
27
  const href = action?.code === "open-result" && action.target.runId != null
28
28
  ? `/review?result=${encodeURIComponent(action.target.taskId)}&run=${action.target.runId}` : browserWorkActionHref(item);
@@ -41,11 +41,14 @@ function rowOf(store, item, now, multi, canAnswer) {
41
41
  const line = waits
42
42
  ? (open ? decision.question : item.status.detail !== item.status.label ? item.status.detail : null)
43
43
  : item.progress ?? (item.status.detail !== item.status.label ? item.status.detail : null);
44
+ // A plan that can't take a yes never offers one: the reason, and the one act that opens it, as the task page says.
45
+ const door = action?.code === "approve-scope" ? closedDoor(action.target.taskId) : null;
44
46
  return {
45
47
  id: item.rootId, title: item.title, href: taskHref(item.rootId),
46
48
  project: multi || item.repo === null ? (item.repo === null ? "Unplaced" : projectName(item.repo)) : null,
47
- age: relativeAge(item.updatedAt, now), chip: { word, tone }, line,
48
- action: href === null || action === null ? null : { label: action.label, href }, question,
49
+ age: relativeAge(item.updatedAt, now), chip: { word, tone }, line: door === null ? line : door.title,
50
+ action: door !== null ? { label: door.road === "agents" ? "Change the agents" : "Edit the plan", href: `${taskHref(action.target.taskId)}#${door.road === "agents" ? "agents" : "scope"}` }
51
+ : href === null || action === null ? null : { label: action.label, href }, question,
49
52
  };
50
53
  }
51
54
  /** Home's view for one person and scope, from two reads of the work index (what waits, what runs). */
@@ -54,26 +57,28 @@ export function homeView(facts) {
54
57
  const read = (view, limit) => workIndexPage(store, now, access, { view, limit, project, ...(facts.evidenceRoot === undefined ? {} : { root: facts.evidenceRoot }) });
55
58
  const needs = read("needs-you", HOME_NEEDS_SHOWN);
56
59
  const running = read("running", HOME_RUNNING_SHOWN);
60
+ // Waiting (docs/plans/plain-toolroll.md P1.4): what Toolroll or another task moves on, with no person asked.
61
+ const waiting = workIndexPage(store, now, access, { view: "queued", limit: HOME_RUNNING_SHOWN, project, ...(facts.evidenceRoot === undefined ? {} : { root: facts.evidenceRoot }) });
57
62
  const totals = needs.totals;
58
63
  // Queued: the Tasks list's own Queued count (the same predicate its view=queued filter lists).
59
64
  const queued = totals.queued;
60
- // Every project is said by name (all=1): /work alone would narrow to the session's open project.
61
- const scopeQuery = project === null ? "all=1&" : `project=${encodeURIComponent(project)}&`;
65
+ // Every project is said by name (scope=all): /tasks alone would narrow to the session's open project.
66
+ const scopeQuery = project === null ? "scope=all&" : `project=${encodeURIComponent(project)}&`;
62
67
  const counts = [
63
- { key: "needs", label: "Needs you", value: totals["needs-you"], href: `/work?${scopeQuery}view=needs-you` },
64
- { key: "running", label: "Running", value: totals.running, href: `/work?${scopeQuery}view=running` },
65
- { key: "queued", label: "Queued", value: queued, href: `/work?${scopeQuery}view=queued` },
66
- { key: "finished", label: "Finished", value: totals.completed, href: `/work?${scopeQuery}view=completed` },
68
+ { key: "needs", label: "Needs you", value: totals["needs-you"], href: `/tasks?${scopeQuery}view=needs-you` },
69
+ { key: "running", label: "Running", value: totals.running, href: `/tasks?${scopeQuery}view=running` },
70
+ { key: "queued", label: "Queued", value: queued, href: `/tasks?${scopeQuery}view=queued` },
71
+ { key: "finished", label: "Finished", value: totals.completed, href: `/tasks?${scopeQuery}view=completed` },
67
72
  ];
68
73
  const multi = project === null && new Set([...needs.items, ...running.items].map(one => one.repo ?? "")).size > 1;
69
74
  const groups = [];
70
75
  for (const ask of ASKS) {
71
- const rows = needs.items.filter(one => one.ask === ask).map(one => rowOf(store, one, now, multi, facts.canAnswer));
76
+ const rows = needs.items.filter(one => one.ask === ask).map(one => rowOf(store, one, now, multi, facts.canAnswer, facts.closedDoor));
72
77
  if (rows.length > 0)
73
78
  groups.push({ key: ask, label: ASK_LABEL[ask], rows });
74
79
  }
75
80
  // A waiting row whose ask the index couldn't name still waits on this person: it goes under Decide, never lost.
76
- const unnamed = needs.items.filter(one => one.ask === null).map(one => rowOf(store, one, now, multi, facts.canAnswer));
81
+ const unnamed = needs.items.filter(one => one.ask === null).map(one => rowOf(store, one, now, multi, facts.canAnswer, facts.closedDoor));
77
82
  if (unnamed.length > 0) {
78
83
  const decide = groups.find(one => one.key === "decide");
79
84
  if (decide)
@@ -81,12 +86,15 @@ export function homeView(facts) {
81
86
  else
82
87
  groups.unshift({ key: "decide", label: ASK_LABEL.decide, rows: unnamed });
83
88
  }
84
- const live = running.items.filter(one => !one.status.views.includes("needs-you")).map(one => rowOf(store, one, now, multi, facts.canAnswer));
89
+ const live = running.items.filter(one => !one.status.views.includes("needs-you")).map(one => rowOf(store, one, now, multi, facts.canAnswer, facts.closedDoor));
85
90
  if (live.length > 0)
86
91
  groups.push({ key: "running", label: "Running", rows: live });
92
+ const waits = waiting.items.filter(one => one.status.label === "Waiting" && !one.status.views.includes("needs-you")).map(one => rowOf(store, one, now, multi, facts.canAnswer, facts.closedDoor));
93
+ if (waits.length > 0)
94
+ groups.push({ key: "waiting", label: "Waiting", rows: waits });
87
95
  const perProject = needs.projects.map(one => ({ name: one.repo === null ? "Unplaced" : projectName(one.repo), needs: one.totals["needs-you"] }));
88
96
  const any = totals.all > 0 || totals.completed > 0 || (totals.archived ?? 0) > 0;
89
- const shown = groups.filter(one => one.key !== "running").reduce((sum, one) => sum + one.rows.length, 0);
97
+ const shown = groups.filter(one => one.key !== "running" && one.key !== "waiting").reduce((sum, one) => sum + one.rows.length, 0);
90
98
  return {
91
99
  kind: "home",
92
100
  say: homeSentence(totals["needs-you"], perProject, project === null ? null : projectName(project), any),
@@ -167,7 +167,7 @@ export function safeReturn(raw) {
167
167
  * button can name. Nothing here is a second redirect framework: one
168
168
  * same-site path, or "/".
169
169
  */
170
- export const LOGIN_RETURN_PAGES = /^\/(oauth\/authorize|t\/[^/]+|r\/[1-9]\d*|code(?:\/[a-f0-9]{32}(?:\/ship)?)?|review|chat|work|board|projects|routines|recipes|fleet|settings(\/[a-z-]+)?|mode)$/;
170
+ export const LOGIN_RETURN_PAGES = /^\/(oauth\/authorize|t\/[^/]+|r\/[1-9]\d*|code(?:\/[a-f0-9]{32}(?:\/ship)?)?|review|chat|tasks|work|board|projects|routines|recipes|fleet|settings(\/[a-z-]+)?|mode)$/;
171
171
  export function loginReturn(raw) {
172
172
  const safe = safeReturn(raw);
173
173
  if (safe === "/")
@@ -223,7 +223,7 @@ export const card = (options) => html `<a class="k-card" href="${options.href}">
223
223
  export function notFound(path, options = {}) {
224
224
  return html `${pageHeader("Not found")}<div class="k-nf">${empty({
225
225
  title: options.title ?? NOT_FOUND.title(path), text: options.text ?? NOT_FOUND.text,
226
- action: acts(btn("Go to Home", { sm: true, href: "/" }), btn("Open Tasks", { sm: true, variant: "ghost", href: "/work" })),
226
+ action: acts(btn("Go to Home", { sm: true, href: "/" }), btn("Open Tasks", { sm: true, variant: "ghost", href: "/tasks" })),
227
227
  })}</div>`;
228
228
  }
229
229
  /** The Settings frame: Overview and the eight groups, a desk breadcrumb, a phone back header, one column. */
@@ -0,0 +1,10 @@
1
+ import type { AssignmentSnapshot } from "../assignment.js";
2
+ import type { MergeReadyView } from "../browser-workspace.js";
3
+ import type { Store } from "../store.js";
4
+ import type { Who } from "./session.js";
5
+ /** The panel for a task page, or null when there is no result to merge yet. */
6
+ export declare function mergeReadyViewOf(store: Store, taskId: string, assignment: AssignmentSnapshot | null | undefined, who: Who, runChecks: {
7
+ action: string;
8
+ level: "quick" | "full";
9
+ returnTo: string;
10
+ } | null, returnTo: string): MergeReadyView | null;
@@ -0,0 +1,30 @@
1
+ /** The task page's "Can I merge?" panel (docs/plans/plain-toolroll.md P1.6): the checklist (merge-checklist.ts) with
2
+ * each blocker's act as the form this page already posts for it. Run checks is the Checks row's own post; Retry is the
3
+ * failed box's own requeue (the page shows it only where that box would); Ask the agent to fix is Send back: the same
4
+ * post to /r/<build>/comment with intent=revise that the result's Send back makes, its note prefilled with the blocker
5
+ * and editable, sealing the same saved notes. Only a signed-in approver gets the forms; anyone else reads the rows. */
6
+ import { randomBytes } from "node:crypto";
7
+ import { checklistFromAssignment } from "../merge-checklist.js";
8
+ import { isRevisionFeedback, revisionBatchOf, revisionSourceOf } from "../result-review.js";
9
+ /** The panel for a task page, or null when there is no result to merge yet. */
10
+ export function mergeReadyViewOf(store, taskId, assignment, who, runChecks, returnTo) {
11
+ if (assignment == null || assignment.receipt === null)
12
+ return null;
13
+ const list = checklistFromAssignment(store, taskId, assignment);
14
+ const person = who.via === "cookie" && who.role === "approver" && !store.isDemo();
15
+ const receipt = assignment.receipt;
16
+ const notes = store.liveDiffComments(receipt.runId).filter(one => isRevisionFeedback(one));
17
+ return {
18
+ answer: list.answer, sentence: list.sentence,
19
+ rows: list.rows.filter(one => one.mark !== "skip").map(one => {
20
+ const act = one.act;
21
+ const form = !person || act === null ? null
22
+ : act.kind === "run-checks" ? runChecks === null || runChecks.action !== `/r/${act.run}/checks` ? null : { kind: "run-checks", ...runChecks }
23
+ : act.kind === "retry" ? { kind: "retry" }
24
+ : act.run !== receipt.runId ? null
25
+ : { kind: "fix", action: `/r/${act.run}/comment`, note: act.note, batch: revisionBatchOf(notes), source: revisionSourceOf(store.getScope(receipt.taskId)?.digest ?? null),
26
+ request: randomBytes(16).toString("hex"), returnTo };
27
+ return { key: one.key, label: one.label, mark: one.mark, say: one.say, act: form };
28
+ }),
29
+ };
30
+ }
@@ -3,6 +3,7 @@
3
3
  * typefaces, declared in the route table's edge stage and answered before authentication.
4
4
  */
5
5
  import type { ServerResponse } from "node:http";
6
+ export declare const PWA_WORKER = "// Toolroll \u2014 push only; deliberately NO fetch handler (no offline cache of an authenticated console).\n// Every shape an older worker took, kept for good, then Home, Tasks' Ready and Board views and a task by its number.\nconst SHAPES = [/^\\/next$/, /^\\/review$/, /^\\/system$/, /^\\/routines$/, /^\\/routines\\/[0-9]+$/, /^\\/d\\/[0-9]+$/, /^\\/r\\/[0-9]+$/, /^\\/approve\\/[0-9]+$/,\n /^\\/$/, /^\\/tasks\\?view=ready$/, /^\\/tasks\\?view=board$/, /^\\/n\\/[1-9][0-9]{0,14}$/];\nself.addEventListener(\"push\", function (event) {\n var data = {};\n try { data = event.data ? event.data.json() : {}; } catch (e) {}\n var body = typeof data.body === \"string\" ? data.body : \"the console needs you\";\n var tag = typeof data.tag === \"string\" ? data.tag : \"toolroll\";\n var url = typeof data.url === \"string\" && SHAPES.some(function (s) { return s.test(data.url); }) ? data.url : \"/\";\n if (typeof data.waiting === \"number\" && data.waiting >= 0 && self.registration.setAppBadge) {\n // A count, never content. Honest no-op where unsupported.\n event.waitUntil(self.registration.setAppBadge(Math.floor(data.waiting)).catch(function () {}));\n }\n event.waitUntil(self.registration.showNotification(\"Toolroll\", { body: body, tag: tag, data: { url: url }, actions: [{ action: \"open\", title: \"Open\" }] }));\n});\nself.addEventListener(\"message\", function (event) {\n // The page recomputes on load/focus and is authoritative over any stale\n // push: {badge: N} sets, {badge: 0} clears.\n var badge = event.data && typeof event.data.badge === \"number\" ? event.data.badge : null;\n if (badge === null || !self.registration.setAppBadge) return;\n if (badge <= 0 && self.registration.clearAppBadge) { self.registration.clearAppBadge().catch(function () {}); return; }\n self.registration.setAppBadge(Math.floor(badge)).catch(function () {});\n});\n// A tap on the ping and its one \"Open\" action land in the same place.\nself.addEventListener(\"notificationclick\", function (event) {\n event.notification.close();\n if (self.registration.clearAppBadge) self.registration.clearAppBadge().catch(function () {});\n var url = event.notification.data && event.notification.data.url;\n var target = new URL(SHAPES.some(function (s) { return s.test(url); }) ? url : \"/\", self.location.origin).href;\n event.waitUntil(clients.matchAll({ type: \"window\" }).then(function (open) {\n for (var i = 0; i < open.length; i++) { if (open[i].url === target && open[i].focus) return open[i].focus(); }\n return clients.openWindow(target);\n }));\n});\n";
6
7
  /**
7
8
  * Answers one install asset or typeface (GET only; the caller checks the method), with nosniff and its own
8
9
  * conservative caching/CSP. False for any other path: unknown names fall through to the router's refusal.
@@ -38,19 +38,21 @@ const PWA_MANIFEST = JSON.stringify({
38
38
  // and the tap, only. notificationclick resolves ONLY allow-listed relative
39
39
  // paths — the payload URL is data, revalidated, never handed raw to the
40
40
  // browser (arc 3 finding 5).
41
- const PWA_WORKER = `// Toolroll — push only; deliberately NO fetch handler (no offline cache of an authenticated console).
42
- const SHAPES = [/^\\/next$/, /^\\/review$/, /^\\/system$/, /^\\/routines$/, /^\\/routines\\/[0-9]+$/, /^\\/d\\/[0-9]+$/, /^\\/r\\/[0-9]+$/, /^\\/approve\\/[0-9]+$/];
41
+ export const PWA_WORKER = `// Toolroll — push only; deliberately NO fetch handler (no offline cache of an authenticated console).
42
+ // Every shape an older worker took, kept for good, then Home, Tasks' Ready and Board views and a task by its number.
43
+ const SHAPES = [/^\\/next$/, /^\\/review$/, /^\\/system$/, /^\\/routines$/, /^\\/routines\\/[0-9]+$/, /^\\/d\\/[0-9]+$/, /^\\/r\\/[0-9]+$/, /^\\/approve\\/[0-9]+$/,
44
+ /^\\/$/, /^\\/tasks\\?view=ready$/, /^\\/tasks\\?view=board$/, /^\\/n\\/[1-9][0-9]{0,14}$/];
43
45
  self.addEventListener("push", function (event) {
44
46
  var data = {};
45
47
  try { data = event.data ? event.data.json() : {}; } catch (e) {}
46
48
  var body = typeof data.body === "string" ? data.body : "the console needs you";
47
49
  var tag = typeof data.tag === "string" ? data.tag : "toolroll";
48
- var url = typeof data.url === "string" && SHAPES.some(function (s) { return s.test(data.url); }) ? data.url : "/next";
50
+ var url = typeof data.url === "string" && SHAPES.some(function (s) { return s.test(data.url); }) ? data.url : "/";
49
51
  if (typeof data.waiting === "number" && data.waiting >= 0 && self.registration.setAppBadge) {
50
52
  // A count, never content. Honest no-op where unsupported.
51
53
  event.waitUntil(self.registration.setAppBadge(Math.floor(data.waiting)).catch(function () {}));
52
54
  }
53
- event.waitUntil(self.registration.showNotification("Toolroll", { body: body, tag: tag, data: { url: url } }));
55
+ event.waitUntil(self.registration.showNotification("Toolroll", { body: body, tag: tag, data: { url: url }, actions: [{ action: "open", title: "Open" }] }));
54
56
  });
55
57
  self.addEventListener("message", function (event) {
56
58
  // The page recomputes on load/focus and is authoritative over any stale
@@ -60,11 +62,12 @@ self.addEventListener("message", function (event) {
60
62
  if (badge <= 0 && self.registration.clearAppBadge) { self.registration.clearAppBadge().catch(function () {}); return; }
61
63
  self.registration.setAppBadge(Math.floor(badge)).catch(function () {});
62
64
  });
65
+ // A tap on the ping and its one "Open" action land in the same place.
63
66
  self.addEventListener("notificationclick", function (event) {
64
67
  event.notification.close();
65
68
  if (self.registration.clearAppBadge) self.registration.clearAppBadge().catch(function () {});
66
69
  var url = event.notification.data && event.notification.data.url;
67
- var target = new URL(SHAPES.some(function (s) { return s.test(url); }) ? url : "/next", self.location.origin).href;
70
+ var target = new URL(SHAPES.some(function (s) { return s.test(url); }) ? url : "/", self.location.origin).href;
68
71
  event.waitUntil(clients.matchAll({ type: "window" }).then(function (open) {
69
72
  for (var i = 0; i < open.length; i++) { if (open[i].url === target && open[i].focus) return open[i].focus(); }
70
73
  return clients.openWindow(target);
@@ -52,14 +52,21 @@ import { refuse, regionScript, relativeAge as relativeAgeOf, screen, settingsRow
52
52
  import { redirect, respond, safeReturn, SAFETY, taskHref } from "./http.js";
53
53
  import { assignmentOf } from "../assignment.js";
54
54
  import { assignmentTaskStatusOf } from "../assignment-presentation.js";
55
- import { githubIdentityOf, homePage, portfolioOverview, startBuilderHtml, workbenchRail } from "./render-pages.js";
55
+ import { githubIdentityOf, homePage, startBuilderHtml } from "./render-pages.js";
56
+ import { retiredRedirect } from "./retired-routes.js";
57
+ import { codeMapStatus, openMap } from "../code-map-read.js";
58
+ import { requestRefresh } from "../code-map-store.js";
59
+ import { codeMapReport } from "../code-map-report.js";
60
+ import { codeMapPage, codeMapReportBlock } from "./render-code-map.js";
61
+ import { FLAKY_WINDOW_DAYS, flakyList, flakyShow } from "../flaky.js";
62
+ import { flakyPage, flakyRowPage } from "./render-flaky.js";
56
63
  import { browsePage, githubReposPage, projectHref, projectPage, projectsPage, unknownProject } from "./render-projects.js";
57
64
  import { notFound as kitNotFound } from "./kit.js";
58
65
  import {} from "./render-pages.js";
59
66
  import {} from "../project-outcomes.js";
60
67
  import { CHECK_LEVEL_WORDS } from "../check-levels.js";
61
68
  import { capsPage } from "./render-settings.js";
62
- import { decisionAnswerScript, taskBody } from "./render-tasks.js";
69
+ import { consentDoorOf, decisionAnswerScript, taskBody } from "./render-tasks.js";
63
70
  import {} from "./session.js";
64
71
  import { rereadBrowser } from "./live.js";
65
72
  import { homeView } from "./home.js";
@@ -185,16 +192,21 @@ export function createPagesHandlers(runtime) {
185
192
  async function homeRedirect(ctx) {
186
193
  const { who, response, now, project } = ctx;
187
194
  if (who.via !== "cookie" || restricted())
188
- return redirect(response, "/work");
195
+ return redirect(response, "/tasks");
189
196
  if (firstRunStepsNow(now) !== null && !store.hasAnyWork())
190
197
  return redirect(response, "/chat");
191
198
  // One scope for the page and the chrome around it: the project its address names (proved by the router), else all.
192
199
  const chrome = chromeFor(project, "none");
193
200
  const projects = chrome.projects ?? [];
194
- const view = { ...homeView({ store, now, access: workAccess(), evidenceRoot, project, projects, chat: chrome.chat === true, canAnswer: who.role === "approver" }),
201
+ const closedDoor = (taskId) => {
202
+ const scope = store.getScope(taskId);
203
+ const door = consentDoorOf(scope, runtime.routeViewOf(taskId, store.lookupRef(taskId), scope, now, who));
204
+ return door.open ? null : door;
205
+ };
206
+ const view = { ...homeView({ store, now, access: workAccess(), evidenceRoot, project, projects, chat: chrome.chat === true, canAnswer: who.role === "approver", closedDoor }),
195
207
  // Until the first result: the welcome's three steps stay one link away.
196
208
  setup: firstRunStepsNow(now) !== null ? "/welcome" : null };
197
- return sendScreen(response, 200, screen("Home", html `<div data-kit-page><h1>Home</h1><p>${view.say}</p><p><a href="/work">Tasks</a></p></div>`, { chrome, workspace: { view } }));
209
+ return sendScreen(response, 200, screen("Home", html `<div data-kit-page><h1>Home</h1><p>${view.say}</p><p><a href="/tasks">Tasks</a></p></div>`, { chrome, workspace: { view } }));
198
210
  }
199
211
  // Search (UI revamp wave B): the ⌘K menu's "See all results". The places come from the page; the tasks here are the
200
212
  // most recent ones in view (live and archived), matched on their title and id. Reads only.
@@ -227,76 +239,6 @@ export function createPagesHandlers(runtime) {
227
239
  response.end();
228
240
  return;
229
241
  }
230
- async function workbench(ctx) {
231
- const { url, who, response, now, project } = ctx;
232
- // The fleet under one gaze (attended A1): the rail rolls up the whole
233
- // ceiling regardless of the open project — watching is cross-project
234
- // by nature; acting happens on task screens that re-prove everything.
235
- const admission = admissionList();
236
- const snapshot = store.boardScoped(null, now, 200, admission);
237
- const rows = snapshot.tasks.filter(facts => facts.repo === null || visible(facts.repo));
238
- const cards = rows.map(facts => classify(facts.blockerRepo !== null && !visible(facts.blockerRepo) ? { ...facts, blockerState: null } : facts, now));
239
- const attention = cards.filter(card => card.lane === "attention").slice(0, 100);
240
- const building = cards.filter(card => card.lane === "building");
241
- const waiting = cards.filter(card => card.lane === "waiting");
242
- const queued = cards.filter(card => card.lane === "queued");
243
- const done = snapshot.done
244
- .filter(one => one.repo === null || visible(one.repo))
245
- .slice(0, 5)
246
- .map(one => ({ taskId: one.taskId, title: one.title, outcome: one.outcome, repo: one.repo }));
247
- const selected = url.searchParams.get("t");
248
- const rail = workbenchRail({ attention, building, waiting, queued, done, selected, saturated: snapshot.saturated });
249
- if (url.searchParams.get("fragment") === "rail") {
250
- // The rail alone: same auth, same ceiling, no shell, no scripts.
251
- return respond(response, 200, "text/html; charset=utf-8", rail);
252
- }
253
- // The portfolio overview (arc slice 1a). All-scope hygiene throughout:
254
- // admission binds inside every bounded SQL read, and every unlimited
255
- // feed's rows pass visible() BEFORE they render or tally — a hidden
256
- // project must not leak into a row, a count, or a dollar.
257
- const sinceIso = new Date(now.getTime() - 24 * 3_600_000).toISOString();
258
- const decisions = store
259
- .listDecisionsScoped(null)
260
- .filter(one => visible(one.repo ?? null))
261
- .slice(0, 10);
262
- const approvals = store.scopesAwaitingApproval(null, 10, admission).filter(one => visible(one.repo ?? null));
263
- const requeueables = store.listRequeueablesScoped(null, now, 10, admission).filter(one => visible(one.repo ?? null));
264
- const cancelledBlockers = store
265
- .listCancelledBlockersScoped(null, 10, admission)
266
- .filter(one => visible(one.repo ?? null) && visible(one.blockerRepo ?? null));
267
- const runs24 = store.runsSinceScoped(sinceIso, null).filter(run => visible(taskRepoOf(run.taskRef)));
268
- const live = store.liveClaims(null, now).filter(one => visible(one.repo));
269
- const ledger = store
270
- .portfolioLedgerScoped(null, sinceIso, 30, admission)
271
- .filter(row => visible(row.repo));
272
- // Gaps stay project-relative (the roll-up inbox's rule): computed for
273
- // the OPEN project only — there is no scope-safe road to another
274
- // project's /caps.
275
- const gaps = project === null ? [] : computeGaps(store, project, now).filter(gap => gap.unblocks.length > 0).slice(0, 10);
276
- const csrf = who.via === "cookie" ? who.session.csrf : "";
277
- let detail = html `${portfolioOverview({
278
- attention, building, waiting, queued, done, saturated: snapshot.saturated,
279
- decisions, approvals, requeueables, cancelledBlockers, gaps,
280
- gapsProject: project, runs24, live, ledger, csrf, now,
281
- })}<div class="workbench-mobile-rail">${rail}</div>`;
282
- if (selected !== null) {
283
- const view = taskViewData(selected, who, null);
284
- detail = view === null
285
- ? html `<p class="workbench-mobile-back"><a href="/workbench">← all work</a></p><div class="card"><p class="meta">No such task — it may have been outside this console's view</p></div>`
286
- : html `<p class="workbench-mobile-back"><a href="/workbench">← all work</a></p>${taskBody({ ...view, degraded: "pane" })}`;
287
- }
288
- return sendScreen(response, 200, screen("portfolio", detail, {
289
- chrome: chromeFor(project, "workbench", html `<div id="wb-rail">${rail}</div><p class="meta" id="wb-rail-stamp"></p>`, "all"),
290
- functional: {
291
- // The decision enhancement rides ONLY the overview: a selected
292
- // task's pane may carry a password ceremony, and sensitive
293
- // pages gain no new scripts (commit-1 review, finding 1).
294
- script: regionScript("wb-rail", "rail") +
295
- (selected === null && decisions.length > 0 ? decisionAnswerScript() : ""),
296
- fetches: true,
297
- },
298
- }));
299
- }
300
242
  async function ledgerPage(ctx) {
301
243
  const { url, who, response, now } = ctx;
302
244
  const admitted = admissionList();
@@ -971,11 +913,12 @@ export function createPagesHandlers(runtime) {
971
913
  * switches the session's project; its Open project button posts to /projects/select, which proves it again. The
972
914
  * name is found among the projects this person may open (projectsSelect's rule: visible here and inside the
973
915
  * ceiling); a name it can't see answers exactly like one that doesn't exist. */
974
- async function projectPageHandler(ctx) {
975
- const { url, who, response, now } = ctx;
916
+ async function projectFound(ctx, page) {
917
+ const { url, who, response } = ctx;
976
918
  let name = "";
919
+ const tail = page === "" ? "" : `/${page}`;
977
920
  try {
978
- name = decodeURIComponent(url.pathname.slice("/projects/".length));
921
+ name = decodeURIComponent(url.pathname.slice("/projects/".length, url.pathname.length - tail.length));
979
922
  }
980
923
  catch {
981
924
  name = "";
@@ -988,8 +931,37 @@ export function createPagesHandlers(runtime) {
988
931
  const open = who.via === "cookie" ? who.session.project : null;
989
932
  if (found === null || !(await authorizedProject(liveCeiling(), found.path))) {
990
933
  const words = unknownProject(name === "" ? "by that name" : name);
991
- return sendScreen(response, 404, screen("Not found", kitNotFound(url.pathname, words), { chrome: chromeFor(open, "projects") }));
934
+ await sendScreen(response, 404, screen("Not found", kitNotFound(url.pathname, words), { chrome: chromeFor(open, "projects") }));
935
+ return null;
992
936
  }
937
+ return { ...found, ambiguous: named.length > 1 };
938
+ }
939
+ /** A project's flaky tests list and one row's history. Reads only; found exactly as the project's page is. */
940
+ async function projectFlakyHandler(ctx) {
941
+ const { url, who, response, now } = ctx;
942
+ const found = await projectFound(ctx, "flaky");
943
+ if (found === null)
944
+ return;
945
+ const open = who.via === "cookie" ? who.session.project : null;
946
+ const chrome = chromeFor(open, "projects");
947
+ const back = projectHref(found.name, found.path, found.ambiguous);
948
+ const selfHref = projectHref(found.name, found.path, found.ambiguous, "flaky");
949
+ const key = url.searchParams.get("row");
950
+ if (key !== null) {
951
+ const shown = flakyShow(store, found.path, key, now);
952
+ if (shown === null)
953
+ return sendScreen(response, 404, screen("Not found", kitNotFound(url.pathname, { title: "Nothing is recorded for that test in this project." }), { chrome }));
954
+ return sendScreen(response, 200, flakyRowPage(chrome, { name: found.name, repo: found.path, back: selfHref, row: shown.row, history: shown.history, now }));
955
+ }
956
+ const days = Number(url.searchParams.get("days") ?? FLAKY_WINDOW_DAYS);
957
+ return sendScreen(response, 200, flakyPage(chrome, { name: found.name, repo: found.path, back, selfHref, list: flakyList(store, found.path, now, days), now }));
958
+ }
959
+ async function projectPageHandler(ctx) {
960
+ const { who, response, now } = ctx;
961
+ const found = await projectFound(ctx, "");
962
+ if (found === null)
963
+ return;
964
+ const open = who.via === "cookie" ? who.session.project : null;
993
965
  const path = found.path;
994
966
  // The tasks that wait on this person or run, from the board's own classification, scoped to this one project.
995
967
  const snapshot = store.boardScoped(path, now, 200, null);
@@ -1009,15 +981,61 @@ export function createPagesHandlers(runtime) {
1009
981
  }
1010
982
  }
1011
983
  return sendScreen(response, 200, projectPage(chromeFor(open, "projects"), {
1012
- name: found.name, path, selfHref: projectHref(found.name, path, named.length > 1), open: open === path, openedAt: found.openedAt,
984
+ name: found.name, path, selfHref: projectHref(found.name, path, found.ambiguous), open: open === path, openedAt: found.openedAt,
1013
985
  peek: peekOf(path, now), tasks, moreTasks: Math.max(0, cards.length - tasks.length),
1014
986
  checks: cookie ? { href: `/settings/checks?repo=${encodeURIComponent(path)}`, level: CHECK_LEVEL_WORDS[projectCheckLevel(store, path).level] } : null,
1015
987
  pullRequests: cookie ? { href: `/settings/pull-requests?repo=${encodeURIComponent(path)}`, on: publishingOf(store, path).on } : null,
1016
988
  knowledgeHref: `/settings/knowledge?repo=${encodeURIComponent(path)}`,
989
+ flakyHref: projectHref(found.name, path, found.ambiguous, "flaky"),
1017
990
  settingsHref: restricted() ? null : `/settings/project?repo=${encodeURIComponent(path)}`,
1018
991
  activity, now,
1019
992
  }));
1020
993
  }
994
+ /** The named project's code map address, for this account; null when it isn't one they can see. */
995
+ async function mapProject(ctx, suffix) {
996
+ let name = "";
997
+ try {
998
+ name = decodeURIComponent(ctx.url.pathname.slice("/projects/".length, -suffix.length));
999
+ }
1000
+ catch {
1001
+ return null;
1002
+ }
1003
+ const { recent, candidates } = visibleProjects();
1004
+ const named = [...recent.map(one => ({ path: one.path, name: one.name })), ...candidates.map(path => ({ path, name: projectName(path) }))].filter(one => one.name === name);
1005
+ const asked = ctx.url.searchParams.get("path") ?? ctx.posted.get("repo");
1006
+ const found = asked !== null ? named.find(one => one.path === asked) ?? null : named.length === 1 ? named[0] : null;
1007
+ if (found === null || !(await authorizedProject(liveCeiling(), found.path)))
1008
+ return null;
1009
+ return { name: found.name, path: found.path, base: `/projects/${encodeURIComponent(found.name)}/map`, selfHref: projectHref(found.name, found.path, named.length > 1) };
1010
+ }
1011
+ async function codeMapHandler(ctx) {
1012
+ const { url, who, response, now } = ctx;
1013
+ const open = who.via === "cookie" ? who.session.project : null;
1014
+ const found = await mapProject(ctx, "/map");
1015
+ if (found === null)
1016
+ return sendScreen(response, 404, screen("Not found", kitNotFound(url.pathname, unknownProject("by that name")), { chrome: chromeFor(open, "projects") }));
1017
+ const asked = url.searchParams.get("v");
1018
+ const pinned = asked !== null && /^[1-9][0-9]{0,14}$/.test(asked) ? Number(asked) : undefined;
1019
+ const status = codeMapStatus(store, found.path, now, iso => new Date(iso).toISOString().slice(11, 16));
1020
+ const opened = status.state === "off" || status.state === "not-available" ? null : openMap(store, found.path, pinned);
1021
+ const held = opened !== null && opened.ok ? opened : null;
1022
+ const repoed = opened !== null && !opened.ok && pinned !== undefined ? openMap(store, found.path) : null;
1023
+ const map = held ?? (repoed !== null && repoed.ok ? repoed : null);
1024
+ const canRefresh = who.via === "cookie" && who.authority !== "local-owner" && store.accountOf(who.name)?.role === "approver" && store.accountCanAccess(who.name, found.path);
1025
+ return sendScreen(response, 200, codeMapPage(chromeFor(open, "projects"), {
1026
+ name: found.name, repo: found.path, base: found.base, selfHref: found.selfHref, status, map: map?.map ?? null, versionId: map?.version.id ?? null, remoteUrl: store.projectRemote(found.path),
1027
+ q: url.searchParams.get("q"), area: url.searchParams.get("area"), file: url.searchParams.get("file"), canRefresh, now, report: codeMapReportBlock(codeMapReport(store, found.path, now)),
1028
+ said: url.searchParams.get("said"), problem: repoed !== null ? "The map changed. Showing the current one." : url.searchParams.get("problem"),
1029
+ }));
1030
+ }
1031
+ async function codeMapRefreshHandler(ctx) {
1032
+ const { response, who, now } = ctx;
1033
+ const found = await mapProject(ctx, "/map/refresh");
1034
+ if (found === null || !store.accountCanAccess(who.name, found.path))
1035
+ return refuse(response, who, 403, "That project is outside your access.", "/projects");
1036
+ const said = requestRefresh(store, found.path, who.name, now);
1037
+ return redirect(response, `${found.base}?said=${encodeURIComponent(said === "queued" ? "Queued. Main is checked on the next pass." : "Already queued.")}`);
1038
+ }
1021
1039
  async function instanceEdge(ctx) {
1022
1040
  const { request, response, url } = ctx;
1023
1041
  const secret = options.instanceSecret?.() ?? null;
@@ -1135,7 +1153,7 @@ export function createPagesHandlers(runtime) {
1135
1153
  const taskId = url.searchParams.get("task");
1136
1154
  const ref = taskId === null ? null : store.lookupRef(taskId.slice(0, 200));
1137
1155
  if (taskId !== null && (ref === null || ref.repo === null || !visible(ref.repo)))
1138
- return refuse(response, who, 404, "That task is not available.", "/work");
1156
+ return refuse(response, who, 404, "That task is not available.", "/tasks");
1139
1157
  const shown = ref !== null ? ref.repo : project !== null && visible(project) ? project : null;
1140
1158
  const connected = shown === null ? null : buildersFor(store, shown, now, ref?.assignedRunner ?? null).alive.length > 0;
1141
1159
  return sendScreen(response, 200, screen("Start a builder", startBuilderHtml({
@@ -1148,10 +1166,13 @@ export function createPagesHandlers(runtime) {
1148
1166
  "projects.github-list": projectsGithubList,
1149
1167
  "projects.page": projectsIndex,
1150
1168
  "projects.project": projectPageHandler,
1169
+ "projects.map": codeMapHandler,
1170
+ "projects.map-refresh": codeMapRefreshHandler,
1171
+ "projects.flaky": projectFlakyHandler,
1151
1172
  "home": homeRedirect,
1152
1173
  "search": searchPage,
1153
1174
  "morning": morningAlias,
1154
- "workbench": workbench,
1175
+ "workbench": async (ctx) => retiredRedirect("workbench", ctx.url, ctx.response),
1155
1176
  "ledger": ledgerPage,
1156
1177
  "activity": activityPage,
1157
1178
  "system": systemScreen,
@@ -2,6 +2,8 @@ import { parseFeedbackImage } from "../result-review.js";
2
2
  import { assignmentSummaryHtml } from "../assignment-ui.js";
3
3
  import {} from "../assignment.js";
4
4
  import {} from "../browser-workspace.js";
5
+ import { webCardHtml } from "../card-render.js";
6
+ import { cardOfProposal, cardPlainOf, cardsFirst, isCardProposal } from "../chat-card.js";
5
7
  import { closedQuestionReason } from "../decision.js";
6
8
  import { CHAT_ACTIONS, sharedActionNeedsReview, sharedActionPayload, sharedActionReviewPath } from "../chat-actions.js";
7
9
  import { CHAT_CONTINUITY_SCRIPT } from "../chat-continuity.js";
@@ -209,7 +211,7 @@ export function chatFleetOverview(snapshot, projects, interactive) {
209
211
  const briefing = "Brief me on what needs my attention, what is building, and the highest-leverage next action across every project.";
210
212
  return html `<section class="card chat-overview" aria-label="live portfolio overview" data-card-kind="fleet-overview"><div class="chat-overview-head"><h2>${projects.length} project${projects.length === 1 ? "" : "s"}</h2>${interactive
211
213
  ? postForm("/chat", html `<button type="submit" name="message" value="${briefing}" class="quiet">Brief me</button>`, { attrs: { class: "inline" } })
212
- : html `<a href="/board?scope=all" class="chat-overview-link">open board</a>`}</div><div class="chat-overview-stats"><a href="/work?view=needs-you" class="chat-overview-stat attention"><b>${needsYou}${snapshot.attentionCount?.saturated ? "+" : ""}</b><span>need you</span></a><a href="/board?scope=all" class="chat-overview-stat live"><b>${running}</b><span>building</span></a><a href="/board?scope=all&amp;view=order" class="chat-overview-stat"><b>${queued}</b><span>queued</span></a><a href="/work" class="chat-overview-stat"><b>${done}</b><span>finished today</span></a></div>${rows.length === 0 ? html `<p class="chat-overview-clear">${needsYou === 0 && running === 0 ? html `<span class="dot dot-ok"></span>No tasks are waiting and no builds are running.` : html `<a href="/work">Open Tasks to inspect current work.</a>`}</p>` : html `<div class="chat-overview-items">${rows}</div>`}${completedWorkHtml(snapshot, projects.map(project => project.label), snapshot.assignmentStates)}${saturated ? html `<p class="meta chat-overview-note">Some items aren’t shown. Open Work to see more.</p>` : ""}</section>`;
214
+ : html `<a href="/tasks?view=board&scope=all" class="chat-overview-link">open board</a>`}</div><div class="chat-overview-stats"><a href="/tasks?view=needs-you" class="chat-overview-stat attention"><b>${needsYou}${snapshot.attentionCount?.saturated ? "+" : ""}</b><span>need you</span></a><a href="/board?scope=all" class="chat-overview-stat live"><b>${running}</b><span>building</span></a><a href="/board?scope=all&amp;view=order" class="chat-overview-stat"><b>${queued}</b><span>queued</span></a><a href="/tasks" class="chat-overview-stat"><b>${done}</b><span>finished today</span></a></div>${rows.length === 0 ? html `<p class="chat-overview-clear">${needsYou === 0 && running === 0 ? html `<span class="dot dot-ok"></span>No tasks are waiting and no builds are running.` : html `<a href="/tasks">Open Tasks to inspect current work.</a>`}</p>` : html `<div class="chat-overview-items">${rows}</div>`}${completedWorkHtml(snapshot, projects.map(project => project.label), snapshot.assignmentStates)}${saturated ? html `<p class="meta chat-overview-note">Some items aren’t shown. Open Work to see more.</p>` : ""}</section>`;
213
215
  }
214
216
  /**
215
217
  * The lead's project rail: one bounded pulse per admitted project, plus
@@ -544,6 +546,17 @@ export function proposalCardParts(view, inert, decision, returnTo = null) {
544
546
  card: { id: view.id, kind: view.kind, label: "Question closed", state: view.state, body: htmlString(body), said: null, links: [], primary: null, dismissable: false, note: null },
545
547
  };
546
548
  }
549
+ // A Toolroll card: data to read, with its fixed links. It confirms nothing, so it waits on nobody.
550
+ if (isCardProposal(view)) {
551
+ const shown = cardOfProposal(view);
552
+ const body = shown === null ? html `<p>${cardPlainOf(view)}</p>` : webCardHtml(shown);
553
+ const links = shown?.buttons.map(one => ({ label: one.label, href: one.path })) ?? [];
554
+ const label = shown?.title ?? "Card";
555
+ return {
556
+ html: html `<article class="card proposal proposal-data" data-card-kind="data"><div class="proposal-body"><h3>${label}</h3>${body}</div>${links.length === 0 ? "" : html `<footer class="proposal-actions">${joinHtml(links.map(one => html `<a class="button-link" href="${one.href}">${one.label}</a>`))}</footer>`}</article>`,
557
+ card: { id: view.id, kind: view.kind, label, state: "pending", body: htmlString(body), said: null, links, primary: null, dismissable: false, note: null, data: true },
558
+ };
559
+ }
547
560
  const presentations = {
548
561
  task: { label: payload["report"] === true ? "Scout investigation" : "New task", action: "file task", icon: html `<path d="M12 5v14"/><path d="M5 12h14"/>` },
549
562
  next: { label: "Queue priority", action: "move to front", icon: html `<path d="M12 19V5"/><path d="m5 12 7-7 7 7"/>` },
@@ -560,7 +573,7 @@ export function proposalCardParts(view, inert, decision, returnTo = null) {
560
573
  icon: html `<path d="M14.7 6.3a4 4 0 0 0-5 5L4 17l3 3 5.7-5.7a4 4 0 0 0 5-5l-2.4 2.4-3-3z"/>`,
561
574
  },
562
575
  agents: { label: "Agents change", action: "change agents", icon: html `<circle cx="12" cy="12" r="3"/><path d="M12 2v3"/><path d="M12 19v3"/><path d="m4.9 4.9 2.2 2.2"/><path d="m16.9 16.9 2.2 2.2"/><path d="M2 12h3"/><path d="M19 12h3"/><path d="m4.9 19.1 2.2-2.2"/><path d="m16.9 7.1 2.2-2.2"/>` },
563
- review: { label: text("operation") === "revise" ? "Changes to make" : "Note for later", action: text("operation") === "revise" ? "Request changes" : "Save for later", icon: html `<path d="M4 5h16v12H8l-4 3z"/>` },
576
+ review: { label: text("operation") === "revise" ? "Changes to make" : "Note for later", action: text("operation") === "revise" ? "Send back" : "Save for later", icon: html `<path d="M4 5h16v12H8l-4 3z"/>` },
564
577
  control: { label: "Open control", action: "Open", icon: html `<path d="M5 12h14m-6-6 6 6-6 6"/>` },
565
578
  action: { label: text("title") || "Review action", action: sharedActionPayload(payload) ? CHAT_ACTIONS[sharedActionPayload(payload).operation].label : "Unavailable", icon: html `<path d="M5 12h14m-6-6 6 6-6 6"/>` },
566
579
  task_action: { label: payload["operation"] === "stop" ? "Stop task?" : payload["operation"] === "resume" ? "Resume task?" : "Task update", action: isChatTaskAction(payload["operation"]) ? CHAT_TASK_ACTIONS[payload["operation"]].label : "Unavailable", icon: html `<path d="M5 12h14m-6-6 6 6-6 6"/>` },
@@ -855,7 +868,7 @@ export function leadBrowserMessages(rows, back, target = { task: null, project:
855
868
  activity: message.activity, createdAt: message.createdAt,
856
869
  ...(() => {
857
870
  // Under the lead's message only, as the server-rendered thread does: the person's message shares the turn.
858
- const parts = message.turn === null || message.role === 'operator' ? [] : rows.proposals.filter(one => one.turn === message.turn)
871
+ const parts = message.turn === null || message.role === 'operator' ? [] : cardsFirst(rows.proposals.filter(one => one.turn === message.turn))
859
872
  .map(one => leadProposalCardParts(one, rows.pending !== null, rows.decisions.get(typeof one.payload['decision'] === 'number' ? one.payload['decision'] : -1) ?? null, back));
860
873
  return { cardsHtml: htmlString(joinHtml(parts.map(one => one.html))), cards: parts.map(one => one.card) };
861
874
  })(),
@@ -0,0 +1,30 @@
1
+ import { type MapStatus } from "../code-map-read.js";
2
+ import { type CodeMapReport } from "../code-map-report.js";
3
+ import type { CodeMap } from "../code-map-shape.js";
4
+ import { type Html } from "../html.js";
5
+ import { type Chrome, type Screen } from "./chrome.js";
6
+ export declare const LIMITS_WORDS: {
7
+ readonly system: "Limits enforced by the system.";
8
+ readonly checked: "Limits checked by Toolroll every 250 ms.";
9
+ };
10
+ export type CodeMapPage = {
11
+ name: string;
12
+ repo: string;
13
+ base: string;
14
+ selfHref: string;
15
+ status: MapStatus;
16
+ map: CodeMap | null;
17
+ versionId: number | null;
18
+ remoteUrl: string | null;
19
+ q: string | null;
20
+ area: string | null;
21
+ file: string | null;
22
+ canRefresh: boolean;
23
+ said: string | null;
24
+ problem: string | null;
25
+ now: Date;
26
+ report: Html | null;
27
+ };
28
+ /** "Builds with and without the map": read by people; nothing switches on it. */
29
+ export declare function codeMapReportBlock(report: CodeMapReport): Html | null;
30
+ export declare function codeMapPage(chrome: Chrome, data: CodeMapPage): Screen;