toolroll 0.9.51 → 0.9.53

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 (164) hide show
  1. package/CHANGELOG.md +20 -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/backup.d.ts +8 -0
  8. package/dist/backup.js +10 -1
  9. package/dist/board.d.ts +3 -19
  10. package/dist/board.js +4 -42
  11. package/dist/browser/workspace.css +1 -1
  12. package/dist/browser/workspace.js +18 -18
  13. package/dist/browser-workspace.d.ts +3 -1
  14. package/dist/builder.d.ts +9 -40
  15. package/dist/builder.js +50 -257
  16. package/dist/chat-actions.js +3 -14
  17. package/dist/chat-controls.d.ts +0 -4
  18. package/dist/chat-controls.js +0 -1
  19. package/dist/chat-decide.js +1 -3
  20. package/dist/chat-delivery.d.ts +0 -2
  21. package/dist/chat-delivery.js +0 -1
  22. package/dist/chat-quiet.js +2 -2
  23. package/dist/chat-task-actions.d.ts +1 -1
  24. package/dist/chat-task-actions.js +0 -2
  25. package/dist/claim.d.ts +79 -244
  26. package/dist/claim.js +688 -1023
  27. package/dist/cli.d.ts +2 -0
  28. package/dist/cli.js +10 -2
  29. package/dist/contracts/cli.d.ts +0 -13
  30. package/dist/contracts/cli.js +0 -2
  31. package/dist/contracts/console-api.d.ts +5 -10
  32. package/dist/contracts/console-api.js +4 -10
  33. package/dist/contracts/flow.d.ts +50 -0
  34. package/dist/contracts/flow.js +26 -1
  35. package/dist/contracts/lead-tools.d.ts +0 -13
  36. package/dist/contracts/lead-tools.js +3 -3
  37. package/dist/contracts/store-json.d.ts +1 -25
  38. package/dist/contracts/store-json.js +3 -13
  39. package/dist/converse.d.ts +1 -11
  40. package/dist/converse.js +6 -38
  41. package/dist/coordinator.d.ts +0 -1
  42. package/dist/coordinator.js +3 -5
  43. package/dist/database-reclaim.d.ts +4 -0
  44. package/dist/database-reclaim.js +13 -0
  45. package/dist/demo.d.ts +1 -1
  46. package/dist/demo.js +17 -27
  47. package/dist/desktop-update-gate.js +0 -1
  48. package/dist/desktop-update.d.ts +1 -1
  49. package/dist/desktop-update.js +1 -3
  50. package/dist/dispatch.d.ts +1 -1
  51. package/dist/dispatch.js +1 -1
  52. package/dist/dispose.d.ts +2 -3
  53. package/dist/dispose.js +17 -92
  54. package/dist/envelope.js +0 -3
  55. package/dist/exec.d.ts +0 -64
  56. package/dist/exec.js +3 -356
  57. package/dist/exhaustion.d.ts +22 -76
  58. package/dist/exhaustion.js +14 -140
  59. package/dist/export.js +4 -4
  60. package/dist/flow-engine.js +1 -1
  61. package/dist/flow-schedule.d.ts +154 -0
  62. package/dist/flow-schedule.js +321 -0
  63. package/dist/flow-share.js +1 -1
  64. package/dist/flow-triggers.d.ts +6 -1
  65. package/dist/flow-triggers.js +65 -10
  66. package/dist/flows-cli.js +8 -4
  67. package/dist/flows-ui.js +2 -0
  68. package/dist/flows.js +1 -1
  69. package/dist/guides.js +9 -29
  70. package/dist/invoke.d.ts +1 -24
  71. package/dist/invoke.js +17 -259
  72. package/dist/lead-status.js +2 -4
  73. package/dist/lead-voice.js +1 -1
  74. package/dist/live-bus.d.ts +3 -3
  75. package/dist/live-bus.js +3 -3
  76. package/dist/mate-doors.d.ts +1 -2
  77. package/dist/mate-doors.js +12 -25
  78. package/dist/mate-tools.d.ts +2 -2
  79. package/dist/mate-tools.js +10 -22
  80. package/dist/modes.d.ts +6 -7
  81. package/dist/modes.js +7 -13
  82. package/dist/monitoring.js +32 -8
  83. package/dist/notification-delivery.d.ts +19 -0
  84. package/dist/notification-delivery.js +37 -0
  85. package/dist/operate-remote-arguments.js +2 -6
  86. package/dist/operate-remote.js +6 -7
  87. package/dist/operate.d.ts +0 -6
  88. package/dist/operate.js +283 -2378
  89. package/dist/phase-routing.d.ts +13 -23
  90. package/dist/phase-routing.js +13 -35
  91. package/dist/plan-auto.js +2 -2
  92. package/dist/planner-source.js +2 -1
  93. package/dist/policy.d.ts +1 -4
  94. package/dist/policy.js +0 -8
  95. package/dist/process-recovery-eligibility.js +2 -3
  96. package/dist/process-recovery-settlement.js +1 -0
  97. package/dist/project-delete.js +3 -15
  98. package/dist/proposal.d.ts +4 -24
  99. package/dist/proposal.js +3 -88
  100. package/dist/provider-auth.d.ts +3 -3
  101. package/dist/provider-auth.js +4 -6
  102. package/dist/provider.d.ts +11 -25
  103. package/dist/provider.js +4 -36
  104. package/dist/push.js +1 -1
  105. package/dist/recipe-ui.js +6 -6
  106. package/dist/recipes.d.ts +3 -3
  107. package/dist/recipes.js +27 -23
  108. package/dist/retention.js +6 -5
  109. package/dist/run-closeout.d.ts +1 -1
  110. package/dist/run-closeout.js +1 -4
  111. package/dist/scope.d.ts +11 -65
  112. package/dist/scope.js +17 -82
  113. package/dist/serve.js +16 -25
  114. package/dist/server/chat.js +7 -30
  115. package/dist/server/flows.js +11 -10
  116. package/dist/server/guards.js +0 -1
  117. package/dist/server/pages-assets.js +1 -1
  118. package/dist/server/pages.js +0 -2
  119. package/dist/server/people-tokens.js +0 -4
  120. package/dist/server/route-table.d.ts +1 -1
  121. package/dist/server/route-table.js +2 -17
  122. package/dist/server/settings.js +4 -9
  123. package/dist/server/shared.d.ts +29 -212
  124. package/dist/server/shared.js +73 -780
  125. package/dist/server/task-views.js +9 -76
  126. package/dist/server/tasks.d.ts +1 -50
  127. package/dist/server/tasks.js +36 -946
  128. package/dist/setup-guide.d.ts +2 -2
  129. package/dist/slack-chat.d.ts +0 -2
  130. package/dist/slack-chat.js +0 -1
  131. package/dist/store.d.ts +142 -1382
  132. package/dist/store.js +900 -4083
  133. package/dist/surface.js +10 -25
  134. package/dist/task-composer.d.ts +1 -1
  135. package/dist/task-composer.js +1 -1
  136. package/dist/task-control.d.ts +3 -10
  137. package/dist/task-control.js +2 -7
  138. package/dist/teammate-desk.js +1 -1
  139. package/dist/teammates-ui.js +1 -1
  140. package/dist/telegram-mate.d.ts +0 -3
  141. package/dist/telegram-mate.js +0 -1
  142. package/dist/templates.d.ts +11 -7
  143. package/dist/templates.js +12 -9
  144. package/dist/toolroll-update.d.ts +24 -3
  145. package/dist/toolroll-update.js +159 -16
  146. package/dist/webhooks.d.ts +10 -22
  147. package/dist/webhooks.js +43 -81
  148. package/dist/work-index.js +3 -7
  149. package/dist/workspace-revision.d.ts +28 -1
  150. package/dist/workspace-revision.js +276 -49
  151. package/dist/workspace-ui.js +1 -1
  152. package/package.json +1 -3
  153. package/scripts/recovery-canary.mjs +3 -3
  154. package/dist/contest.d.ts +0 -318
  155. package/dist/contest.js +0 -753
  156. package/dist/held.d.ts +0 -150
  157. package/dist/held.js +0 -799
  158. package/dist/liveness.d.ts +0 -30
  159. package/dist/liveness.js +0 -42
  160. package/dist/pricing.d.ts +0 -45
  161. package/dist/pricing.js +0 -77
  162. package/dist/routine.d.ts +0 -228
  163. package/dist/routine.js +0 -764
  164. package/dist/supervisor.mjs +0 -297
