toolroll 0.9.91 → 0.9.93

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 (124) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/approval-authority.d.ts +9 -1
  3. package/dist/approval-authority.js +15 -2
  4. package/dist/assignment-status.d.ts +1 -1
  5. package/dist/assignment-ui.js +2 -2
  6. package/dist/assignment.js +11 -3
  7. package/dist/browser/workspace.css +1 -1
  8. package/dist/browser/workspace.js +30 -30
  9. package/dist/browser-crew.js +2 -2
  10. package/dist/browser-workspace.d.ts +3 -5
  11. package/dist/build-review.js +2 -0
  12. package/dist/builder.js +21 -2
  13. package/dist/chat-actions.d.ts +11 -3
  14. package/dist/chat-actions.js +47 -2
  15. package/dist/chat-decide.js +2 -0
  16. package/dist/chat-review.d.ts +2 -1
  17. package/dist/chat-review.js +2 -2
  18. package/dist/check-facts.d.ts +124 -0
  19. package/dist/check-facts.js +272 -0
  20. package/dist/check-report.d.ts +5 -0
  21. package/dist/check-report.js +4 -3
  22. package/dist/cli.js +5 -0
  23. package/dist/contracts/chat-actions.d.ts +10 -0
  24. package/dist/contracts/chat-actions.js +4 -0
  25. package/dist/contracts/console-api.d.ts +3 -2
  26. package/dist/contracts/console-api.js +5 -2
  27. package/dist/contracts/release-check.d.ts +25 -1
  28. package/dist/contracts/release-check.js +19 -0
  29. package/dist/contracts/route-results.d.ts +121 -0
  30. package/dist/contracts/route-results.js +71 -0
  31. package/dist/contracts/route.d.ts +343 -5
  32. package/dist/contracts/route.js +36 -7
  33. package/dist/contracts/store-json.d.ts +2 -0
  34. package/dist/desktop-update.d.ts +2 -2
  35. package/dist/desktop-update.js +13 -9
  36. package/dist/dispose.js +6 -0
  37. package/dist/eval-hooks.d.ts +55 -0
  38. package/dist/eval-hooks.js +227 -4
  39. package/dist/flaky.d.ts +17 -0
  40. package/dist/flaky.js +9 -0
  41. package/dist/flow-engine.js +1 -1
  42. package/dist/flow-pull-request.js +1 -1
  43. package/dist/lead-doors.js +4 -2
  44. package/dist/lead-tools.js +10 -2
  45. package/dist/local-owner.d.ts +1 -1
  46. package/dist/local-owner.js +11 -0
  47. package/dist/memory-cli.d.ts +3 -0
  48. package/dist/memory-cli.js +3 -3
  49. package/dist/memory-pass.js +5 -1
  50. package/dist/model-choices.d.ts +7 -3
  51. package/dist/model-choices.js +26 -11
  52. package/dist/models-cli.d.ts +205 -0
  53. package/dist/models-cli.js +143 -4
  54. package/dist/models-ui.d.ts +57 -0
  55. package/dist/models-ui.js +44 -1
  56. package/dist/needs-you.d.ts +7 -7
  57. package/dist/needs-you.js +12 -10
  58. package/dist/operate-remote-arguments.js +4 -4
  59. package/dist/operate-remote.js +1 -1
  60. package/dist/operate.js +191 -23
  61. package/dist/phase-routing.d.ts +82 -2
  62. package/dist/phase-routing.js +132 -18
  63. package/dist/project-delete.d.ts +4 -0
  64. package/dist/project-delete.js +16 -4
  65. package/dist/project-memory.d.ts +8 -0
  66. package/dist/project-memory.js +20 -1
  67. package/dist/pull-request-flow.js +1 -1
  68. package/dist/push.d.ts +10 -3
  69. package/dist/push.js +22 -6
  70. package/dist/release-adapters.js +11 -1
  71. package/dist/release-approval.d.ts +191 -0
  72. package/dist/release-approval.js +382 -0
  73. package/dist/release-auto.js +22 -4
  74. package/dist/release-card.d.ts +73 -4
  75. package/dist/release-card.js +168 -23
  76. package/dist/release-cli.js +9 -1
  77. package/dist/release.d.ts +30 -0
  78. package/dist/release.js +42 -3
  79. package/dist/result-actions.d.ts +4 -0
  80. package/dist/result-actions.js +3 -0
  81. package/dist/result-acts.d.ts +3 -3
  82. package/dist/result-acts.js +1 -1
  83. package/dist/route-hooks.d.ts +54 -0
  84. package/dist/route-hooks.js +179 -0
  85. package/dist/route-results.d.ts +70 -0
  86. package/dist/route-results.js +340 -0
  87. package/dist/route-rules.d.ts +324 -0
  88. package/dist/route-rules.js +781 -0
  89. package/dist/route-savings.d.ts +136 -0
  90. package/dist/route-savings.js +193 -0
  91. package/dist/route-store.d.ts +300 -0
  92. package/dist/route-store.js +598 -0
  93. package/dist/scope.d.ts +3 -1
  94. package/dist/scope.js +4 -1
  95. package/dist/server/guards.js +1 -0
  96. package/dist/server/pages-assets.js +1 -1
  97. package/dist/server/render-chat.js +2 -2
  98. package/dist/server/render-release.d.ts +17 -0
  99. package/dist/server/render-release.js +84 -0
  100. package/dist/server/render-results.d.ts +0 -2
  101. package/dist/server/render-results.js +4 -6
  102. package/dist/server/render-tasks.d.ts +7 -0
  103. package/dist/server/render-tasks.js +16 -3
  104. package/dist/server/route-table.d.ts +1 -1
  105. package/dist/server/route-table.js +19 -1
  106. package/dist/server/settings.d.ts +2 -1
  107. package/dist/server/settings.js +87 -1
  108. package/dist/server/task-views.js +6 -0
  109. package/dist/server/tasks.d.ts +15 -0
  110. package/dist/server/tasks.js +105 -20
  111. package/dist/settings-proofs.js +2 -0
  112. package/dist/spend-ui.d.ts +11 -0
  113. package/dist/spend-ui.js +49 -2
  114. package/dist/stop-proof.d.ts +67 -0
  115. package/dist/stop-proof.js +293 -0
  116. package/dist/store.d.ts +106 -31
  117. package/dist/store.js +653 -128
  118. package/dist/surface.js +17 -5
  119. package/dist/task-outcome-cli.js +24 -2
  120. package/dist/toolroll-update-ui.js +2 -2
  121. package/dist/toolroll-update.js +4 -2
  122. package/dist/work-index.js +7 -9
  123. package/dist/work-summary.d.ts +2 -2
  124. package/package.json +1 -1
