toolroll 0.9.8 → 0.9.9
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 +22 -0
- package/dist/assignment-brief.d.ts +2 -0
- package/dist/assignment-brief.js +2 -1
- package/dist/assignment-presentation.js +3 -1
- package/dist/assignment-status.d.ts +1 -1
- package/dist/assignment-ui.js +5 -0
- package/dist/assignment.d.ts +9 -0
- package/dist/assignment.js +39 -7
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +18 -18
- package/dist/browser-crew.js +4 -0
- package/dist/browser-workspace.d.ts +40 -0
- package/dist/builder.d.ts +2 -0
- package/dist/builder.js +114 -8
- package/dist/chat-controls.d.ts +1 -1
- package/dist/chat-controls.js +1 -1
- package/dist/check-progress.js +4 -3
- package/dist/checkout-cleanup.d.ts +40 -2
- package/dist/checkout-cleanup.js +129 -9
- package/dist/desktop-update.d.ts +52 -0
- package/dist/desktop-update.js +147 -5
- package/dist/evidence.d.ts +6 -0
- package/dist/evidence.js +8 -0
- package/dist/first-run.d.ts +1 -1
- package/dist/first-run.js +7 -5
- package/dist/lead-context.d.ts +1 -1
- package/dist/lead-context.js +4 -2
- package/dist/lead-status.d.ts +10 -1
- package/dist/lead-status.js +21 -24
- package/dist/needs-you.d.ts +55 -0
- package/dist/needs-you.js +83 -0
- package/dist/operate.d.ts +2 -0
- package/dist/operate.js +112 -14
- package/dist/plane-review.js +4 -0
- package/dist/process-recovery-eligibility.d.ts +1 -0
- package/dist/project-learning.d.ts +3 -3
- package/dist/project-learning.js +28 -17
- package/dist/proof.d.ts +3 -1
- package/dist/proof.js +4 -2
- package/dist/result-completion.d.ts +4 -0
- package/dist/result-completion.js +11 -0
- package/dist/review-context.d.ts +3 -0
- package/dist/review-context.js +45 -5
- package/dist/run-closeout.d.ts +89 -0
- package/dist/run-closeout.js +302 -0
- package/dist/serve.d.ts +15 -1
- package/dist/serve.js +129 -14
- package/dist/setup-guide.d.ts +2 -2
- package/dist/shared-deps.d.ts +94 -0
- package/dist/shared-deps.js +365 -0
- package/dist/storage.js +2 -0
- package/dist/store.d.ts +36 -0
- package/dist/store.js +125 -29
- package/dist/task-status.d.ts +18 -4
- package/dist/task-status.js +35 -18
- package/dist/telegram-progress.js +2 -1
- package/dist/telegram-status.js +3 -3
- package/dist/test-temp.d.ts +19 -0
- package/dist/test-temp.js +98 -0
- package/dist/toolroll-update-ui.js +7 -2
- package/dist/toolroll-update.d.ts +21 -1
- package/dist/toolroll-update.js +135 -5
- package/dist/work-index.d.ts +11 -0
- package/dist/work-index.js +103 -16
- package/dist/work-summary.d.ts +3 -1
- package/dist/workspace-ui.js +1 -1
- package/dist/worktree.d.ts +16 -0
- package/dist/worktree.js +47 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.9 — 2026-10-02
|
|
4
|
+
|
|
5
|
+
- **Faster, lighter tests.** The unit suite runs about three times faster
|
|
6
|
+
(the biggest test files are split by area), tests use at most 4 workers,
|
|
7
|
+
and a project's Quick check can be a typecheck plus the tests for the
|
|
8
|
+
changed files.
|
|
9
|
+
- **Toolroll cleans up after itself.** When a run ends, it stops what the run
|
|
10
|
+
left running. Tests delete their temp folders, and `toolroll storage clean`
|
|
11
|
+
removes leftovers. Replaced release checks give up their checkouts, and
|
|
12
|
+
kept checkouts drop their dependencies until they're needed.
|
|
13
|
+
- **Checkouts share dependencies.** One install per lockfile, linked into
|
|
14
|
+
each checkout: about 1,900 files instead of 20,000, set up in under a second.
|
|
15
|
+
- **Every "Needs you" says what it needs, with the button that does it.**
|
|
16
|
+
Anything you can't act on shows as Waiting instead.
|
|
17
|
+
- **Finished tasks read Complete everywhere.** Status, the console and chat
|
|
18
|
+
agree, and old "process exit not recorded" entries clear themselves.
|
|
19
|
+
- **Updates never hang.** The updater settles leftover records itself, stops
|
|
20
|
+
waiting after 2 minutes otherwise, and says the one command that clears it.
|
|
21
|
+
Release checks now test upgrading from the last three releases.
|
|
22
|
+
- **Screenshots stay out of the repository.** Images under `evidence/` are
|
|
23
|
+
left out of commits and show as one line in the review.
|
|
24
|
+
|
|
3
25
|
## 0.9.8 — 2026-10-01
|
|
4
26
|
|
|
5
27
|
- **One automatic review per build.** When a build's checks pass (or it
|
|
@@ -23,6 +23,8 @@ export type AssignmentCatchUp = {
|
|
|
23
23
|
} | null;
|
|
24
24
|
nextAction: AssignmentSnapshot["primaryAction"];
|
|
25
25
|
attention: string[];
|
|
26
|
+
/** Where the next action leads in the console (navigation only). */
|
|
27
|
+
nextHref?: string | null;
|
|
26
28
|
decisions: {
|
|
27
29
|
id: number;
|
|
28
30
|
runId: number;
|
package/dist/assignment-brief.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** A bounded catch-up over saved project/task records, not conversation memory.
|
|
2
2
|
* Callers authenticate first and pass their current project ceiling. */
|
|
3
|
+
import { browserWorkActionHref } from './browser-crew.js';
|
|
3
4
|
import { createHash } from "node:crypto";
|
|
4
5
|
import { workIndexPage } from "./work-index.js";
|
|
5
6
|
import { publicChatText } from "./chat-display.js";
|
|
@@ -72,7 +73,7 @@ export function assignmentCatchUp(store, now, access, query = {}, evidenceRoot)
|
|
|
72
73
|
runId: item.liveRunId ?? item.unfinishedRunId ?? item.resultRunId, resultRunId: item.resultRunId,
|
|
73
74
|
outcome: saved?.["handoff"] == null ? null : text(String(saved["handoff"]), 400),
|
|
74
75
|
checks: item.resultRunId === null ? null : { status: "not-read", exitCode: null, detail: "Saved checks were not revalidated. Use assignment show to inspect them." },
|
|
75
|
-
nextAction: item.primaryAction, attention: attention.slice(0, 3).map(one => text(one, 160)),
|
|
76
|
+
nextAction: item.primaryAction, nextHref: browserWorkActionHref(item), attention: attention.slice(0, 3).map(one => text(one, 160)),
|
|
76
77
|
decisions: decisions.map(row => ({ id: Number(row["id"]), runId: Number(row["run"]), question: text(String(row["question"]), 240), state: String(row["state"]),
|
|
77
78
|
overdue: row["state"] !== "answered" && row["deadline"] !== null && String(row["deadline"]) <= now.toISOString(), choice: row["choice"] == null ? null : text(String(row["choice"]), 80) })),
|
|
78
79
|
};
|
|
@@ -35,9 +35,11 @@ export function assignmentPresentationOf(assignment, options = {}) {
|
|
|
35
35
|
// rank stay the existing projection's (pages and tests key off them).
|
|
36
36
|
const shared = { label: taskStatus.headline, tone: workToneOf(taskStatus.headline), detail: taskStatus.sentence, action: null };
|
|
37
37
|
const needsYou = taskStatus.headline === 'Needs you';
|
|
38
|
+
// Waiting needs no one: never listed under Needs you.
|
|
39
|
+
const waiting = taskStatus.headline === 'Waiting';
|
|
38
40
|
const status = state === 'working'
|
|
39
41
|
? { ...shared, token: `assignment-${state}`,
|
|
40
|
-
views: [...new Set([...(work?.views?.filter(view => view !== 'completed') ?? ['all']), ...(needsYou ? ['needs-you'] : [])])], rank: needsYou ? 0 : work?.rank ?? 2 }
|
|
42
|
+
views: [...new Set([...(work?.views?.filter(view => view !== 'completed' && !(waiting && view === 'needs-you')) ?? ['all']), ...(needsYou ? ['needs-you'] : [])])], rank: needsYou ? 0 : waiting ? 2 : work?.rank ?? 2 }
|
|
41
43
|
: { ...shared, token: `assignment-${state}`,
|
|
42
44
|
views: state === 'ready-to-check' || state === 'needs-decision' ? ['all', 'needs-you'] : state === 'complete' ? ['all', 'completed']
|
|
43
45
|
: state === 'checking' && work?.views?.includes('running') ? ['all', 'running'] : ['all'],
|
|
@@ -17,7 +17,7 @@ export declare function assignmentStatusBrief(assignment: AssignmentSnapshot): {
|
|
|
17
17
|
} | null;
|
|
18
18
|
runId: number | null;
|
|
19
19
|
primaryAction: {
|
|
20
|
-
code: "inspect-decisions" | "inspect-run" | "inspect-stop" | "inspect-task" | "open-pr" | "reconcile-run" | import("./dispatch.js").DispatchAction;
|
|
20
|
+
code: "confirm-stopped" | "inspect-decisions" | "inspect-run" | "inspect-stop" | "inspect-task" | "open-pr" | "reconcile-run" | "sign-in" | import("./dispatch.js").DispatchAction;
|
|
21
21
|
label: string;
|
|
22
22
|
target: import("./work-summary.js").WorkTarget;
|
|
23
23
|
} | null;
|
package/dist/assignment-ui.js
CHANGED
|
@@ -27,6 +27,11 @@ export function assignmentActionHref(assignment) {
|
|
|
27
27
|
return `/d/${decisionId}`;
|
|
28
28
|
if (action.code === 'inspect-decisions')
|
|
29
29
|
return '/';
|
|
30
|
+
if (action.code === 'sign-in')
|
|
31
|
+
return '/settings#providers';
|
|
32
|
+
// The confirmation sits on the task page, behind the password.
|
|
33
|
+
if (action.code === 'confirm-stopped')
|
|
34
|
+
return `/t/${encodeURIComponent(assignment.rootId)}#confirm-stopped`;
|
|
30
35
|
if (action.code === 'open-pr' && /^https:\/\/github\.com\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+\/pull\/\d+$/.test(assignment.publication?.prUrl ?? ''))
|
|
31
36
|
return assignment.publication.prUrl;
|
|
32
37
|
if (runId !== null && (action.code === 'open-result' || action.code === 'open-pr' || action.code === 'retry-review')) {
|
package/dist/assignment.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { type WorkAction, type WorkSummaryAccess } from "./work-summary.js";
|
|
|
3
3
|
import { type VerifiedApprover } from "./principal.js";
|
|
4
4
|
import { type CheckLevel } from "./check-levels.js";
|
|
5
5
|
import { type BuildReviewView } from "./review-switch.js";
|
|
6
|
+
import { type NeedKey, type WaitKey } from "./needs-you.js";
|
|
6
7
|
export type AssignmentAccess = WorkSummaryAccess;
|
|
7
8
|
export type AssignmentOwner = {
|
|
8
9
|
kind: "coordinator" | "lead";
|
|
@@ -99,6 +100,14 @@ export type AssignmentSnapshot = {
|
|
|
99
100
|
deployment: {
|
|
100
101
|
status: "not-recorded";
|
|
101
102
|
};
|
|
103
|
+
/** What this assignment asks of a person, or waits for when no person can act (needs-you.ts). */
|
|
104
|
+
need?: {
|
|
105
|
+
key: NeedKey;
|
|
106
|
+
build: number | null;
|
|
107
|
+
} | {
|
|
108
|
+
wait: WaitKey;
|
|
109
|
+
build: number | null;
|
|
110
|
+
} | null;
|
|
102
111
|
};
|
|
103
112
|
/** Status-first handoff for routine reads. Fetch get_assignment only when
|
|
104
113
|
* inspecting its exact evidence; do not repeat a full proof in every poll. */
|
package/dist/assignment.js
CHANGED
|
@@ -6,7 +6,7 @@ import { currentActor, withActor } from "./actor.js";
|
|
|
6
6
|
import { replacedWords } from "./task-status.js";
|
|
7
7
|
import { COMPLETION_ACTION, familyChangedFiles } from "./result-completion.js";
|
|
8
8
|
import { homedir } from "node:os";
|
|
9
|
-
import { readSchemaVersion } from "./store.js";
|
|
9
|
+
import { readSchemaVersion, stopFactOf } from "./store.js";
|
|
10
10
|
import { approvalOf } from "./scope.js";
|
|
11
11
|
import { openWorkDecisionOf, taskWorkSummaryOf, workDecisionAction } from "./work-summary.js";
|
|
12
12
|
import { evidenceRoot, readVerifiedArtifact, readVerifiedReport } from "./evidence.js";
|
|
@@ -17,6 +17,8 @@ import { historicalAssessmentReason } from "./assignment-presentation.js";
|
|
|
17
17
|
import { runCheckLevel } from "./check-levels.js";
|
|
18
18
|
import { followUpChecksOf, withFollowUps } from "./result-follow-ups.js";
|
|
19
19
|
import { buildReviewOf, findingWords } from "./review-switch.js";
|
|
20
|
+
import { NEEDS, WAITS, processNeedOf } from "./needs-you.js";
|
|
21
|
+
import { assignmentStageOf } from "./task-status.js";
|
|
20
22
|
/** Status-first handoff for routine reads. Fetch get_assignment only when
|
|
21
23
|
* inspecting its exact evidence; do not repeat a full proof in every poll. */
|
|
22
24
|
export function assignmentBrief(assignment) {
|
|
@@ -183,7 +185,9 @@ export function assignmentOf(store, taskId, now, access, root) {
|
|
|
183
185
|
let detail = work.status.detail;
|
|
184
186
|
let primaryAction = work.primaryAction;
|
|
185
187
|
const live = work.liveRunId !== null || work.status.token === "running";
|
|
186
|
-
const
|
|
188
|
+
const processFact = result === null ? null : stopFactOf(store, result.id);
|
|
189
|
+
const processProblem = processFact?.problem ?? null;
|
|
190
|
+
let need = null;
|
|
187
191
|
// The exact completed result, scope, family and custody fences apply to
|
|
188
192
|
// every deliverable. Human acceptance remains its own recorded authority;
|
|
189
193
|
// it never changes a machine verdict or supplies a missing report.
|
|
@@ -205,10 +209,23 @@ export function assignmentOf(store, taskId, now, access, root) {
|
|
|
205
209
|
primaryAction = successor === null ? { code: "inspect-task", label: "View assignment", target: { taskId: current.id, runId: null, decisionId: null }, access: "read", retry: "read-again" }
|
|
206
210
|
: { code: "inspect-task", label: `Open ${successor}`, target: { taskId: successor, runId: null, decisionId: null }, access: "read", retry: "read-again" };
|
|
207
211
|
}
|
|
208
|
-
else if (!live &&
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
+
else if (!live && processFact !== null && result !== null) {
|
|
213
|
+
// Toolroll can't confirm the build stopped. A person confirms it when
|
|
214
|
+
// nothing of it may still run; otherwise the work waits, needing no one.
|
|
215
|
+
const process = processNeedOf(processFact);
|
|
216
|
+
if ("need" in process) {
|
|
217
|
+
state = "needs-decision";
|
|
218
|
+
detail = NEEDS[process.need].sentence({ build: process.build });
|
|
219
|
+
primaryAction = { code: "confirm-stopped", label: NEEDS[process.need].action.label, target: { taskId: current.id, runId: process.build, decisionId: null },
|
|
220
|
+
access: access.principal === "operator" ? "operator-control" : "operator-handoff", retry: "refresh-before-acting" };
|
|
221
|
+
need = { key: process.need, build: process.build };
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
state = "working";
|
|
225
|
+
detail = WAITS[process.wait]({ build: process.build });
|
|
226
|
+
primaryAction = { code: "inspect-run", label: "View the build", target: { taskId: current.id, runId: process.build, decisionId: null }, access: "read", retry: "read-again" };
|
|
227
|
+
need = { wait: process.wait, build: process.build };
|
|
228
|
+
}
|
|
212
229
|
}
|
|
213
230
|
else if (current.state === "done" && questions.length > 0 && work.status.tone !== "problem" &&
|
|
214
231
|
!["stopping", "stopped", "review-failed", "review-exhausted"].includes(work.status.token) &&
|
|
@@ -252,6 +269,14 @@ export function assignmentOf(store, taskId, now, access, root) {
|
|
|
252
269
|
? completionKind === null ? "The research report is missing or incomplete. Inspect the saved report before checking this handoff."
|
|
253
270
|
: "The saved report is awaiting resolution of its current scope or hold."
|
|
254
271
|
: "Inspect the saved result and resolve its remaining execution or scope issue.";
|
|
272
|
+
// Built to an earlier plan: the result page can't resolve it (accepting would leave it stuck), building again can.
|
|
273
|
+
if (current.state === "done" && result !== null && scope !== null && scope.termsProblem == null && approvalOf(scope).approved && !!result.scopeDigest && scope.digest !== result.scopeDigest &&
|
|
274
|
+
store.activeHolds(current.refId, now).length === 0 && unfinished === null && store.currentLiveLease(current.refId, now) === null && store.finalResultReason(result.id) === null) {
|
|
275
|
+
detail = NEEDS.rebuild.sentence({ build: result.id });
|
|
276
|
+
primaryAction = { code: "retry-task", label: NEEDS.rebuild.action.label, target: { taskId: current.id, runId: result.id, decisionId: null },
|
|
277
|
+
access: access.principal === "operator" ? "operator-control" : "operator-handoff", retry: "refresh-before-acting" };
|
|
278
|
+
need = { key: "rebuild", build: result.id };
|
|
279
|
+
}
|
|
255
280
|
}
|
|
256
281
|
if (earlierActive.length > 0) {
|
|
257
282
|
attention.push(`${earlierActive.length} earlier task version${earlierActive.length === 1 ? " is" : "s are"} still active.`);
|
|
@@ -266,6 +291,13 @@ export function assignmentOf(store, taskId, now, access, root) {
|
|
|
266
291
|
}
|
|
267
292
|
if (state === "needs-decision")
|
|
268
293
|
attention.push(detail);
|
|
294
|
+
// Every Needs you action wears its need's own words (needs-you.ts), on every surface.
|
|
295
|
+
if (state === "needs-decision" && primaryAction !== null && need === null) {
|
|
296
|
+
const read = assignmentStageOf({ state, primaryAction, review }, { token: work.status.token, views: work.status.views });
|
|
297
|
+
// A result review keeps its own, more specific words (See what is missing, Open the failed check).
|
|
298
|
+
if (read.stage === "needs-you" && read.need !== undefined && read.need !== "other" && read.need !== "review-result")
|
|
299
|
+
primaryAction = { ...primaryAction, label: NEEDS[read.need].action.label };
|
|
300
|
+
}
|
|
269
301
|
if (receipt !== null) {
|
|
270
302
|
if (finishedBuild && receipt.checks.status !== "passed")
|
|
271
303
|
attention.push(receipt.checks.detail);
|
|
@@ -307,7 +339,7 @@ export function assignmentOf(store, taskId, now, access, root) {
|
|
|
307
339
|
return { version: 1, rootId: family.root.id, activeTaskId: current.id, repo: current.repo, title: family.root.title,
|
|
308
340
|
state, detail, primaryAction, attention: [...new Set(attention)], attempts, owner, receipt, savedContext, completion, handoff,
|
|
309
341
|
publication: publication === null ? null : { state: publication.state, prUrl: publication.prUrl, remoteState: publication.remoteState },
|
|
310
|
-
review, deployment: { status: "not-recorded" } };
|
|
342
|
+
review, deployment: { status: "not-recorded" }, ...(need === null ? {} : { need }) };
|
|
311
343
|
}
|
|
312
344
|
function admittedOwner(store, taskId, owner, now, root) {
|
|
313
345
|
if (owner.kind !== "coordinator")
|