toolroll 0.9.60 → 0.9.61

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 (118) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/agent-board.d.ts +208 -0
  3. package/dist/agent-board.js +454 -0
  4. package/dist/agent-fence.d.ts +19 -0
  5. package/dist/agent-fence.js +100 -8
  6. package/dist/agent-pulse.d.ts +43 -0
  7. package/dist/agent-pulse.js +214 -0
  8. package/dist/agent-turns.d.ts +52 -0
  9. package/dist/agent-turns.js +137 -0
  10. package/dist/agents-board.d.ts +101 -0
  11. package/dist/agents-board.js +225 -0
  12. package/dist/assignment-adapters.d.ts +1 -0
  13. package/dist/assignment-status.d.ts +1 -1
  14. package/dist/assignment.d.ts +4 -0
  15. package/dist/assignment.js +8 -6
  16. package/dist/browser/workspace.css +1 -1
  17. package/dist/browser/workspace.js +29 -25
  18. package/dist/browser-crew.js +2 -0
  19. package/dist/browser-workspace.d.ts +79 -1
  20. package/dist/browser-workspace.js +1 -0
  21. package/dist/builder.d.ts +8 -0
  22. package/dist/builder.js +113 -13
  23. package/dist/chat-channel.js +9 -0
  24. package/dist/chat-core.d.ts +6 -0
  25. package/dist/chat-core.js +8 -0
  26. package/dist/chat-delivery.js +22 -0
  27. package/dist/chat-messages.js +8 -1
  28. package/dist/chat-quiet.js +1 -0
  29. package/dist/chat-voice.d.ts +2 -0
  30. package/dist/chat-voice.js +2 -0
  31. package/dist/checkout-cleanup.d.ts +3 -0
  32. package/dist/checkout-cleanup.js +11 -2
  33. package/dist/contracts/console-api.d.ts +3 -1
  34. package/dist/contracts/console-api.js +5 -1
  35. package/dist/contracts/lead-tools.d.ts +16 -2
  36. package/dist/contracts/lead-tools.js +5 -1
  37. package/dist/desktop-update.d.ts +1 -1
  38. package/dist/evidence-pack.js +1 -1
  39. package/dist/evidence.d.ts +6 -0
  40. package/dist/evidence.js +14 -3
  41. package/dist/exec.d.ts +32 -0
  42. package/dist/exec.js +229 -10
  43. package/dist/invoke.js +110 -18
  44. package/dist/landed-cleanup.d.ts +157 -0
  45. package/dist/landed-cleanup.js +707 -0
  46. package/dist/lead-cli.js +22 -19
  47. package/dist/lead-contract.js +1 -1
  48. package/dist/lead-doors.js +45 -1
  49. package/dist/lead-tools.js +40 -7
  50. package/dist/live-input.d.ts +68 -0
  51. package/dist/live-input.js +254 -0
  52. package/dist/live.d.ts +1 -1
  53. package/dist/live.js +4 -2
  54. package/dist/maintenance.d.ts +2 -0
  55. package/dist/maintenance.js +5 -2
  56. package/dist/operate-remote-arguments.js +5 -1
  57. package/dist/operate-remote.js +1 -1
  58. package/dist/operate.d.ts +1 -1
  59. package/dist/operate.js +188 -16
  60. package/dist/orphan-sweep.d.ts +2 -0
  61. package/dist/orphan-sweep.js +5 -3
  62. package/dist/project-delete-ui.d.ts +11 -0
  63. package/dist/project-delete-ui.js +29 -2
  64. package/dist/project-tools.d.ts +7 -11
  65. package/dist/project-tools.js +13 -37
  66. package/dist/provider.d.ts +65 -8
  67. package/dist/provider.js +80 -21
  68. package/dist/result-completion.d.ts +7 -0
  69. package/dist/result-completion.js +9 -0
  70. package/dist/runner.d.ts +4 -2
  71. package/dist/runner.js +3 -3
  72. package/dist/serve.js +19 -1
  73. package/dist/server/live.d.ts +3 -0
  74. package/dist/server/live.js +35 -2
  75. package/dist/server/pages.js +59 -35
  76. package/dist/server/render-chat.js +12 -0
  77. package/dist/server/render-tasks.d.ts +12 -2
  78. package/dist/server/render-tasks.js +27 -7
  79. package/dist/server/route-table.js +4 -1
  80. package/dist/server/settings.js +2 -1
  81. package/dist/server/task-views.js +9 -0
  82. package/dist/server/tasks.js +63 -0
  83. package/dist/shared-deps.d.ts +9 -4
  84. package/dist/shared-deps.js +89 -13
  85. package/dist/staged-runtimes.d.ts +16 -4
  86. package/dist/staged-runtimes.js +160 -38
  87. package/dist/steer-live.d.ts +19 -0
  88. package/dist/steer-live.js +93 -0
  89. package/dist/steer-status.d.ts +8 -2
  90. package/dist/steer-status.js +19 -5
  91. package/dist/storage-sweep-child.d.ts +1 -0
  92. package/dist/storage-sweep-child.js +76 -0
  93. package/dist/storage-sweep-process.d.ts +41 -0
  94. package/dist/storage-sweep-process.js +187 -0
  95. package/dist/storage-sweep.d.ts +30 -6
  96. package/dist/storage-sweep.js +214 -28
  97. package/dist/storage-ui.d.ts +2 -0
  98. package/dist/storage-ui.js +16 -4
  99. package/dist/storage.d.ts +6 -0
  100. package/dist/storage.js +35 -0
  101. package/dist/store.d.ts +163 -2
  102. package/dist/store.js +350 -15
  103. package/dist/surface.js +4 -2
  104. package/dist/sweep-quarantine.d.ts +28 -0
  105. package/dist/sweep-quarantine.js +153 -0
  106. package/dist/task-status.d.ts +2 -0
  107. package/dist/task-status.js +5 -1
  108. package/dist/test-temp.d.ts +6 -3
  109. package/dist/test-temp.js +15 -9
  110. package/dist/toolroll-update.js +3 -2
  111. package/dist/work-index.d.ts +1 -0
  112. package/dist/work-index.js +32 -12
  113. package/dist/work-summary.d.ts +2 -2
  114. package/dist/workspace-revision.js +3 -1
  115. package/dist/worktree.d.ts +3 -1
  116. package/dist/worktree.js +93 -10
  117. package/package.json +1 -1
  118. package/scripts/provider-canary.mjs +235 -147
