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/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. */
|
package/dist/task-status.js
CHANGED
|
@@ -114,7 +114,9 @@ function detailsOf(headline, facts) {
|
|
|
114
114
|
row("pull-request", "Pull request", `${name} open`, "none", { href: github });
|
|
115
115
|
}
|
|
116
116
|
const req = facts.requirements;
|
|
117
|
-
if (req != null && req.total > 0)
|
|
117
|
+
if (req != null && req.total > 0 && req.unverified === true)
|
|
118
|
+
row("requirements", "Requirements", "Unverified", "none");
|
|
119
|
+
else if (req != null && req.total > 0) {
|
|
118
120
|
const unmet = req.total - req.met - req.yours;
|
|
119
121
|
const text = `${req.met} of ${req.total} met${req.yours > 0 ? ` · You check ${req.yours}` : ""}`;
|
|
120
122
|
row("requirements", "Requirements", text, unmet > 0 ? problem(headline) : req.met === req.total ? "ok" : "none", unmet > 0 && headline !== "Failed" ? { action: { label: "See which", href: facts.links?.checks ?? facts.links?.result ?? null } } : {});
|
|
@@ -264,6 +266,10 @@ export function requirementsOf(matrix) {
|
|
|
264
266
|
const met = (row) => row.state === "pass" || (row.assessment?.evidenceState === "pass" && (row.review ?? null) === null);
|
|
265
267
|
return { met: matrix.filter(met).length, total: matrix.length, yours: matrix.filter(row => !met(row) && row.state === "manual-review").length };
|
|
266
268
|
}
|
|
269
|
+
/** A refuted report verifies none of its requirements, whatever it marked met (a person's acceptance doesn't change that). */
|
|
270
|
+
export function unverifiedWhenRefuted(requirements, verdict) {
|
|
271
|
+
return requirements === null || verdict !== "refuted" ? requirements : { ...requirements, unverified: true };
|
|
272
|
+
}
|
|
267
273
|
/** Saved evidence health from the receipt's artifact list and its caveats. */
|
|
268
274
|
export function evidenceOf(receipt, problems = receipt?.caveats ?? []) {
|
|
269
275
|
if (receipt === null)
|
|
@@ -297,7 +303,7 @@ export function assignmentStatusFacts(assignment, options = {}) {
|
|
|
297
303
|
stage, ...(need === undefined ? {} : { need }), ...(wait === undefined ? {} : { wait }), ...(build === null ? {} : { needContext: { build } }), reason, report,
|
|
298
304
|
checks,
|
|
299
305
|
pullRequest: withResult ? pullRequest : null,
|
|
300
|
-
requirements: withResult ? requirementsOf(receipt?.proof?.matrix) : null,
|
|
306
|
+
requirements: withResult ? unverifiedWhenRefuted(requirementsOf(receipt?.proof?.matrix), receipt?.proof?.verdict) : null,
|
|
301
307
|
evidence: withResult ? options.evidence ?? evidenceOf(receipt, [...(receipt?.caveats ?? []), ...assignment.attention]) : null,
|
|
302
308
|
completedBy: assignment.completion === null ? null : assignment.completion.lead === true ? "the lead" : assignment.completion.actor.replace(/^(?:operator|coordinator|lead):/, ""),
|
|
303
309
|
action: options.action ?? null,
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { assignmentOf } from "./assignment.js";
|
|
8
8
|
import { chatResultHref, chatControlHref } from "./chat-controls.js";
|
|
9
9
|
import { phoneText, projectLabel } from "./telegram-status.js";
|
|
10
|
-
import { assignmentStatusFacts, headlineEmoji, pullRequestFactOf, requirementsOf, statusDetailLines, taskStatusOf } from "./task-status.js";
|
|
10
|
+
import { assignmentStatusFacts, headlineEmoji, pullRequestFactOf, requirementsOf, statusDetailLines, unverifiedWhenRefuted, taskStatusOf } from "./task-status.js";
|
|
11
11
|
import { leadClaimOf } from "./lead-voice.js";
|
|
12
12
|
import { chatTitle } from "./chat-voice.js";
|
|
13
13
|
import { manualReviewOnly } from "./proof.js";
|
|
@@ -49,7 +49,7 @@ export function telegramProgressCard(store, run, taskId, project, now = new Date
|
|
|
49
49
|
: exit !== null ? { status: "failed", exitCode: exit, head: run.headRevision }
|
|
50
50
|
: machine === "verified" ? { status: "passed", exitCode: null, head: run.headRevision }
|
|
51
51
|
: machine === "attested" ? { status: "not-run", exitCode: null, head: null } : null;
|
|
52
|
-
facts = { stage: "finished", report: run.role === "scout", checks, requirements: requirementsOf(proof?.matrix),
|
|
52
|
+
facts = { stage: "finished", report: run.role === "scout", checks, requirements: unverifiedWhenRefuted(requirementsOf(proof?.matrix), proof?.verdict),
|
|
53
53
|
...(pullRequest === undefined ? {} : { pullRequest }) };
|
|
54
54
|
}
|
|
55
55
|
else if (accessBlocked) {
|
package/dist/work-index.js
CHANGED
|
@@ -11,7 +11,7 @@ import { plainReasonOf, replacedWords, stageOfCode, taskStatusOf, workToneOf } f
|
|
|
11
11
|
import { isCheckLevel } from './check-levels.js';
|
|
12
12
|
import { MARKER } from './worktree.js';
|
|
13
13
|
import { withFollowUps } from './result-follow-ups.js';
|
|
14
|
-
import { ASKS, NEED_ASK, NEEDS, processNeedOf } from './needs-you.js';
|
|
14
|
+
import { ASKS, NEED_ASK, NEEDS, failedAttemptSentence, processNeedOf } from './needs-you.js';
|
|
15
15
|
import { LEAD_CLAIM_SQL, LEAD_IDLE_MS } from './lead-voice.js';
|
|
16
16
|
export const WORK_INDEX_PAGE_LIMIT = 40;
|
|
17
17
|
export const WORK_INDEX_MAX_LIMIT = 100;
|
|
@@ -369,7 +369,7 @@ export function workIndexPage(store, now, access, options = {}) {
|
|
|
369
369
|
'running_count',running_count,'completed_count',completed_count,'queued',queued,'done_recently',done_recently)) FROM project_totals) row_json FROM totals
|
|
370
370
|
UNION ALL SELECT 1,0,0,0,0,0,0,0,0,0,json_object('root_ref',root_ref,'root_id',root_id,'id',id,'root_title',root_title,'repo',repo,
|
|
371
371
|
'state',state,'created_at',created_at,'family_updated',family_updated,'version_count',version_count,'earlier_active',earlier_active,
|
|
372
|
-
'earlier_id',earlier_id,'broken',broken,'live_run',live_run,'result_id',page_result_id,'result_outcome',(SELECT outcome FROM run WHERE id=page_result_id),
|
|
372
|
+
'earlier_id',earlier_id,'broken',broken,'live_run',live_run,'result_id',page_result_id,'result_outcome',(SELECT outcome FROM run WHERE id=page_result_id),'attempt_reason',(SELECT substr(reason,1,1000) FROM run WHERE task_ref IN (SELECT ref_id FROM members WHERE root_ref=page.root_ref) AND (finished_at IS NOT NULL OR outcome IS NOT NULL) AND role NOT IN ('planner','reviewer') ORDER BY id DESC LIMIT 1),
|
|
373
373
|
'publication_url',(SELECT pr_url FROM publication WHERE run=page_result_id),
|
|
374
374
|
'result_role',(SELECT role FROM run WHERE id=page_result_id),'live_role',(SELECT role FROM run WHERE id=page.live_run),
|
|
375
375
|
'check_status',(SELECT status FROM run_check WHERE run=page_result_id),
|
|
@@ -462,7 +462,7 @@ function itemOf(row, principal, probe) {
|
|
|
462
462
|
'decision-queue': ['Needs your decision', 'The decision queue is full.', 'inspect-decisions', 'Review open questions'],
|
|
463
463
|
'result-needs-attention': ['Needs your decision', 'Inspect the saved result and resolve its remaining execution or scope issue.', 'inspect-run', 'Inspect run'],
|
|
464
464
|
'process-needs-attention': ['Needs your decision', 'A process exit is not recorded. Open the attempt to check whether its work has stopped.', 'inspect-run', 'Inspect run'],
|
|
465
|
-
failed: ['Needs your decision', 'The last attempt stopped
|
|
465
|
+
failed: ['Needs your decision', 'The last attempt stopped.', 'retry-task', 'Review and retry'],
|
|
466
466
|
'vanished-run': ['Needs your decision', 'An unfinished attempt has no current live claim.', 'reconcile-run', 'Check the unfinished attempt'],
|
|
467
467
|
stopping: ['Needs your decision', 'Stopping. The work so far is kept.', 'inspect-stop', 'View stop details'],
|
|
468
468
|
stopped: ['Needs your decision', 'Paused by a person. The work so far is kept.', 'resume-run', 'Review pause'],
|
|
@@ -502,6 +502,9 @@ function itemOf(row, principal, probe) {
|
|
|
502
502
|
}
|
|
503
503
|
if (code === 'terminal-dependency' && s(row, 'dependency_id') !== null)
|
|
504
504
|
detail = `${s(row, 'dependency_id')} ${s(row, 'dependency_state') === 'cancelled' ? 'was cancelled' : 'failed'} before it finished.`;
|
|
505
|
+
// A failed task says the latest finished attempt across its whole family, whatever its outcome: its recorded reason, or that none was recorded.
|
|
506
|
+
if (code === 'failed')
|
|
507
|
+
detail = failedAttemptSentence(s(row, 'attempt_reason'));
|
|
505
508
|
if (code === 'result-needs-attention' && n(row, 'custody_unresolved'))
|
|
506
509
|
detail = 'A process exit is not recorded. Open the result to check whether its work has stopped.';
|
|
507
510
|
if (code === 'result-needs-attention' && ['built', 'no-change'].includes(s(row, 'result_outcome') ?? '')) {
|
package/dist/workspace-ui.d.ts
CHANGED
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
* claimed without one.
|
|
18
18
|
*/
|
|
19
19
|
import type { DispatchDiagnosis } from "./dispatch.js";
|
|
20
|
+
import { ACCEPT_NEEDS_REASON } from "./result-acts.js";
|
|
21
|
+
export { ACCEPT_NEEDS_REASON };
|
|
20
22
|
import { type ProofVerdict } from "./proof.js";
|
|
21
23
|
import type { ReviewRetryState, TaskState } from "./store.js";
|
|
22
24
|
import type { TaskControlView } from "./task-control.js";
|
|
@@ -194,6 +196,48 @@ export declare function dispatchActionLabel(dispatch: DispatchDiagnosis | null):
|
|
|
194
196
|
* when nothing refutes it (a person's acceptance settles it). The result page
|
|
195
197
|
* shows it before Request changes, its one ink act. */
|
|
196
198
|
export declare function cantAcceptYetOf(verdict: ProofVerdict | null, reasons: readonly string[], accepted: boolean): string | null;
|
|
199
|
+
/** The result's headline while its report disagrees with its saved changes. */
|
|
200
|
+
export declare const MISMATCH_HEADLINE = "The report doesn't match the changes";
|
|
201
|
+
/** One way the report disagrees with the saved changes, in plain words: the
|
|
202
|
+
* file and lines it concerns when they are in the saved changes, or
|
|
203
|
+
* `inChanges: false` for a file the report names that the changes don't have.
|
|
204
|
+
* `reason` is the recorded words it says plainly. */
|
|
205
|
+
export type ReportMismatch = {
|
|
206
|
+
text: string;
|
|
207
|
+
path: string | null;
|
|
208
|
+
lines: {
|
|
209
|
+
from: number;
|
|
210
|
+
to: number;
|
|
211
|
+
} | null;
|
|
212
|
+
inChanges: boolean | null;
|
|
213
|
+
/** The report's own note (1-based) the disagreement is about, when it names one. */
|
|
214
|
+
note: number | null;
|
|
215
|
+
reason: string;
|
|
216
|
+
};
|
|
217
|
+
/** Each recorded way a refuted report disagrees with its saved changes, said
|
|
218
|
+
* plainly and tied to the changed lines it concerns. `changes` maps each
|
|
219
|
+
* changed file to the lines of its first change (null when it has no line
|
|
220
|
+
* changes, a binary file say). Presentation only: the verdict is unchanged. */
|
|
221
|
+
export declare function reportMismatchesOf(reasons: readonly string[], criteria: readonly {
|
|
222
|
+
id: string;
|
|
223
|
+
statement: string;
|
|
224
|
+
answered: readonly {
|
|
225
|
+
kind: string;
|
|
226
|
+
ref: string;
|
|
227
|
+
}[];
|
|
228
|
+
state?: string;
|
|
229
|
+
detail?: readonly string[];
|
|
230
|
+
}[], changes: ReadonlyMap<string, {
|
|
231
|
+
from: number;
|
|
232
|
+
to: number;
|
|
233
|
+
} | null>): ReportMismatch[];
|
|
234
|
+
/** The line a failing check ended on: the last line of its error output that
|
|
235
|
+
* names an error, else the last line it printed, with its 1-based number in
|
|
236
|
+
* the saved log. Null when the log printed nothing. */
|
|
237
|
+
export declare function lastErrorLineOf(log: string): {
|
|
238
|
+
line: number;
|
|
239
|
+
text: string;
|
|
240
|
+
} | null;
|
|
197
241
|
/** The result page's own sentence when the decision is on it. */
|
|
198
242
|
export declare const RESULT_DECISION_SENTENCE = "Review the change, then accept it or ask for changes.";
|
|
199
243
|
/** What the result page's Accept says, from facts it already shows: the
|
package/dist/workspace-ui.js
CHANGED
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
* observed" name observed publication records, and deployment is never
|
|
17
17
|
* claimed without one.
|
|
18
18
|
*/
|
|
19
|
-
import {
|
|
19
|
+
import { ACCEPT_NEEDS_REASON } from "./result-acts.js";
|
|
20
|
+
export { ACCEPT_NEEDS_REASON };
|
|
21
|
+
import { GOAL_ASSESSMENT_PENDING, manualReviewOnly, plainReasonWords } from "./proof.js";
|
|
20
22
|
import { plainReasonOf, stageOfCode, taskStatusOf, workToneOf } from "./task-status.js";
|
|
21
23
|
export const WORK_VIEWS = [
|
|
22
24
|
{ key: "all", label: "All", hint: "Every task in view, most urgent first.", empty: "Nothing is in progress. Describe work in chat or add a task, and it appears here." },
|
|
@@ -318,9 +320,89 @@ const VERIFIED_RESULT = new Set(["ready-to-review", "pr-opened", "merge-observed
|
|
|
318
320
|
export function cantAcceptYetOf(verdict, reasons, accepted) {
|
|
319
321
|
if (accepted || verdict !== "refuted")
|
|
320
322
|
return null;
|
|
323
|
+
// A mismatch is the status card's own headline; here only what accepting takes.
|
|
321
324
|
return evidenceProblemOf(verdict, reasons) === "checks-failed"
|
|
322
325
|
? "Can't accept yet: the project's check failed on these changes."
|
|
323
|
-
:
|
|
326
|
+
: ACCEPT_NEEDS_REASON;
|
|
327
|
+
}
|
|
328
|
+
/** The result's headline while its report disagrees with its saved changes. */
|
|
329
|
+
export const MISMATCH_HEADLINE = "The report doesn't match the changes";
|
|
330
|
+
const OVERCLAIMED = /^claimed changed paths? not in the sealed diff: ([\s\S]+)$/;
|
|
331
|
+
const RESTATED = /^criterion "([^"]+)" was signed as "[\s\S]*" and the proof restates it as "([\s\S]*)"$/;
|
|
332
|
+
const CONTRADICTED_BY_CAVEAT = /^criterion "([^"]+)" is marked met, but caveat [0-9]+ admits an exception to it: ([\s\S]*)$/;
|
|
333
|
+
const NOTE_NUMBER = /^caveat ([0-9]+) /;
|
|
334
|
+
const CONTRADICTED_BY_REVIEW = /^(?:reviewer:)?[^\s"]+ contradicts criterion "([^"]+)": [\s\S]*$/i;
|
|
335
|
+
/** Each recorded way a refuted report disagrees with its saved changes, said
|
|
336
|
+
* plainly and tied to the changed lines it concerns. `changes` maps each
|
|
337
|
+
* changed file to the lines of its first change (null when it has no line
|
|
338
|
+
* changes, a binary file say). Presentation only: the verdict is unchanged. */
|
|
339
|
+
export function reportMismatchesOf(reasons, criteria, changes) {
|
|
340
|
+
const named = (id) => {
|
|
341
|
+
const statement = criteria.find(one => one.id === id)?.statement.trim() ?? "";
|
|
342
|
+
if (statement === "")
|
|
343
|
+
return `requirement ${id}`;
|
|
344
|
+
return `“${statement.length > 90 ? `${statement.slice(0, 90).replace(/\s+\S*$/, "")}…` : statement}”`;
|
|
345
|
+
};
|
|
346
|
+
// The first file the requirement cites that the saved changes have.
|
|
347
|
+
const place = (id) => {
|
|
348
|
+
const path = criteria.find(one => one.id === id)?.answered.find(ref => ref.kind === "changed-path" && changes.has(ref.ref))?.ref ?? null;
|
|
349
|
+
return path === null ? { path: null, lines: null, inChanges: null } : { path, lines: changes.get(path) ?? null, inChanges: true };
|
|
350
|
+
};
|
|
351
|
+
const rows = reasons.flatMap((reason) => {
|
|
352
|
+
const over = OVERCLAIMED.exec(reason);
|
|
353
|
+
if (over !== null)
|
|
354
|
+
return over[1].split(", ").map(path => ({ text: "The report says it changed", path, lines: null, inChanges: changes.has(path), note: null, reason }));
|
|
355
|
+
const restated = RESTATED.exec(reason);
|
|
356
|
+
if (restated !== null)
|
|
357
|
+
return [{ text: `The report rewords ${named(restated[1])} as “${restated[2]}”`, ...place(restated[1]), note: null, reason }];
|
|
358
|
+
const caveat = CONTRADICTED_BY_CAVEAT.exec(reason);
|
|
359
|
+
const note = /^criterion "[^"]+" is marked met, but caveat ([0-9]+) /.exec(reason)?.[1] ?? NOTE_NUMBER.exec(reason)?.[1] ?? null;
|
|
360
|
+
if (caveat !== null)
|
|
361
|
+
return [{ text: `The report marks ${named(caveat[1])} met, but its own note says: ${caveat[2]}`, ...place(caveat[1]), note: Number(note), reason }];
|
|
362
|
+
const review = CONTRADICTED_BY_REVIEW.exec(reason);
|
|
363
|
+
if (review !== null)
|
|
364
|
+
return [{ text: plainReasonWords(reason), ...place(review[1]), note: null, reason }];
|
|
365
|
+
const unsigned = UNSIGNED_NOTE.exec(reason);
|
|
366
|
+
if (unsigned !== null)
|
|
367
|
+
return [{ text: `The report's note ${unsigned[1]} is about ${unsigned[2]}, which isn't one of the signed requirements: ${unsigned[3]}`, path: null, lines: null, inChanges: null, note: Number(unsigned[1]), reason }];
|
|
368
|
+
if (FAILED_CHECK.test(reason))
|
|
369
|
+
return [];
|
|
370
|
+
return [{ text: sentenceOf(plainReasonWords(reason)), path: null, lines: null, inChanges: null, note: note === null ? null : Number(note), reason }];
|
|
371
|
+
});
|
|
372
|
+
// Evidence a requirement cites that doesn't hold, unless a reason above already says so.
|
|
373
|
+
const evidence = criteria.flatMap(one => one.state !== "failed" && one.state !== "missing" ? [] : (one.detail ?? [])
|
|
374
|
+
.filter(detail => !reasons.includes(detail) && !/is waiting for the final check|requires manual-review evidence|is marked met, but caveat|was signed as/.test(detail))
|
|
375
|
+
.map((detail) => {
|
|
376
|
+
const lead = [`criterion "${one.id}"'s `, `criterion "${one.id}" `].find(prefix => detail.startsWith(prefix));
|
|
377
|
+
const rest = lead === undefined ? detail : detail.slice(lead.length);
|
|
378
|
+
return { text: `${sentenceOf(named(one.id))}: ${rest}`, ...place(one.id), note: null, reason: detail };
|
|
379
|
+
}));
|
|
380
|
+
return [...rows, ...evidence];
|
|
381
|
+
}
|
|
382
|
+
const UNSIGNED_NOTE = /^caveat ([0-9]+) names ("[^"]*"(?:, "[^"]*")*), (?:a criterion the proof authored|which is no signed)[\s\S]*?: ([\s\S]*)$/;
|
|
383
|
+
/** A sentence starts with a capital letter. */
|
|
384
|
+
function sentenceOf(text) {
|
|
385
|
+
return text === "" ? text : `${text.charAt(0).toUpperCase()}${text.slice(1)}`;
|
|
386
|
+
}
|
|
387
|
+
/** The line a failing check ended on: the last line of its error output that
|
|
388
|
+
* names an error, else the last line it printed, with its 1-based number in
|
|
389
|
+
* the saved log. Null when the log printed nothing. */
|
|
390
|
+
export function lastErrorLineOf(log) {
|
|
391
|
+
const lines = log.split("\n");
|
|
392
|
+
const printed = (text) => text.trim() !== "" && !/^\$ /.test(text) && !/^\(exit -?[0-9]+\)$/.test(text.trim()) && !/^--- (stdout|stderr) ---$/.test(text.trim());
|
|
393
|
+
const stderr = lines.findIndex(text => text.trim() === "--- stderr ---");
|
|
394
|
+
const pick = (from, to) => {
|
|
395
|
+
let last = null;
|
|
396
|
+
for (let index = to - 1; index >= from; index -= 1) {
|
|
397
|
+
if (!printed(lines[index]))
|
|
398
|
+
continue;
|
|
399
|
+
if (/error|fail|✗|✕|×|assert|exception|panic|cannot|not found/i.test(lines[index]))
|
|
400
|
+
return { line: index + 1, text: lines[index].trim() };
|
|
401
|
+
last ??= index;
|
|
402
|
+
}
|
|
403
|
+
return last === null ? null : { line: last + 1, text: lines[last].trim() };
|
|
404
|
+
};
|
|
405
|
+
return (stderr >= 0 ? pick(stderr + 1, lines.length) : null) ?? pick(0, stderr >= 0 ? stderr : lines.length);
|
|
324
406
|
}
|
|
325
407
|
/** The result page's own sentence when the decision is on it. */
|
|
326
408
|
export const RESULT_DECISION_SENTENCE = "Review the change, then accept it or ask for changes.";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolroll",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.14",
|
|
4
4
|
"description": "A control plane for unattended coding agents — queue tasks, walk away, come back to pull requests. Agents build in leased worktrees, never touch your default branch, and interrupt you only for decisions that need a human.",
|
|
5
5
|
"directories": {
|
|
6
6
|
"doc": "docs"
|