toolroll 0.9.52 → 0.9.54

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 (187) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +55 -80
  3. package/dist/agentconfig.d.ts +11 -36
  4. package/dist/agentconfig.js +3 -62
  5. package/dist/approval-policy.d.ts +1 -1
  6. package/dist/approval-policy.js +1 -1
  7. package/dist/board.d.ts +3 -19
  8. package/dist/board.js +4 -42
  9. package/dist/browser/workspace.js +18 -18
  10. package/dist/browser-workspace.d.ts +2 -1
  11. package/dist/builder.d.ts +9 -40
  12. package/dist/builder.js +50 -257
  13. package/dist/chat-actions.js +3 -14
  14. package/dist/chat-ask.js +8 -8
  15. package/dist/chat-controls.d.ts +0 -4
  16. package/dist/chat-controls.js +0 -1
  17. package/dist/chat-core.d.ts +171 -0
  18. package/dist/chat-core.js +288 -0
  19. package/dist/chat-decide.d.ts +10 -8
  20. package/dist/chat-decide.js +27 -24
  21. package/dist/chat-delivery-state.d.ts +51 -75
  22. package/dist/chat-delivery-state.js +140 -170
  23. package/dist/chat-delivery.d.ts +21 -4
  24. package/dist/chat-delivery.js +313 -195
  25. package/dist/chat-flow.d.ts +29 -1
  26. package/dist/chat-flow.js +24 -50
  27. package/dist/chat-messages.d.ts +319 -0
  28. package/dist/chat-messages.js +841 -0
  29. package/dist/chat-migration.d.ts +30 -0
  30. package/dist/chat-migration.js +802 -0
  31. package/dist/chat-quiet.d.ts +3 -0
  32. package/dist/chat-quiet.js +7 -2
  33. package/dist/chat-task-actions.d.ts +1 -1
  34. package/dist/chat-task-actions.js +0 -2
  35. package/dist/claim.d.ts +79 -244
  36. package/dist/claim.js +688 -1023
  37. package/dist/cli.d.ts +2 -0
  38. package/dist/cli.js +10 -2
  39. package/dist/contracts/chat-content.d.ts +71 -2
  40. package/dist/contracts/chat-content.js +22 -3
  41. package/dist/contracts/chat-tables.d.ts +11 -4
  42. package/dist/contracts/chat-tables.js +165 -87
  43. package/dist/contracts/cli.d.ts +0 -13
  44. package/dist/contracts/cli.js +0 -2
  45. package/dist/contracts/console-api.d.ts +5 -10
  46. package/dist/contracts/console-api.js +4 -10
  47. package/dist/contracts/discord-callback.js +2 -1
  48. package/dist/contracts/flow.d.ts +50 -0
  49. package/dist/contracts/flow.js +26 -1
  50. package/dist/contracts/lead-tools.d.ts +0 -13
  51. package/dist/contracts/lead-tools.js +3 -3
  52. package/dist/contracts/slack-callback.js +3 -2
  53. package/dist/contracts/store-json.d.ts +1 -29
  54. package/dist/contracts/store-json.js +4 -17
  55. package/dist/contracts/teams-callback.js +2 -1
  56. package/dist/converse.d.ts +1 -11
  57. package/dist/converse.js +6 -38
  58. package/dist/coordinator.d.ts +0 -1
  59. package/dist/coordinator.js +3 -5
  60. package/dist/demo.js +16 -26
  61. package/dist/desktop-update-gate.js +0 -1
  62. package/dist/desktop-update.js +1 -3
  63. package/dist/discord-chat.js +19 -38
  64. package/dist/discord-settings.js +4 -13
  65. package/dist/discord.js +4 -14
  66. package/dist/dispatch.d.ts +1 -1
  67. package/dist/dispatch.js +1 -1
  68. package/dist/dispose.d.ts +2 -3
  69. package/dist/dispose.js +17 -92
  70. package/dist/envelope.js +0 -3
  71. package/dist/exec.d.ts +0 -64
  72. package/dist/exec.js +3 -356
  73. package/dist/exhaustion.d.ts +22 -76
  74. package/dist/exhaustion.js +14 -140
  75. package/dist/export.js +8 -8
  76. package/dist/flow-engine.js +1 -1
  77. package/dist/flow-schedule.d.ts +154 -0
  78. package/dist/flow-schedule.js +321 -0
  79. package/dist/flow-share.js +1 -1
  80. package/dist/flow-triggers.d.ts +6 -1
  81. package/dist/flow-triggers.js +65 -10
  82. package/dist/flows-cli.js +8 -4
  83. package/dist/flows-ui.js +2 -0
  84. package/dist/flows.js +1 -1
  85. package/dist/guides.js +9 -29
  86. package/dist/invoke.d.ts +1 -24
  87. package/dist/invoke.js +17 -259
  88. package/dist/lead-status.js +2 -4
  89. package/dist/lead-voice.js +1 -1
  90. package/dist/mate-doors.d.ts +1 -2
  91. package/dist/mate-doors.js +12 -25
  92. package/dist/mate-tools.d.ts +2 -2
  93. package/dist/mate-tools.js +13 -24
  94. package/dist/modes.d.ts +19 -8
  95. package/dist/modes.js +24 -14
  96. package/dist/notification-rows.d.ts +30 -0
  97. package/dist/notification-rows.js +102 -0
  98. package/dist/operate-remote-arguments.js +2 -6
  99. package/dist/operate-remote.js +6 -7
  100. package/dist/operate.d.ts +0 -6
  101. package/dist/operate.js +226 -2248
  102. package/dist/phase-routing.d.ts +13 -23
  103. package/dist/phase-routing.js +13 -35
  104. package/dist/plan-auto.js +2 -2
  105. package/dist/plane-review.js +5 -11
  106. package/dist/planner-source.js +2 -1
  107. package/dist/policy.d.ts +1 -4
  108. package/dist/policy.js +0 -8
  109. package/dist/process-recovery-eligibility.js +2 -3
  110. package/dist/project-delete.js +24 -41
  111. package/dist/proposal.d.ts +4 -24
  112. package/dist/proposal.js +3 -88
  113. package/dist/provider-auth.d.ts +3 -3
  114. package/dist/provider-auth.js +4 -6
  115. package/dist/provider.d.ts +11 -25
  116. package/dist/provider.js +4 -36
  117. package/dist/push.js +1 -1
  118. package/dist/recipe-ui.js +6 -6
  119. package/dist/recipes.d.ts +3 -3
  120. package/dist/recipes.js +27 -23
  121. package/dist/result-shots.js +2 -2
  122. package/dist/retention.js +1 -1
  123. package/dist/run-closeout.d.ts +1 -1
  124. package/dist/run-closeout.js +1 -4
  125. package/dist/scope.d.ts +11 -65
  126. package/dist/scope.js +17 -82
  127. package/dist/serve.js +15 -24
  128. package/dist/server/chat.js +7 -30
  129. package/dist/server/flows.js +11 -10
  130. package/dist/server/guards.js +0 -1
  131. package/dist/server/pages-assets.js +1 -1
  132. package/dist/server/pages.js +0 -2
  133. package/dist/server/people-tokens.js +0 -4
  134. package/dist/server/route-table.d.ts +1 -1
  135. package/dist/server/route-table.js +2 -17
  136. package/dist/server/settings.js +6 -12
  137. package/dist/server/shared.d.ts +28 -212
  138. package/dist/server/shared.js +71 -780
  139. package/dist/server/task-views.js +9 -76
  140. package/dist/server/tasks.d.ts +1 -50
  141. package/dist/server/tasks.js +36 -946
  142. package/dist/setup-guide.d.ts +2 -2
  143. package/dist/slack-api.d.ts +5 -1
  144. package/dist/slack-api.js +2 -2
  145. package/dist/slack-chat.d.ts +9 -6
  146. package/dist/slack-chat.js +29 -53
  147. package/dist/slack-settings.js +6 -15
  148. package/dist/slack.js +6 -16
  149. package/dist/store.d.ts +200 -1702
  150. package/dist/store.js +811 -5439
  151. package/dist/surface.js +10 -24
  152. package/dist/task-composer.d.ts +1 -1
  153. package/dist/task-composer.js +1 -1
  154. package/dist/task-control.d.ts +3 -10
  155. package/dist/task-control.js +2 -7
  156. package/dist/teammate-desk.js +1 -1
  157. package/dist/teammate-question.js +12 -12
  158. package/dist/teammates-ui.js +1 -1
  159. package/dist/teams-chat.js +12 -9
  160. package/dist/teams-settings.js +2 -2
  161. package/dist/teams.js +4 -5
  162. package/dist/telegram-flow.js +5 -5
  163. package/dist/telegram-mate.d.ts +0 -3
  164. package/dist/telegram-mate.js +0 -1
  165. package/dist/telegram.js +4 -4
  166. package/dist/templates.d.ts +11 -7
  167. package/dist/templates.js +12 -9
  168. package/dist/toolroll-update.d.ts +20 -0
  169. package/dist/toolroll-update.js +104 -5
  170. package/dist/work-index.js +2 -6
  171. package/dist/workspace-revision.js +3 -1
  172. package/dist/workspace-ui.js +1 -1
  173. package/package.json +1 -1
  174. package/scripts/recovery-canary.mjs +3 -3
  175. package/dist/contest.d.ts +0 -318
  176. package/dist/contest.js +0 -753
  177. package/dist/held.d.ts +0 -150
  178. package/dist/held.js +0 -799
  179. package/dist/liveness.d.ts +0 -30
  180. package/dist/liveness.js +0 -42
  181. package/dist/pricing.d.ts +0 -45
  182. package/dist/pricing.js +0 -77
  183. package/dist/routine.d.ts +0 -228
  184. package/dist/routine.js +0 -764
  185. package/dist/slack-state.d.ts +0 -13
  186. package/dist/slack-state.js +0 -10
  187. package/dist/supervisor.mjs +0 -297