package/dist/surface.js CHANGED
@@ -116,12 +116,12 @@ const GUIDE_ROWS = [
116
116
  notableReasons: ["usage", "ambiguous", "drift", "timeout", "check-failed", "ci-failed", "tree-mismatch", "conflict", "mixed-runtime", "gate-held", "after-swap"] },
117
117
  operator("demo", "a seeded throwaway sandbox"),
118
118
  ...KNOWLEDGE_DESCRIPTORS.map(spec => ({ invocation: `knowledge ${spec.action}`, synopsis: spec.synopsis, audience: "audience" in spec ? spec.audience : "agent", agentMayInvoke: !("audience" in spec), mutation: spec.mutation, flags: spec.flags, ...(spec.takesQuery ? { positionals: [{ name: "query", required: true, meaning: "search text or source file for impact" }] } : {}) })),
119
- ...MEMORY_DESCRIPTORS.map(spec => ({ invocation: `memory ${spec.action}`, synopsis: spec.synopsis, audience: "agent", agentMayInvoke: true, mutation: spec.mutation, flags: spec.flags, ...(spec.takesQuery ? { positionals: [{ name: "query", required: true, meaning: "search text, a decision id, or the decision sentence" }] } : {}) })),
119
+ ...MEMORY_DESCRIPTORS.map(spec => ({ invocation: `memory ${spec.action}`, synopsis: spec.synopsis, audience: "audience" in spec ? spec.audience : "agent", agentMayInvoke: !("audience" in spec), mutation: spec.mutation, flags: spec.flags, ...(spec.takesQuery ? { positionals: [{ name: "query", required: true, meaning: "search text, a decision id, or the decision sentence" }] } : {}) })),
120
120
  // Flow writes carry an approver's credential: an agent runs them only as the person asked, after showing the preview.
