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/CHANGELOG.md CHANGED
@@ -1,5 +1,46 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.93 — 2026-11-06
4
+
5
+ - **Approve a release with one tap.** When a release is ready for your yes,
6
+ your phone gets one notification ("A release is ready for your approval")
7
+ that opens a single screen: what changed since the last release, the
8
+ GitHub checks, the full terms, and one **Approve release** button. Approve
9
+ and walk away: it deploys only after everything passes, and you hear back
10
+ only if something fails. In the terminal, `toolroll release approve` finds
11
+ the release itself — no task names or codes to copy.
12
+ - **No more "Confirm it stopped".** When a finished build's process record
13
+ is incomplete, Toolroll now checks the computer itself, stops anything left
14
+ over that it can prove is that build's, and carries on. The task says
15
+ "Making sure build #N stopped. This clears on its own." Releases wait
16
+ through it instead of stopping.
17
+ - **Checks record what they saw.** Each finished check now keeps an advisory
18
+ record of the exact code, conditions and failures, groundwork for a list
19
+ of flaky tests and for lessons from fixed failures. It never changes a
20
+ check's result.
21
+
22
+ ## 0.9.92 — 2026-11-05
23
+
24
+ - **Toolroll learns which cheaper models do the job.** Every task now gets a
25
+ result record (see it on the task page, or run `toolroll models results`).
26
+ Once a cheaper model has done well on a project's small or routine tasks,
27
+ Settings → Models suggests it, with the evidence. Accepting needs your
28
+ password and only affects that project's eligible tasks; if the cheaper
29
+ model then does badly, Toolroll switches back on its own and tells you.
30
+ Approvals show a line saying why a model was picked. Also in the terminal:
31
+ `toolroll models suggestions`, `accept`, `dismiss`, `turn-off`.
32
+ - **Try a cheaper model on the next 10 tasks.** When a cheaper model has no
33
+ track record yet, Settings → Models offers a trial. Each trial task says
34
+ "trial 3 of 10" and must be approved by a person; Stop ends it any time
35
+ (`toolroll models try` / `stop`).
36
+ - **See what it saved.** The Spend page and `toolroll spend` show cost per
37
+ task before and after each rule or trial — observed, not a promise, and
38
+ only once there are enough priced results.
39
+ - **Project memory is changed by a person only.** A personal API token used
40
+ from the remote CLI could record or retire project decisions without a
41
+ password; now it can't, and neither can the lead. Reading memory works as
42
+ before.
43
+
3
44
  ## 0.9.91 — 2026-11-04
4
45
 
5
46
  - **A deploy checks your HTTPS console before it changes anything.** If the
@@ -1,7 +1,15 @@
1
1
  /** SQL over a `task_scope` alias and a repo expression: whether that row's approval authority stands at `now` (an SQL
2
2
  * expression, used exactly three times: the mode and grant clauses, and the release's expiry). It never asks whether the row is approved.
3
3
  * COALESCE: a NULL basis would make the OR chain NULL, and NOT NULL reads as "not dead"; NULL stands on nothing. */
4
- export declare const authorityStandsSql: (scope: string, repo: string, now: string) => string;
4
+ export declare function authorityStandsSql(scope: string, repo: string, now: string): string;
5
+ /**
6
+ * SQL, conjunctive with every alternative above (results routing slice 3): a task whose sealed route tries a cheaper
7
+ * model (its build leg's selection is a trial) stands only on a PERSON's own yes to these exact bytes — the ceremony
8
+ * (`password`) or their paired-chat tap (`chat`), with that person's proof at the seal's instant, and no machine's.
9
+ * A mode, a grant, a schedule, a carry, an automatic seal or the lead never stand for one, whatever else allows them.
10
+ * The route is matched as text, so a route that can't be read as JSON is held to it too (fail closed).
11
+ */
12
+ export declare function trialSealStandsSql(scope: string): string;
5
13
  /**
6
14
  * SQL, conjunctive with every alternative above (easy-start plan C5): a seal a local owner gave (its proof row at the
7
15
  * seal's instant says `local-owner`) stands only while local mode is on, for that owner, at the local generation its
@@ -20,7 +20,8 @@ const PERSON_PROOFS = "('password-now', 'sso-now', 'fresh-password', 'fresh-sso'
20
20
  /** SQL over a `task_scope` alias and a repo expression: whether that row's approval authority stands at `now` (an SQL
21
21
  * expression, used exactly three times: the mode and grant clauses, and the release's expiry). It never asks whether the row is approved.
22
22
  * COALESCE: a NULL basis would make the OR chain NULL, and NOT NULL reads as "not dead"; NULL stands on nothing. */
