toolroll 0.9.74 → 0.9.76

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 (119) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/dist/agent-fence.js +8 -0
  3. package/dist/approval-authority.js +6 -2
  4. package/dist/approval-rules-ui.d.ts +11 -0
  5. package/dist/approval-rules-ui.js +13 -1
  6. package/dist/assignment.d.ts +3 -0
  7. package/dist/assignment.js +27 -8
  8. package/dist/batch-checks.js +3 -1
  9. package/dist/browser/workspace.js +27 -27
  10. package/dist/browser-workspace.d.ts +1 -1
  11. package/dist/builder.js +1 -1
  12. package/dist/chat-acceptance.d.ts +4 -1
  13. package/dist/chat-acceptance.js +13 -3
  14. package/dist/cli.js +1 -1
  15. package/dist/coding-handoff.js +4 -3
  16. package/dist/coding-ui.js +2 -2
  17. package/dist/coding-workspace.js +23 -9
  18. package/dist/contracts/console-api.d.ts +6 -1
  19. package/dist/contracts/console-api.js +11 -0
  20. package/dist/control-ui.d.ts +3 -2
  21. package/dist/control-ui.js +7 -2
  22. package/dist/demo.js +5 -3
  23. package/dist/deploy-gate.d.ts +3 -0
  24. package/dist/deploy-gate.js +3 -2
  25. package/dist/desktop-update.d.ts +4 -1
  26. package/dist/desktop-update.js +43 -9
  27. package/dist/discover.js +6 -1
  28. package/dist/first-run.d.ts +8 -5
  29. package/dist/first-run.js +42 -7
  30. package/dist/flow-card-task.js +3 -2
  31. package/dist/flow-unattended.d.ts +2 -0
  32. package/dist/flow-unattended.js +12 -1
  33. package/dist/git-incident.d.ts +49 -0
  34. package/dist/git-incident.js +69 -0
  35. package/dist/git-views.d.ts +20 -0
  36. package/dist/git-views.js +120 -0
  37. package/dist/grant.js +3 -1
  38. package/dist/integrations.d.ts +4 -3
  39. package/dist/integrations.js +5 -6
  40. package/dist/lead-contract.d.ts +1 -1
  41. package/dist/lead-contract.js +2 -2
  42. package/dist/lead-follow.js +3 -1
  43. package/dist/openrouter-models.d.ts +5 -2
  44. package/dist/openrouter-models.js +7 -4
  45. package/dist/operate.js +9 -1
  46. package/dist/planner.js +5 -1
  47. package/dist/project-knowledge.js +1 -1
  48. package/dist/project-learning.js +65 -7
  49. package/dist/project-tools.js +19 -1
  50. package/dist/pull-request-flow.js +4 -0
  51. package/dist/pull-request-ui.d.ts +1 -1
  52. package/dist/pull-request-ui.js +5 -1
  53. package/dist/push.d.ts +2 -0
  54. package/dist/push.js +24 -19
  55. package/dist/read-only-request.d.ts +4 -0
  56. package/dist/read-only-request.js +15 -0
  57. package/dist/release-adapters.d.ts +24 -2
  58. package/dist/release-adapters.js +136 -10
  59. package/dist/release-authority.d.ts +46 -0
  60. package/dist/release-authority.js +64 -0
  61. package/dist/release-auto.d.ts +153 -0
  62. package/dist/release-auto.js +496 -0
  63. package/dist/release-cli.js +56 -2
  64. package/dist/release-coverage.js +1 -1
  65. package/dist/release.d.ts +105 -1
  66. package/dist/release.js +229 -9
  67. package/dist/repository-context.js +5 -2
  68. package/dist/result-follow-ups.js +23 -2
  69. package/dist/result-review.js +5 -1
  70. package/dist/scope.d.ts +31 -2
  71. package/dist/scope.js +44 -3
  72. package/dist/scout.js +5 -1
  73. package/dist/serve.js +162 -6
  74. package/dist/server/agents-live-filed.d.ts +17 -0
  75. package/dist/server/agents-live-filed.js +97 -0
  76. package/dist/server/agents-live-room.js +4 -1
  77. package/dist/server/chat.js +31 -23
  78. package/dist/server/http.d.ts +5 -0
  79. package/dist/server/last-checks.d.ts +17 -0
  80. package/dist/server/last-checks.js +22 -0
  81. package/dist/server/pages.js +18 -4
  82. package/dist/server/people-tokens.js +7 -4
  83. package/dist/server/render-pages.d.ts +2 -1
  84. package/dist/server/render-pages.js +28 -24
  85. package/dist/server/render-results.d.ts +11 -2
  86. package/dist/server/render-results.js +54 -12
  87. package/dist/server/render-settings.js +3 -3
  88. package/dist/server/render-tasks.d.ts +10 -0
  89. package/dist/server/render-tasks.js +30 -17
  90. package/dist/server/route-table.js +17 -1
  91. package/dist/server/runtime.d.ts +16 -1
  92. package/dist/server/settings.js +111 -31
  93. package/dist/server/task-views.js +8 -1
  94. package/dist/server/tasks.js +11 -2
  95. package/dist/storage-ui.d.ts +2 -0
  96. package/dist/storage-ui.js +1 -1
  97. package/dist/storage.js +2 -1
  98. package/dist/store.d.ts +4 -1
  99. package/dist/store.js +52 -13
  100. package/dist/subagent-work.js +3 -0
  101. package/dist/task-base.js +1 -1
  102. package/dist/task-status.d.ts +28 -1
  103. package/dist/task-status.js +93 -11
  104. package/dist/teardown-git.d.ts +59 -5
  105. package/dist/teardown-git.js +1022 -90
  106. package/dist/telegram-progress.js +4 -4
  107. package/dist/telegram-status.js +8 -3
  108. package/dist/test-infra.d.ts +26 -0
  109. package/dist/test-infra.js +321 -0
  110. package/dist/tool-secret-store.js +8 -0
  111. package/dist/toolroll-update-ui.d.ts +2 -0
  112. package/dist/toolroll-update-ui.js +4 -3
  113. package/dist/toolroll-update.js +10 -9
  114. package/dist/work-index.js +29 -12
  115. package/dist/workspace-ui.d.ts +5 -0
  116. package/dist/workspace-ui.js +38 -10
  117. package/dist/worktree.d.ts +4 -0
  118. package/dist/worktree.js +13 -7
  119. package/package.json +1 -1
