toolroll 0.9.6 → 0.9.8
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 +64 -1
- package/dist/actor.d.ts +21 -0
- package/dist/actor.js +48 -0
- package/dist/assignment-adapters.d.ts +2 -0
- package/dist/assignment-adapters.js +2 -0
- package/dist/assignment-ui.js +3 -1
- package/dist/assignment.d.ts +15 -0
- package/dist/assignment.js +80 -16
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +28 -24
- package/dist/browser-workspace.d.ts +117 -2
- package/dist/build-review.d.ts +38 -0
- package/dist/build-review.js +341 -0
- package/dist/builder.d.ts +1 -0
- package/dist/builder.js +33 -13
- package/dist/chat-actions.js +2 -1
- package/dist/chat-delivery.js +2 -0
- package/dist/chat-quiet.js +11 -1
- package/dist/chat-task-actions.d.ts +13 -0
- package/dist/chat-task-actions.js +19 -1
- package/dist/check-levels-ui.d.ts +26 -0
- package/dist/check-levels-ui.js +84 -0
- package/dist/check-levels.d.ts +67 -0
- package/dist/check-levels.js +171 -0
- package/dist/claim.d.ts +22 -0
- package/dist/claim.js +43 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +6 -0
- package/dist/demo-chat.js +4 -1
- package/dist/dispatch.js +9 -3
- package/dist/dispose.d.ts +12 -3
- package/dist/dispose.js +19 -5
- package/dist/exec.d.ts +5 -0
- package/dist/exec.js +30 -4
- package/dist/export.js +3 -2
- package/dist/first-run.d.ts +34 -0
- package/dist/first-run.js +37 -1
- package/dist/flow-gallery-ui.d.ts +30 -0
- package/dist/flow-gallery-ui.js +85 -0
- package/dist/flow-gallery.d.ts +111 -0
- package/dist/flow-gallery.js +445 -0
- package/dist/flow-share.d.ts +102 -0
- package/dist/flow-share.js +614 -0
- package/dist/flow-starters.d.ts +2 -0
- package/dist/flow-starters.js +1 -1
- package/dist/flow-steps.js +7 -0
- package/dist/flow-triggers.js +2 -0
- package/dist/flows-cli.d.ts +56 -0
- package/dist/flows-cli.js +109 -3
- package/dist/flows-ui.d.ts +15 -1
- package/dist/flows-ui.js +31 -6
- package/dist/guides.js +27 -2
- package/dist/invoke.js +5 -0
- package/dist/lead-context.js +6 -2
- package/dist/mate-doors.d.ts +0 -15
- package/dist/mate-doors.js +5 -1
- package/dist/mate-tools.js +9 -4
- package/dist/models-ui.js +1 -1
- package/dist/modes.d.ts +3 -1
- package/dist/modes.js +3 -1
- package/dist/operate.d.ts +4 -1
- package/dist/operate.js +711 -98
- package/dist/process-custody.js +8 -0
- package/dist/project-concurrency.d.ts +62 -0
- package/dist/project-concurrency.js +116 -0
- package/dist/project-delete-ui.d.ts +7 -0
- package/dist/project-delete-ui.js +19 -2
- package/dist/project-delete.js +4 -0
- package/dist/provider.js +16 -0
- package/dist/pull-request-flow.d.ts +9 -0
- package/dist/pull-request-flow.js +21 -3
- package/dist/result-follow-ups.d.ts +101 -0
- package/dist/result-follow-ups.js +333 -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-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/scope.js +11 -0
- package/dist/serve.d.ts +211 -2
- package/dist/serve.js +1038 -145
- package/dist/store.d.ts +112 -4
- package/dist/store.js +447 -27
- package/dist/surface.js +36 -9
- package/dist/tailnet.d.ts +11 -0
- package/dist/tailnet.js +47 -0
- package/dist/task-outcome-cli.js +2 -2
- package/dist/task-status.d.ts +13 -3
- package/dist/task-status.js +38 -16
- package/dist/team-cli.js +3 -0
- package/dist/telegram-status.js +2 -2
- package/dist/verification-evidence.js +21 -1
- package/dist/webhooks.js +5 -0
- package/dist/work-index.js +39 -6
- package/dist/work-summary.d.ts +0 -4
- package/dist/work-summary.js +4 -1
- package/dist/workspace-ui.d.ts +6 -1
- package/dist/workspace-ui.js +14 -11
- package/package.json +1 -1
package/dist/surface.js
CHANGED
|
@@ -135,8 +135,27 @@ export const COMMAND_GUIDE = [
|
|
|
135
135
|
flags: [jsonFlag, dbFlag, keyFlag,
|
|
136
136
|
{ name: "id", takesValue: true, meaning: "choose the task id" },
|
|
137
137
|
{ name: "repo", takesValue: true, meaning: "which repository the task belongs to" },
|
|
138
|
-
{ name: "report", takesValue: false, meaning: "a scout task: the deliverable is a report, never a branch" }
|
|
139
|
-
|
|
138
|
+
{ name: "report", takesValue: false, meaning: "a scout task: the deliverable is a report, never a branch" },
|
|
139
|
+
{ name: "replaces", takesValue: true, meaning: "the task this one replaces: it is cancelled and reads \"Replaced by <id>\"" },
|
|
140
|
+
{ name: "token", takesValue: true, meaning: "a lead token: filed as the lead, so it pings nobody" },
|
|
141
|
+
{ name: "checks", takesValue: true, meaning: "quick|full|off: this task's checks, overriding the project's level (\"skip the tests\" is off)" }],
|
|
142
|
+
notableReasons: ["duplicate", "usage", "unknown-task", "unauthenticated"] },
|
|
143
|
+
{ invocation: "task ask", synopsis: "hand a task to a person: one message saying why, and the task is theirs", audience: "agent", agentMayInvoke: true, mutation: "unkeyed",
|
|
144
|
+
positionals: [{ name: "id", required: true, meaning: "the task" }],
|
|
145
|
+
flags: [jsonFlag, dbFlag,
|
|
146
|
+
{ name: "person", takesValue: true, meaning: "who to ask (someone who can see the task's project)" },
|
|
147
|
+
{ name: "why", takesValue: true, meaning: "what they need to do, one line" },
|
|
148
|
+
{ name: "token", takesValue: true, meaning: "the lead token (or TOOLROLL_LEAD_TOKEN)" }],
|
|
149
|
+
notableReasons: ["unknown-task", "unknown-person", "unplaced", "unauthenticated"] },
|
|
150
|
+
{ invocation: "task checks", synopsis: "run checks on the task's latest result, on its exact commit, now; a pass upgrades its status and a failure stays visible",
|
|
151
|
+
audience: "agent", agentMayInvoke: true, mutation: "identity-idempotent",
|
|
152
|
+
positionals: [{ name: "task", required: true, meaning: "any version of the task" }],
|
|
153
|
+
flags: [jsonFlag, dbFlag, { name: "level", takesValue: true, meaning: "quick (default) or full" }],
|
|
154
|
+
notableReasons: ["no-result", "no-check", "checks-failed"] },
|
|
155
|
+
{ invocation: "task add-tests", synopsis: "file a small task to write tests for the task's latest change; it waits for approval like any task",
|
|
156
|
+
audience: "agent", agentMayInvoke: true, mutation: "identity-idempotent",
|
|
157
|
+
positionals: [{ name: "task", required: true, meaning: "any version of the task" }], flags: [jsonFlag, dbFlag],
|
|
158
|
+
notableReasons: ["no-result"] },
|
|
140
159
|
{ invocation: "task list", synopsis: "a page of saved task status", audience: "agent", agentMayInvoke: true, mutation: "none",
|
|
141
160
|
flags: [jsonFlag, dbFlag, { name: "state", takesValue: true, meaning: "queued|running|done|failed|cancelled" },
|
|
142
161
|
{ name: "view", takesValue: true, meaning: "all|needs-you|running|completed" },
|
|
@@ -168,8 +187,9 @@ export const COMMAND_GUIDE = [
|
|
|
168
187
|
{ invocation: "task merge", synopsis: "merge the task's pull request once its checks pass — the console's Merge, behind the approver's password; the project's method (squash by default), branch deleted, recorded in the ledger",
|
|
169
188
|
audience: "operator", agentMayInvoke: false, mutation: "identity-idempotent",
|
|
170
189
|
positionals: [{ name: "task", required: true, meaning: "any version of the task" }],
|
|
171
|
-
flags: [jsonFlag, dbFlag, { name: "as", takesValue: true, meaning: "the approver merging" }, { name: "token", takesValue: true, meaning: "their password" }
|
|
172
|
-
|
|
190
|
+
flags: [jsonFlag, dbFlag, { name: "as", takesValue: true, meaning: "the approver merging" }, { name: "token", takesValue: true, meaning: "their password" },
|
|
191
|
+
{ name: "anyway", takesValue: false, meaning: "merge a Quick or Off result without waiting for its full check" }],
|
|
192
|
+
notableReasons: ["usage", "not-an-approver", "full-checks"] },
|
|
173
193
|
...["show", "updates", "claim", "check", "brief", "inbox", "ack"].map(action => ({
|
|
174
194
|
invocation: `assignment ${action}`,
|
|
175
195
|
synopsis: action === "show" ? "read the root, current work, owner and exact receipt"
|
|
@@ -193,7 +213,8 @@ export const COMMAND_GUIDE = [
|
|
|
193
213
|
...(action === "ack" ? [{ name: "batch", takesValue: true, meaning: "exact batch ID returned by assignment inbox" }] : []),
|
|
194
214
|
],
|
|
195
215
|
})),
|
|
196
|
-
operator("task state", "set a task's state by hand — an operator correction, not a workflow step"),
|
|
216
|
+
operator("task state", "set a task's state by hand — an operator correction, not a workflow step (--replaced-by <id> with cancelled)"),
|
|
217
|
+
operator("lead token", "mint the lead's credential behind your password (--revoke ends it): its own work pings nobody"),
|
|
197
218
|
{ invocation: "task block", synopsis: "make one task wait for another", audience: "agent", agentMayInvoke: true, mutation: "keyed",
|
|
198
219
|
positionals: [{ name: "id", required: true, meaning: "the waiting task" }],
|
|
199
220
|
flags: [jsonFlag, dbFlag, keyFlag, { name: "on", takesValue: true, meaning: "the blocker's id" }],
|
|
@@ -244,12 +265,13 @@ export const COMMAND_GUIDE = [
|
|
|
244
265
|
operator("task approve", "the yes — nothing builds without one; binds to the scope digest"),
|
|
245
266
|
operator("task requeue", "exit a stall: incidents resolved, strikes cleared, queued again"),
|
|
246
267
|
operator("task regate", "explicitly rerun the approved check on the last exact commit — no agent or automatic retry"),
|
|
247
|
-
operator("task review", "
|
|
268
|
+
operator("task review", "show a run's automatic review: HIGH findings, suggested follow-ups, or why it was not reviewed"),
|
|
248
269
|
operator("task accept", "record an explicit acceptance with limitations; does not change checks or prove deployment"),
|
|
249
270
|
operator("task repair", "inspect or approve an existing historical repair draft; use task revise for new feedback"),
|
|
250
271
|
operator("task route", "which configured agents plan, build and repair this task; phase and risk changes require renewed approval"),
|
|
251
272
|
operator("task reopen", "resume external work its tracker closed and has been SEEN open again"),
|
|
252
273
|
operator("task stop", "stop ONE exact live attempt (--run <id>): durable before any process is signalled, answers 'stopping' until its own processes are established gone; work, branch, and evidence preserved; no strike, no retry"),
|
|
274
|
+
operator("run settle", "last resort for a finished run whose process witness can't be proven either way: records your --why in the ledger; refuses while any of the run's processes is alive"),
|
|
253
275
|
operator("task resume", "resume the exact stopped attempt (--run <id>): refuses until it is quiescent, lifts only that stop's hold, approves nothing — the next pass re-proves the scope and inherits the draft with fresh proof"),
|
|
254
276
|
// ---- leases (runner surface) ----
|
|
255
277
|
{ invocation: "claim", synopsis: "take one ready, approved task; answers with a lease", audience: "agent", agentMayInvoke: true, mutation: "keyed",
|
|
@@ -289,7 +311,7 @@ export const COMMAND_GUIDE = [
|
|
|
289
311
|
positionals: [{ name: "id", required: true, meaning: "the routine" }], flags: [jsonFlag, dbFlag] },
|
|
290
312
|
{ invocation: "config show", synopsis: "phase and spend configuration", audience: "agent", agentMayInvoke: true, mutation: "none", flags: [jsonFlag, dbFlag, repoFlag] },
|
|
291
313
|
{ invocation: "setup show", synopsis: "installation setup", audience: "agent", agentMayInvoke: true, mutation: "none", flags: [jsonFlag, dbFlag] },
|
|
292
|
-
{ invocation: "verify show", synopsis: "the repository's approved
|
|
314
|
+
{ invocation: "verify show", synopsis: "the repository's check level, its approved full and quick commands, and a suggested quick command", audience: "agent", agentMayInvoke: true, mutation: "none", flags: [jsonFlag, dbFlag, repoFlag] },
|
|
293
315
|
{ invocation: "intake show", synopsis: "intake configuration", audience: "agent", agentMayInvoke: true, mutation: "none", flags: [jsonFlag, dbFlag] },
|
|
294
316
|
{ invocation: "providers", synopsis: "which agent providers this binary can drive, and their transports; --report records this machine's readiness under its runner", audience: "agent", agentMayInvoke: true, mutation: "none",
|
|
295
317
|
flags: [jsonFlag,
|
|
@@ -309,6 +331,7 @@ export const COMMAND_GUIDE = [
|
|
|
309
331
|
operator("runner retire", "retire a worker"),
|
|
310
332
|
operator("runner bind", "replace which repositories a worker may build in"),
|
|
311
333
|
operator("runner capacity", "set how many tasks a worker runs at once; running work carries on, the new number applies at the next claim"),
|
|
334
|
+
operator("project concurrency", "set how many of a project's tasks build at once (default 2, never past the worker's capacity); running work carries on"),
|
|
312
335
|
operator("coordinator mint", "mint the MCP filing credential — repo-bound, rate-limited, token shown once"),
|
|
313
336
|
operator("coordinator revoke", "revoke an MCP filing credential"),
|
|
314
337
|
operator("mcp", "serve the MCP gateway over stdio — the coordinator credential is the only key it accepts"),
|
|
@@ -336,12 +359,16 @@ export const COMMAND_GUIDE = [
|
|
|
336
359
|
operator("mode set", "sign a per-repository operating mode (password ceremony; standard or hands-off, always expiring)"),
|
|
337
360
|
operator("publish merge", "say yes to ONE waiting merge — it fires only when CI is seen green on the exact commit you authorized"),
|
|
338
361
|
operator("publish refire", "recover a merge whose issued call went silent — re-enters the full merge road, never auto-retried"),
|
|
362
|
+
operatorRead("review show", "whether a project's finished builds get one automatic review"),
|
|
363
|
+
operator("review on", "one automatic review of each finished build whose check passes; only HIGH findings send it back, once"),
|
|
364
|
+
operator("review off", "finished builds go straight to the person, unreviewed"),
|
|
339
365
|
operator("mode revoke", "revoke a repository's mode — one click, every act it covered falls back to its own ceremony"),
|
|
340
366
|
operator("config set", "set phase or spend configuration"),
|
|
341
367
|
operator("config clear", "clear phase or spend configuration"),
|
|
342
368
|
operator("setup clear", "clear installation setup"),
|
|
343
|
-
operator("verify set", "approve the one shell command the plane re-runs after every future commit; --self-heal binds one approved-setup replay when a required project executable is missing"),
|
|
344
|
-
operator("verify clear", "revoke a repository's approved verification command"),
|
|
369
|
+
operator("verify set", "approve the one shell command the plane re-runs after every future commit (--quick \"<cmd>\" approves the fast check Quick runs); --self-heal binds one approved-setup replay when a required project executable is missing"),
|
|
370
|
+
operator("verify clear", "revoke a repository's approved verification command; --level quick revokes only the quick command"),
|
|
371
|
+
operator("verify level", "set a project's checks after each build: quick (the approved quick command), full, or off — an approver's act, in the ledger"),
|
|
345
372
|
operator("intake grant", "grant an intake source"),
|
|
346
373
|
operator("intake run", "run intake now"),
|
|
347
374
|
operator("intake preview", "preview what intake would file"),
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type Runner = (file: string, args: readonly string[], options: {
|
|
2
|
+
timeoutMs: number;
|
|
3
|
+
}) => Promise<{
|
|
4
|
+
code: number | null;
|
|
5
|
+
stdout: string;
|
|
6
|
+
notFound?: boolean;
|
|
7
|
+
}>;
|
|
8
|
+
/** The full MagicDNS name first (machine.tailnet.ts.net), then its short form (machine). Empty when Tailscale isn't running. */
|
|
9
|
+
export declare function tailnetNamesOf(statusJson: string): string[];
|
|
10
|
+
export declare function readTailnetNames(runner?: Runner, platform?: NodeJS.Platform): Promise<string[]>;
|
|
11
|
+
export {};
|
package/dist/tailnet.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This computer's own names on its tailnet, when Tailscale is up: the console
|
|
3
|
+
* answers to them on its port without an --allow-host, and the phone card
|
|
4
|
+
* names the address to type. Read with `tailscale status --json`, which
|
|
5
|
+
* spends nothing and changes nothing; any failure means no tailnet.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync } from "node:fs";
|
|
8
|
+
import { run } from "./exec.js";
|
|
9
|
+
/** The Mac app ships its command line inside the app bundle. */
|
|
10
|
+
const MAC_APP_CLI = "/Applications/Tailscale.app/Contents/MacOS/Tailscale";
|
|
11
|
+
const NAME = /^[a-z0-9]([a-z0-9-]{0,62})(\.[a-z0-9]([a-z0-9-]{0,62}))*$/;
|
|
12
|
+
/** The full MagicDNS name first (machine.tailnet.ts.net), then its short form (machine). Empty when Tailscale isn't running. */
|
|
13
|
+
export function tailnetNamesOf(statusJson) {
|
|
14
|
+
let status;
|
|
15
|
+
try {
|
|
16
|
+
status = JSON.parse(statusJson);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
if (typeof status !== "object" || status === null)
|
|
22
|
+
return [];
|
|
23
|
+
const body = status;
|
|
24
|
+
if (body.BackendState !== "Running" || typeof body.Self?.DNSName !== "string")
|
|
25
|
+
return [];
|
|
26
|
+
const full = body.Self.DNSName.replace(/\.$/, "").toLowerCase();
|
|
27
|
+
if (!NAME.test(full) || full.length > 253)
|
|
28
|
+
return [];
|
|
29
|
+
const short = full.split(".")[0];
|
|
30
|
+
return short === full ? [full] : [full, short];
|
|
31
|
+
}
|
|
32
|
+
export async function readTailnetNames(runner = run, platform = process.platform) {
|
|
33
|
+
const candidates = ["tailscale", ...(platform === "darwin" && existsSync(MAC_APP_CLI) ? [MAC_APP_CLI] : [])];
|
|
34
|
+
for (const file of candidates) {
|
|
35
|
+
try {
|
|
36
|
+
const answer = await runner(file, ["status", "--json"], { timeoutMs: 3_000 });
|
|
37
|
+
if (answer.notFound === true)
|
|
38
|
+
continue;
|
|
39
|
+
if (answer.code === 0)
|
|
40
|
+
return tailnetNamesOf(answer.stdout);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// No Tailscale here: no tailnet names.
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return [];
|
|
47
|
+
}
|
package/dist/task-outcome-cli.js
CHANGED
|
@@ -126,7 +126,7 @@ export async function runTaskMergeCommand(positional, flags, context) {
|
|
|
126
126
|
return result.ok ? 0 : result.reason === 'usage' ? 2 : 3;
|
|
127
127
|
};
|
|
128
128
|
const fail = (reason, message) => emit({ ok: false, reason, message });
|
|
129
|
-
const allowed = new Set(['json', 'db', 'as', 'token']);
|
|
129
|
+
const allowed = new Set(['json', 'db', 'as', 'token', 'anyway']);
|
|
130
130
|
for (const key of flags.keys())
|
|
131
131
|
if (!allowed.has(key))
|
|
132
132
|
return fail('usage', `--${key} is not a task merge option.`);
|
|
@@ -147,7 +147,7 @@ export async function runTaskMergeCommand(positional, flags, context) {
|
|
|
147
147
|
const view = newestPullRequestOf(store, family.versions.map(one => one.id));
|
|
148
148
|
if (view === null)
|
|
149
149
|
return fail('no-pr', 'This task has no pull request. Open one with task complete --pull-request.');
|
|
150
|
-
const merged = await mergeAsPerson(store, { runId: view.runId, name: acting.name, password: acting.token, ...(context.exec === undefined ? {} : { exec: context.exec }), clock: context.clock });
|
|
150
|
+
const merged = await mergeAsPerson(store, { runId: view.runId, name: acting.name, password: acting.token, ...(flags.get('anyway') === true ? { anyway: true } : {}), ...(context.exec === undefined ? {} : { exec: context.exec }), clock: context.clock });
|
|
151
151
|
if (!merged.ok)
|
|
152
152
|
return emit({ ...merged, reason: merged.reason === 'password' ? 'not-an-approver' : merged.reason, pullRequest: view });
|
|
153
153
|
const after = newestPullRequestOf(store, family.versions.map(one => one.id));
|
package/dist/task-status.d.ts
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
* output) is an amber note on its own row with one action; the exact
|
|
12
12
|
* technical reason stays one tap away in `why`. */
|
|
13
13
|
import type { AssignmentSnapshot } from "./assignment.js";
|
|
14
|
+
/** A cancelled task with a successor reads "Replaced by <id>" everywhere, never "Cancelled". */
|
|
15
|
+
export declare const replacedWords: (successor: string) => string;
|
|
14
16
|
export declare const HEADLINES: readonly ["Queued", "Planning", "Needs you", "Building", "Ready for review", "Complete", "Failed", "Stopped"];
|
|
15
17
|
export type Headline = (typeof HEADLINES)[number];
|
|
16
18
|
export type HeadlineTone = "neutral" | "live" | "attention" | "ready" | "success" | "danger";
|
|
@@ -40,11 +42,16 @@ export type TaskStatus = {
|
|
|
40
42
|
primaryAction: StatusAction | null;
|
|
41
43
|
why: string[];
|
|
42
44
|
};
|
|
43
|
-
|
|
45
|
+
/** `reviewing`: the one automatic review (build-review.ts) is reading a finished build; it reads Building, not a headline of its own. */
|
|
46
|
+
export type TaskStage = "queued" | "planning" | "needs-you" | "building" | "checking" | "reviewing" | "finished" | "complete" | "failed" | "stopped";
|
|
44
47
|
export type ChecksFact = {
|
|
45
48
|
status: "passed" | "failed" | "running" | "not-run" | "unavailable";
|
|
46
49
|
exitCode: number | null;
|
|
47
50
|
head: string | null;
|
|
51
|
+
/** The check level that ran (check-levels.ts). Off never reads Ready; a quick pass says so. */
|
|
52
|
+
level?: "quick" | "full" | "off" | null;
|
|
53
|
+
/** A follow-up check (Run checks) waiting or running on this commit. */
|
|
54
|
+
running?: "quick" | "full" | null;
|
|
48
55
|
};
|
|
49
56
|
export type PullRequestFact = {
|
|
50
57
|
state: "none" | "opening" | "open" | "merged" | "closed" | "failed";
|
|
@@ -79,11 +86,12 @@ export type TaskStatusFacts = {
|
|
|
79
86
|
} | null;
|
|
80
87
|
completedBy?: string | null;
|
|
81
88
|
action?: StatusAction | null;
|
|
82
|
-
/** Where a row's one action leads: the result (and its Checks tab), the task's pull request. */
|
|
89
|
+
/** Where a row's one action leads: the result (and its Checks tab), the task's pull request, Run checks. */
|
|
83
90
|
links?: {
|
|
84
91
|
result?: string | null;
|
|
85
92
|
checks?: string | null;
|
|
86
93
|
pullRequest?: string | null;
|
|
94
|
+
runChecks?: string | null;
|
|
87
95
|
};
|
|
88
96
|
/** Exact technical reasons, shown only on request. */
|
|
89
97
|
why?: readonly string[];
|
|
@@ -114,7 +122,9 @@ export declare function pullRequestFactOf(publication: {
|
|
|
114
122
|
} | null): PullRequestFact | null;
|
|
115
123
|
/** The assignment's stage. The work status, when the caller read one, names
|
|
116
124
|
* the exact reason (a running planner, a pause, a failed attempt, a sign-in). */
|
|
117
|
-
export declare function assignmentStageOf(assignment: Pick<AssignmentSnapshot, "state" | "primaryAction"
|
|
125
|
+
export declare function assignmentStageOf(assignment: Pick<AssignmentSnapshot, "state" | "primaryAction"> & {
|
|
126
|
+
review?: AssignmentSnapshot["review"];
|
|
127
|
+
}, work?: {
|
|
118
128
|
token: string;
|
|
119
129
|
views?: readonly string[];
|
|
120
130
|
} | null, planning?: boolean): {
|
package/dist/task-status.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
/** A cancelled task with a successor reads "Replaced by <id>" everywhere, never "Cancelled". */
|
|
2
|
+
export const replacedWords = (successor) => `Replaced by ${successor}`;
|
|
3
|
+
const REPLACED = /^Replaced by \S+/;
|
|
1
4
|
export const HEADLINES = ["Queued", "Planning", "Needs you", "Building", "Ready for review", "Complete", "Failed", "Stopped"];
|
|
2
5
|
export const HEADLINE_TONE = {
|
|
3
6
|
Queued: "neutral", Planning: "live", "Needs you": "attention", Building: "live",
|
|
@@ -10,8 +13,15 @@ export function headlineOf(facts) {
|
|
|
10
13
|
case "planning": return "Planning";
|
|
11
14
|
case "needs-you": return "Needs you";
|
|
12
15
|
case "building":
|
|
13
|
-
case "checking":
|
|
14
|
-
case "
|
|
16
|
+
case "checking":
|
|
17
|
+
case "reviewing": return "Building";
|
|
18
|
+
case "finished":
|
|
19
|
+
if (facts.report)
|
|
20
|
+
return "Ready for review";
|
|
21
|
+
if (facts.checks?.status === "failed")
|
|
22
|
+
return "Failed";
|
|
23
|
+
// Off: ready like any build; the sentence and the Checks row say no check ran.
|
|
24
|
+
return "Ready for review";
|
|
15
25
|
case "complete": return "Complete";
|
|
16
26
|
case "failed": return "Failed";
|
|
17
27
|
case "stopped": return "Stopped";
|
|
@@ -27,12 +37,15 @@ function sentenceOf(headline, facts) {
|
|
|
27
37
|
case "Needs you": return facts.need === "approval" ? "Review the plan and approve it to start." : reason ?? (facts.need === "answer" ? "Answer the question so the work can continue."
|
|
28
38
|
: facts.need === "sign-in" ? "Sign in again; the task starts on its own after."
|
|
29
39
|
: "Something needs your decision before the work can continue.");
|
|
30
|
-
case "Building": return facts.stage === "checking" ? "Checks are running on the change."
|
|
40
|
+
case "Building": return facts.stage === "checking" ? "Checks are running on the change."
|
|
41
|
+
: facts.stage === "reviewing" ? "Reviewing the change before it reaches you." : reason ?? "An agent is working on it.";
|
|
31
42
|
case "Ready for review":
|
|
32
43
|
if (facts.report)
|
|
33
44
|
return "The report is ready to read. Read it, then mark it complete.";
|
|
45
|
+
if (facts.checks?.level === "off" && facts.checks.status !== "passed")
|
|
46
|
+
return "Checks are off for this project. Review the change, then mark it complete.";
|
|
34
47
|
if (facts.checks?.status === "passed")
|
|
35
|
-
return
|
|
48
|
+
return `${facts.checks.level === "quick" ? "Quick checks" : "Checks"} passed${sha === null ? "" : ` on ${sha}`}. Review the change, then mark it complete.`;
|
|
36
49
|
// Never claim a check that isn't known to have passed.
|
|
37
50
|
if (facts.checks == null)
|
|
38
51
|
return "Review the change, then mark it complete.";
|
|
@@ -44,7 +57,7 @@ function sentenceOf(headline, facts) {
|
|
|
44
57
|
}
|
|
45
58
|
case "Failed":
|
|
46
59
|
if (facts.checks?.status === "failed")
|
|
47
|
-
return
|
|
60
|
+
return `${facts.checks.level === "quick" ? "Quick checks" : "Checks"} failed${sha === null ? "" : ` on ${sha}`}. See what broke, then retry or ask for changes.`;
|
|
48
61
|
return reason ?? "The last attempt stopped before it finished. Review it, then retry.";
|
|
49
62
|
case "Stopped": return reason ?? "Stopped by a person. The work so far is kept.";
|
|
50
63
|
}
|
|
@@ -58,14 +71,20 @@ function detailsOf(headline, facts) {
|
|
|
58
71
|
if (checks != null && !facts.report) {
|
|
59
72
|
const sha = short(checks.head);
|
|
60
73
|
const checksHref = facts.links?.checks ?? facts.links?.result ?? null;
|
|
61
|
-
|
|
62
|
-
|
|
74
|
+
const runChecks = facts.links?.runChecks === undefined ? null : { label: "Run checks", href: facts.links.runChecks };
|
|
75
|
+
const quick = checks.level === "quick";
|
|
76
|
+
if (checks.running != null)
|
|
77
|
+
row("checks", "Checks", `${checks.running === "quick" ? "Quick" : "Full"} checks running`, "running", { href: checksHref });
|
|
78
|
+
else if (checks.status === "passed")
|
|
79
|
+
row("checks", "Checks", `${quick ? "Quick checks passed" : "Passed"}${sha === null ? "" : ` on ${sha}`}`, "ok", quick && runChecks !== null && headline !== "Complete" ? { href: checksHref, action: { label: "Run full checks", href: runChecks.href } } : { href: checksHref });
|
|
63
80
|
else if (checks.status === "failed")
|
|
64
|
-
row("checks", "Checks",
|
|
81
|
+
row("checks", "Checks", `${quick ? "Quick checks failed" : "Failed"}${checks.exitCode === null ? "" : ` (exit ${checks.exitCode})`}`, problem(headline), headline === "Failed" ? { href: checksHref } : { action: { label: "See what failed", href: checksHref } });
|
|
65
82
|
else if (checks.status === "running")
|
|
66
83
|
row("checks", "Checks", "Running", "running");
|
|
84
|
+
else if (checks.level === "off")
|
|
85
|
+
row("checks", "Checks", "Off for this project", "none", runChecks === null ? {} : { action: runChecks });
|
|
67
86
|
else if (checks.status === "not-run")
|
|
68
|
-
row("checks", "Checks", "Didn't run", "none");
|
|
87
|
+
row("checks", "Checks", "Didn't run", "none", runChecks === null ? {} : { action: runChecks });
|
|
69
88
|
else
|
|
70
89
|
row("checks", "Checks", "Couldn't be read", "note", { action: { label: "Open the result", href: facts.links?.result ?? null } });
|
|
71
90
|
}
|
|
@@ -123,7 +142,7 @@ export function taskStatusOf(facts) {
|
|
|
123
142
|
const CODE_STAGE = {
|
|
124
143
|
queued: ["queued"], ready: ["queued"], "worker-at-capacity": ["queued"], "retry-scheduled": ["queued"], updating: ["queued"],
|
|
125
144
|
"waiting-dependency": ["queued"], "scouting-ready": ["queued"], "planning-ready": ["queued"], "provider-quota": ["queued"], "planner-source": ["queued"],
|
|
126
|
-
running: ["building"], reviewing: ["
|
|
145
|
+
running: ["building"], reviewing: ["reviewing"], "review-pending": ["building"],
|
|
127
146
|
"signed-out": ["needs-you", "sign-in"],
|
|
128
147
|
"needs-approval": ["needs-you", "approval"],
|
|
129
148
|
"waiting-decision": ["needs-you", "answer"], "decision-queue": ["needs-you", "answer"],
|
|
@@ -137,7 +156,7 @@ const CODE_STAGE = {
|
|
|
137
156
|
* building reason is scheduler wording, and the default sentence is plainer. */
|
|
138
157
|
const QUEUED_REASONS = new Set(["worker-at-capacity", "waiting-dependency", "retry-scheduled", "updating", "planning-ready", "scouting-ready", "provider-quota"]);
|
|
139
158
|
export function plainReasonOf(stage, code, detail) {
|
|
140
|
-
if (stage === "building" || stage === "checking" || stage === "planning")
|
|
159
|
+
if (stage === "building" || stage === "checking" || stage === "reviewing" || stage === "planning")
|
|
141
160
|
return null;
|
|
142
161
|
if (stage === "queued")
|
|
143
162
|
return QUEUED_REASONS.has(code) ? detail ?? null : null;
|
|
@@ -182,7 +201,7 @@ export function assignmentStageOf(assignment, work, planning = false) {
|
|
|
182
201
|
case "cancelled": return { stage: "stopped" };
|
|
183
202
|
case "complete": return { stage: "complete" };
|
|
184
203
|
case "ready-to-check": return { stage: "finished" };
|
|
185
|
-
case "checking": return { stage: "checking" };
|
|
204
|
+
case "checking": return { stage: assignment.review?.state === "pending" ? "reviewing" : "checking" };
|
|
186
205
|
case "working": {
|
|
187
206
|
const token = work?.token ?? "running";
|
|
188
207
|
const read = stageOfCode(token, { needsPerson: work?.views?.includes("needs-you") ?? false, planning });
|
|
@@ -231,14 +250,15 @@ export function assignmentStatusFacts(assignment, options = {}) {
|
|
|
231
250
|
const withResult = finished || stage === "failed" || (stage === "needs-you" && receipt !== null);
|
|
232
251
|
const report = receipt?.completionKind === "research-report";
|
|
233
252
|
const checks = receipt === null || !withResult ? null
|
|
234
|
-
: { status: receipt.checks.status, exitCode: receipt.checks.exitCode, head: receipt.head
|
|
253
|
+
: { status: receipt.checks.status, exitCode: receipt.checks.exitCode, head: receipt.head,
|
|
254
|
+
...(receipt.checks.level == null ? {} : { level: receipt.checks.level }), ...(receipt.checks.running == null ? {} : { running: receipt.checks.running }) };
|
|
235
255
|
const publication = assignment.publication;
|
|
236
256
|
const pullRequest = options.pullRequest !== undefined ? options.pullRequest
|
|
237
257
|
: publication === null ? (withResult && !report ? { state: "none", number: null, url: null, ci: null, error: null } : null)
|
|
238
258
|
: pullRequestFactOf(publication);
|
|
239
259
|
// Ready, Complete and Failed speak for themselves; every other stage keeps its recorded reason.
|
|
240
260
|
const reason = stage === "needs-you" || stage === "stopped" || stage === "queued" || (stage === "failed" && checks?.status !== "failed")
|
|
241
|
-
? stage === "stopped" && assignment.state === "cancelled" ? "Cancelled. Nothing else will run." : plainReasonOf(stage, options.work?.token ?? "", options.work?.detail ?? assignment.detail)
|
|
261
|
+
? stage === "stopped" && assignment.state === "cancelled" ? REPLACED.test(assignment.detail) ? assignment.detail : "Cancelled. Nothing else will run." : plainReasonOf(stage, options.work?.token ?? "", options.work?.detail ?? assignment.detail)
|
|
242
262
|
: null;
|
|
243
263
|
return {
|
|
244
264
|
stage, ...(need === undefined ? {} : { need }), reason, report,
|
|
@@ -246,7 +266,7 @@ export function assignmentStatusFacts(assignment, options = {}) {
|
|
|
246
266
|
pullRequest: withResult ? pullRequest : null,
|
|
247
267
|
requirements: withResult ? requirementsOf(receipt?.proof?.matrix) : null,
|
|
248
268
|
evidence: withResult ? options.evidence ?? evidenceOf(receipt, [...(receipt?.caveats ?? []), ...assignment.attention]) : null,
|
|
249
|
-
completedBy: assignment.completion === null ? null : assignment.completion.actor.replace(/^(?:operator|coordinator|lead):/, ""),
|
|
269
|
+
completedBy: assignment.completion === null ? null : assignment.completion.lead === true ? "the lead" : assignment.completion.actor.replace(/^(?:operator|coordinator|lead):/, ""),
|
|
250
270
|
action: options.action ?? null,
|
|
251
271
|
...(options.links === undefined ? {} : { links: options.links }),
|
|
252
272
|
why: options.why ?? [],
|
|
@@ -307,7 +327,9 @@ export const TASK_STATUS_CSS = `.status-headline{display:flex;align-items:center
|
|
|
307
327
|
* no assignment. A done task is Complete once a person marked its result;
|
|
308
328
|
* a refuted result whose project check failed is Failed. */
|
|
309
329
|
export function stageOfDispatch(d, options = {}) {
|
|
310
|
-
if (d.code === "
|
|
330
|
+
if (d.code === "reviewing")
|
|
331
|
+
return { stage: "reviewing" };
|
|
332
|
+
if (d.code === "complete" || d.code.startsWith("review-"))
|
|
311
333
|
return { stage: options.completed ? "complete" : "finished" };
|
|
312
334
|
if (d.code === "proof-refuted")
|
|
313
335
|
return /approved check failed|checks failed/i.test(d.detail ?? "") ? { stage: "failed" } : { stage: "needs-you", need: "other" };
|
package/dist/team-cli.js
CHANGED
|
@@ -305,6 +305,9 @@ function render(reply, listing) {
|
|
|
305
305
|
/** null means preserve the existing local command path. Nothing here opens a database. */
|
|
306
306
|
export async function maybeRunTeamCommand(argv, write, options = {}) {
|
|
307
307
|
const command = argv[0];
|
|
308
|
+
// `lead token` is local: the credential a lead agent acts with on this computer.
|
|
309
|
+
if (command === 'lead' && argv[1] === 'token')
|
|
310
|
+
return null;
|
|
308
311
|
if (!['connect', 'lead', 'conversation', 'chat', 'brief'].includes(command ?? '') && !argv.includes('--profile'))
|
|
309
312
|
return null;
|
|
310
313
|
const json = argv.includes('--json');
|
package/dist/telegram-status.js
CHANGED
|
@@ -65,7 +65,7 @@ function groupOf(d) {
|
|
|
65
65
|
if (d.condition === "running")
|
|
66
66
|
return "Working";
|
|
67
67
|
if (d.code === "cancelled")
|
|
68
|
-
return "Cancelled";
|
|
68
|
+
return d.summary.startsWith("Replaced by ") ? "Replaced" : "Cancelled";
|
|
69
69
|
if (d.code === "complete")
|
|
70
70
|
return "Finished";
|
|
71
71
|
if (d.code === "review-pending" || d.action === null)
|
|
@@ -105,7 +105,7 @@ export function phoneStatus(store, repos, now, focused = null) {
|
|
|
105
105
|
lines.push("Newest 60 tasks only — older work may still need attention.", "");
|
|
106
106
|
if (snapshot.tasks.length === 0)
|
|
107
107
|
lines.push("No tasks are recorded in these projects.");
|
|
108
|
-
for (const group of ["Needs attention", "Working", "Waiting / next up", "Finished", "Cancelled"]) {
|
|
108
|
+
for (const group of ["Needs attention", "Working", "Waiting / next up", "Finished", "Replaced", "Cancelled"]) {
|
|
109
109
|
const rows = snapshot.tasks.filter(t => t.dispatch !== null && t.dispatch !== undefined && groupOf(t.dispatch) === group);
|
|
110
110
|
if (rows.length === 0)
|
|
111
111
|
continue;
|
|
@@ -7,6 +7,7 @@ import { createHash } from "node:crypto";
|
|
|
7
7
|
import { adjudicate } from "./proof.js";
|
|
8
8
|
import { parseReviewContext, reviewContextCustodyProblem } from "./review-context.js";
|
|
9
9
|
import { readVerifiedArtifact, storeEvidence, scanForSecrets, redactSecretLines } from "./evidence.js";
|
|
10
|
+
import { liveQuickCommand, quickVerifyKey, runCheckLevel } from "./check-levels.js";
|
|
10
11
|
export const VERIFICATION_RECEIPT_CAPTURE = "machine verification receipt v1";
|
|
11
12
|
export const REVIEW_GATE_NAME = "REVIEW-VERIFICATION.json";
|
|
12
13
|
const hash = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
@@ -36,11 +37,15 @@ export function verificationEvidence(store, root, runId) {
|
|
|
36
37
|
if (!source)
|
|
37
38
|
return fail("The verification source run is missing.");
|
|
38
39
|
const repo = store.refById(source.taskRef)?.repo;
|
|
39
|
-
const command = repo ? store.liveVerifyCommand(repo) : null;
|
|
40
40
|
const artifacts = store.artifactsFor(runId);
|
|
41
41
|
const receipts = artifacts.filter(isVerificationReceipt);
|
|
42
42
|
const machine = store.proofVerdictFor(runId);
|
|
43
43
|
const verified = (machine?.machineVerdict ?? machine?.verdict) === "verified";
|
|
44
|
+
// A run whose checks were Off recorded no check; that is not a gap.
|
|
45
|
+
if (!receipts.length && !verified && runCheckLevel(store, runId) === "off")
|
|
46
|
+
return { ok: true, bytes: null, digest: hash(null) };
|
|
47
|
+
// A Quick run is bound to the project's approved quick command, a Full one to the full command.
|
|
48
|
+
const command = repo ? sealedCommandKey(root, receipts) === quickVerifyKey(repo) ? liveQuickCommand(store, repo) : store.liveVerifyCommand(repo) : null;
|
|
44
49
|
if (!command && !receipts.length && !verified)
|
|
45
50
|
return { ok: true, bytes: null, digest: hash(null) };
|
|
46
51
|
if (!command || command.approvedAt > source.startedAt)
|
|
@@ -129,6 +134,21 @@ export function verificationEvidence(store, root, runId) {
|
|
|
129
134
|
const bytes = JSON.stringify(receipt);
|
|
130
135
|
return { ok: true, bytes, digest: hash({ receipt, receipts }) };
|
|
131
136
|
}
|
|
137
|
+
/** The grant key a run's single sealed receipt names, read without trusting it further. */
|
|
138
|
+
function sealedCommandKey(root, receipts) {
|
|
139
|
+
if (receipts.length !== 1)
|
|
140
|
+
return null;
|
|
141
|
+
const sealed = readVerifiedArtifact(root, receipts[0]);
|
|
142
|
+
if (!sealed.ok)
|
|
143
|
+
return null;
|
|
144
|
+
try {
|
|
145
|
+
const repo = JSON.parse(sealed.content.toString("utf8")).command?.repo;
|
|
146
|
+
return typeof repo === "string" ? repo : null;
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
132
152
|
/** Only a sealed failed check can start unattended diagnosis. Lost custody,
|
|
133
153
|
* changed approvals/trees and incomplete evidence still need attention; they
|
|
134
154
|
* must not become another code-writing attempt. Keep the log out of the small
|
package/dist/webhooks.js
CHANGED
|
@@ -267,6 +267,11 @@ export async function webhookPass(store, options) {
|
|
|
267
267
|
return report;
|
|
268
268
|
const claimed = store.claimDeliveries(owner, DELIVERY_CLAIM_MS, clock());
|
|
269
269
|
for (const row of claimed) {
|
|
270
|
+
// The lead's own work posts to no channel; the console keeps it.
|
|
271
|
+
if (store.leadQuiet(row)) {
|
|
272
|
+
store.finalizeDelivery(row.id, owner, { ok: true, receipt: "skipped:quiet" }, clock());
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
270
275
|
const link = linkFor(options.consoleUrl, row);
|
|
271
276
|
const outcomes = await Promise.all(options.targets.map(target => postWebhook(target, row, link, options.fetcher ?? fetch)));
|
|
272
277
|
const failed = outcomes.filter(one => !one.ok);
|
package/dist/work-index.js
CHANGED
|
@@ -4,7 +4,9 @@ import { createHash } from 'node:crypto';
|
|
|
4
4
|
import { readProjectAccess } from './project-access.js';
|
|
5
5
|
import { pauseOnTaskProviders, providerName, signInReason } from './provider-auth.js';
|
|
6
6
|
import { scopeTermsProblem } from './store.js';
|
|
7
|
-
import { plainReasonOf, stageOfCode, taskStatusOf, workToneOf } from './task-status.js';
|
|
7
|
+
import { plainReasonOf, replacedWords, stageOfCode, taskStatusOf, workToneOf } from './task-status.js';
|
|
8
|
+
import { isCheckLevel } from './check-levels.js';
|
|
9
|
+
import { withFollowUps } from './result-follow-ups.js';
|
|
8
10
|
export const WORK_INDEX_PAGE_LIMIT = 40;
|
|
9
11
|
export const WORK_INDEX_MAX_LIMIT = 100;
|
|
10
12
|
const EMPTY = { all: 0, 'needs-you': 0, running: 0, completed: 0 };
|
|
@@ -297,11 +299,15 @@ export function workIndexPage(store, now, access, options = {}) {
|
|
|
297
299
|
'publication_url',(SELECT pr_url FROM publication WHERE run=page_result_id),
|
|
298
300
|
'result_role',(SELECT role FROM run WHERE id=page_result_id),'live_role',(SELECT role FROM run WHERE id=page.live_run),
|
|
299
301
|
'check_status',(SELECT status FROM run_check WHERE run=page_result_id),
|
|
302
|
+
'check_level',(SELECT outcome FROM action_ledger WHERE repo IS page.repo AND run_id=page_result_id AND action='checks used' ORDER BY id DESC LIMIT 1),
|
|
303
|
+
'check_follows',(SELECT json_group_array(q.outcome||' '||COALESCE((SELECT substr(f.outcome,instr(f.outcome,':')+1) FROM action_ledger f WHERE f.repo IS page.repo AND f.run_id=page_result_id
|
|
304
|
+
AND f.action='checks finished' AND f.outcome LIKE q.id||':%' LIMIT 1),'open')) FROM (SELECT id,outcome FROM action_ledger WHERE repo IS page.repo AND run_id=page_result_id AND action='checks requested' ORDER BY id) q),
|
|
300
305
|
'proof_verdict',(SELECT COALESCE(machine_verdict,verdict) FROM proof_verdict WHERE run=page_result_id),
|
|
301
306
|
'proof_failed_check',(SELECT verdict='refuted' AND reasons_json LIKE '%approved verification command exited%' FROM proof_verdict WHERE run=page_result_id),'code',code,'needs',needs,'family_running',family_running,'rank',rank,'sort_at',sort_at,
|
|
302
307
|
'checked_actor',checked_actor,'checked_at',checked_at,'checked_digest',checked_digest,'question_id',question_id,
|
|
303
308
|
'dependency_id',dependency_id,'dependency_state',dependency_state,'custody_unresolved',custody_unresolved,'hold_kind',hold_kind,'hold_reason',substr(hold_reason,1,300),'stop_run',stop_run,'unfinished',unfinished,'signed_out',signed_out,
|
|
304
|
-
'question_run',(SELECT run FROM decision WHERE id=page.question_id),
|
|
309
|
+
'question_run',(SELECT run FROM decision WHERE id=page.question_id),'replaced_by',(SELECT successor FROM task_replacement WHERE task_ref=page.ref_id),
|
|
310
|
+
'completed_by_lead',(SELECT lead FROM task_act WHERE task_ref=page.root_ref AND act='completed' ORDER BY id DESC LIMIT 1),
|
|
305
311
|
'question_task',(SELECT r.external_id FROM decision d JOIN run ON run.id=d.run JOIN task_ref r ON r.id=run.task_ref WHERE d.id=page.question_id)) FROM page`)
|
|
306
312
|
.all({ ...parameters(now, access, options), $cursorRoot: cursor?.root ?? 0, $cursorRank: cursor?.rank ?? 0, $cursorAt: cursor?.at ?? '', $limit: limit + 1, $recent: new Date(now.getTime() - 86_400_000).toISOString() });
|
|
307
313
|
const selected = rows.slice(1).map(row => JSON.parse(String(row['row_json'])));
|
|
@@ -320,6 +326,27 @@ export function workCountsByProject(store, now, access) {
|
|
|
320
326
|
}
|
|
321
327
|
/** Labels stay navigation, never an authorization to mutate. Full task
|
|
322
328
|
* opening re-proves process custody, receipt bytes and approval terms. */
|
|
329
|
+
/** The list's Checks: the build's own result, its level, and follow-up checks on the same commit (result-follow-ups.ts). */
|
|
330
|
+
function listChecksOf(own, level, follows) {
|
|
331
|
+
let done = [];
|
|
332
|
+
try {
|
|
333
|
+
// The row's JSON already holds the list; an older reader may hand over its text.
|
|
334
|
+
done = (typeof follows === 'string' ? JSON.parse(follows) : Array.isArray(follows) ? follows : []).flatMap(one => {
|
|
335
|
+
const [ask, said] = String(one).split(' ');
|
|
336
|
+
if (ask !== 'quick' && ask !== 'full')
|
|
337
|
+
return [];
|
|
338
|
+
return [{ level: ask, state: said === 'passed' || said === 'failed' ? said : said === 'open' ? 'waiting' : 'not-run', head: '', exitCode: null }];
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
catch {
|
|
342
|
+
done = [];
|
|
343
|
+
}
|
|
344
|
+
const ran = isCheckLevel(level) ? level : null;
|
|
345
|
+
const read = withFollowUps({ status: own ?? 'not-run', level: ran, exitCode: null, head: null }, done);
|
|
346
|
+
if (own === null && read.status === 'not-run' && ran !== 'off' && read.running === null)
|
|
347
|
+
return null;
|
|
348
|
+
return { status: read.status, exitCode: null, head: null, level: read.level, running: read.running };
|
|
349
|
+
}
|
|
323
350
|
function itemOf(row, principal) {
|
|
324
351
|
const code = String(row['code']), id = String(row['id']);
|
|
325
352
|
const need = n(row, 'needs') === 1, running = n(row, 'family_running') === 1;
|
|
@@ -368,6 +395,12 @@ function itemOf(row, principal) {
|
|
|
368
395
|
}
|
|
369
396
|
if (code === 'running' && n(row, 'version_count') > 1)
|
|
370
397
|
label = 'Revising';
|
|
398
|
+
// Replaced, never "Cancelled": the successor is one tap away.
|
|
399
|
+
const successor = code === 'cancelled' ? s(row, 'replaced_by') : null;
|
|
400
|
+
if (successor !== null) {
|
|
401
|
+
detail = `${replacedWords(successor)}.`;
|
|
402
|
+
actionLabel = `Open ${successor}`;
|
|
403
|
+
}
|
|
371
404
|
if (code === 'terminal-dependency' && s(row, 'dependency_id') !== null)
|
|
372
405
|
detail = `${s(row, 'dependency_id')} ${s(row, 'dependency_state') === 'cancelled' ? 'was cancelled' : 'failed'} before it finished.`;
|
|
373
406
|
if (code === 'result-needs-attention' && n(row, 'custody_unresolved'))
|
|
@@ -382,7 +415,7 @@ function itemOf(row, principal) {
|
|
|
382
415
|
}
|
|
383
416
|
const read = actionCode !== null && ['inspect-task', 'inspect-run', 'inspect-stop', 'inspect-decisions', 'open-result'].includes(actionCode);
|
|
384
417
|
const primaryAction = actionCode === null ? null : { code: actionCode, label: actionLabel,
|
|
385
|
-
target: { taskId: code === 'earlier-active' ? s(row, 'earlier_id') ?? id : actionCode === 'answer-decision' ? s(row, 'question_task') ?? id : id,
|
|
418
|
+
target: { taskId: successor ?? (code === 'earlier-active' ? s(row, 'earlier_id') ?? id : actionCode === 'answer-decision' ? s(row, 'question_task') ?? id : id),
|
|
386
419
|
runId: actionCode === 'answer-decision' ? n(row, 'question_run') || null : actionCode === 'inspect-run' && code === 'running' ? n(row, 'live_run') || null : actionCode === 'open-result' || actionCode === 'inspect-run' ? n(row, 'result_id') || null
|
|
387
420
|
: actionCode === 'reconcile-run' ? n(row, 'unfinished') || null : ['resume-run', 'inspect-stop'].includes(actionCode) ? n(row, 'stop_run') || null : null,
|
|
388
421
|
decisionId: actionCode === 'answer-decision' ? n(row, 'question_id') || null : null },
|
|
@@ -392,11 +425,11 @@ function itemOf(row, principal) {
|
|
|
392
425
|
const reading = stageOfCode(code, { needsPerson: need, planning: s(row, 'live_role') === 'planner', operatorHold: s(row, 'hold_kind') === 'operator' });
|
|
393
426
|
const checkStatus = s(row, 'check_status'), verdict = s(row, 'proof_verdict');
|
|
394
427
|
const finished = reading.stage === 'finished' || reading.stage === 'complete';
|
|
428
|
+
const own = checkStatus === 'failed' || n(row, 'proof_failed_check') === 1 ? 'failed' : checkStatus === 'passed' || verdict === 'verified' ? 'passed' : null;
|
|
395
429
|
const shared = taskStatusOf({ stage: reading.stage, ...(reading.need === undefined ? {} : { need: reading.need }),
|
|
396
430
|
reason: finished ? null : plainReasonOf(reading.stage, code, detail || null), report: s(row, 'result_role') === 'scout',
|
|
397
|
-
checks: !finished ? null :
|
|
398
|
-
|
|
399
|
-
completedBy: code === 'complete' ? String(row['checked_actor']).replace(/^(?:operator|coordinator|lead):/, '') : null });
|
|
431
|
+
checks: !finished ? null : listChecksOf(own, s(row, 'check_level'), row['check_follows']),
|
|
432
|
+
completedBy: code !== 'complete' ? null : n(row, 'completed_by_lead') === 1 ? 'the lead' : String(row['checked_actor']).replace(/^(?:operator|coordinator|lead):/, '') });
|
|
400
433
|
label = shared.headline;
|
|
401
434
|
detail = shared.sentence;
|
|
402
435
|
const views = ['all'];
|
package/dist/work-summary.d.ts
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/** Read-only work context shared by console, CLI, and agent adapters.
|
|
2
|
-
* State, decisions, approval, process custody and proof still belong to
|
|
3
|
-
* their existing owners. Action hints are navigation/proposal guidance,
|
|
4
|
-
* never credentials or permission to bypass an owning operation. */
|
|
5
1
|
import { type DispatchAction } from "./dispatch.js";
|
|
6
2
|
import type { Store } from "./store.js";
|
|
7
3
|
import { type DisplayStatus, type WorkFacts, type WorkStatus } from "./workspace-ui.js";
|
package/dist/work-summary.js
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
* State, decisions, approval, process custody and proof still belong to
|
|
3
3
|
* their existing owners. Action hints are navigation/proposal guidance,
|
|
4
4
|
* never credentials or permission to bypass an owning operation. */
|
|
5
|
+
import { resultCheckLevel } from "./result-follow-ups.js";
|
|
5
6
|
import { diagnoseTaskDispatch } from "./dispatch.js";
|
|
6
7
|
import { isAlive } from "./runner.js";
|
|
7
8
|
import { taskControlOf } from "./task-control.js";
|
|
9
|
+
import { buildReviewOf } from "./review-switch.js";
|
|
8
10
|
import { reviewFactsOf, workStatusOf } from "./workspace-ui.js";
|
|
9
11
|
/** Call only after admitting the task's project. Keep the question's
|
|
10
12
|
* exact run and id; never substitute a family's newest attempt. Expiry
|
|
@@ -98,10 +100,11 @@ export function taskWorkSummaryOf(store, taskId, now, access) {
|
|
|
98
100
|
result: result === null ? null : {
|
|
99
101
|
runId: result.id, role: result.role, outcome: result.outcome,
|
|
100
102
|
verdict: proof?.verdict ?? null, reasons: proof?.reasons ?? [], accepted: store.proofAcceptance(result.id) !== null,
|
|
103
|
+
checkLevel: resultCheckLevel(store, result.id, now),
|
|
101
104
|
review: reviewFactsOf(store.reviewRetryStateOf(result.id), name => {
|
|
102
105
|
const runner = store.getRunner(name)?.runner;
|
|
103
106
|
return runner !== undefined && isAlive(runner, now);
|
|
104
|
-
}),
|
|
107
|
+
}, buildReviewOf(store, result.id)?.state === "pending"),
|
|
105
108
|
...(result.outcome !== "no-change" ? {} : { recordComplete: (() => {
|
|
106
109
|
const kinds = new Set(store.artifactsFor(result.id).map(one => one.kind));
|
|
107
110
|
return kinds.has("handoff") && kinds.has("terminal-diff");
|