toolroll 0.9.85 → 0.9.87

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 (220) hide show
  1. package/CHANGELOG.md +83 -0
  2. package/dist/agent-fence.d.ts +6 -2
  3. package/dist/agent-fence.js +3 -2
  4. package/dist/agents-board.d.ts +12 -0
  5. package/dist/agents-board.js +26 -0
  6. package/dist/approval-rules-ui.d.ts +4 -1
  7. package/dist/approval-rules-ui.js +31 -15
  8. package/dist/assignment-ui.js +1 -1
  9. package/dist/assignment.js +4 -3
  10. package/dist/backup-ui.d.ts +5 -4
  11. package/dist/backup-ui.js +65 -32
  12. package/dist/browser/terminal.css +1 -1
  13. package/dist/browser/terminal.js +57 -54
  14. package/dist/browser/workspace.css +2 -2
  15. package/dist/browser/workspace.js +35 -68
  16. package/dist/browser-crew.js +1 -1
  17. package/dist/browser-workspace.d.ts +363 -177
  18. package/dist/browser-workspace.js +10 -9
  19. package/dist/chat-actions.d.ts +11 -1
  20. package/dist/chat-actions.js +53 -1
  21. package/dist/chat-channel.js +1 -0
  22. package/dist/chat-controls.d.ts +1 -1
  23. package/dist/chat-controls.js +1 -1
  24. package/dist/chat-quiet.js +3 -2
  25. package/dist/chat-settings-page.d.ts +29 -9
  26. package/dist/chat-settings-page.js +59 -28
  27. package/dist/check-levels-ui.d.ts +2 -1
  28. package/dist/check-levels-ui.js +67 -58
  29. package/dist/claim.d.ts +3 -0
  30. package/dist/claim.js +4 -1
  31. package/dist/contracts/chat-actions.d.ts +9 -0
  32. package/dist/contracts/chat-actions.js +3 -0
  33. package/dist/contracts/cli.d.ts +43 -0
  34. package/dist/contracts/cli.js +4 -0
  35. package/dist/contracts/console-api.d.ts +4 -1
  36. package/dist/contracts/console-api.js +4 -1
  37. package/dist/contracts/flow-propose.d.ts +4 -1
  38. package/dist/contracts/flow-propose.js +1 -1
  39. package/dist/contracts/flow.d.ts +41 -0
  40. package/dist/contracts/flow.js +4 -2
  41. package/dist/contracts/lead-tools.d.ts +21 -0
  42. package/dist/contracts/lead-tools.js +2 -0
  43. package/dist/contracts/store-json.d.ts +2 -0
  44. package/dist/contracts/trigger-payloads.d.ts +20 -0
  45. package/dist/contracts/trigger-payloads.js +4 -0
  46. package/dist/coordinator.js +1 -0
  47. package/dist/credentials-ui.d.ts +5 -3
  48. package/dist/credentials-ui.js +58 -31
  49. package/dist/deploy-gate.js +4 -0
  50. package/dist/design/kit.css +685 -0
  51. package/dist/design/kit.d.ts +208 -0
  52. package/dist/design/kit.js +177 -0
  53. package/dist/design/tokens.css +19 -1
  54. package/dist/discord-settings.js +8 -4
  55. package/dist/dispatch.d.ts +3 -0
  56. package/dist/dispatch.js +4 -1
  57. package/dist/export-ui.d.ts +6 -4
  58. package/dist/export-ui.js +18 -8
  59. package/dist/flow-browser.d.ts +103 -0
  60. package/dist/flow-browser.js +204 -0
  61. package/dist/flow-changed.d.ts +14 -0
  62. package/dist/flow-changed.js +53 -0
  63. package/dist/flow-code.d.ts +5 -1
  64. package/dist/flow-code.js +4 -1
  65. package/dist/flow-engine.js +16 -0
  66. package/dist/flow-gallery-ui.d.ts +41 -4
  67. package/dist/flow-gallery-ui.js +95 -63
  68. package/dist/flow-live.js +9 -1
  69. package/dist/flow-pull-request.js +15 -7
  70. package/dist/flow-share.js +3 -1
  71. package/dist/flow-starters-ui.d.ts +3 -9
  72. package/dist/flow-starters-ui.js +12 -23
  73. package/dist/flow-steps.d.ts +9 -1
  74. package/dist/flow-steps.js +55 -5
  75. package/dist/flow-triggers.js +55 -0
  76. package/dist/flow-unattended-words.d.ts +20 -0
  77. package/dist/flow-unattended-words.js +24 -0
  78. package/dist/flow-unattended.d.ts +9 -5
  79. package/dist/flow-unattended.js +45 -23
  80. package/dist/flows-cli.d.ts +19 -0
  81. package/dist/flows-cli.js +54 -5
  82. package/dist/flows-ui.d.ts +12 -6
  83. package/dist/flows-ui.js +152 -32
  84. package/dist/flows.js +5 -2
  85. package/dist/integrations-ui.d.ts +6 -4
  86. package/dist/integrations-ui.js +34 -40
  87. package/dist/integrations.d.ts +2 -2
  88. package/dist/integrations.js +4 -4
  89. package/dist/kits-ui.d.ts +8 -10
  90. package/dist/kits-ui.js +29 -33
  91. package/dist/kits.js +1 -1
  92. package/dist/knowledge-ui.js +76 -19
  93. package/dist/lead-tools.js +35 -6
  94. package/dist/lead-voice.js +16 -5
  95. package/dist/ledger-view.d.ts +2 -0
  96. package/dist/ledger-view.js +41 -38
  97. package/dist/models-ui.d.ts +8 -3
  98. package/dist/models-ui.js +82 -59
  99. package/dist/monitoring-ui.d.ts +4 -2
  100. package/dist/monitoring-ui.js +44 -13
  101. package/dist/needs-you.d.ts +1 -1
  102. package/dist/needs-you.js +8 -0
  103. package/dist/operate-remote-arguments.js +1 -0
  104. package/dist/operate-remote.js +1 -1
  105. package/dist/operate.d.ts +1 -1
  106. package/dist/operate.js +249 -6
  107. package/dist/passwordless.d.ts +1 -1
  108. package/dist/passwordless.js +1 -1
  109. package/dist/people-audit-ui.d.ts +2 -1
  110. package/dist/people-audit-ui.js +25 -23
  111. package/dist/policy-ui.d.ts +3 -2
  112. package/dist/policy-ui.js +27 -21
  113. package/dist/policy.d.ts +3 -0
  114. package/dist/policy.js +14 -1
  115. package/dist/project-delete-ui.d.ts +4 -2
  116. package/dist/project-delete-ui.js +135 -126
  117. package/dist/project-delete.js +7 -3
  118. package/dist/project-knowledge.d.ts +10 -10
  119. package/dist/publish.d.ts +11 -0
  120. package/dist/publish.js +16 -3
  121. package/dist/pull-request-flow.js +35 -4
  122. package/dist/pull-request-ui.d.ts +2 -1
  123. package/dist/pull-request-ui.js +39 -28
  124. package/dist/recipe-ui.d.ts +4 -2
  125. package/dist/recipe-ui.js +14 -4
  126. package/dist/release-adapters.d.ts +3 -0
  127. package/dist/release-adapters.js +22 -2
  128. package/dist/release-cli.js +81 -9
  129. package/dist/release.d.ts +21 -0
  130. package/dist/release.js +40 -4
  131. package/dist/releases.js +1 -1
  132. package/dist/request-budget-ui.d.ts +4 -2
  133. package/dist/request-budget-ui.js +30 -19
  134. package/dist/result-follow-ups.js +4 -0
  135. package/dist/retention-ui.d.ts +4 -3
  136. package/dist/retention-ui.js +15 -12
  137. package/dist/scope.d.ts +8 -0
  138. package/dist/scope.js +13 -0
  139. package/dist/serve.d.ts +0 -1
  140. package/dist/serve.js +51 -7
  141. package/dist/server/agents-computers.d.ts +9 -0
  142. package/dist/server/agents-computers.js +67 -0
  143. package/dist/server/chat.js +20 -18
  144. package/dist/server/chrome.d.ts +1 -1
  145. package/dist/server/chrome.js +19 -9
  146. package/dist/server/console.css +31 -2
  147. package/dist/server/flows.js +79 -36
  148. package/dist/server/home.d.ts +35 -0
  149. package/dist/server/home.js +104 -0
  150. package/dist/server/http.d.ts +3 -0
  151. package/dist/server/http.js +5 -0
  152. package/dist/server/kit-standalone.d.ts +22 -0
  153. package/dist/server/kit-standalone.js +69 -0
  154. package/dist/server/kit.d.ts +405 -0
  155. package/dist/server/kit.js +361 -0
  156. package/dist/server/pages.d.ts +2 -1
  157. package/dist/server/pages.js +213 -174
  158. package/dist/server/people-tokens.js +26 -85
  159. package/dist/server/render-history.d.ts +51 -0
  160. package/dist/server/render-history.js +228 -0
  161. package/dist/server/render-new-task.d.ts +29 -0
  162. package/dist/server/render-new-task.js +96 -0
  163. package/dist/server/render-next.d.ts +63 -0
  164. package/dist/server/render-next.js +109 -0
  165. package/dist/server/render-pages.d.ts +35 -190
  166. package/dist/server/render-pages.js +154 -563
  167. package/dist/server/render-people.d.ts +24 -3
  168. package/dist/server/render-people.js +93 -72
  169. package/dist/server/render-projects.d.ts +103 -0
  170. package/dist/server/render-projects.js +247 -0
  171. package/dist/server/render-results.d.ts +5 -24
  172. package/dist/server/render-results.js +48 -185
  173. package/dist/server/render-settings.d.ts +5 -74
  174. package/dist/server/render-settings.js +102 -493
  175. package/dist/server/render-tasks.d.ts +43 -5
  176. package/dist/server/render-tasks.js +131 -91
  177. package/dist/server/route-table.js +31 -5
  178. package/dist/server/settings.js +276 -255
  179. package/dist/server/task-views.d.ts +8 -0
  180. package/dist/server/task-views.js +90 -2
  181. package/dist/server/tasks.d.ts +18 -0
  182. package/dist/server/tasks.js +148 -41
  183. package/dist/server/terminals.js +1 -1
  184. package/dist/server/welcome.d.ts +22 -0
  185. package/dist/server/welcome.js +69 -0
  186. package/dist/settings-groups-ui.d.ts +279 -0
  187. package/dist/settings-groups-ui.js +615 -0
  188. package/dist/settings-kit.d.ts +42 -6
  189. package/dist/settings-kit.js +69 -7
  190. package/dist/settings-proofs.js +1 -0
  191. package/dist/setup-guide.d.ts +2 -2
  192. package/dist/skills-ui.js +59 -7
  193. package/dist/slack-settings.js +12 -5
  194. package/dist/spend-ui.d.ts +45 -26
  195. package/dist/spend-ui.js +124 -56
  196. package/dist/sso-ui.d.ts +5 -2
  197. package/dist/sso-ui.js +63 -31
  198. package/dist/storage-ui.d.ts +3 -2
  199. package/dist/storage-ui.js +56 -50
  200. package/dist/store.d.ts +182 -5
  201. package/dist/store.js +512 -38
  202. package/dist/surface.js +23 -3
  203. package/dist/task-archive.d.ts +10 -0
  204. package/dist/task-archive.js +16 -0
  205. package/dist/task-control.d.ts +1 -1
  206. package/dist/teams-settings.d.ts +2 -1
  207. package/dist/teams-settings.js +24 -41
  208. package/dist/telegram-settings.d.ts +0 -4
  209. package/dist/telegram-settings.js +52 -25
  210. package/dist/tidy.d.ts +130 -0
  211. package/dist/tidy.js +503 -0
  212. package/dist/toolroll-update-ui.d.ts +8 -4
  213. package/dist/toolroll-update-ui.js +55 -45
  214. package/dist/tools-ui.d.ts +5 -3
  215. package/dist/tools-ui.js +107 -42
  216. package/dist/work-index.d.ts +24 -0
  217. package/dist/work-index.js +56 -30
  218. package/dist/workspace-ui.d.ts +1 -1
  219. package/dist/workspace-ui.js +8 -4
  220. package/package.json +1 -1