package/dist/operate.js CHANGED
@@ -18,8 +18,10 @@ import { evidencePack, exportDay, ledgerExportChunks, standaloneEvidenceHtml } f
18
18
  import { matchesOutsideCheckpoint } from "./ledger-chain.js";
19
19
  import { CLEANUP_CHOICES, bytesWords, cleanupWords, diskBytes, parseCleanup, storageReport } from "./storage.js";
20
20
  import { removeStaleTestTemp, tempRoots, testTempFolders } from "./test-temp.js";
21
+ import { storageSweepSupervisor } from "./storage-sweep-process.js";
21
22
  import { dailyStorageSweep, lastSweep, nextSweepAt, saveSweep, storageSweepOff as storageSweepOffNow, sweepDetails, sweepWords as storageSweepWords } from "./storage-sweep.js";
22
23
  import { removeShared, sharedDepsRoot, sharedUse, unusedShared } from "./shared-deps.js";
24
+ import { cleanLandedBacklog, gitOf, landedBacklog, reconcileLanded, BACKLOG_DAYS } from "./landed-cleanup.js";
23
25
  import { checkoutPlan, cleanCheckouts, discardCheckout, finishedCheckouts, slimKeptCheckouts, whyWords } from "./checkout-cleanup.js";
24
26
  import { closeOutCheckouts, closeOutRuns, stoppedWords } from "./run-closeout.js";
25
27
  import { MIN_DAYS, RETENTION_KINDS, countWords, dailyRetention, isRetentionKind, lastSweepAt, parsePeriod, periodLabel, periodWords, retentionPlan, sweepWords } from "./retention.js";
@@ -254,6 +256,7 @@ export const OPERATE_HELP = `toolroll — operating the queue
254
256
  toolroll task evidence <id> the task's evidence pack as JSON (--html for a printable page; --out <file>)
255
257
  toolroll storage where the disk goes: database, build checkouts, releases, evidence, leftover test temp folders
256
258
  toolroll storage clean [--yes] preview removing finished tasks' clean checkouts (their branches stay) and test temp folders older than a day; --yes removes them
259
+ toolroll storage clean --landed [--yes] preview work that landed in main over a week ago: marks its results complete, removes their clean checkouts and local branches; --yes does it (instance operator)
257
260
  toolroll storage cleanup <when> remove a finished task's clean checkout: finished|2d|7d|never (instance operator)
258
261
  toolroll storage discard <path> --yes throw away a checkout kept for its changes; its branch stays
259
262
  toolroll integrations [--json] which integrations work: Connected, Not set up or Broken, and what to do (--saved: no new checks)
@@ -309,9 +312,17 @@ export const OPERATE_HELP = `toolroll — operating the queue
309
312
  back in filing order
310
313
  toolroll task steer <id> --note "..."
311
314
  guidance for the agent — a running
312
- build reads it when it next checks
313
- for notes, otherwise the next
314
- attempt does
315
+ Claude build gets it in seconds,
316
+ another running build reads it when
317
+ it next checks for notes, otherwise
318
+ the next attempt does; --now pauses
319
+ the running Claude build first
320
+ toolroll task pause <id> [--run <id>] stop a running Claude build's
321
+ current turn and keep the build; the
322
+ next note continues it (Stop ends it)
323
+ toolroll task continue <id> [--run <id>]
324
+ resume a paused Claude build where it
325
+ stopped, without a note
315
326
  toolroll task assign <id> --runner <name> | --anyone