23
- export const authorityStandsSql = (scope, repo, now) => `COALESCE((((COALESCE(${scope}.approval_basis, '') IN ('password', 'chat') AND NOT ${machineSealSql(scope)}
23
+ export function authorityStandsSql(scope, repo, now) {
24
+ return `COALESCE((((COALESCE(${scope}.approval_basis, '') IN ('password', 'chat') AND NOT ${machineSealSql(scope)}
24
25
  AND (${personSealSql(scope)} OR NOT ${automationSql(scope)} OR ${approverStandsSql(scope, repo)}))
25
26
  OR (${scope}.approval_basis = 'carried' AND ${carrySealSql(scope)})
26
27
  OR (${scope}.approval_basis = 'mode' AND EXISTS (SELECT 1 FROM operating_mode om
@@ -30,7 +31,19 @@ export const authorityStandsSql = (scope, repo, now) => `COALESCE((((COALESCE(${
30
31
  OR (${scope}.approval_basis = 'grant' AND ${grantStandsSql(`${scope}.mode_digest`, now, repo)})
31
32
  OR (${scope}.approval_basis = 'schedule' AND ${approverStandsSql(scope, repo)})
32
33
  OR (${scope}.approval_basis IN ('auto-check', 'auto-green') AND ${autoSealSql(scope, repo)} AND ${approverStandsSql(scope, repo)}))
33
- AND ${releaseStandsSql(scope, now)} AND ${localSealStandsSql(scope, repo)}), 0)`;
34
+ AND ${releaseStandsSql(scope, now)} AND ${localSealStandsSql(scope, repo)}
35
+ AND (instr(COALESCE(${scope}.approved_route_json, ''), '"trial"') = 0 OR ${trialSealStandsSql(scope)})), 0)`;
36
+ }
37
+ /**
38
+ * SQL, conjunctive with every alternative above (results routing slice 3): a task whose sealed route tries a cheaper
39
+ * model (its build leg's selection is a trial) stands only on a PERSON's own yes to these exact bytes — the ceremony
40
+ * (`password`) or their paired-chat tap (`chat`), with that person's proof at the seal's instant, and no machine's.
41
+ * A mode, a grant, a schedule, a carry, an automatic seal or the lead never stand for one, whatever else allows them.
42
+ * The route is matched as text, so a route that can't be read as JSON is held to it too (fail closed).
43
+ */
44
+ export function trialSealStandsSql(scope) {
45
+ return `(COALESCE(${scope}.approval_basis, '') IN ('password', 'chat') AND NOT ${machineSealSql(scope)} AND ${personSealSql(scope)})`;
46
+ }
34
47
  /**
35
48
  * SQL, conjunctive with every alternative above (easy-start plan C5): a seal a local owner gave (its proof row at the
36
49
  * seal's instant says `local-owner`) stands only while local mode is on, for that owner, at the local generation its
@@ -23,7 +23,7 @@ export declare function assignmentStatusBrief(assignment: AssignmentSnapshot): {
23
23
  } | null;
24
24
  runId: number | null;
25
25
  primaryAction: {
26
- code: "answer-agent" | "confirm-stopped" | "inspect-decisions" | "inspect-run" | "inspect-stop" | "inspect-task" | "open-pr" | "reconcile-run" | "sign-in" | import("./dispatch.js").DispatchAction;
26
+ code: "answer-agent" | "inspect-decisions" | "inspect-run" | "inspect-stop" | "inspect-task" | "open-pr" | "recheck-stopped" | "reconcile-run" | "sign-in" | import("./dispatch.js").DispatchAction;
27
27
  label: string;
28
28
  target: import("./work-summary.js").WorkTarget;
29
29
  } | null;
@@ -31,8 +31,8 @@ export function assignmentActionHref(assignment) {
31
31
  if (action.code === 'sign-in')
32
32
  return '/settings/agents#providers';
33
33
  // The confirmation sits on the task page, behind the password.
34
- if (action.code === 'confirm-stopped')
35
- return `/t/${encodeURIComponent(assignment.rootId)}#confirm-stopped`;
34
+ if (action.code === 'recheck-stopped')
35
+ return `/t/${encodeURIComponent(assignment.rootId)}#recheck-stopped`;
36
36
  if (action.code === 'open-pr' && /^https:\/\/github\.com\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+\/pull\/\d+$/.test(assignment.publication?.prUrl ?? ''))
37
37
  return assignment.publication.prUrl;
38
38
  if (runId !== null && (action.code === 'open-result' || action.code === 'open-pr' || action.code === 'retry-review')) {
@@ -252,13 +252,14 @@ export function assignmentOf(store, taskId, now, access, root, memo) {
252
252
  : { code: "inspect-task", label: `Open ${successor}`, target: { taskId: successor, runId: null, decisionId: null }, access: "read", retry: "read-again" };
253
253
  }
254
254
  else if (!live && processFact !== null && result !== null) {
255
- // Toolroll can't confirm the build stopped. A person confirms it when
256
- // nothing of it may still run; otherwise the work waits, needing no one.
255
+ // Toolroll hasn't yet proven the build stopped. It proves that itself
256
+ // (stop-proof.ts), so the work waits, needing no one; only when this
257
+ // computer can't look is it held, offering Check again — never a vouch.
257
258
  const process = processNeedOf(processFact);
258
259
  if ("need" in process) {
259
260
  state = "needs-decision";
260
261
  detail = NEEDS[process.need].sentence({ build: process.build });
261
- primaryAction = { code: "confirm-stopped", label: NEEDS[process.need].action.label, target: { taskId: current.id, runId: process.build, decisionId: null },
262
+ primaryAction = { code: "recheck-stopped", label: NEEDS[process.need].action.label, target: { taskId: current.id, runId: process.build, decisionId: null },
262
263
  access: access.principal === "operator" ? "operator-control" : "operator-handoff", retry: "refresh-before-acting" };
263
264
  need = { key: process.need, build: process.build };
264
265
  }
@@ -480,6 +481,11 @@ function acknowledgeCurrent(store, current, receiptDigest, actor, now, root, acc
480
481
  // The nightly check of main is a check only (release-once.md, part 3): nobody completes it.
481
482
  if (store.handle.prepare("SELECT 1 AS hit FROM nightly_check WHERE task = ?").get(current.receipt.taskId) !== undefined)
482
483
  return { ok: false, reason: "check-only", message: "The nightly check of main is only a check; it isn't completed." };
484
+ // Toolroll is still proving its build stopped (stop-proof.ts): a wait that clears on its own, said as one, so a
485
+ // release waits for it instead of stopping.
486
+ if (current.state !== "ready-to-check" && current.state !== "complete" && current.need != null && "wait" in current.need && STOP_WAITS.has(current.need.wait)) {
487
+ return { ok: false, reason: "stopping", message: `Toolroll is still making sure ${current.need.build === null ? "the build" : `build #${current.need.build}`} stopped. This clears on its own.` };
488
+ }
483
489
  if (current.state !== "ready-to-check" && current.state !== "complete")
484
490
  return { ok: false, reason: "not-ready", message: "This assignment still has unresolved execution, scope or decisions." };
485
491
  // A release task completes only while its release approval stands for it (release-once.md, liveReleaseOf).
@@ -674,3 +680,5 @@ export function assignmentUpdates(store, now, access, query, root) {
674
680
  });
675
681
  return { events, nextCursor: page.length === 0 ? after : Number(page.at(-1)["id"]), hasMore: rows.length > limit };
676
682
  }
683
+ /** The waits of a build Toolroll is still proving stopped by itself (needs-you.ts processNeedOf). */
684
+ const STOP_WAITS = new Set(["build-finishing", "build-checking", "build-stopping"]);