@@ -133,3 +133,67 @@ export function releaseSealProblem(store, terms) {
133
133
  }
134
134
  return null;
135
135
  }
136
+ // ------------------------------------------------------------------ automatic release approval (auto-approve-green.md)
137
+ /** "Release approval: Ask me / Approve automatically when checks pass": a project setting, newest choice wins (a ledger
138
+ * row each); `ask` by default. Turning it on is a person's credential act (a password typed now); off is one click. */
139
+ export const RELEASE_APPROVAL_ACTION = "release approval changed";
140
+ /** The plane's record of what proved an automatic seal (`auto-check` or `auto-green`): one ledger row per seal, its
141
+ * id named by the seal's `mode_digest` as `auto:<id>`. Only release-auto.ts writes it. */
142
+ export const AUTO_EVIDENCE_ACTION = "release auto-approved";
143
+ /** An automatic seal taken back for good (the setting lowered, a refusal): its outcome names the seal's `auto:<id>`, and
144
+ * that evidence never stands again. */
145
+ export const AUTO_WITHDRAWN_ACTION = "release auto-approval withdrawn";
146
+ /** Read live at every use. Fail closed: a missing, unreadable or unknown row is `ask`. */
147
+ export function releaseApprovalSetting(store, repo) {
148
+ let row;
149
+ try {
150
+ row = store.handle.prepare("SELECT actor, at, outcome FROM action_ledger WHERE repo = ? AND task_id IS NULL AND action = ? ORDER BY id DESC LIMIT 1").get(repo, RELEASE_APPROVAL_ACTION);
151
+ }
152
+ catch {
153
+ return { auto: false, setBy: null, at: null };
154
+ }
155
+ if (row === undefined || (row["outcome"] !== "auto" && row["outcome"] !== "ask"))
156
+ return { auto: false, setBy: null, at: null };
157
+ return { auto: row["outcome"] === "auto", setBy: String(row["actor"]), at: String(row["at"]) };
158
+ }
159
+ export const releaseAutoApproves = (store, repo) => releaseApprovalSetting(store, repo).auto;
160
+ /** SQL: the newest setting row for `repo` (an SQL expression) says `auto`. */
161
+ export const releaseAutoSql = (repo) => `COALESCE((SELECT ra_set.outcome = 'auto' FROM action_ledger ra_set WHERE ra_set.repo = ${repo} AND ra_set.task_id IS NULL
162
+ AND ra_set.action = '${RELEASE_APPROVAL_ACTION}' ORDER BY ra_set.id DESC LIMIT 1), 0)`;
163
+ /**
164
+ * SQL over a `task_scope` alias: its automatic seal (`auto-check` or `auto-green`) stands on its own evidence row — this
165
+ * task, this kind, `mode_digest = 'auto:<row id>'`, the row's digest and candidate exactly the sealed ones — on a release
166
+ * task (a candidate and release terms signed in), while the project's setting is on. The approver's standing is read
167
+ * beside it (approval-authority.ts).
168
+ */
169
+ export const autoSealSql = (scope, repo) => `(${scope}.candidate IS NOT NULL AND ${scope}.release_terms_json IS NOT NULL AND ${releaseAutoSql(repo)}
170
+ AND EXISTS (SELECT 1 FROM action_ledger ae WHERE ae.task_id = ${scope}.task_id AND ae.action = '${AUTO_EVIDENCE_ACTION}'
171
+ AND ae.outcome = ${scope}.approval_basis AND ${scope}.mode_digest = 'auto:' || ae.id AND json_valid(ae.detail)
172
+ AND json_extract(ae.detail, '$.v') = 1 AND json_extract(ae.detail, '$.digest') = ${scope}.approved_digest
173
+ AND json_extract(ae.detail, '$.candidate') = ${scope}.candidate)
174
+ AND NOT EXISTS (SELECT 1 FROM action_ledger aw WHERE aw.task_id = ${scope}.task_id AND aw.action = '${AUTO_WITHDRAWN_ACTION}' AND aw.outcome = ${scope}.mode_digest))`;
175
+ export function autoEvidenceOf(store, taskId, modeDigest, kind) {
176
+ const match = /^auto:([1-9][0-9]{0,15})$/.exec(modeDigest ?? "");
177
+ if (match === null)
178
+ return null;
179
+ const row = store.handle.prepare("SELECT id, at, actor, task_id, outcome, detail FROM action_ledger WHERE id = ? AND action = ?").get(Number(match[1]), AUTO_EVIDENCE_ACTION);
180
+ if (row === undefined || row["task_id"] !== taskId || row["outcome"] !== kind)
181
+ return null;
182
+ if (store.handle.prepare("SELECT 1 AS hit FROM action_ledger WHERE task_id = ? AND action = ? AND outcome = ?").get(taskId, AUTO_WITHDRAWN_ACTION, `auto:${match[1]}`) !== undefined)
183
+ return null;
184
+ let detail;
185
+ try {
186
+ detail = JSON.parse(String(row["detail"]));
187
+ }
188
+ catch {
189
+ return null;
190
+ }
191
+ if (detail === null || typeof detail !== "object" || detail["v"] !== 1 || typeof detail["digest"] !== "string" || typeof detail["candidate"] !== "string")
192
+ return null;
193
+ const ci = Array.isArray(detail["ci"]) ? detail["ci"].filter((one) => one !== null && typeof one === "object" && Number.isSafeInteger(one.id) && typeof one.name === "string") : [];
194
+ const textOr = (value) => typeof value === "string" ? value : null;
195
+ return {
196
+ id: Number(row["id"]), kind, taskId, digest: detail["digest"], candidate: detail["candidate"], at: String(row["at"]), by: String(detail["by"] ?? row["actor"]),
197
+ run: Number.isSafeInteger(detail["run"]) ? Number(detail["run"]) : null, receipt: textOr(detail["receipt"]), ci, observedAt: textOr(detail["observedAt"]), base: textOr(detail["base"]),
198
+ };
199
+ }
@@ -0,0 +1,153 @@
1
+ import { type CheckFailure } from "./release.js";
2
+ import { RELEASE_APPROVAL_ACTION, releaseAutoApproves, type ReleaseApprovalSetting, releaseApprovalSetting } from "./release-authority.js";
3
+ import { type Store } from "./store.js";
4
+ export { RELEASE_APPROVAL_ACTION, releaseApprovalSetting, releaseAutoApproves, type ReleaseApprovalSetting };
5
+ /** The setting's words, one place. */
6
+ export declare const RELEASE_APPROVAL_WORDS: {
7
+ readonly ask: "Ask me";
8
+ readonly auto: "Approve automatically when checks pass";
9
+ };
10
+ /** How old the plane's own CI observation may be when it seals. */
11
+ export declare const CI_OBSERVATION_MS: number;
12
+ /** One check run on the candidate as the plane's own `gh` reported it (oldest first). */
13
+ export type ObservedCheckRun = {
14
+ id: number | null;
15
+ name: string;
16
+ status: string;
17
+ conclusion: string | null;
18
+ app: string | null;
19
+ suite?: number | null;
20
+ head?: string | null;
21
+ workflow?: number | null;
22
+ };
23
+ /** The plane's own look at GitHub CI for one commit (release-adapters.ts makes it, right before the seal). */
24
+ export type CiObservation = {
25
+ head: string;
26
+ observedAt: string;
27
+ runs: readonly ObservedCheckRun[];
28
+ /** Whether the history is whole (fewer check suites than GitHub's check-runs ceiling): false or absent refuses. */
29
+ complete?: boolean;
30
+ };
31
+ type Refusal = {
32
+ ok: false;
33
+ why: string;
34
+ };
35
+ /**
36
+ * Change the setting: an approver's act, proven by the caller (on: a password typed now; off: one click). Lowering it,
37
+ * in the same transaction, unseals every automatic seal of the project whose release hasn't started merging and stops
38
+ * their live releases, so nothing approves — or goes on — on the old setting.
39
+ */
40
+ export declare function setReleaseApproval(store: Store, repo: string, auto: boolean, by: string, now: Date): {
41
+ changed: boolean;
42
+ withdrawn: string[];
43
+ };
44
+ /**
45
+ * The approval step with the setting on: seal `auto-check`, so the release task's build runs its check before anyone
46
+ * approves it. Idempotent: an `auto-check` already standing on this digest is the answer. A person's own approval
47
+ * already there is left alone (the coordinator then takes the person's road).
48
+ */
49
+ export declare function authorizeAutoCheck(store: Store, input: {
50
+ taskId: string;
51
+ digest: string;
52
+ candidate: string;
53
+ /** Where the candidate left its release's base branch, as the plane read it from GitHub (release-adapters.ts
54
+ * readMergeBase): the one comparison base every check of this commit is judged against. Never a task branch. */
55
+ base: string;
56
+ now: Date;
57
+ }): {
58
+ ok: true;
59
+ already: boolean;
60
+ } | Refusal;
61
+ /**
62
+ * What an automatic approval may ship without a person, as an allowlist over each changed path (auto-approve-green.md):
63
+ * product code under `src/` (its tests included; never its test helpers or fixtures, nor build configuration), test files
64
+ * under `test/` (only `*.test.ts`: never a helper, fixture or setup file; deleting any test needs a person), docs
65
+ * (never the testing policy or plans, never agent or security policy), the changelog and README, and a version bump of
66
+ * package.json and package-lock.json. Everything else — CI, scripts (the check, the build, the harness), test/,
67
+ * dependencies, the lockfile beyond its version, config — and every path the release carry rules never carry, needs a
68
+ * person. Null when the path may ship.
69
+ */
70
+ export declare function autoPathProblem(path: string): string | null;
71
+ /** Flakes: how many failed attempts one CI job, and how many unsuccessful release-check runs, may have before a person
72
+ * decides. The head and digest are pinned, so between attempts only nondeterminism differs. */
73
+ export declare const MAX_FAILED_ATTEMPTS = 2;
74
+ /** One CI attempt that failed before its job's latest attempt passed, as the evidence records it. */
75
+ export type CiFailure = CheckFailure;
76
+ /**
77
+ * GitHub CI on the exact head, as the plane read it (every attempt): fresh, every run GitHub Actions, and the same
78
+ * verdict every later release boundary uses (release.ts prChecksVerdict: exact head, each job by its latest attempt,
79
+ * every required check present), with the flake cap. The tolerated failures are returned for the evidence.
80
+ */
81
+ export declare function ciVerdict(observation: CiObservation, candidate: string, now: Date): {
82
+ ok: true;
83
+ failures: CiFailure[];
84
+ } | {
85
+ ok: false;
86
+ problem: string;
87
+ };
88
+ /** ciVerdict's problem, or null when CI passes. */
89
+ export declare function ciProblem(observation: CiObservation, candidate: string, now: Date): string | null;
90
+ /**
91
+ * The release approval, once the check passed: seal `auto-green` over the standing `auto-check`, or refuse in words —
92
+ * and on any refusal take the `auto-check` back, so a person decides. Everything is proven again in this one write
93
+ * transaction (docs/plans/auto-approve-green.md, "What auto-green proves").
94
+ */
95
+ export declare function sealAutoGreen(store: Store, input: {
96
+ taskId: string;
97
+ digest: string;
98
+ candidate: string;
99
+ run: number;
100
+ receipt: string;
101
+ ci: CiObservation;
102
+ evidenceRoot: string;
103
+ now: Date;
104
+ }): {
105
+ ok: true;
106
+ by: string;
107
+ at: string;
108
+ ci: {
109
+ id: number;
110
+ name: string;
111
+ }[];
112
+ } | Refusal;
113
+ /** The automatic road ended without an approval (a refusal, a failed check, a CI failure after the deploy): take the
114
+ * `auto-check` back so the task asks a person, and tell the owner why. Idempotent. */
115
+ export declare function giveUpAutoApproval(store: Store, input: {
116
+ taskId: string;
117
+ why: string;
118
+ now: Date;
119
+ }): void;
120
+ /** A release check failed while checking automatically: tell the owner. The `auto-check` stays, so a regate of the same
121
+ * commit and digest (a flake) may still approve itself, within MAX_FAILED_ATTEMPTS failed runs. */
122
+ export declare function noteAutoCheckFailed(store: Store, input: {
123
+ taskId: string;
124
+ why: string;
125
+ now: Date;
126
+ }): void;
127
+ /** "Released 0.9.x automatically": the release that approved itself finished. */
128
+ export declare function noteAutoRelease(store: Store, input: {
129
+ taskId: string;
130
+ tag: string;
131
+ now: Date;
132
+ }): void;
133
+ /** Whether a task carries an automatic release seal (any state): it never merges or deploys outside `toolroll release`. */
134
+ export declare function autoReleaseSealed(store: Store, taskId: string): boolean;
135
+ /** The plane's record that an automatic release's publication waits for a person (CI stopped passing after its merge),
136
+ * and a person's answer. The newest row for the task wins. */
137
+ export declare const PUBLISH_HELD_ACTION = "release publish held";
138
+ export declare const PUBLISH_ALLOWED_ACTION = "release publish allowed";
139
+ export declare function holdAutoPublish(store: Store, input: {
140
+ taskId: string;
141
+ why: string;
142
+ now: Date;
143
+ }): void;
144
+ /** Why the release's tag, publication and Homebrew wait for a person, or null. */
145
+ export declare function autoPublishHeld(store: Store, taskId: string): string | null;
146
+ /** A person lets a held publication go on: the caller proved a person (never the lead, a token or a mode). */
147
+ export declare function allowAutoPublish(store: Store, input: {
148
+ taskId: string;
149
+ by: string;
150
+ now: Date;
151
+ }): {
152
+ ok: true;
153
+ } | Refusal;