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
|
@@ -14,7 +14,7 @@ import { type CriterionMatrixRow } from "../proof.js";
|
|
|
14
14
|
import { type QualityMode } from "../quality.js";
|
|
15
15
|
import { type Runner } from "../runner.js";
|
|
16
16
|
import { type Scope, type UnattendedPermissionMode } from "../scope.js";
|
|
17
|
-
import { type Decision, type Incident, type RepairChainRow, type Run, type Store, type
|
|
17
|
+
import { type Decision, type Incident, type RepairChainRow, type Run, type Store, type TaskState, type WorktreeRow } from "../store.js";
|
|
18
18
|
import { tally } from "../summary.js";
|
|
19
19
|
import { type WorkIndexGroup, type WorkIndexPage } from "../work-index.js";
|
|
20
20
|
import { type WorkFacts, type WorkStatus, type WorkView } from "../workspace-ui.js";
|
|
@@ -284,8 +284,8 @@ export declare function workPage(chrome: Chrome, data: {
|
|
|
284
284
|
multiProject: boolean;
|
|
285
285
|
now: Date;
|
|
286
286
|
limits?: BrowserLimits | null;
|
|
287
|
+
state?: TaskState | null;
|
|
287
288
|
}): Screen;
|
|
288
|
-
export declare function tasksPage(chrome: Chrome, tasks: Task[], state: TaskState | null, problem: string | null, repo?: string | null, prefill?: TaskComposerPrefill | null, permissionDefault?: UnattendedPermissionMode, qualityDefault?: QualityMode, replaced?: ReadonlyMap<string, string>): Screen;
|
|
289
289
|
export declare function browsePage(chrome: Chrome, data: {
|
|
290
290
|
at: string;
|
|
291
291
|
root: string;
|
|
@@ -442,7 +442,8 @@ export declare function originUrlOf(repoPath: string): string | null;
|
|
|
442
442
|
* action each. Names, descriptions, and paths are gh/filesystem DATA —
|
|
443
443
|
* escaped at the sink like everything else. */
|
|
444
444
|
export declare function githubReposPage(chrome: Chrome, data: {
|
|
445
|
-
|
|
445
|
+
/** The last listing; null until someone lists (a POST: listing asks GitHub, so a page visit never does). */
|
|
446
|
+
listed: ListOutcome | null;
|
|
446
447
|
local: Map<string, string>;
|
|
447
448
|
registered: Set<string>;
|
|
448
449
|
/** Unused: postForm adds the request's CSRF field. Kept for callers. */
|
|
@@ -492,13 +493,17 @@ export declare function fleetScript(): string;
|
|
|
492
493
|
* (finding 18); missing that, a select counts clean.
|
|
493
494
|
*/
|
|
494
495
|
export declare function queueScript(): string;
|
|
495
|
-
export declare function newTaskPage(chrome: Chrome, project: string | null,
|
|
496
|
+
export declare function newTaskPage(chrome: Chrome, project: string | null,
|
|
497
|
+
/** undefined: keep the revision the refused form posted (its draft), so a project switch since still refuses. */
|
|
498
|
+
projectRevision: number | undefined, problem: string | null, candidates?: {
|
|
496
499
|
id: string;
|
|
497
500
|
title: string;
|
|
498
501
|
}[], permissionDefault?: UnattendedPermissionMode, qualityDefault?: QualityMode, projects?: {
|
|
499
502
|
path: string;
|
|
500
503
|
name: string;
|
|
501
|
-
}[]
|
|
504
|
+
}[],
|
|
505
|
+
/** A shipped template's fields, or a refused filing's own words, back in the form. */
|
|
506
|
+
prefill?: TaskComposerPrefill | null): Screen;
|
|
502
507
|
/**
|
|
503
508
|
* The triage flow: everything waiting on a person, one card at a time.
|
|
504
509
|
* Full context ON the card, the act inline, and every act lands back here
|
|
@@ -287,6 +287,10 @@ export function boardBody(data, ciRed) {
|
|
|
287
287
|
return `${Math.round(minutes / 60)}h`;
|
|
288
288
|
return `${Math.round(minutes / (24 * 60))}d`;
|
|
289
289
|
};
|
|
290
|
+
// A lane's head is a status chip in its own tone (after ReUI's badge with a dot): amber only for the lane that
|
|
291
|
+
// waits on a person, blue for building, green for done, neutral otherwise. The word carries the state.
|
|
292
|
+
const LANE_CHIP = { attention: "chip-needs", building: "chip-live", done: "chip-done", queued: "", waiting: "" };
|
|
293
|
+
const laneChip = (key, title) => html `<span class="chip ${LANE_CHIP[key]}"><span class="chip-dot" aria-hidden="true"></span>${title}</span>`;
|
|
290
294
|
const lane = (key, title, hint, renderOne) => {
|
|
291
295
|
const cards = data.cards.filter(card => card.lane === key);
|
|
292
296
|
// The longest-stalled card leads the board: attention sorts by how
|
|
@@ -312,20 +316,21 @@ export function boardBody(data, ciRed) {
|
|
|
312
316
|
// A lane is a section that can fold (board pass): open when it holds
|
|
313
317
|
// cards, folded when empty, so a phone reads the counts first and a
|
|
314
318
|
// desktop column never spends its height on "nothing here".
|
|
315
|
-
return html `<details class="lane lane-${key}"${shown.length === 0 ? null : html ` open`}><summary><h2>${title} <span class="lane-count">${cards.length}${data.saturated ? "+" : ""}</span></h2></summary><p class="hint">${hint}</p>${shown.length === 0 ? html `<p class="meta lane-empty">nothing here</p>` : shown.map((card, index) => renderOne(card, index))}${more > 0 ? html `<a class="lane-more" href="/
|
|
319
|
+
return html `<details class="lane lane-${key}"${shown.length === 0 ? null : html ` open`}><summary><h2>${laneChip(key === "attention" && cards.length === 0 ? "queued" : key, title)} <span class="lane-count">${cards.length}${data.saturated ? "+" : ""}</span></h2></summary><p class="hint">${hint}</p>${shown.length === 0 ? html `<p class="meta lane-empty">nothing here</p>` : shown.map((card, index) => renderOne(card, index))}${more > 0 ? html `<a class="lane-more" href="/work">+${more} more in Tasks</a>` : null}</details>`;
|
|
316
320
|
};
|
|
317
321
|
// The card's facts: mono key–value pairs under the title (board pass) —
|
|
318
322
|
// task, worker, runtime — the same grammar on every lane, so the eye
|
|
319
323
|
// learns one card. Chips carry the words (project, reservation).
|
|
324
|
+
// A value is mono only when a machine said it (a worker, model or branch name, a clock); words stay Geist.
|
|
320
325
|
const facts = (rows) => rows.length === 0
|
|
321
326
|
? null
|
|
322
|
-
: html `<span class="facts">${rows.map(([k, v]) => html `<span class="fact"><span class="k">${k}</span><span class="v">${v}</span></span>`)}</span>`;
|
|
327
|
+
: html `<span class="facts">${rows.map(([k, v, mono]) => html `<span class="fact"><span class="k">${k}</span><span class="${mono === undefined ? "v" : "v mono"}">${v}</span></span>`)}</span>`;
|
|
323
328
|
const chips = (parts) => {
|
|
324
329
|
const kept = parts.filter((one) => one !== null);
|
|
325
330
|
return kept.length === 0 ? null : html `<span class="chips">${joinHtml(kept, " ")}</span>`;
|
|
326
331
|
};
|
|
327
|
-
const plain = (card) => html `<a class="lane-card" href="${card.href}" data-vt="${card.taskId}"><span class="
|
|
328
|
-
...(card.stalledSince === null ? [] : [["waiting", age(card.stalledSince)]]),
|
|
332
|
+
const plain = (card) => html `<a class="lane-card" href="${card.href}" data-vt="${card.taskId}"><span class="t">${card.title}</span><span class="why">${card.reason}</span><span class="id">${card.taskId}</span>${facts([
|
|
333
|
+
...(card.stalledSince === null ? [] : [["waiting", age(card.stalledSince), "mono"]]),
|
|
329
334
|
])}${chips([
|
|
330
335
|
chip(card.repo),
|
|
331
336
|
])}</a>`;
|
|
@@ -342,8 +347,8 @@ export function boardBody(data, ciRed) {
|
|
|
342
347
|
}
|
|
343
348
|
return heads;
|
|
344
349
|
})();
|
|
345
|
-
const queuedCard = (card) => html `<a class="lane-card" href="${card.href}" data-vt="${card.taskId}"><span class="
|
|
346
|
-
|
|
350
|
+
const queuedCard = (card) => html `<a class="lane-card" href="${card.href}" data-vt="${card.taskId}"><span class="t">${card.title}</span><span class="why">${card.reason}</span><span class="id">${card.taskId}</span>${facts([
|
|
351
|
+
card.assignedRunner === null ? ["worker", "any free worker"] : ["worker", card.assignedRunner, "mono"],
|
|
347
352
|
])}${chips([
|
|
348
353
|
queuedHeads.get(`${card.assignedRunner ?? ""}|${card.repo ?? ""}`) === card.taskId
|
|
349
354
|
? html `<span class="badge">${card.assignedRunner === null ? "next up" : `next for ${card.assignedRunner}`}</span>`
|
|
@@ -367,13 +372,15 @@ export function boardBody(data, ciRed) {
|
|
|
367
372
|
const steps = step === null
|
|
368
373
|
? null
|
|
369
374
|
: html `<span class="steps" role="img" aria-label="${`step ${step + 1} of ${BUILD_STEPS.length}: ${BUILD_STEPS[step]}`}">${BUILD_STEPS.map((_, index) => html `<i class="${index < step ? "on" : index === step ? "now" : ""}"></i>`)}</span>`;
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
375
|
+
// Who, which model and which branch, as one muted line: words in Geist, the model and branch in mono.
|
|
376
|
+
const model = claim.model === null ? null : `${claim.provider !== null && claim.provider !== "claude" ? `${claim.provider} · ` : ""}${claim.model}`;
|
|
377
|
+
const branch = claim.branch === null ? null : `${claim.branch}${workspace === null ? "" : ` · ${workspace}`}`;
|
|
378
|
+
const who = joinHtml([
|
|
379
|
+
html `<span>${claim.runner}</span>`,
|
|
380
|
+
model === null ? null : html `<span class="mono">${model}</span>`,
|
|
381
|
+
branch === null ? null : html `<span class="mono">${branch}</span>`,
|
|
382
|
+
].filter((one) => one !== null), html `<span aria-hidden="true"> · </span>`);
|
|
383
|
+
return html `<a class="lane-card building" href="${card.href}" data-vt="${card.taskId}"><span class="t"><span class="dot dot-ok pulse"></span>${card.title}</span>${steps}<span class="live-line"><span class="stage">${live}</span>${tickingClock(claim.claimedAt, data.now)}</span><span class="id">${card.taskId}</span><span class="who">${who}</span>${chips([
|
|
377
384
|
card.attempt === null ? null : html `<span class="badge">Attempt ${card.attempt}</span>`,
|
|
378
385
|
chip(card.repo),
|
|
379
386
|
])}</a>`;
|
|
@@ -384,8 +391,8 @@ export function boardBody(data, ciRed) {
|
|
|
384
391
|
const pr = row.prNumber === null
|
|
385
392
|
? null
|
|
386
393
|
: html `<span class="badge badge-open">PR #${row.prNumber}</span>${ciRed(row.prNumber) ? html ` <span class="badge badge-failed">CI failing</span>` : null}`;
|
|
387
|
-
return html `<a class="lane-card" href="${taskHref(row.taskId)}" data-vt="${row.taskId}"><span class="
|
|
388
|
-
...(row.ranMinutes === null ? [] : [["ran", `${row.ranMinutes}m
|
|
394
|
+
return html `<a class="lane-card" href="${taskHref(row.taskId)}" data-vt="${row.taskId}"><span class="t">${row.title}</span>${row.handoff === null ? null : html `<span class="why">${row.handoff.length > 120 ? row.handoff.slice(0, 120) + "…" : row.handoff}</span>`}<span class="id">${row.taskId}</span>${facts([
|
|
395
|
+
...(row.ranMinutes === null ? [] : [["ran", `${row.ranMinutes}m`, "mono"]]),
|
|
389
396
|
["usage", runCostWords({ authMode: row.authMode, costUsd: row.costUsd, tokensIn: null, tokensOut: null })],
|
|
390
397
|
])}${chips([
|
|
391
398
|
html `<span class="badge badge-done">${row.outcome === "no-change" ? "No change" : "Built"}</span>`,
|
|
@@ -417,11 +424,11 @@ export function boardBody(data, ciRed) {
|
|
|
417
424
|
toggle,
|
|
418
425
|
workerStrip(data.workers, data.cards, data.now),
|
|
419
426
|
html `<div class="board" data-now="${String(data.now.getTime())}">`,
|
|
420
|
-
lane("attention", "
|
|
421
|
-
lane("queued", "
|
|
422
|
-
lane("waiting", "
|
|
423
|
-
lane("building", "
|
|
424
|
-
html `<details class="lane lane-done"${data.done.length === 0 ? null : html ` open`}><summary><h2
|
|
427
|
+
lane("attention", "Needs you", "These wait for a person.", plain),
|
|
428
|
+
lane("queued", "Queued", "Starts when a worker is free.", queuedCard),
|
|
429
|
+
lane("waiting", "Waiting", "Paused until a time, another task, or a requirement is ready.", plain),
|
|
430
|
+
lane("building", "Building", "One agent per card, in its own workspace.", building),
|
|
431
|
+
html `<details class="lane lane-done"${data.done.length === 0 ? null : html ` open`}><summary><h2>${laneChip("done", html `<a href="/done">Done recently</a>`)}</h2></summary><p class="hint">The most recent. The full list is under Done.</p>${doneCards}</details>`,
|
|
425
432
|
html `</div>`,
|
|
426
433
|
], "\n");
|
|
427
434
|
}
|
|
@@ -503,7 +510,7 @@ export function homePage(chrome, data) {
|
|
|
503
510
|
? joinHtml([
|
|
504
511
|
html `<div class="card">`,
|
|
505
512
|
html `<p><strong>Nothing is queued yet — here is the whole loop:</strong></p>`,
|
|
506
|
-
html `<p>1. <a href="/tasks">Add a task</a> — plain words for work you want done${data.repo === null ? null : html ` in <span class="mono">${data.repo}</span>`}.</p>`,
|
|
513
|
+
html `<p>1. <a href="/tasks/new">Add a task</a> — plain words for work you want done${data.repo === null ? null : html ` in <span class="mono">${data.repo}</span>`}.</p>`,
|
|
507
514
|
html `<p>2. Open it and write its scope — the goal, and what it must not become. Approve exactly that.</p>`,
|
|
508
515
|
html `<p>3. Keep Toolroll running on the builder machine. Approved tasks build unattended, each on its own branch.</p>`,
|
|
509
516
|
html `<p class="meta">When an agent is unsure it stops and asks — those questions land here, under “waiting on you”.</p>`,
|
|
@@ -596,12 +603,15 @@ export function taskComposerHtml(data) {
|
|
|
596
603
|
}
|
|
597
604
|
export const TASK_GROUP_LABEL = { ...ASK_LABEL, building: 'Building', rest: 'Recent' };
|
|
598
605
|
export function workPage(chrome, data) {
|
|
599
|
-
|
|
606
|
+
// The stored-state filter (the old task list's) rides every link but a change of state, which starts on page one.
|
|
607
|
+
const href = (view, cursor, state = data.state ?? null) => {
|
|
600
608
|
const query = new URLSearchParams();
|
|
601
609
|
if (data.projectFilter !== undefined)
|
|
602
610
|
query.set('project', data.projectFilter);
|
|
603
611
|
if (view !== 'all')
|
|
604
612
|
query.set('view', view);
|
|
613
|
+
if (state !== null)
|
|
614
|
+
query.set('state', state);
|
|
605
615
|
if (cursor !== undefined)
|
|
606
616
|
query.set('cursor', cursor);
|
|
607
617
|
return `/work${query.size ? `?${query}` : ''}`;
|
|
@@ -614,16 +624,22 @@ export function workPage(chrome, data) {
|
|
|
614
624
|
: browserWorkActionHref(row);
|
|
615
625
|
const action = actionHref === null || row.primaryAction === null ? null : html `<a class="work-action" data-primary-action href="${actionHref}">${row.primaryAction.label} →</a>`;
|
|
616
626
|
const project = data.multiProject || row.repo === null ? html `<span class="project-label">${row.repo === null ? 'Unplaced' : projectName(row.repo)}</span>` : null;
|
|
617
|
-
return html `<article class="work-row" data-task="${row.rootId}" data-work-status="${row.status.token}" data-work-views="${row.status.views.join(' ')}"><div class="work-row-main"><a class="work-title" href="${taskHref(row.rootId)}">${row.title}</a><p class="work-meta">${project}<span>${relativeAge(row.updatedAt, data.now)}</span></p></div><div class="work-row-status">${statusLineHtml(row.status)}${action}${row.familyProblem === null ? null : html `<p class="problem">${row.familyProblem}</p>`}${row.status.views.includes('needs-you') && !['write-scope', 'approve-scope'].includes(row.primaryAction?.code ?? '') && row.status.detail !== row.status.label && row.status.detail !== row.familyProblem ? html `<p class="work-detail">${row.status.detail}</p>` : null}${workDiagnosticsHtml(row.status.diagnostics)}</div></article>`;
|
|
627
|
+
return html `<article class="work-row" data-task="${row.rootId}" data-work-status="${row.status.token}" data-work-views="${row.status.views.join(' ')}"><div class="work-row-main"><a class="work-title" href="${taskHref(row.rootId)}">${row.title}</a><p class="work-meta">${project}<span>${relativeAge(row.updatedAt, data.now)}</span><span class="mono">${row.rootId}</span></p></div><div class="work-row-status">${statusLineHtml(row.status)}${action}${row.familyProblem === null ? null : html `<p class="problem">${row.familyProblem}</p>`}${row.status.views.includes('needs-you') && !['write-scope', 'approve-scope'].includes(row.primaryAction?.code ?? '') && row.status.detail !== row.status.label && row.status.detail !== row.familyProblem ? html `<p class="work-detail">${row.status.detail}</p>` : null}${workDiagnosticsHtml(row.status.diagnostics)}</div></article>`;
|
|
618
628
|
};
|
|
619
629
|
const current = WORK_VIEWS.find(one => one.key === data.view);
|
|
620
|
-
const
|
|
621
|
-
|
|
622
|
-
|
|
630
|
+
const filtered = (data.state ?? null) !== null;
|
|
631
|
+
// The state filter, as real links (the old task list's), so a page without the React view can still change it.
|
|
632
|
+
const stateLinks = html `<nav class="work-states" aria-label="Task state"><span class="meta">State:</span> ${joinHtml([html `<a href="${href(data.view, undefined, null)}"${filtered ? null : html ` aria-current="page"`}>Any</a>`,
|
|
633
|
+
...TASK_STATES.map(one => html `<a href="${href(data.view, undefined, one)}"${data.state === one ? html ` aria-current="page"` : null}>${one}</a>`)], html ` · `)}</nav>`;
|
|
634
|
+
const list = data.work.items.length === 0 && filtered && !data.previous
|
|
635
|
+
? html `<div class="work-empty" data-work-empty="${data.view}"><p>No tasks are ${data.state} in this view.</p><a href="${href(data.view, undefined, null)}">Show every state →</a></div>`
|
|
636
|
+
: data.work.items.length === 0
|
|
637
|
+
? html `<div class="work-empty" data-work-empty="${data.view}"><p>${data.previous ? 'There are no more tasks on this page.' : current.empty}</p>${data.view === 'all' && !data.previous ? html `<p><a class="button-link" href="${chrome.chat === true ? '/chat' : '/tasks/new'}">${chrome.chat === true ? 'Start in chat' : 'Add a task'}</a></p>` : html `<a href="${href('all')}">See all tasks →</a>`}</div>`
|
|
638
|
+
: html `<div class="work-list">${data.work.items.map(rowHtml)}</div>`;
|
|
623
639
|
const pages = !data.previous && data.work.nextCursor === null ? null : html `<nav class="row work-pagination" aria-label="Task pages">${data.previous ? html `<a class="button-link" href="${href(data.view)}">First page</a>` : null}${data.work.nextCursor === null ? null : html `<a class="button-link" rel="next" href="${href(data.view, data.work.nextCursor)}">Next page</a>`}</nav>`;
|
|
624
|
-
const tools = html `<details class="work-tools"><summary>Work tools${CHEVRON_ICON}</summary><nav class="work-tools-menu">${[['/inbox', 'Inbox'], ['/board', 'Board'], ['/board?view=order', 'Order'], ['/
|
|
640
|
+
const tools = html `<details class="work-tools"><summary>Work tools${CHEVRON_ICON}</summary><nav class="work-tools-menu">${[['/inbox', 'Inbox'], ['/board', 'Board'], ['/board?view=order', 'Order'], ['/recipes', 'Recipes'], ...(chrome.projectScoped ? [] : [['/workbench', 'Portfolio']]), ['/ledger', 'Action ledger']]
|
|
625
641
|
.map(([path, label]) => html `<a href="${path}">${label}</a>`)}</nav></details>`;
|
|
626
|
-
const toolLinks = [['/inbox', 'Inbox'], ['/board', 'Board'], ['/board?view=order', 'Order'], ['/
|
|
642
|
+
const toolLinks = [['/inbox', 'Inbox'], ['/board', 'Board'], ['/board?view=order', 'Order'], ['/recipes', 'Recipes'], ...(chrome.projectScoped ? [] : [['/workbench', 'Portfolio']])];
|
|
627
643
|
const view = {
|
|
628
644
|
kind: 'tasks',
|
|
629
645
|
tabs: WORK_VIEWS.map(one => ({ label: one.label, href: href(one.key), count: data.work.totals[one.key], active: one.key === data.view })),
|
|
@@ -649,7 +665,7 @@ export function workPage(chrome, data) {
|
|
|
649
665
|
notes: (row.status.diagnostics ?? []).map(one => `${one.label} · ${one.detail}`),
|
|
650
666
|
};
|
|
651
667
|
}),
|
|
652
|
-
empty: data.work.items.length > 0 ? null : {
|
|
668
|
+
empty: data.work.items.length > 0 ? null : filtered && !data.previous ? { text: `No tasks are ${data.state} in this view.`, action: { label: "Show every state", href: href(data.view, undefined, null) } } : {
|
|
653
669
|
text: data.previous ? 'There are no more tasks on this page.' : current.empty,
|
|
654
670
|
action: data.view === 'all' && !data.previous ? { label: chrome.chat === true ? 'Start in chat' : 'Add a task', href: chrome.chat === true ? '/chat' : '/tasks/new' } : { label: 'See all tasks', href: href('all') },
|
|
655
671
|
},
|
|
@@ -657,26 +673,10 @@ export function workPage(chrome, data) {
|
|
|
657
673
|
tools: toolLinks.map(([path, label]) => ({ label: label, href: path })),
|
|
658
674
|
newTask: { label: 'New task', href: '/tasks/new' },
|
|
659
675
|
limits: data.limits ?? null,
|
|
676
|
+
states: [{ label: 'Any state', href: href(data.view, undefined, null), active: (data.state ?? null) === null },
|
|
677
|
+
...TASK_STATES.map(one => ({ label: one.charAt(0).toUpperCase() + one.slice(1), href: href(data.view, undefined, one), active: data.state === one }))],
|
|
660
678
|
};
|
|
661
|
-
return screen('work', html `<div class="work-head"><h1>Tasks</h1>${tools}</div>${limitsHtml(data.limits ?? null)}${tabs}${list}${pages}`, { chrome, workspace: { view } });
|
|
662
|
-
}
|
|
663
|
-
export function tasksPage(chrome, tasks, state, problem, repo = null, prefill = null, permissionDefault = "auto", qualityDefault = "default", replaced = new Map()) {
|
|
664
|
-
const filters = joinHtml(TASK_STATES.map(one => (one === state ? html `<strong>${one}</strong>` : html `<a href="/tasks?state=${one}">${one}</a>`)), " · ");
|
|
665
|
-
const rows = tasks.length === 0
|
|
666
|
-
? html `<p class="meta">${state === null
|
|
667
|
-
? "The queue is empty — add the first task below. It builds once you approve its scope."
|
|
668
|
-
: `Nothing is ${state}.`}</p>`
|
|
669
|
-
: joinHtml(tasks.map(task => html `<a class="row" href="${taskHref(task.id)}"><span class="mono">${task.id}</span> ${task.title} <span class="right badge badge-${task.state}">${task.state === "cancelled" && replaced.has(task.id) ? `replaced by ${replaced.get(task.id)}` : task.state}</span></a>`), "\n");
|
|
670
|
-
return screen("tasks", joinHtml([
|
|
671
|
-
html `<h1>Tasks</h1>`,
|
|
672
|
-
html `<p class="meta">Work you want done${repo === null ? null : html ` in <strong>${projectName(repo)}</strong>`} — a task builds unattended only after its scope is approved; open one to write or approve its scope</p>`,
|
|
673
|
-
repo === null ? null : html `<p class="meta path-words"><span class="mono">${repo}</span></p>`,
|
|
674
|
-
problem === null ? null : html `<div class="problem">${problem}</div>`,
|
|
675
|
-
html `<p class="meta">Filter: <a href="/tasks">all</a> · ${filters}</p>`,
|
|
676
|
-
rows,
|
|
677
|
-
html `<h2>Add a task</h2>`,
|
|
678
|
-
taskComposerHtml({ project: repo, prefill, permissionDefault, qualityDefault }),
|
|
679
|
-
], "\n"), { chrome });
|
|
679
|
+
return screen('work', html `<div class="work-head"><h1>Tasks</h1>${tools}</div>${limitsHtml(data.limits ?? null)}${tabs}${stateLinks}${list}${pages}`, { chrome, workspace: { view } });
|
|
680
680
|
}
|
|
681
681
|
export function browsePage(chrome, data) {
|
|
682
682
|
const crumb = data.at === data.root ? projectName(data.root) : `${projectName(data.root)}${data.at.slice(data.root.length)}`;
|
|
@@ -925,34 +925,38 @@ export function originUrlOf(repoPath) {
|
|
|
925
925
|
export function githubReposPage(chrome, data) {
|
|
926
926
|
const openForm = (path, label) => postForm("/projects/open", html `<button type="submit">${label}</button>`, { attrs: { class: "inline" }, hidden: { path } });
|
|
927
927
|
const cloneForm = (nameWithOwner) => postForm("/projects/onboard-preview", html `<button type="submit">Clone here →</button>`, { attrs: { class: "inline" }, hidden: { repo: nameWithOwner, root: "0" } });
|
|
928
|
-
const
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
928
|
+
const list = (label) => postForm("/projects/github", html `<button type="submit"${label === "Refresh list" ? html ` class="secondary"` : ""}>${label}</button>`, { attrs: { class: "inline" } });
|
|
929
|
+
const rows = data.listed === null
|
|
930
|
+
? list("List repositories")
|
|
931
|
+
: !data.listed.ok
|
|
932
|
+
? html `<p class="meta">${data.listed.message}</p>`
|
|
933
|
+
: data.listed.repos.length === 0
|
|
934
|
+
? html `<p class="meta">The signed-in GitHub account has no repositories to list</p>`
|
|
935
|
+
: joinHtml(data.listed.repos.map(repo => {
|
|
936
|
+
const localPath = data.local.get(repo.nameWithOwner.toLowerCase()) ?? null;
|
|
937
|
+
const action = localPath !== null && data.openProject === localPath
|
|
938
|
+
? html `<span class="badge badge-done">Open now</span>`
|
|
939
|
+
: localPath !== null
|
|
940
|
+
? openForm(localPath, data.registered.has(localPath) ? "open →" : "add + open →")
|
|
941
|
+
: data.cloneReady
|
|
942
|
+
? cloneForm(repo.nameWithOwner)
|
|
943
|
+
: html `<span class="meta">choose a projects folder first</span>`;
|
|
944
|
+
return joinHtml([
|
|
945
|
+
html `<div class="card project-card">`,
|
|
946
|
+
html `<div class="row"><strong>${repo.nameWithOwner}</strong>${repo.isPrivate ? html ` <span class="badge">Private</span>` : null}`,
|
|
947
|
+
html `<span class="right">${action}</span></div>`,
|
|
948
|
+
localPath === null
|
|
949
|
+
? html `<p class="meta">Not on this machine yet${/^\d{4}-\d{2}-\d{2}T/.test(repo.updatedAt) ? html ` · pushed ${whenTime(repo.updatedAt)}` : null}</p>`
|
|
950
|
+
: html `<p class="meta mono" style="overflow-wrap:anywhere;margin:.2rem 0">${localPath}</p>`,
|
|
951
|
+
repo.description === "" ? null : html `<p class="meta">${repo.description}</p>`,
|
|
952
|
+
html `</div>`,
|
|
953
|
+
], "\n");
|
|
954
|
+
}), "\n");
|
|
952
955
|
return screen("projects", joinHtml([
|
|
953
956
|
html `<h1>Your GitHub repositories</h1>`,
|
|
954
957
|
html `<p class="meta">Repositories available to the GitHub account signed in on this machine — open one you already have, or clone a new one after a quick preview.</p>`,
|
|
955
958
|
rows,
|
|
959
|
+
data.listed === null ? null : list("Refresh list"),
|
|
956
960
|
html `<p class="row" style="margin-top:.6rem"><a class="badge" href="/projects">← back to projects</a></p>`,
|
|
957
961
|
], "\n"), { chrome });
|
|
958
962
|
}
|
|
@@ -1273,7 +1277,11 @@ export function queueScript() {
|
|
|
1273
1277
|
`.then(function(){tell();});});` +
|
|
1274
1278
|
`})();`);
|
|
1275
1279
|
}
|
|
1276
|
-
export function newTaskPage(chrome, project,
|
|
1280
|
+
export function newTaskPage(chrome, project,
|
|
1281
|
+
/** undefined: keep the revision the refused form posted (its draft), so a project switch since still refuses. */
|
|
1282
|
+
projectRevision, problem, candidates = [], permissionDefault = "auto", qualityDefault = "default", projects = [],
|
|
1283
|
+
/** A shipped template's fields, or a refused filing's own words, back in the form. */
|
|
1284
|
+
prefill = null) {
|
|
1277
1285
|
return screen("New task", joinHtml([
|
|
1278
1286
|
html `<section class="task-intake">`,
|
|
1279
1287
|
html `<div class="task-intake-hero"><h1>What should get done?</h1></div>`,
|
|
@@ -1281,7 +1289,8 @@ export function newTaskPage(chrome, project, projectRevision, problem, candidate
|
|
|
1281
1289
|
problem === null ? null : html `<div class="problem">${problem}</div>`,
|
|
1282
1290
|
taskComposerHtml({
|
|
1283
1291
|
project,
|
|
1284
|
-
projectRevision,
|
|
1292
|
+
...(projectRevision === undefined ? {} : { projectRevision }),
|
|
1293
|
+
prefill,
|
|
1285
1294
|
candidates,
|
|
1286
1295
|
permissionDefault,
|
|
1287
1296
|
qualityDefault,
|
|
@@ -16,7 +16,7 @@ import { type BuildReviewView } from "../review-switch.js";
|
|
|
16
16
|
import { type AcceptanceCriterion, type approvalOf } from "../scope.js";
|
|
17
17
|
import { type ReportItem } from "../scout-report.js";
|
|
18
18
|
import { type Artifact, type DiffComment, type Publication, type RepairChainRow, type ReviewRetryState, type Run, type Store, type TaskState } from "../store.js";
|
|
19
|
-
import { type ReleaseState, type PullRequestFact, type TaskStatus } from "../task-status.js";
|
|
19
|
+
import { type ChecksFact, type ReleaseState, type PullRequestFact, type TaskStatus } from "../task-status.js";
|
|
20
20
|
import { type DisplayStatus, type PublicationFacts, type ReviewFacts } from "../workspace-ui.js";
|
|
21
21
|
import { type Chrome, type Screen } from "./chrome.js";
|
|
22
22
|
import { type Html } from "../html.js";
|
|
@@ -423,6 +423,9 @@ export type ProofBundleView = {
|
|
|
423
423
|
repairChain: RepairChainRow | null;
|
|
424
424
|
/** v51: the policy semantic coverage is read under. */
|
|
425
425
|
qualityMode: "default" | "strict";
|
|
426
|
+
/** The saved approved check read now (task-status.ts approvedCheckOf), for a finished build; absent otherwise, and
|
|
427
|
+
* then no words here claim the check passed. */
|
|
428
|
+
checks?: ChecksFact | null;
|
|
426
429
|
};
|
|
427
430
|
/** The smallest complete answer to "what did this task deliver?". It is a
|
|
428
431
|
* projection of the same sealed handoff, proof, screenshot, and diff records
|
|
@@ -471,6 +474,9 @@ export type CompletionReceiptView = {
|
|
|
471
474
|
report: RunReportView | null;
|
|
472
475
|
/** The facts every result surface prints identically (package 3). */
|
|
473
476
|
facts: SharedResultFacts;
|
|
477
|
+
/** The saved approved check as read now (the receipt and any follow-up on the same commit), so the status words
|
|
478
|
+
* read the same answer as the Project checks row (task-status.ts approvedCheckOf). Absent on a scout's report. */
|
|
479
|
+
checks?: ChecksFact | null;
|
|
474
480
|
};
|
|
475
481
|
/** A scout run's report as this run stored it: verified and parsed, or
|
|
476
482
|
* the reason it cannot be shown. The download link serves the exact
|
|
@@ -501,7 +507,10 @@ export declare function runReportView(artifacts: Artifact[], root: string): RunR
|
|
|
501
507
|
* evidence problem in words, and the observed publication state. */
|
|
502
508
|
export declare function sharedResultFactsOf(run: Run, proof: ProofBundleView | null, terminal: TerminalDiffView | null, handoff: StructuredHandoffView | null, report: RunReportView | null, publication: PublicationFacts): SharedResultFacts;
|
|
503
509
|
export declare function proofBundleView(store: Store, run: Run, artifacts: Artifact[], root: string): ProofBundleView | null;
|
|
504
|
-
|
|
510
|
+
/** What the verdict recorded when the build finished, said beside what holds now: a recorded pass is restated only
|
|
511
|
+
* when the check read now passed; unread, it points to the result; unreadable or failed since, it says so. */
|
|
512
|
+
export declare function completionWords(verdict: ProofVerdict | null, reasons: readonly string[], checks: ChecksFact | null | undefined): string;
|
|
513
|
+
export declare function completionReceiptView(store: Store, run: Run, artifacts: Artifact[], root: string, review?: ReviewFacts | null, now?: Date): CompletionReceiptView;
|
|
505
514
|
export type ReviewDiffLine = {
|
|
506
515
|
kind: "context" | "addition" | "deletion" | "meta";
|
|
507
516
|
text: string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { assignmentPresentationOf, historicalAssessmentReason, shortenedMaterialReason } from "../assignment-presentation.js";
|
|
2
2
|
import { assignmentActionHref, assignmentStatusOf, assignmentWithEvidence } from "../assignment-ui.js";
|
|
3
|
-
import { owedAcceptance } from "../assignment.js";
|
|
3
|
+
import { assignmentChecksForRun, owedAcceptance } from "../assignment.js";
|
|
4
|
+
import { releaseCoverageOf } from "../release-coverage.js";
|
|
4
5
|
import {} from "../browser-workspace.js";
|
|
5
6
|
import { CHECK_LEVEL_WORDS, liveQuickCommand } from "../check-levels.js";
|
|
6
7
|
import { LIMITS } from "../decision.js";
|
|
@@ -20,7 +21,7 @@ import {} from "../scope.js";
|
|
|
20
21
|
import { parseReport } from "../scout-report.js";
|
|
21
22
|
import {} from "../store.js";
|
|
22
23
|
import { runCostWords } from "../summary.js";
|
|
23
|
-
import { checkBackingOf, CHECKED_AT_RELEASE, demoChecksOf, pullRequestFactOf, requirementsOf, requirementWordOf, STATUS_MORE, statusDetailsHtml, statusIconSvg, statusWhyHtml } from "../task-status.js";
|
|
24
|
+
import { approvedCheckOf, approvedCheckStateOf, CHECK_COMMAND_CHANGED, CHECK_UNREADABLE, checkBackingOf, CHECKED_AT_RELEASE, checksFactOf, receiptChecksOf, demoChecksOf, pullRequestFactOf, requirementsOf, requirementWordOf, STATUS_MORE, statusDetailsHtml, statusIconSvg, statusWhyHtml } from "../task-status.js";
|
|
24
25
|
import { ACCEPT_NEEDS_REASON, acceptWordsOf, cantAcceptYetOf, evidenceProblemOf, MISMATCH_HEADLINE, receiptHeadingOf, receiptPublicationWords, reportMismatchesOf, RESULT_DECISION_SENTENCE, resultHeadlineOf, resultStatusOf, REVIEW_TOKENS } from "../workspace-ui.js";
|
|
25
26
|
import { createHash, randomBytes } from "node:crypto";
|
|
26
27
|
import { buildsViews, oneLineOf, projectChip, safePrUrl, screen, sentenceCase, strokeIcon, when, whenTime } from "./chrome.js";
|
|
@@ -650,9 +651,12 @@ export function reviewCockpitDetailParts(view, csrf, noted, canRetryReview, tab,
|
|
|
650
651
|
? `${host.base}&run=${run.id}${section === "summary" ? "" : `§ion=${section}`}`
|
|
651
652
|
: `${here}&run=${run.id}${section === "summary" ? "" : `&tab=${section}`}`;
|
|
652
653
|
const assignment = view.assignment === null ? null : assignmentWithEvidence(view.assignment, receiptStatusOf(view.detail.receipt), run.id);
|
|
653
|
-
const
|
|
654
|
+
const saved = assignment?.receipt?.checks;
|
|
655
|
+
// The approved check as the task page reads it (task-status.ts approvedCheckOf): one answer for this whole page.
|
|
656
|
+
const checks = saved === undefined || assignment?.receipt == null ? undefined
|
|
657
|
+
: { ...saved, ...(receiptChecksOf(assignment.receipt) ?? {}), detail: saved.detail };
|
|
654
658
|
if (checks !== undefined)
|
|
655
|
-
parts.push(html `<p class="${checks.status === "failed" || checks.status === "unavailable" ? "problem" : "meta"}" data-actual-checks="${checks.status}">${checks.detail}${checks.logArtifactId === null ? "" : html ` <a href="/r/${run.id}/evidence/${checks.logArtifactId}">Open check output</a>`}</p>`);
|
|
659
|
+
parts.push(html `<p class="${checks.status === "failed" || checks.status === "unavailable" ? "problem" : "meta"}" data-actual-checks="${checks.status}">${checks.status === "unavailable" && saved.cause === "command-changed" ? CHECK_COMMAND_CHANGED : html `${checks.status === "unavailable" && saved.status !== "unavailable" ? `${CHECK_UNREADABLE} ` : ""}${checks.detail}`}${checks.logArtifactId === null ? "" : html ` <a href="/r/${run.id}/evidence/${checks.logArtifactId}">Open check output</a>`}</p>`);
|
|
656
660
|
if (assignment?.completion != null)
|
|
657
661
|
parts.push(html `<p class="meta" data-result-completed>Marked complete by ${assignment.completion.actor.replace(/^operator:/, "")}.</p>`);
|
|
658
662
|
const panel = resultPanelParts(view.detail, {
|
|
@@ -692,7 +696,7 @@ export function reviewCockpitDetailParts(view, csrf, noted, canRetryReview, tab,
|
|
|
692
696
|
const unanswered = accepted || youCheck == null ? [] : youCheck.items.length > 0 ? youCheck.items.map(one => one.statement === "" ? one.words : one.statement) : youCheck.lines;
|
|
693
697
|
const base = !acceptsHere ? null : acceptWordsOf({
|
|
694
698
|
// Checks Off that a release check already covers read as passed (release-coverage.ts).
|
|
695
|
-
checks: checks === undefined ? null :
|
|
699
|
+
checks: checks === undefined ? null : { passed: "passed", failed: "failed", running: "running", "not-run": "not-run", off: "off", unreadable: "unavailable" }[approvedCheckStateOf(checks)],
|
|
696
700
|
// Release-aware words (task-status.ts): a requirement waiting only on a release check, or covered by one, isn't unmet.
|
|
697
701
|
unmet: matrix.filter(row => !["Met", "You check", CHECKED_AT_RELEASE].includes(requirementWordOf(row, null, checks === undefined ? undefined : checkBackingOf(checks)))).length,
|
|
698
702
|
action: complete !== null ? "complete" : "accept",
|
|
@@ -1084,6 +1088,7 @@ export function proofBundleView(store, run, artifacts, root) {
|
|
|
1084
1088
|
: { text: "", artifactId: checkLogArtifact.id, truncated: checkLogArtifact.truncated, problem: read.problem, ...sizes };
|
|
1085
1089
|
}
|
|
1086
1090
|
return {
|
|
1091
|
+
...(run.finishedAt !== null && run.role !== "scout" && run.role !== "reviewer" && run.role !== "planner" ? { checks: savedChecksOf(store, root, run, new Date()) } : {}),
|
|
1087
1092
|
verdict: verdictRow?.verdict ?? null,
|
|
1088
1093
|
reasons: verdictRow?.reasons ?? [],
|
|
1089
1094
|
accepted: acceptanceRow === null ? null : { by: acceptanceRow.approver, note: acceptanceRow.note, at: acceptanceRow.acceptedAt },
|
|
@@ -1114,7 +1119,32 @@ export function proofBundleView(store, run, artifacts, root) {
|
|
|
1114
1119
|
})(),
|
|
1115
1120
|
};
|
|
1116
1121
|
}
|
|
1117
|
-
|
|
1122
|
+
/** What the verdict recorded when the build finished, said beside what holds now: a recorded pass is restated only
|
|
1123
|
+
* when the check read now passed; unread, it points to the result; unreadable or failed since, it says so. */
|
|
1124
|
+
export function completionWords(verdict, reasons, checks) {
|
|
1125
|
+
const recorded = verificationExplanation(verdict, reasons);
|
|
1126
|
+
if (verdict !== "verified")
|
|
1127
|
+
return recorded;
|
|
1128
|
+
const now = approvedCheckStateOf(approvedCheckOf(verdict, reasons, checks));
|
|
1129
|
+
if (checks !== undefined && (now === "passed" || (now === "running" && checks?.status === "passed")))
|
|
1130
|
+
return recorded;
|
|
1131
|
+
if (checks === undefined)
|
|
1132
|
+
return "Open the result to see its checks.";
|
|
1133
|
+
const said = now === "failed" ? `the approved check failed against it${checks?.exitCode == null ? "" : ` (exit ${checks.exitCode})`}.`
|
|
1134
|
+
: checks?.cause === "command-changed" ? "the check command changed since this check." : `${CHECK_UNREADABLE.charAt(0).toLowerCase()}${CHECK_UNREADABLE.slice(1)}`;
|
|
1135
|
+
return `At completion: ${recorded} Now: ${said} It doesn't count as passed.`;
|
|
1136
|
+
}
|
|
1137
|
+
/** A result's saved approved check as the assignment reads it (assignment.ts): the receipt, follow-ups on its commit,
|
|
1138
|
+
* and with checks Off the passing release check that contains it. */
|
|
1139
|
+
function savedChecksOf(store, root, run, now) {
|
|
1140
|
+
const own = checksFactOf(assignmentChecksForRun(store, root, run.id, now), run.headRevision);
|
|
1141
|
+
if (own.level !== "off" || own.status === "passed" || own.status === "failed")
|
|
1142
|
+
return own;
|
|
1143
|
+
const repo = store.refById(run.taskRef)?.repo ?? null;
|
|
1144
|
+
const release = releaseCoverageOf(store, root, repo, run.headRevision);
|
|
1145
|
+
return release == null ? own : { ...own, release };
|
|
1146
|
+
}
|
|
1147
|
+
export function completionReceiptView(store, run, artifacts, root, review = null, now = new Date()) {
|
|
1118
1148
|
const handoff = structuredHandoffView(artifacts, root);
|
|
1119
1149
|
const proof = proofBundleView(store, run, artifacts, root);
|
|
1120
1150
|
const terminal = terminalDiffView(artifacts, root);
|
|
@@ -1149,6 +1179,8 @@ export function completionReceiptView(store, run, artifacts, root, review = null
|
|
|
1149
1179
|
coverageSecondary: coverage.contextGaps.length === 0 && (coverage.satisfied === true || (coverage.satisfied === null && !coverage.required)),
|
|
1150
1180
|
report,
|
|
1151
1181
|
facts: sharedResultFactsOf(run, proof, terminal, handoff, report, publication),
|
|
1182
|
+
// The same reading the task page's Project checks row shows (assignment.ts), never the verdict alone.
|
|
1183
|
+
...(run.role === "scout" ? {} : { checks: savedChecksOf(store, root, run, now) }),
|
|
1152
1184
|
};
|
|
1153
1185
|
}
|
|
1154
1186
|
export const REVIEW_DIFF_LINE_CAP = 4_000;
|
|
@@ -1357,8 +1389,9 @@ export function evidenceBundleCard(view, run, publication = null, review = null)
|
|
|
1357
1389
|
if (view.verdict !== null) {
|
|
1358
1390
|
// The same status line every other surface renders for this run
|
|
1359
1391
|
// (workspace package 1), beside the verdict's own explanation.
|
|
1360
|
-
const status = resultStatusOf({ runId, role: run.role, outcome: run.outcome, verdict: view.verdict, reasons: view.reasons, accepted: view.accepted !== null, review
|
|
1361
|
-
|
|
1392
|
+
const status = resultStatusOf({ runId, role: run.role, outcome: run.outcome, verdict: view.verdict, reasons: view.reasons, accepted: view.accepted !== null, review,
|
|
1393
|
+
...(view.checks === undefined ? {} : { checks: view.checks }) }, publication);
|
|
1394
|
+
parts.push(html `<p class="row" data-proof-verdict="${dispatchStatusToken(view.verdict)}">${statusLineHtml(status)} <span class="meta">${completionWords(view.verdict, view.reasons, view.checks)}</span></p>`);
|
|
1362
1395
|
}
|
|
1363
1396
|
if (view.accepted !== null) {
|
|
1364
1397
|
parts.push(html `<p class="meta">Accepted with an exception by <span class="mono">${view.accepted.by}</span> · ${whenTime(view.accepted.at)}${view.accepted.note === null ? "" : ` — ${view.accepted.note}`}</p>`);
|
|
@@ -1429,7 +1462,8 @@ export function receiptStatusOf(view, review = view.review) {
|
|
|
1429
1462
|
// The evidence health rides the status (repair 2026-09-14): a result
|
|
1430
1463
|
// whose stored records are damaged is never called ready on the strength
|
|
1431
1464
|
// of a verdict recorded before the damage.
|
|
1432
|
-
return evidenceResultStatusOf({ runId: view.runId, role: view.role, outcome: view.outcome, verdict: view.verdict, reasons: view.reasons, accepted: view.accepted, recordComplete: view.recordComplete, review
|
|
1465
|
+
return evidenceResultStatusOf({ runId: view.runId, role: view.role, outcome: view.outcome, verdict: view.verdict, reasons: view.reasons, accepted: view.accepted, recordComplete: view.recordComplete, review,
|
|
1466
|
+
...(view.checks === undefined ? {} : { checks: view.checks }) }, view.publication, view.facts.evidenceHealth);
|
|
1433
1467
|
}
|
|
1434
1468
|
export function completionReceiptCard(view, taskId, place, standing = receiptStatusOf(view), assignment = null, headline = true) {
|
|
1435
1469
|
const current = assignment?.receipt?.runId === view.runId ? assignment : null;
|
|
@@ -1577,8 +1611,11 @@ export function resultPanelParts(detail, o) {
|
|
|
1577
1611
|
// The visible status already says verification is needed. Only omit the
|
|
1578
1612
|
// generic repeat; failed checks, specific reasons and damaged evidence stay.
|
|
1579
1613
|
const missingVerdictNamed = o.headStatus !== false && status.token === "verification-needed" && receipt.verdict === null && receipt.reasons.length === 0;
|
|
1580
|
-
if (current == null && !humanReview && !awaitingGoalReview && (!directAssessment || physicalReasons.length > 0) && !missingVerdictNamed && stored.token !== "evidence-damaged" && (stored.tone === "problem" || stored.tone === "attention"))
|
|
1581
|
-
|
|
1614
|
+
if (current == null && !humanReview && !awaitingGoalReview && (!directAssessment || physicalReasons.length > 0) && !missingVerdictNamed && stored.token !== "evidence-damaged" && (stored.tone === "problem" || stored.tone === "attention")) {
|
|
1615
|
+
// A recorded pass whose check can't be read now is said as what holds now, never as the verdict's pass.
|
|
1616
|
+
// A recorded pass the check read now can't stand behind (unreadable, or failed since) is said as what holds now.
|
|
1617
|
+
attention.push(stored.token === "check-unreadable" || (receipt.verdict === "verified" && stored.token === "checks-failed") ? stored.detail : verificationExplanation(receipt.verdict, physicalReasons));
|
|
1618
|
+
}
|
|
1582
1619
|
// Exact criterion assessment notes are already shown in Requirements;
|
|
1583
1620
|
// do not repeat them as a second current attention message.
|
|
1584
1621
|
if (presentation !== null)
|
|
@@ -1773,7 +1810,12 @@ export function resultPanelParts(detail, o) {
|
|
|
1773
1810
|
if (proof.verdict !== null && !directAssessment) {
|
|
1774
1811
|
// The same status line the panel leads with (one status per surface,
|
|
1775
1812
|
// workspace package 1), beside the machine verdict's own explanation.
|
|
1776
|
-
|
|
1813
|
+
// A recorded pass the saved check can't show now is said so here too (task-status.ts approvedCheckOf).
|
|
1814
|
+
const shown = current?.receipt != null ? receiptChecksOf(current.receipt) : receipt.checks === undefined ? undefined : approvedCheckOf(proof.verdict, proof.reasons, receipt.checks);
|
|
1815
|
+
const unreadNow = proof.verdict === "verified" && shown !== undefined && shown?.status !== "passed" && !humanReview;
|
|
1816
|
+
// completionWords: the verdict at completion, and what holds now when they differ.
|
|
1817
|
+
const words = humanReview ? "At completion: Human review required for the requirements below." : unreadNow ? completionWords(proof.verdict, proof.reasons, shown) : `At completion: ${verificationExplanation(proof.verdict, proof.reasons)}`;
|
|
1818
|
+
checkParts.push(html `<p class="row result-verdict" data-proof-verdict="${dispatchStatusToken(proof.verdict)}"${unreadNow ? html ` data-check-now="${approvedCheckStateOf(shown) ?? "unreadable"}"` : ""}><span class="meta">${words}</span></p>`);
|
|
1777
1819
|
}
|
|
1778
1820
|
else if (proof.verdict === null && run.outcome !== "no-change") {
|
|
1779
1821
|
checkParts.push(html `<p class="meta" data-proof-verdict="none">No verification result is available for this build.</p>`);
|
|
@@ -32,7 +32,7 @@ export function leadSettingsHtml(data) {
|
|
|
32
32
|
const summary = config === null
|
|
33
33
|
? data.signedIn !== null
|
|
34
34
|
? html `<p>The lead is off.</p>${postForm("/settings/lead/on", html `<button type="submit">Use your ${data.signedIn} sign-in</button>`, { returnTo })}`
|
|
35
|
-
: html `<p>The lead is off. Sign in an agent on this computer to turn it on:</p><p><code>${data.command}</code></p
|
|
35
|
+
: html `<p>The lead is off. Sign in an agent on this computer to turn it on:</p><p><code>${data.command}</code></p>${postForm("/lead/check", html `<button type="submit" class="secondary">Check again</button>`, { returnTo })}`
|
|
36
36
|
: data.words !== null
|
|
37
37
|
? html `<p>The lead uses ${data.words}.</p><p class="meta">${config.model === "default" ? "Its default model" : `Model ${config.model}`} · up to ${config.dailyTurns} turns a day · no dollar spend. Every action it proposes waits for you to confirm it.</p>`
|
|
38
38
|
: html `<p>The lead uses the ${config.provider === "anthropic-api" ? "Anthropic" : "OpenRouter"} API with ${config.model}.</p><p class="meta">Up to ${chatMoney(config.weeklyCeilingMicrousd)} a week · up to ${config.dailyTurns} turns a day.</p>`;
|
|
@@ -261,7 +261,7 @@ ${[
|
|
|
261
261
|
html `<details class="provider-manage"><summary>Manage</summary>`,
|
|
262
262
|
postForm("/settings/provider-key", joinHtml([
|
|
263
263
|
"",
|
|
264
|
-
one.connection === undefined ? "" : html `<p class="provider-connection"><strong>${connectionWords(one.connection)}</strong> ${[one.connection.email, one.connection.plan, one.connection.method].filter(Boolean).join(" · ")} · <
|
|
264
|
+
one.connection === undefined ? "" : html `<p class="provider-connection"><strong>${connectionWords(one.connection)}</strong> ${[one.connection.email, one.connection.plan, one.connection.method].filter(Boolean).join(" · ")} · <button type="submit" class="secondary" formaction="/settings/connection/check" formnovalidate>Check again</button></p>`,
|
|
265
265
|
html `<p class="meta">${one.mode === "subscription" ? "Uses its own sign-in, so no API-key spend" : "Uses the API key"} · <span class="mono">${one.envName}</span> · ${one.set ? `key stored${one.updatedAt === null ? "" : ` ${one.updatedAt.slice(0, 10)}`}` : one.ambient ? "key in this server's environment" : "no key stored"}</p>`,
|
|
266
266
|
one.subscriptionCapable
|
|
267
267
|
? html `<label>Sign-in<select name="auth-mode">\
|
|
@@ -372,7 +372,7 @@ ${one.retiredAt === null
|
|
|
372
372
|
: one.set ? { tone: "ok", words: "API key saved" } : one.ambient ? { tone: "ok", words: "Key from this computer’s environment" } : { tone: "off", words: "Not set up" };
|
|
373
373
|
return {
|
|
374
374
|
provider: one.provider, name, tone: status.tone, words: status.words,
|
|
375
|
-
connection: one.connection === undefined ? null : { words: connectionWords(one.connection), facts: [one.connection.email, one.connection.plan, one.connection.method].filter(Boolean).join(" · "),
|
|
375
|
+
connection: one.connection === undefined ? null : { words: connectionWords(one.connection), facts: [one.connection.email, one.connection.plan, one.connection.method].filter(Boolean).join(" · "), checkAction: "/settings/connection/check" },
|
|
376
376
|
usage: `${one.mode === "subscription" ? "Uses its own sign-in, so no API-key spend" : "Uses the API key"} · ${one.set ? `key stored${one.updatedAt === null ? "" : ` ${one.updatedAt.slice(0, 10)}`}` : one.ambient ? "key in this server's environment" : "no key stored"}`,
|
|
377
377
|
envName: one.envName, subscriptionCapable: one.subscriptionCapable, mode: one.mode, set: one.set,
|
|
378
378
|
};
|