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
package/dist/surface.js CHANGED
@@ -114,6 +114,17 @@ const GUIDE_ROWS = [
114
114
  operator("link", "put toolroll on PATH"),
115
115
  operator("unlink", "take it off PATH"),
116
116
  operator("update", "update to the latest release: verified provenance, drained work, backup, rehearsal, health check, automatic restore; --rollback undoes it"),
117
+ { invocation: "release", synopsis: "release Toolroll itself from a branch: gate the exact commit, wait for the owner's approval, check, complete, deploy, merge, tag, publish, Homebrew; rerun to resume",
118
+ audience: "operator", agentMayInvoke: false, mutation: "unkeyed",
119
+ positionals: [{ name: "branch", required: true, meaning: "the pushed branch with an open pull request" }],
120
+ flags: [jsonFlag,
121
+ { name: "repo", takesValue: true, meaning: "the gate checkout the release check runs in (default: this checkout)" },
122
+ { name: "new", takesValue: false, meaning: "release a new commit on the branch, setting the earlier unfinished release aside" },
123
+ { name: "limit", takesValue: true, meaning: "<step>=<minutes>: one waiting step's time limit" },
124
+ { name: "tap", takesValue: true, meaning: "the Homebrew tap, owner/name" },
125
+ { name: "release-gate", takesValue: true, meaning: "<id>: show why an update pause left before a deployment's swap is safe to lift" },
126
+ { name: "yes", takesValue: false, meaning: "with --release-gate: lift that one pause once it is proved" }],
127
+ notableReasons: ["usage", "ambiguous", "drift", "timeout", "check-failed", "ci-failed", "tree-mismatch", "conflict", "mixed-runtime", "gate-held", "after-swap"] },
117
128
  operator("demo", "a seeded throwaway sandbox"),
118
129
  { invocation: 'session capabilities', synopsis: 'Show executable session schemas and operator authority requirements; no credentials needed', audience: 'agent', agentMayInvoke: true, mutation: 'none', flags: [jsonFlag] },
119
130
  ...SESSION_DESCRIPTORS.map(spec => ({
@@ -315,7 +326,7 @@ const GUIDE_ROWS = [
315
326
  { invocation: "heartbeat", synopsis: "still working; extends the lease", audience: "agent", agentMayInvoke: true, mutation: "identity-idempotent",
316
327
  positionals: [{ name: "lease", required: true, meaning: "the lease id" }],
317
328
  flags: [jsonFlag, dbFlag], notableReasons: ["not-leased", "fenced"] },
318
- { invocation: "release", synopsis: "done with it; fenced if superseded", audience: "agent", agentMayInvoke: true, mutation: "identity-idempotent",
329
+ { invocation: "worker release", synopsis: "done with it; fenced if superseded", audience: "agent", agentMayInvoke: true, mutation: "identity-idempotent",
319
330
  positionals: [{ name: "lease", required: true, meaning: "the lease id" }],
320
331
  flags: [jsonFlag, dbFlag], notableReasons: ["not-leased", "fenced"] },
321
332
  { invocation: "reap", synopsis: "release every lease that ran out", audience: "agent", agentMayInvoke: true, mutation: "identity-idempotent", flags: [jsonFlag, dbFlag] },
@@ -428,7 +439,7 @@ const GUIDE_ROWS = [
428
439
  const REMOTE_NO = [
429
440
  // Answered by the local CLI, or a client of a central server itself.
430
441
  "", "pulls", "graph", "repos", "repos add", "repos remove", "repos add-from-github", "contract", "skills list", "skills get", "skills install",
431
- "link", "unlink", "update", "demo", "chat", "connect", "lead list", "lead create", "lead update", "lead member", "lead transfer",
442
+ "link", "unlink", "update", "release", "demo", "chat", "connect", "lead list", "lead create", "lead update", "lead member", "lead transfer",
432
443
  "conversation list", "conversation create", "conversation show", "conversation member", "conversation edit", "conversation withdraw",
433
444
  "conversation read", "conversation follow", "conversation stop",
434
445
  "session capabilities", "session list", "session show", "session changes", "session start", "session send", "session stop", "session resume", "session recover",
@@ -436,7 +447,7 @@ const REMOTE_NO = [
436
447
  "project show", "project use", "brief", "lead token", "lead say", "health",
437
448
  "assignment show", "assignment updates", "assignment claim", "assignment check", "assignment brief", "assignment inbox", "assignment ack",
438
449
  // Workers, the loop and the services that run here.
439
- "claim", "heartbeat", "release", "reap", "tick", "build", "reconcile",
450
+ "claim", "heartbeat", "worker release", "reap", "tick", "build", "reconcile",
440
451
  "runner register", "runner retire", "runner bind", "runner capacity", "coordinator mint", "coordinator revoke", "mcp", "outbox deliver", "peek",
441
452
  "serve", "serve check-public", "watch", "up", "onboard", "daemon", "bridge", "setup show", "setup clear",
442
453
  // Provider keys and model tools on this machine.
@@ -1,5 +1,6 @@
1
1
  /** UI choices translate into the same digest-bound schedules as the CLI. */
2
2
  import { type Schedule } from "./flow-schedule.js";
3
+ import { type Html } from "./html.js";
3
4
  export declare function composerSchedule(fields: URLSearchParams): {
4
5
  ok: true;
5
6
  schedule: string | null;
@@ -9,5 +10,5 @@ export declare function composerSchedule(fields: URLSearchParams): {
9
10
  };
10
11
  export declare function scheduleFields(schedule: Schedule): Record<string, string>;
11
12
  /** A normal form works without JavaScript; enhancement only folds unused fields. */
12
- export declare function scheduleEditorHtml(raw: string | null, submitted?: URLSearchParams): string;
13
+ export declare function scheduleEditorHtml(raw: string | null, submitted?: URLSearchParams): Html;
13
14
  export declare function scheduleEditorScript(): string;
@@ -1,5 +1,6 @@
1
1
  /** UI choices translate into the same digest-bound schedules as the CLI. */
2
2
  import { parseSchedule, scheduleText, validTimezone } from "./flow-schedule.js";
3
+ import { html, joinHtml } from "./html.js";
3
4
  export function composerSchedule(fields) {
4
5
  const repeat = fields.get("repeat") ?? "once";
5
6
  if (repeat === "once")
@@ -29,18 +30,17 @@ export function scheduleFields(schedule) {
29
30
  }
30
31
  return { repeat: schedule.kind, time: schedule.hhmm, timezone: schedule.timezone ?? "UTC", ...(schedule.kind === "weekly" ? { weekday: String(schedule.day) } : {}) };
31
32
  }
32
- const escape = (text) => text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
33
33
  /** A normal form works without JavaScript; enhancement only folds unused fields. */
34
34
  export function scheduleEditorHtml(raw, submitted) {
35
35
  const parsed = raw === null ? null : parseSchedule(raw);
36
36
  const fields = submitted?.has("repeat") ? Object.fromEntries(submitted) : parsed === null ? {} : scheduleFields(parsed);
37
37
  const repeat = fields.repeat ?? "daily";
38
- const value = (name, fallback) => escape(fields[name] ?? fallback);
39
- return `<fieldset data-schedule-editor${raw === null && submitted === undefined ? ' data-detect-timezone="true"' : ""}><legend>When to run</legend>` +
40
- `<label>Repeat<select name="repeat">${[["daily", "Daily"], ["weekly", "Weekly"], ["custom", "Custom interval"]].map(([id, label]) => `<option value="${id}"${repeat === id ? " selected" : ""}>${label}</option>`).join("")}</select></label>` +
41
- `<div data-schedule-kind="weekly"><label>Day<select name="weekday">${["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"].map((day, index) => `<option value="${index}"${String(index) === (fields.weekday ?? "1") ? " selected" : ""}>${day}</option>`).join("")}</select></label></div>` +
42
- `<div data-schedule-kind="calendar"><label>Time<input type="time" name="time" value="${value("time", "09:00")}"></label><label>Timezone<input type="text" name="timezone" value="${value("timezone", "UTC")}" autocomplete="off" placeholder="America/Los_Angeles"></label><p class="meta">Daily and weekly times follow this timezone. A skipped clock time skips that occurrence; a repeated time runs once.</p></div>` +
43
- `<div data-schedule-kind="custom"><label>Every<input type="number" name="interval" min="1" value="${value("interval", "60")}"></label><label>Unit<select name="interval-unit">${["minutes", "hours", "days"].map(unit => `<option${unit === (fields["interval-unit"] ?? "minutes") ? " selected" : ""}>${unit}</option>`).join("")}</select></label><p class="meta">Intervals range from 5 minutes to 7 days.</p></div></fieldset>`;
38
+ const value = (name, fallback) => fields[name] ?? fallback;
39
+ return joinHtml([html `<fieldset data-schedule-editor${raw === null && submitted === undefined ? html ` data-detect-timezone="true"` : ""}><legend>When to run</legend>`,
40
+ html `<label>Repeat<select name="repeat">${[["daily", "Daily"], ["weekly", "Weekly"], ["custom", "Custom interval"]].map(([id, label]) => html `<option value="${id}"${repeat === id ? html ` selected` : ""}>${label}</option>`)}</select></label>`,
41
+ html `<div data-schedule-kind="weekly"><label>Day<select name="weekday">${["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"].map((day, index) => html `<option value="${index}"${String(index) === (fields.weekday ?? "1") ? html ` selected` : ""}>${day}</option>`)}</select></label></div>`,
42
+ html `<div data-schedule-kind="calendar"><label>Time<input type="time" name="time" value="${value("time", "09:00")}"></label><label>Timezone<input type="text" name="timezone" value="${value("timezone", "UTC")}" autocomplete="off" placeholder="America/Los_Angeles"></label><p class="meta">Daily and weekly times follow this timezone. A skipped clock time skips that occurrence; a repeated time runs once.</p></div>`,
43
+ html `<div data-schedule-kind="custom"><label>Every<input type="number" name="interval" min="1" value="${value("interval", "60")}"></label><label>Unit<select name="interval-unit">${["minutes", "hours", "days"].map(unit => html `<option${unit === (fields["interval-unit"] ?? "minutes") ? html ` selected` : ""}>${unit}</option>`)}</select></label><p class="meta">Intervals range from 5 minutes to 7 days.</p></div></fieldset>`]);
44
44
  }
45
45
  export function scheduleEditorScript() {
46
46
  return `(function(){document.querySelectorAll('[data-schedule-editor]').forEach(function(root){
@@ -1,3 +1,4 @@
1
+ import { type Html } from "./html.js";
1
2
  import type { AssignmentSnapshot } from "./assignment.js";
2
3
  import { type NeedAction, type NeedContext, type NeedKey, type WaitKey } from "./needs-you.js";
3
4
  export type { NeedKey, WaitKey } from "./needs-you.js";
@@ -200,9 +201,9 @@ export declare function workToneOf(headline: Headline): "attention" | "problem"
200
201
  export declare function statusDetailLines(status: TaskStatus): string[];
201
202
  /** The headline's emoji for chat cards (colour is never the only signal: the words follow). */
202
203
  export declare function headlineEmoji(headline: Headline): string;
203
- export declare const statusIconSvg: (mark: DetailMark) => string;
204
+ export declare const statusIconSvg: (mark: DetailMark) => Html;
204
205
  /** The detail rows as server HTML: quiet rows, colour only on the icon. */
205
- export declare function statusDetailsHtml(status: TaskStatus): string;
206
+ export declare function statusDetailsHtml(status: TaskStatus): Html;
206
207
  /** The status card's fold of exact reasons: "More", never a third thing called Details. */
207
208
  export declare const STATUS_MORE = "More";
208
209
  /** The technical reasons, one tap away. */
@@ -210,7 +211,7 @@ export declare function statusWhyHtml(status: TaskStatus, extra?: readonly strin
210
211
  token: string;
211
212
  label: string;
212
213
  detail: string;
213
- }[]): string;
214
+ }[]): Html;
214
215
  /** Shared CSS for the server-rendered status: neutral rows, coloured icons. */
215
216
  export declare const TASK_STATUS_CSS = ".status-headline{display:flex;align-items:center;gap:.6rem;margin:0;font-size:1.125rem;font-weight:600;color:var(--foreground)}.status-headline i{width:.625rem;height:.625rem;border-radius:999px;flex-shrink:0;background:var(--muted-foreground)}[data-headline-tone=live] .status-headline i{background:var(--so-info,#0d74ce)}[data-headline-tone=attention] .status-headline i{background:var(--so-attention,var(--attention))}[data-headline-tone=ready] .status-headline i{background:transparent;box-shadow:inset 0 0 0 2px var(--so-success,#218358)}[data-headline-tone=success] .status-headline i{background:var(--so-success,#218358)}[data-headline-tone=danger] .status-headline i{background:var(--so-danger,#c4320a)}.status-sentence{margin:.35rem 0 0;color:var(--muted-foreground)}.status-details{list-style:none;margin:.75rem 0 0;padding:.25rem 0 0;border-top:1px solid var(--so-line,var(--border))}.status-detail{display:grid;grid-template-columns:14px 7.5rem minmax(0,1fr);align-items:center;gap:.6rem;padding:.4rem 0;font-size:.8125rem;color:var(--foreground)}.status-detail .status-icon{align-self:center;color:var(--muted-foreground)}.status-detail--ok .status-icon{color:var(--so-success,#218358)}.status-detail--running .status-icon{color:var(--so-info,#0d74ce)}.status-detail--note .status-icon{color:var(--so-warning,#ab6400)}.status-detail--failed .status-icon{color:var(--so-danger,#c4320a)}.status-detail-label{color:var(--muted-foreground)}.status-detail-text{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 .6rem;min-width:0;overflow-wrap:anywhere}.status-detail-text a{color:inherit}.status-detail-act{font-weight:600;white-space:nowrap}.status-detail--note .status-detail-act{color:var(--so-warning,#ab6400)}@media(max-width:480px){.status-detail{grid-template-columns:14px 6.5rem minmax(0,1fr);min-height:2.75rem;padding:.25rem 0}.status-detail-text a{display:inline-block;padding:.75rem 0;margin:-.75rem 0}}.status-why{border:0;padding:0;margin:.25rem 0 0;background:transparent;box-shadow:none}.status-why>summary{cursor:pointer;min-height:2.75rem;display:list-item;align-content:center;font-size:.8125rem;color:var(--muted-foreground)}.status-why p{margin:.25rem 0 .5rem}.receipt-note{display:flex;align-items:center;gap:.45rem;font-size:.8125rem;color:var(--foreground)}.receipt-note .status-icon{color:var(--so-warning,#ab6400)}";
216
217
  /** A dispatch diagnosis as its stage, for the phone and CLI reads that hold
@@ -11,6 +11,7 @@
11
11
  * output) is an amber note on its own row with one action; the exact
12
12
  * technical reason stays one tap away in `why`. */
13
13
  import { DEFAULT_LEAD_NAME } from "./lead-identity.js";
14
+ import { html } from "./html.js";
14
15
  import { CODE_NEED, NEEDS, needSentence, waitSentence } from "./needs-you.js";
15
16
  /** A cancelled task with a successor reads "Replaced by <id>" everywhere, never "Cancelled". */
16
17
  export const replacedWords = (successor) => `Replaced by ${successor}`;
@@ -398,24 +399,23 @@ export function statusDetailLines(status) {
398
399
  export function headlineEmoji(headline) {
399
400
  return { Queued: "🕓", Planning: "📝", "Needs you": "👋", Waiting: "⏸", Building: "⏳", "Ready for review": "✅", Complete: "✅", Failed: "❌", Stopped: "⏹" }[headline];
400
401
  }
401
- const escapeHtml = (value) => value.replace(/[&<>"']/g, char => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[char]);
402
402
  const ICON_PATHS = {
403
- ok: `<path d="M20 6 9 17l-5-5"/>`,
404
- running: `<circle cx="12" cy="12" r="4"/>`,
405
- none: `<circle cx="12" cy="12" r="3"/>`,
406
- note: `<path d="M12 8v5"/><path d="M12 16.5h.01"/><path d="M10.3 3.9 2.4 18a2 2 0 0 0 1.7 3h15.8a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/>`,
407
- failed: `<path d="M18 6 6 18M6 6l12 12"/>`,
403
+ ok: html `<path d="M20 6 9 17l-5-5"/>`,
404
+ running: html `<circle cx="12" cy="12" r="4"/>`,
405
+ none: html `<circle cx="12" cy="12" r="3"/>`,
406
+ note: html `<path d="M12 8v5"/><path d="M12 16.5h.01"/><path d="M10.3 3.9 2.4 18a2 2 0 0 0 1.7 3h15.8a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/>`,
407
+ failed: html `<path d="M18 6 6 18M6 6l12 12"/>`,
408
408
  };
409
- export const statusIconSvg = (mark) => `<svg class="status-icon status-icon--${mark}" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.25" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${ICON_PATHS[mark]}</svg>`;
409
+ export const statusIconSvg = (mark) => html `<svg class="status-icon status-icon--${mark}" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.25" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${ICON_PATHS[mark]}</svg>`;
410
410
  /** The detail rows as server HTML: quiet rows, colour only on the icon. */
411
411
  export function statusDetailsHtml(status) {
412
412
  if (status.details.length === 0)
413
- return "";
414
- return `<ul class="status-details" aria-label="Status">` + status.details.map(one => {
415
- const text = one.href === null ? escapeHtml(one.text) : `<a href="${escapeHtml(one.href)}">${escapeHtml(one.text)}</a>`;
416
- const action = one.action === null ? "" : one.action.href === null ? `<span class="status-detail-act">${escapeHtml(one.action.label)}</span>` : `<a class="status-detail-act" href="${escapeHtml(one.action.href)}">${escapeHtml(one.action.label)}</a>`;
417
- return `<li class="status-detail status-detail--${one.mark}" data-status-detail="${one.key}" data-mark="${one.mark}">${statusIconSvg(one.mark)}<span class="status-detail-label">${escapeHtml(one.label)}</span><span class="status-detail-text"><span>${text}</span>${action}</span></li>`;
418
- }).join("") + `</ul>`;
413
+ return html ``;
414
+ return html `<ul class="status-details" aria-label="Status">${status.details.map(one => {
415
+ const text = one.href === null ? one.text : html `<a href="${one.href}">${one.text}</a>`;
416
+ const action = one.action === null ? "" : one.action.href === null ? html `<span class="status-detail-act">${one.action.label}</span>` : html `<a class="status-detail-act" href="${one.action.href}">${one.action.label}</a>`;
417
+ return html `<li class="status-detail status-detail--${one.mark}" data-status-detail="${one.key}" data-mark="${one.mark}">${statusIconSvg(one.mark)}<span class="status-detail-label">${one.label}</span><span class="status-detail-text"><span>${text}</span>${action}</span></li>`;
418
+ })}</ul>`;
419
419
  }
420
420
  /** The status card's fold of exact reasons: "More", never a third thing called Details. */
421
421
  export const STATUS_MORE = "More";
@@ -423,9 +423,8 @@ export const STATUS_MORE = "More";
423
423
  export function statusWhyHtml(status, extra = [], diagnostics = []) {
424
424
  const lines = [...new Set([...status.details.flatMap(one => one.why === null ? [] : [`${one.label}: ${one.why}`]), ...status.why, ...extra])];
425
425
  if (lines.length === 0 && diagnostics.length === 0)
426
- return "";
427
- return `<details class="status-why"><summary>${STATUS_MORE}</summary>${lines.map(one => `<p class="meta">${escapeHtml(one)}</p>`).join("")}` +
428
- diagnostics.map(one => `<p class="meta" data-work-diagnostic="${escapeHtml(one.token)}">${escapeHtml(one.label)} · ${escapeHtml(one.detail)}</p>`).join("") + `</details>`;
426
+ return html ``;
427
+ return html `<details class="status-why"><summary>${STATUS_MORE}</summary>${lines.map(one => html `<p class="meta">${one}</p>`)}${diagnostics.map(one => html `<p class="meta" data-work-diagnostic="${one.token}">${one.label} · ${one.detail}</p>`)}</details>`;
429
428
  }
430
429
  /** Shared CSS for the server-rendered status: neutral rows, coloured icons. */
431
430
  export const TASK_STATUS_CSS = `.status-headline{display:flex;align-items:center;gap:.6rem;margin:0;font-size:1.125rem;font-weight:600;color:var(--foreground)}.status-headline i{width:.625rem;height:.625rem;border-radius:999px;flex-shrink:0;background:var(--muted-foreground)}[data-headline-tone=live] .status-headline i{background:var(--so-info,#0d74ce)}[data-headline-tone=attention] .status-headline i{background:var(--so-attention,var(--attention))}[data-headline-tone=ready] .status-headline i{background:transparent;box-shadow:inset 0 0 0 2px var(--so-success,#218358)}[data-headline-tone=success] .status-headline i{background:var(--so-success,#218358)}[data-headline-tone=danger] .status-headline i{background:var(--so-danger,#c4320a)}.status-sentence{margin:.35rem 0 0;color:var(--muted-foreground)}.status-details{list-style:none;margin:.75rem 0 0;padding:.25rem 0 0;border-top:1px solid var(--so-line,var(--border))}.status-detail{display:grid;grid-template-columns:14px 7.5rem minmax(0,1fr);align-items:center;gap:.6rem;padding:.4rem 0;font-size:.8125rem;color:var(--foreground)}.status-detail .status-icon{align-self:center;color:var(--muted-foreground)}.status-detail--ok .status-icon{color:var(--so-success,#218358)}.status-detail--running .status-icon{color:var(--so-info,#0d74ce)}.status-detail--note .status-icon{color:var(--so-warning,#ab6400)}.status-detail--failed .status-icon{color:var(--so-danger,#c4320a)}.status-detail-label{color:var(--muted-foreground)}.status-detail-text{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 .6rem;min-width:0;overflow-wrap:anywhere}.status-detail-text a{color:inherit}.status-detail-act{font-weight:600;white-space:nowrap}.status-detail--note .status-detail-act{color:var(--so-warning,#ab6400)}@media(max-width:480px){.status-detail{grid-template-columns:14px 6.5rem minmax(0,1fr);min-height:2.75rem;padding:.25rem 0}.status-detail-text a{display:inline-block;padding:.75rem 0;margin:-.75rem 0}}.status-why{border:0;padding:0;margin:.25rem 0 0;background:transparent;box-shadow:none}.status-why>summary{cursor:pointer;min-height:2.75rem;display:list-item;align-content:center;font-size:.8125rem;color:var(--muted-foreground)}.status-why p{margin:.25rem 0 .5rem}.receipt-note{display:flex;align-items:center;gap:.45rem;font-size:.8125rem;color:var(--foreground)}.receipt-note .status-icon{color:var(--so-warning,#ab6400)}`;
@@ -2,8 +2,8 @@ import type { IncomingMessage, ServerResponse } from 'node:http';
2
2
  import { type TeamActor, type TeamExecute } from './team-contract.js';
3
3
  import { type Admission } from './request-budget.js';
4
4
  export declare const TEAM_REQUEST_BYTES: number;
5
- /** How long a password bearer's proof stands on a long /api/team/events stream before its password is checked again.
6
- * The account, its revocation and its credential generation are still rechecked on every tick. */
5
+ /** How long a password bearer's proof stands on a long live team stream (GET /live?room=team) before its password is
6
+ * checked again. The account, its revocation and its credential generation are still rechecked on every signal. */
7
7
  export declare const TEAM_PASSWORD_REVERIFY_MS: number;
8
8
  /** Account role belongs to HTTP authentication; API tokens carry their own scope. */
9
9
  export type TeamHttpActor = TeamActor & {
@@ -15,15 +15,11 @@ export type TeamHttpOptions = {
15
15
  revalidate: (request: IncomingMessage, actor: TeamActor) => boolean;
16
16
  authorizeMutation: (request: IncomingMessage, actor: TeamActor) => boolean;
17
17
  execute: TeamExecute;
18
- /** Current, authorized event cursor. null means the audience is no longer available. */
19
- cursor?: (actor: TeamActor, conversationId?: string) => number | null;
20
- /** Source admission before credential verification. Streams are charged once when opened. */
18
+ /** Source admission before credential verification. */
21
19
  admit?: (request: IncomingMessage) => Admission;
22
20
  /** Proved account/token admission, before reading the body or executing any operation. */
23
21
  admitAuthenticated?: (request: IncomingMessage, actor: TeamActor) => Admission;
24
- streamIntervalMs?: number;
25
- streams?: Set<ServerResponse>;
26
22
  };
27
- /** Authenticated browser and remote CLI share one operation boundary. Streams
28
- * are refresh hints only: no model calls, admissions, or completion writes. */
23
+ /** Authenticated browser and remote CLI share one operation boundary. Live
24
+ * updates come from GET /live?room=team (server/live.ts). */
29
25
  export declare function handleTeamHttp(request: IncomingMessage, response: ServerResponse, options: TeamHttpOptions): Promise<boolean>;
package/dist/team-http.js CHANGED
@@ -2,8 +2,8 @@ import { adapterPolicy } from "./server/route-policy.js";
2
2
  import { TEAM_OPERATIONS, TEAM_MUTATIONS, teamScopeAllows } from './team-contract.js';
3
3
  import { limitWords } from './request-budget.js';
4
4
  export const TEAM_REQUEST_BYTES = 64 * 1024;
5
- /** How long a password bearer's proof stands on a long /api/team/events stream before its password is checked again.
6
- * The account, its revocation and its credential generation are still rechecked on every tick. */
5
+ /** How long a password bearer's proof stands on a long live team stream (GET /live?room=team) before its password is
6
+ * checked again. The account, its revocation and its credential generation are still rechecked on every signal. */
7
7
  export const TEAM_PASSWORD_REVERIFY_MS = 5 * 60_000;
8
8
  const failure = (code, message) => ({ version: 1, ok: false, code, message });
9
9
  function send(response, status, value, headers = {}) {
@@ -33,11 +33,11 @@ function valid(value) {
33
33
  !!row.args && typeof row.args === 'object' && !Array.isArray(row.args) &&
34
34
  !['actor', 'generation', 'authenticatedActor'].some(key => key in row.args);
35
35
  }
36
- /** Authenticated browser and remote CLI share one operation boundary. Streams
37
- * are refresh hints only: no model calls, admissions, or completion writes. */
36
+ /** Authenticated browser and remote CLI share one operation boundary. Live
37
+ * updates come from GET /live?room=team (server/live.ts). */
38
38
  export async function handleTeamHttp(request, response, options) {
39
39
  const url = new URL(request.url ?? '/', 'http://standing-orders.local');
40
- if (url.pathname !== '/api/team' && url.pathname !== '/api/team/events')
40
+ if (url.pathname !== '/api/team')
41
41
  return false;
42
42
  const reject = (status, code, message) => { send(response, status, failure(code, message)); request.resume(); return true; };
43
43
  if ([...url.searchParams.keys()].some(key => /token|password|credential|authorization/i.test(key)))
@@ -71,64 +71,6 @@ export async function handleTeamHttp(request, response, options) {
71
71
  if (!adapterPolicy(policyPrincipal()).ok)
72
72
  return reject(403, 'read-only', 'Your token reads only. Use an act token for this.');
73
73
  const conversationId = url.searchParams.get('conversation') ?? undefined;
74
- if (url.pathname === '/api/team/events') {
75
- if (request.method !== 'GET')
76
- return reject(405, 'method-not-allowed', 'Use GET for conversation updates.');
77
- let initial;
78
- try {
79
- initial = await options.execute(actor, { operation: conversationId ? 'show' : 'list', args: conversationId ? { conversationId } : {} });
80
- }
81
- catch {
82
- return reject(503, 'unavailable', 'Conversation updates are unavailable.');
83
- }
84
- if (!initial.ok || !initial.snapshot) {
85
- send(response, 403, initial);
86
- return true;
87
- }
88
- response.writeHead(200, { 'content-type': 'text/event-stream', 'cache-control': 'no-store', 'x-content-type-options': 'nosniff', 'x-accel-buffering': 'no' });
89
- options.streams?.add(response);
90
- let cursor = initial.snapshot.cursor;
91
- response.write(`id: ${cursor}\nevent: change\ndata: {"cursor":${cursor}}\n\n`);
92
- let active = false;
93
- const close = () => { clearInterval(timer); options.streams?.delete(response); response.end(); };
94
- const revoked = () => { response.write('event: revoked\ndata: {}\n\n'); close(); };
95
- const tick = async () => {
96
- if (active || response.destroyed || response.writableEnded)
97
- return;
98
- active = true;
99
- try {
100
- if (!options.revalidate(request, actor))
101
- return revoked();
102
- let next;
103
- if (options.cursor)
104
- next = options.cursor(actor, conversationId);
105
- else {
106
- const reply = await options.execute(actor, { operation: conversationId ? 'show' : 'list', args: conversationId ? { conversationId } : {} });
107
- next = reply.ok ? reply.snapshot?.cursor ?? null : null;
108
- }
109
- if (next === null)
110
- return revoked();
111
- if (next !== cursor) {
112
- cursor = next;
113
- // A slow client receives a fresh scoped snapshot on reconnect, never an unbounded replay buffer.
114
- if (!response.write(`id: ${cursor}\nevent: change\ndata: {"cursor":${cursor}}\n\n`))
115
- close();
116
- }
117
- }
118
- catch {
119
- response.write('event: unavailable\ndata: {}\n\n');
120
- close();
121
- }
122
- finally {
123
- active = false;
124
- }
125
- };
126
- const timer = setInterval(() => { void tick(); }, options.streamIntervalMs ?? 1_000);
127
- timer.unref();
128
- request.once('close', close);
129
- response.once('close', close);
130
- return true;
131
- }
132
74
  let input;
133
75
  if (request.method === 'GET')
134
76
  input = { operation: conversationId ? 'show' : 'list', args: conversationId ? { conversationId } : {} };
package/dist/team-ui.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { type Html } from './html.js';
1
2
  import type { TeamSnapshot } from './team-contract.js';
2
3
  /** Readable fallback uses the same admitted projection as the interactive UI. */
3
- export declare function teamWorkspaceHtml(snapshot: TeamSnapshot): string;
4
+ export declare function teamWorkspaceHtml(snapshot: TeamSnapshot): Html;
package/dist/team-ui.js CHANGED
@@ -1,9 +1,6 @@
1
- const escape = (value) => value.replace(/[&<>"']/g, char => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[char]);
1
+ import { html } from './html.js';
2
2
  /** Readable fallback uses the same admitted projection as the interactive UI. */
3
3
  export function teamWorkspaceHtml(snapshot) {
4
4
  const selected = snapshot.selected;
5
- return `<section class="team-workspace"><h1>${escape(selected?.title ?? 'Team chat')}</h1>` +
6
- (selected ? `<p>${selected.visibility === 'team' ? 'Team' : 'Private'} · ${escape(snapshot.leads.find(lead => lead.id === selected.leadId)?.name ?? 'Lead')}</p>` : '') +
7
- (snapshot.messages.length ? snapshot.messages.map(message => `<article><strong>${escape(message.author)}</strong><p style="white-space:pre-wrap;overflow-wrap:anywhere">${escape(message.text)}</p>${message.status === 'queued' || message.status === 'running' ? `<p>${message.status === 'queued' ? 'Queued' : 'Working'}</p>` : ''}${message.error ? `<p role="alert">${escape(message.error)}</p>` : ''}</article>`).join('') : '<p>Start a conversation with your team lead.</p>') +
8
- '<noscript>Enable JavaScript to send messages and manage team conversations.</noscript><p><a href="/chat?private=1">Previous private chat</a></p></section>';
5
+ return html `<section class="team-workspace"><h1>${selected?.title ?? 'Team chat'}</h1>${selected ? html `<p>${selected.visibility === 'team' ? 'Team' : 'Private'} · ${snapshot.leads.find(lead => lead.id === selected.leadId)?.name ?? 'Lead'}</p>` : ''}${snapshot.messages.length ? snapshot.messages.map(message => html `<article><strong>${message.author}</strong><p style="white-space:pre-wrap;overflow-wrap:anywhere">${message.text}</p>${message.status === 'queued' || message.status === 'running' ? html `<p>${message.status === 'queued' ? 'Queued' : 'Working'}</p>` : ''}${message.error ? html `<p role="alert">${message.error}</p>` : ''}</article>`) : html `<p>Start a conversation with your team lead.</p>`}<noscript>Enable JavaScript to send messages and manage team conversations.</noscript><p><a href="/chat?private=1">Previous private chat</a></p></section>`;
9
6
  }
@@ -30,8 +30,8 @@ const who = (store, question) => { const mate = store.getTeammate(question.teamm
30
30
  /** Everyone else's buttons retire once a question is answered anywhere. */
31
31
  function retireEverywhere(store, question, now, state) {
32
32
  store.retireTelegramQuestion(question, now);
33
- state?.prepare("UPDATE chat_question_action SET consumed=? WHERE question=? AND consumed IS NULL").run(now.toISOString(), question);
34
- state?.prepare("UPDATE chat_question_prompt SET consumed=? WHERE question=? AND consumed IS NULL").run(now.toISOString(), question);
33
+ state?.prepare("UPDATE chat_question_action SET consumed=? WHERE provider=:provider AND question=? AND consumed IS NULL").run(now.toISOString(), question);
34
+ state?.prepare("UPDATE chat_question_prompt SET consumed=? WHERE provider=:provider AND question=? AND consumed IS NULL").run(now.toISOString(), question);
35
35
  }
36
36
  // ---- Telegram ----------------------------------------------------------------------
37
37
  /** Buttons for the question, minted before the send (for the person it asks only); `place` stamps the message they land on. */
@@ -91,23 +91,23 @@ export function questionParts(store, notification, binding) {
91
91
  }
92
92
  /** The live buttons on one part, in the order they were minted. */
93
93
  export function chatQuestionButtons(state, part, now) {
94
- const row = state.prepare("SELECT payload FROM chat_part WHERE id=?").get(part);
94
+ const row = state.prepare("SELECT payload FROM chat_part WHERE provider=:provider AND id=?").get(part);
95
95
  const content = row === undefined ? null : partContent(String(row["payload"]));
96
96
  const labels = new Map((content?.question?.choices ?? []).map(one => [one.choice ?? "", one.label]));
97
- return state.prepare("SELECT token,choice FROM chat_question_action WHERE part=? AND consumed IS NULL AND expires>? ORDER BY rowid").all(part, now.toISOString())
97
+ return state.prepare("SELECT token,choice FROM chat_question_action WHERE provider=:provider AND part=? AND consumed IS NULL AND expires>? ORDER BY rowid").all(part, now.toISOString())
98
98
  .map(one => ({ token: String(one.token), label: (labels.get(one.choice ?? "") ?? IN_WORDS).slice(0, 75), words: one.choice === null }));
99
99
  }
100
100
  /** Show the tapped notice again with the answer, and without its buttons. */
101
101
  function repaint(state, part, event, line) {
102
- const row = state.prepare("SELECT payload FROM chat_part WHERE id=?").get(part);
102
+ const row = state.prepare("SELECT payload FROM chat_part WHERE provider=:provider AND id=?").get(part);
103
103
  const before = row === undefined ? { text: "" } : partContent(String(row["payload"]));
104
104
  const content = { text: `${before.text}\n\n${line}`.slice(0, 3400), edit: event.ts, ...(before.link === undefined ? {} : { link: before.link }), ...(before.channel === undefined ? {} : { channel: before.channel }) };
105
- state.prepare("UPDATE chat_part SET payload=?,state='pending',next_at=NULL WHERE id=?").run(savedChatPart(content), part);
105
+ state.prepare("UPDATE chat_part SET payload=?,state='pending',next_at=NULL WHERE provider=:provider AND id=?").run(savedChatPart(content), part);
106
106
  }
107
107
  /** A tapped question button, inside the action's transaction; false when the token isn't one. */
108
108
  export function applyChatQuestionTap(options, event, binding, token, now) {
109
109
  const { store, state } = options;
110
- const action = state.prepare("SELECT a.*,p.message,e.binding AS owner,e.channel FROM chat_question_action a JOIN chat_part p ON p.id=a.part JOIN chat_event e ON e.id=p.event WHERE a.token=?").get(token);
110
+ const action = state.prepare("SELECT a.token,a.part,a.question,a.choice,a.expires,a.consumed,p.message,e.binding AS owner,e.channel FROM chat_question_action a JOIN chat_part p ON p.provider=a.provider AND p.id=a.part JOIN chat_event e ON e.provider=p.provider AND e.id=p.event WHERE a.provider=:provider AND a.token=?").get(token);
111
111
  if (action === undefined)
112
112
  return false;
113
113
  const say = (text) => state.plan(event.id, [{ text }], now);
@@ -126,9 +126,9 @@ export function applyChatQuestionTap(options, event, binding, token, now) {
126
126
  }
127
127
  if (action["choice"] === null) {
128
128
  // One open prompt per person, across flow decisions and questions: the newest is the one the next message answers.
129
- state.prepare("UPDATE chat_question_prompt SET consumed=? WHERE binding=? AND consumed IS NULL").run(now.toISOString(), binding.id);
130
- state.prepare("UPDATE chat_flow_prompt SET consumed=? WHERE binding=? AND consumed IS NULL").run(now.toISOString(), binding.id);
131
- state.prepare("INSERT INTO chat_question_prompt(binding,question,created,expires) VALUES(?,?,?,?)").run(binding.id, questionId, now.toISOString(), new Date(now.getTime() + PROMPT_MS).toISOString());
129
+ state.prepare("UPDATE chat_question_prompt SET consumed=? WHERE provider=:provider AND binding=? AND consumed IS NULL").run(now.toISOString(), binding.id);
130
+ state.prepare("UPDATE chat_flow_prompt SET consumed=? WHERE provider=:provider AND binding=? AND consumed IS NULL").run(now.toISOString(), binding.id);
131
+ state.prepare("INSERT INTO chat_question_prompt(provider,id,binding,question,created,expires) VALUES(:provider,(SELECT COALESCE(MAX(id),0)+1 FROM chat_question_prompt WHERE provider=:provider),?,?,?,?)").run(binding.id, questionId, now.toISOString(), new Date(now.getTime() + PROMPT_MS).toISOString());
132
132
  say(`Your next message here is your answer to ${who(store, question)}: “${question.question}” Send “cancel” to leave it.`);
133
133
  return true;
134
134
  }
@@ -149,14 +149,14 @@ export function answerChatQuestionPrompt(options, event, binding, input, now) {
149
149
  if (event.channel !== binding.channel)
150
150
  return false;
151
151
  return store.transact(() => {
152
- const prompt = state.prepare("SELECT * FROM chat_question_prompt WHERE binding=? AND consumed IS NULL AND expires>? ORDER BY id DESC LIMIT 1").get(binding.id, now.toISOString());
152
+ const prompt = state.prepare("SELECT * FROM chat_question_prompt WHERE provider=:provider AND binding=? AND consumed IS NULL AND expires>? ORDER BY id DESC LIMIT 1").get(binding.id, now.toISOString());
153
153
  if (prompt === undefined)
154
154
  return false;
155
155
  const id = Number(prompt["id"]), questionId = Number(prompt["question"]);
156
156
  const say = (text) => state.plan(event.id, [{ text }], now);
157
157
  const said = input.text.trim();
158
158
  if (/^cancel\.?$/i.test(said)) {
159
- state.prepare("UPDATE chat_question_prompt SET consumed=? WHERE id=?").run(now.toISOString(), id);
159
+ state.prepare("UPDATE chat_question_prompt SET consumed=? WHERE provider=:provider AND id=?").run(now.toISOString(), id);
160
160
  say("Left it unanswered. The buttons on the message still work.");
161
161
  return true;
162
162
  }
@@ -4,17 +4,18 @@
4
4
  * and its settings. Server-rendered forms, like the other settings pages.
5
5
  */
6
6
  import type { Store, TeammateRow } from "./store.js";
7
+ import { type Html } from "./html.js";
7
8
  /** v94: the Tools section's rule rows; a limit's fields show only while "Up to a limit" is chosen. */
8
9
  export declare const TEAMMATE_CSS = ".teammate-tools .tool-grant{border-top:1px solid var(--so-line);padding-top:12px;margin-top:12px}.teammate-tools .tool-grant:first-of-type{border-top:0;margin-top:0;padding-top:0}.teammate-tools h3{font-size:1rem;margin:0 0 4px}.teammate-tools .tool-rule{display:grid;gap:6px;padding:10px 0;border-bottom:1px solid var(--so-line)}.teammate-tools .tool-rule:last-of-type{border-bottom:0}.teammate-tools .tool-rule .meta{display:block}.teammate-tools select,.teammate-tools input{max-width:100%;box-sizing:border-box}.teammate-tools .tool-limit{display:none;flex-wrap:wrap;align-items:center;gap:6px}.teammate-tools .tool-rule:has(option[value=\"limit\"]:checked) .tool-limit{display:flex}.teammate-tools .tool-rule>select{width:auto;min-width:16em}.teammate-tools .tool-limit select{width:auto}.teammate-tools .tool-limit input{width:7em}.teammate-tools .tool-buttons{display:flex;flex-wrap:wrap;gap:8px;margin-top:8px}.teammate-tools button{min-height:44px}.teammate-activity li{overflow-wrap:anywhere}.teammate-memory .memory-tell,.teammate-memory .memory-search{display:flex;flex-wrap:wrap;gap:8px;align-items:flex-end;margin:0 0 12px}.teammate-memory .memory-tell textarea{flex:1 1 280px}.teammate-memory .memory-search input{flex:1 1 220px}.teammate-memory button{min-height:44px}.teammate-memories{list-style:none;padding:0;margin:0}.teammate-memories li{padding:10px 0;border-top:1px solid var(--so-line);overflow-wrap:anywhere}.teammate-memories li:first-child{border-top:0}.teammate-memories .meta{display:block;font-size:.85rem}.teammate-memories details.memory-edit{border:0;padding:0;margin:2px 0 0;background:transparent;box-shadow:none}.memory-edit summary{cursor:pointer;min-height:32px;display:inline-flex;align-items:center;font-size:.85rem;font-weight:400;color:var(--so-muted)}.tool-undo{border:0;padding:0;margin:8px 0;background:transparent;box-shadow:none}.tool-undo summary{cursor:pointer;min-height:36px;display:inline-flex;align-items:center;font-size:.9rem}.tool-undo-row{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin:6px 0}.tool-undo-row select{width:auto}.teammate-overrides,.teammate-undo{padding-left:18px}.teammate-undo li{margin:6px 0}.teammate-undo form{display:inline}.teammate-week h3{font-size:1rem;margin:14px 0 4px}.teammate-week button{min-height:44px}.teammate-routines{list-style:none;padding:0;margin:8px 0}.teammate-routines li{display:grid;gap:4px;padding:10px 0;border-top:1px solid var(--so-line)}.teammate-routines li:first-child{border-top:0}.routine-buttons{display:flex;flex-wrap:wrap;gap:8px}.routine-buttons button,.routine-add button{min-height:44px}.routine-add{display:flex;flex-wrap:wrap;gap:8px;align-items:flex-end;margin:8px 0}.routine-add label{display:grid;gap:4px;flex:1 1 200px}.routine-add label:first-of-type{flex:0 1 180px}.teammate-actions,.question-options{display:flex;flex-wrap:wrap;gap:8px;margin:8px 0}.teammate-actions form,.question-options form{margin:0}.memory-edit form{display:grid;gap:8px;margin-top:6px}.memory-buttons{display:flex;flex-wrap:wrap;gap:8px}@media(max-width:600px){.teammate-tools select,.teammate-tools input{font-size:16px}}";
9
10
  /** A blank soul file: the sections a teammate reads, to fill in. */
10
11
  export declare const BLANK_SOUL = "---\nname: \nrole: \n---\n\n## Who you are\n\n\n## How you write\n\n\n## What you know\n\n\n## Decide on your own\n- \n\n## Ask first\n- \n\n## Never\n- \n";
11
- export declare function teammatesListHtml(store: Store, mates: readonly TeammateRow[], projects: readonly string[], projectName: (repo: string) => string, csrf: string, canCreate: boolean, notice: {
12
+ export declare function teammatesListHtml(store: Store, mates: readonly TeammateRow[], projects: readonly string[], projectName: (repo: string) => string, canCreate: boolean, notice: {
12
13
  said?: string | null;
13
14
  problem?: string | null;
14
- }): string;
15
- export declare function teammatePageHtml(store: Store, mate: TeammateRow, viewer: string, projectName: (repo: string) => string, csrf: string, canManage: boolean, approvers: readonly string[], notice: {
15
+ }): Html;
16
+ export declare function teammatePageHtml(store: Store, mate: TeammateRow, viewer: string, projectName: (repo: string) => string, canManage: boolean, approvers: readonly string[], notice: {
16
17
  said?: string | null;
17
18
  problem?: string | null;
18
19
  soulDraft?: string | null;
19
20
  query?: string | null;
20
- }): string;
21
+ }): Html;