toolroll 0.9.7 → 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 +48 -1
- package/dist/assignment-adapters.d.ts +1 -0
- package/dist/assignment-adapters.js +2 -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 +15 -0
- package/dist/assignment.js +56 -9
- 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/build-review.d.ts +38 -0
- package/dist/build-review.js +341 -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-levels-ui.d.ts +5 -0
- package/dist/check-levels-ui.js +16 -2
- package/dist/check-progress.js +4 -3
- package/dist/checkout-cleanup.d.ts +40 -2
- package/dist/checkout-cleanup.js +129 -9
- package/dist/cli.js +2 -0
- package/dist/desktop-update.d.ts +52 -0
- package/dist/desktop-update.js +147 -5
- package/dist/dispatch.js +2 -1
- package/dist/dispose.d.ts +4 -3
- package/dist/dispose.js +12 -4
- package/dist/evidence.d.ts +6 -0
- package/dist/evidence.js +8 -0
- package/dist/exec.d.ts +5 -0
- package/dist/exec.js +30 -4
- package/dist/first-run.d.ts +1 -1
- package/dist/first-run.js +8 -6
- package/dist/invoke.js +5 -0
- 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/modes.d.ts +3 -1
- package/dist/modes.js +3 -1
- 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 +231 -36
- package/dist/plane-review.js +4 -0
- package/dist/process-custody.js +8 -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/provider.js +16 -0
- package/dist/result-completion.d.ts +4 -0
- package/dist/result-completion.js +11 -0
- package/dist/retention-ui.d.ts +3 -1
- package/dist/retention-ui.js +4 -4
- package/dist/retention.d.ts +13 -6
- package/dist/retention.js +50 -12
- package/dist/review-context.d.ts +3 -0
- package/dist/review-context.js +45 -5
- package/dist/review-switch.d.ts +41 -0
- package/dist/review-switch.js +88 -0
- package/dist/reviewer.d.ts +24 -2
- package/dist/reviewer.js +49 -1
- 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 +158 -18
- 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 +72 -1
- package/dist/store.js +258 -31
- package/dist/surface.js +5 -1
- package/dist/task-status.d.ts +22 -5
- package/dist/task-status.js +45 -24
- 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/work-summary.js +2 -1
- package/dist/workspace-ui.d.ts +3 -1
- package/dist/workspace-ui.js +10 -9
- package/dist/worktree.d.ts +16 -0
- package/dist/worktree.js +47 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,53 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
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
|
+
|
|
25
|
+
## 0.9.8 — 2026-10-01
|
|
26
|
+
|
|
27
|
+
- **One automatic review per build.** When a build's checks pass (or it
|
|
28
|
+
finishes, with checks Off) in a project with review on, one read-only
|
|
29
|
+
reviewer reads the scope, acceptance and diff and grades each finding HIGH,
|
|
30
|
+
MEDIUM or LOW. Any HIGH sends the task back once, with the findings as its
|
|
31
|
+
note; a HIGH on that revision comes to you. MEDIUM and LOW never block: they
|
|
32
|
+
show on the result as suggested follow-ups. A review that fails or times
|
|
33
|
+
out never blocks: the result reaches you marked Not reviewed. Turn it on or
|
|
34
|
+
off per project in Settings or with `toolroll review on|off --repo <path>`;
|
|
35
|
+
it's on by default under a hands-off mode. Release candidate checks are
|
|
36
|
+
never reviewed.
|
|
37
|
+
- **Evidence is kept 1, 7, 14 or 28 days, or forever.** Settings →
|
|
38
|
+
Retention and `toolroll retention set evidence 1d` take any of them. Until
|
|
39
|
+
someone chooses, evidence is kept 28 days: **after upgrading, the daily
|
|
40
|
+
sweep starts removing finished tasks' evidence older than 28 days.** A
|
|
41
|
+
period chosen before stays as it was; one the page no longer offers shows
|
|
42
|
+
as "30 days (custom)".
|
|
43
|
+
Nothing a task still needs is removed: unfinished tasks, results Ready for
|
|
44
|
+
review, anything on hold, live runs and release candidates.
|
|
45
|
+
- **A run whose process never started no longer blocks updates.** A spawn
|
|
46
|
+
that fails now settles its own record, and Toolroll settles a finished run's
|
|
47
|
+
leftover record once its processes are provably gone, noting it in the
|
|
48
|
+
ledger. For the rare record that can't be proven either way, an approver
|
|
49
|
+
can run `toolroll run settle <run> --why "<reason>"`; it refuses while
|
|
50
|
+
anything of the run is alive.
|
|
4
51
|
|
|
5
52
|
## 0.9.7 — 2026-10-01
|
|
6
53
|
|
|
@@ -140,6 +140,7 @@ export declare function assignmentForCoordinator(store: Store, token: string, op
|
|
|
140
140
|
prUrl: string | null;
|
|
141
141
|
remoteState: string | null;
|
|
142
142
|
} | null;
|
|
143
|
+
review: import("./review-switch.js").BuildReviewView | null;
|
|
143
144
|
deployment: {
|
|
144
145
|
status: "not-recorded";
|
|
145
146
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Assignment reads and delivery acknowledgments. These adapters never start
|
|
2
2
|
* work, answer a decision, accept proof, or change an approval. */
|
|
3
|
+
import { buildReviewLines } from "./review-switch.js";
|
|
3
4
|
import { readFileSync, statSync } from "node:fs";
|
|
4
5
|
import { authenticateCoordinator } from "./coordinator.js";
|
|
5
6
|
import { assignmentOf, assignmentUpdates, claimAssignment, checkAssignment } from "./assignment.js";
|
|
@@ -113,6 +114,7 @@ function assignmentLines(assignment) {
|
|
|
113
114
|
...(receipt.agentReport === null ? [] : [`Agent report: ${receipt.agentReport}`]),
|
|
114
115
|
`Receipt: ${receipt.digest}`,
|
|
115
116
|
]),
|
|
117
|
+
...buildReviewLines(assignment.review ?? null).map(line => line.trimStart().replace(/^review: /, "Review: ")),
|
|
116
118
|
...(assignment.savedContext === undefined ? [] : [
|
|
117
119
|
...(assignment.savedContext.goal === null ? [] : [`Goal: ${assignment.savedContext.goal}`]),
|
|
118
120
|
...(assignment.savedContext.outOfScope === null ? [] : [`Out of scope: ${assignment.savedContext.outOfScope}`]),
|
|
@@ -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
|
@@ -2,6 +2,8 @@ import { type Store, type ProofVerdictRow, type ProofAcceptanceRow } from "./sto
|
|
|
2
2
|
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
|
+
import { type BuildReviewView } from "./review-switch.js";
|
|
6
|
+
import { type NeedKey, type WaitKey } from "./needs-you.js";
|
|
5
7
|
export type AssignmentAccess = WorkSummaryAccess;
|
|
6
8
|
export type AssignmentOwner = {
|
|
7
9
|
kind: "coordinator" | "lead";
|
|
@@ -92,9 +94,20 @@ export type AssignmentSnapshot = {
|
|
|
92
94
|
prUrl: string | null;
|
|
93
95
|
remoteState: string | null;
|
|
94
96
|
} | null;
|
|
97
|
+
/** The result's one automatic review, when its project had review on:
|
|
98
|
+
* pending, HIGH findings, suggested follow-ups (MEDIUM/LOW), or not reviewed. */
|
|
99
|
+
review: BuildReviewView | null;
|
|
95
100
|
deployment: {
|
|
96
101
|
status: "not-recorded";
|
|
97
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;
|
|
98
111
|
};
|
|
99
112
|
/** Status-first handoff for routine reads. Fetch get_assignment only when
|
|
100
113
|
* inspecting its exact evidence; do not repeat a full proof in every poll. */
|
|
@@ -142,6 +155,7 @@ export declare function assignmentBrief(assignment: AssignmentSnapshot | null):
|
|
|
142
155
|
prUrl: string | null;
|
|
143
156
|
remoteState: string | null;
|
|
144
157
|
} | null;
|
|
158
|
+
review: BuildReviewView | null;
|
|
145
159
|
deployment: {
|
|
146
160
|
status: "not-recorded";
|
|
147
161
|
};
|
|
@@ -220,6 +234,7 @@ export declare function assignmentUpdates(store: Store, now: Date, access: Assig
|
|
|
220
234
|
prUrl: string | null;
|
|
221
235
|
remoteState: string | null;
|
|
222
236
|
} | null;
|
|
237
|
+
review: BuildReviewView | null;
|
|
223
238
|
deployment: {
|
|
224
239
|
status: "not-recorded";
|
|
225
240
|
};
|
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";
|
|
@@ -16,6 +16,9 @@ import { noteAssignmentStatus } from "./assignment-status.js";
|
|
|
16
16
|
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
|
+
import { buildReviewOf, findingWords } from "./review-switch.js";
|
|
20
|
+
import { NEEDS, WAITS, processNeedOf } from "./needs-you.js";
|
|
21
|
+
import { assignmentStageOf } from "./task-status.js";
|
|
19
22
|
/** Status-first handoff for routine reads. Fetch get_assignment only when
|
|
20
23
|
* inspecting its exact evidence; do not repeat a full proof in every poll. */
|
|
21
24
|
export function assignmentBrief(assignment) {
|
|
@@ -29,7 +32,7 @@ export function assignmentBrief(assignment) {
|
|
|
29
32
|
base: receipt.base, head: receipt.head, completionKind: receipt.completionKind,
|
|
30
33
|
checks: receipt.checks, proofAcceptance: receipt.proofAcceptance, verdict: receipt.proof?.verdict ?? null,
|
|
31
34
|
criteria: { passed: receipt.proof?.matrix.filter(row => row.state === "pass").length ?? 0, total: receipt.proof?.matrix.length ?? 0 }, evidence: receipt.evidence },
|
|
32
|
-
publication: assignment.publication, deployment: assignment.deployment };
|
|
35
|
+
publication: assignment.publication, review: assignment.review, deployment: assignment.deployment };
|
|
33
36
|
}
|
|
34
37
|
const digest = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
35
38
|
/** A lookup added with lead-quiet, read safely: a deploy proves completion with this code over the INSTALLED runtime's
|
|
@@ -182,11 +185,17 @@ export function assignmentOf(store, taskId, now, access, root) {
|
|
|
182
185
|
let detail = work.status.detail;
|
|
183
186
|
let primaryAction = work.primaryAction;
|
|
184
187
|
const live = work.liveRunId !== null || work.status.token === "running";
|
|
185
|
-
const
|
|
188
|
+
const processFact = result === null ? null : stopFactOf(store, result.id);
|
|
189
|
+
const processProblem = processFact?.problem ?? null;
|
|
190
|
+
let need = null;
|
|
186
191
|
// The exact completed result, scope, family and custody fences apply to
|
|
187
192
|
// every deliverable. Human acceptance remains its own recorded authority;
|
|
188
193
|
// it never changes a machine verdict or supplies a missing report.
|
|
189
|
-
|
|
194
|
+
// Kept out of the sealed receipt (sealedChecks): the review never changes the result's digest.
|
|
195
|
+
const review = result === null ? null : olderStoreSafe(() => buildReviewOf(store, result.id), null);
|
|
196
|
+
// A result under its one automatic review has not reached the person yet.
|
|
197
|
+
const reviewing = review?.state === "pending" && current.state === "done";
|
|
198
|
+
const ready = !reviewing && current.state === "done" && result !== null && completionKind !== null &&
|
|
190
199
|
scope !== null && scope.termsProblem == null && approvalOf(scope).approved && scope.digest === result.scopeDigest &&
|
|
191
200
|
store.activeHolds(current.refId, now).length === 0 && store.applicableStopFor(result.id) === null &&
|
|
192
201
|
processProblem === null &&
|
|
@@ -200,10 +209,23 @@ export function assignmentOf(store, taskId, now, access, root) {
|
|
|
200
209
|
primaryAction = successor === null ? { code: "inspect-task", label: "View assignment", target: { taskId: current.id, runId: null, decisionId: null }, access: "read", retry: "read-again" }
|
|
201
210
|
: { code: "inspect-task", label: `Open ${successor}`, target: { taskId: successor, runId: null, decisionId: null }, access: "read", retry: "read-again" };
|
|
202
211
|
}
|
|
203
|
-
else if (!live &&
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
+
}
|
|
207
229
|
}
|
|
208
230
|
else if (current.state === "done" && questions.length > 0 && work.status.tone !== "problem" &&
|
|
209
231
|
!["stopping", "stopped", "review-failed", "review-exhausted"].includes(work.status.token) &&
|
|
@@ -213,6 +235,11 @@ export function assignmentOf(store, taskId, now, access, root) {
|
|
|
213
235
|
detail = question.decision.question;
|
|
214
236
|
primaryAction = workDecisionAction(question.taskId, question.decision, access.principal);
|
|
215
237
|
}
|
|
238
|
+
else if (reviewing && result !== null) {
|
|
239
|
+
state = "checking";
|
|
240
|
+
detail = "An automatic review is reading this result before it reaches you.";
|
|
241
|
+
primaryAction = { code: "inspect-run", label: "Open result", target: { taskId: current.id, runId: result.id, decisionId: null }, access: "read", retry: "read-again" };
|
|
242
|
+
}
|
|
216
243
|
else if (ready && receipt !== null) {
|
|
217
244
|
state = "ready-to-check";
|
|
218
245
|
detail = completionKind === "research-report" ? "The research report is ready for the lead to read."
|
|
@@ -242,6 +269,14 @@ export function assignmentOf(store, taskId, now, access, root) {
|
|
|
242
269
|
? completionKind === null ? "The research report is missing or incomplete. Inspect the saved report before checking this handoff."
|
|
243
270
|
: "The saved report is awaiting resolution of its current scope or hold."
|
|
244
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
|
+
}
|
|
245
280
|
}
|
|
246
281
|
if (earlierActive.length > 0) {
|
|
247
282
|
attention.push(`${earlierActive.length} earlier task version${earlierActive.length === 1 ? " is" : "s are"} still active.`);
|
|
@@ -256,6 +291,13 @@ export function assignmentOf(store, taskId, now, access, root) {
|
|
|
256
291
|
}
|
|
257
292
|
if (state === "needs-decision")
|
|
258
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
|
+
}
|
|
259
301
|
if (receipt !== null) {
|
|
260
302
|
if (finishedBuild && receipt.checks.status !== "passed")
|
|
261
303
|
attention.push(receipt.checks.detail);
|
|
@@ -263,6 +305,11 @@ export function assignmentOf(store, taskId, now, access, root) {
|
|
|
263
305
|
attention.push(...(proof?.reasons ?? []).filter(reason => !historicalAssessmentReason(reason)));
|
|
264
306
|
attention.push(...receipt.caveats.filter(reason => !proof?.reasons.includes(reason)));
|
|
265
307
|
}
|
|
308
|
+
if (review !== null && review.sentBackAs === null) {
|
|
309
|
+
if (review.state === "not-reviewed")
|
|
310
|
+
attention.push(`Not reviewed: ${review.reason ?? "the automatic review did not finish"}.`);
|
|
311
|
+
attention.push(...review.high.map(one => `Review: ${findingWords(one)}`));
|
|
312
|
+
}
|
|
266
313
|
if (state !== "complete")
|
|
267
314
|
completion = null;
|
|
268
315
|
if (current.state !== "cancelled")
|
|
@@ -292,7 +339,7 @@ export function assignmentOf(store, taskId, now, access, root) {
|
|
|
292
339
|
return { version: 1, rootId: family.root.id, activeTaskId: current.id, repo: current.repo, title: family.root.title,
|
|
293
340
|
state, detail, primaryAction, attention: [...new Set(attention)], attempts, owner, receipt, savedContext, completion, handoff,
|
|
294
341
|
publication: publication === null ? null : { state: publication.state, prUrl: publication.prUrl, remoteState: publication.remoteState },
|
|
295
|
-
deployment: { status: "not-recorded" } };
|
|
342
|
+
review, deployment: { status: "not-recorded" }, ...(need === null ? {} : { need }) };
|
|
296
343
|
}
|
|
297
344
|
function admittedOwner(store, taskId, owner, now, root) {
|
|
298
345
|
if (owner.kind !== "coordinator")
|