121
121
  // Letting a flow run on its own raises authority: the owner's own password ceremony, never an agent's act.
122
122
  ...FLOWS_DESCRIPTORS.map(spec => ({ invocation: `flows ${spec.action}`, synopsis: spec.synopsis, audience: "audience" in spec ? spec.audience : "agent", agentMayInvoke: !("audience" in spec), mutation: spec.mutation, flags: spec.flags,
123
123
  ...("positionals" in spec ? { positionals: spec.positionals.map(name => ({ name, required: true, meaning: name === "flow" ? "a flow id from flows list" : name === "card" ? "a card id from flows show" : name === "on|off" ? "on signs the grant, off revokes it" : "a trigger id from flows show, or for trigger add its settings as JSON, a JSON file, or -" })) } : {}) })),
124
- ...MODELS_DESCRIPTORS.map(spec => ({ invocation: `models ${spec.action}`, synopsis: spec.synopsis, audience: "agent", agentMayInvoke: true, mutation: spec.mutation, flags: spec.flags, ...(spec.takesQuery ? { positionals: [{ name: "target", required: true, meaning: "the CLI to update, or on/off" }] } : {}) })),
124
+ ...MODELS_DESCRIPTORS.map(spec => ({ invocation: `models ${spec.action}`, synopsis: spec.synopsis, audience: "agent", agentMayInvoke: true, mutation: spec.mutation, flags: spec.flags, ...(spec.takesQuery ? { positionals: [{ name: "target", required: true, meaning: "positional" in spec ? spec.positional : "the CLI to update, or on/off" }] } : {}) })),
125
125
  // ---- the queue (agent surface) ----
126
126
  { invocation: "status", synopsis: "running work, queued reasons, results to review, the latest release check and plan windows", audience: "agent", agentMayInvoke: true, mutation: "none", flags: [jsonFlag, dbFlag] },
127
127
  { invocation: 'health', synopsis: 'recent server latency, event-loop delay, SQLite writes and live streams from the saved connection; instance operators only', audience: 'agent', agentMayInvoke: true, mutation: 'none', flags: [jsonFlag, { name: 'profile', takesValue: true, meaning: 'saved server connection' }] },
@@ -315,7 +315,7 @@ const GUIDE_ROWS = [
315
315
  { name: "days", takesValue: true, meaning: "also finished tasks untouched this many days (1–365, default 14)" },
316
316
  { name: "except", takesValue: true, meaning: "comma-separated tasks from the list to leave out" }],
317
317
  notableReasons: ["usage", "not-an-approver", "empty"] },
318
- operator("run settle", "last resort for a finished run whose process witness can't be proven either way: records your --why in the ledger; refuses while any of the run's processes is alive"),
318
+ operator("run settle", "have Toolroll check again whether a finished run stopped: it looks at this computer's processes itself, stops only leftovers proven the run's, and settles the run when none is left (nobody vouches; --why is ignored)"),
319
319
  operator("task resume", "resume the exact stopped attempt (--run <id>): refuses until it is quiescent, lifts only that stop's hold, approves nothing — the next pass re-proves the scope and inherits the draft with fresh proof"),
320
320
  // ---- leases (runner surface) ----
