toolroll 0.9.6 → 0.9.7
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 +38 -0
- package/dist/actor.d.ts +21 -0
- package/dist/actor.js +48 -0
- package/dist/assignment-adapters.d.ts +1 -0
- package/dist/assignment-ui.js +3 -1
- package/dist/assignment.d.ts +9 -0
- package/dist/assignment.js +62 -13
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +28 -24
- package/dist/browser-workspace.d.ts +117 -2
- 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 +21 -0
- package/dist/check-levels-ui.js +70 -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 +4 -0
- package/dist/demo-chat.js +4 -1
- package/dist/dispatch.js +7 -2
- package/dist/dispose.d.ts +8 -0
- package/dist/dispose.js +7 -1
- 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/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/operate.d.ts +4 -1
- package/dist/operate.js +601 -85
- 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/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/scope.js +11 -0
- package/dist/serve.d.ts +211 -2
- package/dist/serve.js +1009 -141
- package/dist/store.d.ts +76 -3
- package/dist/store.js +311 -22
- package/dist/surface.js +31 -8
- 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 +8 -1
- package/dist/task-status.js +28 -10
- 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 +2 -0
- package/dist/workspace-ui.d.ts +3 -0
- package/dist/workspace-ui.js +5 -3
- 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" }],
|
|
@@ -289,7 +310,7 @@ export const COMMAND_GUIDE = [
|
|
|
289
310
|
positionals: [{ name: "id", required: true, meaning: "the routine" }], flags: [jsonFlag, dbFlag] },
|
|
290
311
|
{ invocation: "config show", synopsis: "phase and spend configuration", audience: "agent", agentMayInvoke: true, mutation: "none", flags: [jsonFlag, dbFlag, repoFlag] },
|
|
291
312
|
{ invocation: "setup show", synopsis: "installation setup", audience: "agent", agentMayInvoke: true, mutation: "none", flags: [jsonFlag, dbFlag] },
|
|
292
|
-
{ invocation: "verify show", synopsis: "the repository's approved
|
|
313
|
+
{ 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
314
|
{ invocation: "intake show", synopsis: "intake configuration", audience: "agent", agentMayInvoke: true, mutation: "none", flags: [jsonFlag, dbFlag] },
|
|
294
315
|
{ 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
316
|
flags: [jsonFlag,
|
|
@@ -309,6 +330,7 @@ export const COMMAND_GUIDE = [
|
|
|
309
330
|
operator("runner retire", "retire a worker"),
|
|
310
331
|
operator("runner bind", "replace which repositories a worker may build in"),
|
|
311
332
|
operator("runner capacity", "set how many tasks a worker runs at once; running work carries on, the new number applies at the next claim"),
|
|
333
|
+
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
334
|
operator("coordinator mint", "mint the MCP filing credential — repo-bound, rate-limited, token shown once"),
|
|
313
335
|
operator("coordinator revoke", "revoke an MCP filing credential"),
|
|
314
336
|
operator("mcp", "serve the MCP gateway over stdio — the coordinator credential is the only key it accepts"),
|
|
@@ -340,8 +362,9 @@ export const COMMAND_GUIDE = [
|
|
|
340
362
|
operator("config set", "set phase or spend configuration"),
|
|
341
363
|
operator("config clear", "clear phase or spend configuration"),
|
|
342
364
|
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"),
|
|
365
|
+
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"),
|
|
366
|
+
operator("verify clear", "revoke a repository's approved verification command; --level quick revokes only the quick command"),
|
|
367
|
+
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
368
|
operator("intake grant", "grant an intake source"),
|
|
346
369
|
operator("intake run", "run intake now"),
|
|
347
370
|
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";
|
|
@@ -45,6 +47,10 @@ export type ChecksFact = {
|
|
|
45
47
|
status: "passed" | "failed" | "running" | "not-run" | "unavailable";
|
|
46
48
|
exitCode: number | null;
|
|
47
49
|
head: string | null;
|
|
50
|
+
/** The check level that ran (check-levels.ts). Off never reads Ready; a quick pass says so. */
|
|
51
|
+
level?: "quick" | "full" | "off" | null;
|
|
52
|
+
/** A follow-up check (Run checks) waiting or running on this commit. */
|
|
53
|
+
running?: "quick" | "full" | null;
|
|
48
54
|
};
|
|
49
55
|
export type PullRequestFact = {
|
|
50
56
|
state: "none" | "opening" | "open" | "merged" | "closed" | "failed";
|
|
@@ -79,11 +85,12 @@ export type TaskStatusFacts = {
|
|
|
79
85
|
} | null;
|
|
80
86
|
completedBy?: string | null;
|
|
81
87
|
action?: StatusAction | null;
|
|
82
|
-
/** Where a row's one action leads: the result (and its Checks tab), the task's pull request. */
|
|
88
|
+
/** Where a row's one action leads: the result (and its Checks tab), the task's pull request, Run checks. */
|
|
83
89
|
links?: {
|
|
84
90
|
result?: string | null;
|
|
85
91
|
checks?: string | null;
|
|
86
92
|
pullRequest?: string | null;
|
|
93
|
+
runChecks?: string | null;
|
|
87
94
|
};
|
|
88
95
|
/** Exact technical reasons, shown only on request. */
|
|
89
96
|
why?: readonly string[];
|
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",
|
|
@@ -11,7 +14,13 @@ export function headlineOf(facts) {
|
|
|
11
14
|
case "needs-you": return "Needs you";
|
|
12
15
|
case "building":
|
|
13
16
|
case "checking": return "Building";
|
|
14
|
-
case "finished":
|
|
17
|
+
case "finished":
|
|
18
|
+
if (facts.report)
|
|
19
|
+
return "Ready for review";
|
|
20
|
+
if (facts.checks?.status === "failed")
|
|
21
|
+
return "Failed";
|
|
22
|
+
// Off: ready like any build; the sentence and the Checks row say no check ran.
|
|
23
|
+
return "Ready for review";
|
|
15
24
|
case "complete": return "Complete";
|
|
16
25
|
case "failed": return "Failed";
|
|
17
26
|
case "stopped": return "Stopped";
|
|
@@ -31,8 +40,10 @@ function sentenceOf(headline, facts) {
|
|
|
31
40
|
case "Ready for review":
|
|
32
41
|
if (facts.report)
|
|
33
42
|
return "The report is ready to read. Read it, then mark it complete.";
|
|
43
|
+
if (facts.checks?.level === "off" && facts.checks.status !== "passed")
|
|
44
|
+
return "Checks are off for this project. Review the change, then mark it complete.";
|
|
34
45
|
if (facts.checks?.status === "passed")
|
|
35
|
-
return
|
|
46
|
+
return `${facts.checks.level === "quick" ? "Quick checks" : "Checks"} passed${sha === null ? "" : ` on ${sha}`}. Review the change, then mark it complete.`;
|
|
36
47
|
// Never claim a check that isn't known to have passed.
|
|
37
48
|
if (facts.checks == null)
|
|
38
49
|
return "Review the change, then mark it complete.";
|
|
@@ -44,7 +55,7 @@ function sentenceOf(headline, facts) {
|
|
|
44
55
|
}
|
|
45
56
|
case "Failed":
|
|
46
57
|
if (facts.checks?.status === "failed")
|
|
47
|
-
return
|
|
58
|
+
return `${facts.checks.level === "quick" ? "Quick checks" : "Checks"} failed${sha === null ? "" : ` on ${sha}`}. See what broke, then retry or ask for changes.`;
|
|
48
59
|
return reason ?? "The last attempt stopped before it finished. Review it, then retry.";
|
|
49
60
|
case "Stopped": return reason ?? "Stopped by a person. The work so far is kept.";
|
|
50
61
|
}
|
|
@@ -58,14 +69,20 @@ function detailsOf(headline, facts) {
|
|
|
58
69
|
if (checks != null && !facts.report) {
|
|
59
70
|
const sha = short(checks.head);
|
|
60
71
|
const checksHref = facts.links?.checks ?? facts.links?.result ?? null;
|
|
61
|
-
|
|
62
|
-
|
|
72
|
+
const runChecks = facts.links?.runChecks === undefined ? null : { label: "Run checks", href: facts.links.runChecks };
|
|
73
|
+
const quick = checks.level === "quick";
|
|
74
|
+
if (checks.running != null)
|
|
75
|
+
row("checks", "Checks", `${checks.running === "quick" ? "Quick" : "Full"} checks running`, "running", { href: checksHref });
|
|
76
|
+
else if (checks.status === "passed")
|
|
77
|
+
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
78
|
else if (checks.status === "failed")
|
|
64
|
-
row("checks", "Checks",
|
|
79
|
+
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
80
|
else if (checks.status === "running")
|
|
66
81
|
row("checks", "Checks", "Running", "running");
|
|
82
|
+
else if (checks.level === "off")
|
|
83
|
+
row("checks", "Checks", "Off for this project", "none", runChecks === null ? {} : { action: runChecks });
|
|
67
84
|
else if (checks.status === "not-run")
|
|
68
|
-
row("checks", "Checks", "Didn't run", "none");
|
|
85
|
+
row("checks", "Checks", "Didn't run", "none", runChecks === null ? {} : { action: runChecks });
|
|
69
86
|
else
|
|
70
87
|
row("checks", "Checks", "Couldn't be read", "note", { action: { label: "Open the result", href: facts.links?.result ?? null } });
|
|
71
88
|
}
|
|
@@ -231,14 +248,15 @@ export function assignmentStatusFacts(assignment, options = {}) {
|
|
|
231
248
|
const withResult = finished || stage === "failed" || (stage === "needs-you" && receipt !== null);
|
|
232
249
|
const report = receipt?.completionKind === "research-report";
|
|
233
250
|
const checks = receipt === null || !withResult ? null
|
|
234
|
-
: { status: receipt.checks.status, exitCode: receipt.checks.exitCode, head: receipt.head
|
|
251
|
+
: { status: receipt.checks.status, exitCode: receipt.checks.exitCode, head: receipt.head,
|
|
252
|
+
...(receipt.checks.level == null ? {} : { level: receipt.checks.level }), ...(receipt.checks.running == null ? {} : { running: receipt.checks.running }) };
|
|
235
253
|
const publication = assignment.publication;
|
|
236
254
|
const pullRequest = options.pullRequest !== undefined ? options.pullRequest
|
|
237
255
|
: publication === null ? (withResult && !report ? { state: "none", number: null, url: null, ci: null, error: null } : null)
|
|
238
256
|
: pullRequestFactOf(publication);
|
|
239
257
|
// Ready, Complete and Failed speak for themselves; every other stage keeps its recorded reason.
|
|
240
258
|
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)
|
|
259
|
+
? 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
260
|
: null;
|
|
243
261
|
return {
|
|
244
262
|
stage, ...(need === undefined ? {} : { need }), reason, report,
|
|
@@ -246,7 +264,7 @@ export function assignmentStatusFacts(assignment, options = {}) {
|
|
|
246
264
|
pullRequest: withResult ? pullRequest : null,
|
|
247
265
|
requirements: withResult ? requirementsOf(receipt?.proof?.matrix) : null,
|
|
248
266
|
evidence: withResult ? options.evidence ?? evidenceOf(receipt, [...(receipt?.caveats ?? []), ...assignment.attention]) : null,
|
|
249
|
-
completedBy: assignment.completion === null ? null : assignment.completion.actor.replace(/^(?:operator|coordinator|lead):/, ""),
|
|
267
|
+
completedBy: assignment.completion === null ? null : assignment.completion.lead === true ? "the lead" : assignment.completion.actor.replace(/^(?:operator|coordinator|lead):/, ""),
|
|
250
268
|
action: options.action ?? null,
|
|
251
269
|
...(options.links === undefined ? {} : { links: options.links }),
|
|
252
270
|
why: options.why ?? [],
|
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,6 +2,7 @@
|
|
|
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";
|
|
@@ -98,6 +99,7 @@ export function taskWorkSummaryOf(store, taskId, now, access) {
|
|
|
98
99
|
result: result === null ? null : {
|
|
99
100
|
runId: result.id, role: result.role, outcome: result.outcome,
|
|
100
101
|
verdict: proof?.verdict ?? null, reasons: proof?.reasons ?? [], accepted: store.proofAcceptance(result.id) !== null,
|
|
102
|
+
checkLevel: resultCheckLevel(store, result.id, now),
|
|
101
103
|
review: reviewFactsOf(store.reviewRetryStateOf(result.id), name => {
|
|
102
104
|
const runner = store.getRunner(name)?.runner;
|
|
103
105
|
return runner !== undefined && isAlive(runner, now);
|
package/dist/workspace-ui.d.ts
CHANGED
|
@@ -43,6 +43,9 @@ export type ResultFacts = {
|
|
|
43
43
|
/** A no-change conclusion's two presence facts (handoff + sealed diff);
|
|
44
44
|
* undefined when the caller did not read them. */
|
|
45
45
|
recordComplete?: boolean;
|
|
46
|
+
/** The check level this result now stands at (check-levels.ts): Off reads
|
|
47
|
+
* Ready for review, saying no check ran, until a follow-up check passes. */
|
|
48
|
+
checkLevel?: "quick" | "full" | "off" | null;
|
|
46
49
|
/** This exact run's independent review (v50 retry projection), read
|
|
47
50
|
* per run so an older selected result keeps its own words; undefined
|
|
48
51
|
* when the caller did not read it, null when never requested. */
|
package/dist/workspace-ui.js
CHANGED
|
@@ -327,9 +327,11 @@ export function workStatusOf(facts, resultDisplay) {
|
|
|
327
327
|
const read = done ? RESULT_NEEDS_YOU.has(status.token) ? { stage: "needs-you", need: "other" } : stageOfCode(status.token, { needsPerson: false })
|
|
328
328
|
: stageOfCode(status.token, { needsPerson: status.views.includes("needs-you"), planning: facts.dispatch?.code === "running" && facts.dispatch.role === "planner", operatorHold: facts.dispatch?.action === "unhold" });
|
|
329
329
|
const stage = done && read.stage !== "needs-you" && read.stage !== "failed" ? "finished" : read.stage;
|
|
330
|
+
const level = facts.result?.checkLevel ?? null;
|
|
330
331
|
const shared = taskStatusOf({ stage, ...(read.need === undefined ? {} : { need: read.need }), reason: plainReasonOf(stage, status.token, status.detail),
|
|
331
|
-
report: facts.result?.role === "scout", checks: stage
|
|
332
|
-
|
|
332
|
+
report: facts.result?.role === "scout", checks: stage !== "finished" ? null : VERIFIED_RESULT.has(status.token) ? { status: "passed", exitCode: null, head: null, level }
|
|
333
|
+
: level === "off" ? { status: "not-run", exitCode: null, head: null, level } : null });
|
|
334
|
+
return { ...status, label: shared.headline, tone: workToneOf(shared.headline), detail: stage === "finished" && level !== "off" ? status.detail : shared.sentence };
|
|
333
335
|
}
|
|
334
336
|
function workStatusWordsOf(facts, resultDisplay) {
|
|
335
337
|
const dispatch = facts.dispatch;
|
|
@@ -372,7 +374,7 @@ function workStatusWordsOf(facts, resultDisplay) {
|
|
|
372
374
|
return { ...result, views, rank: needs ? 0 : 3 };
|
|
373
375
|
}
|
|
374
376
|
if (facts.state === "cancelled") {
|
|
375
|
-
return { token: "cancelled", label: "Cancelled", detail: dispatch?.detail ?? "Nothing else will run for this task.", tone: "muted", action: null, views, rank: 4 };
|
|
377
|
+
return { token: "cancelled", label: dispatch?.summary.startsWith("Replaced by ") ? dispatch.summary : "Cancelled", detail: dispatch?.detail ?? "Nothing else will run for this task.", tone: "muted", action: null, views, rank: 4 };
|
|
376
378
|
}
|
|
377
379
|
if (question !== null && facts.state !== "failed" && dispatch !== null && QUESTION_FIRST.has(dispatch.code)) {
|
|
378
380
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolroll",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.7",
|
|
4
4
|
"description": "A control plane for unattended coding agents — queue tasks, walk away, come back to pull requests. Agents build in leased worktrees, never touch your default branch, and interrupt you only for decisions that need a human.",
|
|
5
5
|
"directories": {
|
|
6
6
|
"doc": "docs"
|