toolroll 0.9.13 → 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 +8 -0
- package/dist/assignment-ui.js +2 -1
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +19 -19
- package/dist/browser-crew.js +2 -1
- package/dist/browser-workspace.d.ts +28 -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 +2 -0
- package/dist/result-acts.js +2 -0
- package/dist/serve.js +140 -60
- package/dist/task-status.d.ts +4 -0
- package/dist/task-status.js +8 -2
- package/dist/telegram-progress.js +2 -2
- package/dist/work-index.js +6 -3
- package/dist/workspace-ui.d.ts +44 -0
- package/dist/workspace-ui.js +84 -2
- package/package.json +1 -1
package/dist/browser-crew.js
CHANGED
|
@@ -25,7 +25,8 @@ export function browserWorkActionHref(item) {
|
|
|
25
25
|
return `/r/${runId}`;
|
|
26
26
|
const anchor = action.code === 'approve-scope' ? '#approve'
|
|
27
27
|
: action.code === 'inspect-stop' || action.code === 'resume-run' ? '#task-control'
|
|
28
|
-
|
|
28
|
+
// Retry (and Build again) is the task's status card itself, at the top: no anchor to its options.
|
|
29
|
+
: action.code === 'unhold' ? '#task-actions'
|
|
29
30
|
: action.code === 'write-scope' || action.code === 'select-agent' ? '#scope'
|
|
30
31
|
: action.code === 'inspect-hold' ? '#holds'
|
|
31
32
|
: action.code === 'start-worker' || action.code === 'repair-dependency' ? '#run-status' : '';
|
|
@@ -367,6 +367,18 @@ export type BrowserTaskView = {
|
|
|
367
367
|
rebuild?: {
|
|
368
368
|
action: string;
|
|
369
369
|
} | null;
|
|
370
|
+
/** A failed task's recorded reason in one line (the run's reason, the failing check's last error line, or that none was
|
|
371
|
+
* recorded) and where its log lines are; and Retry itself, with an optional note. */
|
|
372
|
+
failure?: {
|
|
373
|
+
line: string;
|
|
374
|
+
link: {
|
|
375
|
+
label: string;
|
|
376
|
+
href: string;
|
|
377
|
+
} | null;
|
|
378
|
+
} | null;
|
|
379
|
+
retry?: {
|
|
380
|
+
action: string;
|
|
381
|
+
} | null;
|
|
370
382
|
/** Server cards that may need a person now (stop/resume, scope prompt, plan, live attempt). */
|
|
371
383
|
lead: {
|
|
372
384
|
key: string;
|
|
@@ -587,6 +599,22 @@ export type BrowserResultView = {
|
|
|
587
599
|
problem: boolean;
|
|
588
600
|
logHref: string | null;
|
|
589
601
|
} | null;
|
|
602
|
+
/** A refuted result's recorded disagreements: the headline when the report doesn't match the changes (null when a
|
|
603
|
+
* failed check is the blocker, so the status keeps its own), each disagreement in plain words with the changed lines
|
|
604
|
+
* it concerns (`absent`: a file the changes don't have), and the recorded words they already say, so no caveat
|
|
605
|
+
* repeats them. Null otherwise. */
|
|
606
|
+
mismatch: {
|
|
607
|
+
headline: string | null;
|
|
608
|
+
rows: {
|
|
609
|
+
text: string;
|
|
610
|
+
path: string | null;
|
|
611
|
+
lines: string | null;
|
|
612
|
+
href: string | null;
|
|
613
|
+
absent: boolean;
|
|
614
|
+
noteLabel: string | null;
|
|
615
|
+
}[];
|
|
616
|
+
said: string[];
|
|
617
|
+
} | null;
|
|
590
618
|
/** The one ink act that resolves the result, the one outline act beside it, and why it can't be accepted yet (result-acts.ts). */
|
|
591
619
|
acts: ResultActs;
|
|
592
620
|
/** Run checks on this result's commit: the project's check, when it didn't run. */
|
package/dist/dispatch.js
CHANGED
|
@@ -13,6 +13,7 @@ import { plannerSourceProblemOf } from "./planner-source.js";
|
|
|
13
13
|
import { authPauseOf, authWaitOf, providerName, signInReason } from "./provider-auth.js";
|
|
14
14
|
import { REVIEW_TOKENS, resultStatusOf, reviewFactsOf } from "./workspace-ui.js";
|
|
15
15
|
import { replacedWords } from "./task-status.js";
|
|
16
|
+
import { failedAttemptSentence, latestFinishedAttempt } from "./needs-you.js";
|
|
16
17
|
import { buildReviewOf } from "./review-switch.js";
|
|
17
18
|
import { BUILT_IN, parseCapabilityKey } from "./store.js";
|
|
18
19
|
export const DEFAULT_MAX_OPEN_DECISIONS = 5;
|
|
@@ -218,8 +219,12 @@ export function diagnoseTaskDispatch(store, taskId, now) {
|
|
|
218
219
|
return successor === null ? answer("cancelled", "terminal", "Cancelled", "Nothing else will run for this task.")
|
|
219
220
|
: answer("cancelled", "terminal", replacedWords(successor), `${replacedWords(successor)}. Nothing else will run for this task.`);
|
|
220
221
|
}
|
|
221
|
-
if (task.state === "failed")
|
|
222
|
-
|
|
222
|
+
if (task.state === "failed") {
|
|
223
|
+
// The family's latest finished attempt's own recorded reason, or that none was recorded: never "review its incident".
|
|
224
|
+
const family = store.taskFamilyOf(taskId, ref.repo === null ? [] : [ref.repo], ref.repo === null);
|
|
225
|
+
const runs = family === null || family.problem !== null ? store.runsFor(ref.id) : family.versions.flatMap(version => store.runsFor(version.refId));
|
|
226
|
+
return answer("failed", "terminal", "Needs a retry", failedAttemptSentence(latestFinishedAttempt(runs)?.reason), { action: "retry-task" });
|
|
227
|
+
}
|
|
223
228
|
if (store.hasLiveClaim(ref.id, now))
|
|
224
229
|
return answer("running", "running", "Running now", "A worker owns the current live claim.");
|
|
225
230
|
if (task.state === "running")
|
package/dist/needs-you.d.ts
CHANGED
|
@@ -59,4 +59,28 @@ export declare function processNeedOf(fact: {
|
|
|
59
59
|
} | null;
|
|
60
60
|
/** Dispatch and work-index codes that wait on a person, as their need. */
|
|
61
61
|
export declare const CODE_NEED: Readonly<Record<string, NeedKey>>;
|
|
62
|
+
/** A stopped run's recorded reason code, in words (the run record, the thread, a failed task's status). */
|
|
63
|
+
export declare const RUN_REASON_WORDS: Readonly<Record<string, string>>;
|
|
64
|
+
/** A run's recorded reason in words. A recorded code is never shown as it is
|
|
65
|
+
* stored: one with no words of its own reads as an unexpected stop; a reason
|
|
66
|
+
* already written as words (it has a space) is said as written. */
|
|
67
|
+
export declare function runReasonWords(reason: string): string;
|
|
68
|
+
/** The latest finished build attempt among a task's runs (any order), whatever its outcome: planning and reviewing
|
|
69
|
+
* are not attempts, and an older failure never stands in for a newer attempt. */
|
|
70
|
+
export declare function latestFinishedAttempt<T extends {
|
|
71
|
+
id: number;
|
|
72
|
+
role: string;
|
|
73
|
+
finishedAt: string | null;
|
|
74
|
+
outcome?: string | null;
|
|
75
|
+
}>(runs: readonly T[]): T | null;
|
|
76
|
+
export declare const NO_REASON_RECORDED = "No reason was recorded for this attempt.";
|
|
77
|
+
export declare const INTERNAL_ERROR = "The attempt stopped with an internal error.";
|
|
78
|
+
/** About as long as one plain line reads. */
|
|
79
|
+
export declare const REASON_LINE_LIMIT = 140;
|
|
80
|
+
/** A recorded reason that is machine output, not words: a stack trace, a file path or "Error:" text. Its detail
|
|
81
|
+
* belongs behind a link, never in the line itself. */
|
|
82
|
+
export declare function isInternalErrorReason(reason: string | null | undefined): boolean;
|
|
83
|
+
/** A failed attempt in one plain line: its recorded reason in words (first line only, about 140 characters at most),
|
|
84
|
+
* that it stopped with an internal error when the reason is machine output, or that none was recorded. */
|
|
85
|
+
export declare function failedAttemptSentence(reason: string | null | undefined): string;
|
|
62
86
|
export {};
|
package/dist/needs-you.js
CHANGED
|
@@ -90,3 +90,103 @@ export const CODE_NEED = {
|
|
|
90
90
|
"no-build-record": "review-result", "verification-needed": "review-result", "evidence-mismatch": "review-result",
|
|
91
91
|
"record-incomplete": "review-result", "evidence-damaged": "review-result",
|
|
92
92
|
};
|
|
93
|
+
/** A stopped run's recorded reason code, in words (the run record, the thread, a failed task's status). */
|
|
94
|
+
export const RUN_REASON_WORDS = {
|
|
95
|
+
agent: "the agent failed",
|
|
96
|
+
"agent-reported": "the agent reported it could not finish",
|
|
97
|
+
"no-op": "nothing changed when something should have",
|
|
98
|
+
"no-handoff": "the agent stopped before handing off; its work was kept and it is being resumed",
|
|
99
|
+
"moved-head": "the branch moved underneath the build",
|
|
100
|
+
"moved-branch": "the branch moved underneath the build",
|
|
101
|
+
timeout: "ran out of time",
|
|
102
|
+
git: "a git step failed",
|
|
103
|
+
"malformed-decision": "the agent's question was malformed",
|
|
104
|
+
"malformed-plan": "the plan was malformed",
|
|
105
|
+
fenced: "another worker took the task over",
|
|
106
|
+
unapproved: "the scope was not approved",
|
|
107
|
+
"scope-changed": "the scope changed after approval",
|
|
108
|
+
capability: "a requirement was missing",
|
|
109
|
+
setup: "the workspace preparation step failed",
|
|
110
|
+
"provider-init": "the agent could not start",
|
|
111
|
+
"commit-failure": "the commit failed",
|
|
112
|
+
"protected-branch": "refused to touch a protected branch",
|
|
113
|
+
"wrong-branch": "the checkout was on the wrong branch",
|
|
114
|
+
"not-leased": "the lease was not valid",
|
|
115
|
+
"no-claim": "the lease was not valid",
|
|
116
|
+
"not-yours": "the lease was not valid",
|
|
117
|
+
"no-run-record": "the run record was missing",
|
|
118
|
+
"missing-mailbox": "the resume mailbox could not be read",
|
|
119
|
+
"unreadable-mailbox": "the resume mailbox could not be read",
|
|
120
|
+
"revision-brief": "the revision brief could not be read",
|
|
121
|
+
"repaired-park": "resumed from a parked question",
|
|
122
|
+
stopped: "stopped by the operator",
|
|
123
|
+
acceptance: "the result didn't meet its signed requirements",
|
|
124
|
+
evidence: "its evidence could not be saved",
|
|
125
|
+
interrupted: "the attempt was interrupted",
|
|
126
|
+
orphaned: "the attempt lost its worker",
|
|
127
|
+
"retryable-infra": "a temporary problem on the machine stopped it",
|
|
128
|
+
unknown: "the attempt stopped unexpectedly",
|
|
129
|
+
"spawn-failed": "the agent process could not start",
|
|
130
|
+
"provider-protocol": "the agent's reply could not be read",
|
|
131
|
+
"auth-expired": "the agent's sign-in expired",
|
|
132
|
+
"budget-unenforceable": "its spending limit could not be enforced",
|
|
133
|
+
"stale-source": "the request changed while the plan was being made",
|
|
134
|
+
"stale-approval": "the approval no longer matched the scope",
|
|
135
|
+
"source-invalid": "the plan's source could not be recorded",
|
|
136
|
+
"external-closed": "the linked issue was closed while it was being built",
|
|
137
|
+
"repair-admission": "the repair could not start",
|
|
138
|
+
"reviewer-error": "the review failed",
|
|
139
|
+
"malformed-report": "the scout's report was malformed",
|
|
140
|
+
"attempts-exhausted": "it failed too many times in a row",
|
|
141
|
+
};
|
|
142
|
+
/** A run's recorded reason in words. A recorded code is never shown as it is
|
|
143
|
+
* stored: one with no words of its own reads as an unexpected stop; a reason
|
|
144
|
+
* already written as words (it has a space) is said as written. */
|
|
145
|
+
export function runReasonWords(reason) {
|
|
146
|
+
const known = RUN_REASON_WORDS[reason];
|
|
147
|
+
if (known !== undefined)
|
|
148
|
+
return known;
|
|
149
|
+
if (reason.startsWith("decision:"))
|
|
150
|
+
return "the agent asked a question";
|
|
151
|
+
return /\s/.test(reason.trim()) ? reason.trim() : "the attempt stopped unexpectedly";
|
|
152
|
+
}
|
|
153
|
+
/** The latest finished build attempt among a task's runs (any order), whatever its outcome: planning and reviewing
|
|
154
|
+
* are not attempts, and an older failure never stands in for a newer attempt. */
|
|
155
|
+
export function latestFinishedAttempt(runs) {
|
|
156
|
+
let latest = null;
|
|
157
|
+
// Ended means a finish time or an outcome: a reconcile can mark a run failed without stamping finished_at.
|
|
158
|
+
for (const run of runs)
|
|
159
|
+
if ((run.finishedAt !== null || (run.outcome ?? null) !== null) && run.role !== "planner" && run.role !== "reviewer" && (latest === null || run.id > latest.id))
|
|
160
|
+
latest = run;
|
|
161
|
+
return latest;
|
|
162
|
+
}
|
|
163
|
+
export const NO_REASON_RECORDED = "No reason was recorded for this attempt.";
|
|
164
|
+
export const INTERNAL_ERROR = "The attempt stopped with an internal error.";
|
|
165
|
+
/** About as long as one plain line reads. */
|
|
166
|
+
export const REASON_LINE_LIMIT = 140;
|
|
167
|
+
/** A recorded reason that is machine output, not words: a stack trace, a file path or "Error:" text. Its detail
|
|
168
|
+
* belongs behind a link, never in the line itself. */
|
|
169
|
+
export function isInternalErrorReason(reason) {
|
|
170
|
+
const text = reason?.trim() ?? "";
|
|
171
|
+
if (text === "" || RUN_REASON_WORDS[text] !== undefined)
|
|
172
|
+
return false;
|
|
173
|
+
return /\b[A-Za-z]*(?:Error|Exception):/.test(text) // Error:, TypeError:, SomeException:
|
|
174
|
+
|| /^\s*at\s+\S+/m.test(text) && /\n/.test(text) // a stack frame below the first line
|
|
175
|
+
// An absolute path of two or more segments (/Users/…, ~/x/y): plain sentences may name a project file (src/ledger.ts).
|
|
176
|
+
|| /(?:^|[\s(“"'])~?\/(?:[\w.@-]+\/)+[\w.@-]+/.test(text)
|
|
177
|
+
|| /[A-Za-z]:\\/.test(text) // C:\…
|
|
178
|
+
|| /\.[cm]?[jt]sx?:\d+/.test(text); // ledger.ts:14
|
|
179
|
+
}
|
|
180
|
+
/** A failed attempt in one plain line: its recorded reason in words (first line only, about 140 characters at most),
|
|
181
|
+
* that it stopped with an internal error when the reason is machine output, or that none was recorded. */
|
|
182
|
+
export function failedAttemptSentence(reason) {
|
|
183
|
+
const code = reason?.trim() ?? "";
|
|
184
|
+
if (code === "")
|
|
185
|
+
return NO_REASON_RECORDED;
|
|
186
|
+
if (isInternalErrorReason(code))
|
|
187
|
+
return INTERNAL_ERROR;
|
|
188
|
+
const words = runReasonWords(code.split(/\r?\n/, 1)[0].trim()).replace(/\s+/g, " ").replace(/[.\s]+$/, "");
|
|
189
|
+
const line = words.length <= REASON_LINE_LIMIT ? words
|
|
190
|
+
: `${words.slice(0, REASON_LINE_LIMIT - 1).replace(/\s+\S*$/, "").replace(/[,;:\s]+$/, "")}…`;
|
|
191
|
+
return `${line.charAt(0).toUpperCase()}${line.slice(1)}${line.endsWith("…") ? "" : "."}`;
|
|
192
|
+
}
|
package/dist/result-acts.d.ts
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
*
|
|
15
15
|
* Accepting means what it always meant: the same forms post to the same
|
|
16
16
|
* endpoints. This only decides which one is ink. */
|
|
17
|
+
/** The words over the reason field an Accept of a refuted result requires. */
|
|
18
|
+
export declare const ACCEPT_NEEDS_REASON = "Accepting needs a reason";
|
|
17
19
|
export type ResultActKind = "accept" | "checks-running" | "run-checks" | "request-changes" | "rebuild" | "confirm-stopped" | "revise" | "draft-repair";
|
|
18
20
|
export type ResultActFacts = {
|
|
19
21
|
/** An Accept this page can post (Mark complete, or the person's acceptance); `ready`: every requirement met and checks passed. */
|
package/dist/result-acts.js
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
*
|
|
15
15
|
* Accepting means what it always meant: the same forms post to the same
|
|
16
16
|
* endpoints. This only decides which one is ink. */
|
|
17
|
+
/** The words over the reason field an Accept of a refuted result requires. */
|
|
18
|
+
export const ACCEPT_NEEDS_REASON = "Accepting needs a reason";
|
|
17
19
|
export function resultActsOf(facts) {
|
|
18
20
|
const request = facts.canRequest ? "request-changes" : null;
|
|
19
21
|
const accept = facts.accept === null ? null : "accept";
|
package/dist/serve.js
CHANGED
|
@@ -75,7 +75,7 @@ import { leadBriefHtml, LEAD_CONTEXT_CSS } from './lead-context.js';
|
|
|
75
75
|
import { assignmentCatchUp } from './assignment-brief.js';
|
|
76
76
|
import { assignmentActionHref, assignmentCardOf, assignmentStatusOf, assignmentSummaryHtml, assignmentWithEvidence, ASSIGNMENT_CSS } from './assignment-ui.js';
|
|
77
77
|
import { assignmentStageOf, pullRequestFactOf, stageOfCode, stageOfDispatch, statusDetailsHtml, statusIconSvg, statusWhyHtml, taskStatusOf, TASK_STATUS_CSS } from './task-status.js';
|
|
78
|
-
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';
|
|
79
79
|
import { assignmentPresentationOf, historicalAssessmentReason, shortenedMaterialReason } from './assignment-presentation.js';
|
|
80
80
|
import { ledgerBody } from "./ledger-view.js";
|
|
81
81
|
import { EVIDENCE_PACK_CSS, evidencePack, evidencePackHtml, exportDay, ledgerExportChunks } from "./evidence-pack.js";
|
|
@@ -164,7 +164,7 @@ import { starterRecipes, savedRecipes, findRecipe, importRecipe, exportRecipe, c
|
|
|
164
164
|
import { recipeFromForm, recipeLibraryHtml, recipeEditorHtml, workflowPreviewHtml, recipeScript, RECIPE_CSS, recipeDefinitionPreviewHtml, recipeRunHtml, recipeAnswersFromForm } from "./recipe-ui.js";
|
|
165
165
|
import { EVIDENCE_CAPS, readVerifiedArtifact, readVerifiedReport, readVerifiedProofForRun, storeEvidence, writeEvidenceFile, scanForSecrets } from "./evidence.js";
|
|
166
166
|
import { GOAL_ASSESSMENT_PENDING, reviewConflict, manualReviewOnly, manualReviewCriterionOf, personCheckWords, plainReasonWords, dispatchStatusToken, passFraction, semanticCoverage, coverageWords, coverageStateWords } from "./proof.js";
|
|
167
|
-
import { WORK_VIEWS, REVIEW_TOKENS, RESULT_DECISION_SENTENCE, acceptWordsOf, cantAcceptYetOf, 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";
|
|
168
168
|
import { PRICED_BUILD_MODELS } from "./pricing.js";
|
|
169
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";
|
|
170
170
|
import { parseReport } from "./scout-report.js";
|
|
@@ -5895,6 +5895,33 @@ export function createDecisionServer(options) {
|
|
|
5895
5895
|
return null;
|
|
5896
5896
|
return { taskId: rootId, view: null, offer: { taskId: receipt.taskId, runId: receipt.runId, digest: receipt.digest }, target };
|
|
5897
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
|
+
}
|
|
5898
5925
|
function taskScreen(response, who, taskId, problem, status, scopeDraft, cancelDraft) {
|
|
5899
5926
|
const admittedFamily = familyOf(taskId);
|
|
5900
5927
|
if (admittedFamily?.problem != null)
|
|
@@ -5917,6 +5944,8 @@ export function createDecisionServer(options) {
|
|
|
5917
5944
|
versions: family.versions.map(one => ({ id: one.id, title: one.title, state: one.state })),
|
|
5918
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 }))) };
|
|
5919
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);
|
|
5920
5949
|
const paneProject = restricted() ? store.lookupRef(taskId)?.repo ?? null : who.via === "cookie" ? who.session.project : null;
|
|
5921
5950
|
const page = taskPage(paneProject === null && !unscopedMode
|
|
5922
5951
|
? chromeFor(paneProject, "tasks")
|
|
@@ -10446,10 +10475,21 @@ export function createDecisionServer(options) {
|
|
|
10446
10475
|
return redirect(response, taskHref(taskId));
|
|
10447
10476
|
}
|
|
10448
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);
|
|
10449
10484
|
const requeued = store.requeueTask(taskId, who.name, now);
|
|
10450
10485
|
if (!requeued.ok) {
|
|
10451
10486
|
return taskScreen(response, who, taskId, `not requeued: ${requeued.reason}`, 409);
|
|
10452
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
|
+
}
|
|
10453
10493
|
// Allow-listed return only — never an arbitrary URL from the form.
|
|
10454
10494
|
return redirect(response, body.get("return") === "inbox" ? "/inbox" : body.get("return") === "next" ? "/next" : taskHref(taskId));
|
|
10455
10495
|
}
|
|
@@ -19828,7 +19868,8 @@ function taskBodyParts(data) {
|
|
|
19828
19868
|
case "approve-scope": return ` <a href="#approve">Review and sign the exact scope</a>.`;
|
|
19829
19869
|
case "answer-decision": return ` <a href="#decisions">Answer the waiting question</a>.`;
|
|
19830
19870
|
case "unhold": return ` Use <strong>Remove hold</strong> when it can continue.`;
|
|
19831
|
-
|
|
19871
|
+
// The reason is the detail itself; Retry is the status card's own act, said once.
|
|
19872
|
+
case "retry-task": return "";
|
|
19832
19873
|
case "repair-capability": return ` <a href="/caps">Repair the requirement</a>.`;
|
|
19833
19874
|
case "repair-dependency":
|
|
19834
19875
|
return blocker?.admitted === true
|
|
@@ -19871,7 +19912,8 @@ function taskBodyParts(data) {
|
|
|
19871
19912
|
: `<p class="meta">This task resumes automatically when the builder reconnects. You do not need to file or approve it again.</p>`) +
|
|
19872
19913
|
`<p><a href="/system">See connection status →</a></p></div></details>`);
|
|
19873
19914
|
}
|
|
19874
|
-
|
|
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);
|
|
19875
19917
|
return href === null ? "" : `<a class="button-link dispatch-action-link" href="${href}">${escape(dispatchActionLabel(diagnosis))}</a>`;
|
|
19876
19918
|
})();
|
|
19877
19919
|
const positive = diagnosis.code === "running" || diagnosis.code === "ready" || diagnosis.code === "planning-ready" || diagnosis.code === "scouting-ready";
|
|
@@ -20569,8 +20611,10 @@ function taskBodyParts(data) {
|
|
|
20569
20611
|
// hold (the hold governs the NEXT start), and requeue refuses while a
|
|
20570
20612
|
// claim is live — so the words say exactly when each becomes real.
|
|
20571
20613
|
const canPlan = data.plan === null && !approval.approved && !data.claimed && task.state === "queued" && (data.coordinator === null || data.coordinator === undefined);
|
|
20572
|
-
|
|
20573
|
-
|
|
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" }
|
|
20574
20618
|
: canPlan
|
|
20575
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" }
|
|
20576
20620
|
: data.plan === "requested"
|
|
@@ -20581,7 +20625,7 @@ function taskBodyParts(data) {
|
|
|
20581
20625
|
const holdAct = `<form method="post" action="${taskHref(task.id)}/hold" class="inline act-hold">` +
|
|
20582
20626
|
`<input type="hidden" name="csrf" value="${escape(data.csrf)}">` +
|
|
20583
20627
|
`<input type="text" name="reason" class="inline" placeholder="reason (optional)" aria-label="hold reason">` +
|
|
20584
|
-
`<button type="submit">
|
|
20628
|
+
`<button type="submit">Hold the next attempt</button></form>`;
|
|
20585
20629
|
const canHold = task.state === "queued" || task.state === "running" || task.state === "failed";
|
|
20586
20630
|
const actsBar = [
|
|
20587
20631
|
`<span id="task-actions"></span><div class="acts-bar">`,
|
|
@@ -20598,7 +20642,7 @@ function taskBodyParts(data) {
|
|
|
20598
20642
|
`</div>`,
|
|
20599
20643
|
primaryAct === null ? "" : `<p class="meta acts-why acts-why-${primaryAct.whyClass}">${primaryAct.why}</p>`,
|
|
20600
20644
|
data.claimed && !stopControlsActive
|
|
20601
|
-
? `<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>`
|
|
20602
20646
|
: "",
|
|
20603
20647
|
].join("\n");
|
|
20604
20648
|
// Cancel gets the same ceremony as an irreversible answer: armed behind
|
|
@@ -20791,7 +20835,7 @@ function taskBodyParts(data) {
|
|
|
20791
20835
|
: run.outcome === null ? "Attempt never finished"
|
|
20792
20836
|
: `${noun[0].toUpperCase()}${noun.slice(1)} ${run.outcome}`;
|
|
20793
20837
|
thread.push({ key: `run-${run.id}`, at: run.finishedAt ?? run.startedAt, kind: "result", who: "agent", author: run.runner,
|
|
20794
|
-
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),
|
|
20795
20839
|
link: href, html: "",
|
|
20796
20840
|
more: previousResult !== null && run.id === newestBuilt?.id && run.taskId === task.id ? { summary: "Result details", html: previousResult.html } : null });
|
|
20797
20841
|
}
|
|
@@ -20886,6 +20930,22 @@ function taskBodyParts(data) {
|
|
|
20886
20930
|
// needs a person, then facts, then folds; the mechanics under Manage.
|
|
20887
20931
|
const MANAGE = new Set(["steering", "waits-for", "holds"]);
|
|
20888
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;
|
|
20889
20949
|
const view = {
|
|
20890
20950
|
kind: "task",
|
|
20891
20951
|
id: task.id,
|
|
@@ -20897,13 +20957,15 @@ function taskBodyParts(data) {
|
|
|
20897
20957
|
{ label: "Ask", href: taskChatHref(data.rootId ?? task.id), active: false },
|
|
20898
20958
|
],
|
|
20899
20959
|
version: data.versionLabel == null ? null : { label: data.versionLabel, current: { label: "Current work", href: taskHref(data.rootId ?? task.id) } },
|
|
20900
|
-
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,
|
|
20901
20962
|
statusHtml,
|
|
20963
|
+
failure: failedCard ? data.failure ?? null : null,
|
|
20964
|
+
retry,
|
|
20902
20965
|
approval: approvalHtml,
|
|
20903
20966
|
confirmStopped: data.csrf !== "" && data.assignment?.primaryAction?.code === "confirm-stopped" && data.assignment.primaryAction.target.runId !== null
|
|
20904
20967
|
? { action: `${taskHref(data.assignment.rootId)}/confirm-stopped`, run: data.assignment.primaryAction.target.runId, checked: needsCheck(data.assignment) } : null,
|
|
20905
|
-
rebuild
|
|
20906
|
-
? { action: `${taskHref(data.assignment.rootId)}/requeue` } : null,
|
|
20968
|
+
rebuild,
|
|
20907
20969
|
// The plan, progress and plan changes are thread entries now; the rest still needs a person here.
|
|
20908
20970
|
lead: [
|
|
20909
20971
|
{ key: "history", html: data.history ?? "" }, { key: "control", html: controlHtml }, { key: "problem", html: problemHtml },
|
|
@@ -21393,7 +21455,7 @@ function reviewCockpitDetailParts(view, csrf, noted, canRetryReview, tab, user)
|
|
|
21393
21455
|
problem: view.detail === null ? view.historyProblem : null,
|
|
21394
21456
|
next: reviewNextActionOf(view, csrf), complete: null, decision: null, checks: null, intent: intentView, noRun: null, panel: null, contest: "",
|
|
21395
21457
|
notes: view.notes.map(one => ({ author: one.author, at: one.createdAt, note: one.note })),
|
|
21396
|
-
acts: { primary: null, secondary: null, line: null }, runChecks: null,
|
|
21458
|
+
acts: { primary: null, secondary: null, line: null }, runChecks: null, mismatch: null,
|
|
21397
21459
|
// The raw run record lives under Details now (2026-10-02): /r/<id> for this result redirects here.
|
|
21398
21460
|
record: run === null ? null : { build: run.id, href: `/r/${run.id}?record=1`, facts: [
|
|
21399
21461
|
{ label: "Agent", value: [run.provider, run.model].filter(Boolean).join(" · ") },
|
|
@@ -21473,6 +21535,40 @@ function reviewCockpitDetailParts(view, csrf, noted, canRetryReview, tab, user)
|
|
|
21473
21535
|
need: need === null || need.accept != null ? null : need.rebuild != null ? "rebuild" : need.confirm !== null ? "confirm-stopped" : null,
|
|
21474
21536
|
next: nextKind === "revise" || nextKind === "draft-repair" ? nextKind : null,
|
|
21475
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
|
+
})();
|
|
21476
21572
|
if (view.notes.length > 0) {
|
|
21477
21573
|
parts.push(`<section class="card cockpit-section" data-cockpit-section="notes"><h3>operator notes</h3>` +
|
|
21478
21574
|
view.notes.map(one => `<p class="row"><span class="meta">${escape(one.author)} · ${whenTime(one.createdAt)}</span> ${escape(one.note)}</p>`).join("\n") +
|
|
@@ -21481,7 +21577,7 @@ function reviewCockpitDetailParts(view, csrf, noted, canRetryReview, tab, user)
|
|
|
21481
21577
|
return {
|
|
21482
21578
|
html: `<section class="cockpit-detail">${parts.join("\n")}</section>`,
|
|
21483
21579
|
selected: {
|
|
21484
|
-
...selected, complete, decision, panel: panel.panel, contest, acts, runChecks,
|
|
21580
|
+
...selected, complete, decision, panel: panel.panel, contest, acts, runChecks, mismatch,
|
|
21485
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}` },
|
|
21486
21582
|
},
|
|
21487
21583
|
};
|
|
@@ -21576,38 +21672,9 @@ function homePhaseWords(run) {
|
|
|
21576
21672
|
function phaseWords(phase) {
|
|
21577
21673
|
return PHASE_WORDS[phase] ?? "the agent is working";
|
|
21578
21674
|
}
|
|
21579
|
-
|
|
21580
|
-
agent: "the agent failed",
|
|
21581
|
-
"agent-reported": "the agent reported it could not finish",
|
|
21582
|
-
"no-op": "nothing changed when something should have",
|
|
21583
|
-
"no-handoff": "the agent stopped before handing off; its work was kept and it is being resumed",
|
|
21584
|
-
"moved-head": "the branch moved underneath the build",
|
|
21585
|
-
"moved-branch": "the branch moved underneath the build",
|
|
21586
|
-
timeout: "ran out of time",
|
|
21587
|
-
git: "a git step failed",
|
|
21588
|
-
"malformed-decision": "the agent's question was malformed",
|
|
21589
|
-
"malformed-plan": "the plan was malformed",
|
|
21590
|
-
fenced: "another worker took the task over",
|
|
21591
|
-
unapproved: "the scope was not approved",
|
|
21592
|
-
"scope-changed": "the scope changed after approval",
|
|
21593
|
-
capability: "a requirement was missing",
|
|
21594
|
-
setup: "the workspace preparation step failed",
|
|
21595
|
-
"provider-init": "the agent could not start",
|
|
21596
|
-
"commit-failure": "the commit failed",
|
|
21597
|
-
"protected-branch": "refused to touch a protected branch",
|
|
21598
|
-
"wrong-branch": "the checkout was on the wrong branch",
|
|
21599
|
-
"not-leased": "the lease was not valid",
|
|
21600
|
-
"no-claim": "the lease was not valid",
|
|
21601
|
-
"not-yours": "the lease was not valid",
|
|
21602
|
-
"no-run-record": "the run record was missing",
|
|
21603
|
-
"missing-mailbox": "the resume mailbox could not be read",
|
|
21604
|
-
"unreadable-mailbox": "the resume mailbox could not be read",
|
|
21605
|
-
"revision-brief": "the revision brief could not be read",
|
|
21606
|
-
"repaired-park": "resumed from a parked question",
|
|
21607
|
-
stopped: "stopped by the operator",
|
|
21608
|
-
};
|
|
21675
|
+
/** A recorded reason in one plain line; machine output (a stack trace, a path, "Error:" text) waits on the run record. */
|
|
21609
21676
|
function reasonWords(reason) {
|
|
21610
|
-
return
|
|
21677
|
+
return isInternalErrorReason(reason) ? "stopped with an internal error" : oneLineOf(runReasonWords(reason.trim().split(/\r?\n/, 1)[0] ?? ""), 140);
|
|
21611
21678
|
}
|
|
21612
21679
|
const INCIDENT_WORDS = {
|
|
21613
21680
|
"malformed-decision": "the agent's question was malformed",
|
|
@@ -22057,18 +22124,23 @@ function reviewDiffHtml(patch, stat, runId, commentable, anchors = new Map()) {
|
|
|
22057
22124
|
: counts.additions === null || counts.deletions === null
|
|
22058
22125
|
? `<span class="diff-file-counts">binary</span>`
|
|
22059
22126
|
: `<span class="diff-file-counts"><b>+${counts.additions}</b><i>−${counts.deletions}</i></span>`;
|
|
22060
|
-
const
|
|
22061
|
-
|
|
22062
|
-
|
|
22063
|
-
|
|
22064
|
-
|
|
22065
|
-
|
|
22066
|
-
|
|
22067
|
-
|
|
22068
|
-
|
|
22069
|
-
|
|
22070
|
-
|
|
22071
|
-
|
|
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("");
|
|
22072
22144
|
const anchor = anchors.get(one.path);
|
|
22073
22145
|
return (`<details class="diff-file"${fileIndex === 0 ? " open" : ""}${anchor === undefined ? "" : ` id="${anchor}"`}>` +
|
|
22074
22146
|
`<summary><span class="diff-file-name">${escape(one.path)}</span>${countWords}</summary>` +
|
|
@@ -22096,7 +22168,9 @@ function checkLogHtml(log, runId, attributes) {
|
|
|
22096
22168
|
if (log.problem !== null) {
|
|
22097
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>`;
|
|
22098
22170
|
}
|
|
22099
|
-
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>` +
|
|
22100
22174
|
storedDownloadLink(runId, log.artifactId, "check log", log.truncated));
|
|
22101
22175
|
}
|
|
22102
22176
|
/** The download link for a stored record (repair 2026-09-14): a shortened
|
|
@@ -22512,6 +22586,8 @@ function resultPanelParts(detail, o) {
|
|
|
22512
22586
|
...(conclusion !== outcome ? [`<p class="recap">${escape(conclusion)}</p>`] : []),
|
|
22513
22587
|
...(handoff !== null && handoff.changes.length > 0 ? [`<ul class="result-changes">${handoff.changes.map(one => `<li>${escape(one)}</li>`).join("")}</ul>`] : []),
|
|
22514
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>`]),
|
|
22515
22591
|
];
|
|
22516
22592
|
if (agentAccount.length > 0)
|
|
22517
22593
|
summaryParts.push(`<details class="result-notes" data-result-notes-agent><summary>What the agent reported</summary>${agentAccount.join("")}</details>`);
|
|
@@ -22901,7 +22977,7 @@ function runFactsRows(run, taskId, live, route = null) {
|
|
|
22901
22977
|
["quality", qualityModeTitle(run.qualityMode ?? "default")],
|
|
22902
22978
|
["outcome", live ? "running" : (run.outcome ?? "never finished")],
|
|
22903
22979
|
["phase", live && run.phase !== null ? phaseWords(run.phase) : null],
|
|
22904
|
-
["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)],
|
|
22905
22981
|
["runner", run.runner, true],
|
|
22906
22982
|
["branch", run.branch, true],
|
|
22907
22983
|
["model", run.model, true],
|
|
@@ -22927,7 +23003,11 @@ function runFactsRows(run, taskId, live, route = null) {
|
|
|
22927
23003
|
.filter((fact) => fact[1] !== null && fact[1] !== "")
|
|
22928
23004
|
.map(([label, value, mono]) => `<p class="row"><span class="meta" style="min-width:8.5rem">${escape(label)}</span> ` +
|
|
22929
23005
|
`<span${mono === true ? ` class="mono"` : ""}>${escape(value)}</span></p>`)
|
|
22930
|
-
.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
|
+
: ""));
|
|
22931
23011
|
}
|
|
22932
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) {
|
|
22933
23013
|
const rows = runFactsRows(run, taskId, running, route);
|