321
321
  { invocation: "claim", synopsis: "take one ready, approved task; answers with a lease", audience: "agent", agentMayInvoke: true, mutation: "keyed",
@@ -460,6 +460,11 @@ const REMOTE_NO = [
460
460
  "serve", "serve check-public", "watch", "up", "onboard", "daemon", "background on", "background off", "background show", "stop", "open", "welcome", "password set", "doctor", "bridge", "setup show", "setup clear",
461
461
  // Provider keys and model tools on this machine.
462
462
  "providers", "keys status", "keys set", "keys clear", "keys verify", "keys auth", "models check", "models update", "models watch",
463
+ // Result records read with this machine's sign-in: project access, never a token's narrower scope.
464
+ "models results",
465
+ // Earned rules (results routing): the project's own results, and its rules' doors, with this machine's sign-in; a
466
+ // remote token never changes which models run (the rule door refuses it in the write too).
467
+ "models suggestions", "models accept", "models dismiss", "models turn-off", "models keep", "models try", "models stop",
463
468
  // Publication and release.
464
469
  "publish", "publish status", "publish merge", "publish refire", "publish unblock", "publish rearm", "publish grant", "publish revoke",
465
470
  // A repository's authority grants on this machine.
@@ -474,7 +479,9 @@ const REMOTE_NO = [
474
479
  const REMOTE_STEP_UP = [
475
480
  "proposals", "decide", "task regate", "task approve", "task accept", "task merge", "run settle",
476
481
  // Putting tasks away and restoring them: a person's act, in the console or on the server's machine.
477
- "task archive", "task restore", "task tidy", "knowledge apply", "memory apply",
482
+ "task archive", "task restore", "task tidy", "knowledge apply",
483
+ // Changing project memory: a person's own password, never a token (the lead only proposes it in chat).
484
+ "memory decide", "memory retire", "memory apply",
478
485
  "flows card approve", "flows card send-back", "flows script approve", "flows unattended",
479
486
  "approver list", "approver add", "people list", "people invite", "people projects", "people revoke",
480
487
  // A person's own API tokens: a password step-up on the server's machine, or the console. Never with a token.
@@ -491,7 +498,7 @@ const REMOTE_YES = [
491
498
  "config show", "verify show", "intake show", "intake run", "intake preview", "intake pr-comments", "template list", "template show",
492
499
  "webhook status", "review show", "chat-approval show", "chat-approval off",
493
500
  "knowledge search", "knowledge impact", "knowledge refresh",
494
- "memory search", "memory decisions", "memory show", "memory decide", "memory retire", "memory review", "memory status",
501
+ "memory search", "memory decisions", "memory show", "memory review", "memory status",
495
502
  "flows list", "flows show", "flows export", "flows create", "flows edit", "flows trigger add", "flows trigger pause", "flows trigger resume",
496
503
  "flows trigger remove", "flows card add", "flows archive",
497
504
  "models status", "models list",
@@ -530,6 +537,11 @@ const LOCAL_ALLOW = {
530
537
  "task archive": "archive-task",
531
538
  "task restore": "archive-task",
532
539
  "repos add": "add-project",
540
+ // Results routing: lowering only (route-store.ts proveRuleDoor checks the act is exactly this one).
541
+ "models suggestions": "read",
542
+ "models turn-off": "routing-turn-off",
543
+ "models dismiss": "dismiss-suggestion",
544
+ "models stop": "trial-stop",
533
545
  };
534
546
  /** Every guide row's local-owner mark, by invocation: `allow` with its act, else `deny`. Exported for the inventory test. */
535
547
  export const LOCAL_POLICY = new Map(GUIDE_ROWS.map(row => [row.invocation,
@@ -4,11 +4,13 @@ import { assignmentOf, checkAssignmentAsOperator } from './assignment.js';
4
4
  import { assignmentForCoordinator, coordinatorToken } from './assignment-adapters.js';
5
5
  import { authenticateCoordinator } from './coordinator.js';
6
6
  import { reproveApprover } from './principal.js';
7
- import { authenticateApprover, personProof } from './scope.js';
7
+ import { authenticateApprover, machineActing, personProof } from './scope.js';
8
+ import { currentActor } from './actor.js';
8
9
  import { requestResultChanges } from './result-actions.js';
9
10
  import { commentSourceKey, revisionSourceOf } from './result-review.js';
10
11
  import { envelopeJson } from './envelope.js';
11
12
  import { completeAndOpenPullRequest, mergeAsPerson, MERGE_NEEDS_PERSON, newestPullRequestOf, publishingOf, pullRequestViewOf } from './pull-request-flow.js';
13
+ import { proveRunStopped } from './stop-proof.js';
12
14
  export async function runTaskOutcomeCommand(action, positional, flags, context) {
13
15
  const command = `task ${action}`;
14
16
  const emit = (result, lines) => {
@@ -35,6 +37,10 @@ export async function runTaskOutcomeCommand(action, positional, flags, context)
35
37
  if (credential.token === null && operator === null)
36
38
  return fail('unauthenticated', 'Sign in with up, or supply --as and --token. Agents can use --token-file or --token-env with a scoped credential.');
37
39
  const { store, now, evidenceRoot } = context;
40
+ // A result whose build Toolroll hasn't yet proven stopped: it looks now (stop-proof.ts), so completing never waits
41
+ // on a person to confirm it, only on Toolroll's own check (a refusal says "stopping" until it lands).
42
+ if (action === 'complete')
43
+ await proveTaskBuildsStopped(store, task, () => now);
38
44
  return store.transact(() => {
39
45
  const coordinator = credential.token === null ? null : authenticateCoordinator(store, credential.token);
40
46
  if (coordinator !== null && !coordinator.ok)
@@ -110,7 +116,9 @@ export async function runTaskOutcomeCommand(action, positional, flags, context)
110
116
  // The shared action returns exact replays before stale-family checks. An
111
117
  // older result cannot create another child once a newer revision is current.
112
118
  const revised = requestResultChanges(store, evidenceRoot, { run: run.id, source, actor, repos,
113
- batch: '', note: feedback, path: '', line: '', request: key, allowMode: operator !== null }, now);
119
+ batch: '', note: feedback, path: '', line: '', request: key, allowMode: operator !== null,
120
+ // A verified approver's own words; a scoped credential or the lead's token is the lead; a remote API run a machine.
121
+ origin: coordinator !== null || currentActor()?.lead === true ? 'lead' : machineActing() ? 'machine' : 'person' }, now);
114
122
  if (!revised.ok)
115
123
  return fail(revised.status === 400 ? 'usage' : 'stale-result', revised.message);
116
124
  const scope = store.getScope(revised.id);
@@ -169,3 +177,17 @@ export function pullRequestLines(view) {
169
177
  return [];
170
178
  return [` pull request: ${view.prUrl ?? 'opening'} · ${view.label}`, ` ${view.detail}`, ...(view.mergeCommit === null ? [] : [` merge commit: ${view.mergeCommit}`])];
171
179
  }
180
+ /** Toolroll's own stop proof for every finished build of a task's family whose stop it can prove here. Best effort: a
181
+ * failure leaves the completion's own refusal to say what is still unproven. */
182
+ async function proveTaskBuildsStopped(store, task, clock) {
183
+ try {
184
+ const family = store.taskFamilyOf(task, null, true);
185
+ const refs = family === null ? [store.lookupRef(task)?.id].filter((one) => one !== undefined) : family.versions.map(version => version.refId);
186
+ for (const ref of refs)
187
+ for (const run of store.runsFor(ref)) {
188
+ if (run.outcome !== null && store.stopQuiescenceFact(run.id)?.cause !== undefined)
189
+ await proveRunStopped(store, run.id, clock);
190
+ }
191
+ }
192
+ catch { /* the completion below refuses with what is still unproven */ }
193
+ }
@@ -24,7 +24,7 @@ export function updateStepsHtml(j, running, options = {}) {
24
24
  const items = UPDATE_STEPS.map((step, i) => {
25
25
  const state = j.phase === "complete" || i < last ? "done" : i > last ? "waiting" : failed ? "failed" : finished ? "waiting" : "now";
26
26
  // Only where it adds something: which work it waits for, or what went wrong.
27
- const detail = state === "now" && step === "draining" && j.waiting ? html `${j.waiting.on}.${j.waiting.action ? html ` If nothing of it is running, run <code class="k-mono">${j.waiting.action}</code>.` : ""}`
27
+ const detail = state === "now" && step === "draining" && j.waiting ? html `${j.waiting.on}.${j.waiting.action ? html ` To have Toolroll check again, run <code class="k-mono">${j.waiting.action}</code>.` : ""}`
28
28
  : (state === "now" && step === "draining") || (state === "failed" && j.phase === "rolling-back") ? html `${j.detail}` : "";
29
29
  return settingsRecord({ title: STEP_WORDS[step], sub: detail, when: STEP_CHIP[state], attrs: { "data-step": step, "data-state": state } });
30
30
  });
@@ -43,7 +43,7 @@ function outcomeHtml(j) {
43
43
  // Stopped on a finished run it can't show has ended: what is in the way and the one command, nothing more.
44
44
  if (j.phase === "refused" && j.waiting) {
45
45
  return settingsSection({ id: "update-outcome", title: `Update to ${j.to.version} stopped: run #${j.waiting.run} is in the way`,
46
- body: html `<div data-update-outcome="stopped">${infoLine(`${j.waiting.on}. New work resumed.`)}${j.waiting.action ? note(html `If nothing of it is running, run <code class="k-mono">${j.waiting.action}</code>, then update again.`) : note("Update again once it has stopped.")}</div>` });
46
+ body: html `<div data-update-outcome="stopped">${infoLine(`${j.waiting.on}. New work resumed.`)}${j.waiting.action ? note(html `To have Toolroll check again, run <code class="k-mono">${j.waiting.action}</code>, then update again.`) : note("Update again once it has stopped.")}</div>` });
47
47
  }
48
48
  const title = j.phase === "refused" ? `Didn't update to ${j.to.version}` : j.phase === "restored" ? `Update to ${j.to.version} didn't finish` : "The update needs attention";
49
49
  return settingsSection({ id: "update-outcome", title, state: html ` ${chip("Failed", "failed")}`,
@@ -39,7 +39,7 @@ import { installLaunchdService, launchdPlist, stopLaunchdService, writeFileDurab
39
39
  import { LAPTOP_LABEL_DARWIN, LAPTOP_LABEL_LINUX, unitFlag } from "./laptop-service.js";
40
40
  import { processGone, readRunnerProcessRecord, realProcessProbe, recordStandsFor, RUNNER_PROCESS_DIR } from "./runner-process.js";
41
41
  import { NAME } from "./names.js";
42
- import { orphanKey, updateSafeSchema, V117_RENAMED_COLUMNS, V117_RENAMED_TABLES, V118_MERGED_TABLES, V119_ADDED_COLUMNS, V120_ADDED_COLUMNS, V121_ADDED_COLUMNS, V122_ADDED_COLUMNS, V123_ADDED_COLUMNS, V125_ADDED_COLUMNS, V126_ADDED_COLUMNS, V128_ADDED_COLUMNS, V129_ADDED_COLUMNS, V134_ADDED_COLUMNS, V135_ADDED_COLUMNS, V138_ADDED_COLUMNS, V143_ADDED_COLUMNS, V147_ADDED_COLUMNS } from "./store.js";
42
+ import { orphanKey, updateSafeSchema, V117_RENAMED_COLUMNS, V117_RENAMED_TABLES, V118_MERGED_TABLES, V119_ADDED_COLUMNS, V120_ADDED_COLUMNS, V121_ADDED_COLUMNS, V122_ADDED_COLUMNS, V123_ADDED_COLUMNS, V125_ADDED_COLUMNS, V126_ADDED_COLUMNS, V128_ADDED_COLUMNS, V129_ADDED_COLUMNS, V134_ADDED_COLUMNS, V135_ADDED_COLUMNS, V138_ADDED_COLUMNS, V143_ADDED_COLUMNS, V147_ADDED_COLUMNS, V148_ADDED_COLUMNS } from "./store.js";
43
43
  import { isNewer, REGISTRY } from "./releases.js";
44
44
  import { markNeverIndex } from "./never-index.js";
45
45
  import { readRuntimeUpdateJournal, RUNTIME_PHASES, RUNTIME_UPDATE_STEPS, stagedStartedAt, updaterStartingOf } from "./contracts/update-journal.js";
@@ -413,7 +413,9 @@ const HISTORY_RULES = {
413
413
  // v128: a lead message's pending flag reads waiting exactly for a team queue's unadmitted operator message.
414
414
  // v147: an account's password flag reads set (1), a browser session reads full with no local binding or key, and the
415
415
  // console record reads no key epoch.
416
- ...Object.fromEntries(Object.entries(addedColumns(V119_ADDED_COLUMNS, V120_ADDED_COLUMNS, V121_ADDED_COLUMNS, V122_ADDED_COLUMNS, V123_ADDED_COLUMNS, V125_ADDED_COLUMNS, V126_ADDED_COLUMNS, V128_ADDED_COLUMNS, V129_ADDED_COLUMNS, V134_ADDED_COLUMNS, V135_ADDED_COLUMNS, V138_ADDED_COLUMNS, V143_ADDED_COLUMNS, V147_ADDED_COLUMNS))
416
+ // v148: a run's execution class and a revision's origin read none (NULL), and a project's suggestion switch reads on.
417
+ // v149 adds tables only (the learning-loops records): no saved row changes, so no rule is needed for it.
418
+ ...Object.fromEntries(Object.entries(addedColumns(V119_ADDED_COLUMNS, V120_ADDED_COLUMNS, V121_ADDED_COLUMNS, V122_ADDED_COLUMNS, V123_ADDED_COLUMNS, V125_ADDED_COLUMNS, V126_ADDED_COLUMNS, V128_ADDED_COLUMNS, V129_ADDED_COLUMNS, V134_ADDED_COLUMNS, V135_ADDED_COLUMNS, V138_ADDED_COLUMNS, V143_ADDED_COLUMNS, V147_ADDED_COLUMNS, V148_ADDED_COLUMNS))
417
419
  .map(([table, rule]) => [table, { ...EARLIER_RULES[table], ...rule }])),
418
420
  };
419
421
  /** The tables a migration may remove whole (v115's removed features). */
@@ -41,7 +41,7 @@ const AGENT_ASK = `(SELECT q.id FROM agent_question q JOIN run qr ON qr.id=q.run
41
41
  const GATE_PAUSE_ON_TASK = pauseOnTaskProviders('p', 'c.ref_id');
42
42
  /** Index codes that wait on a person, as their need (needs-you.ts); dispatch codes come from task-status's own table. */
43
43
  const INDEX_NEED = {
44
- 'earlier-active': 'earlier-version', 'process-needs-attention': 'confirm-stopped', 'invalid-scope': 'define-task', 'decision-queue': 'questions',
44
+ 'earlier-active': 'earlier-version', 'process-needs-attention': 'stop-unproven', 'invalid-scope': 'define-task', 'decision-queue': 'questions',
45
45
  };
46
46
  /** Every index code that can wait on a person. */
47
47
  const PERSON_CODES = ['history-problem', 'earlier-active', 'ready-to-check', 'waiting-decision', 'decision-queue', 'result-needs-attention',
@@ -572,7 +572,7 @@ function itemOf(row, principal, probe, leadName, readiness, readable = false) {
572
572
  const code = String(row['code']), id = String(row['id']);
573
573
  const custody = code === 'process-needs-attention' || (code === 'result-needs-attention' && n(row, 'custody_unresolved') === 1);
574
574
  const process = custody && probe !== undefined ? processNeedOf(probe(id)) : null;
575
- const waiting = process !== null && 'wait' in process;
575
+ const waiting = (process !== null && 'wait' in process) || (custody && probe === undefined);
576
576
  const need = n(row, 'needs') === 1 && !waiting, running = n(row, 'family_running') === 1;
577
577
  // The lead's claim (lead-voice.ts): the row still reads its own stage, worded "<name> is on it".
578
578
  const lead = n(row, 'lead_claim') === 1 ? 'on-it' : n(row, 'lead_claim') === 2 ? 'lapsed' : null;
@@ -647,14 +647,11 @@ function itemOf(row, principal, probe, leadName, readiness, readable = false) {
647
647
  actionCode = 'open-result';
648
648
  actionLabel = 'Open result';
649
649
  }
650
+ // Toolroll proves a build stopped itself; only a stop it can't prove from here offers "Check again", never a vouch.
650
651
  if (process !== null && 'need' in process) {
651
- actionCode = 'confirm-stopped';
652
+ actionCode = 'recheck-stopped';
652
653
  actionLabel = NEEDS[process.need].action.label;
653
654
  }
654
- else if (custody && probe === undefined) {
655
- actionCode = 'confirm-stopped';
656
- actionLabel = NEEDS['confirm-stopped'].action.label;
657
- }
658
655
  if (code === 'signed-out') {
659
656
  actionCode = 'sign-in';
660
657
  actionLabel = NEEDS['sign-in'].action.label;
@@ -668,7 +665,7 @@ function itemOf(row, principal, probe, leadName, readiness, readable = false) {
668
665
  target: { taskId: successor ?? (code === 'earlier-active' ? s(row, 'earlier_id') ?? id : actionCode === 'answer-decision' ? s(row, 'question_task') ?? id : id),
669
666
  runId: actionCode === 'answer-decision' ? n(row, 'question_run') || null : actionCode === 'answer-agent' ? n(row, 'agent_ask_run') || null : actionCode === 'inspect-run' && code === 'running' ? n(row, 'live_run') || null : actionCode === 'open-result' || actionCode === 'inspect-run' ? n(row, 'result_id') || null
670
667
  : actionCode === 'reconcile-run' ? n(row, 'unfinished') || null : ['resume-run', 'inspect-stop'].includes(actionCode) ? n(row, 'stop_run') || null
671
- : actionCode === 'confirm-stopped' ? process?.build ?? (n(row, 'result_id') || null) : null,
668
+ : actionCode === 'recheck-stopped' ? process?.build ?? (n(row, 'result_id') || null) : null,
672
669
  decisionId: actionCode === 'answer-decision' ? n(row, 'question_id') || null : null },
673
670
  access: read ? 'read' : principal === 'operator' ? 'operator-control' : ['answer-decision', 'answer-agent', 'write-scope', 'unhold'].includes(actionCode) ? 'proposal-only' : 'operator-handoff',
674
671
  retry: actionCode === 'reconcile-run' ? 'reconcile-before-retry' : read ? 'read-again' : 'refresh-before-acting' };
@@ -676,7 +673,8 @@ function itemOf(row, principal, probe, leadName, readiness, readable = false) {
676
673
  const coded = stageOfCode(code, { needsPerson: personNeed, planning: s(row, 'live_role') === 'planner', operatorHold: s(row, 'hold_kind') === 'operator' });
677
674
  const reading = process !== null
678
675
  ? 'wait' in process ? { stage: 'waiting', wait: process.wait } : { stage: 'needs-you', need: process.need }
679
- : custody ? { stage: 'needs-you', need: 'confirm-stopped' }
676
+ // Not read here (no process probe): Toolroll's own stop check settles it, so it waits on no one.
677
+ : custody ? { stage: 'waiting', wait: 'build-checking' }
680
678
  : coded.stage === 'needs-you' && (coded.need === undefined || coded.need === 'other') && INDEX_NEED[code] !== undefined ? { stage: 'needs-you', need: INDEX_NEED[code] } : coded;
681
679
  const checkStatus = s(row, 'check_status'), verdict = s(row, 'proof_verdict');
682
680
  const finished = reading.stage === 'finished' || reading.stage === 'complete';
@@ -8,9 +8,9 @@ export type WorkTarget = {
8
8
  decisionId: number | null;
9
9
  };
10
10
  export type WorkAction = {
11
- /** `confirm-stopped`: an approver confirms a finished build stopped (behind the password);
11
+ /** `recheck-stopped`: Toolroll looks again whether a finished build stopped (nobody vouches for it);
12
12
  * `sign-in`: the agent's sign-in, in Settings; `answer-agent`: Reply to a running agent's question on the Agents board. */
13
- code: DispatchAction | "inspect-task" | "inspect-run" | "inspect-stop" | "inspect-decisions" | "reconcile-run" | "open-pr" | "confirm-stopped" | "sign-in" | "answer-agent";
13
+ code: DispatchAction | "inspect-task" | "inspect-run" | "inspect-stop" | "inspect-decisions" | "reconcile-run" | "open-pr" | "recheck-stopped" | "sign-in" | "answer-agent";
14
14
  label: string;
15
15
  target: WorkTarget;
16
16
  access: "read" | "operator-control" | "proposal-only" | "operator-handoff";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolroll",
3
- "version": "0.9.91",
3
+ "version": "0.9.93",
4
4
  "description": "A control plane for unattended coding agents — queue tasks, walk away, come back to pull requests. Agents build in leased worktrees, never touch your default branch, and interrupt you only for decisions that need a human.",
5
5
  "directories": {
6
6
  "doc": "docs"