toolroll 0.9.53 → 0.9.55

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 (254) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +5 -4
  3. package/dist/agent-onboard.js +4 -1
  4. package/dist/approval-rules-ui.d.ts +3 -2
  5. package/dist/approval-rules-ui.js +15 -20
  6. package/dist/assignment-ui.d.ts +3 -2
  7. package/dist/assignment-ui.js +4 -11
  8. package/dist/backup-ui.d.ts +2 -1
  9. package/dist/backup-ui.js +16 -27
  10. package/dist/brand-mark.d.ts +3 -2
  11. package/dist/brand-mark.js +4 -4
  12. package/dist/browser/workspace.css +1 -1
  13. package/dist/browser/workspace.js +25 -25
  14. package/dist/browser-shell.d.ts +2 -1
  15. package/dist/browser-shell.js +9 -9
  16. package/dist/browser-workspace.d.ts +2 -3
  17. package/dist/browser-workspace.js +0 -3
  18. package/dist/chat-ask.js +8 -8
  19. package/dist/chat-core.d.ts +171 -0
  20. package/dist/chat-core.js +288 -0
  21. package/dist/chat-decide.d.ts +10 -8
  22. package/dist/chat-decide.js +26 -21
  23. package/dist/chat-delivery-state.d.ts +51 -75
  24. package/dist/chat-delivery-state.js +140 -170
  25. package/dist/chat-delivery.d.ts +21 -2
  26. package/dist/chat-delivery.js +313 -194
  27. package/dist/chat-flow.d.ts +29 -1
  28. package/dist/chat-flow.js +24 -50
  29. package/dist/chat-messages.d.ts +319 -0
  30. package/dist/chat-messages.js +841 -0
  31. package/dist/chat-migration.d.ts +30 -0
  32. package/dist/chat-migration.js +802 -0
  33. package/dist/chat-polish.d.ts +5 -4
  34. package/dist/chat-polish.js +9 -10
  35. package/dist/chat-quiet.d.ts +3 -0
  36. package/dist/chat-quiet.js +5 -0
  37. package/dist/check-levels-ui.d.ts +2 -1
  38. package/dist/check-levels-ui.js +26 -53
  39. package/dist/cli.d.ts +3 -1
  40. package/dist/cli.js +9 -3
  41. package/dist/coding-shipping-ui.d.ts +2 -1
  42. package/dist/coding-shipping-ui.js +6 -6
  43. package/dist/coding-ui.d.ts +3 -1
  44. package/dist/coding-ui.js +30 -29
  45. package/dist/contracts/chat-content.d.ts +71 -2
  46. package/dist/contracts/chat-content.js +22 -3
  47. package/dist/contracts/chat-tables.d.ts +11 -4
  48. package/dist/contracts/chat-tables.js +165 -87
  49. package/dist/contracts/cli.d.ts +1 -3
  50. package/dist/contracts/console-api.d.ts +1 -1
  51. package/dist/contracts/console-api.js +6 -5
  52. package/dist/contracts/discord-callback.js +2 -1
  53. package/dist/contracts/health.d.ts +1 -3
  54. package/dist/contracts/slack-callback.js +3 -2
  55. package/dist/contracts/store-json.d.ts +1 -5
  56. package/dist/contracts/store-json.js +1 -4
  57. package/dist/contracts/teams-callback.js +2 -1
  58. package/dist/control-ui.d.ts +5 -4
  59. package/dist/control-ui.js +9 -34
  60. package/dist/credentials-ui.d.ts +5 -4
  61. package/dist/credentials-ui.js +23 -24
  62. package/dist/demo-chat.d.ts +3 -3
  63. package/dist/demo-chat.js +29 -76
  64. package/dist/discord-chat.js +19 -38
  65. package/dist/discord-settings.d.ts +3 -2
  66. package/dist/discord-settings.js +21 -55
  67. package/dist/discord.js +4 -14
  68. package/dist/envelope.js +10 -0
  69. package/dist/evidence-pack.d.ts +2 -1
  70. package/dist/evidence-pack.js +45 -49
  71. package/dist/export-ui.d.ts +7 -2
  72. package/dist/export-ui.js +4 -10
  73. package/dist/export.js +4 -4
  74. package/dist/flow-gallery-ui.d.ts +4 -3
  75. package/dist/flow-gallery-ui.js +28 -43
  76. package/dist/flow-starters-ui.d.ts +2 -1
  77. package/dist/flow-starters-ui.js +12 -15
  78. package/dist/flow-triggers.d.ts +1 -0
  79. package/dist/flow-triggers.js +16 -14
  80. package/dist/flows-ui.d.ts +4 -3
  81. package/dist/flows-ui.js +19 -30
  82. package/dist/flows.js +2 -1
  83. package/dist/guides.js +108 -3
  84. package/dist/html.d.ts +76 -0
  85. package/dist/html.js +173 -0
  86. package/dist/integrations-ui.d.ts +2 -1
  87. package/dist/integrations-ui.js +19 -23
  88. package/dist/kits-ui.d.ts +5 -4
  89. package/dist/kits-ui.js +18 -20
  90. package/dist/knowledge-ui.d.ts +6 -5
  91. package/dist/knowledge-ui.js +26 -25
  92. package/dist/lead-context.d.ts +11 -1
  93. package/dist/lead-context.js +6 -8
  94. package/dist/ledger-view.d.ts +2 -1
  95. package/dist/ledger-view.js +27 -29
  96. package/dist/limits-ui.d.ts +2 -1
  97. package/dist/limits-ui.js +7 -7
  98. package/dist/live-bus.d.ts +50 -8
  99. package/dist/live-bus.js +133 -50
  100. package/dist/mate-tools.js +3 -2
  101. package/dist/mcp-oauth.js +35 -36
  102. package/dist/models-ui.d.ts +2 -1
  103. package/dist/models-ui.js +25 -35
  104. package/dist/modes.d.ts +13 -1
  105. package/dist/modes.js +17 -1
  106. package/dist/monitoring-ui.d.ts +4 -3
  107. package/dist/monitoring-ui.js +13 -26
  108. package/dist/notification-rows.d.ts +30 -0
  109. package/dist/notification-rows.js +102 -0
  110. package/dist/openrouter-models.d.ts +2 -1
  111. package/dist/openrouter-models.js +5 -9
  112. package/dist/operate.js +16 -9
  113. package/dist/people-audit-ui.d.ts +2 -1
  114. package/dist/people-audit-ui.js +13 -13
  115. package/dist/plane-review.js +5 -11
  116. package/dist/policy-ui.d.ts +3 -2
  117. package/dist/policy-ui.js +17 -23
  118. package/dist/project-delete-ui.d.ts +4 -3
  119. package/dist/project-delete-ui.js +15 -27
  120. package/dist/project-delete.js +21 -26
  121. package/dist/pull-request-ui.d.ts +2 -1
  122. package/dist/pull-request-ui.js +17 -32
  123. package/dist/recipe-ui.d.ts +8 -7
  124. package/dist/recipe-ui.js +39 -32
  125. package/dist/release-adapters.d.ts +60 -0
  126. package/dist/release-adapters.js +435 -0
  127. package/dist/release-cli.d.ts +24 -0
  128. package/dist/release-cli.js +181 -0
  129. package/dist/release-gate.d.ts +80 -0
  130. package/dist/release-gate.js +150 -0
  131. package/dist/release-links.d.ts +29 -0
  132. package/dist/release-links.js +102 -0
  133. package/dist/release.d.ts +331 -0
  134. package/dist/release.js +550 -0
  135. package/dist/reply-shape.d.ts +2 -1
  136. package/dist/reply-shape.js +5 -5
  137. package/dist/repository-context-ui.d.ts +2 -1
  138. package/dist/repository-context-ui.js +2 -7
  139. package/dist/request-budget-ui.d.ts +2 -1
  140. package/dist/request-budget-ui.js +16 -16
  141. package/dist/result-review.d.ts +2 -3
  142. package/dist/result-review.js +3 -9
  143. package/dist/result-shots.js +2 -2
  144. package/dist/retention-ui.d.ts +7 -1
  145. package/dist/retention-ui.js +7 -11
  146. package/dist/retention.js +1 -1
  147. package/dist/serve.d.ts +11 -2
  148. package/dist/serve.js +69 -58
  149. package/dist/server/chat.d.ts +3 -6
  150. package/dist/server/chat.js +969 -1006
  151. package/dist/server/chrome.d.ts +291 -0
  152. package/dist/server/chrome.js +747 -0
  153. package/dist/server/console.css +2819 -0
  154. package/dist/server/flows.d.ts +2 -6
  155. package/dist/server/flows.js +840 -766
  156. package/dist/server/guards.d.ts +1 -1
  157. package/dist/server/guards.js +5 -2
  158. package/dist/server/handler-context.d.ts +1 -1
  159. package/dist/server/handler-context.js +1 -1
  160. package/dist/server/handler-registry.d.ts +8 -6
  161. package/dist/server/handler-registry.js +14 -2
  162. package/dist/server/http.d.ts +274 -0
  163. package/dist/server/http.js +239 -0
  164. package/dist/server/live.d.ts +34 -0
  165. package/dist/server/live.js +309 -0
  166. package/dist/server/pages.d.ts +2 -6
  167. package/dist/server/pages.js +808 -789
  168. package/dist/server/people-tokens.d.ts +1 -6
  169. package/dist/server/people-tokens.js +580 -572
  170. package/dist/server/remote.d.ts +4 -7
  171. package/dist/server/remote.js +37 -70
  172. package/dist/server/render-chat.d.ts +402 -0
  173. package/dist/server/render-chat.js +927 -0
  174. package/dist/server/render-pages.d.ts +535 -0
  175. package/dist/server/render-pages.js +1277 -0
  176. package/dist/server/render-people.d.ts +32 -0
  177. package/dist/server/render-people.js +121 -0
  178. package/dist/server/render-results.d.ts +744 -0
  179. package/dist/server/render-results.js +2237 -0
  180. package/dist/server/render-settings.d.ts +147 -0
  181. package/dist/server/render-settings.js +577 -0
  182. package/dist/server/render-tasks.d.ts +632 -0
  183. package/dist/server/render-tasks.js +2345 -0
  184. package/dist/server/request-context.d.ts +46 -0
  185. package/dist/server/request-context.js +17 -0
  186. package/dist/server/route-table.d.ts +25 -3
  187. package/dist/server/route-table.js +163 -75
  188. package/dist/server/runtime.d.ts +15 -7
  189. package/dist/server/runtime.js +7 -1
  190. package/dist/server/session.d.ts +138 -0
  191. package/dist/server/session.js +94 -0
  192. package/dist/server/settings.d.ts +1 -5
  193. package/dist/server/settings.js +2182 -2117
  194. package/dist/server/task-views.d.ts +11 -4
  195. package/dist/server/task-views.js +26 -22
  196. package/dist/server/tasks.d.ts +9 -9
  197. package/dist/server/tasks.js +1486 -1418
  198. package/dist/server-telemetry.d.ts +4 -3
  199. package/dist/server-telemetry.js +4 -8
  200. package/dist/skills-ui.d.ts +4 -3
  201. package/dist/skills-ui.js +14 -17
  202. package/dist/skills.d.ts +1 -1
  203. package/dist/skills.js +2 -1
  204. package/dist/slack-api.d.ts +5 -1
  205. package/dist/slack-api.js +2 -2
  206. package/dist/slack-chat.d.ts +9 -4
  207. package/dist/slack-chat.js +29 -52
  208. package/dist/slack-settings.d.ts +3 -2
  209. package/dist/slack-settings.js +23 -57
  210. package/dist/slack.js +6 -16
  211. package/dist/spend-ui.d.ts +2 -1
  212. package/dist/spend-ui.js +9 -32
  213. package/dist/sso-ui.d.ts +3 -2
  214. package/dist/sso-ui.js +26 -27
  215. package/dist/storage-ui.d.ts +2 -1
  216. package/dist/storage-ui.js +14 -33
  217. package/dist/store.d.ts +97 -364
  218. package/dist/store.js +207 -1531
  219. package/dist/surface.js +14 -3
  220. package/dist/task-composer.d.ts +2 -1
  221. package/dist/task-composer.js +7 -7
  222. package/dist/task-status.d.ts +4 -3
  223. package/dist/task-status.js +15 -16
  224. package/dist/team-http.d.ts +5 -9
  225. package/dist/team-http.js +5 -63
  226. package/dist/team-ui.d.ts +2 -1
  227. package/dist/team-ui.js +2 -5
  228. package/dist/teammate-question.js +12 -12
  229. package/dist/teammates-ui.d.ts +5 -4
  230. package/dist/teammates-ui.js +61 -90
  231. package/dist/teams-chat.js +12 -9
  232. package/dist/teams-settings.d.ts +3 -2
  233. package/dist/teams-settings.js +22 -33
  234. package/dist/teams.js +4 -5
  235. package/dist/telegram-flow.js +5 -5
  236. package/dist/telegram-settings.d.ts +3 -2
  237. package/dist/telegram-settings.js +14 -25
  238. package/dist/telegram.js +4 -4
  239. package/dist/toolroll-update-ui.d.ts +3 -2
  240. package/dist/toolroll-update-ui.js +35 -39
  241. package/dist/toolroll-update.d.ts +10 -0
  242. package/dist/toolroll-update.js +50 -3
  243. package/dist/tools-ui.d.ts +5 -2
  244. package/dist/tools-ui.js +31 -31
  245. package/dist/when-html.d.ts +11 -3
  246. package/dist/when-html.js +5 -6
  247. package/dist/workspace-revision.js +3 -1
  248. package/dist/workspace-ui.d.ts +4 -2
  249. package/dist/workspace-ui.js +10 -10
  250. package/package.json +1 -1
  251. package/dist/server/shared.d.ts +0 -3151
  252. package/dist/server/shared.js +0 -12001
  253. package/dist/slack-state.d.ts +0 -13
  254. package/dist/slack-state.js +0 -10