316
327
  reserve it for one worker (it joins
317
328
  the back of that worker's queue) or
@@ -600,7 +611,7 @@ Exit codes
600
611
  * (surface.ts) is tested for exact equality against them.
601
612
  */
602
613
  export const TASK_ACTIONS = [
603
- "add", "list", "show", "wait", "state", "block", "unblock", "next", "steer", "assign",
614
+ "add", "list", "show", "wait", "state", "block", "unblock", "next", "steer", "pause", "continue", "assign",
604
615
  "reopen", "scope", "approve", "hold", "unhold", "require", "requeue", "regate", "plan",
605
616
  "review", "accept", "repair", "route", "stop", "resume", "complete", "revise", "merge", "ask",
606
617
  "checks", "add-tests",
@@ -653,6 +664,8 @@ export const OPERATE_VALUE_FLAGS = new Set([
653
664
  "access", "projects", "overlap",
654
665
  // audit: a token by name where --token is a credential (remote).
655
666
  "token-name",
667
+ // task pause / task steer --now: the asker's own request id, so a repeat is the same request.
668
+ "request",
656
669
  ]);
657
670
  export const OPERATE_BOOLEAN_FLAGS = new Set([
658
671
  "json", "yes", "all", "brief", "local", "history", "latest-watch", "dry-run", "file",
@@ -666,6 +679,8 @@ export const OPERATE_BOOLEAN_FLAGS = new Set([
666
679
  "saved",
667
680
  // task complete: also open the result's pull request.
668
681
  "pull-request",
682
+ // storage clean --landed: work already in main.
683
+ "landed",
669
684
  // up: the exact start-up details (containment) in the terminal too.
670
685
  "verbose",
671
686
  // lead token: end the lead's credential.
@@ -676,6 +691,8 @@ export const OPERATE_BOOLEAN_FLAGS = new Set([
676
691
  "also",
677
692
  // tokens: the person's password, piped (never an argument).
678
693
  "password-stdin",
694
+ // task steer: pause the running Claude build, then send the note.
695
+ "now",
679
696
  ]);
680
697
  export function parseOperateArgs(argv, ownValues = new Set()) {
681
698
  const positional = [];
@@ -1720,9 +1737,9 @@ async function runnerCommand(positional, flags, context) {
1720
1737
  if (name === undefined || given === undefined || positional.length > 3) {
1721
1738
  return fail(write, json, "runner capacity", "usage", "`runner capacity <name> <n> --as <you> --token <t>`", EXIT.usage);
1722
1739
  }
1723
- const capacity = parseRunnerCapacity(given);
1740
+ const capacity = parseRunnerCapacity(given, { allowZero: true });
1724
1741
  if (capacity === null) {
1725
- return fail(write, json, "runner capacity", "usage", `capacity is a whole number of tasks, 1 to ${RUNNER_CAPACITY_MAX}`, EXIT.usage);
1742
+ return fail(write, json, "runner capacity", "usage", `capacity is a whole number of tasks, 0 to ${RUNNER_CAPACITY_MAX}; 0 stops new builds`, EXIT.usage);
1726
1743
  }
1727
1744
  const acting = await askCredentials(flags, context);
1728
1745
  const verified = acting === null ? null : authenticateApprover(store, acting.name, acting.token);
@@ -1734,6 +1751,12 @@ async function runnerCommand(positional, flags, context) {
1734
1751
  return fail(write, json, "runner capacity", changed.reason, changed.reason === "retired" ? `${name} is retired` : `no runner \`${name}\``, EXIT.refused);
1735
1752
  }
1736
1753
  const running = store.liveClaimCount(name, now);
1754
+ if (changed.after === 0) {
1755
+ return succeed(write, json, "runner capacity", { name, before: changed.before, after: changed.after, running }, () => [
1756
+ `${name} takes no new builds. ${running === 0 ? "Nothing is running." : `${running} running task(s) will finish.`}`,
1757
+ `Resume with: toolroll runner capacity ${name} <1-${RUNNER_CAPACITY_MAX}>`,
1758
+ ]);
1759
+ }
1737
1760
  return succeed(write, json, "runner capacity", { name, before: changed.before, after: changed.after, running }, () => [
1738
1761
  changed.before === changed.after
1739
1762
  ? `${name} already runs up to ${changed.after} at once.`
@@ -3422,6 +3445,14 @@ async function reconcileCommand(flags, context) {
3422
3445
  reaped: reaped.map(claim => claim.leaseId),
3423
3446
  });
3424
3447
  }
3448
+ // Landed work (landed-cleanup.ts): finish teardowns a crash cut short, then mark newly landed results complete (a
3449
+ // ledger entry says why) and remove their clean checkouts and local build and plan branches.
3450
+ let landed = { completed: [], steps: [] };
3451
+ try {
3452
+ landed = await reconcileLanded(store, worktrees, gitOf(context.gitRunner), repo, context.evidenceRoot, clock);
3453
+ }
3454
+ catch { /* the next pass tries again */ }
3455
+ const landedGone = landed.steps.filter(one => one.outcome === "removed");
3425
3456
  // Checkout cleanup (Settings → Storage): finished work's clean checkouts go as the setting says (by default when
3426
3457
  // the task is complete or cancelled); their branches stay. Each removal is in the ledger.
3427
3458
  // Completing or cancelling a task stops anything still running from its checkout first.
@@ -3440,9 +3471,10 @@ async function reconcileCommand(flags, context) {
3440
3471
  const retained = retention === null ? [] : retention.counts.filter(one => one.count > 0);
3441
3472
  // The daily storage sweep (storage-sweep.ts): stale test temp folders, orphans Toolroll left running, finished
3442
3473
  // checkouts, extra staged runtimes and dependency installs unused for a week; saved for Settings → Storage.
3474
+ // Launch only: report a result here only if it already completed. Later results save independently.
3443
3475
  let storageSwept = null;
3444
3476
  try {
3445
- storageSwept = await dailyStorageSweep(store, { databaseFile: context.databaseFile, pool: worktrees, tempRoots: context.tempRoots ?? tempRoots() }, clock);
3477
+ storageSwept = dailyStorageSweep(store, { databaseFile: context.databaseFile, pool: worktrees, tempRoots: context.tempRoots ?? tempRoots(), ...(context.sweepOwner === undefined ? {} : { owner: context.sweepOwner }) }, clock);
3446
3478
  }
3447
3479
  catch {
3448
3480
  storageSwept = null; /* the next pass tries again */
@@ -3458,6 +3490,8 @@ async function reconcileCommand(flags, context) {
3458
3490
  closedRuns.length === 0 &&
3459
3491
  closedCheckouts.length === 0 &&
3460
3492
  slimmed.slimmed.length === 0 &&
3493
+ landed.completed.length === 0 &&
3494
+ landedGone.length === 0 &&
3461
3495
  liveSwept.removed.length === 0;
3462
3496
  return succeed(write, json, "reconcile", {
3463
3497
  recovered,
@@ -3468,6 +3502,7 @@ async function reconcileCommand(flags, context) {
3468
3502
  checkoutsKept: pruned.kept,
3469
3503
  processesStopped: [...closedRuns.map(one => ({ run: one.runId, pids: one.done.stopped.map(row => row.pid) })), ...closedCheckouts.map(one => ({ checkout: one.path, pids: one.done.stopped.map(row => row.pid) }))],
3470
3504
  checkoutsSlimmed: slimmed.slimmed.map(one => one.path),
3505
+ landed: { completed: landed.completed, steps: landed.steps },
3471
3506
  liveViewsSwept: liveSwept.removed.length,
3472
3507
  retention: retention === null ? null : { counts: retention.counts, freedBytes: retention.freed },
3473
3508
  storageSweep: storageSwept,
@@ -3481,6 +3516,8 @@ async function reconcileCommand(flags, context) {
3481
3516
  ...(pruned.removed.length === 0 ? [] : [`Removed ${pruned.removed.length} finished task checkout(s), about ${bytesWords(pruned.freed)}; their branches stay.`]),
3482
3517
  ...closedRuns.map(one => `Run #${one.runId} ended; stopped ${stoppedWords(one.done)}.`),
3483
3518
  ...closedCheckouts.map(one => `${one.path}: its task finished; stopped ${stoppedWords(one.done)}.`),
3519
+ ...(landed.completed.length === 0 ? [] : [`Marked ${landed.completed.length} landed result(s) complete: ${landed.completed.join(", ")}.`]),
3520
+ ...(landedGone.length === 0 ? [] : [`Removed landed work: ${landedGone.filter(one => one.kind === "checkout").length} checkout(s), ${landedGone.filter(one => one.kind === "branch").length} local branch(es).`]),
3484
3521
  ...(slimmed.slimmed.length === 0 ? [] : [`Dropped dependencies and build output from ${slimmed.slimmed.length} kept checkout(s), about ${bytesWords(slimmed.slimmed.reduce((sum, one) => sum + one.bytes, 0))}.`]),
3485
3522
  ...(liveSwept.removed.length === 0 ? [] : [`Cleared ${liveSwept.removed.length} finished live view(s).`]),
3486
3523
  ...(retention === null || retained.length === 0 ? [] : [`Retention removed ${sweepWords(retention.counts, retention.freed)}.`]),
@@ -4346,8 +4383,10 @@ async function providersCommand(flags, context) {
4346
4383
  }
4347
4384
  write(` isolation ${audit.isolation.flag === null
4348
4385
  ? "no hermetic flag"
4349
- : `${audit.isolation.flag} exists, not passed${audit.isolation.resumeSafe === false ? " — it would break session resume" : audit.isolation.resumeSafe === null ? " — its effect on resume is unvalidated" : ""}`}`);
4350
- write(` config surface ${audit.configSurface.join("; ")}`);
4386
+ : audit.isolation.enforced
4387
+ ? `builds run in their own setup (${audit.isolation.flag}) — none of your own hooks, plugins or MCP servers; reviews keep theirs`
4388
+ : `${audit.isolation.flag} exists, not passed${audit.isolation.resumeSafe === false ? " — it would break session resume" : audit.isolation.resumeSafe === null ? " — its effect on resume is unvalidated" : ""}`}`);
4389
+ write(` ${audit.isolation.enforced ? "builds get " : "config surface"} ${audit.configSurface.join("; ")}`);
4351
4390
  write("");
4352
4391
  }
4353
4392
  write(" \u2192 toolroll config show which provider each phase actually resolves to");
@@ -6105,8 +6144,10 @@ async function runWatchLoop(args) {
6105
6144
  const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
6106
6145
  // Reconciliation has its own cadence and sink. Sharing tick's sink would
6107
6146
  // interleave envelopes while a build awaits its provider or project check.
6147
+ const sweepOwner = {};
6108
6148
  const maintenance = startMaintenance({
6109
6149
  intervalMs: reconcileEveryMs,
6150
+ onStop: () => storageSweepSupervisor(context.databaseFile).stop(sweepOwner),
6110
6151
  shouldStop: stopping,
6111
6152
  onError: error => {
6112
6153
  reconciliationReady = false;
@@ -6117,7 +6158,7 @@ async function runWatchLoop(args) {
6117
6158
  },
6118
6159
  run: async () => {
6119
6160
  const maintenanceLines = [];
6120
- const maintenanceContext = { ...quietContext, write: line => maintenanceLines.push(line) };
6161
+ const maintenanceContext = { ...quietContext, sweepOwner, write: line => maintenanceLines.push(line) };
6121
6162
  const code = await reconcileCommand(passFlags(), maintenanceContext);
6122
6163
  if (code !== EXIT.ok)
6123
6164
  throw new Error(maintenanceLines.join(" "));
@@ -8472,7 +8513,7 @@ function taskCommand(positional, flags, context) {
8472
8513
  return taskAction(action, rest, flags, context);
8473
8514
  }
8474
8515
  /** Task verbs that are the lead working on a task, not reading it. */
8475
- const LEAD_WORK_VERBS = new Set(["state", "steer", "reopen", "requeue", "regate", "repair", "resume", "stop", "scope", "approve", "plan", "checks", "revise", "hold", "unhold", "route", "require"]);
8516
+ const LEAD_WORK_VERBS = new Set(["state", "steer", "pause", "continue", "reopen", "requeue", "regate", "repair", "resume", "stop", "scope", "approve", "plan", "checks", "revise", "hold", "unhold", "route", "require"]);
8476
8517
  function taskAction(action, rest, flags, context) {
8477
8518
  switch (action) {
8478
8519
  case "complete":
@@ -8513,6 +8554,10 @@ function taskAction(action, rest, flags, context) {
8513
8554
  return nextTask(rest, flags, context);
8514
8555
  case "steer":
8515
8556
  return steerTask(rest, flags, context);
8557
+ case "pause":
8558
+ return pauseTask(rest, flags, context);
8559
+ case "continue":
8560
+ return continueTask(rest, flags, context);
8516
8561
  case "assign":
8517
8562
  return assignTask(rest, flags, context);
8518
8563
  case "reopen":
@@ -9454,6 +9499,9 @@ async function steerTask(positional, flags, context) {
9454
9499
  if (!authed.ok) {
9455
9500
  return fail(write, json, "task steer", "not-an-approver", "that is not an approver, or the token does not match", EXIT.refused);
9456
9501
  }
9502
+ // --now: Pause and steer — the running Claude build's turn stops, then this note continues it.
9503
+ if (flags.get("now") === true)
9504
+ return pauseRunningBuild("task steer", id, note, verifiedAuthor(acting.name), flags, context);
9457
9505
  const filed = store.fileSteerNote(id, verifiedAuthor(acting.name), note, context.now, mutationFrom(flags, context.now));
9458
9506
  if (!filed.ok) {
9459
9507
  const detail = filed.reason === "unknown-task"
@@ -9465,8 +9513,87 @@ async function steerTask(positional, flags, context) {
9465
9513
  }
9466
9514
  const steered = store.lookupRef(id);
9467
9515
  const live = steered !== null && store.steeringLiveBuild(steered.id, context.now) !== null;
9516
+ const liveInput = steered !== null && store.liveInputRun(steered.id, context.now) !== null;
9468
9517
  return succeed(write, json, "task steer", { task: id, note: filed.id, standing: live ? "waiting" : "next-attempt" }, () => [
9469
- `Noted. ${steerDestinationWords(live)} \`task show ${id}\` says when it has been read.`,
9518
+ `Noted. ${steerDestinationWords(live, liveInput)} \`task show ${id}\` says when it has been read.`,
9519
+ ]);
9520
+ }
9521
+ /**
9522
+ * `toolroll task pause <id> [--run <n>] --as <you> --token <t>` (live steering): stop a running Claude build's
9523
+ * current turn and keep the build — its session waits for the next note. Never a stop: nothing ends, holds or
9524
+ * reclaims the attempt. Bound to the exact run (--run, or the one taking live input now); a repeated --request id
9525
+ * is the same request.
9526
+ */
9527
+ async function pauseTask(positional, flags, context) {
9528
+ const { store, write, json } = context;
9529
+ const id = positional[0];
9530
+ if (id === undefined)
9531
+ return fail(write, json, "task pause", "usage", "`toolroll task pause <id> [--run <run-id>] --as <you> --token <t>`", EXIT.usage);
9532
+ const acting = await askCredentials(flags, context);
9533
+ if (acting === null)
9534
+ return fail(write, json, "task pause", "usage", "pausing takes `--as <you> --token <t>` — it is an operator's act and is recorded against your name", EXIT.usage);
9535
+ const authed = authenticateApprover(store, acting.name, acting.token);
9536
+ if (!authed.ok)
9537
+ return fail(write, json, "task pause", "not-an-approver", "that is not an approver, or the token does not match", EXIT.refused);
9538
+ return pauseRunningBuild("task pause", id, undefined, verifiedAuthor(acting.name), flags, context);
9539
+ }
9540
+ /**
9541
+ * `toolroll task continue <id> [--run <n>] --as <you> --token <t>` (live steering): Resume a paused Claude build with a
9542
+ * short continue frame and no note. Bound to the exact run (--run, or the one taking live input now), and only while it
9543
+ * is paused; a repeated --request id is the same request.
9544
+ */
9545
+ async function continueTask(positional, flags, context) {
9546
+ const { store, write, json } = context;
9547
+ const id = positional[0];
9548
+ if (id === undefined)
9549
+ return fail(write, json, "task continue", "usage", "`toolroll task continue <id> [--run <run-id>] --as <you> --token <t>`", EXIT.usage);
9550
+ const acting = await askCredentials(flags, context);
9551
+ if (acting === null)
9552
+ return fail(write, json, "task continue", "usage", "resuming takes `--as <you> --token <t>` — it is an operator's act and is recorded against your name", EXIT.usage);
9553
+ const authed = authenticateApprover(store, acting.name, acting.token);
9554
+ if (!authed.ok)
9555
+ return fail(write, json, "task continue", "not-an-approver", "that is not an approver, or the token does not match", EXIT.refused);
9556
+ const ref = store.lookupRef(id);
9557
+ if (ref === null || store.getTask(id) === null)
9558
+ return fail(write, json, "task continue", "unknown-task", `no task \`${id}\``, EXIT.refused);
9559
+ const runText = text(flags, "run");
9560
+ const named = runText === undefined ? store.liveInputRun(ref.id, context.now) : Number(runText);
9561
+ if (named === null)
9562
+ return fail(write, json, "task continue", "not-live", `${id} has no running Claude build to resume`, EXIT.refused);
9563
+ if (!Number.isInteger(named) || named < 1)
9564
+ return fail(write, json, "task continue", "usage", "--run names one exact attempt by its number", EXIT.usage);
9565
+ const requestId = text(flags, "request") ?? text(flags, "key") ?? `cli-${randomUUID()}`;
9566
+ const asked = store.requestResume({ taskId: id, runId: named, by: verifiedAuthor(acting.name), via: "cli", requestId }, context.now);
9567
+ if (!asked.ok)
9568
+ return fail(write, json, "task continue", asked.reason, asked.detail, EXIT.refused, { run: named });
9569
+ return succeed(write, json, "task continue", { task: id, run: named, resume: asked.resume, repeated: asked.duplicate }, () => [
9570
+ asked.duplicate ? `Build #${named} of ${id} was already asked to resume with that request; nothing changed.`
9571
+ : `Resuming build #${named} of ${id} where it stopped.`,
9572
+ ]);
9573
+ }
9574
+ /** Pause, or Pause and steer, through the store's one action; the words both CLI doors print. */
9575
+ function pauseRunningBuild(command, id, note, by, flags, context) {
9576
+ const { store, write, json } = context;
9577
+ const ref = store.lookupRef(id);
9578
+ if (ref === null || store.getTask(id) === null)
9579
+ return fail(write, json, command, "unknown-task", `no task \`${id}\``, EXIT.refused);
9580
+ const runText = text(flags, "run");
9581
+ const named = runText === undefined ? store.liveInputRun(ref.id, context.now) : Number(runText);
9582
+ if (named === null) {
9583
+ return fail(write, json, command, "not-live", note === undefined
9584
+ ? `${id} has no running Claude build to pause`
9585
+ : `${id} has no running Claude build to pause; send the note without --now`, EXIT.refused);
9586
+ }
9587
+ if (!Number.isInteger(named) || named < 1)
9588
+ return fail(write, json, command, "usage", "--run names one exact attempt by its number", EXIT.usage);
9589
+ const requestId = text(flags, "request") ?? text(flags, "key") ?? `cli-${randomUUID()}`;
9590
+ const asked = store.requestPause({ taskId: id, runId: named, by, via: "cli", requestId, ...(note === undefined ? {} : { note }) }, context.now);
9591
+ if (!asked.ok)
9592
+ return fail(write, json, command, asked.reason, asked.detail, EXIT.refused, { run: named });
9593
+ return succeed(write, json, command, { task: id, run: named, pause: asked.pause, note: asked.note, repeated: asked.duplicate }, () => [
9594
+ asked.duplicate ? `Build #${named} of ${id} was already asked to pause with that request; nothing changed.`
9595
+ : note === undefined ? `Pausing build #${named} of ${id}. Its next note continues it; \`task stop\` ends it.`
9596
+ : `Pausing build #${named} of ${id}, then sending your note. \`task show ${id}\` says when it has been read.`,
9470
9597
  ]);
9471
9598
  }
9472
9599
  /** The mirror of block: stop waiting. Removal cannot create a cycle. */
@@ -10991,7 +11118,7 @@ async function retentionCommand(positional, flags, context) {
10991
11118
  const periods = store.setRetentionPeriod(kind, days, acting.name, context.clock());
10992
11119
  return succeed(context.write, context.json, command, { periods }, () => [`${label(kind)}: kept ${days === null ? "forever" : `for ${periodWords(days)}`}.`]);
10993
11120
  }
10994
- /** `storage`, `storage cleanup [<when>]`, `storage clean [--yes]`, `storage discard <path> --yes`: where the disk goes,
11121
+ /** `storage`, `storage cleanup [<when>]`, `storage clean [--landed] [--yes]`, `storage discard <path> --yes`: where the disk goes,
10995
11122
  * and task checkouts: when a finished task's clean checkout is removed, cleaning up now, and throwing away one kept for
10996
11123
  * its changes. Changing anything takes an instance operator. */
10997
11124
  async function storageCommand(positional, flags, context) {
@@ -10999,7 +11126,7 @@ async function storageCommand(positional, flags, context) {
10999
11126
  const command = `storage ${action ?? ""}`.trim();
11000
11127
  const store = context.store;
11001
11128
  const changes = (action === "cleanup" && rest.length > 0) || (action === "clean" && flags.has("yes")) || action === "discard";
11002
- const allowed = new Set([...(changes ? ["as", "token", "token-file", "token-env"] : []), ...(action === "clean" || action === "discard" ? ["yes"] : []), "db", "json"]);
11129
+ const allowed = new Set([...(changes ? ["as", "token", "token-file", "token-env"] : []), ...(action === "clean" || action === "discard" ? ["yes"] : []), ...(action === "clean" ? ["landed"] : []), "db", "json"]);
11003
11130
  for (const name of flags.keys())
11004
11131
  if (!allowed.has(name))
11005
11132
  return fail(context.write, context.json, command, "usage", `--${name} is not a ${command} option.`, EXIT.usage);
@@ -11077,6 +11204,11 @@ async function storageCommand(positional, flags, context) {
11077
11204
  const saved = store.setCheckoutCleanup(cleanup, actor, context.clock());
11078
11205
  return succeed(context.write, context.json, command, { cleanup: saved }, () => [`Saved. A finished task's clean checkout is removed ${cleanupWords(saved)}; its branch stays.`]);
11079
11206
  }
11207
+ if (action === "clean" && flags.has("landed")) {
11208
+ if (rest.length > 0 || flags.get("landed") !== true)
11209
+ return fail(context.write, context.json, "storage clean --landed", "usage", "Use storage clean --landed to preview, then storage clean --landed --yes.", EXIT.usage);
11210
+ return landedCommand(flags.has("yes"), operator, refused, pool, context);
11211
+ }
11080
11212
  if (action === "clean") {
11081
11213
  if (rest.length > 0)
11082
11214
  return fail(context.write, context.json, command, "usage", "Use storage clean to preview, then storage clean --yes.", EXIT.usage);
@@ -11102,7 +11234,7 @@ async function storageCommand(positional, flags, context) {
11102
11234
  return refused("cleans up checkouts");
11103
11235
  const done = await cleanCheckouts(store, pool, context.clock, { manual: true, actor });
11104
11236
  const temp = leftovers(true);
11105
- const swept = removeStaleTestTemp(roots, context.clock());
11237
+ const swept = await removeStaleTestTemp(roots, context.clock());
11106
11238
  if (swept.removed.length > 0) {
11107
11239
  store.recordAction({ at: context.clock().toISOString(), actor, repo: null, taskId: null, runId: null, action: "test temp folders removed", outcome: "removed", source: "request",
11108
11240
  detail: `${swept.removed.length} older than a day, about ${bytesWords(temp.staleBytes)}${swept.failed.length === 0 ? "" : `; ${swept.failed.length} couldn't be removed`}` });
@@ -11150,7 +11282,47 @@ async function storageCommand(positional, flags, context) {
11150
11282
  return fail(context.write, context.json, command, "refused", done.message, EXIT.refused);
11151
11283
  return succeed(context.write, context.json, command, { discarded: target, freedBytes: done.bytes }, () => [`Discarded ${target}, about ${bytesWords(done.bytes)}; its branch stays.`]);
11152
11284
  }
11153
- return fail(context.write, context.json, command, "usage", "Use storage, storage cleanup <when>, storage clean [--yes] or storage discard <path> --yes.", EXIT.usage);
11285
+ return fail(context.write, context.json, command, "usage", "Use storage, storage cleanup <when>, storage clean [--landed] [--yes] or storage discard <path> --yes.", EXIT.usage);
11286
+ }
11287
+ /** `storage clean --landed [--yes]`: the one-time pass over work that piled up before reconcile did this on its own.
11288
+ * Results whose work landed at least a week ago are marked complete (a ledger entry says why) and their clean
11289
+ * checkouts and local branches go; so do Toolroll's own branches no task uses whose work landed. A preview changes
11290
+ * nothing; --yes takes an instance operator, records every teardown first, then carries it out. */
11291
+ async function landedCommand(yes, operator, refused, pool, context) {
11292
+ const command = "storage clean --landed";
11293
+ const git = gitOf(context.gitRunner);
11294
+ const plurals = (count, one, many = `${one}s`) => `${count} ${count === 1 ? one : many}`;
11295
+ const keepLine = (one) => ` ${projectName(one.repo)} ${one.what}: ${one.why}`;
11296
+ const stepLine = (one) => ` ${projectName(one.repo)} ${one.target}${one.why === null ? "" : `: ${one.why}`}`;
11297
+ const counts = (plan) => ({
11298
+ results: plan.results.length, complete: plan.results.filter(one => one.completes).length,
11299
+ checkouts: plan.results.reduce((sum, one) => sum + one.targets.filter(target => target.kind === "checkout").length, 0) + plan.branches.filter(one => one.checkout !== null).length,
11300
+ branches: plan.results.reduce((sum, one) => sum + one.targets.filter(target => target.kind === "branch").length, 0) + plan.branches.length,
11301
+ });
11302
+ if (!yes) {
11303
+ const plan = await landedBacklog(context.store, git, context.evidenceRoot, context.clock());
11304
+ const n = counts(plan);
11305
+ const anything = n.results + plan.branches.length + plan.pending > 0;
11306
+ return succeed(context.write, context.json, command, { preview: true, cutoff: plan.cutoff, results: plan.results, branches: plan.branches, kept: plan.kept, pending: plan.pending }, () => [
11307
+ anything ? `Would mark ${plurals(n.complete, "landed result")} complete and remove ${plurals(n.checkouts, "checkout")} and ${plurals(n.branches, "local branch", "local branches")}.` : `Nothing landed more than ${BACKLOG_DAYS} days ago is left to clean up.`,
11308
+ ...plan.results.map(one => ` ${projectName(one.repo)} ${one.taskId}: ${one.reason}${one.completes ? "" : " (already complete)"}`),
11309
+ ...plan.branches.map(one => ` ${projectName(one.repo)} ${one.branch}: ${one.reason}`),
11310
+ ...(plan.pending === 0 ? [] : [`${plurals(plan.pending, "item")} from an earlier clean-up will finish too.`]),
11311
+ ...(plan.kept.length === 0 ? [] : [`Stays (${plan.kept.length}):`, ...plan.kept.map(keepLine)]),
11312
+ anything ? "Nothing was changed. Run toolroll storage clean --landed --yes to do it." : "Nothing was changed.",
11313
+ ]);
11314
+ }
11315
+ const actor = await operator();
11316
+ if (actor === null)
11317
+ return refused("cleans up landed work");
11318
+ const plan = await landedBacklog(context.store, git, context.evidenceRoot, context.clock());
11319
+ const done = await cleanLandedBacklog(context.store, pool, git, context.evidenceRoot, context.clock, plan, actor);
11320
+ const gone = done.steps.filter(one => one.outcome === "removed" || one.outcome === "already gone");
11321
+ const stays = done.steps.filter(one => one.outcome === "waits" || one.outcome === "kept");
11322
+ return succeed(context.write, context.json, command, { completed: done.completed, steps: done.steps, refused: done.refused, kept: plan.kept }, () => [
11323
+ `Marked ${plurals(done.completed.length, "landed result")} complete. Removed ${plurals(gone.filter(one => one.kind === "checkout").length, "checkout")} and ${plurals(gone.filter(one => one.kind === "branch").length, "local branch", "local branches")}.`,
11324
+ ...(stays.length + done.refused.length === 0 ? [] : [`Stays (${stays.length + done.refused.length}):`, ...stays.map(stepLine), ...done.refused.map(keepLine)]),
11325
+ ]);
11154
11326
  }
11155
11327
  function checkoutSummary(plan) {
11156
11328
  return { count: plan.count, totalBytes: plan.totalBytes, waitingReview: plan.waitingReview, withChanges: plan.withChanges, cleanBytes: plan.freeBytes, cleanCount: plan.go.length, cleanup: plan.cleanup };
@@ -57,6 +57,8 @@ export type OrphanOptions = StopOptions & {
57
57
  tempRoots?: readonly string[];
58
58
  locate?: (pids: readonly number[]) => Map<number, Located>;
59
59
  self?: number;
60
+ /** A root live work uses (storage-sweep.ts): asked at both looks; true leaves everything in it running. */
61
+ spare?: (path: string) => boolean;
60
62
  };
61
63
  /** Stop the orphans Toolroll left (see the top of this file), each in the ledger. */
62
64
  export declare function sweepOrphans(store: Store, now: () => Date, options?: OrphanOptions): Promise<OrphanSweep>;
@@ -180,7 +180,8 @@ export async function sweepOrphans(store, now, options = {}) {
180
180
  catch {
181
181
  return { stopped: [], failed: ["the process list couldn't be read"] };
182
182
  }
183
- const roots = sweepRoots(store, options.pool ?? null, census, options.tempRoots ?? [], now());
183
+ const rootsNow = (rows) => sweepRoots(store, options.pool ?? null, rows, options.tempRoots ?? [], now()).filter(root => options.spare?.(root.path) !== true);
184
+ const roots = rootsNow(census);
184
185
  if (roots.length === 0)
185
186
  return { stopped: [], failed };
186
187
  const guard = protectedProcesses(census, options.self);
@@ -195,14 +196,15 @@ export async function sweepOrphans(store, now, options = {}) {
195
196
  const first = orphanTargets(census, located, roots, guard);
196
197
  if (first.length === 0)
197
198
  return { stopped: [], failed };
198
- // Right before the signal, everything again: the same process, still an orphan (or under one), still inside.
199
+ // Right before the signal, everything again: the same process, still an orphan (or under one), still inside a root
200
+ // that is still one (no run or lease took its checkout since, nothing live uses it).
199
201
  let again;
200
202
  try {
201
203
  const fresh = read();
202
204
  const freshGuard = protectedProcesses(fresh, options.self);
203
205
  const freshLocated = locate(fresh.filter(row => row.ppid === 1).map(row => row.pid));
204
206
  const before = new Map(first.map(one => [one.row.pid, one]));
205
- again = orphanTargets(fresh, freshLocated, roots, freshGuard).filter(one => {
207
+ again = orphanTargets(fresh, freshLocated, rootsNow(fresh), freshGuard).filter(one => {
206
208
  const was = before.get(one.row.pid);
207
209
  return was !== undefined && Math.abs(was.row.bornAt - one.row.bornAt) <= BIRTH_SLACK_MS && was.root.path === one.root.path;
208
210
  });
@@ -20,6 +20,17 @@ export type ProjectSettingsView = {
20
20
  building: number;
21
21
  canChange: boolean;
22
22
  };
23
+ /** What its builds can reach: the project's named tools and whether each loads (names and words only, never a
24
+ * secret), or why they couldn't be read. */
25
+ access?: {
26
+ tools: {
27
+ name: string;
28
+ loads: boolean;
29
+ words: string;
30
+ }[];
31
+ } | {
32
+ problem: string;
33
+ };
23
34
  };
24
35
  /** The project's page: its path, what Toolroll holds for it, and (for an instance operator) Delete project. */
25
36
  export declare function projectSettingsHtml(view: ProjectSettingsView, notice: {
@@ -14,7 +14,34 @@ export const PROJECT_DELETE_CSS = `.project-settings,.project-delete{max-width:7
14
14
  `.project-settings button,.project-delete button{justify-self:start;min-height:36px;white-space:nowrap}.project-delete .actions a{min-height:36px;display:inline-flex;align-items:center}` +
15
15
  `.project-settings .builds-at-once{margin-top:20px}.project-settings .builds-at-once h2{font-size:1rem;margin:0 0 4px}.project-settings .builds-at-once form{margin-top:4px;gap:4px}` +
16
16
  `.project-settings .builds-at-once .builds-row{display:flex;gap:8px;align-items:stretch}.project-settings .builds-at-once input{width:5.5rem;min-height:44px;margin:0;box-sizing:border-box}.project-settings .builds-at-once button{min-height:44px;margin:0}` +
17
- `.project-settings .builds-at-once .meta{margin:0}.project-settings form.remove-project{margin-top:20px;gap:4px}.project-settings form.remove-project button{min-height:44px}.project-settings form.remove-project .meta{margin:0}`;
17
+ `.project-settings .builds-at-once .meta{margin:0}.project-settings form.remove-project{margin-top:20px;gap:4px}.project-settings form.remove-project button{min-height:44px}.project-settings form.remove-project .meta{margin:0}` +
18
+ `.project-settings .build-access{margin-top:20px;min-width:0;overflow-wrap:anywhere}.project-settings .build-access h2{font:var(--so-typography-title-sm);margin:0 0 4px}.project-settings .build-access p{margin:0}` +
19
+ `.project-settings .build-access .tool-name{font:var(--so-typography-mono)}.project-settings .build-access details{margin:4px 0 0}.project-settings .build-access summary{min-height:44px;display:inline-flex;align-items:center;cursor:pointer;color:var(--so-muted);font:var(--so-typography-label)}` +
20
+ `.project-settings .build-access ul{margin:0 0 4px;padding-left:20px;font:var(--so-typography-body);color:var(--so-ink)}.project-settings .build-access li{margin:2px 0}.project-settings .build-access li .meta{color:var(--so-muted)}` +
21
+ `.project-settings .build-access a.manage-tools{display:inline-flex;align-items:center;justify-content:center;min-height:44px;margin-top:4px;padding:0 14px;white-space:nowrap;background:var(--so-paper);color:var(--so-ink);border:1px solid var(--so-input-line);border-radius:var(--so-radius-md);font:var(--so-typography-label);text-decoration:none}` +
22
+ `.project-settings .build-access a.manage-tools:hover{background:var(--so-raised)}.project-settings .build-access a.manage-tools:focus-visible,.project-settings .build-access summary:focus-visible{outline:2px solid var(--so-ink);outline-offset:2px}`;
23
+ /** Build access: one sentence for what a build gets, the detail on demand, and the way to change it. */
24
+ function accessHtml(view) {
25
+ const access = view.access;
26
+ if (access === undefined)
27
+ return "";
28
+ const manage = html `<a class="manage-tools" href="/settings/tools?repo=${encodeURIComponent(view.repo)}">Manage tools</a>`;
29
+ const always = [
30
+ "Your other projects, keys and sign-in files are out of reach.",
31
+ "Your own Claude and Codex hooks, plugins and MCP servers don't load.",
32
+ "Builds sign in with your existing Claude or Codex login.",
33
+ ];
34
+ if ("problem" in access) {
35
+ return html `<section class="build-access" aria-labelledby="build-access-title"><h2 id="build-access-title">Build access</h2><p class="problem" role="alert">${access.problem}</p><details><summary>Details</summary><ul>${always.map(one => html `<li>${one}</li>`)}</ul></details>${manage}</section>`;
36
+ }
37
+ const loading = access.tools.filter(one => one.loads);
38
+ const names = loading.map((one, at) => html `${at === 0 ? "" : ", "}<span class="tool-name">${one.name}</span>`);
39
+ const outcome = loading.length === 0
40
+ ? html `<p>Builds work in their task's folder, with no extra tools.</p>`
41
+ : html `<p>Builds work in their task's folder, with ${loading.length === 1 ? "one tool" : `${loading.length} tools`}: ${names}.</p>`;
42
+ const tools = access.tools.map(one => html `<li><span class="tool-name">${one.name}</span> <span class="meta">· ${one.words}</span></li>`);
43
+ return html `<section class="build-access" aria-labelledby="build-access-title"><h2 id="build-access-title">Build access</h2>${outcome}<details><summary>Details</summary><ul>${always.map(one => html `<li>${one}</li>`)}${tools}</ul></details>${manage}</section>`;
44
+ }
18
45
  /** Builds at once: one number, one Save. The worker's cap is said only when it lowers the number. */
19
46
  function buildsHtml(view) {
20
47
  const b = view.builds;
@@ -30,7 +57,7 @@ const note = (notice) => notice.problem ? html `<p class="problem" role="alert">
30
57
  /** The project's page: its path, what Toolroll holds for it, and (for an instance operator) Delete project. */
31
58
  export function projectSettingsHtml(view, notice) {
32
59
  const held = html `<p>Toolroll holds ${holdingsWords(view.holdings)} for <strong>${view.name}</strong>.</p>`;
33
- const head = html `${note(notice)}<p class="path">${view.repo}</p>${held}${buildsHtml(view)}`;
60
+ const head = html `${note(notice)}<p class="path">${view.repo}</p>${held}${buildsHtml(view)}${accessHtml(view)}`;
34
61
  if (!view.canDelete)
35
62
  return html `<section class="project-settings">${head}<p class="meta">An instance operator can delete a project.</p></section>`;
36
63
  const body = view.running.length > 0
@@ -142,22 +142,19 @@ export type ToolLaunchArgs = {
142
142
  cleanup: () => void;
143
143
  privateDir?: string;
144
144
  };
145
- /** Claude: only these servers (`--strict-mcp-config`), written to a private 0600 file removed after the run. */
145
+ /** Claude: only these servers, written to a private 0600 file removed after the run (the provider adds `--strict-mcp-config`). */
146
146
  export declare function claudeToolArgs(launch: ToolLaunch): ToolLaunchArgs;
147
- export declare function carriedCodexSettings(includeModel: boolean, codexHome?: string): string[];
148
147
  /** Where the tool launcher lives beside this module, built. */
149
148
  export declare function toolLauncherPath(): string;
150
149
  /**
151
- * Codex: the operator's config.toml is ignored whole (its MCP servers
152
- * with it), ChatGPT apps and plugins are off, the model settings above
153
- * are carried, and each tool is named by `-c` and started through the
154
- * launcher. Codex gives its own environment to the agent's shell whatever
150
+ * Codex: each tool is named by `-c` and started through the launcher. The
151
+ * operator's config.toml, rules, apps, plugins and hooks are already off
152
+ * (the provider's CODEX_BUILD_ISOLATION_ARGV), and nothing of config.toml
153
+ * is carried. Codex gives its own environment to the agent's shell whatever
155
154
  * its policy says, so no secret rides it: each tool's values sit in the
156
155
  * run's private 0600 file and reach only that tool's process.
157
156
  */
158
- export declare function codexToolArgs(launch: ToolLaunch, options: {
159
- includeModel: boolean;
160
- codexHome?: string;
157
+ export declare function codexToolArgs(launch: ToolLaunch, options?: {
161
158
  launcher?: string;
162
159
  node?: string;
163
160
  enabledTools?: Readonly<Record<string, readonly string[]>>;
@@ -182,12 +179,11 @@ export declare function toolLaunchFor(store: Pick<Store, "projectTools" | "getRu
182
179
  export declare function prepareRunTools(store: Pick<Store, "projectTools" | "getRun" | "refById" | "getScope" | "toolSealFor" | "recordRunTools" | "orgPolicy">, runId: number, provider: "claude" | "codex" | "openrouter" | "gemini", options: {
183
180
  home?: string;
184
181
  now: Date;
185
- includeModel: boolean;
186
182
  readOnly?: Readonly<Record<string, readonly string[]>>;
187
183
  withheld?: Readonly<Record<string, string>>;
188
184
  }): ToolLaunchArgs;
189
185
  /** When a launch's tools cannot be prepared, it still gets none of anything else's: the same isolation with an empty list. */
190
- export declare function noToolsArgs(provider: "claude" | "codex" | "openrouter" | "gemini", includeModel: boolean): ToolLaunchArgs;
186
+ export declare function noToolsArgs(provider: "claude" | "codex" | "openrouter" | "gemini"): ToolLaunchArgs;
191
187
  type ToolWrites = Pick<Store, "projectTools" | "addProjectTool" | "removeProjectTool" | "recordProjectToolTest">;
192
188
  /** Add one tool (and, for an import, the values it brings) to a project's builds. */
193
189
  export declare function addToolTo(store: ToolWrites, repo: string, input: ToolSpec, source: string, by: string, now: Date, options?: {