@@ -320,7 +320,6 @@ export function createPagesHandlers(runtime) {
320
320
  settings: options.telegramTokenFile !== undefined,
321
321
  building: store.liveClaims(project, now),
322
322
  runners: store.listRunners(),
323
- heldSessions: new Map(store.openHeldSessions().reduce((by, one) => by.set(one.runner, (by.get(one.runner) ?? 0) + 1), new Map())),
324
323
  worktrees: store
325
324
  .listWorktrees()
326
325
  .filter(one => project === null || sameRepo(one.repo, project)),
@@ -346,7 +345,6 @@ export function createPagesHandlers(runtime) {
346
345
  }),
347
346
  building: store.liveClaims(project, now),
348
347
  runners: store.listRunners(),
349
- heldSessions: new Map(store.openHeldSessions().reduce((by, one) => by.set(one.runner, (by.get(one.runner) ?? 0) + 1), new Map())),
350
348
  worktrees: store.listWorktrees().filter(one => project === null || sameRepo(one.repo, project)),
351
349
  episode: project === null ? null : store.latestWatchEpisode(project),
352
350
  outboxPending: store.pendingForAttention().length,
@@ -73,7 +73,6 @@ export function createPeopleHandlers(runtime) {
73
73
  `<p class="meta">Operators with all-project access also manage the instance.</p><fieldset><legend>Projects</legend>${projectChoices.length === 0 ? `<p class="meta">Add a project before granting selected access.</p>` : projectChoices.map(repo => `<label class="row"><input type="checkbox" name="projects" value="${escape(repo)}"${selected?.includes(repo) ? " checked" : ""}>${escape(projectName(repo))}</label>`).join("")}</fieldset>`;
74
74
  const cards = accounts.map(one => {
75
75
  const seen = lastSeenOf(one.name);
76
- const attended = store.openAttendedOf(one.name);
77
76
  const acts = store.recentActsOf(one.name);
78
77
  const standing = one.revokedAt !== null
79
78
  ? `revoked ${escape(one.revokedAt.slice(0, 16).replace("T", " "))} by ${personChip(one.revokedBy ?? "?")}`
@@ -87,9 +86,6 @@ export function createPeopleHandlers(runtime) {
87
86
  approverView && one.revokedAt === null ? `<details><summary>Edit project access</summary><form method="post" action="/people/projects">${hiddenFields({ csrf, name: one.name })}${accessFields(one.projects)}<label>Your password<input type="password" name="token" autocomplete="current-password" required></label><p class="meta">Changing access signs this person out and ends their derived sessions and modes. Previously approved work stays recorded.</p><button type="submit">Save project access</button></form></details>` : "",
88
87
  `<p class="meta">${seen === null ? "not signed in right now" : `signed in \u2014 active ${escape(new Date(seen).toISOString().slice(11, 16))} UTC`} \u00b7 joined ${escape(one.addedAt.slice(0, 10))}</p>`,
89
88
  `<p><a href="${escape(personHref(one.name))}">Tokens and remote activity</a></p>`,
90
- attended.length === 0
91
- ? ""
92
- : `<p class="row">watching now: ${attended.map(session => `<a href="/t/${escape(session.taskId)}">${escape(session.taskId)}</a>`).join(", ")}</p>`,
93
89
  acts.length === 0
94
90
  ? `<p class="meta">No recorded acts yet</p>`
95
91
  : acts.map(act => `<p class="row meta">${escape(act.kind)} ${escape(act.subject)} \u00b7 ${escape(act.at.slice(0, 16).replace("T", " "))}</p>`).join("\n"),
@@ -17,7 +17,7 @@ export type Caller = "anonymous" | "service" | "cookie" | "bearer" | "oauth" | "
17
17
  /** What the caller must hold: nothing, read, act (an approver or an `act` token), or a fresh password/provider check on top. */
18
18
  export type RouteScope = "none" | "read" | "act" | "step-up";
19
19
  /** Where the request's project comes from. */
20
- export type ProjectResolver = "none" | "session" | "form" | "form-path" | "task" | "run" | "decision" | "incident" | "routine" | "flow" | "proposal" | "conversation" | "coding" | "adapter";
20
+ export type ProjectResolver = "none" | "session" | "form" | "form-path" | "task" | "run" | "decision" | "incident" | "flow" | "proposal" | "conversation" | "coding" | "adapter";
21
21
  /**
22
22
  * What a project-limited account may do here:
23
23
  * - `deny`: instance access only.
@@ -120,7 +120,6 @@ get("tasks", "runs", "/runs", { limited: "collection" });
120
120
  get("pages", "menu", "/menu", { limited: "collection", ...free });
121
121
  get("tasks", "run.page", `^\\/r\\/${ID}$`, { project: "run", limited: "resource", ...free, sample: "/r/1" });
122
122
  get("tasks", "run.evidence", `^\\/r\\/${ID}\\/evidence\\/${ID}$`, { project: "run", limited: "resource", ...free, sample: "/r/1/evidence/2" });
123
- get("tasks", "contest.page", `^\\/contest\\/${ID}$`, { ...free, sample: "/contest/1" });
124
123
  get("pages", "caps", "/caps");
125
124
  get("flows", "flows.new", "/flows/new", { limited: "resource", ...free });
126
125
  get("flows", "flows.gallery", "^\\/flows\\/new\\/[a-z-]{1,40}$", { limited: "resource", ...free, sample: "/flows/new/triage" });
@@ -139,13 +138,14 @@ for (const one of ["run", "start", "new", "edit", "from-task", "preview", "expor
139
138
  get("flows", `recipes.${one}`, `/recipes/${one}`, { limited: "collection" });
140
139
  // Any other recipe screen answers the recipe area's own refusal.
141
140
  get("flows", "recipes.other", "^\\/recipes\\/.*$", { sample: "/recipes/unknown" });
141
+ // v115: routines became scheduled flows; old links to them land on the flows page.
142
142
  get("tasks", "routines", "/routines", { limited: "collection" });
143
143
  get("chat", "chat.stream", "/chat/stream", free);
144
144
  get("chat", "chat.mate-status", "/chat/mate/status", free);
145
145
  get("chat", "chat.demo-live", "/chat/demo/live", free);
146
146
  get("chat", "chat.page", "/chat", { project: "conversation", limited: "conversation", ...free });
147
147
  get("chat", "chat.ack", `^\\/chat\\/ack\\/${ID}$`, { sample: "/chat/ack/1" });
148
- get("tasks", "routine.page", `^\\/routines\\/${ID}$`, { project: "routine", limited: "resource", sample: "/routines/1" });
148
+ get("tasks", "routine.page", `^\\/routines\\/${ID}$`, { limited: "resource", sample: "/routines/1" });
149
149
  get("settings", "control", "/control");
150
150
  get("settings", "control.connection", "/control/connection");
151
151
  get("chat", "chat.action", `^\\/chat\\/action\\/${ID}$`, { project: "proposal", limited: "proposal", ...free, sample: "/chat/action/1" });
@@ -166,8 +166,6 @@ get("chat", "lead.status", "/lead/status", free);
166
166
  get("chat", "chat.task-status", "/chat/task-status", free);
167
167
  get("tasks", "decision.evidence", `^\\/d\\/${ID}\\/evidence\\/${ID}$`, { project: "decision", limited: "resource", ...free, sample: "/d/1/evidence/2" });
168
168
  // ---- console actions -------------------------------------------------------------------------------------------
169
- // The attended beat guards itself (cookie, form type, Sec-Fetch-Site) before the shared mutation guard.
170
- post("tasks", "session.attended-beats", "/session/attended-beats", { callers: ["cookie"], scopeRefusal: "your login can watch, not keep sessions alive", callerRefusal: { status: 403, type: "text/plain; charset=utf-8", body: "watching is a browser session's act" } });
171
169
  // The scripted demo: the handler answers a non-browser caller "no page here".
172
170
  post("chat", "chat.demo", "^\\/chat\\/demo\\/(ask|[0-9]{1,9}\\/(approve|change|revise|complete))$", { sample: "/chat/demo/ask" });
173
171
  post("settings", "provider.resume", "^\\/providers\\/[a-z]+\\/resume$", { sample: "/providers/codex/resume" });
@@ -243,12 +241,6 @@ post("people", "people.revoke", "/people/revoke", { scope: "step-up" });
243
241
  post("pages", "fleet.retire", "/fleet/runner/retire", { scope: "step-up" });
244
242
  // Answering a parked decision needs act access and irreversible-choice confirmation, not password step-up.
245
243
  post("tasks", "decision.answer", `^\\/d\\/${ID}\\/answer$`, { project: "decision", limited: "resource", scope: "act", sample: "/d/1/answer" });
246
- post("tasks", "contest.act.arm", `^\\/contest\\/${ID}\\/arm$`, { sample: "/contest/1/arm" });
247
- post("tasks", "contest.act.pick", `^\\/contest\\/${ID}\\/pick$`, { scope: "step-up", sample: "/contest/1/pick" });
248
- post("tasks", "contest.act.abandon", `^\\/contest\\/${ID}\\/abandon$`, { scope: "step-up", sample: "/contest/1/abandon" });
249
- post("tasks", "task.attend.attend-preview", `^\\/t\\/${TASK}\\/attend-preview$`, { callers: ["cookie"], project: "task", sample: "/t/one/attend-preview" });
250
- post("tasks", "task.attend.attend", `^\\/t\\/${TASK}\\/attend$`, { scope: "step-up", project: "task", sample: "/t/one/attend" });
251
- post("tasks", "task.attend.attend-revoke", `^\\/t\\/${TASK}\\/attend-revoke$`, { callers: ["cookie"], project: "task", sample: "/t/one/attend-revoke" });
252
244
  post("tasks", "task.act.hold", `^\\/t\\/${TASK}\\/hold$`, { project: "task", limited: "resource", sample: "/t/one/hold" });
253
245
  post("tasks", "task.act.unhold", `^\\/t\\/${TASK}\\/unhold$`, { project: "task", limited: "resource", sample: "/t/one/unhold" });
254
246
  post("tasks", "task.act.requeue", `^\\/t\\/${TASK}\\/requeue$`, { project: "task", limited: "resource", sample: "/t/one/requeue" });
@@ -291,15 +283,8 @@ post("chat", "chat.ack-send", `^\\/chat\\/ack\\/${ID}$`, { scope: "step-up", sam
291
283
  for (const one of ["prepare", "preview", "import", "save"])
292
284
  post("flows", `recipes.${one}-send`, `/recipes/${one}`, { limited: "collection" });
293
285
  post("flows", "recipes.launch-send", "/recipes/launch", { limited: "collection" });
294
- post("tasks", "routines.add", "/routines/add", { project: "form", limited: "collection" });
295
- post("tasks", "routine.act.approve", `^\\/routines\\/${ID}\\/approve$`, { scope: "step-up", project: "routine", limited: "resource", sample: "/routines/1/approve" });
296
- post("tasks", "routine.act.refresh", `^\\/routines\\/${ID}\\/refresh$`, { project: "routine", limited: "resource", sample: "/routines/1/refresh" });
297
- post("tasks", "routine.act.pause", `^\\/routines\\/${ID}\\/pause$`, { project: "routine", limited: "resource", sample: "/routines/1/pause" });
298
- post("tasks", "routine.act.resume", `^\\/routines\\/${ID}\\/resume$`, { project: "routine", limited: "resource", sample: "/routines/1/resume" });
299
- post("tasks", "routine.act.run-now", `^\\/routines\\/${ID}\\/run-now$`, { scope: "step-up", project: "routine", limited: "resource", sample: "/routines/1/run-now" });
300
286
  post("tasks", "run.act", `^\\/r\\/${ID}\\/(note|comment|revise|draft-repair|checks|add-tests)$`, { project: "run", limited: "resource", sample: "/r/1/note" });
301
287
  post("tasks", "session.editor-links", "/session/editor-links", { viewer: true });
302
- post("tasks", "run.turn", `^\\/r\\/${ID}\\/turn$`, { project: "run", sample: "/r/1/turn" });
303
288
  post("tasks", "incident.resolve", `^\\/i\\/${ID}\\/resolve$`, { project: "incident", sample: "/i/1/resolve" });
304
289
  /** The sole route table, in match order. */
305
290
  export const ROUTES = Object.freeze(rows.map(one => Object.freeze(one)));
@@ -66,7 +66,7 @@ import { startConnect } from "../mcp-connect.js";
66
66
  import { decideProposal, listProposals, memoryStatus } from "../memory-pass.js";
67
67
  import { checkModels, isNewModel, modelOptions, modelWords, RUNTIME_TOOLS, runtimeStates, seenModels, setWatch, updateRuntime, watchState } from "../model-catalog.js";
68
68
  import { modelsHtml, modelsScript } from "../models-ui.js";
69
- import { MODE_MAX_DAYS, modeDigestOf, modeTermsJson, modeWords, presetTerms } from "../modes.js";
69
+ import { CHAT_APPROVE_ALL, MODE_MAX_DAYS, modeDigestOf, modeTermsJson, modeWords, presetTerms } from "../modes.js";
70
70
  import { monitoringChange, readMonitoring, saveMonitoring } from "../monitoring-settings.js";
71
71
  import { monitoringHtml, signingSecretHtml } from "../monitoring-ui.js";
72
72
  import { openRouterPickerScript } from "../openrouter-models.js";
@@ -96,7 +96,6 @@ import { addProjectInstructions, ASSISTANTS, detectPreparation, modelChoices, pr
96
96
  import { skillsHtml, skillsScript } from "../skills-ui.js";
97
97
  import { checkSlackCredentials, clearSlackCredentials, loadSlackCredentials, saveSlackCredentials, SLACK_MANIFEST, SlackError } from "../slack-api.js";
98
98
  import { slackSettingsHtml } from "../slack-settings.js";
99
- import { SlackState } from "../slack-state.js";
100
99
  import { spendCsv, spendHtml } from "../spend-ui.js";
101
100
  import { budgetStates, monthNamed, spendItems, usd as spendUsd, teammateNames as teammateNamesOf } from "../spend.js";
102
101
  import { removeSsoSettings, saveSsoSettings, SSO_CALLBACK, ssoChangeWords } from "../sso-settings.js";
@@ -200,10 +199,6 @@ export function createSettingsHandlers(runtime) {
200
199
  `<option value="">approve plans and merges here, with my password (the default)</option>` +
201
200
  `<option value="1">approve plans and merge ready pull requests from my paired chat, two taps each</option>` +
202
201
  `</select><span class="meta">Plans that widen permissions, exceed the attempt cap or touch protected paths still open Toolroll.</span></label>`,
203
- `<label>If a subscription runs out<select name="allow-paid-fallback">` +
204
- `<option value="">never switch to a paid API key on its own (the default, every preset)</option>` +
205
- `<option value="1">allow the approved fallback — spend moves to that account</option>` +
206
- `</select></label>`,
207
202
  `<button type="submit">Read the full terms</button>`,
208
203
  `</form>`,
209
204
  `</div>`,
@@ -1603,7 +1598,7 @@ export function createSettingsHandlers(runtime) {
1603
1598
  const body = readForm(posted, CONSOLE_FORMS.slack);
1604
1599
  if (who.via !== "cookie" || who.role !== "approver" || restricted() || !options.configDir)
1605
1600
  return refuse(response, who, 403, "An installation approver can connect Slack.", "/settings");
1606
- const dir = options.configDir, state = new SlackState(store), action = url.pathname.split("/").at(-1);
1601
+ const dir = options.configDir, state = new ChatState(store, "slack"), action = url.pathname.split("/").at(-1);
1607
1602
  const show = (problem, status = 400) => sendScreen(response, status, screen("Slack", slackSettingsHtml(store, dir, who.session.csrf, { problem, who: who.name }), { chrome: chromeFor(projectOf(who, request) ?? null, "settings") }));
1608
1603
  if (["password", "app-token", "bot-token"].some(key => body.getAll(key).length > 1))
1609
1604
  return show("Submit one value for each field.");
@@ -2049,16 +2044,15 @@ export function createSettingsHandlers(runtime) {
2049
2044
  planAuto: body.get("plan-auto") === "1",
2050
2045
  reviewAuto: false,
2051
2046
  reviewRetryAuto: false,
2052
- // The paid-fallback grant is NEVER a preset default (R8): unchecked
2053
- // stays false on every preset — only the explicit box grants it.
2054
- allowPaidFallback: body.get("allow-paid-fallback") === "1",
2055
2047
  // The repair-auto grant is the SAME rule (v40): unchecked stays
2056
2048
  // false on every preset — only the explicit box grants it, and the
2057
2049
  // attempt cap it carries is meaningless without it.
2058
2050
  repairAuto: false,
2059
2051
  repairMaxAttempts: 0,
2060
- // Approving from the paired chat: only the explicit choice grants it.
2052
+ // Approving from the paired chat: only the explicit choice grants it, naming every chat app it covers (a grant
2053
+ // signed before it named them stays Telegram only).
2061
2054
  chatApprove: body.get("chat-approve") === "1",
2055
+ ...(body.get("chat-approve") === "1" ? { chatApproveChats: CHAT_APPROVE_ALL } : {}),
2062
2056
  publication: body.get("publication") === "automerge" ? "automerge" : "notify",
2063
2057
  };
2064
2058
  if (["review-auto", "review-retry-auto", "repair-auto"].some(field => body.get(field) === "1")) {
@@ -2080,7 +2074,7 @@ export function createSettingsHandlers(runtime) {
2080
2074
  `<input type="hidden" name="csrf" value="${escape(who.session.csrf)}">` +
2081
2075
  `<input type="hidden" name="nonce" value="${escape(nonce)}">` +
2082
2076
  `<input type="hidden" name="digest" value="${escape(digest)}">` +
2083
- ["name", "days", "publication", "auto-approve", "plan-auto", "chat-approve", "allow-paid-fallback"]
2077
+ ["name", "days", "publication", "auto-approve", "plan-auto", "chat-approve"]
2084
2078
  .map(field => `<input type="hidden" name="${field}" value="${escape(body.get(field) ?? "")}">`)
2085
2079
  .join("") +
2086
2080
  `<input type="hidden" name="expiry" value="${escape(expiry)}">` +