@@ -1,75 +1,41 @@
1
1
  import { loadPrimary } from "./webhooks.js";
2
+ import { html, postForm } from "./html.js";
2
3
  import { loadDiscordCredentials } from "./discord-api.js";
3
4
  import { ChatState } from "./chat-delivery-state.js";
4
- const escape = (text) => text
5
- .replaceAll("&", "&")
6
- .replaceAll("<", "&lt;")
7
- .replaceAll(">", "&gt;")
8
- .replaceAll('"', "&quot;")
9
- .replaceAll("'", "&#39;");
10
- export function discordSettingsHtml(store, dir, csrf, options = {}) {
5
+ export function discordSettingsHtml(store, dir, options = {}) {
11
6
  const credentials = loadDiscordCredentials(dir), state = new ChatState(store, "discord"), bindings = credentials ? state.bindings(credentials.installation).filter(one => state.live(one)) : [], binding = options.who === undefined ? null : bindings.find(one => one.approver === options.who) ?? null, others = bindings.length - (binding === null ? 0 : 1);
12
- const runtime = credentials
13
- ? state.db
14
- .prepare("SELECT connected,problem,lease_until FROM discord_runtime WHERE installation=?")
15
- .get(credentials.installation)
16
- : null;
7
+ const runtime = credentials ? state.runtime(credentials.installation) : null;
17
8
  const now = options.now ?? new Date(), live = runtime &&
18
- typeof runtime.lease_until === "string" &&
19
- runtime.lease_until > now.toISOString() &&
9
+ runtime.leaseUntil !== null &&
10
+ runtime.leaseUntil > now.toISOString() &&
20
11
  runtime.connected;
21
- const hidden = `<input type="hidden" name="csrf" value="${escape(csrf)}">`;
22
- const password = '<label>Your Toolroll password<input style="min-height:44px" type="password" name="password" autocomplete="current-password" required></label>';
23
- const post = (action, content) => `<form method="post" action="/settings/discord/${action}" class="card">${hidden}${content}</form>`;
24
- let content = "";
12
+ const password = html `<label>Your Toolroll password<input style="min-height:44px" type="password" name="password" autocomplete="current-password" required></label>`;
13
+ const post = (action, content) => postForm(`/settings/discord/${action}`, content, { attrs: { class: "card" } });
14
+ const parts = [];
25
15
  if (!credentials) {
26
- content =
27
- "<p>Manage projects and review results in a private Discord conversation.</p>" +
28
- '<details open><summary>Create your Discord app</summary><ol><li><a href="https://discord.com/developers/applications" target="_blank" rel="noopener noreferrer">Create an application</a>, then copy its token from the Bot page.</li><li>Keep the Interactions Endpoint URL empty and privileged intents off.</li><li>Under Installation, enable Guild Install with the bot scope and no server permissions. Use its install link to add it to your server, then open a direct message to the bot.</li></ol></details>' +
29
- post("connect", '<label>Bot token<input style="min-height:44px" type="password" name="bot-token" autocomplete="off" required></label>' +
30
- password +
31
- '<p class="meta">The token stays on this installation. Next, pair your own Discord account.</p><button type="submit">Connect Discord</button>');
16
+ parts.push(html `<p>Manage projects and review results in a private Discord conversation.</p>`, html `<details open><summary>Create your Discord app</summary><ol><li><a href="https://discord.com/developers/applications" target="_blank" rel="noopener noreferrer">Create an application</a>, then copy its token from the Bot page.</li><li>Keep the Interactions Endpoint URL empty and privileged intents off.</li><li>Under Installation, enable Guild Install with the bot scope and no server permissions. Use its install link to add it to your server, then open a direct message to the bot.</li></ol></details>`, post("connect", html `<label>Bot token<input style="min-height:44px" type="password" name="bot-token" autocomplete="off" required></label>${password}<p class="meta">The token stays on this installation. Next, pair your own Discord account.</p><button type="submit">Connect Discord</button>`));
32
17
  }
33
18
  else {
34
- content = `<p><strong>${escape(credentials.workspace)}</strong> · ${live ? "Connected" : "Waiting for connection"}</p>`;
19
+ parts.push(html `<p><strong>${credentials.workspace}</strong> · ${live ? "Connected" : "Waiting for connection"}</p>`);
35
20
  if (runtime?.problem)
36
- content += `<p role="status">${escape(String(runtime.problem))}</p>`;
21
+ parts.push(html `<p role="status">${String(runtime.problem)}</p>`);
37
22
  if (!live && !runtime?.problem)
38
- content +=
39
- "<p>Keep the Toolroll worker running to receive Discord messages.</p>";
23
+ parts.push(html `<p>Keep the Toolroll worker running to receive Discord messages.</p>`);
40
24
  if (options.code) {
41
- content +=
42
- "<h2>Pair your account</h2><p>Send this in a direct message to your Toolroll Discord app. It expires in 10 minutes.</p>" +
43
- `<label>Pairing message<input type="text" readonly value="pair ${escape(options.code)}" autocomplete="off" style="width:100%;max-width:100%;font-size:14px;min-height:44px"></label>` +
44
- '<a class="button-link" style="min-height:44px;white-space:nowrap" href="/settings/discord">Check connection</a>';
25
+ parts.push(html `<h2>Pair your account</h2><p>Send this in a direct message to your Toolroll Discord app. It expires in 10 minutes.</p>`, html `<label>Pairing message<input type="text" readonly value="pair ${options.code}" autocomplete="off" style="width:100%;max-width:100%;font-size:14px;min-height:44px"></label>`, html `<a class="button-link" style="min-height:44px;white-space:nowrap" href="/settings/discord">Check connection</a>`);
45
26
  }
46
27
  else if (binding && state.live(binding)) {
47
- const pending = Number(state.db
48
- .prepare("SELECT count(*) n FROM discord_part p JOIN discord_event e ON e.id=p.event WHERE e.binding=? AND p.state='pending'")
49
- .get(binding.id)?.n ?? 0);
50
- const failed = Number(state.db
51
- .prepare("SELECT count(*) n FROM discord_part p JOIN discord_event e ON e.id=p.event WHERE e.binding=? AND p.state='dropped'")
52
- .get(binding.id)?.n ?? 0);
53
- content +=
54
- `<p>Your Discord account is paired.${others ? ` ${others} teammate${others === 1 ? " is" : "s are"} paired too.` : ""}${pending ? ` ${pending} replies waiting to send.` : ""}${failed ? ` ${failed} replies could not be delivered. Open the saved chat to recover them.` : ""}</p>` +
55
- "<p>Try “What needs my attention?” or “Show the evidence for the latest result.”</p>" +
56
- '<p><a href="/chat">Open saved chat</a></p>' +
57
- (loadPrimary(process.env, dir) === "discord"
58
- ? "<p>Task updates are sent here.</p>"
59
- : post("alerts", '<button type="submit">Send task updates here</button>'));
28
+ const { pending, dropped: failed } = state.partCounts(binding.id);
29
+ parts.push(html `<p>Your Discord account is paired.${others ? ` ${others} teammate${others === 1 ? " is" : "s are"} paired too.` : ""}${pending ? ` ${pending} replies waiting to send.` : ""}${failed ? ` ${failed} replies could not be delivered. Open the saved chat to recover them.` : ""}</p>`, html `<p>Try “What needs my attention?” or “Show the evidence for the latest result.”</p>`, html `<p><a href="/chat">Open saved chat</a></p>`, loadPrimary(process.env, dir) === "discord"
30
+ ? html `<p>Task updates are sent here.</p>`
31
+ : post("alerts", html `<button type="submit">Send task updates here</button>`));
60
32
  }
61
33
  else {
62
- content += post("pair", "<h2>Pair your account</h2><p>Your paired Discord account can read your connected projects and confirm proposed changes. Password approvals still open in Toolroll." +
63
- (others ? ` ${others} teammate${others === 1 ? " is" : "s are"} already paired.` : "") + "</p>" +
64
- password +
65
- '<button type="submit">Create pairing code</button>');
34
+ parts.push(post("pair", html `<h2>Pair your account</h2><p>Your paired Discord account can read your connected projects and confirm proposed changes. Password approvals still open in Toolroll.${others ? ` ${others} teammate${others === 1 ? " is" : "s are"} already paired.` : ""}</p>${password}<button type="submit">Create pairing code</button>`));
66
35
  }
67
36
  if (binding && state.live(binding) && !options.code)
68
- content += post("unpair", password + '<p class="meta">Unpairing ends every open button in your chat. Teammates are unaffected.</p><button type="submit">Unpair my account</button>');
69
- content +=
70
- "<details><summary>Connection settings</summary>" +
71
- post("disconnect", password + '<button type="submit">Disconnect Discord</button>') +
72
- "</details>";
37
+ parts.push(post("unpair", html `${password}<p class="meta">Unpairing ends every open button in your chat. Teammates are unaffected.</p><button type="submit">Unpair my account</button>`));
38
+ parts.push(html `<details><summary>Connection settings</summary>${post("disconnect", html `${password}<button type="submit">Disconnect Discord</button>`)}</details>`);
73
39
  }
74
- return `<section style="max-width:42rem;overflow-wrap:anywhere"><p><a href="/settings">Settings</a></p><h1>Discord</h1>${options.problem ? `<p class="problem" role="alert">${escape(options.problem)}</p>` : ""}${content}<details><summary>Access and setup</summary><p>Private messages and replies use your Toolroll permissions. Shared channels are not supported. Secure review links use this installation’s configured HTTPS console address.</p><p>The worker connects out to Discord. It does not need a public webhook or a Tailscale account.</p></details></section>`;
40
+ return html `<section style="max-width:42rem;overflow-wrap:anywhere"><p><a href="/settings">Settings</a></p><h1>Discord</h1>${options.problem ? html `<p class="problem" role="alert">${options.problem}</p>` : ""}${parts}<details><summary>Access and setup</summary><p>Private messages and replies use your Toolroll permissions. Shared channels are not supported. Secure review links use this installation’s configured HTTPS console address.</p><p>The worker connects out to Discord. It does not need a public webhook or a Tailscale account.</p></details></section>`;
75
41
  }
package/dist/discord.js CHANGED
@@ -37,9 +37,7 @@ export async function followDiscord(options) {
37
37
  current.botToken === credentials.botToken &&
38
38
  state.owns(credentials.installation, owner));
39
39
  };