@@ -1,42 +1,28 @@
1
1
  /**
2
- * Settings → Storage: how much space task checkouts use, what a clean-up would free now, a Clean up button whose
3
- * preview (what goes, what stays and why, with paths) sits behind the password, what the last storage sweep removed
4
- * and when (its paths in a disclosure) and why the next one waits, what landed work's teardown did and still waits on (in a disclosure, with the
5
- * one-time command for older work), and when a finished task's clean checkout goes by itself. An instance operator's page.
2
+ * Settings → Storage: how much space task checkouts use, what a clean-up would free now, the clean-up's preview (what
3
+ * goes, what stays and why, with paths) and its Remove behind the password, what the last storage sweep removed and
4
+ * when (its paths in a disclosure) and why the next one waits, what landed work's teardown did and still waits on (in a
5
+ * disclosure, with the one-time command for older work), and when a finished task's clean checkout goes by itself. An
6
+ * instance operator's page, built from the revamp kit; the Settings frame adds the title.
6
7
  */
7
8
  import { homedir } from "node:os";
8
9
  import { basename } from "node:path";
9
- import { html, postForm, reauth } from "./html.js";
10
+ import { html, joinHtml } from "./html.js";
10
11
  import { previewDigest, whyWords } from "./checkout-cleanup.js";
