toolroll 0.9.94 → 0.9.95

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 (191) hide show
  1. package/CHANGELOG.md +12 -1
  2. package/dist/agent-fence.d.ts +6 -0
  3. package/dist/agent-fence.js +2 -2
  4. package/dist/assignment-status.d.ts +1 -1
  5. package/dist/assignment-ui.js +2 -0
  6. package/dist/attest.d.ts +3 -0
  7. package/dist/attest.js +10 -0
  8. package/dist/browser/terminal.js +1 -1
  9. package/dist/browser/workspace.css +1 -1
  10. package/dist/browser/workspace.js +24 -24
  11. package/dist/browser-crew.js +2 -0
  12. package/dist/browser-workspace.d.ts +5 -0
  13. package/dist/build-review.js +2 -0
  14. package/dist/builder.d.ts +7 -2
  15. package/dist/builder.js +183 -79
  16. package/dist/card-catalog.d.ts +209 -0
  17. package/dist/card-catalog.js +133 -0
  18. package/dist/card-cli.d.ts +43 -0
  19. package/dist/card-cli.js +44 -0
  20. package/dist/card-render.d.ts +26 -0
  21. package/dist/card-render.js +223 -0
  22. package/dist/card-sources.d.ts +68 -0
  23. package/dist/card-sources.js +234 -0
  24. package/dist/chat-actions.d.ts +95 -8
  25. package/dist/chat-actions.js +449 -45
  26. package/dist/chat-approval.d.ts +13 -0
  27. package/dist/chat-approval.js +39 -9
  28. package/dist/chat-card-part.d.ts +11 -0
  29. package/dist/chat-card-part.js +18 -0
  30. package/dist/chat-card.d.ts +46 -0
  31. package/dist/chat-card.js +99 -0
  32. package/dist/chat-channel.d.ts +2 -0
  33. package/dist/chat-channel.js +26 -14
  34. package/dist/chat-controls.d.ts +4 -0
  35. package/dist/chat-controls.js +4 -0
  36. package/dist/chat-decide.d.ts +3 -14
  37. package/dist/chat-decide.js +43 -54
  38. package/dist/chat-delivery.js +13 -4
  39. package/dist/chat-flow.d.ts +0 -27
  40. package/dist/chat-flow.js +31 -2
  41. package/dist/chat-quiet.js +1 -1
  42. package/dist/chat-task-actions.d.ts +3 -2
  43. package/dist/chat-task-actions.js +19 -5
  44. package/dist/chat-terms.d.ts +14 -2
  45. package/dist/chat-terms.js +35 -0
  46. package/dist/check-failure.d.ts +2 -0
  47. package/dist/check-failure.js +34 -2
  48. package/dist/check-repair.d.ts +47 -0
  49. package/dist/check-repair.js +240 -0
  50. package/dist/claim.d.ts +6 -0
  51. package/dist/claim.js +13 -2
  52. package/dist/cli-help.d.ts +1 -1
  53. package/dist/cli-help.js +2 -0
  54. package/dist/cli.js +4 -0
  55. package/dist/coding-provider.d.ts +1 -1
  56. package/dist/coding-provider.js +9 -5
  57. package/dist/coding-workspace.d.ts +1 -1
  58. package/dist/coding-workspace.js +4 -4
  59. package/dist/contracts/chat-actions.d.ts +83 -0
  60. package/dist/contracts/chat-actions.js +18 -0
  61. package/dist/contracts/chat-card.d.ts +91 -0
  62. package/dist/contracts/chat-card.js +54 -0
  63. package/dist/contracts/chat-content.d.ts +191 -0
  64. package/dist/contracts/chat-content.js +4 -0
  65. package/dist/contracts/console-api.d.ts +2 -0
  66. package/dist/contracts/console-api.js +2 -0
  67. package/dist/contracts/flow-propose.d.ts +8 -1
  68. package/dist/contracts/flow-propose.js +3 -1
  69. package/dist/contracts/flow-send.d.ts +126 -0
  70. package/dist/contracts/flow-send.js +3 -0
  71. package/dist/contracts/lead-tools.d.ts +173 -0
  72. package/dist/contracts/lead-tools.js +15 -0
  73. package/dist/contracts/store-json.d.ts +16 -0
  74. package/dist/discord-chat.js +14 -0
  75. package/dist/dispatch.d.ts +1 -1
  76. package/dist/dispatch.js +7 -1
  77. package/dist/dispose.d.ts +19 -0
  78. package/dist/dispose.js +47 -7
  79. package/dist/eval-oracle.js +4 -1
  80. package/dist/evidence-pair.d.ts +53 -0
  81. package/dist/evidence-pair.js +128 -0
  82. package/dist/exhaustion.d.ts +17 -4
  83. package/dist/exhaustion.js +44 -4
  84. package/dist/export.js +1 -1
  85. package/dist/failing-first.d.ts +31 -0
  86. package/dist/failing-first.js +44 -0
  87. package/dist/fallback-schema.d.ts +8 -0
  88. package/dist/fallback-schema.js +40 -0
  89. package/dist/fallback.d.ts +138 -0
  90. package/dist/fallback.js +605 -0
  91. package/dist/findings.d.ts +224 -0
  92. package/dist/findings.js +449 -0
  93. package/dist/fleet-schema.d.ts +17 -0
  94. package/dist/fleet-schema.js +135 -0
  95. package/dist/flow-browser.js +2 -2
  96. package/dist/flow-code.d.ts +2 -2
  97. package/dist/flow-code.js +9 -5
  98. package/dist/flow-result-card.d.ts +22 -0
  99. package/dist/flow-result-card.js +55 -0
  100. package/dist/flow-send.js +12 -9
  101. package/dist/flow-steps.js +1 -1
  102. package/dist/flow-triggers.js +1 -1
  103. package/dist/flow-unattended.d.ts +26 -0
  104. package/dist/flow-unattended.js +12 -3
  105. package/dist/flows-ui.js +10 -2
  106. package/dist/follow-through-brief.d.ts +12 -0
  107. package/dist/follow-through-brief.js +31 -0
  108. package/dist/follow-through-cli.d.ts +23 -0
  109. package/dist/follow-through-cli.js +153 -0
  110. package/dist/git-incident.d.ts +16 -0
  111. package/dist/git-incident.js +69 -6
  112. package/dist/handover-note.d.ts +33 -0
  113. package/dist/handover-note.js +106 -0
  114. package/dist/invoke.d.ts +20 -1
  115. package/dist/invoke.js +108 -44
  116. package/dist/lead-cli.js +26 -24
  117. package/dist/lead-contract.js +1 -0
  118. package/dist/lead-doors.d.ts +4 -0
  119. package/dist/lead-doors.js +2 -2
  120. package/dist/lead-progress.js +2 -0
  121. package/dist/lead-tools.js +111 -32
  122. package/dist/lead.js +1 -1
  123. package/dist/local-owner.js +5 -0
  124. package/dist/modes.d.ts +6 -6
  125. package/dist/modes.js +3 -1
  126. package/dist/needs-you.d.ts +5 -2
  127. package/dist/needs-you.js +14 -2
  128. package/dist/operate-remote-arguments.js +1 -0
  129. package/dist/operate-remote.js +1 -1
  130. package/dist/operate.d.ts +1 -1
  131. package/dist/operate.js +165 -14
  132. package/dist/plan-login.d.ts +14 -0
  133. package/dist/plan-login.js +58 -0
  134. package/dist/planner.d.ts +1 -1
  135. package/dist/planner.js +2 -1
  136. package/dist/project-delete-ui.d.ts +12 -0
  137. package/dist/project-delete-ui.js +18 -1
  138. package/dist/project-delete.js +6 -0
  139. package/dist/project-knowledge.d.ts +6 -6
  140. package/dist/project-tools.d.ts +4 -4
  141. package/dist/project-tools.js +23 -7
  142. package/dist/protected-storage-look.d.ts +1 -0
  143. package/dist/protected-storage-look.js +13 -0
  144. package/dist/protected-storage.d.ts +70 -39
  145. package/dist/protected-storage.js +196 -147
  146. package/dist/provider-capture.d.ts +34 -0
  147. package/dist/provider-capture.js +53 -0
  148. package/dist/release-carry.js +10 -1
  149. package/dist/release.js +9 -1
  150. package/dist/scope.d.ts +22 -3
  151. package/dist/scope.js +71 -20
  152. package/dist/scout.d.ts +1 -1
  153. package/dist/scout.js +2 -1
  154. package/dist/serve.js +2 -0
  155. package/dist/server/chat.js +2 -2
  156. package/dist/server/console.css +50 -0
  157. package/dist/server/render-chat.js +14 -1
  158. package/dist/server/render-follow-through.d.ts +11 -0
  159. package/dist/server/render-follow-through.js +68 -0
  160. package/dist/server/render-results.js +1 -1
  161. package/dist/server/render-tasks.d.ts +3 -0
  162. package/dist/server/render-tasks.js +24 -5
  163. package/dist/server/route-table.js +3 -0
  164. package/dist/server/settings.js +37 -12
  165. package/dist/server/task-views.js +5 -0
  166. package/dist/server/tasks.js +76 -0
  167. package/dist/settings-groups-ui.d.ts +1 -0
  168. package/dist/settings-groups-ui.js +3 -1
  169. package/dist/settings-kit.js +3 -2
  170. package/dist/settings-proofs.js +3 -1
  171. package/dist/setup-guide.d.ts +2 -2
  172. package/dist/slack-chat.js +14 -1
  173. package/dist/steer-mailbox.d.ts +8 -1
  174. package/dist/steer-mailbox.js +164 -6
  175. package/dist/store.d.ts +11 -4
  176. package/dist/store.js +93 -30
  177. package/dist/surface.js +24 -4
  178. package/dist/task-control.d.ts +2 -1
  179. package/dist/task-status.js +5 -0
  180. package/dist/teardown-git.d.ts +2 -0
  181. package/dist/teardown-git.js +42 -16
  182. package/dist/telegram-lead.js +23 -6
  183. package/dist/telegram.js +12 -1
  184. package/dist/toolroll-update.js +2 -0
  185. package/dist/work-index.js +22 -7
  186. package/dist/work-summary.d.ts +1 -1
  187. package/dist/work-summary.js +5 -2
  188. package/dist/workspace-ui.d.ts +3 -1
  189. package/dist/workspace-ui.js +10 -1
  190. package/dist/worktree.js +23 -1
  191. package/package.json +2 -1
