toolroll 0.9.74 → 0.9.76
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 +80 -0
- package/dist/agent-fence.js +8 -0
- package/dist/approval-authority.js +6 -2
- package/dist/approval-rules-ui.d.ts +11 -0
- package/dist/approval-rules-ui.js +13 -1
- package/dist/assignment.d.ts +3 -0
- package/dist/assignment.js +27 -8
- package/dist/batch-checks.js +3 -1
- package/dist/browser/workspace.js +27 -27
- package/dist/browser-workspace.d.ts +1 -1
- package/dist/builder.js +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 +23 -9
- package/dist/contracts/console-api.d.ts +6 -1
- package/dist/contracts/console-api.js +11 -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.d.ts +3 -0
- package/dist/deploy-gate.js +3 -2
- package/dist/desktop-update.d.ts +4 -1
- package/dist/desktop-update.js +43 -9
- package/dist/discover.js +6 -1
- package/dist/first-run.d.ts +8 -5
- package/dist/first-run.js +42 -7
- 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/git-incident.d.ts +49 -0
- package/dist/git-incident.js +69 -0
- package/dist/git-views.d.ts +20 -0
- package/dist/git-views.js +120 -0
- package/dist/grant.js +3 -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 +9 -1
- package/dist/planner.js +5 -1
- package/dist/project-knowledge.js +1 -1
- package/dist/project-learning.js +65 -7
- package/dist/project-tools.js +19 -1
- package/dist/pull-request-flow.js +4 -0
- 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.d.ts +24 -2
- package/dist/release-adapters.js +136 -10
- package/dist/release-authority.d.ts +46 -0
- package/dist/release-authority.js +64 -0
- package/dist/release-auto.d.ts +153 -0
- package/dist/release-auto.js +496 -0
- package/dist/release-cli.js +56 -2
- package/dist/release-coverage.js +1 -1
- package/dist/release.d.ts +105 -1
- package/dist/release.js +229 -9
- package/dist/repository-context.js +5 -2
- package/dist/result-follow-ups.js +23 -2
- package/dist/result-review.js +5 -1
- package/dist/scope.d.ts +31 -2
- package/dist/scope.js +44 -3
- package/dist/scout.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.d.ts +10 -0
- package/dist/server/render-tasks.js +30 -17
- package/dist/server/route-table.js +17 -1
- package/dist/server/runtime.d.ts +16 -1
- package/dist/server/settings.js +111 -31
- package/dist/server/task-views.js +8 -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 +4 -1
- package/dist/store.js +52 -13
- package/dist/subagent-work.js +3 -0
- package/dist/task-base.js +1 -1
- package/dist/task-status.d.ts +28 -1
- package/dist/task-status.js +93 -11
- package/dist/teardown-git.d.ts +59 -5
- package/dist/teardown-git.js +1022 -90
- package/dist/telegram-progress.js +4 -4
- package/dist/telegram-status.js +8 -3
- package/dist/test-infra.d.ts +26 -0
- package/dist/test-infra.js +321 -0
- 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/dist/worktree.d.ts +4 -0
- package/dist/worktree.js +13 -7
- package/package.json +1 -1
package/dist/server/chat.js
CHANGED
|
@@ -28,7 +28,7 @@ import { projectName } from "../project.js";
|
|
|
28
28
|
import { fileTaskProposal } from "../proposal.js";
|
|
29
29
|
import { providerName } from "../provider-auth.js";
|
|
30
30
|
import { validModelId } from "../provider.js";
|
|
31
|
-
import { loadOrCreateVapidKeys, validatePushEndpoint } from "../push.js";
|
|
31
|
+
import { loadOrCreateVapidKeys, loadVapidKeys, validatePushEndpoint } from "../push.js";
|
|
32
32
|
import { parseResultTab, RESULT_REVIEW_SCRIPT } from "../result-review.js";
|
|
33
33
|
import {} from "../store.js";
|
|
34
34
|
import { runActivityOf } from "../task-activity.js";
|
|
@@ -44,7 +44,7 @@ import {} from "./render-pages.js";
|
|
|
44
44
|
import { completionForm, hardBlockerOf, homePhaseWords, owedAcceptanceOf, resultPanelHtml } from "./render-results.js";
|
|
45
45
|
import { browserCaller, oneLineUa } from "./session.js";
|
|
46
46
|
export function createChatHandlers(runtime) {
|
|
47
|
-
const { firstRunStepsNow, managedRepos, leadWords, store, options, phoneSetup, visible, liveTurns, leadSaid, evidenceRoot, clock, sessions, CHAT_CANDIDATE_TTL_MS, chatFetcher, CHAT_CANDIDATES_PER_APPROVER, chatCeilingDigest, workAccess, familyOf, firstTasks, leadPrincipal, taskChatFocus, chatScopeOf, leadConversationRows, demoLeadHere, sendScreen, chromeFor, teamBrowserReply, team, teamChatProvider, runVisible, runIsLive, resultDetailOf, pullRequestTargetOf, chatEnablement, startLeadConversation, projectFamilyPeek, needsYouBadge, liveRefreshSeconds, chatKeyFor, chatCatalog, providerHome, mintApprovalNonce, checkLocalAgents, agentSignInCommand, bustBadge, authenticateApprover, revisionDestination, armTaskResume, consumeApprovalNonce } = runtime;
|
|
47
|
+
const { firstRunStepsNow, managedRepos, leadWords, store, options, phoneSetup, visible, liveTurns, leadSaid, evidenceRoot, clock, sessions, CHAT_CANDIDATE_TTL_MS, chatFetcher, CHAT_CANDIDATES_PER_APPROVER, chatCeilingDigest, workAccess, familyOf, firstTasks, leadPrincipal, taskChatFocus, chatScopeOf, leadConversationRows, demoLeadHere, sendScreen, chromeFor, teamBrowserReply, team, teamChatProvider, runVisible, runIsLive, resultDetailOf, pullRequestTargetOf, chatEnablement, startLeadConversation, projectFamilyPeek, needsYouBadge, liveRefreshSeconds, chatKeyFor, chatCatalog, chatCatalogKnown, providerHome, mintApprovalNonce, checkLocalAgents, agentSignInCommand, bustBadge, authenticateApprover, revisionDestination, armTaskResume, consumeApprovalNonce } = runtime;
|
|
48
48
|
// The read-only refresh (package 2): the same JSON status as before —
|
|
49
49
|
// session binding, the send receipt, the live turn — plus a version
|
|
50
50
|
// of the DISPLAYED facts and, only when the caller's version differs,
|
|
@@ -65,7 +65,6 @@ export function createChatHandlers(runtime) {
|
|
|
65
65
|
return respond(response, 200, "application/json", JSON.stringify({ session: null }));
|
|
66
66
|
}
|
|
67
67
|
const sessionId = session?.id ?? NOT_STARTED_SESSION;
|
|
68
|
-
store.sweepStaleLeadTurns(now);
|
|
69
68
|
const request = url.searchParams.get("request") ?? "";
|
|
70
69
|
// Received: the engine took it, or the lead queue holds it (a queued acknowledgement lost on the way still frees the draft).
|
|
71
70
|
const received = session !== null && /^[a-f0-9]{32}$/.test(request) && store.leadRequestReceived(session.id, request);
|
|
@@ -151,9 +150,8 @@ export function createChatHandlers(runtime) {
|
|
|
151
150
|
chrome: chromeFor(null, 'chat', undefined, 'all'), workspace: { team: reply.snapshot, notices: [DEPRECATED_PAGE.team, ...(said ? [said] : [])] },
|
|
152
151
|
}));
|
|
153
152
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
store.sweepCoordinatorProposals(now);
|
|
153
|
+
// Settling a turn past its deadline (its spend) and expiring old proposals are the background passes' work, never a
|
|
154
|
+
// page visit's (docs/plans/get-side-effects.md); the lead's follow pass sweeps every few seconds.
|
|
157
155
|
sweepChatDrafts(Date.now());
|
|
158
156
|
const requestedTask = url.searchParams.get("task");
|
|
159
157
|
const focusTask = taskChatFocus(requestedTask, now, who, { mintNonce: !requestContext.getStore()?.workspaceRead });
|
|
@@ -341,7 +339,7 @@ export function createChatHandlers(runtime) {
|
|
|
341
339
|
tail: found === null || found.source === "environment" ? null : redactToken(found.key),
|
|
342
340
|
};
|
|
343
341
|
}),
|
|
344
|
-
openrouterModels: (
|
|
342
|
+
openrouterModels: chatCatalogKnown()?.map(one => one.id) ?? null,
|
|
345
343
|
liveModels: [...modelOptions(store, "claude", now, providerHome), ...modelOptions(store, "codex", now, providerHome)],
|
|
346
344
|
csrf: who.session.csrf,
|
|
347
345
|
problem: url.searchParams.get("said") ??
|
|
@@ -425,22 +423,39 @@ export function createChatHandlers(runtime) {
|
|
|
425
423
|
const { who, response } = ctx;
|
|
426
424
|
if (who.via !== "cookie")
|
|
427
425
|
return respond(response, 403, "application/json", JSON.stringify({ error: "session" }));
|
|
428
|
-
|
|
426
|
+
// The pair is made as the console starts (docs/plans/get-side-effects.md); this only reads it.
|
|
427
|
+
const keys = options.telegramTokenFile === undefined ? null : loadVapidKeys(dirname(options.telegramTokenFile));
|
|
429
428
|
response.setHeader("cache-control", "no-store");
|
|
429
|
+
if (keys === null)
|
|
430
|
+
return respond(response, 503, "application/json", JSON.stringify({ error: "push is not set up on this console" }));
|
|
430
431
|
return respond(response, 200, "application/json", JSON.stringify({ key: keys.publicKey }));
|
|
431
432
|
}
|
|
432
|
-
//
|
|
433
|
-
//
|
|
433
|
+
// What this computer's agent CLIs said last, and whether the lead is on. A read: the answer comes from the background
|
|
434
|
+
// checks or a Check now (POST /lead/check), never from this request (docs/plans/get-side-effects.md).
|
|
435
|
+
const leadState = () => JSON.stringify({ lead: store.getChatConfig() === null ? "off" : "on", agent: runtime.localSignIn?.signedIn ?? null,
|
|
436
|
+
checkedAt: runtime.localSignIn === null ? null : new Date(runtime.localSignIn.at).toISOString(), command: agentSignInCommand() });
|
|
434
437
|
async function leadStatus(ctx) {
|
|
435
438
|
const { who, response } = ctx;
|
|
436
439
|
if (who.via !== "cookie")
|
|
437
440
|
return respond(response, 403, "application/json", JSON.stringify({ error: "sign in" }));
|
|
441
|
+
response.setHeader("cache-control", "no-store");
|
|
442
|
+
return respond(response, 200, "application/json", leadState());
|
|
443
|
+
}
|
|
444
|
+
// While no agent is signed in, Chat asks this computer again on its own (onboarding), and Settings → Lead has Check
|
|
445
|
+
// now: a fresh, non-spending sign-in check at most every few seconds, and the lead turns on as soon as one is found
|
|
446
|
+
// (the lead-by-default rule). A POST with the person's form token, so no prefetch or link preview can set it off.
|
|
447
|
+
async function leadCheck(ctx) {
|
|
448
|
+
const { response, posted } = ctx;
|
|
449
|
+
const body = readForm(posted, CONSOLE_FORMS.leadCheck);
|
|
438
450
|
if (Date.now() - runtime.leadRecheckedAt > 4_000) {
|
|
439
451
|
runtime.leadRecheckedAt = Date.now();
|
|
440
452
|
await Promise.race([checkLocalAgents(true), new Promise(done => setTimeout(done, 8_000).unref?.())]);
|
|
441
453
|
}
|
|
454
|
+
const back = body.get("return");
|
|
455
|
+
if (back !== null)
|
|
456
|
+
return redirect(response, safeReturn(back));
|
|
442
457
|
response.setHeader("cache-control", "no-store");
|
|
443
|
-
return respond(response, 200, "application/json",
|
|
458
|
+
return respond(response, 200, "application/json", leadState());
|
|
444
459
|
}
|
|
445
460
|
async function taskStatus(ctx) {
|
|
446
461
|
const { url, who, response, now } = ctx;
|
|
@@ -1157,7 +1172,7 @@ export function createChatHandlers(runtime) {
|
|
|
1157
1172
|
intro: HOW_IT_WORKS,
|
|
1158
1173
|
lead: lead === null ? null : { words: `The lead uses ${lead}`, href: "/settings/lead" },
|
|
1159
1174
|
// With no lead yet and no agent signed in, the page asks this computer again on its own.
|
|
1160
|
-
recheck: store.getChatConfig() === null && agent !== undefined && !agent.done ? "/lead/
|
|
1175
|
+
recheck: store.getChatConfig() === null && agent !== undefined && !agent.done ? "/lead/check" : null,
|
|
1161
1176
|
};
|
|
1162
1177
|
}
|
|
1163
1178
|
/** After the first Ready result, once (onboarding): the phone, by a chat app or the console over Tailscale. */
|
|
@@ -1469,18 +1484,10 @@ export function createChatHandlers(runtime) {
|
|
|
1469
1484
|
planUse: (windows?.tiles ?? []).map(one => ({ name: one.name, window: one.window, percent: one.percent, detail: one.detail, tone: one.tone })),
|
|
1470
1485
|
};
|
|
1471
1486
|
}
|
|
1487
|
+
/** The first tasks last found for a project (docs/plans/get-side-effects.md): the background checks ask gh and
|
|
1488
|
+
* git grep while the first run is open; a page shows what they found, or the generic three. */
|
|
1472
1489
|
function firstTasksFor(repo) {
|
|
1473
|
-
|
|
1474
|
-
let entry = firstTasks.get(repo);
|
|
1475
|
-
if (entry === undefined || (entry.until <= at && !entry.reading)) {
|
|
1476
|
-
const reading = { until: at + 10 * 60_000, found: entry?.found ?? null, reading: true };
|
|
1477
|
-
firstTasks.set(repo, reading);
|
|
1478
|
-
entry = reading;
|
|
1479
|
-
void findFirstTasks(repo, options.firstTaskRunner ?? execRun)
|
|
1480
|
-
.then(found => { reading.found = found; }, () => { })
|
|
1481
|
-
.finally(() => { reading.reading = false; });
|
|
1482
|
-
}
|
|
1483
|
-
return entry.found ?? firstTaskSuggestions({ issues: [], todos: [] });
|
|
1490
|
+
return firstTasks.get(repo)?.found ?? firstTaskSuggestions({ issues: [], todos: [] });
|
|
1484
1491
|
}
|
|
1485
1492
|
function approverCandidateCount(approver) {
|
|
1486
1493
|
let count = 0;
|
|
@@ -1518,6 +1525,7 @@ export function createChatHandlers(runtime) {
|
|
|
1518
1525
|
"chat.action": sharedActionReview,
|
|
1519
1526
|
"push.key": pushKey,
|
|
1520
1527
|
"lead.status": leadStatus,
|
|
1528
|
+
"lead.check": leadCheck,
|
|
1521
1529
|
"chat.task-status": taskStatus,
|
|
1522
1530
|
"chat.demo.ask": demoAsk,
|
|
1523
1531
|
"chat.demo.approve": demoAct("approve"),
|
package/dist/server/http.d.ts
CHANGED
|
@@ -165,6 +165,11 @@ export type ServeOptions = {
|
|
|
165
165
|
* the exact part of the ceiling, so an addition or removal shows without a restart. */
|
|
166
166
|
admittedRepos?: () => readonly string[];
|
|
167
167
|
connectionProbe?: typeof execRun;
|
|
168
|
+
/** The console's background checks (docs/plans/get-side-effects.md): sign-in checks, integration checks, model
|
|
169
|
+
* lists and the tidy-ups pages used to do. On for `up` and `serve`; a test turns them on, and `backgroundChecksMs` sets
|
|
170
|
+
* how often they run and ask this computer's agents again. */
|
|
171
|
+
backgroundChecks?: boolean;
|
|
172
|
+
backgroundChecksMs?: number;
|
|
168
173
|
connectionHome?: string;
|
|
169
174
|
/** Test seam for Chat's first tasks: the `gh` and `git grep` reads. */
|
|
170
175
|
firstTaskRunner?: typeof execRun;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The last answer of each check a page shows (docs/plans/get-side-effects.md). A page reads `known`; the background
|
|
3
|
+
* checks and a person's Check now (a POST) call `run`. One per console, held in memory: a restart asks again.
|
|
4
|
+
*/
|
|
5
|
+
export type LastCheck<T> = {
|
|
6
|
+
at: number;
|
|
7
|
+
value: T;
|
|
8
|
+
};
|
|
9
|
+
export declare class LastChecks {
|
|
10
|
+
private readonly answers;
|
|
11
|
+
private readonly running;
|
|
12
|
+
known<T>(key: string): LastCheck<T> | null;
|
|
13
|
+
/** Whether the last answer is older than `everyMs` (or missing). */
|
|
14
|
+
due(key: string, everyMs: number, now?: number): boolean;
|
|
15
|
+
/** Run the check (one at a time per key) and keep its answer. */
|
|
16
|
+
run<T>(key: string, check: () => Promise<T>): Promise<T>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export class LastChecks {
|
|
2
|
+
answers = new Map();
|
|
3
|
+
running = new Map();
|
|
4
|
+
known(key) {
|
|
5
|
+
return this.answers.get(key) ?? null;
|
|
6
|
+
}
|
|
7
|
+
/** Whether the last answer is older than `everyMs` (or missing). */
|
|
8
|
+
due(key, everyMs, now = Date.now()) {
|
|
9
|
+
const last = this.answers.get(key);
|
|
10
|
+
return last === undefined || now - last.at >= everyMs;
|
|
11
|
+
}
|
|
12
|
+
/** Run the check (one at a time per key) and keep its answer. */
|
|
13
|
+
run(key, check) {
|
|
14
|
+
const pending = this.running.get(key);
|
|
15
|
+
if (pending !== undefined)
|
|
16
|
+
return pending;
|
|
17
|
+
const started = check().then(value => { this.answers.set(key, { at: Date.now(), value }); return value; })
|
|
18
|
+
.finally(() => { this.running.delete(key); });
|
|
19
|
+
this.running.set(key, started);
|
|
20
|
+
return started;
|
|
21
|
+
}
|
|
22
|
+
}
|
package/dist/server/pages.js
CHANGED
|
@@ -28,6 +28,7 @@ import { agentLanesOf, agentRowOf, agentsBoardOf, agentFactsFor, laneWindowOf, s
|
|
|
28
28
|
import { markAgentSeen } from "../agent-board.js";
|
|
29
29
|
import { agentsLiveOf } from "../agents-live.js";
|
|
30
30
|
import { readChildWindow } from "../live-children-read.js";
|
|
31
|
+
import { leadFiledLinks } from "./agents-live-filed.js";
|
|
31
32
|
import { readMainWindow } from "../live-main-read.js";
|
|
32
33
|
import { runLiveCommands } from "../store.js";
|
|
33
34
|
import { prometheusMetrics } from "../metrics.js";
|
|
@@ -53,7 +54,7 @@ import { decisionAnswerScript, taskBody } from "./render-tasks.js";
|
|
|
53
54
|
import {} from "./session.js";
|
|
54
55
|
import { rereadBrowser } from "./live.js";
|
|
55
56
|
export function createPagesHandlers(runtime) {
|
|
56
|
-
const { clock, store, visible, consoleProjects, restricted, ceiling, options, projectFamilyPeek, sendScreen, chromeFor, unscopedMode, admissionList, taskRepoOf, taskViewData, runVisible, evidenceRoot, workAccess, identify, liveCeiling, sessions, deletedRepos, authenticateApprover, authenticateCredential, projectOf, lookupSession } = runtime;
|
|
57
|
+
const { clock, lastChecks, store, visible, consoleProjects, restricted, ceiling, options, projectFamilyPeek, sendScreen, chromeFor, unscopedMode, admissionList, taskRepoOf, taskViewData, runVisible, evidenceRoot, workAccess, identify, liveCeiling, sessions, deletedRepos, authenticateApprover, authenticateCredential, projectOf, lookupSession } = runtime;
|
|
57
58
|
// The filesystem browser (operator request): pick a project folder by
|
|
58
59
|
// looking, not typing. CONFINED to exactly what opening allows —
|
|
59
60
|
// explicit --repo mode has an enumerable list and gets no browser at
|
|
@@ -123,7 +124,8 @@ export function createPagesHandlers(runtime) {
|
|
|
123
124
|
const { who, response } = ctx;
|
|
124
125
|
if (store.isDemo())
|
|
125
126
|
return refuse(response, who, 403, "the sandbox never talks to GitHub");
|
|
126
|
-
|
|
127
|
+
// The last listing (docs/plans/get-side-effects.md): List repositories posts; opening the page asks GitHub nothing.
|
|
128
|
+
const listed = lastChecks.known("github-repos")?.value ?? null;
|
|
127
129
|
// What this machine already has, matched by the clone's own recorded
|
|
128
130
|
// origin — registered projects first, then the immediate children of
|
|
129
131
|
// the configured roots. Identities are compared case-insensitively
|
|
@@ -142,7 +144,7 @@ export function createPagesHandlers(runtime) {
|
|
|
142
144
|
// Roots are walked ONLY when there are listed repos to match against
|
|
143
145
|
// (finding 1), through an iterator that stops at 400 entries — a
|
|
144
146
|
// directory with a million children costs 400 stats, not a scan.
|
|
145
|
-
if (listed.ok && listed.repos.length > 0) {
|
|
147
|
+
if (listed !== null && listed.ok && listed.repos.length > 0) {
|
|
146
148
|
for (const root of ceiling.roots) {
|
|
147
149
|
const children = [];
|
|
148
150
|
try {
|
|
@@ -182,6 +184,14 @@ export function createPagesHandlers(runtime) {
|
|
|
182
184
|
openProject: who.via === "cookie" ? who.session.project ?? null : null,
|
|
183
185
|
}));
|
|
184
186
|
}
|
|
187
|
+
// List repositories / Refresh list: ask the GitHub CLI signed in on this machine, then show the page.
|
|
188
|
+
async function projectsGithubList(ctx) {
|
|
189
|
+
const { who, response } = ctx;
|
|
190
|
+
if (store.isDemo())
|
|
191
|
+
return refuse(response, who, 403, "the sandbox never talks to GitHub");
|
|
192
|
+
await lastChecks.run("github-repos", () => (options.ghList ?? listGithubRepos)());
|
|
193
|
+
return redirect(response, "/projects/github");
|
|
194
|
+
}
|
|
185
195
|
async function projectsIndex(ctx) {
|
|
186
196
|
const { url, who, response } = ctx;
|
|
187
197
|
return void projectsScreen(response, who, url.searchParams.get("said"), 200, safeReturn(url.searchParams.get("return")));
|
|
@@ -1027,7 +1037,10 @@ export function createPagesHandlers(runtime) {
|
|
|
1027
1037
|
if (again === null)
|
|
1028
1038
|
return respond(response, 401, "application/json", JSON.stringify({ error: "session" }));
|
|
1029
1039
|
const shown = tiles.filter(tile => { const run = store.getRun(tile.runId); return run !== null && canSee(run); });
|
|
1030
|
-
|
|
1040
|
+
// Work the viewer's own lead filed nests under it (docs/plans/lead-dispatched-tiles.md): proved from the store now,
|
|
1041
|
+
// for the tiles already shown, and only ever for their owner.
|
|
1042
|
+
const filed = again.role === "approver" ? leadFiledLinks(store, again.name, shown.map(tile => tile.runId)) : new Map();
|
|
1043
|
+
return respond(response, 200, "application/json", JSON.stringify({ tiles: shown.map(tile => ({ ...tile, filedBy: filed.get(tile.runId) ?? null })), leads: leadsStillAdmitted(runtime, again, leads) }));
|
|
1031
1044
|
}
|
|
1032
1045
|
/** This console's key for the Live grid's read cursors: random, in memory only (never where a build can reach), so a
|
|
1033
1046
|
* restart makes old cursors start over visibly. A cursor is state, never permission. */
|
|
@@ -1070,6 +1083,7 @@ export function createPagesHandlers(runtime) {
|
|
|
1070
1083
|
const registrations = handlersOf("pages", {
|
|
1071
1084
|
"projects.browse": projectsBrowse,
|
|
1072
1085
|
"projects.github": projectsGithub,
|
|
1086
|
+
"projects.github-list": projectsGithubList,
|
|
1073
1087
|
"projects.page": projectsIndex,
|
|
1074
1088
|
"home": homeRedirect,
|
|
1075
1089
|
"morning": morningAlias,
|
|
@@ -39,7 +39,7 @@ export function sameVisitCheck(authTime, startedAt, now) {
|
|
|
39
39
|
return Math.min(Math.max(at, startedAt), now);
|
|
40
40
|
}
|
|
41
41
|
export function createPeopleHandlers(runtime) {
|
|
42
|
-
const { joinBySource, ssoOffer, ssoVisits, ssoSettings, options, clock, ssoHandoffs, managedRepos, store, visible, admissionList, sendScreen, chromeFor, restricted, sessions, consoleProjects, authenticateApprover, authenticateCredential, bustBadge, projectOf, signInLinks, linkKey, fromThisComputer, recordSignIn, passwordAllowed, arrival, defaultProject, cookieSecure, consoleOrigin, providerFor, joinSourceOf, signInBudget, signInActor, minutesWords } = runtime;
|
|
42
|
+
const { joinBySource, ssoOffer, ssoVisits, ssoSettings, options, clock, ssoHandoffs, managedRepos, store, visible, admissionList, sendScreen, chromeFor, restricted, sessions, consoleProjects, authenticateApprover, authenticateCredential, bustBadge, projectOf, signInLinks, linkKey, fromThisComputer, recordSignIn, passwordAllowed, arrival, defaultProject, cookieSecure, consoleOrigin, providerFor, providerKnown, joinSourceOf, signInBudget, signInActor, minutesWords } = runtime;
|
|
43
43
|
// v101: Settings → Sessions & tokens: where you're signed in, and your API tokens. An instance operator can see everyone's.
|
|
44
44
|
async function sessionsPage(ctx) {
|
|
45
45
|
const { url, who, response, project } = ctx;
|
|
@@ -353,9 +353,12 @@ ${reauth(html `<label>Password <input type="password" name="token" autocomplete=
|
|
|
353
353
|
const origin = consoleOrigin(request.headers.host);
|
|
354
354
|
if (origin === null)
|
|
355
355
|
return page(response, 400, loginPage("Sign in from this computer (localhost) or from your https address.", returnTo, ssoOffer()));
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
356
|
+
// Discovered as the console starts and in the background (docs/plans/get-side-effects.md): following this link
|
|
357
|
+
// fetches nothing.
|
|
358
|
+
const known = providerKnown(settings.issuer);
|
|
359
|
+
if (known === null)
|
|
360
|
+
return page(response, 503, loginPage(`${settings.label} isn't answering yet. Try again in a moment.`, returnTo, ssoOffer()));
|
|
361
|
+
const found = { provider: known };
|
|
359
362
|
const visit = newOidcVisit(), redirectUri = `${origin}${SSO_CALLBACK}`;
|
|
360
363
|
for (const [key, one] of ssoVisits)
|
|
361
364
|
if (one.expires < Date.now())
|
|
@@ -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. */
|
|
@@ -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
|
}
|
|
@@ -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
|
};
|