toolroll 0.9.74 → 0.9.75
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 +31 -0
- package/dist/assignment.d.ts +3 -0
- package/dist/assignment.js +27 -8
- package/dist/browser/workspace.js +27 -27
- package/dist/browser-workspace.d.ts +1 -1
- package/dist/chat-acceptance.d.ts +4 -1
- package/dist/chat-acceptance.js +13 -3
- package/dist/cli.js +1 -1
- package/dist/coding-handoff.js +4 -3
- package/dist/coding-ui.js +2 -2
- package/dist/coding-workspace.js +16 -4
- package/dist/contracts/console-api.d.ts +5 -1
- package/dist/contracts/console-api.js +10 -0
- package/dist/control-ui.d.ts +3 -2
- package/dist/control-ui.js +7 -2
- package/dist/demo.js +5 -3
- package/dist/desktop-update.d.ts +3 -0
- package/dist/desktop-update.js +43 -9
- package/dist/first-run.d.ts +8 -5
- package/dist/first-run.js +39 -6
- package/dist/flow-card-task.js +3 -2
- package/dist/flow-unattended.d.ts +2 -0
- package/dist/flow-unattended.js +12 -1
- package/dist/integrations.d.ts +4 -3
- package/dist/integrations.js +5 -6
- package/dist/lead-contract.d.ts +1 -1
- package/dist/lead-contract.js +2 -2
- package/dist/lead-follow.js +3 -1
- package/dist/openrouter-models.d.ts +5 -2
- package/dist/openrouter-models.js +7 -4
- package/dist/operate.js +2 -0
- package/dist/project-learning.js +44 -6
- package/dist/project-tools.js +19 -1
- package/dist/pull-request-ui.d.ts +1 -1
- package/dist/pull-request-ui.js +5 -1
- package/dist/push.d.ts +2 -0
- package/dist/push.js +24 -19
- package/dist/read-only-request.d.ts +4 -0
- package/dist/read-only-request.js +15 -0
- package/dist/repository-context.js +6 -1
- package/dist/result-follow-ups.js +20 -1
- package/dist/result-review.js +5 -1
- package/dist/serve.js +162 -6
- package/dist/server/agents-live-filed.d.ts +17 -0
- package/dist/server/agents-live-filed.js +97 -0
- package/dist/server/agents-live-room.js +4 -1
- package/dist/server/chat.js +31 -23
- package/dist/server/http.d.ts +5 -0
- package/dist/server/last-checks.d.ts +17 -0
- package/dist/server/last-checks.js +22 -0
- package/dist/server/pages.js +18 -4
- package/dist/server/people-tokens.js +7 -4
- package/dist/server/render-pages.d.ts +2 -1
- package/dist/server/render-pages.js +28 -24
- package/dist/server/render-results.d.ts +11 -2
- package/dist/server/render-results.js +54 -12
- package/dist/server/render-settings.js +3 -3
- package/dist/server/render-tasks.js +7 -4
- package/dist/server/route-table.js +14 -1
- package/dist/server/runtime.d.ts +16 -1
- package/dist/server/settings.js +82 -29
- package/dist/server/task-views.js +0 -1
- package/dist/server/tasks.js +11 -2
- package/dist/storage-ui.d.ts +2 -0
- package/dist/storage-ui.js +1 -1
- package/dist/storage.js +2 -1
- package/dist/store.d.ts +3 -0
- package/dist/store.js +33 -9
- package/dist/subagent-work.js +3 -0
- package/dist/task-status.d.ts +28 -1
- package/dist/task-status.js +93 -11
- package/dist/teardown-git.d.ts +27 -1
- package/dist/teardown-git.js +185 -39
- package/dist/telegram-progress.js +4 -4
- package/dist/telegram-status.js +8 -3
- package/dist/tool-secret-store.js +8 -0
- package/dist/toolroll-update-ui.d.ts +2 -0
- package/dist/toolroll-update-ui.js +4 -3
- package/dist/toolroll-update.js +10 -9
- package/dist/work-index.js +29 -12
- package/dist/workspace-ui.d.ts +5 -0
- package/dist/workspace-ui.js +38 -10
- package/package.json +1 -1
package/dist/toolroll-update.js
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
* previous runtime and its backups. Every update, rollback, refusal and
|
|
23
23
|
* failure is a ledger entry.
|
|
24
24
|
*/
|
|
25
|
+
import { inReadOnlyRequest } from "./read-only-request.js";
|
|
25
26
|
import { createHash, randomUUID, verify as signatureValid, X509Certificate } from "node:crypto";
|
|
26
27
|
import { spawnSync } from "node:child_process";
|
|
27
28
|
import { accessSync, chmodSync, closeSync, constants, copyFileSync, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, readdirSync, readFileSync, readlinkSync, realpathSync, renameSync, rmSync, statfsSync, statSync, symlinkSync, writeFileSync } from "node:fs";
|
|
@@ -29,7 +30,7 @@ import { homedir, userInfo } from "node:os";
|
|
|
29
30
|
import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
30
31
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
31
32
|
import { createRequire } from "node:module";
|
|
32
|
-
import { clearWords, durableJson, LINGERING_LIMIT_MS, lingeringRun, lingeringWords, settleLeftoverRecords, sqliteLock, desktopUpdateWaitingOf, stoppedWaitingWords, updateWaitingWords, verifiedDatabaseBackup } from "./desktop-update.js";
|
|
33
|
+
import { clearWords, durableJson, LINGERING_LIMIT_MS, lingeringRun, lingeringWords, settleLeftoverRecords, sqliteLock, lockHeld, desktopUpdateWaitingOf, stoppedWaitingWords, updateWaitingWords, verifiedDatabaseBackup } from "./desktop-update.js";
|
|
33
34
|
import { activeUpdateWork, freezeUpdateGate, installUpdateGate, removeUpdateGate, updateAdmissionPaused, updateGateOwned } from "./desktop-update-gate.js";
|
|
34
35
|
import { processMayBeAlive } from "./process-liveness.js";
|
|
35
36
|
import { removeTerminalUpdateGate } from "./terminal-catalog.js";
|
|
@@ -1445,11 +1446,8 @@ export function runtimeUpdateStatus(stateDir) {
|
|
|
1445
1446
|
j = null;
|
|
1446
1447
|
}
|
|
1447
1448
|
let running = false;
|
|
1448
|
-
if (j && !runtimeUpdateTerminal(j.phase))
|
|
1449
|
-
|
|
1450
|
-
running = lock === null;
|
|
1451
|
-
lock?.close();
|
|
1452
|
-
}
|
|
1449
|
+
if (j && !runtimeUpdateTerminal(j.phase))
|
|
1450
|
+
running = lockHeld(join(j.stageDir, "worker.sqlite"));
|
|
1453
1451
|
const whatsNew = j && j.kind === "update" && j.phase === "complete" && !j.seen ? { version: j.to.version, notes: j.notes ?? [] } : null;
|
|
1454
1452
|
let last = null;
|
|
1455
1453
|
try {
|
|
@@ -1525,10 +1523,13 @@ export function releaseStalledUpdate(stateDir, now) {
|
|
|
1525
1523
|
* waiting update, `toolroll update`'s or the desktop app's. */
|
|
1526
1524
|
export function waitingUpdate(databaseFile, inTheWay, now, desktopStates) {
|
|
1527
1525
|
const stateDir = dirname(databaseFile);
|
|
1528
|
-
|
|
1529
|
-
|
|
1526
|
+
// A page visit only reads (docs/plans/get-side-effects.md): the console's background pass releases a stalled update.
|
|
1527
|
+
if (!inReadOnlyRequest()) {
|
|
1528
|
+
try {
|
|
1529
|
+
releaseStalledUpdate(stateDir, now);
|
|
1530
|
+
}
|
|
1531
|
+
catch { /* an unreadable record is shown as it is */ }
|
|
1530
1532
|
}
|
|
1531
|
-
catch { /* an unreadable record is shown as it is */ }
|
|
1532
1533
|
return updateWaitingOf(stateDir, inTheWay) ?? desktopUpdateWaitingOf(databaseFile, inTheWay, desktopStates);
|
|
1533
1534
|
}
|
|
1534
1535
|
export function markWhatsNewSeen(stateDir) {
|
package/dist/work-index.js
CHANGED
|
@@ -10,7 +10,7 @@ import { readProjectAccess } from './project-access.js';
|
|
|
10
10
|
import { pauseOnTaskProviders, providerName, signInReason } from './provider-auth.js';
|
|
11
11
|
import { scopeTermsProblem, stopFactOf } from './store.js';
|
|
12
12
|
import { assignmentChecksForRun } from './assignment.js';
|
|
13
|
-
import { checkBackingOf, completionBlockersOf, leadOnIt, plainReasonOf, replacedWords, requirementsOf, stageOfCode, taskStatusOf, unverifiedWhenRefuted, workToneOf } from './task-status.js';
|
|
13
|
+
import { approvedCheckOf, checkBackingOf, completionBlockersOf, leadOnIt, plainReasonOf, replacedWords, requirementsOf, stageOfCode, taskStatusOf, unverifiedWhenRefuted, workToneOf } from './task-status.js';
|
|
14
14
|
import { releaseCoverageOf } from './release-coverage.js';
|
|
15
15
|
import { buildReviewOf } from './review-switch.js';
|
|
16
16
|
import { shownVerdictOf } from './proof.js';
|
|
@@ -429,7 +429,7 @@ function readWorkIndexPage(store, now, access, options, taskId = null) {
|
|
|
429
429
|
// automatic review and, with checks Off, the release check that covers it.
|
|
430
430
|
const memo = new Map();
|
|
431
431
|
const readiness = (row, checks) => resultReadinessFacts(store, now, options.root, row, checks, memo);
|
|
432
|
-
return { items: page.map(row => itemOf(row, access.principal, probe, leadName, readiness)), projects, totals: rows[0] === undefined ? { ...EMPTY } : counts(rows[0]),
|
|
432
|
+
return { items: page.map(row => itemOf(row, access.principal, probe, leadName, readiness, options.root !== undefined)), projects, totals: rows[0] === undefined ? { ...EMPTY } : counts(rows[0]),
|
|
433
433
|
groups: rows[0] === undefined ? { ...NO_GROUPS } : groupCounts(rows[0]), nextCursor, limit, view };
|
|
434
434
|
}
|
|
435
435
|
export function workCountsByProject(store, now, access) {
|
|
@@ -489,22 +489,36 @@ function listChecksOf(own, level, follows) {
|
|
|
489
489
|
return null;
|
|
490
490
|
return { status: read.status, exitCode: null, head: null, level: read.level, running: read.running };
|
|
491
491
|
}
|
|
492
|
+
/** A check as a read without the evidence files may show it: only what never claims a pass it can't prove. */
|
|
493
|
+
function unreadChecksOf(checks) {
|
|
494
|
+
if (checks === null)
|
|
495
|
+
return null;
|
|
496
|
+
return checks.status === 'failed' || (checks.level === 'off' && checks.status !== 'passed' && checks.release == null) ? checks : null;
|
|
497
|
+
}
|
|
492
498
|
/** A ready result's requirements, acceptance and completion blockers from its saved records, as assignmentOf reads them. */
|
|
493
499
|
function resultReadinessFacts(store, now, root, row, recorded, memo) {
|
|
494
500
|
const run = n(row, 'result_id'), repo = s(row, 'repo');
|
|
495
501
|
if (!run)
|
|
496
|
-
return { checks: recorded };
|
|
502
|
+
return { checks: root === undefined ? unreadChecksOf(recorded) : recorded };
|
|
497
503
|
const report = s(row, 'result_role') === 'scout';
|
|
498
|
-
//
|
|
499
|
-
|
|
504
|
+
// Read its saved receipt, as the page does: a recorded result (or a verdict alone) is not a check that can still be
|
|
505
|
+
// read, and the row must never say passed while the task page says the check couldn't be read.
|
|
506
|
+
const read = root !== undefined && !report ? assignmentChecksForRun(store, root, run, now) : null;
|
|
507
|
+
// A recorded failure is never lost because its saved files can't be read now (fail closed: negatives stand).
|
|
508
|
+
const saved = read !== null && recorded?.status === 'failed' && (read.status === 'unavailable' || read.status === 'not-run')
|
|
509
|
+
? { ...read, status: 'failed', exitCode: recorded.exitCode } : read;
|
|
500
510
|
const listed = saved === null ? recorded : { status: saved.status, exitCode: saved.exitCode, head: s(row, 'result_head'),
|
|
501
511
|
...(saved.level == null ? {} : { level: saved.level }), ...(saved.running == null ? {} : { running: saved.running }), ...(saved.batch == null ? {} : { batch: saved.batch }) };
|
|
502
|
-
const
|
|
512
|
+
const covered = listed?.level === 'off' && listed.status !== 'passed' && listed.status !== 'failed'
|
|
503
513
|
? { ...listed, release: releaseCoverageOf(store, root, repo, s(row, 'result_head'), { memo }) } : listed;
|
|
504
514
|
const proof = store.proofVerdictFor(run), accepted = store.proofAcceptance(run) !== null;
|
|
515
|
+
// The approved check as the task page reads it (task-status.ts): a recorded pass with no readable check doesn't count.
|
|
516
|
+
// Without the evidence root nothing can be read, so the row makes no claim about the check at all: a recorded
|
|
517
|
+
// failure or checks Off still say so; a pass, or a missing check, is the result's own page to say.
|
|
518
|
+
const checks = report ? covered : root === undefined ? unreadChecksOf(covered) : approvedCheckOf(proof?.verdict ?? null, proof?.reasons ?? [], covered);
|
|
505
519
|
const review = buildReviewOf(store, run);
|
|
506
520
|
const high = review === null || review.sentBackAs !== null || review.state !== 'reviewed' ? 0 : review.high.length;
|
|
507
|
-
const checkRequired = requiredCheckCommandFor(store, repo, String(row['id']), checks?.level) !== null;
|
|
521
|
+
const checkRequired = root === undefined && checks === null ? false : requiredCheckCommandFor(store, repo, String(row['id']), checks?.level) !== null;
|
|
508
522
|
const backing = checkBackingOf(checks);
|
|
509
523
|
const requirements = requirementsOf(proof?.matrix, backing);
|
|
510
524
|
const verdict = shownVerdictOf(proof?.verdict ?? null, { checkFailed: !report && checks?.status === 'failed', highFindings: high, reviewPending: review?.state === 'pending',
|
|
@@ -512,7 +526,7 @@ function resultReadinessFacts(store, now, root, row, recorded, memo) {
|
|
|
512
526
|
const blockers = completionBlockersOf({ report, checks, checkRequired, matrix: proof?.matrix, verdict: proof?.verdict ?? null, accepted, high });
|
|
513
527
|
return { checks, requirements: unverifiedWhenRefuted(requirements, verdict), ...(accepted ? { accepted } : {}), ...(blockers.length === 0 ? {} : { blockers }) };
|
|
514
528
|
}
|
|
515
|
-
function itemOf(row, principal, probe, leadName, readiness) {
|
|
529
|
+
function itemOf(row, principal, probe, leadName, readiness, readable = false) {
|
|
516
530
|
const code = String(row['code']), id = String(row['id']);
|
|
517
531
|
const custody = code === 'process-needs-attention' || (code === 'result-needs-attention' && n(row, 'custody_unresolved') === 1);
|
|
518
532
|
const process = custody && probe !== undefined ? processNeedOf(probe(id)) : null;
|
|
@@ -624,15 +638,18 @@ function itemOf(row, principal, probe, leadName, readiness) {
|
|
|
624
638
|
: coded.stage === 'needs-you' && (coded.need === undefined || coded.need === 'other') && INDEX_NEED[code] !== undefined ? { stage: 'needs-you', need: INDEX_NEED[code] } : coded;
|
|
625
639
|
const checkStatus = s(row, 'check_status'), verdict = s(row, 'proof_verdict');
|
|
626
640
|
const finished = reading.stage === 'finished' || reading.stage === 'complete';
|
|
627
|
-
|
|
628
|
-
const
|
|
629
|
-
const
|
|
641
|
+
// Only a recorded check is a pass; a verdict alone isn't one (task-status.ts approvedCheckOf reads it with the receipt).
|
|
642
|
+
const own = checkStatus === 'failed' || n(row, 'proof_failed_check') === 1 ? 'failed' : checkStatus === 'passed' ? 'passed' : null;
|
|
643
|
+
const recordedChecks = !finished ? null : listChecksOf(own, s(row, 'check_level'), row['check_follows']);
|
|
644
|
+
// A read without the evidence files claims no pass (unreadChecksOf); the readiness read below says the rest.
|
|
645
|
+
const listedChecks = readable ? recordedChecks : unreadChecksOf(recordedChecks);
|
|
646
|
+
const ready = reading.stage === 'finished' && readiness !== undefined ? readiness(row, recordedChecks) : null;
|
|
630
647
|
// Toolroll marked it complete because its work landed: the reason, said once.
|
|
631
648
|
const landed = code === 'complete' ? landedReasonOf(s(row, 'checked_actor'), s(row, 'checked_detail')) : null;
|
|
632
649
|
const shared = taskStatusOf({ stage: reading.stage, ...(reading.need === undefined ? {} : { need: reading.need }), ...(reading.wait === undefined ? {} : { wait: reading.wait }),
|
|
633
650
|
...(process === null ? {} : { needContext: { build: process.build } }),
|
|
634
651
|
reason: finished ? null : plainReasonOf(reading.stage, code, detail || null), report: s(row, 'result_role') === 'scout',
|
|
635
|
-
checks: ready
|
|
652
|
+
checks: ready !== null ? ready.checks : listedChecks,
|
|
636
653
|
...(ready === null ? {} : { requirements: ready.requirements ?? null, ...(ready.accepted ? { accepted: true } : {}), ...(ready.blockers === undefined ? {} : { blockers: ready.blockers }) }),
|
|
637
654
|
completedBy: code !== 'complete' || landed !== null ? null : n(row, 'completed_by_lead') === 1 ? 'the lead' : String(row['checked_actor']).replace(/^(?:operator|coordinator|lead):/, ''),
|
|
638
655
|
...(landed === null ? {} : { landed }),
|
package/dist/workspace-ui.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export { ACCEPT_NEEDS_REASON, cantAcceptYetOf, evidenceProblemOf, type EvidenceP
|
|
|
22
22
|
import { type ProofVerdict } from "./proof.js";
|
|
23
23
|
import type { ReviewRetryState, TaskState } from "./store.js";
|
|
24
24
|
import type { TaskControlView } from "./task-control.js";
|
|
25
|
+
import { type ChecksFact } from "./task-status.js";
|
|
25
26
|
import { type Html } from "./html.js";
|
|
26
27
|
/** The Work destination's views — shortcuts over the same rows, never a
|
|
27
28
|
* persisted state. All is the default. */
|
|
@@ -53,6 +54,10 @@ export type ResultFacts = {
|
|
|
53
54
|
* per run so an older selected result keeps its own words; undefined
|
|
54
55
|
* when the caller did not read it, null when never requested. */
|
|
55
56
|
review?: ReviewFacts | null;
|
|
57
|
+
/** The saved approved check as read now (assignmentChecksForRun), for `approvedCheckOf`: the words say the check
|
|
58
|
+
* passed only when this reading does. Undefined: the caller read no check (a list or dispatch read), so the words
|
|
59
|
+
* name only what the verdict recorded when the build finished. */
|
|
60
|
+
checks?: ChecksFact | null;
|
|
56
61
|
};
|
|
57
62
|
/** The result's review facts as `reviewRetryStateOf` records them, plus
|
|
58
63
|
* the one liveness fact the words depend on. */
|
package/dist/workspace-ui.js
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
import { ACCEPT_NEEDS_REASON, FAILED_CHECK, cantAcceptYetOf, evidenceProblemOf } from "./result-acts.js";
|
|
20
20
|
export { ACCEPT_NEEDS_REASON, cantAcceptYetOf, evidenceProblemOf };
|
|
21
21
|
import { manualReviewOnly, plainReasonWords } from "./proof.js";
|
|
22
|
-
import { OPEN_RESULT, plainReasonOf, stageOfCode, taskStatusOf, workToneOf } from "./task-status.js";
|
|
22
|
+
import { CHECK_COMMAND_CHANGED, CHECK_UNREADABLE, OPEN_RESULT, approvedCheckOf, approvedCheckStateOf, plainReasonOf, stageOfCode, taskStatusOf, workToneOf } from "./task-status.js";
|
|
23
23
|
import { whenUtc } from "./when-html.js";
|
|
24
24
|
import { html, postForm } from "./html.js";
|
|
25
25
|
export const WORK_VIEWS = [
|
|
@@ -122,9 +122,15 @@ export function resultStatusOf(result, publication = null) {
|
|
|
122
122
|
return (result?.role === "builder" ? reviewStatusOf(result.review ?? null) : null) ?? storedResultStatusOf(result, publication);
|
|
123
123
|
}
|
|
124
124
|
/** What the machine itself recorded about a result, in a clause. */
|
|
125
|
-
function machineVerdictWords(result) {
|
|
126
|
-
if (result.verdict
|
|
125
|
+
function machineVerdictWords(result, check) {
|
|
126
|
+
if (check !== undefined && check?.status === "failed" && evidenceProblemOf(result.verdict, result.reasons) !== "checks-failed") {
|
|
127
|
+
return `the approved check failed against it${check.exitCode === null ? "" : ` (exit ${check.exitCode})`}`;
|
|
128
|
+
}
|
|
129
|
+
if (result.verdict === "verified" && passedNow(check))
|
|
127
130
|
return "it verified the result before the acceptance";
|
|
131
|
+
// Unread here, or unreadable now: never restated as a pass.
|
|
132
|
+
if (result.verdict === "verified")
|
|
133
|
+
return check === undefined ? "open the result for its checks" : "its check result couldn't be read";
|
|
128
134
|
if (result.verdict === "attested")
|
|
129
135
|
return "the checks on record are the agent's own report";
|
|
130
136
|
const problem = evidenceProblemOf(result.verdict, result.reasons);
|
|
@@ -136,6 +142,14 @@ function machineVerdictWords(result) {
|
|
|
136
142
|
return "its evidence did not match the sealed record";
|
|
137
143
|
return "its required evidence was missing";
|
|
138
144
|
}
|
|
145
|
+
/** The approved check passed (task-status.ts approvedCheckStateOf): its own pass, or checks Off covered by a passing
|
|
146
|
+
* release check. A re-run underway keeps the pass it had. */
|
|
147
|
+
const passedNow = (check) => {
|
|
148
|
+
const state = approvedCheckStateOf(check);
|
|
149
|
+
return state === "passed" || (state === "running" && check?.status === "passed");
|
|
150
|
+
};
|
|
151
|
+
/** A result whose check this surface didn't read: no pass is claimed, the result page says. */
|
|
152
|
+
const UNREAD_CHECK = "Open the result to see its checks.";
|
|
139
153
|
function storedResultStatusOf(result, publication) {
|
|
140
154
|
if (result === null || result.runId === null) {
|
|
141
155
|
return {
|
|
@@ -151,6 +165,8 @@ function storedResultStatusOf(result, publication) {
|
|
|
151
165
|
}
|
|
152
166
|
const published = publicationStatusOf(publication);
|
|
153
167
|
const withPublication = (detail) => (published === null ? detail : `${detail} ${published.detail}`);
|
|
168
|
+
// Did the approved check pass for this result (task-status.ts): the one answer the Checks row and the road read.
|
|
169
|
+
const check = result.checks === undefined ? undefined : approvedCheckOf(result.verdict, result.reasons, result.checks);
|
|
154
170
|
const humanReview = manualReviewOnly({ verdict: result.verdict ?? "", reasons: result.reasons });
|
|
155
171
|
if (result.accepted && humanReview) {
|
|
156
172
|
return { token: "accepted-exception", label: "Accepted by a person", detail: withPublication("A person accepted this result. The recorded checks are unchanged."), tone: "done", action: { label: "Open the acceptance", kind: "open-review" } };
|
|
@@ -162,14 +178,18 @@ function storedResultStatusOf(result, publication) {
|
|
|
162
178
|
// Acceptance records a person's decision; it neither proves nor
|
|
163
179
|
// disproves anything about the checks. The machine's own verdict is
|
|
164
180
|
// restated as recorded.
|
|
165
|
-
detail: withPublication(`An approver accepted this result by hand, and the recorded exception says why. The machine's verdict is unchanged: ${machineVerdictWords(result)}.`),
|
|
181
|
+
detail: withPublication(`An approver accepted this result by hand, and the recorded exception says why. The machine's verdict is unchanged: ${machineVerdictWords(result, check)}.`),
|
|
166
182
|
tone: "done",
|
|
167
183
|
action: { label: "Open the recorded exception", kind: "open-review" },
|
|
168
184
|
};
|
|
169
185
|
}
|
|
170
|
-
const
|
|
186
|
+
const recordedProblem = evidenceProblemOf(result.verdict, result.reasons);
|
|
187
|
+
// A failed check that a later check on the same commit passed no longer fails the result; a check that failed
|
|
188
|
+
// since a passing verdict does.
|
|
189
|
+
const problem = recordedProblem === "checks-failed" && passedNow(check) ? "none"
|
|
190
|
+
: recordedProblem !== "mismatched" && check?.status === "failed" ? "checks-failed" : recordedProblem;
|
|
171
191
|
if (problem === "checks-failed") {
|
|
172
|
-
const exit = failedCheckExit(result.reasons);
|
|
192
|
+
const exit = check?.status === "failed" ? check.exitCode : failedCheckExit(result.reasons);
|
|
173
193
|
return {
|
|
174
194
|
token: "checks-failed",
|
|
175
195
|
label: "Changes saved, but checks failed",
|
|
@@ -218,7 +238,14 @@ function storedResultStatusOf(result, publication) {
|
|
|
218
238
|
action: { label: "See what is missing", kind: "open-review" },
|
|
219
239
|
};
|
|
220
240
|
}
|
|
221
|
-
|
|
241
|
+
// Any verdict short of a pass the check confirms: never "passed" when the check can't be read now.
|
|
242
|
+
if (check !== undefined && !passedNow(check) && result.verdict === "verified") {
|
|
243
|
+
return check?.status === "running"
|
|
244
|
+
? { token: "checks-running", label: "Checks running", detail: withPublication("The approved check is running on this result."), tone: "live", action: { label: OPEN_RESULT, kind: "open-result" } }
|
|
245
|
+
: { token: "check-unreadable", label: "Result saved — check result couldn't be read", detail: withPublication(`${check?.cause === "command-changed" ? CHECK_COMMAND_CHANGED : CHECK_UNREADABLE} This result isn't verified.`),
|
|
246
|
+
tone: "problem", action: { label: "Open the check", kind: "open-review" } };
|
|
247
|
+
}
|
|
248
|
+
if (result.verdict === "attested" && !passedNow(check)) {
|
|
222
249
|
return {
|
|
223
250
|
token: "agent-attested",
|
|
224
251
|
label: "Result saved — checks reported by the agent",
|
|
@@ -231,7 +258,7 @@ function storedResultStatusOf(result, publication) {
|
|
|
231
258
|
return {
|
|
232
259
|
token: published.token,
|
|
233
260
|
label: published.label,
|
|
234
|
-
detail: `The approved check passed against this result. ${published.detail}`,
|
|
261
|
+
detail: check === undefined ? `${UNREAD_CHECK} ${published.detail}` : `The approved check passed against this result. ${published.detail}`,
|
|
235
262
|
tone: published.token === "pr-closed" ? "muted" : "done",
|
|
236
263
|
action: published.token === "merge-observed" ? { label: OPEN_RESULT, kind: "open-result" } : { label: "Open the pull request", kind: "open-pr" },
|
|
237
264
|
};
|
|
@@ -239,7 +266,8 @@ function storedResultStatusOf(result, publication) {
|
|
|
239
266
|
return {
|
|
240
267
|
token: "ready-to-review",
|
|
241
268
|
label: "Ready",
|
|
242
|
-
|
|
269
|
+
// Only a check read here may be called passed; a surface that read none says where to see it.
|
|
270
|
+
detail: withPublication(check === undefined ? UNREAD_CHECK : "The approved check passed against this result."),
|
|
243
271
|
tone: "ready",
|
|
244
272
|
action: { label: OPEN_RESULT, kind: "open-result" },
|
|
245
273
|
};
|
|
@@ -301,7 +329,7 @@ export function dispatchActionLabel(dispatch) {
|
|
|
301
329
|
}
|
|
302
330
|
/** Result tokens a done task can wear, as the shared stage they mean. A
|
|
303
331
|
* result that is missing its record waits on a person; a failed check fails. */
|
|
304
|
-
const RESULT_NEEDS_YOU = new Set(["no-build-record", "verification-needed", "evidence-mismatch", "record-incomplete", "evidence-damaged"]);
|
|
332
|
+
const RESULT_NEEDS_YOU = new Set(["no-build-record", "verification-needed", "evidence-mismatch", "record-incomplete", "evidence-damaged", "check-unreadable"]);
|
|
305
333
|
/** Result tokens that only follow a passing project check. */
|
|
306
334
|
const VERIFIED_RESULT = new Set(["ready-to-review", "pr-opened", "merge-observed", "pr-closed"]);
|
|
307
335
|
/** A finished result's display status in the shared headline's words: a
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolroll",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.75",
|
|
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"
|