package/dist/operate.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { runCardCommand } from "./card-cli.js";
1
2
  import { mintSecret } from "./secret-mint.js";
2
3
  import { ACP_AGENTS, ACP_PROVIDER_IDS, isAcpProvider, isKeyedProvider } from "./acp-agents.js";
3
4
  import { inspectAcpAgent } from "./acp-inspect.js";
@@ -242,12 +243,15 @@ import { createScheduledFlow, describeSchedule, parseSchedule } from "./flow-sch
242
243
  import { fileTaskProposal, validateTaskText } from "./proposal.js";
243
244
  import { TEMPLATES, templateByName } from "./templates.js";
244
245
  import { isDirectChatProvider, isSubscriptionChatProvider, priceOf, PRICED_MODELS } from "./converse.js";
246
+ import { clearFallbackChain, dispatchEntryOf, entryMovesOnWords, entryWords, fallbackSettingOf, parseEntry, reconcileWalks, setFallbackChain, setPaidGrant } from "./fallback.js";
245
247
  import { resolvePhaseAgent, resolveScopeProfile, resolveRouteCandidates, routeOfTask, INSTALLATION_SCOPE } from "./agentconfig.js";
246
248
  import { applyModelChoices, stateDigest } from "./model-choices.js";
247
249
  import { isTaskSize, legOf, projectRoute, routeDigestOf, routeWords, TASK_SIZES, PHASES as ROUTE_PHASES } from "./phase-routing.js";
248
250
  import { observeProviderReadiness, reportProviderReadinessAuthed } from "./runner.js";
249
251
  import { retryVanishedWhenStopped } from "./vanished-retry.js";
250
252
  import { requestMissingRechecks } from "./check-only.js";
253
+ import { checkRepairOfRun, resumeParkedRepairs, startCheckRepair } from "./check-repair.js";
254
+ import { capturesCommand, evidenceCommand, failingFirstCommand, findingCommand, handoverCommand } from "./follow-through-cli.js";
251
255
  import { recordBuilderWish } from "./builder-restart.js";
252
256
  import { expireFastStarts, fastRefusalWords, fastStartBuild, keepGrownChange, prepareFastStart } from "./fast-start.js";
253
257
  import { fastTermsLines } from "./fast-terms.js";