40
- const problem = (message) => state
41
- .prepare("UPDATE chat_runtime SET problem=? WHERE installation=? AND owner=?")
42
- .run(message, credentials.installation, owner);
40
+ const problem = (message) => state.setProblem(credentials.installation, owner, message);
43
41
  const api = discordApi(credentials.botToken);
44
42
  const client = new Client({
45
43
  // Guild messages and their content are needed for rooms; the Message
@@ -63,9 +61,7 @@ export async function followDiscord(options) {
63
61
  if (!same() || !validated)
64
62
  return;
65
63
  connected = true;
66
- state
67
- .prepare("UPDATE chat_runtime SET connected=?,problem=NULL WHERE installation=? AND owner=?")
68
- .run(new Date().toISOString(), credentials.installation, owner);
64
+ state.setConnected(credentials.installation, owner, new Date());
69
65
  };
70
66
  client.on("shardReady", online);
71
67
  client.on("shardResume", online);
@@ -133,11 +129,7 @@ export async function followDiscord(options) {
133
129
  try {
134
130
  await client.login(credentials.botToken);
135
131
  while (same()) {
136
- const retry = state
137
- .prepare("SELECT retry_at FROM chat_runtime WHERE installation=?")
138
- .get(credentials.installation)?.retry_at;
139
- if (!connected ||
140
- (typeof retry === "string" && retry > new Date().toISOString())) {
132
+ if (!connected || state.retryAt(credentials.installation) > new Date().toISOString()) {
141
133
  await pause(1000);
142
134
  continue;
143
135
  }
@@ -160,9 +152,7 @@ export async function followDiscord(options) {
160
152
  alive = false;
161
153
  options.signal.removeEventListener("abort", stop);
162
154
  await client.destroy().catch(() => { });
163
- state
164
- .prepare("UPDATE chat_runtime SET owner=NULL,lease_until=NULL,connected=NULL WHERE installation=? AND owner=?")
165
- .run(credentials.installation, owner);
155
+ state.stop(credentials.installation, owner);
166
156
  }
167
157
  await pause(5000);
168
158
  }
package/dist/envelope.js CHANGED
@@ -71,6 +71,16 @@ export const DOCUMENTED_REASONS = [
71
71
  "repair-attempts-spent", // the chain's signed attempt cap is spent
72
72
  "repair-no-progress", // two consecutive repair attempts failed to shrink the unresolved set
73
73
  "repair-refused-integrity", // a refutation named an altered term, not a gap — no automatic repair
74
+ "ambiguous", // a value could mean two things (a lease id or a branch) — refused, never guessed
75
+ "drift", // the branch, pull request or result moved from the gated commit
76
+ "check-failed", // the release check failed; it stays visible and nothing reruns it
77
+ "ci-failed", // a required pull-request check failed or is missing — nothing merged
78
+ "base-moved", // main gained a commit the release branch lacks — nothing merged
79
+ "tree-mismatch", // main's merge commit has a different tree than the gated commit — nothing tagged
80
+ "conflict", // a task or tag exists for a different commit
81
+ "mixed-runtime", // the console, worker and CLI names don't run one build
82
+ "gate-held", // a deployment stopped before its swap and left new work paused — `release --release-gate <id>`
83
+ "after-swap", // the deployment got past stopping the service, so its pause is its own recovery's to lift
74
84
  ];
75
85
  let captured = null;
76
86
  let captureHook = null;
@@ -2,6 +2,7 @@ import { type Filer } from "./approval-policy.js";
2
2
  import { type AssignmentAccess, type AssignmentSnapshot } from "./assignment.js";
3
3
  import type { SealedLedgerEntry, Store } from "./store.js";
4
4
  import type { LedgerChainReport } from "./ledger-chain.js";
5
+ import { type Html } from "./html.js";
5
6
  export declare const EVIDENCE_PACK_FORMAT = "standing-orders/evidence-pack/v1";
6
7
  /** How to recheck one entry's seal, for whoever reads the JSON. */
7
8
  export declare const SEAL_RECIPE = "hash = sha256(prev + \"\\n\" + JSON.stringify([id, at, actor, repo, taskId, runId, action, outcome, source, detail]))";
@@ -179,7 +180,7 @@ export declare function ledgerExportChunks(store: Store, range: {
179
180
  }, access: AssignmentAccess, who: string, now: Date, evidenceRoot: string): Generator<string, void, undefined>;
180
181
  export declare const EVIDENCE_PACK_CSS: string;
181
182
  /** The pack as a page: the same facts, in the order an auditor reads them. No script. */
182
- export declare function evidencePackHtml(pack: EvidencePack): string;
183
+ export declare function evidencePackHtml(pack: EvidencePack): Html;
183
184
  /** The page as a file on its own (the CLI's --html): the same facts with just enough style to print. */
184
185
  export declare function standaloneEvidenceHtml(pack: EvidencePack): string;
185
186
  export {};
@@ -15,6 +15,7 @@ import { rulesWords } from "./approval-policy.js";
15
15
  import { assignmentOf } from "./assignment.js";
16
16
  import { projectName } from "./project.js";
17
17
  import { changedFilesOf } from "./result-completion.js";
18
+ import { html, htmlString, joinHtml, styleElement } from "./html.js";
18
19
  export const EVIDENCE_PACK_FORMAT = "standing-orders/evidence-pack/v1";
19
20
  /** How to recheck one entry's seal, for whoever reads the JSON. */
20
21
  export const SEAL_RECIPE = 'hash = sha256(prev + "\\n" + JSON.stringify([id, at, actor, repo, taskId, runId, action, outcome, source, detail]))';
@@ -173,13 +174,12 @@ export function* ledgerExportChunks(store, range, scope, access, who, now, evide
173
174
  yield `],"truncated":${JSON.stringify({ entries: moreEntries, packs: morePacks })}}`;
174
175
  }
175
176
  // ---- The printable page -----------------------------------------------------
176
- const e = (value) => String(value ?? "").replace(/[&<>"']/g, c => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
177
- const when = (at) => at === null ? "—" : `${e(at.slice(0, 16).replace("T", " "))} UTC`;
178
- const short = (hash) => hash === null ? "—" : `<code title="${e(hash)}">${e(hash.slice(0, 12))}</code>`;
177
+ const when = (at) => at === null ? html `—` : html `${at.slice(0, 16).replace("T", " ")} UTC`;
178
+ const short = (hash) => hash === null ? html `—` : html `<code title="${hash}">${hash.slice(0, 12)}</code>`;
179
179
  const usd = (value) => value === null ? "—" : `$${value.toFixed(value < 1 ? 4 : 2)}`;
180
180
  /** Only a web address links; anything else (a stored value that isn't one) is shown as text. */
181
- const link = (url) => /^https:\/\/[^\s"'<>]+$/.test(url) ? `<a href="${e(url)}">${e(url)}</a>` : e(url);
182
- const filer = (who) => who === null || who.name === null ? "automation" : who.kind === "person" ? e(who.name) : `${e(who.name)} (${e(who.kind)})`;
181
+ const link = (url) => /^https:\/\/[^\s"'<>]+$/.test(url) ? html `<a href="${url}">${url}</a>` : html `${url}`;
182
+ const filer = (who) => who === null || who.name === null ? html `automation` : who.kind === "person" ? html `${who.name}` : html `${who.name} (${who.kind})`;
183
183
  export const EVIDENCE_PACK_CSS = `.evidence-pack{max-width:960px;min-width:0}.evidence-pack h1{margin:0}.evidence-pack h2{font-size:.9375rem;margin:24px 0 8px}.evidence-pack h3{font-size:.875rem;margin:16px 0 6px}` +
184
184
  `.evidence-head{display:flex;justify-content:space-between;gap:12px;flex-wrap:wrap;align-items:baseline}.evidence-pack dl{display:grid;grid-template-columns:minmax(120px,180px) minmax(0,1fr);gap:4px 16px;margin:0}` +
185
185
  `.evidence-pack dt{color:var(--muted-foreground);font-size:.8125rem}.evidence-pack dd{margin:0;min-width:0;overflow-wrap:anywhere}.evidence-pack table{width:100%;border-collapse:collapse;font-size:.8125rem}` +
@@ -193,66 +193,62 @@ export const EVIDENCE_PACK_CSS = `.evidence-pack{max-width:960px;min-width:0}.ev
193
193
  export function evidencePackHtml(pack) {
194
194
  const task = pack.task;
195
195
  const chain = pack.ledger.chain.ok
196
- ? `<p class="evidence-chain ok" data-evidence-chain="ok"><strong>Ledger chain verified</strong> · ${pack.ledger.chain.entries} entries · head ${short(pack.ledger.chain.head)}${pack.ledger.chain.checkedAt === null ? "" : ` · checked in full ${when(pack.ledger.chain.checkedAt)}`}${pack.ledger.checkpoint === null ? "" : ` · last checkpoint at entry #${pack.ledger.checkpoint.through}`}</p>`
197
- : `<p class="evidence-chain problem" role="alert" data-evidence-chain="broken"><strong>Ledger chain broken</strong> · ${e(pack.ledger.chain.problem)}</p>`;
196
+ ? html `<p class="evidence-chain ok" data-evidence-chain="ok"><strong>Ledger chain verified</strong> · ${pack.ledger.chain.entries} entries · head ${short(pack.ledger.chain.head)}${pack.ledger.chain.checkedAt === null ? "" : html ` · checked in full ${when(pack.ledger.chain.checkedAt)}`}${pack.ledger.checkpoint === null ? "" : html ` · last checkpoint at entry #${pack.ledger.checkpoint.through}`}</p>`
197
+ : html `<p class="evidence-chain problem" role="alert" data-evidence-chain="broken"><strong>Ledger chain broken</strong> · ${pack.ledger.chain.problem}</p>`;
198
198
  const versions = pack.versions.map((version, index) => {
199
199
  const scope = version.scope;
200
200
  const approved = scope?.approved ?? null;
201
201
  const approvals = scope === null ? [] : scope.approvals.filter(one => one.digest === (approved?.digest ?? scope.digest));
202
202
  const rows = [
203
- ["Filed", `${when(version.filedAt)} by ${filer(version.filedBy)}`],
204
- ...(scope === null ? [["Scope", "None written"]] : [
205
- ["Goal", e(scope.goal)],
206
- ...(scope.touches.length ? [["Touches", scope.touches.map(e).join(", ")]] : []),
207
- ...(scope.acceptance.length ? [["Acceptance", `<ol class="criteria">${scope.acceptance.map(one => `<li>${e(one.statement)}</li>`).join("")}</ol>`]] : []),
208
- ...(scope.writtenBy.length ? [["Scope written by", [...new Set(scope.writtenBy.map(one => one.author))].map(e).join(", ")]] : []),
209
- ["Approved", approved === null ? "Not approved" : `${e(approved.by)}${approved.basis === "mode" ? " (operating mode)" : ""} · ${when(approved.at)}${approved.current ? "" : " · the scope changed after this"}`],
210
- ...(approvals.length > 1 ? [["Approvals", approvals.map(one => `${e(one.approver)} · ${when(one.at)}`).join("<br>")]] : []),
203
+ ["Filed", html `${when(version.filedAt)} by ${filer(version.filedBy)}`],
204
+ ...(scope === null ? [["Scope", html `None written`]] : [
205
+ ["Goal", html `${scope.goal}`],
206
+ ...(scope.touches.length ? [["Touches", joinHtml(scope.touches, ", ")]] : []),
207
+ ...(scope.acceptance.length ? [["Acceptance", html `<ol class="criteria">${scope.acceptance.map(one => html `<li>${one.statement}</li>`)}</ol>`]] : []),
208
+ ...(scope.writtenBy.length ? [["Scope written by", joinHtml([...new Set(scope.writtenBy.map(one => one.author))], ", ")]] : []),
209
+ ["Approved", approved === null ? html `Not approved` : html `${approved.by}${approved.basis === "mode" ? " (operating mode)" : ""} · ${when(approved.at)}${approved.current ? "" : " · the scope changed after this"}`],
210
+ ...(approvals.length > 1 ? [["Approvals", joinHtml(approvals.map(one => html `${one.approver} · ${when(one.at)}`), html `<br>`)]] : []),
211
211
  ["Scope digest", short(scope.digest)],
212
212
  ]),
213
213
  ];
214
- const runs = version.runs.length === 0 ? `<p class="meta">No agent ran.</p>` :
215
- `<div class="evidence-table"><table><thead><tr><th>Run</th><th>Role</th><th>Agent</th><th>Outcome</th><th>Cost</th><th>Finished</th><th>Commit</th></tr></thead><tbody>` +
216
- version.runs.map(run => `<tr><td><a href="/r/${run.id}">#${run.id}</a></td><td>${e(run.role)}</td><td>${e(run.agent)}${run.model ? ` · ${e(run.model)}` : ""}</td><td>${e(run.outcome ?? "running")}</td><td>${usd(run.costUsd)}</td><td>${when(run.finishedAt)}</td><td>${short(run.head)}</td></tr>`).join("") +
217
- `</tbody></table></div>`;
218
- const changes = version.runs.filter(run => run.role === "builder" || run.role === "repair").map(run => `<p class="meta">Run #${run.id}${run.diff === null ? "" : ` · diff ${short(run.diff.sha256)}${run.diff.complete ? "" : " (partial)"}`}</p>` +
219
- (run.changedFiles === null ? `<p class="meta">Its changed files couldn't be read.</p>` : run.changedFiles.length === 0 ? `<p class="meta">No files changed.</p>` : `<ul class="files">${run.changedFiles.map(file => `<li>${e(file)}</li>`).join("")}</ul>`)).join("");
220
- return `<section data-evidence-version="${e(version.id)}"><h2>${pack.versions.length > 1 ? `${index === 0 ? "Request" : `Revision ${index}`} · ` : "Request and approval · "}${e(version.id)}</h2><dl>${rows.map(([label, value]) => `<dt>${label}</dt><dd>${value}</dd>`).join("")}</dl>` +
221
- `<h3>Agents</h3>${runs}${changes === "" ? "" : `<h3>Changes</h3>${changes}`}</section>`;
222
- }).join("");
214
+ const runs = version.runs.length === 0 ? html `<p class="meta">No agent ran.</p>` :
215
+ html `<div class="evidence-table"><table><thead><tr><th>Run</th><th>Role</th><th>Agent</th><th>Outcome</th><th>Cost</th><th>Finished</th><th>Commit</th></tr></thead><tbody>${version.runs.map(run => html `<tr><td><a href="/r/${run.id}">#${run.id}</a></td><td>${run.role}</td><td>${run.agent}${run.model ? html ` · ${run.model}` : ""}</td><td>${run.outcome ?? "running"}</td><td>${usd(run.costUsd)}</td><td>${when(run.finishedAt)}</td><td>${short(run.head)}</td></tr>`)}</tbody></table></div>`;
216
+ const changes = version.runs.filter(run => run.role === "builder" || run.role === "repair").map(run => html `<p class="meta">Run #${run.id}${run.diff === null ? "" : html ` · diff ${short(run.diff.sha256)}${run.diff.complete ? "" : " (partial)"}`}</p>${run.changedFiles === null ? html `<p class="meta">Its changed files couldn't be read.</p>` : run.changedFiles.length === 0 ? html `<p class="meta">No files changed.</p>` : html `<ul class="files">${run.changedFiles.map(file => html `<li>${file}</li>`)}</ul>`}`);
217
+ return html `<section data-evidence-version="${version.id}"><h2>${pack.versions.length > 1 ? `${index === 0 ? "Request" : `Revision ${index}`} · ` : "Request and approval · "}${version.id}</h2><dl>${rows.map(([label, value]) => html `<dt>${label}</dt><dd>${value}</dd>`)}</dl><h3>Agents</h3>${runs}${changes.length === 0 ? "" : html `<h3>Changes</h3>${changes}`}</section>`;
218
+ });
223
219
  const result = pack.result;
224
- const outcome = result === null ? `<section><h2>Result</h2><p class="meta">No finished result yet.</p></section>` :
225
- `<section data-evidence-result><h2>Result</h2><dl>` +
226
- `<dt>Checks</dt><dd>${e(result.checks.detail)}${result.checks.command ? ` <code>${e(result.checks.command)}</code>` : ""}</dd>` +
227
- `<dt>Marked complete</dt><dd>${result.completedBy === null ? "Not yet" : `${e(result.completedBy)} · ${when(result.completedAt)}`}</dd>` +
228
- `<dt>Published</dt><dd>${result.publication === null ? "Not published" : `${e(result.publication.state)}${result.publication.prUrl ? ` · ${link(result.publication.prUrl)}` : ""}${result.publication.remoteState ? ` · ${e(result.publication.remoteState.toLowerCase())}` : ""}`}</dd>` +
229
- `<dt>Commit</dt><dd>${short(result.base)} → ${short(result.head)}</dd>` +
230
- (result.caveats.length ? `<dt>Caveats</dt><dd>${result.caveats.map(e).join("<br>")}</dd>` : "") +
231
- `</dl></section>`;
220
+ const outcome = result === null ? html `<section><h2>Result</h2><p class="meta">No finished result yet.</p></section>` :
221
+ joinHtml([html `<section data-evidence-result><h2>Result</h2><dl>`,
222
+ html `<dt>Checks</dt><dd>${result.checks.detail}${result.checks.command ? html ` <code>${result.checks.command}</code>` : ""}</dd>`,
223
+ html `<dt>Marked complete</dt><dd>${result.completedBy === null ? "Not yet" : html `${result.completedBy} · ${when(result.completedAt)}`}</dd>`,
224
+ html `<dt>Published</dt><dd>${result.publication === null ? "Not published" : html `${result.publication.state}${result.publication.prUrl ? html ` · ${link(result.publication.prUrl)}` : ""}${result.publication.remoteState ? html ` · ${result.publication.remoteState.toLowerCase()}` : ""}`}</dd>`,
225
+ html `<dt>Commit</dt><dd>${short(result.base)} → ${short(result.head)}</dd>`,
226
+ result.caveats.length ? html `<dt>Caveats</dt><dd>${joinHtml(result.caveats, html `<br>`)}</dd>` : "",
227
+ html `</dl></section>`]);
232
228
  const decisions = pack.ledger.entries.filter(one => one.source !== "request");
233
229
  const requests = pack.ledger.entries.length - decisions.length;
234
230
  const askedWords = `${requests} ${requests === 1 ? "request" : "requests"} made through the console (accepted or refused)`;
235
- const ledger = `<section><h2>Ledger</h2>${decisions.length === 0 ? `<p class="meta">No entries.</p>` :
236
- `<div class="evidence-table"><table><thead><tr><th>#</th><th>Time</th><th>Who</th><th>What</th><th>Outcome</th><th>Seal</th></tr></thead><tbody>` +
237
- decisions.map(one => `<tr data-ledger-id="${one.id}"><td>${one.id}</td><td>${when(one.at)}</td><td>${e(one.actor)}</td><td>${e(one.action)}${one.detail ? `<br><span class="meta">${e(one.detail)}</span>` : ""}</td><td>${e(one.outcome)}</td><td>${short(one.seal?.hash ?? null)}</td></tr>`).join("") +
238
- `</tbody></table></div>`}${requests > 0 ? `<p class="meta">Plus ${askedWords}, listed in the JSON.</p>` : ""}${pack.ledger.truncated ? `<p class="meta">This pack holds ${decisions.length} of the task's entries and the newest ${requests} requests; the ledger export has the rest.</p>` : ""}</section>`;
231
+ const ledger = html `<section><h2>Ledger</h2>${decisions.length === 0 ? html `<p class="meta">No entries.</p>` :
232
+ html `<div class="evidence-table"><table><thead><tr><th>#</th><th>Time</th><th>Who</th><th>What</th><th>Outcome</th><th>Seal</th></tr></thead><tbody>${decisions.map(one => html `<tr data-ledger-id="${one.id}"><td>${one.id}</td><td>${when(one.at)}</td><td>${one.actor}</td><td>${one.action}${one.detail ? html `<br><span class="meta">${one.detail}</span>` : ""}</td><td>${one.outcome}</td><td>${short(one.seal?.hash ?? null)}</td></tr>`)}</tbody></table></div>`}${requests > 0 ? html `<p class="meta">Plus ${askedWords}, listed in the JSON.</p>` : ""}${pack.ledger.truncated ? html `<p class="meta">This pack holds ${decisions.length} of the task's entries and the newest ${requests} requests; the ledger export has the rest.</p>` : ""}</section>`;
239
233
  const cost = pack.totals.runs === 0 ? "None ran" : `${pack.totals.runs} ${pack.totals.runs === 1 ? "run" : "runs"} · ${usd(pack.totals.costUsd)}`;
240
234
  // The rules as they stand, and each change to them (the approval above was under whichever applied then).
241
- const rules = pack.rules === null ? "None" : `${e(pack.rules.now)}${pack.rules.changes.length === 0 ? "" :
242
- `<ul class="criteria">${pack.rules.changes.slice(-5).map(one => `<li class="meta">${when(one.at)} · ${e(one.by)}: ${e(one.change)}</li>`).join("")}</ul>`}`;
243
- return `<article class="evidence-pack" data-evidence-pack="${e(task.id)}">` +
244
- `<div class="evidence-head"><h1>Evidence pack</h1><p class="evidence-actions"><a href="/t/${encodeURIComponent(task.id)}/evidence?format=json" download>Download JSON</a> · <a href="/t/${encodeURIComponent(task.id)}">Back to task</a></p></div>` +
245
- `<p><strong>${e(task.title)}</strong></p>` +
246
- `<dl><dt>Task</dt><dd>${e(task.id)}${task.project === null ? "" : ` · ${e(task.project)}`} · ${e(task.state)}</dd>` +
247
- `<dt>Approval rules</dt><dd>${rules}</dd><dt>Agents</dt><dd>${cost}</dd>` +
248
- `<dt>Prepared</dt><dd>${when(pack.generatedAt)} for ${e(pack.generatedBy)}</dd></dl>${chain}` +
249
- versions + outcome + ledger +
250
- `<p class="evidence-foot">Pack digest <code>${e(pack.digest)}</code>. Each ledger entry's seal can be rechecked from the JSON: ${e(pack.ledger.recipe)}.</p></article>`;
235
+ const rules = pack.rules === null ? html `None` : html `${pack.rules.now}${pack.rules.changes.length === 0 ? "" :
236
+ html `<ul class="criteria">${pack.rules.changes.slice(-5).map(one => html `<li class="meta">${when(one.at)} · ${one.by}: ${one.change}</li>`)}</ul>`}`;
237
+ return joinHtml([html `<article class="evidence-pack" data-evidence-pack="${task.id}">`,
238
+ html `<div class="evidence-head"><h1>Evidence pack</h1><p class="evidence-actions"><a href="/t/${encodeURIComponent(task.id)}/evidence?format=json" download>Download JSON</a> · <a href="/t/${encodeURIComponent(task.id)}">Back to task</a></p></div>`,
239
+ html `<p><strong>${task.title}</strong></p>`,
240
+ html `<dl><dt>Task</dt><dd>${task.id}${task.project === null ? "" : ` · ${task.project}`} · ${task.state}</dd>`,
241
+ html `<dt>Approval rules</dt><dd>${rules}</dd><dt>Agents</dt><dd>${cost}</dd>`,
242
+ html `<dt>Prepared</dt><dd>${when(pack.generatedAt)} for ${pack.generatedBy}</dd></dl>${chain}`,
243
+ versions, outcome, ledger,
244
+ html `<p class="evidence-foot">Pack digest <code>${pack.digest}</code>. Each ledger entry's seal can be rechecked from the JSON: ${pack.ledger.recipe}.</p></article>`]);
251
245
  }
252
246
  /** The page as a file on its own (the CLI's --html): the same facts with just enough style to print. */
253
247
  export function standaloneEvidenceHtml(pack) {
254
- return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Evidence pack · ${e(pack.task.id)}</title>` +
255
- `<style>:root{--muted-foreground:#5c5c5c;--border:#dedede;--success:#1a7f37;--font-mono:ui-monospace,SFMono-Regular,Menlo,monospace}` +
248
+ const css = `:root{--muted-foreground:#5c5c5c;--border:#dedede;--success:#1a7f37;--font-mono:ui-monospace,SFMono-Regular,Menlo,monospace}` +
256
249
  `body{font:14px/1.5 system-ui,-apple-system,sans-serif;margin:32px auto;padding:0 16px;max-width:960px;color:#171717;background:#fff}.meta{color:var(--muted-foreground);font-size:.8125rem}.evidence-actions{display:none}` +
257
- EVIDENCE_PACK_CSS + `</style></head><body>${evidencePackHtml(pack)}</body></html>\n`;
250
+ EVIDENCE_PACK_CSS;
251
+ // The file is the sink: its bytes.
252
+ return htmlString(html `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Evidence pack · ${pack.task.id}</title>${styleElement(css)}</head><body>${evidencePackHtml(pack)}</body></html>
253
+ `);
258
254
  }
@@ -1,4 +1,9 @@
1
+ /**
2
+ * Settings → Data (v105): an instance operator downloads everything Standing
3
+ * Orders knows as one .zip. One form: the password, typed again, and a button.
4
+ */
5
+ import { type Html } from "./html.js";
1
6
  export declare const EXPORT_CSS: string;
2
- export declare function dataExportHtml(csrf: string, notice: {
7
+ export declare function dataExportHtml(notice: {
3
8
  problem?: string | null;
4
- }): string;
9
+ }): Html;
package/dist/export-ui.js CHANGED
@@ -2,16 +2,10 @@
2
2
  * Settings → Data (v105): an instance operator downloads everything Standing
3
3
  * Orders knows as one .zip. One form: the password, typed again, and a button.
4
4
  */
5
- const e = (value) => String(value ?? "").replace(/[&<>"']/g, c => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
5
+ import { html, postForm } from "./html.js";
6
6
  export const EXPORT_CSS = `.data-export{max-width:720px;min-width:0}.data-export label{display:grid;gap:4px;margin:14px 0 0;font-size:.875rem}` +
7
7
  `.data-export input[type=password]{width:100%;max-width:360px;box-sizing:border-box}.data-export button[type=submit]{margin-top:14px;min-height:44px;white-space:nowrap}`;
8
- export function dataExportHtml(csrf, notice) {
9
- const note = notice.problem ? `<p class="problem" role="alert">${e(notice.problem)}</p>` : "";
10
- return `<section class="data-export">${note}` +
11
- `<p>Everything Toolroll knows, as one .zip: projects, tasks, runs and cost, the action ledger, evidence packs, chats, flows, teammates and settings. ` +
12
- `Passwords, keys and tokens are never included.</p>` +
13
- `<form method="post" action="/settings/data"><input type="hidden" name="csrf" value="${e(csrf)}">` +
14
- `<label>Your Toolroll password<input type="password" name="password" autocomplete="current-password" required></label>` +
15
- `<button type="submit">Download export</button></form>` +
16
- `<p class="meta">The same export from the command line: <code>toolroll export --out &lt;path&gt; [--zip]</code></p></section>`;
8
+ export function dataExportHtml(notice) {
9
+ const note = notice.problem ? html `<p class="problem" role="alert">${notice.problem}</p>` : "";
10
+ return html `<section class="data-export">${note}<p>Everything Toolroll knows, as one .zip: projects, tasks, runs and cost, the action ledger, evidence packs, chats, flows, teammates and settings. Passwords, keys and tokens are never included.</p>${postForm("/settings/data", html `<label>Your Toolroll password<input type="password" name="password" autocomplete="current-password" required></label><button type="submit">Download export</button>`)}<p class="meta">The same export from the command line: <code>toolroll export --out &lt;path&gt; [--zip]</code></p></section>`;
17
11
  }
package/dist/export.js CHANGED
@@ -29,20 +29,20 @@ import { readEmailSettings } from "./email-settings.js";
29
29
  import { SUBSCRIPTION_CAPABLE, readAuthMode } from "./keys.js";
30
30
  export const EXPORT_FORMAT = "standing-orders/export/v1";
31
31
  /** Tables that hold nothing but secrets or short-lived sign-in material, and derived search indexes. */
32
- const SECRET_TABLES = new Set(["web_session", "ceremony_nonce", "telegram_pairing", "slack_pair", "discord_pair", "teams_pair", "oauth_code", "oauth_refresh"]);
32
+ const SECRET_TABLES = new Set(["web_session", "ceremony_nonce", "chat_pair", "oauth_code", "oauth_refresh"]);
33
33
  const DERIVED_TABLE = /^(sqlite_|memory_search)/;
34
34
  /** Columns that hold a secret (or what stands in for one): never exported. */
35
35
  const SECRET_COLUMNS = new Set([
36
36
  "api_token.secret_hash", "lead_credential.secret_hash", "approver.credential_hash", "coordinator_credential.credential_hash", "runner.credential_hash", "invite.token_hash",
37
37
  "flow_trigger.hook_hash", "push_subscription.endpoint", "push_subscription.p256dh", "push_subscription.auth",
38
- "chat_turn.credential_key", "mate_session.credential_key", "mate_turn.credential_key", "bridge_lease.push_url", "workflow_preview.token",
38
+ "chat_turn.credential_key", "mate_session.credential_key", "mate_turn.credential_key", "chat_runtime.push_url", "workflow_preview.token",
39
39
  "quota.credential_fp", "oauth_grant.renew_hash", "oauth_client.source_hash",
40
40
  ]);
41
41
  /** A column named like this holds a secret unless it is reviewed below; an unreviewed one is dropped. */
42
42
  const SECRET_NAME = /hash|secret|token|passw|cookie|csrf|credential|p256dh|^auth$|key|nonce|endpoint|url/i;
43
43
  /** Reviewed: named like a secret, but it isn't one (a seal, a digest, a lookup name, an address with no key). */
44
44
  const REVIEWED_COLUMNS = new Set([
45
- "artifact.key", "budget.scope_key", "mate_thread.scope_key", "diff_comment.source_key", "discord_meta.key", "slack_meta.key", "teams_meta.key",
45
+ "artifact.key", "budget.scope_key", "mate_thread.scope_key", "diff_comment.source_key", "chat_meta.key",
46
46
  "flow_trigger_event.key", "installation_fact.key", "mcp_idempotency.key", "memory_gap.key", "mutation.idempotency_key", "notification.dedupe_key",
47
47
  "service_cursor.key", "ledger_checkpoint.hash", "ledger_seal.hash", "merge_intent.grant_terms_hash", "plan_revision.parent_hash",
48
48
  "publication.body_hash", "publication.pr_url", "team_message.payload_hash", "team_request.payload_hash", "backend_grant.credential_scope",
@@ -55,7 +55,7 @@ export function categoryOf(table) {
55
55
  [/^(action_ledger|ledger_seal|ledger_checkpoint|sync_ledger)$/, "ledger"],
56
56
  [/^teammate/, "teammates"],
57
57
  [/^(flow|workflow_)/, "flows"],
58
- [/^(chat_|mate_|team_|telegram_|slack_|discord_|teams_|notification|push_|bridge_lease)/, "chats"],
58
+ [/^(chat_|mate_|team_|notification|push_)/, "chats"],
59
59
  [/^(approver|api_token|lead_credential|invite|sso_identity|coordinator_|oauth_)/, "people"],
60
60
  [/^(run|artifact|claim|worktree$|execution_slot|criterion_review|diff_comment|proof_|review_request|repair_chain|incident|publication$|merge_|side_spend|knowledge_snapshot|learning_snapshot|skill_snapshot)/, "runs"],
61
61
  [/^(task|hold$|plan_|scope_|tool_seal|decision|external_|skill_test)/, "tasks"],
@@ -1,11 +1,12 @@
1
1
  import { type GalleryAnswers, type GalleryPreview, type GalleryTemplate, type GalleryTool } from "./flow-gallery.js";
2
2
  import { type FlowDefinition, type FlowStage } from "./flows.js";
3
+ import { type Html } from "./html.js";
3
4
  export declare const GALLERY_CSS: string;
4
5
  /** A flow's main path, as a person reads it: from the start, each step's first way on (a sort's first answer, a choose's
5
6
  * first option that goes somewhere, else next; a check's failure when passing just ends), never back to a step shown. */
6
7
  export declare function mainPath(definition: FlowDefinition): FlowStage[];
7
8
  /** A flow's steps in order, joined by arrows; a person's decisions carry the accent. */
8
- export declare function stepStrip(definition: FlowDefinition): string;
9
+ export declare function stepStrip(definition: FlowDefinition): Html;
9
10
  /** The gallery: every template, grouped, each with one "Use this". `repo` carries a chosen project to the template's page. */
10
11
  export declare function galleryHtml(input: {
11
12
  repo: string | null;
@@ -14,7 +15,7 @@ export declare function galleryHtml(input: {
14
15
  id: string;
15
16
  state: "connected" | "open" | "taken";
16
17
  }[] | null;
17
- }): string;
18
+ }): Html;
18
19
  /** A template's page: its questions, then what it will do and never do, then Create. */
19
20
  export declare function galleryUseHtml(input: {
20
21
  template: GalleryTemplate;
@@ -32,4 +33,4 @@ export declare function galleryUseHtml(input: {
32
33
  /** The template's tools in this project; `said` is how the last Connect went. */
33
34
  tools?: readonly GalleryTool[];
34
35
  said?: string | null;
35
- }): string;
36
+ }): Html;