toolroll 0.9.12 → 0.9.14
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 +14 -0
- package/dist/assignment-ui.js +3 -2
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +25 -25
- package/dist/browser-crew.js +3 -2
- package/dist/browser-workspace.d.ts +46 -0
- package/dist/dispatch.js +7 -2
- package/dist/needs-you.d.ts +24 -0
- package/dist/needs-you.js +100 -0
- package/dist/result-acts.d.ts +46 -0
- package/dist/result-acts.js +38 -0
- package/dist/result-review.js +1 -1
- package/dist/serve.d.ts +1 -0
- package/dist/serve.js +202 -75
- package/dist/task-status.d.ts +4 -0
- package/dist/task-status.js +8 -2
- package/dist/telegram-progress.js +2 -2
- package/dist/test-temp.js +1 -1
- package/dist/work-index.js +6 -3
- package/dist/workspace-ui.d.ts +48 -0
- package/dist/workspace-ui.js +95 -3
- package/package.json +1 -1
package/dist/serve.js
CHANGED
|
@@ -56,6 +56,7 @@ import { knowledgeHtml, knowledgeContextHtml, KNOWLEDGE_CSS, decisionsHtml, memo
|
|
|
56
56
|
import { listDecisions, recordDecision, retireDecision, searchMemory } from "./project-memory.js";
|
|
57
57
|
import { decideProposal, listProposals, memoryStatus } from "./memory-pass.js";
|
|
58
58
|
import { learningHtml } from "./workspace-ui.js";
|
|
59
|
+
import { resultActsOf } from "./result-acts.js";
|
|
59
60
|
import { createSessionEndpoint } from './session-server.js';
|
|
60
61
|
import { handleTeamHttp } from './team-http.js';
|
|
61
62
|
import { teamWorkspaceHtml } from './team-ui.js';
|
|
@@ -74,7 +75,7 @@ import { leadBriefHtml, LEAD_CONTEXT_CSS } from './lead-context.js';
|
|
|
74
75
|
import { assignmentCatchUp } from './assignment-brief.js';
|
|
75
76
|
import { assignmentActionHref, assignmentCardOf, assignmentStatusOf, assignmentSummaryHtml, assignmentWithEvidence, ASSIGNMENT_CSS } from './assignment-ui.js';
|
|
76
77
|
import { assignmentStageOf, pullRequestFactOf, stageOfCode, stageOfDispatch, statusDetailsHtml, statusIconSvg, statusWhyHtml, taskStatusOf, TASK_STATUS_CSS } from './task-status.js';
|
|
77
|
-
import { ASKS, ASK_LABEL, NEEDS } from './needs-you.js';
|
|
78
|
+
import { ASKS, ASK_LABEL, NEEDS, NO_REASON_RECORDED, failedAttemptSentence, isInternalErrorReason, latestFinishedAttempt, runReasonWords } from './needs-you.js';
|
|
78
79
|
import { assignmentPresentationOf, historicalAssessmentReason, shortenedMaterialReason } from './assignment-presentation.js';
|
|
79
80
|
import { ledgerBody } from "./ledger-view.js";
|
|
80
81
|
import { EVIDENCE_PACK_CSS, evidencePack, evidencePackHtml, exportDay, ledgerExportChunks } from "./evidence-pack.js";
|
|
@@ -163,7 +164,7 @@ import { starterRecipes, savedRecipes, findRecipe, importRecipe, exportRecipe, c
|
|
|
163
164
|
import { recipeFromForm, recipeLibraryHtml, recipeEditorHtml, workflowPreviewHtml, recipeScript, RECIPE_CSS, recipeDefinitionPreviewHtml, recipeRunHtml, recipeAnswersFromForm } from "./recipe-ui.js";
|
|
164
165
|
import { EVIDENCE_CAPS, readVerifiedArtifact, readVerifiedReport, readVerifiedProofForRun, storeEvidence, writeEvidenceFile, scanForSecrets } from "./evidence.js";
|
|
165
166
|
import { GOAL_ASSESSMENT_PENDING, reviewConflict, manualReviewOnly, manualReviewCriterionOf, personCheckWords, plainReasonWords, dispatchStatusToken, passFraction, semanticCoverage, coverageWords, coverageStateWords } from "./proof.js";
|
|
166
|
-
import { WORK_VIEWS, REVIEW_TOKENS, RESULT_DECISION_SENTENCE, acceptWordsOf, parseWorkView, resultStatusOf, resultHeadlineOf, receiptHeadingOf, receiptPublicationWords, reviewFactsOf, workStatusOf, primaryDestinationOf, needsPerson, dispatchActionLabel, } from "./workspace-ui.js";
|
|
167
|
+
import { WORK_VIEWS, REVIEW_TOKENS, RESULT_DECISION_SENTENCE, acceptWordsOf, cantAcceptYetOf, evidenceProblemOf, lastErrorLineOf, reportMismatchesOf, ACCEPT_NEEDS_REASON, MISMATCH_HEADLINE, parseWorkView, resultStatusOf, resultHeadlineOf, receiptHeadingOf, receiptPublicationWords, reviewFactsOf, workStatusOf, primaryDestinationOf, needsPerson, dispatchActionLabel, } from "./workspace-ui.js";
|
|
167
168
|
import { PRICED_BUILD_MODELS } from "./pricing.js";
|
|
168
169
|
import { RESULT_TABS, RESULT_REVIEW_SCRIPT, isRevisionFeedback, evidenceResultStatusOf, evidenceShortenedWords, parseResultTab, resultLeadOf, evidenceProblemDetailsOf, evidenceHealthOf, resultFactsAttributes, resultFactsAttributeMap, resultReturnTarget, commentSourceKey, REQUEST_TOKEN, revisionBatchOf, parseRevisionBatch, revisionSourceOf, } from "./result-review.js";
|
|
169
170
|
import { parseReport } from "./scout-report.js";
|
|
@@ -1761,7 +1762,9 @@ export function createDecisionServer(options) {
|
|
|
1761
1762
|
}
|
|
1762
1763
|
// Exact result links carry their own read context. Check the stored
|
|
1763
1764
|
// placement against both account and instance access before using it;
|
|
1764
|
-
// viewing a result never changes the session's selected project
|
|
1765
|
+
// viewing a result never changes the session's selected project, and
|
|
1766
|
+
// the page's project switch keeps showing the person's own choice.
|
|
1767
|
+
const chosenProject = project;
|
|
1765
1768
|
if (url.pathname === "/review" && url.searchParams.has("result")) {
|
|
1766
1769
|
const wanted = url.searchParams.get("result") ?? "";
|
|
1767
1770
|
const ref = wanted.length > 0 && wanted.length <= 64 && !hasForbiddenControls(wanted) ? store.lookupRef(wanted) : null;
|
|
@@ -2390,11 +2393,14 @@ export function createDecisionServer(options) {
|
|
|
2390
2393
|
const selectedRow = wantedId === null ? ranked[0] ?? null : chosen;
|
|
2391
2394
|
const csrf = who.via === "cookie" ? who.session.csrf : "";
|
|
2392
2395
|
const expectedRun = url.searchParams.get("run");
|
|
2396
|
+
// An exact link to a result this person can't see (or that never existed) reads the same either way.
|
|
2397
|
+
if (expectedRun !== null && wantedId !== null && chosen === null)
|
|
2398
|
+
return refuse(response, who, 404, "No such result in your projects.", "/work");
|
|
2393
2399
|
if (expectedRun !== null && (!/^[1-9]\d*$/.test(expectedRun) || selectedRow?.runId !== Number(expectedRun))) {
|
|
2394
2400
|
return sendScreen(response, 409, screen("Result changed", '<h1>Result changed</h1><p>This acceptance link no longer matches the current result. Review the current task before accepting.</p><p class="refusal-back"><a class="button-link" href="/review">Review results</a></p>', { chrome: chromeFor(project, "runs") }));
|
|
2395
2401
|
}
|
|
2396
2402
|
const selected = selectedRow === null ? null : reviewCockpitViewOf(selectedRow, who, now);
|
|
2397
|
-
const reviewPage = reviewCockpitPage(chromeFor(project, "runs"), {
|
|
2403
|
+
const reviewPage = reviewCockpitPage(chromeFor(wantedId === null ? project : chosenProject, "runs"), {
|
|
2398
2404
|
queue: ranked,
|
|
2399
2405
|
queueCap: REVIEW_QUEUE_CAP,
|
|
2400
2406
|
selected,
|
|
@@ -3020,6 +3026,14 @@ export function createDecisionServer(options) {
|
|
|
3020
3026
|
}
|
|
3021
3027
|
return respond(response, 200, "application/json", JSON.stringify({ text: window.text, nextOffset: window.nextOffset, final: !live && window.eof }));
|
|
3022
3028
|
}
|
|
3029
|
+
// One result page (2026-10-02): a builder's finished result opens on
|
|
3030
|
+
// its task's result page, titled with the task. The raw run record
|
|
3031
|
+
// waits there under Details, and here with ?record=1.
|
|
3032
|
+
if (url.searchParams.get("record") === null && !running && found.role === "builder" && (found.outcome === "built" || found.outcome === "no-change") &&
|
|
3033
|
+
completedRowFor(taskId, null)?.runId === found.id) {
|
|
3034
|
+
const tab = parseResultTab(url.searchParams.get("tab"));
|
|
3035
|
+
return redirect(response, `${reviewHref(taskId, found.id)}${tab === "summary" ? "" : `&tab=${tab}`}`);
|
|
3036
|
+
}
|
|
3023
3037
|
// Pollers exist only for a LIVE run — an orphaned null-outcome run
|
|
3024
3038
|
// would otherwise be refetched forever (finding 15). The nonce is
|
|
3025
3039
|
// sendScreen's business now.
|
|
@@ -5881,6 +5895,33 @@ export function createDecisionServer(options) {
|
|
|
5881
5895
|
return null;
|
|
5882
5896
|
return { taskId: rootId, view: null, offer: { taskId: receipt.taskId, runId: receipt.runId, digest: receipt.digest }, target };
|
|
5883
5897
|
}
|
|
5898
|
+
/** What went wrong with the latest finished attempt (whatever its outcome, never an older failure), in one line: a
|
|
5899
|
+
* failing check's last error line (linked to that line in its saved log), else the attempt's recorded reason in plain
|
|
5900
|
+
* words, else that no reason was recorded. A reason that is machine output reads as an internal error, its detail
|
|
5901
|
+
* behind a link to that line of the attempt's record; nothing links to this task's own page. */
|
|
5902
|
+
function failureOf(runs, assignment) {
|
|
5903
|
+
const last = latestFinishedAttempt(runs);
|
|
5904
|
+
if (last === null)
|
|
5905
|
+
return { line: NO_REASON_RECORDED, link: null };
|
|
5906
|
+
const receipt = assignment?.receipt?.runId === last.id ? assignment.receipt : null;
|
|
5907
|
+
if (receipt !== null && receipt.checks.status === "failed" && receipt.checks.logArtifactId !== null) {
|
|
5908
|
+
const artifact = store.artifactsFor(last.id).find(one => one.id === receipt.checks.logArtifactId) ?? null;
|
|
5909
|
+
let read = null;
|
|
5910
|
+
try {
|
|
5911
|
+
read = artifact === null ? null : readVerifiedArtifact(evidenceRoot, artifact);
|
|
5912
|
+
}
|
|
5913
|
+
catch {
|
|
5914
|
+
read = null;
|
|
5915
|
+
}
|
|
5916
|
+
const found = read !== null && read.ok ? lastErrorLineOf(read.content.toString("utf8")) : null;
|
|
5917
|
+
if (found !== null) {
|
|
5918
|
+
return { line: oneLineOf(found.text, 140), link: { label: `Check output, line ${found.line}`,
|
|
5919
|
+
href: `${reviewHref(receipt.taskId)}&run=${last.id}&tab=checks#check-log-L${found.line}` } };
|
|
5920
|
+
}
|
|
5921
|
+
}
|
|
5922
|
+
return { line: failedAttemptSentence(last.reason),
|
|
5923
|
+
link: isInternalErrorReason(last.reason) ? { label: "The recorded error", href: `/r/${last.id}?record=1#run-reason-detail` } : null };
|
|
5924
|
+
}
|
|
5884
5925
|
function taskScreen(response, who, taskId, problem, status, scopeDraft, cancelDraft) {
|
|
5885
5926
|
const admittedFamily = familyOf(taskId);
|
|
5886
5927
|
if (admittedFamily?.problem != null)
|
|
@@ -5903,6 +5944,8 @@ export function createDecisionServer(options) {
|
|
|
5903
5944
|
versions: family.versions.map(one => ({ id: one.id, title: one.title, state: one.state })),
|
|
5904
5945
|
runs: family.versions.flatMap(version => version.id === data.task.id ? data.runs.map(run => ({ ...run, taskId: version.id })) : store.runsFor(version.refId).map(run => ({ ...run, taskId: version.id }))) };
|
|
5905
5946
|
}
|
|
5947
|
+
// What went wrong reads the family's latest finished attempt, as the Tasks row does.
|
|
5948
|
+
presentedData.failure = failureOf(presentedData.family?.runs ?? data.runs, data.assignment ?? null);
|
|
5906
5949
|
const paneProject = restricted() ? store.lookupRef(taskId)?.repo ?? null : who.via === "cookie" ? who.session.project : null;
|
|
5907
5950
|
const page = taskPage(paneProject === null && !unscopedMode
|
|
5908
5951
|
? chromeFor(paneProject, "tasks")
|
|
@@ -10432,10 +10475,21 @@ export function createDecisionServer(options) {
|
|
|
10432
10475
|
return redirect(response, taskHref(taskId));
|
|
10433
10476
|
}
|
|
10434
10477
|
case "requeue": {
|
|
10478
|
+
// Retry may carry a note for the next attempt: the same steering note, checked before anything changes.
|
|
10479
|
+
const note = (body.get("note") ?? "").trim();
|
|
10480
|
+
if (note !== "" && who.via !== "cookie")
|
|
10481
|
+
return refuse(response, who, 403, "steering is a browser session's act");
|
|
10482
|
+
if (note !== "" && !validateNote(note).ok)
|
|
10483
|
+
return taskScreen(response, who, taskId, "that note will not store, so nothing was retried", 400);
|
|
10435
10484
|
const requeued = store.requeueTask(taskId, who.name, now);
|
|
10436
10485
|
if (!requeued.ok) {
|
|
10437
10486
|
return taskScreen(response, who, taskId, `not requeued: ${requeued.reason}`, 409);
|
|
10438
10487
|
}
|
|
10488
|
+
if (note !== "") {
|
|
10489
|
+
const filed = store.fileSteerNote(taskId, verifiedAuthor(who.name), note, now);
|
|
10490
|
+
if (!filed.ok)
|
|
10491
|
+
return taskScreen(response, who, taskId, "Retried. The note wasn't saved; add it under Steering.", 409);
|
|
10492
|
+
}
|
|
10439
10493
|
// Allow-listed return only — never an arbitrary URL from the form.
|
|
10440
10494
|
return redirect(response, body.get("return") === "inbox" ? "/inbox" : body.get("return") === "next" ? "/next" : taskHref(taskId));
|
|
10441
10495
|
}
|
|
@@ -10975,7 +11029,7 @@ export function createDecisionServer(options) {
|
|
|
10975
11029
|
const completed = checkAssignmentAsOperator(store, taskId, digest, principal, now, evidenceRoot);
|
|
10976
11030
|
if (!completed.ok)
|
|
10977
11031
|
return taskScreen(response, who, taskId, completed.message, 409);
|
|
10978
|
-
return redirect(response, reviewHref(taskId, Number(namedRun)
|
|
11032
|
+
return redirect(response, reviewHref(taskId, Number(namedRun)));
|
|
10979
11033
|
}
|
|
10980
11034
|
case "retry-review": {
|
|
10981
11035
|
return taskScreen(response, who, taskId, "Separate agent reviews have retired. Open the saved result to mark it complete or request a revision.", 410);
|
|
@@ -12708,7 +12762,7 @@ ${THEME_MAPPING}
|
|
|
12708
12762
|
--font-mono: "Geist Mono", ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
|
|
12709
12763
|
/* Phone spacing steps (760px and narrower): side gutter, gap under the
|
|
12710
12764
|
header, gap between page blocks, between rows, and inside a card. */
|
|
12711
|
-
--so-phone-gutter:
|
|
12765
|
+
--so-phone-gutter: 16px; --so-phone-gap: 12px; --so-phone-block: 14px; --so-phone-row: 8px; --so-phone-card: 12px;
|
|
12712
12766
|
}
|
|
12713
12767
|
@media (prefers-color-scheme: dark) {
|
|
12714
12768
|
:root:not([data-theme="light"]) {
|
|
@@ -18175,9 +18229,9 @@ function statusActionHref(status, taskId, runId, prUrl, repo) {
|
|
|
18175
18229
|
return null;
|
|
18176
18230
|
switch (status.action.kind) {
|
|
18177
18231
|
case "open-result": return runId === null ? taskHref(taskId) : `/r/${runId}`;
|
|
18178
|
-
case "open-review": return repo === undefined ? reviewHref(taskId) : reviewHref(taskId, runId
|
|
18232
|
+
case "open-review": return repo === undefined ? reviewHref(taskId) : reviewHref(taskId, runId);
|
|
18179
18233
|
case "open-run": return runId === null ? taskHref(taskId) : `/r/${runId}`;
|
|
18180
|
-
case "open-pr": return safePrUrl(prUrl) ?? (repo === undefined ? reviewHref(taskId) : reviewHref(taskId, runId
|
|
18234
|
+
case "open-pr": return safePrUrl(prUrl) ?? (repo === undefined ? reviewHref(taskId) : reviewHref(taskId, runId));
|
|
18181
18235
|
case "open-task":
|
|
18182
18236
|
default: return taskHref(taskId);
|
|
18183
18237
|
}
|
|
@@ -18211,7 +18265,7 @@ function workPage(chrome, data) {
|
|
|
18211
18265
|
const rowHtml = (row) => {
|
|
18212
18266
|
const target = row.primaryAction?.target;
|
|
18213
18267
|
const actionHref = row.primaryAction?.code === 'open-result' && target?.runId != null
|
|
18214
|
-
? `/review?result=${encodeURIComponent(target.taskId)}&run=${target.runId}
|
|
18268
|
+
? `/review?result=${encodeURIComponent(target.taskId)}&run=${target.runId}`
|
|
18215
18269
|
: browserWorkActionHref(row);
|
|
18216
18270
|
const action = actionHref === null || row.primaryAction === null ? '' : `<a class="work-action" data-primary-action href="${escape(actionHref)}">${escape(row.primaryAction.label)} →</a>`;
|
|
18217
18271
|
const project = data.multiProject || row.repo === null ? `<span class="project-label">${row.repo === null ? 'Unplaced' : escape(projectName(row.repo))}</span>` : '';
|
|
@@ -18245,7 +18299,7 @@ function workPage(chrome, data) {
|
|
|
18245
18299
|
rows: data.work.items.map(row => {
|
|
18246
18300
|
const target = row.primaryAction?.target;
|
|
18247
18301
|
const actionHref = row.primaryAction?.code === 'open-result' && target?.runId != null
|
|
18248
|
-
? `/review?result=${encodeURIComponent(target.taskId)}&run=${target.runId}
|
|
18302
|
+
? `/review?result=${encodeURIComponent(target.taskId)}&run=${target.runId}`
|
|
18249
18303
|
: browserWorkActionHref(row);
|
|
18250
18304
|
const needsYouDetail = (row.status.views.includes('needs-you') || row.status.label === 'Failed') && !['write-scope', 'approve-scope'].includes(row.primaryAction?.code ?? '') && row.status.label !== 'Ready for review' && row.status.detail !== row.status.label && row.status.detail !== row.familyProblem;
|
|
18251
18305
|
return {
|
|
@@ -19814,7 +19868,8 @@ function taskBodyParts(data) {
|
|
|
19814
19868
|
case "approve-scope": return ` <a href="#approve">Review and sign the exact scope</a>.`;
|
|
19815
19869
|
case "answer-decision": return ` <a href="#decisions">Answer the waiting question</a>.`;
|
|
19816
19870
|
case "unhold": return ` Use <strong>Remove hold</strong> when it can continue.`;
|
|
19817
|
-
|
|
19871
|
+
// The reason is the detail itself; Retry is the status card's own act, said once.
|
|
19872
|
+
case "retry-task": return "";
|
|
19818
19873
|
case "repair-capability": return ` <a href="/caps">Repair the requirement</a>.`;
|
|
19819
19874
|
case "repair-dependency":
|
|
19820
19875
|
return blocker?.admitted === true
|
|
@@ -19857,7 +19912,8 @@ function taskBodyParts(data) {
|
|
|
19857
19912
|
: `<p class="meta">This task resumes automatically when the builder reconnects. You do not need to file or approve it again.</p>`) +
|
|
19858
19913
|
`<p><a href="/system">See connection status →</a></p></div></details>`);
|
|
19859
19914
|
}
|
|
19860
|
-
|
|
19915
|
+
// Never a link to this page's own acts: Retry sits on the status card and below.
|
|
19916
|
+
const href = diagnosis.action === "retry-task" ? null : taskRecoveryHref(task.id, diagnosis);
|
|
19861
19917
|
return href === null ? "" : `<a class="button-link dispatch-action-link" href="${href}">${escape(dispatchActionLabel(diagnosis))}</a>`;
|
|
19862
19918
|
})();
|
|
19863
19919
|
const positive = diagnosis.code === "running" || diagnosis.code === "ready" || diagnosis.code === "planning-ready" || diagnosis.code === "scouting-ready";
|
|
@@ -20555,8 +20611,10 @@ function taskBodyParts(data) {
|
|
|
20555
20611
|
// hold (the hold governs the NEXT start), and requeue refuses while a
|
|
20556
20612
|
// claim is live — so the words say exactly when each becomes real.
|
|
20557
20613
|
const canPlan = data.plan === null && !approval.approved && !data.claimed && task.state === "queued" && (data.coordinator === null || data.coordinator === undefined);
|
|
20558
|
-
|
|
20559
|
-
|
|
20614
|
+
// A failed task's status card carries Retry itself (the same requeue); here it would be a second ink act.
|
|
20615
|
+
const cardRetries = task.state === "failed" && data.assignment != null && data.csrf !== "";
|
|
20616
|
+
const primaryAct = stopControlsActive || cardRetries ? null : stalled && !data.claimed
|
|
20617
|
+
? { html: act("requeue", "Retry, keeping the branch and workspace"), why: "Resolves the incidents, clears the failed attempts and queues the task again. The branch and workspace are kept.", whyClass: "retry" }
|
|
20560
20618
|
: canPlan
|
|
20561
20619
|
? { html: act("plan", "plan first"), why: "plan first sends an agent to read the repository, ask you questions, and propose a scope — nothing builds until you approve it", whyClass: "plan" }
|
|
20562
20620
|
: data.plan === "requested"
|
|
@@ -20567,7 +20625,7 @@ function taskBodyParts(data) {
|
|
|
20567
20625
|
const holdAct = `<form method="post" action="${taskHref(task.id)}/hold" class="inline act-hold">` +
|
|
20568
20626
|
`<input type="hidden" name="csrf" value="${escape(data.csrf)}">` +
|
|
20569
20627
|
`<input type="text" name="reason" class="inline" placeholder="reason (optional)" aria-label="hold reason">` +
|
|
20570
|
-
`<button type="submit">
|
|
20628
|
+
`<button type="submit">Hold the next attempt</button></form>`;
|
|
20571
20629
|
const canHold = task.state === "queued" || task.state === "running" || task.state === "failed";
|
|
20572
20630
|
const actsBar = [
|
|
20573
20631
|
`<span id="task-actions"></span><div class="acts-bar">`,
|
|
@@ -20584,7 +20642,7 @@ function taskBodyParts(data) {
|
|
|
20584
20642
|
`</div>`,
|
|
20585
20643
|
primaryAct === null ? "" : `<p class="meta acts-why acts-why-${primaryAct.whyClass}">${primaryAct.why}</p>`,
|
|
20586
20644
|
data.claimed && !stopControlsActive
|
|
20587
|
-
? `<p class="meta acts-why">a worker is building this right now — <em>
|
|
20645
|
+
? `<p class="meta acts-why">a worker is building this right now — <em>Hold the next attempt</em> stops the one after it; cancel waits for the current build to finish${stalled ? "; retry becomes available after this attempt finishes" : ""}</p>`
|
|
20588
20646
|
: "",
|
|
20589
20647
|
].join("\n");
|
|
20590
20648
|
// Cancel gets the same ceremony as an irreversible answer: armed behind
|
|
@@ -20777,7 +20835,7 @@ function taskBodyParts(data) {
|
|
|
20777
20835
|
: run.outcome === null ? "Attempt never finished"
|
|
20778
20836
|
: `${noun[0].toUpperCase()}${noun.slice(1)} ${run.outcome}`;
|
|
20779
20837
|
thread.push({ key: `run-${run.id}`, at: run.finishedAt ?? run.startedAt, kind: "result", who: "agent", author: run.runner,
|
|
20780
|
-
title: `${title}${revision}`, text: run.handoff === null ? (run.reason === null || run.outcome === "built" ? null : reasonWords(run.reason)) : oneLineOf(run.handoff, 600),
|
|
20838
|
+
title: `${title}${revision}`, text: run.handoff === null ? (run.outcome === "failed" ? failedAttemptSentence(run.reason) : run.reason === null || run.outcome === "built" ? null : reasonWords(run.reason)) : oneLineOf(run.handoff, 600),
|
|
20781
20839
|
link: href, html: "",
|
|
20782
20840
|
more: previousResult !== null && run.id === newestBuilt?.id && run.taskId === task.id ? { summary: "Result details", html: previousResult.html } : null });
|
|
20783
20841
|
}
|
|
@@ -20872,6 +20930,22 @@ function taskBodyParts(data) {
|
|
|
20872
20930
|
// needs a person, then facts, then folds; the mechanics under Manage.
|
|
20873
20931
|
const MANAGE = new Set(["steering", "waits-for", "holds"]);
|
|
20874
20932
|
const finished = task.state === "done" || task.state === "cancelled";
|
|
20933
|
+
// Failed: the card says what went wrong in one line, and its one act is Retry itself (the same requeue), never a link to
|
|
20934
|
+
// this page; someone who can't retry here (a viewer, a live claim) gets no act at all.
|
|
20935
|
+
const statusCard = data.assignment != null && assignmentOptions !== null ? assignmentCardOf(data.assignment, assignmentOptions) : null;
|
|
20936
|
+
const failedCard = statusCard?.status.headline === "Failed";
|
|
20937
|
+
// A link to this very page, with no section to open, goes nowhere: a card never offers one.
|
|
20938
|
+
const bareSelfLink = (href) => {
|
|
20939
|
+
if (href.includes("#"))
|
|
20940
|
+
return false;
|
|
20941
|
+
const [path, query = ""] = href.split("?");
|
|
20942
|
+
const version = new URLSearchParams(query).get("version");
|
|
20943
|
+
return [data.assignment?.rootId, data.rootId, task.id].some(id => id != null && path === `/t/${encodeURIComponent(id)}`) && (version === null || version === task.id);
|
|
20944
|
+
};
|
|
20945
|
+
// Build again, in place, names its form after the card's own act.
|
|
20946
|
+
const rebuild = data.csrf !== "" && data.assignment?.primaryAction?.code === "retry-task" && data.assignment.need != null && "key" in data.assignment.need && data.assignment.need.key === "rebuild"
|
|
20947
|
+
? { action: `${taskHref(data.assignment.rootId)}/requeue` } : null;
|
|
20948
|
+
const retry = failedCard && task.state === "failed" && !data.claimed && !stopControlsActive && data.csrf !== "" ? { action: `${taskHref(task.id)}/requeue` } : null;
|
|
20875
20949
|
const view = {
|
|
20876
20950
|
kind: "task",
|
|
20877
20951
|
id: task.id,
|
|
@@ -20883,13 +20957,15 @@ function taskBodyParts(data) {
|
|
|
20883
20957
|
{ label: "Ask", href: taskChatHref(data.rootId ?? task.id), active: false },
|
|
20884
20958
|
],
|
|
20885
20959
|
version: data.versionLabel == null ? null : { label: data.versionLabel, current: { label: "Current work", href: taskHref(data.rootId ?? task.id) } },
|
|
20886
|
-
status:
|
|
20960
|
+
status: statusCard === null ? null : failedCard ? { ...statusCard, action: null, status: { ...statusCard.status, sentence: data.failure?.line ?? statusCard.status.sentence } }
|
|
20961
|
+
: statusCard.action !== null && rebuild === null && bareSelfLink(statusCard.action.href) ? { ...statusCard, action: null } : statusCard,
|
|
20887
20962
|
statusHtml,
|
|
20963
|
+
failure: failedCard ? data.failure ?? null : null,
|
|
20964
|
+
retry,
|
|
20888
20965
|
approval: approvalHtml,
|
|
20889
20966
|
confirmStopped: data.csrf !== "" && data.assignment?.primaryAction?.code === "confirm-stopped" && data.assignment.primaryAction.target.runId !== null
|
|
20890
20967
|
? { action: `${taskHref(data.assignment.rootId)}/confirm-stopped`, run: data.assignment.primaryAction.target.runId, checked: needsCheck(data.assignment) } : null,
|
|
20891
|
-
rebuild
|
|
20892
|
-
? { action: `${taskHref(data.assignment.rootId)}/requeue` } : null,
|
|
20968
|
+
rebuild,
|
|
20893
20969
|
// The plan, progress and plan changes are thread entries now; the rest still needs a person here.
|
|
20894
20970
|
lead: [
|
|
20895
20971
|
{ key: "history", html: data.history ?? "" }, { key: "control", html: controlHtml }, { key: "problem", html: problemHtml },
|
|
@@ -21141,7 +21217,9 @@ export function orderChangedFiles(files, proofCitesPaths) {
|
|
|
21141
21217
|
? churn(b) - churn(a)
|
|
21142
21218
|
: a.path.localeCompare(b.path));
|
|
21143
21219
|
}
|
|
21144
|
-
|
|
21220
|
+
/** The one result page's address: the task and its run, never a project path
|
|
21221
|
+
* (the page finds the result's project itself and keeps the person's own). */
|
|
21222
|
+
const reviewHref = (taskId, runId = null) => `/review?result=${encodeURIComponent(taskId)}${runId === null ? "" : `&run=${runId}`}`;
|
|
21145
21223
|
/** The receipt's own proof-state word, so the cockpit and the task page
|
|
21146
21224
|
* never disagree on the state's name or its precedence: the stored
|
|
21147
21225
|
* verdict decides, whatever the run's outcome — a no-change run with a
|
|
@@ -21266,7 +21344,7 @@ function reviewCockpitPage(chrome, data) {
|
|
|
21266
21344
|
const why = status === null
|
|
21267
21345
|
? reasons.length === 0 ? "" : `<span class="cockpit-why">${escape(reasons[0])}${reasons.length > 1 ? ` · +${reasons.length - 1} more` : ""}</span>`
|
|
21268
21346
|
: `<span class="cockpit-why">${statusLineHtml(status)}</span>`;
|
|
21269
|
-
return (`<li data-review-priority="${row.priority.band}"><a class="cockpit-row${current ? " current" : ""}" href="${escape(reviewHref(row.taskId, row.runId
|
|
21347
|
+
return (`<li data-review-priority="${row.priority.band}"><a class="cockpit-row${current ? " current" : ""}" href="${escape(reviewHref(row.taskId, row.runId))}"${current ? ` aria-current="page"` : ""}>` +
|
|
21270
21348
|
`<span class="cockpit-row-head"><strong>${escape(row.title)}</strong></span>` +
|
|
21271
21349
|
`<span class="cockpit-row-meta">${whenTime(row.completedAt)}${row.runId === null ? " · no build" : row.outcome === "no-change" ? " · no change" : ""}${row.prNumber === null ? "" : ` · PR #${row.prNumber}`}</span>` +
|
|
21272
21350
|
(row.historyProblem ? `<span class="cockpit-why" data-history-problem>History unavailable</span>` : why) +
|
|
@@ -21295,7 +21373,7 @@ function reviewCockpitPage(chrome, data) {
|
|
|
21295
21373
|
const status = row.assignment == null ? null : assignmentPresentationOf(row.assignment).status;
|
|
21296
21374
|
const reasons = row.priority.reasons;
|
|
21297
21375
|
return {
|
|
21298
|
-
title: row.title, href: reviewHref(row.taskId, row.runId
|
|
21376
|
+
title: row.title, href: reviewHref(row.taskId, row.runId), at: row.completedAt,
|
|
21299
21377
|
status: status === null ? null : { label: status.label, tone: status.tone },
|
|
21300
21378
|
notes: [...(row.historyProblem ? ["History unavailable"] : status === null && reasons.length > 0 ? [reasons[0]] : []), ...(status !== null && row.ciFailing ? ["CI is failing"] : [])],
|
|
21301
21379
|
current: selected !== null && selected.taskId === row.taskId,
|
|
@@ -21377,6 +21455,15 @@ function reviewCockpitDetailParts(view, csrf, noted, canRetryReview, tab, user)
|
|
|
21377
21455
|
problem: view.detail === null ? view.historyProblem : null,
|
|
21378
21456
|
next: reviewNextActionOf(view, csrf), complete: null, decision: null, checks: null, intent: intentView, noRun: null, panel: null, contest: "",
|
|
21379
21457
|
notes: view.notes.map(one => ({ author: one.author, at: one.createdAt, note: one.note })),
|
|
21458
|
+
acts: { primary: null, secondary: null, line: null }, runChecks: null, mismatch: null,
|
|
21459
|
+
// The raw run record lives under Details now (2026-10-02): /r/<id> for this result redirects here.
|
|
21460
|
+
record: run === null ? null : { build: run.id, href: `/r/${run.id}?record=1`, facts: [
|
|
21461
|
+
{ label: "Agent", value: [run.provider, run.model].filter(Boolean).join(" · ") },
|
|
21462
|
+
{ label: "Worker", value: run.runner },
|
|
21463
|
+
...(run.branch === null ? [] : [{ label: "Branch", value: run.branch }]),
|
|
21464
|
+
...(run.finishedAt === null ? [] : [{ label: "Finished", value: when(run.finishedAt) }]),
|
|
21465
|
+
...(run.ranMinutes === null ? [] : [{ label: "Ran", value: `${run.ranMinutes} min` }]),
|
|
21466
|
+
] },
|
|
21380
21467
|
};
|
|
21381
21468
|
if (run === null || view.detail === null) {
|
|
21382
21469
|
parts.push(`<section class="card cockpit-section" id="verification" data-cockpit-section="proof"><h3>verification</h3>` +
|
|
@@ -21419,7 +21506,8 @@ function reviewCockpitDetailParts(view, csrf, noted, canRetryReview, tab, user)
|
|
|
21419
21506
|
// The one decision, after the evidence: Accept only when every requirement is met and the checks passed.
|
|
21420
21507
|
const acceptsHere = complete !== null || panel.panel.need?.accept != null;
|
|
21421
21508
|
const matrix = proof === null || proof.proofProblem !== null ? [] : proof.matrix;
|
|
21422
|
-
const
|
|
21509
|
+
const blocked = cantAcceptYetOf(proof?.verdict ?? null, proof?.reasons ?? [], accepted);
|
|
21510
|
+
const words = !acceptsHere ? null : { sentence: RESULT_DECISION_SENTENCE, ...acceptWordsOf({
|
|
21423
21511
|
checks: checks === undefined ? null : checks.running != null ? "running" : checks.level === "off" && checks.status !== "passed" ? "off" : checks.status,
|
|
21424
21512
|
unmet: matrix.filter(row => row.state !== "pass" && row.state !== "manual-review").length,
|
|
21425
21513
|
yours: complete === null || accepted ? 0 : panel.panel.youCheck?.lines.length ?? 0,
|
|
@@ -21427,6 +21515,60 @@ function reviewCockpitDetailParts(view, csrf, noted, canRetryReview, tab, user)
|
|
|
21427
21515
|
publishing: view.detail?.publishing ?? "other",
|
|
21428
21516
|
proof: proof !== null && proof.proof !== null && proof.proofProblem === null,
|
|
21429
21517
|
}) };
|
|
21518
|
+
// Refuted: plain Accept stays as allowed, in outline, and the one line before the acts (acts.line) says why.
|
|
21519
|
+
const decision = words === null || blocked === null ? words : { ...words, label: "Accept", ready: false };
|
|
21520
|
+
// Run checks: no check ran on this result (or its saved one can't be read), the project has one, and an approver may run it.
|
|
21521
|
+
const followUps = view.detail.followUps ?? null;
|
|
21522
|
+
const checksRunning = checks?.running != null || (followUps?.checks.some(one => one.state === "waiting" || one.state === "running") ?? false);
|
|
21523
|
+
const runChecks = canRetryReview && csrf !== "" && followUps !== null && (followUps.full || followUps.quick) && !checksRunning &&
|
|
21524
|
+
(checks === undefined || checks.status === "not-run" || checks.status === "unavailable")
|
|
21525
|
+
// Back on this result's Checks tab, where #follow-ups shows the run it started.
|
|
21526
|
+
? { action: `/r/${run.id}/checks`, level: followUps.full ? "full" : "quick", returnTo: `${here}&run=${run.id}&tab=checks` } : null;
|
|
21527
|
+
const need = panel.panel.need;
|
|
21528
|
+
const nextKind = selected.next?.kind;
|
|
21529
|
+
const acts = resultActsOf({
|
|
21530
|
+
accept: decision === null ? null : { ready: decision.ready },
|
|
21531
|
+
runChecks: runChecks !== null,
|
|
21532
|
+
checksRunning,
|
|
21533
|
+
blocked,
|
|
21534
|
+
canRequest: panel.panel.canRequest && panel.panel.request !== null,
|
|
21535
|
+
need: need === null || need.accept != null ? null : need.rebuild != null ? "rebuild" : need.confirm !== null ? "confirm-stopped" : null,
|
|
21536
|
+
next: nextKind === "revise" || nextKind === "draft-repair" ? nextKind : null,
|
|
21537
|
+
});
|
|
21538
|
+
// A refuted result's card lists every recorded disagreement, each tied to its lines; when the report itself doesn't
|
|
21539
|
+
// match the changes (not a failed check), that is the headline.
|
|
21540
|
+
const mismatch = proof === null || accepted || proof.verdict !== "refuted" ? null : (() => {
|
|
21541
|
+
const patch = view.detail?.terminal?.patch ?? null;
|
|
21542
|
+
const files = patch === null || "problem" in patch ? [] : parseReviewDiff(patch.text).files;
|
|
21543
|
+
// Each file's first change: the lines it added (else every line it shows), and where that change starts (its link target).
|
|
21544
|
+
const hunkStart = new Map();
|
|
21545
|
+
const changes = new Map(files.map(file => {
|
|
21546
|
+
const shown = file.hunks[0]?.lines.filter(line => line.newLine !== null && line.kind !== "meta") ?? [];
|
|
21547
|
+
if (shown[0]?.newLine != null)
|
|
21548
|
+
hunkStart.set(file.path, shown[0].newLine);
|
|
21549
|
+
const added = shown.filter(line => line.kind === "addition");
|
|
21550
|
+
const lines = (added.length > 0 ? added : shown).map(line => line.newLine);
|
|
21551
|
+
return [file.path, lines.length === 0 ? null : { from: Math.min(...lines), to: Math.max(...lines) }];
|
|
21552
|
+
}));
|
|
21553
|
+
const changesHref = `${here}&run=${run.id}&tab=changes`;
|
|
21554
|
+
const found = reportMismatchesOf(proof.reasons, proof.proofProblem === null ? proof.matrix : [], changes);
|
|
21555
|
+
// The saved report itself can't be read: that is the evidence problem, said once.
|
|
21556
|
+
if (proof.proofProblem !== null)
|
|
21557
|
+
found.push({ text: `The saved report can't be read: ${proof.proofProblem}`, path: null, lines: null, inChanges: null, note: null, reason: proof.proofProblem });
|
|
21558
|
+
if (found.length === 0)
|
|
21559
|
+
return null;
|
|
21560
|
+
return {
|
|
21561
|
+
headline: evidenceProblemOf(proof.verdict, proof.reasons) === "mismatched" ? MISMATCH_HEADLINE : null,
|
|
21562
|
+
rows: found.map(one => ({
|
|
21563
|
+
text: one.text, path: one.path, absent: one.inChanges === false,
|
|
21564
|
+
lines: one.lines === null ? null : one.lines.from === one.lines.to ? `line ${one.lines.from}` : `lines ${one.lines.from}–${one.lines.to}`,
|
|
21565
|
+
href: one.path !== null && one.inChanges === true ? `${changesHref}#${diffFileAnchor(one.path)}${hunkStart.has(one.path) ? `-L${hunkStart.get(one.path)}` : ""}`
|
|
21566
|
+
: one.note !== null && (proof.proof?.caveats.length ?? 0) >= one.note ? `${here}&run=${run.id}#report-note-${one.note}` : null,
|
|
21567
|
+
noteLabel: one.path === null && one.note !== null ? `The report, note ${one.note}` : null,
|
|
21568
|
+
})),
|
|
21569
|
+
said: [...new Set([...proof.reasons, ...found.map(one => one.reason)].flatMap(reason => [reason, plainReasonWords(reason)]))],
|
|
21570
|
+
};
|
|
21571
|
+
})();
|
|
21430
21572
|
if (view.notes.length > 0) {
|
|
21431
21573
|
parts.push(`<section class="card cockpit-section" data-cockpit-section="notes"><h3>operator notes</h3>` +
|
|
21432
21574
|
view.notes.map(one => `<p class="row"><span class="meta">${escape(one.author)} · ${whenTime(one.createdAt)}</span> ${escape(one.note)}</p>`).join("\n") +
|
|
@@ -21435,7 +21577,7 @@ function reviewCockpitDetailParts(view, csrf, noted, canRetryReview, tab, user)
|
|
|
21435
21577
|
return {
|
|
21436
21578
|
html: `<section class="cockpit-detail">${parts.join("\n")}</section>`,
|
|
21437
21579
|
selected: {
|
|
21438
|
-
...selected, complete, decision, panel: panel.panel, contest,
|
|
21580
|
+
...selected, complete, decision, panel: panel.panel, contest, acts, runChecks, mismatch,
|
|
21439
21581
|
checks: checks === undefined ? null : { detail: checks.detail, problem: checks.status === "failed" || checks.status === "unavailable", logHref: checks.logArtifactId === null ? null : `/r/${run.id}/evidence/${checks.logArtifactId}` },
|
|
21440
21582
|
},
|
|
21441
21583
|
};
|
|
@@ -21530,38 +21672,9 @@ function homePhaseWords(run) {
|
|
|
21530
21672
|
function phaseWords(phase) {
|
|
21531
21673
|
return PHASE_WORDS[phase] ?? "the agent is working";
|
|
21532
21674
|
}
|
|
21533
|
-
|
|
21534
|
-
agent: "the agent failed",
|
|
21535
|
-
"agent-reported": "the agent reported it could not finish",
|
|
21536
|
-
"no-op": "nothing changed when something should have",
|
|
21537
|
-
"no-handoff": "the agent stopped before handing off; its work was kept and it is being resumed",
|
|
21538
|
-
"moved-head": "the branch moved underneath the build",
|
|
21539
|
-
"moved-branch": "the branch moved underneath the build",
|
|
21540
|
-
timeout: "ran out of time",
|
|
21541
|
-
git: "a git step failed",
|
|
21542
|
-
"malformed-decision": "the agent's question was malformed",
|
|
21543
|
-
"malformed-plan": "the plan was malformed",
|
|
21544
|
-
fenced: "another worker took the task over",
|
|
21545
|
-
unapproved: "the scope was not approved",
|
|
21546
|
-
"scope-changed": "the scope changed after approval",
|
|
21547
|
-
capability: "a requirement was missing",
|
|
21548
|
-
setup: "the workspace preparation step failed",
|
|
21549
|
-
"provider-init": "the agent could not start",
|
|
21550
|
-
"commit-failure": "the commit failed",
|
|
21551
|
-
"protected-branch": "refused to touch a protected branch",
|
|
21552
|
-
"wrong-branch": "the checkout was on the wrong branch",
|
|
21553
|
-
"not-leased": "the lease was not valid",
|
|
21554
|
-
"no-claim": "the lease was not valid",
|
|
21555
|
-
"not-yours": "the lease was not valid",
|
|
21556
|
-
"no-run-record": "the run record was missing",
|
|
21557
|
-
"missing-mailbox": "the resume mailbox could not be read",
|
|
21558
|
-
"unreadable-mailbox": "the resume mailbox could not be read",
|
|
21559
|
-
"revision-brief": "the revision brief could not be read",
|
|
21560
|
-
"repaired-park": "resumed from a parked question",
|
|
21561
|
-
stopped: "stopped by the operator",
|
|
21562
|
-
};
|
|
21675
|
+
/** A recorded reason in one plain line; machine output (a stack trace, a path, "Error:" text) waits on the run record. */
|
|
21563
21676
|
function reasonWords(reason) {
|
|
21564
|
-
return
|
|
21677
|
+
return isInternalErrorReason(reason) ? "stopped with an internal error" : oneLineOf(runReasonWords(reason.trim().split(/\r?\n/, 1)[0] ?? ""), 140);
|
|
21565
21678
|
}
|
|
21566
21679
|
const INCIDENT_WORDS = {
|
|
21567
21680
|
"malformed-decision": "the agent's question was malformed",
|
|
@@ -22011,18 +22124,23 @@ function reviewDiffHtml(patch, stat, runId, commentable, anchors = new Map()) {
|
|
|
22011
22124
|
: counts.additions === null || counts.deletions === null
|
|
22012
22125
|
? `<span class="diff-file-counts">binary</span>`
|
|
22013
22126
|
: `<span class="diff-file-counts"><b>+${counts.additions}</b><i>−${counts.deletions}</i></span>`;
|
|
22014
|
-
const
|
|
22015
|
-
|
|
22016
|
-
|
|
22017
|
-
|
|
22018
|
-
|
|
22019
|
-
|
|
22020
|
-
|
|
22021
|
-
|
|
22022
|
-
|
|
22023
|
-
|
|
22024
|
-
|
|
22025
|
-
|
|
22127
|
+
const fileAnchor = anchors.get(one.path);
|
|
22128
|
+
// Each change is a link target of its own (#<file anchor>-L<first new line>): a result names the exact lines.
|
|
22129
|
+
const hunks = one.hunks.map(hunk => {
|
|
22130
|
+
const first = hunk.lines.find(line => line.newLine !== null && line.kind !== "meta")?.newLine ?? null;
|
|
22131
|
+
return `<section class="diff-hunk"${fileAnchor === undefined || first === null ? "" : ` id="${fileAnchor}-L${first}"`}><div class="diff-hunk-head">${escape(hunk.header)}</div>` +
|
|
22132
|
+
`<div class="diff-lines">${hunk.lines.map(line => {
|
|
22133
|
+
const lineNumber = line.newLine ?? line.oldLine;
|
|
22134
|
+
const side = line.newLine === null && line.oldLine !== null ? "old" : "new";
|
|
22135
|
+
const annotate = !commentable || lineNumber === null || line.kind === "meta"
|
|
22136
|
+
? `<span class="diff-annotate-space"></span>`
|
|
22137
|
+
: `<button type="button" class="diff-annotate pick-line" data-path="${escape(one.path)}" data-line="${lineNumber}" data-side="${side}" aria-label="Annotate ${escape(one.path)}, ${side} line ${lineNumber}" title="Annotate this line">${annotateIcon}</button>`;
|
|
22138
|
+
const marker = line.kind === "addition" ? "+" : line.kind === "deletion" ? "−" : line.kind === "context" ? " " : "·";
|
|
22139
|
+
return (`<div class="diff-line diff-${line.kind}">${annotate}` +
|
|
22140
|
+
`<span class="diff-gutter">${line.oldLine ?? ""}</span><span class="diff-gutter">${line.newLine ?? ""}</span>` +
|
|
22141
|
+
`<code><b aria-hidden="true">${marker}</b>${escape(line.text)}</code></div>`);
|
|
22142
|
+
}).join("")}</div></section>`;
|
|
22143
|
+
}).join("");
|
|
22026
22144
|
const anchor = anchors.get(one.path);
|
|
22027
22145
|
return (`<details class="diff-file"${fileIndex === 0 ? " open" : ""}${anchor === undefined ? "" : ` id="${anchor}"`}>` +
|
|
22028
22146
|
`<summary><span class="diff-file-name">${escape(one.path)}</span>${countWords}</summary>` +
|
|
@@ -22050,7 +22168,9 @@ function checkLogHtml(log, runId, attributes) {
|
|
|
22050
22168
|
if (log.problem !== null) {
|
|
22051
22169
|
return `<p class="problem" data-check-log="damaged"${attributes}>The check log no longer verifies (${escape(log.problem)}). Its output is not shown, and there is nothing to download.</p>`;
|
|
22052
22170
|
}
|
|
22053
|
-
return (`<details data-check-log="${log.truncated ? "shortened" : "ok"}"${attributes}><summary>Check output${log.truncated ? ` (shortened — ${log.bytesStored} of ${log.bytesOriginal} bytes stored)` : ""}</summary><pre class="mono" style="overflow-x:auto;max-height:18rem">${
|
|
22171
|
+
return (`<details data-check-log="${log.truncated ? "shortened" : "ok"}"${attributes}><summary>Check output${log.truncated ? ` (shortened — ${log.bytesStored} of ${log.bytesOriginal} bytes stored)` : ""}</summary><pre class="mono" style="overflow-x:auto;max-height:18rem">${
|
|
22172
|
+
// Each line is a link target (#check-log-L<n>): a failed task names the exact line its check ended on.
|
|
22173
|
+
log.text.split("\n").map((line, index) => `<span id="check-log-L${index + 1}">${escape(line)}</span>`).join("\n")}</pre></details>` +
|
|
22054
22174
|
storedDownloadLink(runId, log.artifactId, "check log", log.truncated));
|
|
22055
22175
|
}
|
|
22056
22176
|
/** The download link for a stored record (repair 2026-09-14): a shortened
|
|
@@ -22344,7 +22464,7 @@ function resultPanelParts(detail, o) {
|
|
|
22344
22464
|
// Needs you: the action that resolves it comes first; Request changes stays beside it, never alone.
|
|
22345
22465
|
// On the result itself, "Review result" would link here: accepting it is what resolves it.
|
|
22346
22466
|
const need = needActionOf(current, taskStatus, o.csrf, o.returnTo, {
|
|
22347
|
-
accepted: proof?.accepted != null, humanReview, run: run.id, action: `${taskHref(detail.taskId)}/accept-proof`,
|
|
22467
|
+
accepted: proof?.accepted != null, humanReview, run: run.id, action: `${taskHref(detail.taskId)}/accept-proof`, refuted: proof?.verdict === "refuted",
|
|
22348
22468
|
// What Store.finalResultReason refuses: a verified, attested, accepted or published result.
|
|
22349
22469
|
rebuildable: proof?.accepted == null && proof?.verdict !== "verified" && proof?.verdict !== "attested" && !(detail.publication !== null && ["pushed", "opened"].includes(detail.publication.state))
|
|
22350
22470
|
});
|
|
@@ -22466,6 +22586,8 @@ function resultPanelParts(detail, o) {
|
|
|
22466
22586
|
...(conclusion !== outcome ? [`<p class="recap">${escape(conclusion)}</p>`] : []),
|
|
22467
22587
|
...(handoff !== null && handoff.changes.length > 0 ? [`<ul class="result-changes">${handoff.changes.map(one => `<li>${escape(one)}</li>`).join("")}</ul>`] : []),
|
|
22468
22588
|
...(handoff !== null && handoff.verification.length > 0 ? [`<p class="meta">Checked by the agent:</p><ul class="result-changes" data-cockpit-source="agent-words">${handoff.verification.map(one => `<li>${escape(one)}</li>`).join("")}</ul>`] : []),
|
|
22589
|
+
// The report's own notes, each a link target (#report-note-<n>) a mismatch can point at.
|
|
22590
|
+
...(proof?.proof == null || proof.proof.caveats.length === 0 ? [] : [`<p class="meta">Its notes:</p><ol class="result-changes" data-report-notes>${proof.proof.caveats.map((one, index) => `<li id="report-note-${index + 1}">${escape(one)}</li>`).join("")}</ol>`]),
|
|
22469
22591
|
];
|
|
22470
22592
|
if (agentAccount.length > 0)
|
|
22471
22593
|
summaryParts.push(`<details class="result-notes" data-result-notes-agent><summary>What the agent reported</summary>${agentAccount.join("")}</details>`);
|
|
@@ -22762,7 +22884,7 @@ export function needActionOf(assignment, status, csrf, returnTo, result) {
|
|
|
22762
22884
|
if (action?.code === "retry-task" && status.need.key === "rebuild" && csrf !== "")
|
|
22763
22885
|
return { label: status.need.action.label, ...rebuild };
|
|
22764
22886
|
if (result !== undefined) {
|
|
22765
|
-
if (status.need.key === "review-result" && !result.accepted && csrf !== "")
|
|
22887
|
+
if ((status.need.key === "review-result" || result.refuted === true) && !result.accepted && csrf !== "")
|
|
22766
22888
|
return { label: result.humanReview ? "Accept result" : "Accept with exception", href: null, confirm: null,
|
|
22767
22889
|
accept: { action: result.action, run: result.run, returnTo, note: result.humanReview ? null : "Why is this safe to accept?" } };
|
|
22768
22890
|
// The task page sends the person here: never back to it. What this page can do leads — Accept
|
|
@@ -22772,8 +22894,9 @@ export function needActionOf(assignment, status, csrf, returnTo, result) {
|
|
|
22772
22894
|
return { label: "Accept result", href: null, confirm: null, accept: { action: result.action, run: result.run, returnTo, note: null } };
|
|
22773
22895
|
if (here && csrf !== "" && result.rebuildable === true)
|
|
22774
22896
|
return { label: NEEDS.rebuild.action.label, ...rebuild };
|
|
22897
|
+
// Nothing here resolves it: no act at all, never a link back to the task (which sends the person here).
|
|
22775
22898
|
if (status.need.key === "review-result" || here)
|
|
22776
|
-
return
|
|
22899
|
+
return null;
|
|
22777
22900
|
}
|
|
22778
22901
|
const confirm = action?.code === "confirm-stopped" && action.target.runId !== null && csrf !== ""
|
|
22779
22902
|
? { action: `${taskHref(assignment.rootId)}/confirm-stopped`, run: action.target.runId, returnTo, checked: needsCheck(assignment) } : null;
|
|
@@ -22802,7 +22925,7 @@ function resultNeedAction(need, o, canRequest) {
|
|
|
22802
22925
|
function conciseOutcomeOf(conclusion) {
|
|
22803
22926
|
const flat = oneLineOf(conclusion, 2_000);
|
|
22804
22927
|
const sentence = /^[\s\S]*?[.!?](?=\s|$)/.exec(flat)?.[0] ?? flat;
|
|
22805
|
-
const bounded = sentence.length <=
|
|
22928
|
+
const bounded = sentence.length <= 140 ? sentence : `${sentence.slice(0, 140).replace(/\s+\S*$/, "")}…`;
|
|
22806
22929
|
return bounded.trim();
|
|
22807
22930
|
}
|
|
22808
22931
|
/** Exactly one primary act for the result (repair 2026-09-14), chosen from
|
|
@@ -22854,7 +22977,7 @@ function runFactsRows(run, taskId, live, route = null) {
|
|
|
22854
22977
|
["quality", qualityModeTitle(run.qualityMode ?? "default")],
|
|
22855
22978
|
["outcome", live ? "running" : (run.outcome ?? "never finished")],
|
|
22856
22979
|
["phase", live && run.phase !== null ? phaseWords(run.phase) : null],
|
|
22857
|
-
["reason", run.reason === null ? null : reasonWords(run.reason)],
|
|
22980
|
+
["reason", run.reason === null ? null : isInternalErrorReason(run.reason) ? "stopped with an internal error (below)" : reasonWords(run.reason)],
|
|
22858
22981
|
["runner", run.runner, true],
|
|
22859
22982
|
["branch", run.branch, true],
|
|
22860
22983
|
["model", run.model, true],
|
|
@@ -22880,7 +23003,11 @@ function runFactsRows(run, taskId, live, route = null) {
|
|
|
22880
23003
|
.filter((fact) => fact[1] !== null && fact[1] !== "")
|
|
22881
23004
|
.map(([label, value, mono]) => `<p class="row"><span class="meta" style="min-width:8.5rem">${escape(label)}</span> ` +
|
|
22882
23005
|
`<span${mono === true ? ` class="mono"` : ""}>${escape(value)}</span></p>`)
|
|
22883
|
-
.join("\n") + elapsed
|
|
23006
|
+
.join("\n") + elapsed +
|
|
23007
|
+
// Machine output (a stack trace, a path, "Error:" text) reads as an internal error everywhere else; its detail is here, as recorded.
|
|
23008
|
+
(run.reason !== null && isInternalErrorReason(run.reason)
|
|
23009
|
+
? `<p class="row"><span class="meta" style="min-width:8.5rem">recorded error</span></p><pre class="mono" id="run-reason-detail" style="white-space:pre-wrap;overflow-wrap:anywhere">${escape(run.reason)}</pre>`
|
|
23010
|
+
: ""));
|
|
22884
23011
|
}
|
|
22885
23012
|
function runPage(chrome, run, taskId, artifacts, terminal = null, notes = [], csrf = "", comments = [], ciRepair = null, liveScript, peekable = false, running = false, editor = null, editorToggle = null, noted = false, heldTurns = null, continueOffer = null, structuredHandoff = null, proofBundle = null, route = null, publicationRow = null, review = null, result = null, sourceDigest = null) {
|
|
22886
23013
|
const rows = runFactsRows(run, taskId, running, route);
|
package/dist/task-status.d.ts
CHANGED
|
@@ -86,10 +86,12 @@ export type TaskStatusFacts = {
|
|
|
86
86
|
report?: boolean;
|
|
87
87
|
checks?: ChecksFact | null;
|
|
88
88
|
pullRequest?: PullRequestFact | null;
|
|
89
|
+
/** `unverified`: the report is refuted, so no requirement counts as met however it was marked. */
|
|
89
90
|
requirements?: {
|
|
90
91
|
met: number;
|
|
91
92
|
total: number;
|
|
92
93
|
yours: number;
|
|
94
|
+
unverified?: boolean;
|
|
93
95
|
} | null;
|
|
94
96
|
evidence?: {
|
|
95
97
|
shortened: number;
|
|
@@ -159,6 +161,8 @@ export declare function requirementsOf(matrix: readonly {
|
|
|
159
161
|
} | undefined;
|
|
160
162
|
review?: unknown;
|
|
161
163
|
}[] | null | undefined): NonNullable<TaskStatusFacts["requirements"]> | null;
|
|
164
|
+
/** A refuted report verifies none of its requirements, whatever it marked met (a person's acceptance doesn't change that). */
|
|
165
|
+
export declare function unverifiedWhenRefuted(requirements: NonNullable<TaskStatusFacts["requirements"]> | null, verdict: string | null | undefined): NonNullable<TaskStatusFacts["requirements"]> | null;
|
|
162
166
|
/** Saved evidence health from the receipt's artifact list and its caveats. */
|
|
163
167
|
export declare function evidenceOf(receipt: AssignmentSnapshot["receipt"], problems?: readonly string[]): NonNullable<TaskStatusFacts["evidence"]> | null;
|
|
164
168
|
/** The status facts of one assignment, as every surface reads them. */
|