@@ -321,6 +325,8 @@ export const OPERATE_HELP = `toolroll — operating the queue
321
325
  toolroll project activity --json read the current project supervision report
322
326
  toolroll project rules --repo <p> a project's approval rules; an instance operator changes them with
323
327
  --not-requester on|off, --protect none|project, --protect-paths "a/**,b/**"
328
+ toolroll project fallback --repo <p> [--agents provider:model:plan|api-key,…] [--clear] [--allow-paid|--no-paid]
329
+ Which agents take over when one runs out of its plan. New tasks seal the chain; a paid agent also needs --allow-paid.
324
330
  toolroll project network --repo <p> [--allow h,…] [--remove h,…] [--open|--allowlist] [--local any|none]
325
331
  what the project's builds may reach on the network; an approver changes it
326
332
  (new scopes seal it; approved ones keep theirs)
@@ -717,7 +723,7 @@ export const TASK_ACTIONS = [
717
723
  "add", "new", "keep", "list", "show", "wait", "state", "block", "unblock", "next", "steer", "pause", "continue", "assign",
718
724
  "reopen", "scope", "approve", "hold", "unhold", "require", "requeue", "regate", "plan",
719
725
  "review", "accept", "repair", "route", "stop", "resume", "complete", "revise", "merge", "ask",
720
- "checks", "add-tests", "archive", "restore", "tidy", "ship", "ready",
726
+ "checks", "add-tests", "archive", "restore", "tidy", "ship", "ready", "handover", "before-after", "failing-first",
721
727
  ];
722
728
  export const PUBLISH_ACTIONS = ["setup", "grant", "revoke", "status", "unblock", "rearm", "merge", "refire"];
723
729
  export const CONFIG_ACTIONS = ["show", "set", "clear"];
@@ -732,8 +738,8 @@ export const KEYS_ACTIONS = ["status", "set", "clear", "verify", "auth"];
732
738
  /** Flags whose value is a secret someone types or pastes: taken as the next argument even when it starts with "--". */
733
739
  export const SECRET_VALUE_FLAGS = new Set(["token", "password"]);
734
740
  export const OPERATE_VALUE_FLAGS = new Set([
735
- "key", "db", "runner", "ttl", "state", "on", "reason", "until", "id", "backend", "build", "from",
736
- "allow", "selector", "paths", "credentials", "repo", "token", "capacity",
741
+ "key", "db", "runner", "ttl", "state", "flow", "on", "reason", "until", "id", "backend", "build", "from",
742
+ "allow", "selector", "paths", "credentials", "repo", "token", "capacity", "agents",
737
743
  "goal", "not", "touches", "acceptance", "candidate", "by", "digest", "as", "branch", "pool", "base", "model", "turns",
738
744
  "max", "cap", "probe", "kind", "expires", "cmd", "since", "repair-model",
739
745
  "choose", "choice", "claim", "revision", "note", "max-open-decisions", "name", "days", "publication", "auto-approve", "review-auto", "port", "host", "allow-host",
@@ -756,7 +762,7 @@ export const OPERATE_VALUE_FLAGS = new Set([
756
762
  "command", "timeout-seconds", "setup-digest", "stop-grace", "title", "name", "every", "lines",
757
763
  "label", "reviewers", "limit", "role", "key-file", "weekly-usd", "daily-turns", "per-hour", "token-file", "budget-usd", "build-usd", "sync-max-age", "merge-method",
758
764
  "phase", "tier", "clear-phase", "size", "risky",
759
- "run", "containment", "agent",
765
+ "run", "containment", "agent", "path", "line", "action", "from-review", "from-check", "task",
760
766
  // onboard: the starter flows to switch on.
761
767
  "starter",
762
768
  // flows: templates, steps, scripts and cards.
@@ -799,6 +805,8 @@ export const OPERATE_BOOLEAN_FLAGS = new Set([
799
805
  "html", "csv", "alerts-only",
800
806
  // project network: what its builds may reach (--open | --allowlist).
801
807
  "open", "allowlist",
808
+ // project fallback: whether paid fallback agents may spend.
809
+ "allow-paid", "no-paid",
802
810
  // v105: the full export.
803
811
  "zip",
804
812
  // Settings → Integrations: the last checks, without checking again.
@@ -1314,6 +1322,8 @@ async function dispatch(command, positional, flags, context) {
1314
1322
  return projectConcurrencyCommand(positional, flags, context);
1315
1323
  if (positional[0] === "network")
1316
1324
  return projectNetworkCommand(positional, flags, context);
1325
+ if (positional[0] === "fallback")
1326
+ return projectFallbackCommand(positional, flags, context);
1317
1327
  if (positional[0] === "writes")
1318
1328
  return projectWritesCommand(positional, flags, context);
1319
1329
  if (positional[0] === "check-access")
@@ -1369,8 +1379,14 @@ async function dispatch(command, positional, flags, context) {
1369
1379
  const who = currentLocalOwner() !== null ? localReader(context) : verified !== null && verified.ok ? verified.who : null;
1370
1380
  return runMemoryCommand(positional, flags, { store: context.store, write: context.write, json: context.json, now: context.clock(), actor: who?.name ?? null, repos: who?.repos ?? [], configDir: dirname(context.databaseFile), ...(context.evidenceRoot === undefined ? {} : { evidenceRoot: context.evidenceRoot }) });
1371
1381
  }
1382
+ case "card":
1383
+ return cardCommand(positional, flags, context);
1372
1384
  case "flaky":
1373
1385
  return flakyCommand(positional, flags, context);
1386
+ case "finding":
1387
+ return findingCommand(positional, flags, followThroughContext(context, flags));
1388
+ case "captures":
1389
+ return capturesCommand(positional, followThroughContext(context, flags));
1374
1390
  case "claim":
1375
1391
  return claimCommand(positional, flags, context);
1376
1392
  case "heartbeat":
@@ -2577,6 +2593,8 @@ async function tickCommand(flags, context) {
2577
2593
  const ready = store.listReady(clock(), runner);
2578
2594
  const considered = ready.length;
2579
2595
  const dispatched = [];
2596
+ // Every walk settled before anything dispatches: a crash anywhere leaves a state this pass closes the same way.
2597
+ reconcileWalks(store, clock());
2580
2598
  // The stale-scan (dispatch v3, finding 20): mirrors the courtesy filter
2581
2599
  // kept out of ready are REPORTED here, typed, with a paged episode —
2582
2600
  // an undispatakable tracker item is a 9am fact, not a silent absence.
@@ -2669,7 +2687,15 @@ async function tickCommand(flags, context) {
2669
2687
  }
2670
2688
  }
2671
2689
  const planLeg = route === null ? null : legOf(route.route, "plan");
2672
- const sealedBuildLeg = route !== null && route.source === "approved" ? legOf(route.route, "build") : null;
2690
+ // A task whose agent ran out runs as the next approved entry: every gate below is keyed to that entry (fallback.ts).
2691
+ const fallback = route !== null && route.source === "approved" && !wantsPlan && !wantsScout ? dispatchEntryOf(store, ref.id, id, clock()) : null;
2692
+ if (fallback !== null && "problem" in fallback) {
2693
+ dispatched.push({ id, outcome: "skipped", reason: "agent-config", detail: `${id}: ${fallback.problem}` });
2694
+ continue;
2695
+ }
2696
+ const sealedBuildLeg = route !== null && route.source === "approved"
2697
+ ? fallback !== null ? { provider: fallback.entry.profile.provider, model: fallback.entry.profile.model, chosen: "fallback" } : legOf(route.route, "build")
2698
+ : null;
2673
2699
  const planFlagged = planProvider !== undefined || planModel !== undefined || providerFlag !== undefined || model !== undefined;
2674
2700
  if (wantsPlan && planLeg !== null && planFlagged) {
2675
2701
  // Pass flags cannot contradict the task's plan leg: a flag that names
@@ -2804,7 +2830,7 @@ async function tickCommand(flags, context) {
2804
2830
  // existing approval refusals own that road, and attestation must
2805
2831
  // never mask them. The gateway re-checks before spawn; this skip only
2806
2832
  // keeps the normal road cheap.
2807
- const skipProviders = wantsPlan
2833
+ const skipProviders = wantsPlan || fallback !== null
2808
2834
  ? [spec.provider]
2809
2835
  : [store.getScope(id)?.approvedProfile?.provider].filter((one) => one !== null && one !== undefined);
2810
2836
  let unattestedLane = null;
@@ -2841,7 +2867,7 @@ async function tickCommand(flags, context) {
2841
2867
  }
2842
2868
  // Sprint 8: the organisation policy before the claim (build() looks again before spawn, and lowers).
2843
2869
  {
2844
- const sealed = wantsPlan || wantsScout ? null : store.getScope(id)?.approvedProfile ?? null;
2870
+ const sealed = wantsPlan || wantsScout ? null : fallback?.entry.profile ?? store.getScope(id)?.approvedProfile ?? null;
2845
2871
  const refused = policyHold([sealed !== null ? { profile: sealed }
2846
2872
  : { provider: spec.provider, model: spec.model, ...(wantsPlan ? { session: "planning" } : wantsScout ? { session: "scouting" } : {}) }]);
2847
2873
  if (refused !== null) {
@@ -2855,7 +2881,8 @@ async function tickCommand(flags, context) {
2855
2881
  // Each provider as it bills now.
2856
2882
  // An ACP agent bills as its declaration for the exact model it would run on this runner.
2857
2883
  const sealedModel = store.getScope(id)?.approvedProfile?.model ?? null;
2858
- const budgeted = budgetHold(ref.id, [...new Set([spec.provider, ...skipProviders])].map(provider => isAcpProvider(provider) ? store.agentFor(provider, provider === spec.provider ? spec.model ?? sealedModel : sealedModel, runner) : store.agentsFor([provider])[0]));
2884
+ // A paid fallback entry is a dollar spend whatever its provider's current setting says.
2885
+ const budgeted = budgetHold(ref.id, fallback?.paid === true ? [{ provider: spec.provider, billing: "api-key" }] : [...new Set([spec.provider, ...skipProviders])].map(provider => isAcpProvider(provider) ? store.agentFor(provider, provider === spec.provider ? spec.model ?? sealedModel : sealedModel, runner) : store.agentsFor([provider])[0]));
2859
2886
  if (budgeted.over !== null) {
2860
2887
  dispatched.push({ id, outcome: "skipped", reason: "budget", detail: budgetWords(budgeted) });
2861
2888
  continue;
@@ -2879,6 +2906,7 @@ async function tickCommand(flags, context) {
2879
2906
  repo,
2880
2907
  provider: spec.provider,
2881
2908
  ...(spec.model === null ? {} : { model: spec.model }),
2909
+ ...(fallback === null ? {} : { authMode: fallback.entry.authMode }),
2882
2910
  ...(text(flags, "incarnation") === undefined ? {} : { incarnation: text(flags, "incarnation") }),
2883
2911
  ...(text(flags, "max-open-decisions") === undefined
2884
2912
  ? {}
@@ -3476,12 +3504,16 @@ async function tickCommand(flags, context) {
3476
3504
  reason: disposition.sealed
3477
3505
  ? disposition.disposition === "auth-expired"
3478
3506
  ? "auth-expired — requeued, sign-in needed"
3479
- : `${disposition.failureClass}${disposition.disposition === "backoff" ? ` — retry ${disposition.strikes}/3` : disposition.disposition === "stalled" ? " — stalled" : ""}`
3507
+ : disposition.disposition === "fallback"
3508
+ ? `out of plan — moved to the next approved agent`
3509
+ : `${disposition.failureClass}${disposition.disposition === "backoff" ? ` — retry ${disposition.strikes}/3` : disposition.disposition === "stalled" ? " — stalled" : ""}`
3480
3510
  : "fenced",
3481
3511
  ...(result.ok ? {} : { detail: result.message.slice(0, 200) }),
3482
3512
  worktree: leased.worktree.path,
3483
3513
  });
3484
- broke++;
3514
+ // Moving on isn't this pass breaking: the task is queued for its next agent.
3515
+ if (!(disposition.sealed && disposition.disposition === "fallback"))
3516
+ broke++;
3485
3517
  break;
3486
3518
  case "recorded":
3487
3519
  // The standalone-only arm; unreachable under the tick policy.
@@ -5004,7 +5036,7 @@ async function modeCommand(positional, flags, context) {
5004
5036
  if (acting === null)
5005
5037
  return fail(write, json, "mode", "usage", "signing or revoking a mode takes `--as <you> --token <t>`", EXIT.usage);
5006
5038
  // Signing a mode that lets a chat app approve is a credential act; any other mode signing or revoking is ordinary.
5007
- const authed = (flag(flags, "chat-approve") ? credentialApprover(store, acting.name, acting.token) : authenticateApprover(store, acting.name, acting.token));
5039
+ const authed = (flag(flags, "chat-approve") || flag(flags, "repair-auto") ? credentialApprover(store, acting.name, acting.token) : authenticateApprover(store, acting.name, acting.token));
5008
5040
  if (!authed.ok)
5009
5041
  return fail(write, json, "mode", authed.reason, "that is not an active approver, or the token does not match", EXIT.refused);
5010
5042
  if (action === "revoke") {
@@ -5032,8 +5064,18 @@ async function modeCommand(positional, flags, context) {
5032
5064
  terms.autoApproveFiling = true;
5033
5065
  if (flag(flags, "plan-auto"))
5034
5066
  terms.planAuto = true;
5035
- if (["review-auto", "review-retry-auto", "repair-auto", "repair-max-attempts"].some(name => flags.has(name)))
5036
- return fail(write, json, "mode set", "invalid", "Separate model review and automatic correction loops have been removed; finished work goes to the lead or user.", EXIT.refused);
5067
+ if (["review-auto", "review-retry-auto"].some(name => flags.has(name)))
5068
+ return fail(write, json, "mode set", "invalid", "Separate model review has been removed; finished work goes to the lead or user.", EXIT.refused);
5069
+ // Repairing failed checks by themselves: never a preset default, only these flags, with a limit you sign.
5070
+ if (flags.has("repair-max-attempts") && !flag(flags, "repair-auto"))
5071
+ return fail(write, json, "mode set", "invalid", "--repair-max-attempts needs --repair-auto", EXIT.refused);
5072
+ if (flag(flags, "repair-auto")) {
5073
+ const limit = Number(text(flags, "repair-max-attempts") ?? 2);
5074
+ if (!Number.isInteger(limit) || limit < 1 || limit > 3)
5075
+ return fail(write, json, "mode set", "invalid", "--repair-max-attempts is 1, 2 or 3", EXIT.refused);
5076
+ terms.repairAuto = true;
5077
+ terms.repairMaxAttempts = limit;
5078
+ }
5037
5079
  if (terms.planAuto && !terms.autoApproveFiling)
5038
5080
  return fail(write, json, "mode set", "invalid", "--plan-auto requires automatic filing approval", EXIT.refused);
5039
5081
  // Approving plans and merges from the signer's paired chat: never a preset default, only this flag.
@@ -6519,6 +6561,8 @@ async function runWatchLoop(args) {
6519
6561
  void resumeHeldReleases(store, dirname(context.databaseFile), context.clock());
6520
6562
  // A result whose verdict went missing asks for its one automatic recheck (check-only.ts).
6521
6563
  requestMissingRechecks(store, context.evidenceRoot, context.clock());
6564
+ // A failed check that was parked takes its next step once its task is free again (check-repair.ts).
6565
+ resumeParkedRepairs(store, context.evidenceRoot, context.clock());
6522
6566
  const ran = await runWaitingChecks(store, context.evidenceRoot, { now: context.clock, shouldStop: stopping });
6523
6567
  if (ran > 0)
6524
6568
  progress(`watch: ran ${ran} follow-up check(s)`);
@@ -8891,6 +8935,12 @@ function taskAction(action, rest, flags, context) {
8891
8935
  return requeueTask(rest, flags, context);
8892
8936
  case "regate":
8893
8937
  return regateTaskCommand(rest, flags, context);
8938
+ case "handover":
8939
+ return handoverCommand(rest, followThroughContext(context, flags));
8940
+ case "before-after":
8941
+ return evidenceCommand(rest, flags, followThroughContext(context, flags));
8942
+ case "failing-first":
8943
+ return failingFirstCommand(rest, followThroughContext(context, flags));
8894
8944
  case "plan":
8895
8945
  return planTaskCommand(rest, flags, context);
8896
8946
  case "review":
@@ -9585,9 +9635,18 @@ async function repairTaskCommand(positional, flags, context) {
9585
9635
  const acting = await askCredentials(flags, context);
9586
9636
  if (acting === null)
9587
9637
  return fail(write, json, "task repair", "usage", "Evidence collection requires --as and --token.", EXIT.usage);
9588
- const authenticated = authenticateApprover(store, acting.name, acting.token);
9638
+ // A failed check names its project: the password proves the person, and their access to the run they named.
9639
+ const failed = checkRepairOfRun(store, runId);
9640
+ const authenticated = authenticateApprover(store, acting.name, acting.token, failed?.repo);
9589
9641
  if (!authenticated.ok)
9590
9642
  return fail(write, json, "task repair", authenticated.reason, describeApproveFailure(authenticated.reason, String(runId)), EXIT.refused);
9643
+ // A failed check waiting for a person: this draft is what they asked for, and its approval below is the normal one.
9644
+ if (failed !== null) {
9645
+ const started = startCheckRepair(store, context.evidenceRoot, runId, clock());
9646
+ if (!started.ok)
9647
+ return fail(write, json, "task repair", "refused", started.message, EXIT.refused);
9648
+ chain = store.repairChainFor(runId);
9649
+ }
9591
9650
  const run = store.getRun(runId), proof = store.proofVerdictFor(runId);
9592
9651
  const repo = run && store.refById(run.taskRef)?.repo;
9593
9652
  if (repo && repo === repoFrom(flags) && proof?.matrix.some(row => row.assessment && row.review?.judgement === "cannot-tell") &&
@@ -13931,8 +13990,100 @@ export async function refreshLearning(store, now, reads) {
13931
13990
  catch { /* advisory: the next pass tries again */ }
13932
13991
  }
13933
13992
  }
13993
+ /** `toolroll card <source>`: reads only, for the remembered local login (or --as/--token) and its projects. */
13994
+ async function cardCommand(positional, flags, context) {
13995
+ const acting = await askCredentials(flags, context);
13996
+ const verified = acting === null || currentLocalOwner() !== null ? null : verifyApproverByPassword(context.store, acting.name, acting.token, context.store.knownRepos().filter(repo => context.store.accountCanAccess(acting.name, repo)));
13997
+ const who = currentLocalOwner() !== null ? localReader(context) : verified !== null && verified.ok ? verified.who : null;
13998
+ return runCardCommand(positional, flags, { store: context.store, write: context.write, json: context.json, now: context.clock(), actor: who?.name ?? null, repos: who?.repos ?? [], evidenceRoot: context.evidenceRoot,
13999
+ ok: (result, lines) => succeed(context.write, context.json, "card", result, lines),
14000
+ no: (reason, message) => fail(context.write, context.json, "card", reason, message, reason === "usage" ? EXIT.usage : EXIT.refused) });
14001
+ }
13934
14002
  /** The laptop's local owner as a reader: its name and its projects. Never a proof: every write proves its own act. */
13935
14003
  function localReader(context) {
13936
14004
  const local = currentLocalOwner();
13937
14005
  return local === null ? null : { name: local.account, repos: context.store.knownRepos().filter(repo => context.store.accountCanAccess(local.account, repo)) };
13938
14006
  }
14007
+ /** What the follow-through commands need from this file: its envelope, and a verified person (follow-through-cli.ts). */
14008
+ function followThroughContext(context, flags) {
14009
+ const { store, write, json, clock, evidenceRoot } = context;
14010
+ return {
14011
+ store, evidenceRoot, now: clock(),
14012
+ ok: (command, result, lines) => succeed(write, json, command, result, lines),
14013
+ no: (command, reason, message) => fail(write, json, command, reason, message, reason === "usage" ? EXIT.usage : EXIT.refused),
14014
+ prove: async (command, repo) => {
14015
+ const acting = await askCredentials(flags, context);
14016
+ if (acting === null) {
14017
+ fail(write, json, command, "usage", "this takes `--as <you> --token <t>`", EXIT.usage);
14018
+ return null;
14019
+ }
14020
+ const authed = authenticateApprover(store, acting.name, acting.token, repo);
14021
+ if (!authed.ok) {
14022
+ fail(write, json, command, "not-an-approver", "that is not an approver, or the token does not match", EXIT.refused);
14023
+ return null;
14024
+ }
14025
+ return acting.name;
14026
+ },
14027
+ };
14028
+ }
14029
+ /**
14030
+ * `project fallback --repo <p> [--agents a,b,c] [--clear] [--allow-paid|--no-paid]`: which agents take over when one runs
14031
+ * out of its plan (fallback.ts). New tasks seal the chain; approved ones keep theirs. Changing it is an approver's act
14032
+ * with a password typed now, in the ledger. A lead's token never gets past the store's own check.
14033
+ */
14034
+ async function projectFallbackCommand(positional, flags, context) {
14035
+ const command = "project fallback";
14036
+ const allowed = new Set(["repo", "agents", "clear", "allow-paid", "no-paid", "as", "token", "token-file", "token-env", "db", "json"]);
14037
+ for (const name of flags.keys())
14038
+ if (!allowed.has(name))
14039
+ return fail(context.write, context.json, command, "usage", `--${name} is not a project fallback option.`, EXIT.usage);
14040
+ const repoFlag = text(flags, "repo");
14041
+ const agents = text(flags, "agents");
14042
+ const clearing = flags.has("clear"), allowing = flags.has("allow-paid"), withdrawing = flags.has("no-paid");
14043
+ if (positional.length > 1 || repoFlag === undefined || (clearing && agents !== undefined) || (allowing && withdrawing))
14044
+ return fail(context.write, context.json, command, "usage", "Use project fallback --repo <project path> [--agents provider:model:plan|api-key,…] [--clear] [--allow-paid|--no-paid].", EXIT.usage);
14045
+ const repo = await knownProject(context, repoFlag, { listed: true, canonical: true });
14046
+ if (repo === undefined)
14047
+ return fail(context.write, context.json, command, "not-found", "That isn't a project Toolroll knows.", EXIT.refused);
14048
+ const words = () => {
14049
+ const setting = fallbackSettingOf(context.store, repo);
14050
+ if (setting === null)
14051
+ return [`${repo}: no fallback agents. A task stops when its agent runs out of its plan.`];
14052
+ return [`${repo}: when an agent runs out of its plan, the work moves on to:`,
14053
+ ...setting.entries.map((one, at) => ` ${at + 1}. ${entryWords(one)}. ${entryMovesOnWords(one)}.`),
14054
+ setting.paidGrant === null ? "Paid fallback: not allowed." : `Paid fallback: allowed by ${setting.paidGrant.by}.`];
14055
+ };
14056
+ const shown = () => { const setting = fallbackSettingOf(context.store, repo); return { repo, agents: setting?.entries ?? [], paidAllowed: setting?.paidGrant != null }; };
14057
+ if (agents === undefined && !clearing && !allowing && !withdrawing)
14058
+ return succeed(context.write, context.json, command, shown(), words);
14059
+ const parsed = agents === undefined ? [] : agents.split(",").map(one => parseEntry(one.trim()));
14060
+ const bad = parsed.find((one) => typeof one === "string");
14061
+ if (bad !== undefined)
14062
+ return fail(context.write, context.json, command, "usage", bad, EXIT.usage);
14063
+ const acting = await askCredentials(flags, context);
14064
+ const verified = acting === null ? null : credentialApprover(context.store, acting.name, acting.token, repo);
14065
+ if (acting === null || !verified?.ok || !context.store.accountCanAccess(acting.name, repo))
14066
+ return fail(context.write, context.json, command, "refused", "An approver for this project changes its fallback agents, with their password: pass --as and --token.", EXIT.refused);
14067
+ const now = context.clock();
14068
+ const done = context.store.transact(() => {
14069
+ if (clearing) {
14070
+ const one = clearFallbackChain(context.store, repo, acting.name, now);
14071
+ if (!one.ok)
14072
+ return one;
14073
+ }
14074
+ if (agents !== undefined) {
14075
+ const one = setFallbackChain(context.store, repo, parsed, acting.name, now);
14076
+ if (!one.ok)
14077
+ return one;
14078
+ }
14079
+ if (allowing || withdrawing) {
14080
+ const one = setPaidGrant(context.store, repo, allowing, acting.name, now);
14081
+ if (!one.ok)
14082
+ return one;
14083
+ }
14084
+ return { ok: true };
14085
+ });
14086
+ if (!done.ok)
14087
+ return fail(context.write, context.json, command, "refused", done.problem, EXIT.refused);
14088
+ return succeed(context.write, context.json, command, shown(), () => [...words(), "Tasks scoped from now on use this; tasks already approved keep the chain they were approved with."]);
14089
+ }
@@ -0,0 +1,14 @@
1
+ import type { Billing } from "./spend.js";
2
+ /** How Codex says it's signed in (`codex login status`): a ChatGPT sign-in is its plan; a key or Bedrock is a key;
3
+ * null when it says neither (not signed in, or it didn't answer). */
4
+ export declare function codexLoginBilling(binary: string, omitEnv: readonly string[], env?: Record<string, string | undefined>): Promise<Billing | null>;
5
+ /** Providers whose CLI can say, live and machine-readably, that the credential a run will use is a plan. */
6
+ export declare const PLAN_LOGIN_PROVABLE: ReadonlySet<string>;
7
+ /**
8
+ * How Claude says it is signed in (`claude auth status --json`), read with the run's own environment so it reads the
9
+ * credential the child will use. Strict: a plan only for a first-party claude.ai login with a named subscription; any
10
+ * other shape, a key, a cloud provider or no answer is not a plan.
11
+ */
12
+ export declare function claudeLoginBilling(binary: string, omitEnv: readonly string[], env: Record<string, string | undefined>): Promise<Billing | null>;
13
+ /** The live plan proof for a provider, or null where none exists (the caller refuses). */
14
+ export declare function livePlanLogin(provider: string, binary: string, omitEnv: readonly string[], env: Record<string, string | undefined>): Promise<Billing | null>;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * How a CLI says it is signed in, read live, right before a run: the gateway's billing evidence, and the proof a
3
+ * plan-pinned fallback agent needs before it runs (fallback.ts). Only providers listed in PLAN_LOGIN_PROVABLE can be a
4
+ * plan fallback: for any other there is no live proof, so filing and the chain setting refuse it.
5
+ */
6
+ import { run as runCommand } from "./exec.js";
7
+ /** How Codex says it's signed in (`codex login status`): a ChatGPT sign-in is its plan; a key or Bedrock is a key;
8
+ * null when it says neither (not signed in, or it didn't answer). */
9
+ export async function codexLoginBilling(binary, omitEnv, env) {
10
+ try {
11
+ const status = await runCommand(binary, ["login", "status"], { timeoutMs: 15_000, omitEnv, processGroup: true,
12
+ ...(env === undefined ? {} : { env: Object.fromEntries(Object.entries(env).filter((one) => one[1] !== undefined)) }) });
13
+ const said = `${status.stdout}\n${status.stderr}`;
14
+ // Strict: the status line itself, at the start of a line. A line naming both is never read as a plan.
15
+ if (/^Logged in using ChatGPT\s*$/m.test(said) && !/api key|bedrock/i.test(said))
16
+ return "subscription";
17
+ // Anything naming a key or Bedrock is read as a key (never a plan): that only ever narrows.
18
+ return /api key|bedrock/i.test(said) ? "api-key" : null;
19
+ }
20
+ catch {
21
+ return null;
22
+ }
23
+ }
24
+ /** Providers whose CLI can say, live and machine-readably, that the credential a run will use is a plan. */
25
+ export const PLAN_LOGIN_PROVABLE = new Set(["claude", "codex"]);
26
+ /**
27
+ * How Claude says it is signed in (`claude auth status --json`), read with the run's own environment so it reads the
28
+ * credential the child will use. Strict: a plan only for a first-party claude.ai login with a named subscription; any
29
+ * other shape, a key, a cloud provider or no answer is not a plan.
30
+ */
31
+ export async function claudeLoginBilling(binary, omitEnv, env) {
32
+ try {
33
+ const status = await runCommand(binary, ["auth", "status", "--json"], { timeoutMs: 15_000, omitEnv, processGroup: true,
34
+ env: Object.fromEntries(Object.entries(env).filter((one) => one[1] !== undefined)) });
35
+ if (status.code !== 0)
36
+ return null;
37
+ const read = JSON.parse(status.stdout);
38
+ if (read === null || typeof read !== "object" || Array.isArray(read))
39
+ return null;
40
+ const said = read;
41
+ if (said["loggedIn"] !== true)
42
+ return null;
43
+ if (said["authMethod"] === "claude.ai" && said["apiProvider"] === "firstParty" && typeof said["subscriptionType"] === "string" && /^[a-z0-9_-]{1,32}$/.test(said["subscriptionType"]))
44
+ return "subscription";
45
+ return "api-key";
46
+ }
47
+ catch {
48
+ return null;
49
+ }
50
+ }
51
+ /** The live plan proof for a provider, or null where none exists (the caller refuses). */
52
+ export async function livePlanLogin(provider, binary, omitEnv, env) {
53
+ if (provider === "codex")
54
+ return codexLoginBilling(binary, omitEnv, env);
55
+ if (provider === "claude")
56
+ return claudeLoginBilling(binary, omitEnv, env);
57
+ return null;
58
+ }
package/dist/planner.d.ts CHANGED
@@ -2,7 +2,7 @@ import type { ProviderId } from "./contracts/provider.js";
2
2
  import type { Store } from "./store.js";
3
3
  import { type ParsedDecision, type Problem } from "./decision.js";
4
4
  import { type ParsedPlan, type PlanProblem } from "./plan.js";
5
- import type { Runner } from "./builder.js";
5
+ import { type Runner } from "./builder.js";
6
6
  import { type PlannerSource } from "./planner-source.js";
7
7
  export type PlanRequest = {
8
8
  /** v105: what's left of a monthly budget this API-key work counts toward (the CLI's own cap), when one does. */
package/dist/planner.js CHANGED
@@ -32,6 +32,7 @@ import { TEXT_LIMITS } from "./text-limits.js";
32
32
  import { invokeAgent } from "./invoke.js";
33
33
  import { TOKEN_ENVS as TELEGRAM_TOKEN_ENVS } from "./telegram.js";
34
34
  import { evidenceRoot, mailboxName, planFileName, quarantineMailboxes, readMailbox, storeEvidence, writeEvidenceFile, } from "./evidence.js";
35
+ import { gitFailureWords } from "./builder.js";
35
36
  import { MARKER as LEASE_MARKER } from "./worktree.js";
36
37
  import { liveLogContext } from "./live-detail.js";
37
38
  import { openLiveLog } from "./live.js";
@@ -537,7 +538,7 @@ export async function plan(store, request) {
537
538
  }
538
539
  const revision = await git(GIT, ["--no-optional-locks", "rev-parse", "HEAD"], { cwd: worktree });
539
540
  if (revision.code !== 0) {
540
- return { ok: false, kind: "failure", reason: "git", message: `could not read the base revision in ${worktree}` };
541
+ return { ok: false, kind: "failure", reason: "git", message: `could not read the base revision in ${worktree} (${gitFailureWords(revision)})` };
541
542
  }
542
543
  const baseRevision = revision.stdout.trim();
543
544
  store.stampRun(request.runId, { baseRevision });
@@ -1,3 +1,10 @@
1
+ /**
2
+ * Settings → Project: what Toolroll holds for a project, and deleting
3
+ * it. An instance operator deletes in two steps: type the project's name,
4
+ * then read exactly what goes and confirm with their password (or a fresh
5
+ * sign-in with the identity provider). The repository itself stays.
6
+ */
7
+ import { type FallbackSetting } from "./fallback.js";
1
8
  import type { ProjectSandbox } from "./project-sandbox.js";
2
9
  import type { ProjectNetwork } from "./egress.js";
3
10
  import type { WritesMode } from "./writes-mode.js";
@@ -49,6 +56,11 @@ export type ProjectSettingsView = {
49
56
  setting: ProjectNetwork;
50
57
  canChange: boolean;
51
58
  };
59
+ /** Who takes over when an agent runs out of its plan (fallback.ts), and whether this person may change it. */
60
+ fallback?: {
61
+ setting: FallbackSetting | null;
62
+ canChange: boolean;
63
+ };
52
64
  /** Where its builds may write (writes-mode.ts), and whether this person may change it. */
53
65
  writes?: {
54
66
  setting: WritesMode;
@@ -4,6 +4,7 @@
4
4
  * then read exactly what goes and confirm with their password (or a fresh
5
5
  * sign-in with the identity provider). The repository itself stays.
6
6
  */
7
+ import { entryMovesOnWords, entryWords } from "./fallback.js";
7
8
  import { sandboxRuntimeWords } from "./sandbox-runtime.js";
8
9
  import { CHECK_ACCESS_WARNINGS, signInHostsMissing } from "./check-access.js";
9
10
  import { html, joinHtml, postForm } from "./html.js";
@@ -132,6 +133,22 @@ function networkHtml(view) {
132
133
  note("Tasks scoped from now on use this; tasks already approved keep the network they were approved with."),
133
134
  ]) }))]) });
134
135
  }
136
+ function fallbackHtml(view) {
137
+ const fallback = view.fallback;
138
+ if (fallback === undefined)
139
+ return "";
140
+ const { setting } = fallback;
141
+ const lead = setting === null ? html `When an agent runs out of its plan, the task stops.` : html `When an agent runs out of its plan, the work moves to the next agent here.`;
142
+ const lines = setting === null ? "" : joinHtml(setting.entries.map(one => note(`${entryWords(one)}. ${entryMovesOnWords(one)}.`)));
143
+ return settingsSection({ id: "fallback", title: "Fallback agents", state: setting === null ? "" : ` · ${setting.entries.length}`, lead,
144
+ body: joinHtml([lines, !fallback.canChange ? APPROVER_CHANGES : disclose("Change", kitSectionForm({ proof: "project-fallback", action: "/settings/project/fallback", id: "fallback-form", gate: true, hidden: { repo: view.repo },
145
+ body: joinHtml([
146
+ formRow("Agents, in order", "One per line: provider:model:plan or provider:model:api-key. Empty turns this off.", textarea({ id: "fallback-agents", name: "agents", value: setting === null ? "" : setting.entries.map(one => `${one.provider}:${one.model}:${one.authMode === "subscription" ? "plan" : "api-key"}`).join("\n"), rows: 3,
147
+ attrs: { autocomplete: "off", autocapitalize: "off", spellcheck: "false", placeholder: "codex:gpt-5.6:plan", "aria-label": "Fallback agents, in order" } }), "top"),
148
+ formRow("Paid fallback", "An agent on an API key spends money. It runs only while this is on.", switchInput({ id: "fallback-paid", name: "paid", label: "Allowed", on: setting?.paidGrant != null, attrs: { value: "on", "aria-label": "Allow paid fallback agents to spend" } }), "inline"),
149
+ note("Tasks scoped from now on use this; tasks already approved keep the chain they were approved with."),
150
+ ]) }))]) });
151
+ }
135
152
  /** Live view: hidden by default; showing commands takes the password, hiding them is one step. */
136
153
  function liveViewHtml(view) {
137
154
  const live = view.liveView;
@@ -205,7 +222,7 @@ export function projectSettingsHtml(view, said) {
205
222
  formRow("Recorded remote", view.remoteUrl ? null : "None. Landed work waits for your review.", view.remoteUrl ? mono(view.remoteUrl) : html ``, view.remoteUrl ? "wide" : "inline"),
206
223
  disclose("Change remote", joinHtml([note("An owner or instance operator can run:"), codeLine("toolroll repos add <project> --remote <url>")])),
207
224
  ]) });
208
- const settings = joinHtml([buildsHtml(view), accessHtml(view), networkHtml(view), writesHtml(view), sandboxHtml(view), checkAccessHtml(view), liveViewHtml(view), codeMapHtml(view)]);
225
+ const settings = joinHtml([buildsHtml(view), accessHtml(view), networkHtml(view), fallbackHtml(view), writesHtml(view), sandboxHtml(view), checkAccessHtml(view), liveViewHtml(view), codeMapHtml(view)]);
209
226
  if (!view.canDelete)
210
227
  return joinHtml([answer, project, settings, note("An instance operator can remove or delete a project.")]);
211
228
  const deleteBody = view.running.length > 0
@@ -50,6 +50,8 @@ const SETTINGS = [
50
50
  ["intake_grant", "repo"], ["capability", "repo"], ["project_tool", "repo"],
51
51
  ["learning_policy", "repo"], ["operating_mode", "repo"], ["project_knowledge", "repo"], ["live_detail_setting", "repo"],
52
52
  ["project_decision", "repo"], ["flow_script", "repo"], ["workflow_recipe", "repo"], ["team_lead_project", "project"],
53
+ // Its fallback agents and, on the same row, its paid fallback grant (fallback.ts).
54
+ ["fallback_setting", "repo"],
53
55
  ];
54
56
  /** The project's Models rows, counted with its settings and deleted by forgetProjectModels only. */
55
57
  export const MODEL_SETTINGS = Object.freeze([["phase_config", "scope"], ["phase_tier_config", "scope"], ["phase_tier_alternate", "scope"]]);
@@ -264,6 +266,10 @@ function deleteRows(store, repo, d, now) {
264
266
  del("diff_comment", `${IN("run")} OR ${IN("reviewer_run")} OR ${IN("artifact")}`, R, R, A);
265
267
  del("review_request", `${IN("run")} OR ${IN("reviewer_run")}`, R, R);
266
268
  del("repair_chain", IN("source_run"), R);
269
+ // What happens to a result after its agent finishes (fleet-schema.ts): a project's own rows, answers first.
270
+ del("finding_answer", "finding IN (SELECT id FROM finding WHERE repo = ?)", repo);
271
+ for (const table of ["finding", "check_repair", "evidence_pair", "failing_first", "provider_capture"])
272
+ del(table, "repo = ?", repo);
267
273
  for (const table of ["incident", "proof_acceptance", "proof_verdict", "run_process", "run_route", "run_spend", "run_tool", "run_note", "run_stop", "run_checkpoint", "execution_slot"])
268
274
  del(table, IN("run"), R);
269
275
  del("code_map_delivery", IN("run"), R);
@@ -22,18 +22,18 @@ export type KnowledgeView = ReturnType<typeof knowledgeView>;
22
22
  export declare function knowledgeVersion(store: Store, repo: string, actor: string, revision: number): Knowledge;
23
23
  /** Chat reads only an admitted project. Keep the full source in the project UI. */
24
24
  export declare function conversationKnowledge(store: Store, repo: string, actor: string, reference?: string, decision?: number): {
25
- title?: never;
25
+ references?: never;
26
26
  revision: number;
27
27
  stale: boolean;
28
28
  notice: string;
29
+ title?: never;
29
30
  content?: never;
30
31
  history?: never;
31
32
  instructions?: never;
32
- references?: never;
33
33
  decisions?: never;
34
34
  waiting?: never;
35
35
  } | {
36
- title?: never;
36
+ references?: never;
37
37
  stale?: never;
38
38
  decision: {
39
39
  repo: string;
@@ -51,14 +51,15 @@ export declare function conversationKnowledge(store: Store, repo: string, actor:
51
51
  };
52
52
  afterwards?: string;
53
53
  notice: string;
54
+ title?: never;
54
55
  content?: never;
55
56
  revision?: never;
56
57
  history?: never;
57
58
  instructions?: never;
58
- references?: never;
59
59
  decisions?: never;
60
60
  waiting?: never;
61
61
  } | {
62
+ references?: never;
62
63
  stale?: never;
63
64
  revision: number;
64
65
  title: string;
@@ -66,12 +67,11 @@ export declare function conversationKnowledge(store: Store, repo: string, actor:
66
67
  notice: string;
67
68
  history?: never;
68
69
  instructions?: never;
69
- references?: never;
70
70
  decisions?: never;
71
71
  waiting?: never;
72
72
  } | {
73
- title?: never;
74
73
  stale?: never;
74
+ title?: never;
75
75
  content?: never;
76
76
  revision: number;
77
77
  history: {
@@ -109,7 +109,7 @@ export declare function testTool(spec: ToolSpec, values: Record<string, string>,
109
109
  timeoutMs?: number;
110
110
  env?: NodeJS.ProcessEnv;
111
111
  omitEnv?: readonly string[];
112
- server: ToolServer;
112
+ server: ToolServer | Promise<ToolServer>;
113
113
  }): Promise<{
114
114
  ok: true;
115
115
  tools: string[];
@@ -126,7 +126,7 @@ export declare function listProjectToolActions(spec: ToolSpec, values: Record<st
126
126
  timeoutMs?: number;
127
127
  env?: NodeJS.ProcessEnv;
128
128
  omitEnv?: readonly string[];
129
- server: ToolServer;
129
+ server: ToolServer | Promise<ToolServer>;
130
130
  }): Promise<{
131
131
  ok: true;
132
132
  actions: ToolActionInfo[];
@@ -152,7 +152,7 @@ export declare function callProjectTool(spec: ToolSpec, values: Record<string, s
152
152
  timeoutMs?: number;
153
153
  env?: NodeJS.ProcessEnv;
154
154
  omitEnv?: readonly string[];
155
- server: ToolServer;
155
+ server: ToolServer | Promise<ToolServer>;
156
156
  }): Promise<ToolCall>;
157
157
  /** The exact servers one launch gets, and those left out with why (`repo`: the project, from the run's record). */
158
158
  export type ToolLaunch = {
@@ -312,7 +312,7 @@ export type ToolServer = {
312
312
  export declare function toolServerFor(store: ToolServerStore, repo: string, options?: {
313
313
  home?: string;
314
314
  available?: () => boolean;
315
- }): ToolServer;
315
+ }): Promise<ToolServer>;
316
316
  /** Each project's `.mcp.json` as the console last read it off its thread (its background pass): what its pages and the
317
317
  * lead offer, so neither reads a project folder on the console's thread. */
318
318
  export declare const savedProjectMcp: Map<string, Record<string, unknown> | null>;