11
12
  import { CLEANUP_CHOICES, bytesWords } from "./storage.js";
12
13
  import { sweepDetails, sweepWords } from "./storage-sweep.js";
13
14
  import { sweepScheduleWords } from "./storage-sweep-schedule.js";
15
+ import { codeLine, disclose, formRow, icon, infoLine, note, notice, select, settingsRecord, settingsSection } from "./server/kit.js";
16
+ import { kitSectionForm } from "./settings-kit.js";
14
17
  const plural = (count, one, many = `${one}s`) => `${count} ${count === 1 ? one : many}`;
15
- export const STORAGE_CSS = `.storage{max-width:720px;min-width:0;overflow-wrap:anywhere}.storage h2{font-size:1.0625rem;margin:24px 0 4px}` +
16
- `.storage .storage-state{display:grid;gap:12px}.storage .storage-state h2{margin:0}.storage .storage-state p{margin:0}` +
17
- `.storage details.clean-up,.storage details.discard{border:0;padding:0;margin:0;background:transparent}` +
18
- `.storage details.clean-up>summary{list-style:none;display:inline-flex;align-items:center;min-height:2.125rem;padding:.35rem .75rem;border-radius:calc(var(--radius) - 3px);` +
19
- `background:var(--primary);color:var(--primary-foreground);font-weight:600;font-size:.8125rem;cursor:pointer;white-space:nowrap}` +
20
- `.storage details.clean-up>summary::-webkit-details-marker{display:none}.storage details.clean-up[open]>summary{background:var(--card);color:var(--foreground);border:1px solid var(--input)}` +
21
- `.storage .preview{display:grid;gap:12px;margin-top:12px;padding-top:12px;border-top:1px solid var(--border)}.storage .preview h3{font-size:.875rem;margin:0}` +
22
- `.storage ul.checkouts{list-style:none;padding:0;margin:0}` +
23
- `.storage ul.checkouts li{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:2px 12px;padding:8px 0;border-top:1px solid var(--border);font-size:.8125rem}` +
24
- `.storage ul.checkouts .name{font-weight:500;min-width:0}.storage ul.checkouts .size{font-variant-numeric:tabular-nums;white-space:nowrap}` +
25
- `.storage ul.checkouts .path{grid-column:1/-1;font-family:var(--font-mono, ui-monospace, monospace);font-size:.6875rem;color:var(--muted-foreground);word-break:break-all}` +
26
- `.storage ul.checkouts .why{grid-column:1/-1}` +
27
- `.storage details.discard{grid-column:1/-1}.storage details.discard>summary{cursor:pointer;color:var(--destructive);min-height:2.25rem;display:inline-flex;align-items:center}` +
28
- `.storage form{display:grid;gap:10px;margin:0}.storage label{display:grid;gap:4px;font-size:.8125rem;font-weight:500;max-width:20rem}` +
29
- `.storage select,.storage input[type=password]{width:100%;box-sizing:border-box;min-height:2.125rem}.storage form button{justify-self:start;white-space:nowrap}` +
30
- `.storage form.setting button[type=submit],.storage .preview form.remove button[type=submit]{background:var(--primary);color:var(--primary-foreground);border-color:var(--primary);font-weight:600}` +
31
- `.storage .sweep{display:grid;gap:4px}.storage .sweep p{margin:0}.storage .sweep .problem{color:var(--destructive)}` +
32
- `.storage details.sweep-details>summary{cursor:pointer;min-height:2.25rem;display:inline-flex;align-items:center;font-size:.8125rem;color:var(--muted-foreground)}` +
33
- `.storage details.landed>summary{cursor:pointer;min-height:2.25rem;display:inline-flex;align-items:center;font-size:.8125rem;color:var(--muted-foreground)}` +
34
- `.storage details.landed .landed-body{display:grid;gap:6px;margin-top:4px;font-size:.8125rem}.storage details.landed p{margin:0}` +
35
- `.storage details.landed ul{margin:0;padding-left:18px;overflow-wrap:anywhere}.storage details.landed .target{font-family:var(--font-mono, ui-monospace, monospace);font-size:.6875rem}` +
36
- `.storage details.landed code{font-family:var(--font-mono, ui-monospace, monospace);font-size:.75rem;overflow-wrap:anywhere}` +
37
- `.storage details.sweep-details ul{margin:4px 0 0;padding-left:18px;font-family:var(--font-mono, ui-monospace, monospace);font-size:.6875rem;color:var(--muted-foreground);overflow-wrap:anywhere}` +
38
- `@media (max-width:900px){.storage details.sweep-details>summary,.storage details.landed>summary{min-height:44px}.storage details.clean-up>summary,.storage form button{min-height:44px}.storage label{max-width:none}.storage select,.storage input[type=password]{min-height:44px;font-size:16px}.storage .preview form.remove button{width:100%}}`;
39
- /** Paths under the home folder as ~/…, as the checkout list shows them. */
18
+ /** The page is kit pieces now; nothing of the old look is left. Kept as an export for the shared stylesheet. */
19
+ export const STORAGE_CSS = "";
20
+ /** A path under the home folder as ~/…. */
21
+ function tilde(path) {
22
+ const home = homedir();
23
+ return home !== "" && home !== "/" && path.startsWith(`${home}/`) ? `~${path.slice(home.length)}` : path;
24
+ }
25
+ /** Paths under the home folder as ~/… anywhere in a line, as the checkout list shows them. */
40
26
  function homeAsTilde(line) {
41
27
  const home = homedir();
42
28
  return home === "" || home === "/" ? line : line.split(`${home}/`).join("~/");
@@ -49,41 +35,61 @@ function whenWords(at) {
49
35
  /** When the last sweep ran and what it did, then why the next one waits or when it is due; paths and problems behind Details. */
50
36
  function sweepHtml(sweep) {
51
37
  const last = sweep.last;
52
- const schedule = sweep.schedule === undefined ? (sweep.off ? html `<p class="meta" data-sweep-state="off">Automatic sweep: off.</p>` : "")
53
- : html `<p class="meta" data-sweep-state="${sweep.schedule.state}">Automatic sweep: ${sweepScheduleWords(sweep.schedule, whenWords)}</p>`;
38
+ const schedule = sweep.schedule === undefined ? (sweep.off ? html `<p class="k-note" data-sweep-state="off">Automatic sweep: off.</p>` : "")
39
+ : html `<p class="k-note" data-sweep-state="${sweep.schedule.state}">Automatic sweep: ${sweepScheduleWords(sweep.schedule, whenWords)}</p>`;
54
40
  if (last === null)
55
- return html `<div class="sweep" data-last-sweep=""><p>Not swept yet.</p>${schedule}</div>`;
41
+ return html `<div data-last-sweep="">${note("Not swept yet.")}${schedule}</div>`;
56
42
  const who = last.source === "manual" ? `Cleaned up by ${last.actor}` : "Swept";
57
43
  const details = sweepDetails(last.parts);
58
44
  const problem = last.parts.some(one => one.failed > 0 || one.problem !== undefined);
59
- return html `<div class="sweep" data-last-sweep="${last.at}"><p${problem ? html ` class="problem"` : ""}>${who} <time datetime="${last.at}">${whenWords(last.at)}</time>. ${sweepWords(last.parts)}</p>${schedule}${details.length === 0 ? "" : html `<details class="sweep-details"><summary>Details</summary><ul>${details.map(line => html `<li>${homeAsTilde(line)}</li>`)}</ul></details>`}</div>`;
45
+ const said = html `${who} <time datetime="${last.at}">${whenWords(last.at)}</time>. ${sweepWords(last.parts)}`;
46
+ return html `<div data-last-sweep="${last.at}">${problem ? html `<p class="k-notice" role="alert">${icon("alert", true)}<span>${said}</span></p>` : note(said)}${schedule}${details.length === 0 ? "" : disclose("Details", joinHtml(details.map(line => codeLine(homeAsTilde(line)))))}</div>`;
60
47
  }
61
48
  /** Landed work, on demand: what its teardown did, what it waits on and why, and the one-time command for older work. */
62
49
  function landedHtml(landed) {
63
- const home = homedir();
64
50
  const done = landed.completed + landed.checkouts + landed.branches === 0 ? "No landed work cleaned up yet."
65
51
  : `${plural(landed.completed, "task")} marked complete; ${plural(landed.checkouts, "checkout")} and ${plural(landed.branches, "local branch", "local branches")} removed.`;
66
- const waiting = landed.waiting.length === 0 ? "" : html `<p>Waiting (${landed.waiting.length}):</p><ul data-landed-waiting>${landed.waiting.map(one => html `<li><span class="target">${home !== "" && one.target.startsWith(`${home}/`) ? `~${one.target.slice(home.length)}` : one.target}</span>${one.why === null ? "" : html `: ${one.why}`}</li>`)}</ul>`;
67
- return html `<details class="landed" data-landed><summary>Landed work</summary><div class="landed-body"><p>${done}</p>${waiting}<p class="meta">For work that landed before this: <code>toolroll storage clean --landed</code> shows what would go; add <code>--yes</code> to do it.</p></div></details>`;
52
+ const waiting = landed.waiting.length === 0 ? "" : html `<div data-landed-waiting>${note(`Waiting (${landed.waiting.length}):`)}${landed.waiting.map(one => settingsRecord({ title: html `<span class="k-mono">${tilde(one.target)}</span>`, sub: one.why ?? "" }))}</div>`;
53
+ return html `<div data-landed>${disclose("Landed work", joinHtml([note(done), waiting,
54
+ note(html `For work that landed before this: <code class="k-mono">toolroll storage clean --landed</code> shows what would go; add <code class="k-mono">--yes</code> to do it.`)]))}</div>`;
68
55
  }
56
+ /** One checkout: the task it was for, its size, where it is (the home folder as ~), why it stays, and Discard when it has changes. */
69
57
  function item(one) {
58
+ const why = one.why === null ? "" : `${whyWords(one)[0].toUpperCase()}${whyWords(one).slice(1)}`;
70
59
  const discard = one.why === "has changes"
71
- ? html `<details class="discard"><summary>Discard</summary>${postForm("/settings/storage/discard", html `<p class="meta">Throws away this checkout and its uncommitted changes. Its branch stays.</p>${reauth(html `<label>Your Toolroll password<input type="password" name="password" autocomplete="current-password" required></label>`)}<button type="submit" class="danger">Discard changes</button>`, { hidden: { path: one.path } })}</details>`
60
+ ? disclose("Discard", kitSectionForm({ proof: "storage-discard", action: "/settings/storage/discard", id: `discard-${previewId(one.path)}`, hidden: { path: one.path },
61
+ save: { label: "Discard changes", variant: "danger" }, body: note("Throws away this checkout and its uncommitted changes. Its branch stays.") }))
72
62
  : "";
73
- // The task it was for, then where it is (the home folder as ~), then why it stays.
74
- const home = homedir();
75
- const where = home !== "" && one.path.startsWith(`${home}/`) ? `~${one.path.slice(home.length)}` : one.path;
76
- return html `<li data-checkout="${one.path}"><span class="name">${one.taskId ?? basename(one.path)}</span><span class="size">${bytesWords(one.bytes)}</span><span class="path">${where}</span>${one.why === null ? "" : html `<span class="why">${whyWords(one)[0].toUpperCase() + whyWords(one).slice(1)}</span>`}${discard}</li>`;
63
+ return settingsRecord({ title: one.taskId ?? basename(one.path), attrs: { "data-checkout": one.path }, when: bytesWords(one.bytes),
64
+ sub: html `<span class="k-mono">${tilde(one.path)}</span>${why === "" ? "" : ` · ${why}`}`, more: discard });
77
65
  }
78
- export function storageHtml(view, notice) {
66
+ /** A short id for a checkout's form, from its path. */
67
+ const previewId = (path) => path.replace(/[^a-z0-9]+/gi, "-").replace(/^-|-$/g, "").slice(-48) || "checkout";
68
+ export function storageHtml(view, said) {
79
69
  const { plan } = view;
80
- const note = notice.problem ? html `<p class="problem" role="alert">${notice.problem}</p>` : notice.said ? html `<p role="status">${notice.said}</p>` : "";
81
70
  const facts = [plural(plan.count, "checkout"), `${plan.waitingReview} waiting for review`, `${plan.withChanges} kept for their changes`].join(" · ");
82
71
  const frees = plan.go.length === 0 ? "Nothing to clean up now." : `Cleaning up now frees about ${bytesWords(plan.freeBytes)}.`;
83
- const remove = plan.go.length === 0 ? "" :
84
- postForm("/settings/storage/clean", html `${reauth(html `<label>Your Toolroll password<input type="password" name="password" autocomplete="current-password" required></label>`)}<button type="submit">Remove ${plural(plan.go.length, "checkout")}</button>`, { attrs: { class: "remove" }, hidden: { preview: previewDigest(plan) } });
85
- const preview = html `<details class="clean-up" data-clean-up${notice.problem?.startsWith("Enter your Toolroll password to clean") ? html ` open` : ""}><summary>Clean up</summary><div class="preview">${plan.go.length === 0 ? html `<p>Nothing to remove now.</p>` :
86
- html `<h3>Removes ${plural(plan.go.length, "checkout")}, about ${bytesWords(plan.freeBytes)}</h3><p class="meta">Their branches stay, so nothing committed is lost.</p><ul class="checkouts" data-goes>${plan.go.map(one => item(one))}</ul>`}${plan.stay.length === 0 ? "" : html `<h3>Stays (${plan.stay.length})</h3><ul class="checkouts" data-stays>${plan.stay.map(one => item(one))}</ul>`}${remove}</div></details>`;
87
- const options = CLEANUP_CHOICES.map(one => html `<option value="${one.value}"${one.value === plan.cleanup ? html ` selected` : ""}>${one.label}</option>`);
88
- return html `<section class="storage">${note}<div class="card storage-state"><h2 data-checkout-bytes="${plan.totalBytes}">Checkouts use ${bytesWords(plan.totalBytes)}</h2><p class="meta">${facts}</p>${view.measured ?? ""}<p data-clean-bytes="${plan.freeBytes}">${frees}</p>${view.sweep === undefined ? "" : sweepHtml(view.sweep)}${view.landed === undefined ? "" : landedHtml(view.landed)}${preview}</div><h2>Automatic cleanup</h2>${postForm("/settings/storage", html `<label>Remove a finished task's clean checkout<select name="cleanup">${options}</select></label><p class="meta">Its branch stays. Checkouts with changes, commits on no branch, in use, or waiting for review are kept. When a task's work is already in main, Toolroll marks it complete and removes its checkout and local branches.</p>${reauth(html `<label>Your Toolroll password<input type="password" name="password" autocomplete="current-password" required></label>`)}<button type="submit">Save</button>`, { attrs: { class: "setting" } })}</section>`;
72
+ const checkouts = settingsSection({ id: "checkouts", title: `Checkouts use ${bytesWords(plan.totalBytes)}`,
73
+ body: joinHtml([
74
+ said.problem ? notice(said.problem) : said.said ? infoLine(said.said) : "",
75
+ html `<p class="k-note" data-checkout-bytes="${plan.totalBytes}">${facts}</p>`,
76
+ view.measured ?? "",
77
+ html `<p class="k-note" data-clean-bytes="${plan.freeBytes}">${frees}</p>`,
78
+ view.sweep === undefined ? "" : sweepHtml(view.sweep),
79
+ view.landed === undefined ? "" : landedHtml(view.landed),
80
+ ]) });
81
+ const remove = plan.go.length === 0 ? "" : kitSectionForm({ proof: "storage-clean", action: "/settings/storage/clean", id: "clean-form", hidden: { preview: previewDigest(plan) },
82
+ save: { label: `Remove ${plural(plan.go.length, "checkout")}`, variant: "danger" }, body: html `` });
83
+ const goes = plan.go.length === 0 ? note("Nothing to remove now.")
84
+ : html `<div data-goes>${plan.go.map(one => item(one))}</div>`;
85
+ const stays = plan.stay.length === 0 ? "" : disclose(`Stays (${plan.stay.length})`, html `<div data-stays>${plan.stay.map(one => item(one))}</div>`);
86
+ const cleanUp = settingsSection({ id: "clean-up", title: "Clean up now",
87
+ lead: plan.go.length === 0 ? "Nothing to remove now." : `Removes ${plural(plan.go.length, "checkout")}, about ${bytesWords(plan.freeBytes)}. Their branches stay, so nothing committed is lost.`,
88
+ body: html `<div data-clean-up>${joinHtml([plan.go.length === 0 ? "" : goes, remove, stays])}</div>` });
89
+ const automatic = settingsSection({ id: "automatic-cleanup", title: "Automatic cleanup",
90
+ lead: "Its branch stays. Checkouts with changes, commits on no branch, in use, or waiting for review are kept. When a task's work is already in main, Toolroll marks it complete and removes its checkout and local branches.",
91
+ body: kitSectionForm({ proof: "storage-cleanup", action: "/settings/storage", id: "cleanup-form", gate: true,
92
+ body: formRow("Remove a finished task's clean checkout", null, select({ id: "storage-cleanup", name: "cleanup", label: "Remove a finished task's clean checkout", current: plan.cleanup,
93
+ items: CLEANUP_CHOICES.map(one => [one.value, one.label]) }), "inline") }) });
94
+ return joinHtml([checkouts, cleanUp, automatic]);
89
95
  }
package/dist/store.d.ts CHANGED
@@ -5,6 +5,7 @@ import { type ProjectSandbox } from "./project-sandbox.js";
5
5
  import type { FencePolicy } from "./agent-fence.js";
6
6
  import { type NetworkProfile, type ProjectNetwork, type RunNetworkRecord } from "./egress.js";
7
7
  import { type AcpLaunch } from "./acp-attest.js";
8
+ export { refArchived, taskArchived } from "./task-archive.js";
8
9
  import { type ChatBinding } from "./chat-core.js";
9
10
  import { ChatMessages, TELEGRAM_HOLD_REASONS, TELEGRAM_SKIPPED_ELSEWHERE, TELEGRAM_SKIPPED_OTHER_CHAT, TELEGRAM_SKIPPED_QUIET } from "./chat-messages.js";
10
11
  import { isLifecycleNotification, isTelegramProgressNotification, LIFECYCLE_KEY_PREFIX, LIFECYCLE_KINDS, proposalTaskOf, type LifecycleKind } from "./notification-rows.js";
@@ -232,7 +233,7 @@ export type SealedLedgerEntry = LedgerEntry & {
232
233
  hash: string;
233
234
  } | null;
234
235
  };
235
- export declare const SCHEMA_VERSION = 143;
236
+ export declare const SCHEMA_VERSION = 146;
236
237
  /** v118: the tables merged into `credential`, in the order they move (a person's API tokens keep their ids first). */
237
238
  export declare const V118_MERGED_TABLES: Readonly<Record<string, CredentialKind>>;
238
239
  /**
@@ -394,6 +395,22 @@ export declare const V143_ADDED_COLUMNS: Readonly<Record<string, Readonly<Record
394
395
  definition: string;
395
396
  saved: string;
396
397
  }>>>>;
398
+ /** v145 (docs/plans/task-tidy.md): `task_archive` is a put-away task version. A row hides it from every list and count
399
+ * and refuses every door that would dispatch, claim or approve it; restore deletes the row. Its history, results,
400
+ * approvals and ledger stay exactly as they were. `merge_in_flight` is a direct merge between its last check and GitHub's
401
+ * answer, owned by its token: a task can't be put away under it (a row older than 15 minutes is a crash, and no
402
+ * longer counts). `deploy_in_flight` is a deployment whose candidate passed its gate (30 minutes, refreshed by every
403
+ * phase that proves it again). `flow_step_task` is the tasks a flow step was admitted for, kept whatever the card does
404
+ * next, so a step still running holds them. `branch_check_run` keeps each completed GitHub workflow run (pushed to
405
+ * the watched branch of the watched repository) the "checks failed" trigger saw, green or red, so tidy can tell a
406
+ * failure notice was fixed by a later commit. */
407
+ export declare const V145_SCHEMA = "\nCREATE TABLE IF NOT EXISTS task_archive (\n task_ref INTEGER PRIMARY KEY REFERENCES task_ref(id),\n by TEXT NOT NULL,\n by_kind TEXT NOT NULL CHECK (by_kind IN ('person','tidy')),\n rule TEXT,\n reason TEXT NOT NULL,\n at TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS branch_check_run (\n repo TEXT NOT NULL,\n run_id INTEGER NOT NULL,\n branch TEXT NOT NULL,\n workflow TEXT NOT NULL,\n head_sha TEXT NOT NULL,\n conclusion TEXT NOT NULL CHECK (conclusion IN ('success','failure')),\n run_created_at TEXT NOT NULL,\n observed_at TEXT NOT NULL,\n PRIMARY KEY (repo, run_id)\n);\nCREATE INDEX IF NOT EXISTS branch_check_run_by_workflow ON branch_check_run (repo, branch, workflow, run_created_at);\nCREATE TABLE IF NOT EXISTS merge_in_flight (\n publication INTEGER PRIMARY KEY REFERENCES publication(id),\n task_ref INTEGER NOT NULL REFERENCES task_ref(id),\n token TEXT NOT NULL,\n by TEXT NOT NULL,\n at TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS deploy_in_flight (\n task_ref INTEGER PRIMARY KEY REFERENCES task_ref(id),\n run INTEGER NOT NULL,\n at TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS flow_step_task (\n card INTEGER NOT NULL,\n entry INTEGER NOT NULL,\n task_id TEXT NOT NULL,\n PRIMARY KEY (card, entry, task_id)\n);\n";
408
+ /** v146: `ci_check` is one pull request check (a GitHub check run or commit status) as the plane's winning observation of
409
+ * that head read it: written only with the observation that won its generation, replacing that head's rows wholesale.
410
+ * `release_step` is one step of a release (release.ts RELEASE_STEPS) as `toolroll release` moved through it: when it
411
+ * started, when it ended and how; recording it never changes the release. Both are read-only history for the Ship and
412
+ * Release views and their CLI twins. */
413
+ export declare const V146_SCHEMA = "\nCREATE TABLE IF NOT EXISTS ci_check (\n github_repo TEXT NOT NULL,\n pr_number INTEGER NOT NULL,\n head_sha TEXT NOT NULL,\n -- A check's identity: its kind (check run or commit status), its workflow and its name, so two workflows' checks\n -- with the same name stay two rows (a failing one never hides behind a passing namesake).\n ident TEXT NOT NULL,\n name TEXT NOT NULL,\n workflow TEXT,\n state TEXT NOT NULL CHECK (state IN ('queued','running','passed','failed','skipped','cancelled','neutral')),\n started_at TEXT,\n completed_at TEXT,\n url TEXT,\n observed_at TEXT NOT NULL,\n PRIMARY KEY (github_repo, pr_number, head_sha, ident)\n);\nCREATE TABLE IF NOT EXISTS release_step (\n task_id TEXT NOT NULL,\n tag TEXT NOT NULL,\n version TEXT NOT NULL,\n pr INTEGER,\n step TEXT NOT NULL,\n state TEXT NOT NULL CHECK (state IN ('running','done','failed','waiting')),\n started_at TEXT NOT NULL,\n ended_at TEXT,\n detail TEXT,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (task_id, tag, step)\n);\nCREATE INDEX IF NOT EXISTS release_step_by_task ON release_step (task_id, updated_at);\n";
397
414
  /**
398
415
  * v135: one approval per release (docs/plans/release-once.md). `release_authority` is the single source of truth for a
399
416
  * release a person approved: one row per generation, changed only by compare-and-swap on `rev`, with a lease whose
@@ -3745,7 +3762,7 @@ export declare class Store {
3745
3762
  rejectedRun: number | null;
3746
3763
  } | {
3747
3764
  ok: false;
3748
- reason: "unknown-task" | "not-stalled" | "claimed" | "accepted-result" | "published" | "moved";
3765
+ reason: "unknown-task" | "not-stalled" | "claimed" | "accepted-result" | "published" | "moved" | "archived";
3749
3766
  };
3750
3767
  /**
3751
3768
  * Cancel, aware of who might be building right now. A live claim wins:
@@ -6072,8 +6089,13 @@ export declare class Store {
6072
6089
  file?: string | null;
6073
6090
  held?: string | null;
6074
6091
  }, now: Date): number;
6075
- /** A person approves a held script (one an imported flow brought): from now on it runs. False when it wasn't held. */
6076
- approveFlowScript(repo: string, name: string): boolean;
6092
+ /** A person approves a held script (one an imported flow brought): from now on it runs. The approval covers exactly
6093
+ * the version and content the person read (`seen`): the check and the release of the hold are one statement in one
6094
+ * transaction, so a script removed and brought back under the same name is never approved unseen. */
6095
+ approveFlowScript(repo: string, name: string, seen: {
6096
+ version: number;
6097
+ digest: string;
6098
+ }): "approved" | "changed" | "not-held";
6077
6099
  removeFlowScript(repo: string, name: string, by?: string, now?: Date): boolean;
6078
6100
  /** Claim a card's visit to a step zone. False when another pass holds it or it already finished. */
6079
6101
  claimFlowStep(step: {
@@ -6892,7 +6914,7 @@ export declare class Store {
6892
6914
  stop: RunStop;
6893
6915
  } | {
6894
6916
  ok: false;
6895
- reason: "no-stop" | "wrong-task" | "stopping" | "already-resumed" | "superseded" | "busy" | "review";
6917
+ reason: "no-stop" | "wrong-task" | "stopping" | "already-resumed" | "superseded" | "busy" | "review" | "archived";
6896
6918
  detail: string;
6897
6919
  };
6898
6920
  /** Every stop recorded on a task, newest request first. */
@@ -8064,3 +8086,158 @@ export declare function dropFeedbackImage(store: Store, runId: number, commentId
8064
8086
  ok: false;
8065
8087
  reason: "not-found" | "image-usable" | "not-authorized";
8066
8088
  };
8089
+ /** A put-away task version (v145, docs/plans/task-tidy.md). */
8090
+ export type TaskArchive = {
8091
+ taskRef: number;
8092
+ taskId: string;
8093
+ by: string;
8094
+ byKind: "person" | "tidy";
8095
+ rule: string | null;
8096
+ reason: string;
8097
+ at: string;
8098
+ };
8099
+ /** Who put a task away, and why: a person by name, or tidy by its rule. */
8100
+ export type ArchiveBy = {
8101
+ by: string;
8102
+ byKind: "person" | "tidy";
8103
+ rule: string | null;
8104
+ reason: string;
8105
+ };
8106
+ export type ArchiveRefusal = "unknown-task" | "live" | "archived" | "not-archived" | "blocked";
8107
+ /** The put-away record of this task, or null when it is not put away. */
8108
+ export declare function archiveOf(store: Store, taskId: string): TaskArchive | null;
8109
+ /** Every version of a task's family, by the same proven lineage the task page reads (`taskFamilyOf`: same project, each
8110
+ * revision bound to its parent's own run by its brief). A family whose lineage doesn't prove is just this task. */
8111
+ export declare function taskFamilyRefs(store: Store, taskId: string): {
8112
+ id: number;
8113
+ taskId: string;
8114
+ }[];
8115
+ /** The first family version with something still going on, or null: running, a live newest claim, an unfinished run,
8116
+ * a process Toolroll hasn't seen exit, a build whose processes were never recorded, a stop still settling, a follow-up
8117
+ * check asked for and not finished, a flow step running for its card, a release in flight (a live release approval
8118
+ * that names it, or a deploy or merge reserved, running or unknown), or a pull request merging (an intent claimed or
8119
+ * firing, or a person's merge between its last check and GitHub's answer). */
8120
+ export declare function liveFamilyAttempt(store: Store, refs: readonly number[], now: Date): string | null;
8121
+ /** Puts a task away (v145): every version of its family, in one write transaction that re-checks it has no live
8122
+ * attempt, that its versions are exactly `expect` when the caller showed a person a family (a preview), and that
8123
+ * `guard` (the caller's own rule, re-read under the lock) still allows it. Its state, history, results, approvals and
8124
+ * ledger are untouched; its open notifications are resolved; one ledger row records who and why. */
8125
+ export declare function archiveTaskFamily(store: Store, taskId: string, by: ArchiveBy, now: Date, options?: {
8126
+ expect?: readonly number[];
8127
+ guard?: (refs: readonly number[]) => string | null;
8128
+ }): {
8129
+ ok: true;
8130
+ refs: number[];
8131
+ } | {
8132
+ ok: false;
8133
+ reason: ArchiveRefusal;
8134
+ message: string;
8135
+ };
8136
+ /** Brings a put-away task back (v145): every version of its family, with a ledger row. Tidy never puts a restored
8137
+ * task away again (tidy.ts reads the `task restored` row). */
8138
+ export declare function restoreTaskFamily(store: Store, taskId: string, by: string, now: Date, guard?: (refs: readonly number[]) => string | null): {
8139
+ ok: true;
8140
+ refs: number[];
8141
+ } | {
8142
+ ok: false;
8143
+ reason: ArchiveRefusal;
8144
+ message: string;
8145
+ };
8146
+ /**
8147
+ * What a LIVE invite grants, for its own join page (Wave E, "Your access"): the role, who invited, the projects and
8148
+ * when it expires. Read without spending an attempt and under exactly inviteIsLive's predicate, so every dead shape
8149
+ * reads as null and still gets the one indistinguishable page (D6). Only a holder of the live token sees it.
8150
+ */
8151
+ export declare function liveInviteTerms(store: Store, tokenValue: string, now: Date): {
8152
+ role: "approver" | "viewer";
8153
+ mintedBy: string;
8154
+ expiresAt: string;
8155
+ projects: ProjectAccess;
8156
+ } | null;
8157
+ /** One pull request check as GitHub last reported it for the observed head (v146 ci_check). */
8158
+ /** `kind`: a check run or a commit status (part of the check's identity); a row built without it reads as a run when it
8159
+ * names a workflow, else as a status. */
8160
+ export type CiCheckRow = {
8161
+ name: string;
8162
+ workflow: string | null;
8163
+ state: "queued" | "running" | "passed" | "failed" | "skipped" | "cancelled" | "neutral";
8164
+ startedAt: string | null;
8165
+ completedAt: string | null;
8166
+ url: string | null;
8167
+ kind?: "run" | "status";
8168
+ };
8169
+ /** `observed`: the authoritative state of the same winning observation (ci_observation), which the overall status
8170
+ * always comes from; the rows only show each check's own timing. */
8171
+ export type ShipChecks = {
8172
+ githubRepo: string;
8173
+ prNumber: number;
8174
+ headSha: string;
8175
+ observedAt: string;
8176
+ observed: "passing" | "failing" | "running" | "none";
8177
+ checks: CiCheckRow[];
8178
+ };
8179
+ /** At most this many checks are kept for one head; GitHub's rollup is far smaller in practice. */
8180
+ export declare const CI_CHECKS_MAX = 200;
8181
+ /**
8182
+ * GitHub's statusCheckRollup (gh pr view --json statusCheckRollup) as check rows: CheckRun items (name, workflowName,
8183
+ * status, conclusion, startedAt, completedAt, detailsUrl) and StatusContext items (context, state, startedAt, targetUrl).
8184
+ * Strict: an item that is not one of those shapes, has no usable name, or names a state it doesn't know is dropped; one
8185
+ * row per identity (kind, workflow and name), and when one identity appears twice (a re-run) the worse state stands;
8186
+ * at most CI_CHECKS_MAX.
8187
+ */
8188
+ export declare function parseCiRollup(rollup: unknown): CiCheckRow[];
8189
+ /**
8190
+ * Replace one head's checks wholesale (v146). Call ONLY with an observation that won its generation (publish.ts settles
8191
+ * and records in one transaction): a losing, stale answer never writes here.
8192
+ */
8193
+ export declare function recordCiChecks(store: Store, githubRepo: string, prNumber: number, headSha: string, rows: readonly CiCheckRow[], now: Date): void;
8194
+ /** A pull request's checks for the head the plane's last winning observation recorded, or null when none was. */
8195
+ export declare function ciChecksFor(store: Store, githubRepo: string, prNumber: number): ShipChecks | null;
8196
+ /** A task's pull request checks: its family's newest publication that has a pull request number, and that pull
8197
+ * request's checks (ciChecksFor). Null: no pull request, or none observed yet. */
8198
+ export declare function shipChecksForTask(store: Store, taskId: string): ShipChecks | null;
8199
+ /** One release step as `toolroll release` moved through it (v146 release_step). */
8200
+ export type ReleaseStepRow = {
8201
+ step: string;
8202
+ state: "running" | "done" | "failed" | "waiting";
8203
+ startedAt: string;
8204
+ endedAt: string | null;
8205
+ detail: string | null;
8206
+ };
8207
+ export type ReleaseProgress = {
8208
+ tag: string;
8209
+ version: string;
8210
+ pr: number | null;
8211
+ startedAt: string;
8212
+ updatedAt: string;
8213
+ steps: ReleaseStepRow[];
8214
+ stopped: {
8215
+ step: string;
8216
+ message: string;
8217
+ at: string;
8218
+ } | null;
8219
+ };
8220
+ /** What a release coordinator says about one step. */
8221
+ export type ReleaseStepEntry = {
8222
+ taskId: string;
8223
+ tag: string;
8224
+ version: string;
8225
+ pr: number | null;
8226
+ step: string;
8227
+ state: ReleaseStepRow["state"];
8228
+ at: Date;
8229
+ detail?: string | null;
8230
+ };
8231
+ /**
8232
+ * Record a release step (v146): `running` or `waiting` opens it (its first start is kept), `done` or `failed` ends it.
8233
+ * A step that runs again after a stop starts its row again. A failed write throws here; the release's adapter swallows
8234
+ * it, so recording never changes a release.
8235
+ */
8236
+ export declare function recordReleaseStep(store: Store, entry: ReleaseStepEntry): void;
8237
+ /** A release's steps, for the release task: the newest tag recorded for it, its steps in the order they ran, and the
8238
+ * step it stopped at when the last thing recorded was a failure. Null: no step recorded. */
8239
+ export declare function releaseProgressFor(store: Store, taskId: string): ReleaseProgress | null;
8240
+ /** The release task whose steps were recorded most recently (v146), or null when none was. */
8241
+ export declare function latestReleaseStepTask(store: Store): string | null;
8242
+ /** Seconds a step or check took (to its end, or to `now` while it runs); null without a start. */
8243
+ export declare function elapsedSeconds(startedAt: string | null, endedAt: string | null, now: Date): number | null;