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/dist/lead-status.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/** Cheap lead-facing status. Every fact comes from indexed SQLite records;
|
|
2
2
|
* this module never opens a repository, retained evidence, or a live log. */
|
|
3
|
-
import {
|
|
3
|
+
import { familyCompleted } from "./result-completion.js";
|
|
4
4
|
import { windowLabel } from "./provider-limits.js";
|
|
5
5
|
import { openAuthPauses, signInCommand, signInReason } from "./provider-auth.js";
|
|
6
6
|
import { BUILT_IN, checkSuitesOf } from "./store.js";
|
|
7
|
-
import { workIndexPage } from "./work-index.js";
|
|
7
|
+
import { workIndexPage, workReadyForReview } from "./work-index.js";
|
|
8
8
|
const maybeNumber = (value) => value === null || value === undefined || !Number.isInteger(Number(value)) ? null : Number(value);
|
|
9
9
|
const CHECK_COLUMNS = "rc.status AS check_status, rc.exit_code AS check_exit_code, rc.suites_json AS check_suites_json";
|
|
10
10
|
function checkOf(row) {
|
|
@@ -90,9 +90,9 @@ export function taskWaitSnapshot(store, taskId, now, watchedRun) {
|
|
|
90
90
|
if (state !== "done")
|
|
91
91
|
return answer("Ready", "Review result", true, 0, "ready");
|
|
92
92
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (
|
|
93
|
+
// A completed family is Complete from any of its tasks.
|
|
94
|
+
const completed = state === "done" && familyCompleted(store, taskId);
|
|
95
|
+
if (completed)
|
|
96
96
|
return answer("Complete", "No action needed", true, 0, "complete");
|
|
97
97
|
if (state === "done")
|
|
98
98
|
return answer("Ready", "Review result", true, 0, "ready");
|
|
@@ -113,7 +113,7 @@ export function taskWaitSnapshot(store, taskId, now, watchedRun) {
|
|
|
113
113
|
return answer("Needs a person", next, true, 1, "needs-person");
|
|
114
114
|
}
|
|
115
115
|
if (task["plan"] === "drafted")
|
|
116
|
-
return answer("Needs a person", "
|
|
116
|
+
return answer("Needs a person", "Approve plan", true, 1, "needs-person");
|
|
117
117
|
if (Number(task["strikes"] ?? 0) >= 3)
|
|
118
118
|
return answer("Needs a person", "Review repeated failures", true, 1, "needs-person");
|
|
119
119
|
// An admitted attempt is already the work being watched. Its immutable
|
|
@@ -168,6 +168,15 @@ function queueReason(row) {
|
|
|
168
168
|
return "ready for a worker";
|
|
169
169
|
}
|
|
170
170
|
/** Whole-installation status, deliberately aggregated and bounded. */
|
|
171
|
+
/** The one-off repair on start. An older build left finished runs' process
|
|
172
|
+
* exits unrecorded and counted completed families as waiting; nothing about
|
|
173
|
+
* a status is stored, so recording every exit that can now be proven and
|
|
174
|
+
* re-deriving each open task's headline from the shared projection clears
|
|
175
|
+
* them. What can't be proven stays as it was. */
|
|
176
|
+
export function repairStaleStatuses(store, now) {
|
|
177
|
+
const exitsRecorded = store.recordFinishedRunExits(now);
|
|
178
|
+
return { exitsRecorded, readyForReview: workReadyForReview(store, now, { principal: "operator", repos: null, includeUnplaced: true }, 0).count };
|
|
179
|
+
}
|
|
171
180
|
export function installationStatus(store, now) {
|
|
172
181
|
const runningRows = store.handle.prepare(`SELECT run.id, run.role, run.phase, ref.external_id AS task
|
|
173
182
|
FROM run INDEXED BY work_unfinished
|
|
@@ -208,22 +217,10 @@ export function installationStatus(store, now) {
|
|
|
208
217
|
if (queuedTasks.length < 8)
|
|
209
218
|
queuedTasks.push({ task: String(row["id"]), reason });
|
|
210
219
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
AND NOT EXISTS (SELECT 1 FROM action_ledger INDEXED BY work_completion
|
|
216
|
-
WHERE action_ledger.task_id = task.id AND action_ledger.action = ? AND action_ledger.source = 'work'
|
|
217
|
-
AND action_ledger.run_id = (SELECT id FROM run INDEXED BY work_result WHERE task_ref = ref.id AND finished_at IS NOT NULL AND role IN ('builder','scout') ORDER BY id DESC LIMIT 1))`)
|
|
218
|
-
.get(BUILT_IN, COMPLETION_ACTION)?.["n"] ?? 0);
|
|
219
|
-
const waitingRows = store.handle.prepare(`SELECT task.id AS task, result.id, ${CHECK_COLUMNS}
|
|
220
|
-
FROM task INDEXED BY task_done_recent
|
|
221
|
-
JOIN task_ref AS ref INDEXED BY sqlite_autoindex_task_ref_1 ON ref.backend = ? AND ref.external_id = task.id
|
|
222
|
-
JOIN run AS result ON result.id = (SELECT id FROM run INDEXED BY work_result WHERE task_ref = ref.id AND finished_at IS NOT NULL AND role IN ('builder','scout') ORDER BY id DESC LIMIT 1)
|
|
223
|
-
LEFT JOIN run_check AS rc ON rc.run = result.id
|
|
224
|
-
WHERE task.state = 'done' AND NOT EXISTS (SELECT 1 FROM action_ledger INDEXED BY work_completion
|
|
225
|
-
WHERE action_ledger.task_id = task.id AND action_ledger.action = ? AND action_ledger.source = 'work' AND action_ledger.run_id = result.id)
|
|
226
|
-
ORDER BY task.updated_at DESC, task.id DESC LIMIT 5`).all(BUILT_IN, COMPLETION_ACTION);
|
|
220
|
+
// One per family, from the same projection as the Tasks list: a completed
|
|
221
|
+
// family (root or any revision) and a superseded version never count.
|
|
222
|
+
const ready = workReadyForReview(store, now, { principal: "operator", repos: null, includeUnplaced: true }, 5);
|
|
223
|
+
const checkRow = (run) => store.handle.prepare(`SELECT ${CHECK_COLUMNS} FROM run_check AS rc WHERE rc.run = ?`).get(run);
|
|
227
224
|
// Run ids grow with time, so the newest finished release check is the
|
|
228
225
|
// first finished row down the partial index.
|
|
229
226
|
const release = store.handle.prepare(`SELECT run.id, run.finished_at, ${CHECK_COLUMNS}, ref.external_id AS task
|
|
@@ -240,7 +237,7 @@ export function installationStatus(store, now) {
|
|
|
240
237
|
reasons: [...reasons].map(([reason, count]) => ({ reason, count })).sort((a, b) => b.count - a.count || a.reason.localeCompare(b.reason)),
|
|
241
238
|
tasks: queuedTasks,
|
|
242
239
|
},
|
|
243
|
-
waitingForReview: { count:
|
|
240
|
+
waitingForReview: { count: ready.count, results: ready.results.map(one => ({ task: one.taskId, run: one.run, check: checkOf(one.run === null ? undefined : checkRow(one.run)) })) },
|
|
244
241
|
releaseCheck: release === undefined ? null : { task: String(release["task"]), run: Number(release["id"]), finishedAt: release["finished_at"] == null ? null : String(release["finished_at"]), check: checkOf(release) },
|
|
245
242
|
planWindows: store.handle.prepare(`SELECT provider, window, used_percent, window_minutes, resets_at, reached, plan, observed_at
|
|
246
243
|
FROM provider_limit INDEXED BY sqlite_autoindex_provider_limit_1 ORDER BY provider, window`).all().map(row => ({
|
|
@@ -271,7 +268,7 @@ export function renderInstallationStatus(status) {
|
|
|
271
268
|
}
|
|
272
269
|
lines.push(status.running.count === 0 ? "Building: none" : `Building: ${status.running.count} — ${status.running.tasks.map(one => `${one.task} (#${one.run}, ${one.phase})`).join(", ")}${status.running.count > status.running.tasks.length ? ", …" : ""}`);
|
|
273
270
|
lines.push(status.queued.count === 0 ? "Queued: none" : `Queued: ${status.queued.count} — ${status.queued.tasks.map(one => `${one.task} (${one.reason})`).join(", ")}${status.queued.count > status.queued.tasks.length ? ", …" : ""}`);
|
|
274
|
-
lines.push(status.waitingForReview.count === 0 ? "Ready for review: none" : `Ready for review: ${status.waitingForReview.count} — ${status.waitingForReview.results.map(one => `${one.task} (#${one.run})`).join(", ")}${status.waitingForReview.count > status.waitingForReview.results.length ? ", …" : ""}`);
|
|
271
|
+
lines.push(status.waitingForReview.count === 0 ? "Ready for review: none" : `Ready for review: ${status.waitingForReview.count} — ${status.waitingForReview.results.map(one => one.run === null ? one.task : `${one.task} (#${one.run})`).join(", ")}${status.waitingForReview.count > status.waitingForReview.results.length ? ", …" : ""}`);
|
|
275
272
|
if (status.releaseCheck === null) {
|
|
276
273
|
lines.push("Release check: none recorded");
|
|
277
274
|
}
|
package/dist/modes.d.ts
CHANGED
|
@@ -29,7 +29,9 @@ export type ModeTerms = {
|
|
|
29
29
|
planAuto: boolean;
|
|
30
30
|
/** Attended mint without the per-mint password — signer only (D8). */
|
|
31
31
|
quickMint: boolean;
|
|
32
|
-
/** Historical signed field; new modes leave it false and no worker consumes
|
|
32
|
+
/** Historical signed field; new modes leave it false and no worker consumes
|
|
33
|
+
* it. Automatic review is the project's review switch (review-switch.ts),
|
|
34
|
+
* on by default while a hands-off mode is active. */
|
|
33
35
|
reviewAuto: boolean;
|
|
34
36
|
/** Historical signed retry term, retained for digest and audit compatibility. */
|
|
35
37
|
reviewRetryAuto: boolean;
|
package/dist/modes.js
CHANGED
|
@@ -157,7 +157,9 @@ export function modeWords(terms) {
|
|
|
157
157
|
terms.planAuto
|
|
158
158
|
? "plans for your pre-authorized filings auto-approve only when the goal, exclusions, paths, acceptance criteria, risk, budget, and agent route remain exactly unchanged; provide a goal, paths and acceptance criteria upfront; amendments and unresolved questions still wait for you"
|
|
159
159
|
: "planner-generated plans wait for your approval",
|
|
160
|
-
|
|
160
|
+
terms.name === "hands-off"
|
|
161
|
+
? "automatic review is on for this project unless you turn it off (`toolroll review off --repo <path>`): each finished build whose check passes (or that finishes, with checks Off) gets one read-only review by the project's review agent; a HIGH finding sends it back once as a revision filed under this mode, a HIGH on that revision comes to you, MEDIUM and LOW findings come to you as suggested follow-ups, and a review that fails or times out never holds the work — it reaches you marked not reviewed"
|
|
162
|
+
: "finished work and saved checks go to the lead or user; automatic review runs only where a project turns it on (`toolroll review on --repo <path>`)",
|
|
161
163
|
...(terms.reviewAuto || terms.reviewRetryAuto
|
|
162
164
|
? ["historical review grants are retained on record but no longer schedule work"] : []),
|
|
163
165
|
terms.perAttemptBudgetMicrousd === null
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** Every reason a task, result or flow card can wait on a person, in one
|
|
2
|
+
* place: the plain sentence it shows and the one action that resolves it
|
|
3
|
+
* (task-status.ts reads it for every surface). A reason no person can act
|
|
4
|
+
* on is not Needs you: it is a Waiting reason, worded as what it waits for.
|
|
5
|
+
*
|
|
6
|
+
* The words never name the machine's internals; `plainSentence` keeps a
|
|
7
|
+
* recorded reason only when it is free of them. */
|
|
8
|
+
/** What a person is asked for. */
|
|
9
|
+
export type NeedKey = "approval" | "card" | "answer" | "questions" | "sign-in" | "confirm-stopped" | "check-stopped" | "hold" | "choose-project" | "define-task" | "fix-request" | "choose-agent" | "fix-dependency" | "add-requirement" | "connect-builder" | "start-builder" | "vanished" | "review-result" | "rebuild" | "earlier-version" | "other";
|
|
10
|
+
/** What the work waits for when no person can act. */
|
|
11
|
+
export type WaitKey = "build-stopping" | "other-computer" | "card-reply" | "card-time" | "card-ci";
|
|
12
|
+
/** The action's code: the WorkAction that owns the act, or one of the acts a surface renders itself. */
|
|
13
|
+
export type NeedActionCode = "approve-scope" | "approve-card" | "answer-decision" | "inspect-decisions" | "sign-in" | "confirm-stopped" | "inspect-hold" | "place-task" | "write-scope" | "select-agent" | "repair-dependency" | "repair-capability" | "start-worker" | "reconcile-run" | "open-result" | "retry-task" | "inspect-task";
|
|
14
|
+
export type NeedAction = {
|
|
15
|
+
code: NeedActionCode;
|
|
16
|
+
label: string;
|
|
17
|
+
};
|
|
18
|
+
/** `build`: the run a sentence names; `provider`: the agent that signed out. */
|
|
19
|
+
export type NeedContext = {
|
|
20
|
+
build?: number | null;
|
|
21
|
+
provider?: string | null;
|
|
22
|
+
};
|
|
23
|
+
/** `useReason`: which recorded reasons say what is needed better than the default words. */
|
|
24
|
+
type Need = {
|
|
25
|
+
sentence: (context: NeedContext) => string;
|
|
26
|
+
action: NeedAction;
|
|
27
|
+
useReason?: (reason: string) => boolean;
|
|
28
|
+
/** How a kept reason reads: by default the reason alone. */
|
|
29
|
+
withReason?: (reason: string) => string;
|
|
30
|
+
};
|
|
31
|
+
export declare const NEEDS: Readonly<Record<NeedKey, Need>>;
|
|
32
|
+
export declare const WAITS: Readonly<Record<WaitKey, (context: NeedContext) => string>>;
|
|
33
|
+
/** Words that name the machine's internals; no Needs you or Waiting sentence carries them. */
|
|
34
|
+
export declare const BANNED_WORDS: readonly ["witness", "unproven", "quiescence", "quiescent", "digest", "scope digest"];
|
|
35
|
+
export declare const hasInternalWords: (text: string) => boolean;
|
|
36
|
+
/** The sentence for a need: its recorded reason when the need keeps one and
|
|
37
|
+
* it reads plainly, otherwise the need's own words. */
|
|
38
|
+
export declare function needSentence(key: NeedKey, reason: string | null, context?: NeedContext): string;
|
|
39
|
+
export declare function waitSentence(key: WaitKey | undefined, reason: string | null, context?: NeedContext): string;
|
|
40
|
+
/** A finished run Toolroll can't prove stopped (store.stopQuiescenceFact):
|
|
41
|
+
* a person can confirm it only when nothing of it may still be running.
|
|
42
|
+
* `unknown`: Toolroll can't check, so the person confirms they checked. */
|
|
43
|
+
export declare function processNeedOf(fact: {
|
|
44
|
+
run: number;
|
|
45
|
+
kind: "open" | "alive" | "elsewhere" | "unprovable" | "unknown";
|
|
46
|
+
} | null): {
|
|
47
|
+
need: "confirm-stopped" | "check-stopped";
|
|
48
|
+
build: number;
|
|
49
|
+
} | {
|
|
50
|
+
wait: WaitKey;
|
|
51
|
+
build: number;
|
|
52
|
+
} | null;
|
|
53
|
+
/** Dispatch and work-index codes that wait on a person, as their need. */
|
|
54
|
+
export declare const CODE_NEED: Readonly<Record<string, NeedKey>>;
|
|
55
|
+
export {};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/** Every reason a task, result or flow card can wait on a person, in one
|
|
2
|
+
* place: the plain sentence it shows and the one action that resolves it
|
|
3
|
+
* (task-status.ts reads it for every surface). A reason no person can act
|
|
4
|
+
* on is not Needs you: it is a Waiting reason, worded as what it waits for.
|
|
5
|
+
*
|
|
6
|
+
* The words never name the machine's internals; `plainSentence` keeps a
|
|
7
|
+
* recorded reason only when it is free of them. */
|
|
8
|
+
const always = () => true;
|
|
9
|
+
const build = (context) => context.build == null ? "the last build" : `build #${context.build}`;
|
|
10
|
+
const Build = (context) => context.build == null ? "The last build" : `Build #${context.build}`;
|
|
11
|
+
export const NEEDS = {
|
|
12
|
+
approval: { sentence: () => "Review the plan and approve it to start.", action: { code: "approve-scope", label: "Approve plan" } },
|
|
13
|
+
card: { sentence: () => "This card is waiting for your approval.", action: { code: "approve-card", label: "Approve card" } },
|
|
14
|
+
answer: { sentence: () => "Answer the question so the work can continue.", action: { code: "answer-decision", label: "Answer the question" }, useReason: always },
|
|
15
|
+
questions: { sentence: () => "Too many questions are open. Answer one so this task can start.", action: { code: "inspect-decisions", label: "Answer questions" } },
|
|
16
|
+
"sign-in": { sentence: context => `${context.provider ?? "Your agent"} needs you to sign in again. The task starts on its own after.`, action: { code: "sign-in", label: "Sign in again" }, useReason: reason => /sign in|API key/i.test(reason) },
|
|
17
|
+
"confirm-stopped": { sentence: context => `Toolroll can't confirm ${build(context)} stopped. Nothing from it is running.`, action: { code: "confirm-stopped", label: "Confirm it stopped" } },
|
|
18
|
+
// Toolroll can't look at all: never "nothing is running"; the person checks, says so, then confirms.
|
|
19
|
+
"check-stopped": { sentence: context => `Toolroll can't check whether ${build(context)} stopped. Make sure nothing from it is running, then confirm.`, action: { code: "confirm-stopped", label: "Confirm it stopped" } },
|
|
20
|
+
hold: { sentence: () => "A hold is keeping this task from starting. Review it to release it.", action: { code: "inspect-hold", label: "Review hold" } },
|
|
21
|
+
"choose-project": { sentence: () => "Choose a project so a builder can start.", action: { code: "place-task", label: "Choose a project" } },
|
|
22
|
+
"define-task": { sentence: () => "Say what the task should do so it can be planned.", action: { code: "write-scope", label: "Define the task" } },
|
|
23
|
+
"fix-request": { sentence: () => "The request can't be planned as written. Edit it so the planner can start.", action: { code: "write-scope", label: "Edit the request" } },
|
|
24
|
+
"choose-agent": { sentence: () => "Choose an agent to build this.", action: { code: "select-agent", label: "Choose an agent" } },
|
|
25
|
+
"fix-dependency": { sentence: () => "A task this one waits on didn't finish. Fix or remove it so this one can start.", action: { code: "repair-dependency", label: "Fix the required task" },
|
|
26
|
+
useReason: reason => /didn't finish|did not finish|before it finished/i.test(reason), withReason: reason => `${reason.replace(/[.\s]*$/, ".")} Fix or remove it so this one can start.` },
|
|
27
|
+
"add-requirement": { sentence: () => "This project is missing something the task needs. Add it so a builder can start.", action: { code: "repair-capability", label: "Add the requirement" } },
|
|
28
|
+
"connect-builder": { sentence: () => "No builder works on this project yet. Connect one so the task can start.", action: { code: "start-worker", label: "Connect a builder" } },
|
|
29
|
+
"start-builder": { sentence: () => "The builder for this project is offline. Start it and the task begins on its own.", action: { code: "start-worker", label: "Start the builder" } },
|
|
30
|
+
vanished: { sentence: context => `${Build(context)} stopped without finishing. Check it, then retry.`, action: { code: "reconcile-run", label: "Check the build" } },
|
|
31
|
+
"review-result": { sentence: () => "Check the result, then accept it or ask for changes.", action: { code: "open-result", label: "Review result" } },
|
|
32
|
+
// Accepting it would not help: it was built to terms that are no longer the plan.
|
|
33
|
+
rebuild: { sentence: context => `${Build(context)} was made to an earlier plan. Build it again to the current plan.`, action: { code: "retry-task", label: "Build again" } },
|
|
34
|
+
"earlier-version": { sentence: () => "An earlier version of this task still needs you. Finish it first.", action: { code: "inspect-task", label: "Open earlier version" } },
|
|
35
|
+
other: { sentence: () => "Open the task to see what it needs from you.", action: { code: "inspect-task", label: "Open the task" }, useReason: always },
|
|
36
|
+
};
|
|
37
|
+
export const WAITS = {
|
|
38
|
+
"build-stopping": context => `Waiting for ${build(context)} to stop. Nothing is needed from you.`,
|
|
39
|
+
"other-computer": context => `Waiting for the computer that ran ${build(context)} to confirm it stopped.`,
|
|
40
|
+
"card-reply": () => "Waiting for a reply.",
|
|
41
|
+
"card-time": () => "Waiting before moving on.",
|
|
42
|
+
"card-ci": () => "Waiting for the pull request's checks.",
|
|
43
|
+
};
|
|
44
|
+
/** Words that name the machine's internals; no Needs you or Waiting sentence carries them. */
|
|
45
|
+
export const BANNED_WORDS = ["witness", "unproven", "quiescence", "quiescent", "digest", "scope digest"];
|
|
46
|
+
const BANNED = new RegExp(`\\b(?:${BANNED_WORDS.join("|")})\\b`, "i");
|
|
47
|
+
export const hasInternalWords = (text) => BANNED.test(text);
|
|
48
|
+
/** The sentence for a need: its recorded reason when the need keeps one and
|
|
49
|
+
* it reads plainly, otherwise the need's own words. */
|
|
50
|
+
export function needSentence(key, reason, context = {}) {
|
|
51
|
+
const need = NEEDS[key];
|
|
52
|
+
const plain = reason?.trim() || null;
|
|
53
|
+
if (need.useReason === undefined || plain === null || !need.useReason(plain) || hasInternalWords(plain))
|
|
54
|
+
return need.sentence(context);
|
|
55
|
+
return need.withReason === undefined ? plain : need.withReason(plain);
|
|
56
|
+
}
|
|
57
|
+
export function waitSentence(key, reason, context = {}) {
|
|
58
|
+
if (key !== undefined)
|
|
59
|
+
return WAITS[key](context);
|
|
60
|
+
const plain = reason?.trim() || null;
|
|
61
|
+
return plain !== null && !hasInternalWords(plain) ? plain : "Waiting for the work to continue on its own.";
|
|
62
|
+
}
|
|
63
|
+
/** A finished run Toolroll can't prove stopped (store.stopQuiescenceFact):
|
|
64
|
+
* a person can confirm it only when nothing of it may still be running.
|
|
65
|
+
* `unknown`: Toolroll can't check, so the person confirms they checked. */
|
|
66
|
+
export function processNeedOf(fact) {
|
|
67
|
+
if (fact === null)
|
|
68
|
+
return null;
|
|
69
|
+
if (fact.kind === "unprovable")
|
|
70
|
+
return { need: "confirm-stopped", build: fact.run };
|
|
71
|
+
if (fact.kind === "unknown")
|
|
72
|
+
return { need: "check-stopped", build: fact.run };
|
|
73
|
+
return { wait: fact.kind === "elsewhere" ? "other-computer" : "build-stopping", build: fact.run };
|
|
74
|
+
}
|
|
75
|
+
/** Dispatch and work-index codes that wait on a person, as their need. */
|
|
76
|
+
export const CODE_NEED = {
|
|
77
|
+
"signed-out": "sign-in", "needs-approval": "approval", "waiting-decision": "answer", "decision-queue": "answer",
|
|
78
|
+
"needs-project": "choose-project", "needs-scope": "define-task", "planner-source": "fix-request", "needs-agent-profile": "choose-agent",
|
|
79
|
+
"terminal-dependency": "fix-dependency", "missing-requirement": "add-requirement", "no-worker-registered": "connect-builder",
|
|
80
|
+
"no-worker-online": "start-builder", "vanished-run": "vanished", "needs-verification": "review-result", "proof-refuted": "review-result",
|
|
81
|
+
"no-build-record": "review-result", "verification-needed": "review-result", "evidence-mismatch": "review-result",
|
|
82
|
+
"record-incomplete": "review-result", "evidence-damaged": "review-result",
|
|
83
|
+
};
|
package/dist/operate.d.ts
CHANGED
|
@@ -37,6 +37,8 @@ export type OperateOptions = {
|
|
|
37
37
|
releaseIo?: ReleaseIo;
|
|
38
38
|
/** Injected by tests: the bin whose real path says how Toolroll was installed. */
|
|
39
39
|
installBin?: string;
|
|
40
|
+
/** Injected by tests: where `storage` looks for leftover test temp folders (default: the temp folder and /tmp). */
|
|
41
|
+
tempRoots?: readonly string[];
|
|
40
42
|
openDatabase?: (file: string) => Store;
|
|
41
43
|
now?: Date;
|
|
42
44
|
/**
|