toolroll 0.9.5 → 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 +49 -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-presentation.d.ts +13 -2
- package/dist/assignment-presentation.js +22 -9
- package/dist/assignment-ui.d.ts +10 -15
- package/dist/assignment-ui.js +19 -18
- 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 +29 -24
- package/dist/browser-workspace.d.ts +120 -12
- package/dist/builder.d.ts +1 -0
- package/dist/builder.js +33 -13
- package/dist/chat-acceptance.js +2 -2
- package/dist/chat-actions.js +2 -1
- package/dist/chat-delivery.js +2 -0
- package/dist/chat-quiet.js +30 -19
- 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.d.ts +0 -5
- package/dist/demo-chat.js +14 -3
- 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/lead-status.d.ts +7 -0
- package/dist/lead-status.js +11 -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 +1070 -204
- 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 +186 -0
- package/dist/task-status.js +335 -0
- package/dist/team-cli.js +3 -0
- package/dist/telegram-progress.js +62 -100
- package/dist/telegram-status.js +21 -8
- package/dist/verification-evidence.js +21 -1
- package/dist/webhooks.js +5 -0
- package/dist/work-index.js +60 -11
- package/dist/work-summary.d.ts +0 -4
- package/dist/work-summary.js +2 -0
- package/dist/workspace-ui.d.ts +10 -0
- package/dist/workspace-ui.js +33 -2
- package/package.json +1 -1
package/dist/telegram-status.js
CHANGED
|
@@ -3,6 +3,8 @@ import { manualReviewOnly } from "./proof.js";
|
|
|
3
3
|
/** Read-only phone views over the same dispatch and proof records as chat.
|
|
4
4
|
* No provider calls, repository access, new workflow state, or inferred success. */
|
|
5
5
|
import { diagnoseTaskDispatch, withDispatchDiagnoses } from "./dispatch.js";
|
|
6
|
+
import { COMPLETION_ACTION } from "./result-completion.js";
|
|
7
|
+
import { plainReasonOf, stageOfDispatch, taskStatusOf } from "./task-status.js";
|
|
6
8
|
import { CHAT_CONTROLS, chatControlHref, chatResultHref } from "./chat-controls.js";
|
|
7
9
|
export function phoneCommand(text) {
|
|
8
10
|
// Slash forms everywhere; the bare words too, because Slack and Discord
|
|
@@ -50,11 +52,20 @@ export const phoneText = plain;
|
|
|
50
52
|
export function projectLabel(repo) {
|
|
51
53
|
return plain(repo.split(/[\\/]/).filter(Boolean).pop() ?? "project", 48);
|
|
52
54
|
}
|
|
55
|
+
/** The shared headline (task-status.ts) for a dispatch diagnosis on the phone. */
|
|
56
|
+
function headlineFor(store, id, d) {
|
|
57
|
+
const completed = d.code === "complete" && store.handle.prepare("SELECT 1 FROM action_ledger WHERE task_id = ? AND action = ? AND source = 'work' LIMIT 1")
|
|
58
|
+
.get(store.taskFamilyOf(id, null, true)?.root.id ?? id, COMPLETION_ACTION) !== undefined;
|
|
59
|
+
const read = stageOfDispatch(d, { completed });
|
|
60
|
+
const finished = read.stage === "finished" || read.stage === "complete" || read.stage === "failed";
|
|
61
|
+
return taskStatusOf({ stage: read.stage, ...(read.need === undefined ? {} : { need: read.need }), reason: finished ? null : plainReasonOf(read.stage, d.code, d.detail),
|
|
62
|
+
checks: read.stage === "failed" && d.code === "proof-refuted" ? { status: "failed", exitCode: null, head: null } : null });
|
|
63
|
+
}
|
|
53
64
|
function groupOf(d) {
|
|
54
65
|
if (d.condition === "running")
|
|
55
66
|
return "Working";
|
|
56
67
|
if (d.code === "cancelled")
|
|
57
|
-
return "Cancelled";
|
|
68
|
+
return d.summary.startsWith("Replaced by ") ? "Replaced" : "Cancelled";
|
|
58
69
|
if (d.code === "complete")
|
|
59
70
|
return "Finished";
|
|
60
71
|
if (d.code === "review-pending" || d.action === null)
|
|
@@ -94,13 +105,13 @@ export function phoneStatus(store, repos, now, focused = null) {
|
|
|
94
105
|
lines.push("Newest 60 tasks only — older work may still need attention.", "");
|
|
95
106
|
if (snapshot.tasks.length === 0)
|
|
96
107
|
lines.push("No tasks are recorded in these projects.");
|
|
97
|
-
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"]) {
|
|
98
109
|
const rows = snapshot.tasks.filter(t => t.dispatch !== null && t.dispatch !== undefined && groupOf(t.dispatch) === group);
|
|
99
110
|
if (rows.length === 0)
|
|
100
111
|
continue;
|
|
101
112
|
lines.push(`${group} · ${rows.length}${snapshot.tasksSaturated ? " in this snapshot" : ""}`);
|
|
102
113
|
for (const task of rows.slice(0, 2)) {
|
|
103
|
-
lines.push(`${plain(task.id, 64)} · ${projectLabel(repos[task.repoIndex])}`, ` ${plain(task.title, 64)} — ${
|
|
114
|
+
lines.push(`${plain(task.id, 64)} · ${projectLabel(repos[task.repoIndex])}`, ` ${plain(task.title, 64)} — ${headlineFor(store, task.id, task.dispatch).headline}`);
|
|
104
115
|
}
|
|
105
116
|
if (rows.length > 2)
|
|
106
117
|
lines.push(` +${rows.length - 2} more in the console`);
|
|
@@ -131,7 +142,7 @@ export function phoneTaskChoices(store, repos, now, query = null, limit = 8) {
|
|
|
131
142
|
if (seen.has(id) || !words.every(word => `${id} ${row.title}`.toLowerCase().includes(word)))
|
|
132
143
|
continue;
|
|
133
144
|
seen.add(id);
|
|
134
|
-
choices.push({ id, title: plain(row.title, 64), label:
|
|
145
|
+
choices.push({ id, title: plain(row.title, 64), label: headlineFor(store, row.id, row.dispatch).headline, group: groupOf(row.dispatch) });
|
|
135
146
|
if (choices.length >= limit)
|
|
136
147
|
break;
|
|
137
148
|
}
|
|
@@ -214,10 +225,11 @@ export function phoneTaskView(store, repos, id, now) {
|
|
|
214
225
|
const d = diagnoseTaskDispatch(store, id, now);
|
|
215
226
|
if (d === null)
|
|
216
227
|
return { text: "This task's status is unavailable. Open it in the console before retrying.", link: null, run: null };
|
|
217
|
-
const
|
|
228
|
+
const shared = headlineFor(store, id, d);
|
|
229
|
+
const lines = [plain(task.title, 140), `${plain(id, 64)} · ${projectLabel(ref.repo)}`, `As of ${now.toISOString().replace("T", " ").slice(0, 19)} UTC`, "", shared.headline];
|
|
218
230
|
const blocker = d.blockerTaskId === null ? null : store.lookupRef(d.blockerTaskId);
|
|
219
231
|
const hiddenDependency = blocker !== null && (blocker.repo === null || !repos.includes(blocker.repo));
|
|
220
|
-
lines.push(hiddenDependency ? "A required task outside this phone view has not finished. Open the dependency in the console." : plain(
|
|
232
|
+
lines.push(hiddenDependency ? "A required task outside this phone view has not finished. Open the dependency in the console." : plain(shared.sentence, 650));
|
|
221
233
|
if (d.nextAt !== null)
|
|
222
234
|
lines.push(`Earliest recorded wake: ${plain(d.nextAt, 40)} (a connected worker is still required).`);
|
|
223
235
|
const runs = store.runsFor(ref.id);
|
|
@@ -247,8 +259,9 @@ export function phoneTaskView(store, repos, id, now) {
|
|
|
247
259
|
if (accepted && !manualReviewOnly(proof))
|
|
248
260
|
lines.push("An operator accepted this result; that does not change its recorded checks.");
|
|
249
261
|
const publication = store.publicationForRun(result.id);
|
|
250
|
-
const delivery = publication?.remoteState === "MERGED" ?
|
|
251
|
-
|
|
262
|
+
const delivery = publication?.remoteState === "MERGED" ? `#${publication.prNumber ?? "?"} merged` : publication?.remoteState === "CLOSED" ? `#${publication.prNumber ?? "?"} closed without merging` : publication?.state === "opened" ? `#${publication.prNumber ?? "?"} open` : publication?.state === "pushed" || publication?.state === "intended" ? "Opening…" : publication?.state === "failed" ? "Couldn't open; the commit is safe locally" : null;
|
|
263
|
+
if (delivery !== null)
|
|
264
|
+
lines.push(`Pull request: ${delivery}.`);
|
|
252
265
|
}
|
|
253
266
|
if (result === undefined)
|
|
254
267
|
link = taskLinkFor(id, d, null);
|
|
@@ -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,6 +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, replacedWords, stageOfCode, taskStatusOf, workToneOf } from './task-status.js';
|
|
8
|
+
import { isCheckLevel } from './check-levels.js';
|
|
9
|
+
import { withFollowUps } from './result-follow-ups.js';
|
|
7
10
|
export const WORK_INDEX_PAGE_LIMIT = 40;
|
|
8
11
|
export const WORK_INDEX_MAX_LIMIT = 100;
|
|
9
12
|
const EMPTY = { all: 0, 'needs-you': 0, running: 0, completed: 0 };
|
|
@@ -194,9 +197,9 @@ const PROJECTION = `WITH RECURSIVE admitted AS MATERIALIZED (
|
|
|
194
197
|
ELSE 'queued' END code
|
|
195
198
|
FROM checked f
|
|
196
199
|
), ranked AS MATERIALIZED (
|
|
197
|
-
SELECT c.*,code NOT IN ('cancelled','complete','running','updating','retry-scheduled','waiting-dependency','worker-at-capacity','
|
|
200
|
+
SELECT c.*,code NOT IN ('cancelled','complete','running','updating','retry-scheduled','waiting-dependency','worker-at-capacity','planning-ready','scouting-ready','queued') needs,
|
|
198
201
|
CASE WHEN code='complete' THEN 3 WHEN code='cancelled' THEN 4 WHEN code='running' THEN 1
|
|
199
|
-
WHEN code IN ('updating','retry-scheduled','waiting-dependency','worker-at-capacity','
|
|
202
|
+
WHEN code IN ('updating','retry-scheduled','waiting-dependency','worker-at-capacity','planning-ready','scouting-ready','queued') THEN 2 ELSE 0 END rank,
|
|
200
203
|
CASE WHEN code='complete' THEN completed.at ELSE family_updated END sort_at,
|
|
201
204
|
completed.actor checked_actor,completed.at checked_at,completed.outcome checked_digest
|
|
202
205
|
FROM classified c LEFT JOIN action_ledger completed ON completed.id=c.checked_id WHERE ($state IS NULL OR c.state=$state) AND ($leadId IS NULL OR EXISTS(SELECT 1 FROM team_task_owner own WHERE own.task_ref=c.root_ref AND own.lead=$leadId))
|
|
@@ -293,10 +296,18 @@ export function workIndexPage(store, now, access, options = {}) {
|
|
|
293
296
|
UNION ALL SELECT 1,0,0,0,0,json_object('root_ref',root_ref,'root_id',root_id,'id',id,'root_title',root_title,'repo',repo,
|
|
294
297
|
'state',state,'created_at',created_at,'family_updated',family_updated,'version_count',version_count,'earlier_active',earlier_active,
|
|
295
298
|
'earlier_id',earlier_id,'broken',broken,'live_run',live_run,'result_id',page_result_id,'result_outcome',(SELECT outcome FROM run WHERE id=page_result_id),
|
|
296
|
-
'publication_url',(SELECT pr_url FROM publication WHERE run=page_result_id),
|
|
299
|
+
'publication_url',(SELECT pr_url FROM publication WHERE run=page_result_id),
|
|
300
|
+
'result_role',(SELECT role FROM run WHERE id=page_result_id),'live_role',(SELECT role FROM run WHERE id=page.live_run),
|
|
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),
|
|
305
|
+
'proof_verdict',(SELECT COALESCE(machine_verdict,verdict) FROM proof_verdict WHERE run=page_result_id),
|
|
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,
|
|
297
307
|
'checked_actor',checked_actor,'checked_at',checked_at,'checked_digest',checked_digest,'question_id',question_id,
|
|
298
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,
|
|
299
|
-
'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),
|
|
300
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`)
|
|
301
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() });
|
|
302
313
|
const selected = rows.slice(1).map(row => JSON.parse(String(row['row_json'])));
|
|
@@ -315,6 +326,27 @@ export function workCountsByProject(store, now, access) {
|
|
|
315
326
|
}
|
|
316
327
|
/** Labels stay navigation, never an authorization to mutate. Full task
|
|
317
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
|
+
}
|
|
318
350
|
function itemOf(row, principal) {
|
|
319
351
|
const code = String(row['code']), id = String(row['id']);
|
|
320
352
|
const need = n(row, 'needs') === 1, running = n(row, 'family_running') === 1;
|
|
@@ -326,15 +358,15 @@ function itemOf(row, principal) {
|
|
|
326
358
|
'ready-to-check': ['Ready', 'The saved result is ready to review.', 'open-result', 'Open result'],
|
|
327
359
|
complete: ['Complete', 'Completion is recorded for this result. Open it to inspect the saved evidence.', 'open-result', 'Open result'],
|
|
328
360
|
cancelled: ['Cancelled', 'This assignment was cancelled.', 'inspect-task', 'View assignment'],
|
|
329
|
-
running: ['Running now', '
|
|
361
|
+
running: ['Running now', '', null, ''],
|
|
330
362
|
'waiting-decision': ['Needs your decision', 'An unanswered question is waiting.', n(row, 'question_id') ? 'answer-decision' : 'inspect-decisions', 'Answer question'],
|
|
331
363
|
'decision-queue': ['Needs your decision', 'The decision queue is full.', 'inspect-decisions', 'Review open questions'],
|
|
332
364
|
'result-needs-attention': ['Needs your decision', 'Inspect the saved result and resolve its remaining execution or scope issue.', 'inspect-run', 'Inspect run'],
|
|
333
365
|
'process-needs-attention': ['Needs your decision', 'A process exit is not recorded. Open the attempt to check whether its work has stopped.', 'inspect-run', 'Inspect run'],
|
|
334
366
|
failed: ['Needs your decision', 'The last attempt stopped; review its incident before retrying.', 'retry-task', 'Review and retry'],
|
|
335
367
|
'vanished-run': ['Needs your decision', 'An unfinished attempt has no current live claim.', 'reconcile-run', 'Check the unfinished attempt'],
|
|
336
|
-
stopping: ['Needs your decision', 'The
|
|
337
|
-
stopped: ['Needs your decision', 'The
|
|
368
|
+
stopping: ['Needs your decision', 'Stopping. The work so far is kept.', 'inspect-stop', 'View stop details'],
|
|
369
|
+
stopped: ['Needs your decision', 'Paused by a person. The work so far is kept.', 'resume-run', 'Review pause'],
|
|
338
370
|
held: ['Needs your decision', s(row, 'hold_reason') ?? 'This task is on hold.', s(row, 'hold_kind') === 'operator' ? 'unhold' : 'inspect-hold', 'Review hold'],
|
|
339
371
|
'waiting-incident': ['Needs your decision', 'An unresolved incident holds the next attempt.', 'retry-task', 'Review and retry'],
|
|
340
372
|
'terminal-dependency': ['Needs your decision', 'A required task did not finish.', 'repair-dependency', 'Review required task'],
|
|
@@ -353,16 +385,22 @@ function itemOf(row, principal) {
|
|
|
353
385
|
updating: ['Waiting for app update', 'New work resumes after the update.', null, ''],
|
|
354
386
|
'planning-ready': ['Planner ready', 'A connected worker can draft the plan.', null, ''],
|
|
355
387
|
'scouting-ready': ['Scout ready', 'A connected worker can produce the report.', null, ''],
|
|
356
|
-
queued: ['
|
|
388
|
+
queued: ['Queued', '', null, ''],
|
|
357
389
|
};
|
|
358
390
|
let [label, detail, actionCode, actionLabel] = words[code];
|
|
359
391
|
if (code === 'signed-out') {
|
|
360
392
|
const [provider = '', authMode = 'subscription'] = (s(row, 'signed_out') ?? '').split(':');
|
|
361
393
|
label = signInReason({ provider: provider, authMode: authMode === 'api-key' ? 'api-key' : 'subscription' });
|
|
362
|
-
detail =
|
|
394
|
+
detail = `${label}. It starts again on its own once ${providerName(provider)} works.`;
|
|
363
395
|
}
|
|
364
396
|
if (code === 'running' && n(row, 'version_count') > 1)
|
|
365
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
|
+
}
|
|
366
404
|
if (code === 'terminal-dependency' && s(row, 'dependency_id') !== null)
|
|
367
405
|
detail = `${s(row, 'dependency_id')} ${s(row, 'dependency_state') === 'cancelled' ? 'was cancelled' : 'failed'} before it finished.`;
|
|
368
406
|
if (code === 'result-needs-attention' && n(row, 'custody_unresolved'))
|
|
@@ -377,12 +415,23 @@ function itemOf(row, principal) {
|
|
|
377
415
|
}
|
|
378
416
|
const read = actionCode !== null && ['inspect-task', 'inspect-run', 'inspect-stop', 'inspect-decisions', 'open-result'].includes(actionCode);
|
|
379
417
|
const primaryAction = actionCode === null ? null : { code: actionCode, label: actionLabel,
|
|
380
|
-
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),
|
|
381
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
|
|
382
420
|
: actionCode === 'reconcile-run' ? n(row, 'unfinished') || null : ['resume-run', 'inspect-stop'].includes(actionCode) ? n(row, 'stop_run') || null : null,
|
|
383
421
|
decisionId: actionCode === 'answer-decision' ? n(row, 'question_id') || null : null },
|
|
384
422
|
access: read ? 'read' : principal === 'operator' ? 'operator-control' : ['answer-decision', 'write-scope', 'unhold'].includes(actionCode) ? 'proposal-only' : 'operator-handoff',
|
|
385
423
|
retry: actionCode === 'reconcile-run' ? 'reconcile-before-retry' : read ? 'read-again' : 'refresh-before-acting' };
|
|
424
|
+
// The one shared status: the headline and sentence every surface uses.
|
|
425
|
+
const reading = stageOfCode(code, { needsPerson: need, planning: s(row, 'live_role') === 'planner', operatorHold: s(row, 'hold_kind') === 'operator' });
|
|
426
|
+
const checkStatus = s(row, 'check_status'), verdict = s(row, 'proof_verdict');
|
|
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;
|
|
429
|
+
const shared = taskStatusOf({ stage: reading.stage, ...(reading.need === undefined ? {} : { need: reading.need }),
|
|
430
|
+
reason: finished ? null : plainReasonOf(reading.stage, code, detail || null), report: s(row, 'result_role') === 'scout',
|
|
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):/, '') });
|
|
433
|
+
label = shared.headline;
|
|
434
|
+
detail = shared.sentence;
|
|
386
435
|
const views = ['all'];
|
|
387
436
|
if (need)
|
|
388
437
|
views.push('needs-you');
|
|
@@ -395,7 +444,7 @@ function itemOf(row, principal) {
|
|
|
395
444
|
versionCount: n(row, 'version_count'), earlierActiveCount: n(row, 'earlier_active'), familyProblem: n(row, 'broken') ? FAMILY_PROBLEM : null,
|
|
396
445
|
liveRunId: n(row, 'live_run') || null, unfinishedRunId: n(row, 'unfinished') || null, resultRunId: n(row, 'result_id') || null, resultTaskId: n(row, 'result_id') ? id : null,
|
|
397
446
|
resultOutcome: s(row, 'result_outcome'), publicationUrl: s(row, 'publication_url'),
|
|
398
|
-
status: { token: `assignment-${assignmentState}`, label, detail, tone:
|
|
447
|
+
status: { token: `assignment-${assignmentState}`, label, detail, tone: workToneOf(shared.headline),
|
|
399
448
|
action: primaryAction === null ? null : { label: actionLabel, kind: actionCode === 'open-result' ? 'open-result' : 'open-task' }, views, rank: n(row, 'rank') },
|
|
400
449
|
primaryAction, completion: code === 'complete' ? { actor: String(row['checked_actor']), at: String(row['checked_at']), digest: String(row['checked_digest']) } : null,
|
|
401
450
|
evidence: 'recorded' };
|
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. */
|
|
@@ -182,6 +185,13 @@ export type WorkStatus = DisplayStatus & {
|
|
|
182
185
|
* concrete act — never every queued task indiscriminately. */
|
|
183
186
|
export declare function needsPerson(dispatch: DispatchDiagnosis | null): boolean;
|
|
184
187
|
export declare function dispatchActionLabel(dispatch: DispatchDiagnosis | null): string;
|
|
188
|
+
/** A finished result's display status in the shared headline's words: a
|
|
189
|
+
* failed project check is Failed, a result missing its record needs a
|
|
190
|
+
* person, every other saved result is Ready for review. Token and action stay. */
|
|
191
|
+
export declare function resultHeadlineOf<T extends DisplayStatus>(status: T): T;
|
|
192
|
+
/** The one shared headline (task-status.ts) over the work status below: the
|
|
193
|
+
* token, views, rank, action and diagnostics stay; the words and tone are
|
|
194
|
+
* the eight headlines'. */
|
|
185
195
|
export declare function workStatusOf(facts: WorkFacts, resultDisplay?: DisplayStatus): WorkStatus;
|
|
186
196
|
/** The counts each view tab wears — from the same rows the page lists. */
|
|
187
197
|
export declare function workCounts(rows: readonly {
|
package/dist/workspace-ui.js
CHANGED
|
@@ -17,10 +17,11 @@
|
|
|
17
17
|
* claimed without one.
|
|
18
18
|
*/
|
|
19
19
|
import { GOAL_ASSESSMENT_PENDING, manualReviewOnly } from "./proof.js";
|
|
20
|
+
import { plainReasonOf, stageOfCode, taskStatusOf, workToneOf } from "./task-status.js";
|
|
20
21
|
export const WORK_VIEWS = [
|
|
21
22
|
{ key: "all", label: "All", hint: "Every task in view, most urgent first.", empty: "Nothing is in progress. Describe work in chat or add a task, and it appears here." },
|
|
22
23
|
{ key: "needs-you", label: "Needs you", hint: "Tasks waiting on an answer, approval, or your inspection.", empty: "Nothing needs you right now. Queued and running work continues on its own." },
|
|
23
|
-
{ key: "running", label: "
|
|
24
|
+
{ key: "running", label: "Building", hint: "Attempts a builder owns right now.", empty: "Nothing is building right now. Approved tasks start when a builder with capacity is connected." },
|
|
24
25
|
{ key: "completed", label: "Complete", hint: "Tasks the lead or user has marked complete. Recorded check results remain available.", empty: "No tasks have been marked complete in this view." },
|
|
25
26
|
];
|
|
26
27
|
export function parseWorkView(raw) {
|
|
@@ -302,7 +303,37 @@ const DISPATCH_ACTION_LABELS = {
|
|
|
302
303
|
export function dispatchActionLabel(dispatch) {
|
|
303
304
|
return dispatch?.action == null ? "View task details" : DISPATCH_ACTION_LABELS[dispatch.action];
|
|
304
305
|
}
|
|
306
|
+
/** Result tokens a done task can wear, as the shared stage they mean. A
|
|
307
|
+
* result that is missing its record waits on a person; a failed check fails. */
|
|
308
|
+
const RESULT_NEEDS_YOU = new Set(["no-build-record", "verification-needed", "evidence-mismatch", "record-incomplete", "evidence-damaged"]);
|
|
309
|
+
/** Result tokens that only follow a passing project check. */
|
|
310
|
+
const VERIFIED_RESULT = new Set(["ready-to-review", "pr-opened", "merge-observed", "pr-closed"]);
|
|
311
|
+
/** A finished result's display status in the shared headline's words: a
|
|
312
|
+
* failed project check is Failed, a result missing its record needs a
|
|
313
|
+
* person, every other saved result is Ready for review. Token and action stay. */
|
|
314
|
+
export function resultHeadlineOf(status) {
|
|
315
|
+
const read = RESULT_NEEDS_YOU.has(status.token) ? { stage: "needs-you", need: "other" }
|
|
316
|
+
: status.token === "checks-failed" ? { stage: "failed" } : { stage: "finished" };
|
|
317
|
+
const shared = taskStatusOf({ ...read, reason: status.detail, checks: read.stage === "failed" ? { status: "failed", exitCode: null, head: null }
|
|
318
|
+
: VERIFIED_RESULT.has(status.token) ? { status: "passed", exitCode: null, head: null } : null });
|
|
319
|
+
return { ...status, label: shared.headline, tone: workToneOf(shared.headline) };
|
|
320
|
+
}
|
|
321
|
+
/** The one shared headline (task-status.ts) over the work status below: the
|
|
322
|
+
* token, views, rank, action and diagnostics stay; the words and tone are
|
|
323
|
+
* the eight headlines'. */
|
|
305
324
|
export function workStatusOf(facts, resultDisplay) {
|
|
325
|
+
const status = workStatusWordsOf(facts, resultDisplay);
|
|
326
|
+
const done = facts.state === "done" && status.token !== "waiting-decision";
|
|
327
|
+
const read = done ? RESULT_NEEDS_YOU.has(status.token) ? { stage: "needs-you", need: "other" } : stageOfCode(status.token, { needsPerson: false })
|
|
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
|
+
const stage = done && read.stage !== "needs-you" && read.stage !== "failed" ? "finished" : read.stage;
|
|
330
|
+
const level = facts.result?.checkLevel ?? null;
|
|
331
|
+
const shared = taskStatusOf({ stage, ...(read.need === undefined ? {} : { need: read.need }), reason: plainReasonOf(stage, status.token, status.detail),
|
|
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 };
|
|
335
|
+
}
|
|
336
|
+
function workStatusWordsOf(facts, resultDisplay) {
|
|
306
337
|
const dispatch = facts.dispatch;
|
|
307
338
|
const result = facts.state === "done" ? resultDisplay ?? resultStatusOf(facts.result, facts.publication) : null;
|
|
308
339
|
const running = facts.liveRunId !== null || (dispatch?.condition === "running" && !REVIEW_TOKENS.has(dispatch.code));
|
|
@@ -343,7 +374,7 @@ export function workStatusOf(facts, resultDisplay) {
|
|
|
343
374
|
return { ...result, views, rank: needs ? 0 : 3 };
|
|
344
375
|
}
|
|
345
376
|
if (facts.state === "cancelled") {
|
|
346
|
-
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 };
|
|
347
378
|
}
|
|
348
379
|
if (question !== null && facts.state !== "failed" && dispatch !== null && QUESTION_FIRST.has(dispatch.code)) {
|
|
349
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"
|