@@ -36,7 +36,7 @@ const PWA_MANIFEST = JSON.stringify({
36
36
  // paths — the payload URL is data, revalidated, never handed raw to the
37
37
  // browser (arc 3 finding 5).
38
38
  const PWA_WORKER = `// Toolroll — push only; deliberately NO fetch handler (no offline cache of an authenticated console).
39
- const SHAPES = [/^\\/next$/, /^\\/review$/, /^\\/system$/, /^\\/routines$/, /^\\/routines\\/[0-9]+$/, /^\\/d\\/[0-9]+$/, /^\\/contest\\/[0-9]+$/, /^\\/r\\/[0-9]+$/];
39
+ const SHAPES = [/^\\/next$/, /^\\/review$/, /^\\/system$/, /^\\/routines$/, /^\\/routines\\/[0-9]+$/, /^\\/d\\/[0-9]+$/, /^\\/r\\/[0-9]+$/];
40
40
  self.addEventListener("push", function (event) {
41
41
  var data = {};
42
42
  try { data = event.data ? event.data.json() : {}; } catch (e) {}
@@ -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)));
@@ -200,10 +200,6 @@ export function createSettingsHandlers(runtime) {
200
200
  `<option value="">approve plans and merges here, with my password (the default)</option>` +
201
201
  `<option value="1">approve plans and merge ready pull requests from my paired chat, two taps each</option>` +
202
202
  `</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
203
  `<button type="submit">Read the full terms</button>`,
208
204
  `</form>`,
209
205
  `</div>`,
@@ -1111,6 +1107,8 @@ export function createSettingsHandlers(runtime) {
1111
1107
  if (who.via !== "cookie" || !store.isInstanceOperator(who.name) || databaseFile === null)
1112
1108
  return refuse(response, who, 403, "An instance operator looks after backups.", "/settings");
1113
1109
  const made = await backupNow(store, databaseFile, "manual", clock);
1110
+ if (made.ok && made.checkpoint !== null && "problem" in made.checkpoint)
1111
+ return redirect(response, `/settings/backups?problem=${encodeURIComponent(`Backed up. The activity log failed its tamper check, so it was not anchored: ${made.checkpoint.problem}`)}`);
1114
1112
  return redirect(response, made.ok ? `/settings/backups?said=${encodeURIComponent("Backed up.")}` : `/settings/backups?problem=${encodeURIComponent(`The backup failed: ${made.error}`)}`);
1115
1113
  }
1116
1114
  // Sprint 8: change the backup schedule. A step-up; the ledger keeps before → after.
@@ -1978,7 +1976,7 @@ export function createSettingsHandlers(runtime) {
1978
1976
  const existing = loadBotToken({}, options.telegramTokenFile);
1979
1977
  const hasEnv = (envValue(process.env, "TELEGRAM_TOKEN") ?? "") !== "";
1980
1978
  const csrf = who.via === "cookie" ? who.session.csrf : "";
1981
- return sendScreen(response, 400, settingsPage(chromeFor(who.via === "cookie" ? who.session.project : defaultProject, "settings"), existing, hasEnv, csrf, saved.message, null, null, null, null, {
1979
+ return sendScreen(response, 400, settingsPage(chromeFor(who.via === "cookie" ? who.session.project : defaultProject, "settings"), existing, hasEnv, csrf, saved.message, options.configDir === undefined ? null : effectivePrimary(process.env, options.configDir, loadBotToken(process.env, options.telegramTokenFile) !== null), null, null, null, {
1982
1980
  ...store.permissionDefault(),
1983
1981
  canManage: who.role === "approver",
1984
1982
  }));
@@ -2047,9 +2045,6 @@ export function createSettingsHandlers(runtime) {
2047
2045
  planAuto: body.get("plan-auto") === "1",
2048
2046
  reviewAuto: false,
2049
2047
  reviewRetryAuto: false,
2050
- // The paid-fallback grant is NEVER a preset default (R8): unchecked
2051
- // stays false on every preset — only the explicit box grants it.
2052
- allowPaidFallback: body.get("allow-paid-fallback") === "1",
2053
2048
  // The repair-auto grant is the SAME rule (v40): unchecked stays
2054
2049
  // false on every preset — only the explicit box grants it, and the
2055
2050
  // attempt cap it carries is meaningless without it.
@@ -2078,7 +2073,7 @@ export function createSettingsHandlers(runtime) {
2078
2073
  `<input type="hidden" name="csrf" value="${escape(who.session.csrf)}">` +
2079
2074
  `<input type="hidden" name="nonce" value="${escape(nonce)}">` +
2080
2075
  `<input type="hidden" name="digest" value="${escape(digest)}">` +
2081
- ["name", "days", "publication", "auto-approve", "plan-auto", "chat-approve", "allow-paid-fallback"]
2076
+ ["name", "days", "publication", "auto-approve", "plan-auto", "chat-approve"]
2082
2077
  .map(field => `<input type="hidden" name="${field}" value="${escape(body.get(field) ?? "")}">`)
2083
2078
  .join("") +
2084
2079
  `<input type="hidden" name="expiry" value="${escape(expiry)}">` +