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.
- package/CHANGELOG.md +80 -0
- package/dist/agent-fence.js +8 -0
- package/dist/approval-authority.js +6 -2
- package/dist/approval-rules-ui.d.ts +11 -0
- package/dist/approval-rules-ui.js +13 -1
- package/dist/assignment.d.ts +3 -0
- package/dist/assignment.js +27 -8
- package/dist/batch-checks.js +3 -1
- package/dist/browser/workspace.js +27 -27
- package/dist/browser-workspace.d.ts +1 -1
- package/dist/builder.js +1 -1
- package/dist/chat-acceptance.d.ts +4 -1
- package/dist/chat-acceptance.js +13 -3
- package/dist/cli.js +1 -1
- package/dist/coding-handoff.js +4 -3
- package/dist/coding-ui.js +2 -2
- package/dist/coding-workspace.js +23 -9
- package/dist/contracts/console-api.d.ts +6 -1
- package/dist/contracts/console-api.js +11 -0
- package/dist/control-ui.d.ts +3 -2
- package/dist/control-ui.js +7 -2
- package/dist/demo.js +5 -3
- package/dist/deploy-gate.d.ts +3 -0
- package/dist/deploy-gate.js +3 -2
- package/dist/desktop-update.d.ts +4 -1
- package/dist/desktop-update.js +43 -9
- package/dist/discover.js +6 -1
- package/dist/first-run.d.ts +8 -5
- package/dist/first-run.js +42 -7
- package/dist/flow-card-task.js +3 -2
- package/dist/flow-unattended.d.ts +2 -0
- package/dist/flow-unattended.js +12 -1
- package/dist/git-incident.d.ts +49 -0
- package/dist/git-incident.js +69 -0
- package/dist/git-views.d.ts +20 -0
- package/dist/git-views.js +120 -0
- package/dist/grant.js +3 -1
- package/dist/integrations.d.ts +4 -3
- package/dist/integrations.js +5 -6
- package/dist/lead-contract.d.ts +1 -1
- package/dist/lead-contract.js +2 -2
- package/dist/lead-follow.js +3 -1
- package/dist/openrouter-models.d.ts +5 -2
- package/dist/openrouter-models.js +7 -4
- package/dist/operate.js +9 -1
- package/dist/planner.js +5 -1
- package/dist/project-knowledge.js +1 -1
- package/dist/project-learning.js +65 -7
- package/dist/project-tools.js +19 -1
- package/dist/pull-request-flow.js +4 -0
- package/dist/pull-request-ui.d.ts +1 -1
- package/dist/pull-request-ui.js +5 -1
- package/dist/push.d.ts +2 -0
- package/dist/push.js +24 -19
- package/dist/read-only-request.d.ts +4 -0
- package/dist/read-only-request.js +15 -0
- package/dist/release-adapters.d.ts +24 -2
- package/dist/release-adapters.js +136 -10
- package/dist/release-authority.d.ts +46 -0
- package/dist/release-authority.js +64 -0
- package/dist/release-auto.d.ts +153 -0
- package/dist/release-auto.js +496 -0
- package/dist/release-cli.js +56 -2
- package/dist/release-coverage.js +1 -1
- package/dist/release.d.ts +105 -1
- package/dist/release.js +229 -9
- package/dist/repository-context.js +5 -2
- package/dist/result-follow-ups.js +23 -2
- package/dist/result-review.js +5 -1
- package/dist/scope.d.ts +31 -2
- package/dist/scope.js +44 -3
- package/dist/scout.js +5 -1
- package/dist/serve.js +162 -6
- package/dist/server/agents-live-filed.d.ts +17 -0
- package/dist/server/agents-live-filed.js +97 -0
- package/dist/server/agents-live-room.js +4 -1
- package/dist/server/chat.js +31 -23
- package/dist/server/http.d.ts +5 -0
- package/dist/server/last-checks.d.ts +17 -0
- package/dist/server/last-checks.js +22 -0
- package/dist/server/pages.js +18 -4
- package/dist/server/people-tokens.js +7 -4
- package/dist/server/render-pages.d.ts +2 -1
- package/dist/server/render-pages.js +28 -24
- package/dist/server/render-results.d.ts +11 -2
- package/dist/server/render-results.js +54 -12
- package/dist/server/render-settings.js +3 -3
- package/dist/server/render-tasks.d.ts +10 -0
- package/dist/server/render-tasks.js +30 -17
- package/dist/server/route-table.js +17 -1
- package/dist/server/runtime.d.ts +16 -1
- package/dist/server/settings.js +111 -31
- package/dist/server/task-views.js +8 -1
- package/dist/server/tasks.js +11 -2
- package/dist/storage-ui.d.ts +2 -0
- package/dist/storage-ui.js +1 -1
- package/dist/storage.js +2 -1
- package/dist/store.d.ts +4 -1
- package/dist/store.js +52 -13
- package/dist/subagent-work.js +3 -0
- package/dist/task-base.js +1 -1
- package/dist/task-status.d.ts +28 -1
- package/dist/task-status.js +93 -11
- package/dist/teardown-git.d.ts +59 -5
- package/dist/teardown-git.js +1022 -90
- package/dist/telegram-progress.js +4 -4
- package/dist/telegram-status.js +8 -3
- package/dist/test-infra.d.ts +26 -0
- package/dist/test-infra.js +321 -0
- package/dist/tool-secret-store.js +8 -0
- package/dist/toolroll-update-ui.d.ts +2 -0
- package/dist/toolroll-update-ui.js +4 -3
- package/dist/toolroll-update.js +10 -9
- package/dist/work-index.js +29 -12
- package/dist/workspace-ui.d.ts +5 -0
- package/dist/workspace-ui.js +38 -10
- package/dist/worktree.d.ts +4 -0
- package/dist/worktree.js +13 -7
- package/package.json +1 -1
package/dist/workspace-ui.js
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
import { ACCEPT_NEEDS_REASON, FAILED_CHECK, cantAcceptYetOf, evidenceProblemOf } from "./result-acts.js";
|
|
20
20
|
export { ACCEPT_NEEDS_REASON, cantAcceptYetOf, evidenceProblemOf };
|
|
21
21
|
import { manualReviewOnly, plainReasonWords } from "./proof.js";
|
|
22
|
-
import { OPEN_RESULT, plainReasonOf, stageOfCode, taskStatusOf, workToneOf } from "./task-status.js";
|
|
22
|
+
import { CHECK_COMMAND_CHANGED, CHECK_UNREADABLE, OPEN_RESULT, approvedCheckOf, approvedCheckStateOf, plainReasonOf, stageOfCode, taskStatusOf, workToneOf } from "./task-status.js";
|
|
23
23
|
import { whenUtc } from "./when-html.js";
|
|
24
24
|
import { html, postForm } from "./html.js";
|
|
25
25
|
export const WORK_VIEWS = [
|
|
@@ -122,9 +122,15 @@ export function resultStatusOf(result, publication = null) {
|
|
|
122
122
|
return (result?.role === "builder" ? reviewStatusOf(result.review ?? null) : null) ?? storedResultStatusOf(result, publication);
|
|
123
123
|
}
|
|
124
124
|
/** What the machine itself recorded about a result, in a clause. */
|
|
125
|
-
function machineVerdictWords(result) {
|
|
126
|
-
if (result.verdict
|
|
125
|
+
function machineVerdictWords(result, check) {
|
|
126
|
+
if (check !== undefined && check?.status === "failed" && evidenceProblemOf(result.verdict, result.reasons) !== "checks-failed") {
|
|
127
|
+
return `the approved check failed against it${check.exitCode === null ? "" : ` (exit ${check.exitCode})`}`;
|
|
128
|
+
}
|
|
129
|
+
if (result.verdict === "verified" && passedNow(check))
|
|
127
130
|
return "it verified the result before the acceptance";
|
|
131
|
+
// Unread here, or unreadable now: never restated as a pass.
|
|
132
|
+
if (result.verdict === "verified")
|
|
133
|
+
return check === undefined ? "open the result for its checks" : "its check result couldn't be read";
|
|
128
134
|
if (result.verdict === "attested")
|
|
129
135
|
return "the checks on record are the agent's own report";
|
|
130
136
|
const problem = evidenceProblemOf(result.verdict, result.reasons);
|
|
@@ -136,6 +142,14 @@ function machineVerdictWords(result) {
|
|
|
136
142
|
return "its evidence did not match the sealed record";
|
|
137
143
|
return "its required evidence was missing";
|
|
138
144
|
}
|
|
145
|
+
/** The approved check passed (task-status.ts approvedCheckStateOf): its own pass, or checks Off covered by a passing
|
|
146
|
+
* release check. A re-run underway keeps the pass it had. */
|
|
147
|
+
const passedNow = (check) => {
|
|
148
|
+
const state = approvedCheckStateOf(check);
|
|
149
|
+
return state === "passed" || (state === "running" && check?.status === "passed");
|
|
150
|
+
};
|
|
151
|
+
/** A result whose check this surface didn't read: no pass is claimed, the result page says. */
|
|
152
|
+
const UNREAD_CHECK = "Open the result to see its checks.";
|
|
139
153
|
function storedResultStatusOf(result, publication) {
|
|
140
154
|
if (result === null || result.runId === null) {
|
|
141
155
|
return {
|
|
@@ -151,6 +165,8 @@ function storedResultStatusOf(result, publication) {
|
|
|
151
165
|
}
|
|
152
166
|
const published = publicationStatusOf(publication);
|
|
153
167
|
const withPublication = (detail) => (published === null ? detail : `${detail} ${published.detail}`);
|
|
168
|
+
// Did the approved check pass for this result (task-status.ts): the one answer the Checks row and the road read.
|
|
169
|
+
const check = result.checks === undefined ? undefined : approvedCheckOf(result.verdict, result.reasons, result.checks);
|
|
154
170
|
const humanReview = manualReviewOnly({ verdict: result.verdict ?? "", reasons: result.reasons });
|
|
155
171
|
if (result.accepted && humanReview) {
|
|
156
172
|
return { token: "accepted-exception", label: "Accepted by a person", detail: withPublication("A person accepted this result. The recorded checks are unchanged."), tone: "done", action: { label: "Open the acceptance", kind: "open-review" } };
|
|
@@ -162,14 +178,18 @@ function storedResultStatusOf(result, publication) {
|
|
|
162
178
|
// Acceptance records a person's decision; it neither proves nor
|
|
163
179
|
// disproves anything about the checks. The machine's own verdict is
|
|
164
180
|
// restated as recorded.
|
|
165
|
-
detail: withPublication(`An approver accepted this result by hand, and the recorded exception says why. The machine's verdict is unchanged: ${machineVerdictWords(result)}.`),
|
|
181
|
+
detail: withPublication(`An approver accepted this result by hand, and the recorded exception says why. The machine's verdict is unchanged: ${machineVerdictWords(result, check)}.`),
|
|
166
182
|
tone: "done",
|
|
167
183
|
action: { label: "Open the recorded exception", kind: "open-review" },
|
|
168
184
|
};
|
|
169
185
|
}
|
|
170
|
-
const
|
|
186
|
+
const recordedProblem = evidenceProblemOf(result.verdict, result.reasons);
|
|
187
|
+
// A failed check that a later check on the same commit passed no longer fails the result; a check that failed
|
|
188
|
+
// since a passing verdict does.
|
|
189
|
+
const problem = recordedProblem === "checks-failed" && passedNow(check) ? "none"
|
|
190
|
+
: recordedProblem !== "mismatched" && check?.status === "failed" ? "checks-failed" : recordedProblem;
|
|
171
191
|
if (problem === "checks-failed") {
|
|
172
|
-
const exit = failedCheckExit(result.reasons);
|
|
192
|
+
const exit = check?.status === "failed" ? check.exitCode : failedCheckExit(result.reasons);
|
|
173
193
|
return {
|
|
174
194
|
token: "checks-failed",
|
|
175
195
|
label: "Changes saved, but checks failed",
|
|
@@ -218,7 +238,14 @@ function storedResultStatusOf(result, publication) {
|
|
|
218
238
|
action: { label: "See what is missing", kind: "open-review" },
|
|
219
239
|
};
|
|
220
240
|
}
|
|
221
|
-
|
|
241
|
+
// Any verdict short of a pass the check confirms: never "passed" when the check can't be read now.
|
|
242
|
+
if (check !== undefined && !passedNow(check) && result.verdict === "verified") {
|
|
243
|
+
return check?.status === "running"
|
|
244
|
+
? { token: "checks-running", label: "Checks running", detail: withPublication("The approved check is running on this result."), tone: "live", action: { label: OPEN_RESULT, kind: "open-result" } }
|
|
245
|
+
: { token: "check-unreadable", label: "Result saved — check result couldn't be read", detail: withPublication(`${check?.cause === "command-changed" ? CHECK_COMMAND_CHANGED : CHECK_UNREADABLE} This result isn't verified.`),
|
|
246
|
+
tone: "problem", action: { label: "Open the check", kind: "open-review" } };
|
|
247
|
+
}
|
|
248
|
+
if (result.verdict === "attested" && !passedNow(check)) {
|
|
222
249
|
return {
|
|
223
250
|
token: "agent-attested",
|
|
224
251
|
label: "Result saved — checks reported by the agent",
|
|
@@ -231,7 +258,7 @@ function storedResultStatusOf(result, publication) {
|
|
|
231
258
|
return {
|
|
232
259
|
token: published.token,
|
|
233
260
|
label: published.label,
|
|
234
|
-
detail: `The approved check passed against this result. ${published.detail}`,
|
|
261
|
+
detail: check === undefined ? `${UNREAD_CHECK} ${published.detail}` : `The approved check passed against this result. ${published.detail}`,
|
|
235
262
|
tone: published.token === "pr-closed" ? "muted" : "done",
|
|
236
263
|
action: published.token === "merge-observed" ? { label: OPEN_RESULT, kind: "open-result" } : { label: "Open the pull request", kind: "open-pr" },
|
|
237
264
|
};
|
|
@@ -239,7 +266,8 @@ function storedResultStatusOf(result, publication) {
|
|
|
239
266
|
return {
|
|
240
267
|
token: "ready-to-review",
|
|
241
268
|
label: "Ready",
|
|
242
|
-
|
|
269
|
+
// Only a check read here may be called passed; a surface that read none says where to see it.
|
|
270
|
+
detail: withPublication(check === undefined ? UNREAD_CHECK : "The approved check passed against this result."),
|
|
243
271
|
tone: "ready",
|
|
244
272
|
action: { label: OPEN_RESULT, kind: "open-result" },
|
|
245
273
|
};
|
|
@@ -301,7 +329,7 @@ export function dispatchActionLabel(dispatch) {
|
|
|
301
329
|
}
|
|
302
330
|
/** Result tokens a done task can wear, as the shared stage they mean. A
|
|
303
331
|
* result that is missing its record waits on a person; a failed check fails. */
|
|
304
|
-
const RESULT_NEEDS_YOU = new Set(["no-build-record", "verification-needed", "evidence-mismatch", "record-incomplete", "evidence-damaged"]);
|
|
332
|
+
const RESULT_NEEDS_YOU = new Set(["no-build-record", "verification-needed", "evidence-mismatch", "record-incomplete", "evidence-damaged", "check-unreadable"]);
|
|
305
333
|
/** Result tokens that only follow a passing project check. */
|
|
306
334
|
const VERIFIED_RESULT = new Set(["ready-to-review", "pr-opened", "merge-observed", "pr-closed"]);
|
|
307
335
|
/** A finished result's display status in the shared headline's words: a
|
package/dist/worktree.d.ts
CHANGED
|
@@ -152,6 +152,10 @@ export declare class WorktreePool {
|
|
|
152
152
|
private readonly store;
|
|
153
153
|
private readonly options;
|
|
154
154
|
private readonly runner;
|
|
155
|
+
/** The pool's own git in a project's repository and its checkouts (making, moving and reading them): hardened against
|
|
156
|
+
* a frozen view of the repository's config and info (teardown-git.ts checkoutGit), which agents can write. An injected
|
|
157
|
+
* runner (tests' stubbed git) is used as given. */
|
|
158
|
+
private readonly hardened;
|
|
155
159
|
/** Carried to the independent storage sweep process. */
|
|
156
160
|
get root(): string;
|
|
157
161
|
constructor(store: Store, options: PoolOptions);
|
package/dist/worktree.js
CHANGED
|
@@ -32,7 +32,7 @@ import { HANDOFF_PREFIX, MAILBOX_SUFFIX, looksLikeProtocolFile, readMailbox } fr
|
|
|
32
32
|
import { parseHandoff } from "./decision.js";
|
|
33
33
|
import { diskBytes } from "./storage.js";
|
|
34
34
|
import { markNeverIndex } from "./never-index.js";
|
|
35
|
-
import { teardownGit } from "./teardown-git.js";
|
|
35
|
+
import { checkoutGit, teardownGit } from "./teardown-git.js";
|
|
36
36
|
/**
|
|
37
37
|
* The note a lease leaves in the checkout, naming the process holding it.
|
|
38
38
|
*
|
|
@@ -268,12 +268,18 @@ export class WorktreePool {
|
|
|
268
268
|
store;
|
|
269
269
|
options;
|
|
270
270
|
runner;
|
|
271
|
+
/** The pool's own git in a project's repository and its checkouts (making, moving and reading them): hardened against
|
|
272
|
+
* a frozen view of the repository's config and info (teardown-git.ts checkoutGit), which agents can write. An injected
|
|
273
|
+
* runner (tests' stubbed git) is used as given. */
|
|
274
|
+
hardened;
|
|
271
275
|
/** Carried to the independent storage sweep process. */
|
|
272
276
|
get root() { return this.options.root; }
|
|
273
277
|
constructor(store, options) {
|
|
274
278
|
this.store = store;
|
|
275
279
|
this.options = options;
|
|
276
280
|
this.runner = options.runner ?? run;
|
|
281
|
+
// Bound to the project's repository from the pool's own request or record (teardown-git.ts GitBinding).
|
|
282
|
+
this.hardened = repo => this.runner === run ? checkoutGit(run, "build", { repo }) : this.runner;
|
|
277
283
|
}
|
|
278
284
|
/**
|
|
279
285
|
* Take a working copy for this branch.
|
|
@@ -475,7 +481,7 @@ export class WorktreePool {
|
|
|
475
481
|
// checkout between the inspection and the move.
|
|
476
482
|
let base;
|
|
477
483
|
if (request.freshBase !== undefined && request.taskRef !== undefined) {
|
|
478
|
-
const fresh = await this.freshen(path, request.branch, request.taskRef, request.freshBase, request.now);
|
|
484
|
+
const fresh = await this.freshen(request.repo, path, request.branch, request.taskRef, request.freshBase, request.now);
|
|
479
485
|
if (!fresh.ok)
|
|
480
486
|
return { ok: false, reason: "git", message: fresh.message };
|
|
481
487
|
base = fresh.standing;
|
|
@@ -524,9 +530,9 @@ export class WorktreePool {
|
|
|
524
530
|
* untracked. The move is `git reset --keep`, which refuses rather than overwrite anything; HEAD and the tree are read
|
|
525
531
|
* again right before and after it, and any surprise fails the lease with the checkout left for a person.
|
|
526
532
|
*/
|
|
527
|
-
async freshen(path, branch, taskRef, fresh, now) {
|
|
533
|
+
async freshen(repo, path, branch, taskRef, fresh, now) {
|
|
528
534
|
const read = async (args) => {
|
|
529
|
-
const out = await this.
|
|
535
|
+
const out = await this.hardened(repo)(GIT, [...READ_ONLY, ...args], { cwd: path, timeoutMs: WORKTREE_TIMEOUT_MS });
|
|
530
536
|
return out.code === 0 ? out.stdout.trim() : null;
|
|
531
537
|
};
|
|
532
538
|
const main = await read(["rev-parse", "--verify", "--quiet", `${fresh.main}^{commit}`]);
|
|
@@ -544,14 +550,14 @@ export class WorktreePool {
|
|
|
544
550
|
return stay("not on its branch");
|
|
545
551
|
if (fresh.recordedBase !== null && fresh.recordedBase !== head)
|
|
546
552
|
return stay("has commits");
|
|
547
|
-
const contained = await this.
|
|
553
|
+
const contained = await this.hardened(repo)(GIT, [...READ_ONLY, "merge-base", "--is-ancestor", head, main], { cwd: path, timeoutMs: WORKTREE_TIMEOUT_MS });
|
|
548
554
|
if (contained.code !== 0)
|
|
549
555
|
return stay("has commits");
|
|
550
556
|
if (await this.isDirty(path) !== false)
|
|
551
557
|
return stay("has changes");
|
|
552
558
|
// Recorded before git moves it: a move that then fails leaves the next attempt's own base as the branch's origin.
|
|
553
559
|
this.store.recordBranchBaseMove(taskRef, branch, head, main, now);
|
|
554
|
-
const moved = await this.
|
|
560
|
+
const moved = await this.hardened(repo)(GIT, ["reset", "--keep", main], { cwd: path, timeoutMs: WORKTREE_TIMEOUT_MS });
|
|
555
561
|
const after = await read(["rev-parse", "--verify", "--quiet", "HEAD^{commit}"]);
|
|
556
562
|
const branchNow = await read(["rev-parse", "--verify", "--quiet", `refs/heads/${branch}^{commit}`]);
|
|
557
563
|
if (moved.code !== 0 || after !== main || branchNow !== main || await this.isDirty(path) !== false) {
|
|
@@ -1052,7 +1058,7 @@ export class WorktreePool {
|
|
|
1052
1058
|
/** All disposal and inspection paths share the hardened teardown boundary. */
|
|
1053
1059
|
teardown = (cwd, args) => teardownGit(this.runner)(cwd, args);
|
|
1054
1060
|
git(cwd, args, prefix = []) {
|
|
1055
|
-
return this.
|
|
1061
|
+
return this.hardened(cwd)(GIT, [...prefix, ...args], { cwd, timeoutMs: WORKTREE_TIMEOUT_MS });
|
|
1056
1062
|
}
|
|
1057
1063
|
}
|
|
1058
1064
|
function firstLine(text) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolroll",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.76",
|
|
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"
|