toolroll 0.9.73 → 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 +63 -0
- package/THIRD_PARTY_NOTICES.md +40 -0
- package/dist/agent-turns.js +5 -0
- package/dist/agents-live.d.ts +31 -0
- package/dist/agents-live.js +43 -0
- package/dist/assignment.d.ts +3 -0
- package/dist/assignment.js +27 -8
- package/dist/browser/THIRD_PARTY_NOTICES.txt +40 -0
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +31 -30
- package/dist/browser-workspace.d.ts +7 -1
- package/dist/builder.js +1 -1
- package/dist/chat-acceptance.d.ts +4 -1
- package/dist/chat-acceptance.js +13 -3
- package/dist/chat-continuity.js +10 -0
- package/dist/chat-quiet.js +3 -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/deploy-gate.js +5 -2
- package/dist/desktop-update.d.ts +4 -1
- package/dist/desktop-update.js +43 -9
- package/dist/dispose.js +35 -2
- package/dist/exec.js +13 -16
- package/dist/first-run.d.ts +20 -1
- package/dist/first-run.js +71 -0
- 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/invoke.d.ts +13 -0
- package/dist/invoke.js +21 -12
- package/dist/knowledge-ui.d.ts +4 -1
- package/dist/knowledge-ui.js +10 -5
- package/dist/lead-contract.d.ts +1 -1
- package/dist/lead-contract.js +2 -2
- package/dist/lead-follow.js +3 -1
- package/dist/lead-progress.d.ts +4 -1
- package/dist/lead-progress.js +3 -1
- package/dist/lead.js +4 -0
- package/dist/live-bus.d.ts +2 -0
- package/dist/live-bus.js +1 -0
- package/dist/live-children-read.d.ts +52 -0
- package/dist/live-children-read.js +333 -0
- package/dist/live-children.d.ts +73 -0
- package/dist/live-children.js +318 -0
- package/dist/live-detail.d.ts +4 -3
- package/dist/live-detail.js +9 -5
- package/dist/live-input.js +7 -8
- package/dist/live-main-read.d.ts +19 -0
- package/dist/live-main-read.js +91 -0
- package/dist/live-open.d.ts +28 -0
- package/dist/live-open.js +125 -0
- package/dist/live-records.d.ts +10 -1
- package/dist/live-records.js +16 -5
- package/dist/live.d.ts +54 -22
- package/dist/live.js +218 -69
- package/dist/openrouter-models.d.ts +5 -2
- package/dist/openrouter-models.js +7 -4
- package/dist/operate.js +5 -2
- package/dist/peek-cli.d.ts +10 -3
- package/dist/peek-cli.js +17 -8
- package/dist/planner.js +1 -1
- package/dist/project-learning.js +44 -6
- package/dist/project-tools.js +19 -1
- package/dist/provider-limits.d.ts +3 -1
- package/dist/provider-limits.js +5 -2
- package/dist/provider.js +6 -12
- 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/release-adapters.js +4 -0
- package/dist/release.d.ts +3 -0
- package/dist/release.js +3 -0
- package/dist/repository-context-ui.js +2 -1
- package/dist/repository-context.js +6 -1
- package/dist/result-follow-ups.js +20 -1
- package/dist/result-review.js +5 -1
- package/dist/run-efficiency.d.ts +3 -3
- package/dist/run-efficiency.js +20 -8
- package/dist/scout.d.ts +3 -0
- package/dist/scout.js +5 -3
- package/dist/serve.js +196 -29
- package/dist/server/agents-live-filed.d.ts +17 -0
- package/dist/server/agents-live-filed.js +97 -0
- package/dist/server/agents-live-lead.d.ts +47 -0
- package/dist/server/agents-live-lead.js +116 -0
- package/dist/server/agents-live-room.d.ts +23 -0
- package/dist/server/agents-live-room.js +91 -0
- package/dist/server/chat.js +115 -53
- package/dist/server/chrome.js +2 -2
- package/dist/server/console.css +68 -40
- 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/live.d.ts +4 -0
- package/dist/server/live.js +42 -2
- package/dist/server/pages.js +98 -4
- package/dist/server/people-tokens.js +7 -4
- package/dist/server/render-chat.d.ts +15 -1
- package/dist/server/render-chat.js +14 -7
- package/dist/server/render-pages.d.ts +10 -5
- package/dist/server/render-pages.js +83 -74
- 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 +13 -3
- package/dist/server/route-table.js +19 -2
- package/dist/server/runtime.d.ts +17 -2
- package/dist/server/settings.js +85 -32
- package/dist/server/task-views.js +1 -2
- package/dist/server/tasks.js +70 -43
- 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 +34 -1
- package/dist/store.js +108 -11
- package/dist/stream-scope.d.ts +18 -0
- package/dist/stream-scope.js +28 -0
- package/dist/subagent-work.js +3 -0
- package/dist/subscription-chat.js +5 -3
- 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/telegram-status.js
CHANGED
|
@@ -4,8 +4,8 @@ import { manualReviewOnly } from "./proof.js";
|
|
|
4
4
|
* No provider calls, repository access, new workflow state, or inferred success. */
|
|
5
5
|
import { diagnoseTaskDispatch, withDispatchDiagnoses } from "./dispatch.js";
|
|
6
6
|
import { familyCompleted } from "./result-completion.js";
|
|
7
|
-
import { assignmentStatusFacts, plainReasonOf, stageOfDispatch, taskStatusOf } from "./task-status.js";
|
|
8
|
-
import { assignmentOf } from "./assignment.js";
|
|
7
|
+
import { approvedCheckOf, approvedCheckStateOf, assignmentStatusFacts, checksFactOf, plainReasonOf, stageOfDispatch, taskStatusOf } from "./task-status.js";
|
|
8
|
+
import { assignmentChecksForRun, assignmentOf } from "./assignment.js";
|
|
9
9
|
import { CHAT_CONTROLS, chatControlHref, chatResultHref } from "./chat-controls.js";
|
|
10
10
|
export function phoneCommand(text) {
|
|
11
11
|
// Slash forms everywhere; the bare words too, because Slack and Discord
|
|
@@ -257,7 +257,12 @@ export function phoneTaskView(store, repos, id, now, root) {
|
|
|
257
257
|
const proof = store.proofVerdictFor(result.id);
|
|
258
258
|
link = taskLinkFor(id, d, { run: result.id, verdict: proof !== null });
|
|
259
259
|
const accepted = store.proofAcceptance(result.id) !== null;
|
|
260
|
-
|
|
260
|
+
// Did the approved check pass (task-status.ts approvedCheckOf): read with the saved files, else no claim at all.
|
|
261
|
+
const checkNow = root === undefined || proof?.verdict !== "verified" ? null
|
|
262
|
+
: approvedCheckStateOf(approvedCheckOf(proof.verdict, proof.reasons, checksFactOf(assignmentChecksForRun(store, root, result.id, now), result.headRevision)));
|
|
263
|
+
const verifiedWords = root === undefined ? "Open the result to see its checks" : checkNow === "passed" ? "Checks passed at completion"
|
|
264
|
+
: checkNow === "failed" ? "Checks failed" : checkNow === "running" ? "Checks running" : "The check result couldn't be read";
|
|
265
|
+
const proofWords = { verified: verifiedWords, attested: "Checks reported by the agent, not run by Toolroll", short: "Required saved material is missing", refuted: "The saved result conflicts with the approved scope" };
|
|
261
266
|
lines.push(`Checks: ${proof === null ? "No completion record saved" : manualReviewOnly(proof) ? accepted ? "Accepted by a person; the recorded checks are unchanged" : "A person must inspect this result; no recorded check failed" : proofWords[proof.verdict]}.`);
|
|
262
267
|
if (manualReviewOnly(proof) && !accepted)
|
|
263
268
|
lines.push('Reply “Send the result summary” for the checks, notes and screenshots.');
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
* database records which key Toolroll made: a folder, key, link or lock that
|
|
28
28
|
* something else put there first is refused, never used.
|
|
29
29
|
*/
|
|
30
|
+
import { inReadOnlyRequest } from "./read-only-request.js";
|
|
30
31
|
import { spawnSync } from "node:child_process";
|
|
31
32
|
import { createCipheriv, createDecipheriv, createHash, randomBytes } from "node:crypto";
|
|
32
33
|
import { closeSync, constants, fstatSync, fsyncSync, linkSync, lstatSync, mkdirSync, openSync, readSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeSync } from "node:fs";
|
|
@@ -541,6 +542,10 @@ export function secretFolder(state) {
|
|
|
541
542
|
const known = folders.get(root);
|
|
542
543
|
if (known !== undefined && unchanged(known))
|
|
543
544
|
return known;
|
|
545
|
+
// A page visit prepares nothing (docs/plans/get-side-effects.md): the console prepares its folder as it starts, and
|
|
546
|
+
// an action checks a changed one again. Until then the page says secrets can't be read right now.
|
|
547
|
+
if (inReadOnlyRequest())
|
|
548
|
+
throw new SecretStoreError(NOT_READY);
|
|
544
549
|
// Changed since it was checked (or never seen): checked against the same record (the database's, for Toolroll's
|
|
545
550
|
// own folder; this process's, for a test's), and refused if it doesn't match.
|
|
546
551
|
const named = pending.get(root);
|
|
@@ -797,6 +802,9 @@ export function readHeldSecret(state, name, file) {
|
|
|
797
802
|
const backend = secretBackend();
|
|
798
803
|
if (backend === null)
|
|
799
804
|
return readOwnFile(path);
|
|
805
|
+
// A page visit moves nothing and takes no lock (docs/plans/get-side-effects.md): the keychain, else the old file.
|
|
806
|
+
if (inReadOnlyRequest())
|
|
807
|
+
return readSealed(backend, folder, heldItem(folder, name)) ?? readOwnFile(path);
|
|
800
808
|
return withSecretLock(folder, `held.${name}`, () => {
|
|
801
809
|
moveHeldIn(backend, folder, name, path);
|
|
802
810
|
return readSealed(backend, folder, heldItem(folder, name));
|
|
@@ -65,9 +65,10 @@ export function updatesHtml(view, notice) {
|
|
|
65
65
|
: view.method.kind === "source" ? html `<h2>Toolroll ${view.current}</h2><p class="meta">This runs from a source checkout. Update it with git.</p>`
|
|
66
66
|
: newer && view.method.kind === "desktop" ? html `<h2>Toolroll ${latest} is available</h2><p class="meta">You have ${view.current}. Update it from the Toolroll app.</p>`
|
|
67
67
|
: newer ? html `<h2>Toolroll ${latest} is available</h2><p class="meta">You have ${view.current}. Running work finishes first, and your current version is kept so you can go back.</p>`
|
|
68
|
-
: "off" in view.latest ? html `<h2>Toolroll ${view.current}</h2><p class="meta">Update checks are off.</p
|
|
69
|
-
:
|
|
70
|
-
: html `<h2>Toolroll ${view.current}
|
|
68
|
+
: "off" in view.latest ? html `<h2>Toolroll ${view.current}</h2><p class="meta">Update checks are off.</p>${postForm("/settings/updates/check", html `<button type="submit">Check now</button>`, { attrs: { class: "update-actions" } })}`
|
|
69
|
+
: "pending" in view.latest ? html `<h2>Toolroll ${view.current}</h2><p class="meta">Not checked for a newer release yet.</p>${postForm("/settings/updates/check", html `<button type="submit">Check now</button>`, { attrs: { class: "update-actions" } })}`
|
|
70
|
+
: latest === null ? html `<h2>Toolroll ${view.current}</h2><p class="meta">Couldn’t check for a newer release: ${"problem" in view.latest ? view.latest.problem : ""}</p>`
|
|
71
|
+
: html `<h2>Toolroll ${view.current} is up to date</h2>`;
|
|
71
72
|
const form = newer && !["npx", "source", "desktop"].includes(view.method.kind)
|
|
72
73
|
? postForm("/settings/updates", html `${reauth(html `<label class="step-up">Your Toolroll password<input type="password" name="password" autocomplete="current-password" required></label>`)}<div class="update-actions"><button type="submit" name="when" value="now" class="primary">Update now</button><button type="submit" name="when" value="when-idle">When idle</button><button type="submit" name="when" value="tonight">Tonight (03:00)</button></div>`, { hidden: { version: latest } })
|
|
73
74
|
: "";
|
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"
|