tickmarkr 2.6.2 → 2.6.4
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/README.md +12 -2
- package/dist/adapters/codex.d.ts +2 -1
- package/dist/adapters/codex.js +26 -5
- package/dist/adapters/model-lints.d.ts +1 -0
- package/dist/adapters/model-lints.js +48 -1
- package/dist/adapters/model-windows.d.ts +2 -1
- package/dist/adapters/model-windows.js +18 -7
- package/dist/adapters/types.d.ts +6 -0
- package/dist/cli/commands/approve.d.ts +6 -3
- package/dist/cli/commands/approve.js +43 -6
- package/dist/cli/commands/doctor.d.ts +8 -2
- package/dist/cli/commands/doctor.js +6 -1
- package/dist/cli/commands/fleet.js +264 -59
- package/dist/cli/commands/init.js +5 -3
- package/dist/cli/commands/plan.js +13 -8
- package/dist/cli/commands/report.d.ts +40 -2
- package/dist/cli/commands/report.js +278 -11
- package/dist/cli/commands/resume.js +2 -4
- package/dist/cli/commands/run.d.ts +11 -0
- package/dist/cli/commands/run.js +33 -5
- package/dist/cli/commands/status.js +49 -4
- package/dist/cli/commands/verify.js +332 -121
- package/dist/compile/native.js +137 -0
- package/dist/config/config.d.ts +40 -9
- package/dist/config/config.js +133 -15
- package/dist/config/fleet-overlay.d.ts +13 -3
- package/dist/config/fleet-overlay.js +12 -8
- package/dist/drivers/herdr.d.ts +12 -0
- package/dist/drivers/herdr.js +51 -0
- package/dist/drivers/orca.d.ts +9 -1
- package/dist/drivers/orca.js +29 -7
- package/dist/drivers/types.d.ts +2 -0
- package/dist/drivers/types.js +2 -2
- package/dist/gates/acceptance.d.ts +7 -0
- package/dist/gates/acceptance.js +27 -5
- package/dist/gates/baseline.d.ts +20 -1
- package/dist/gates/baseline.js +100 -20
- package/dist/gates/cache.d.ts +8 -0
- package/dist/gates/cache.js +12 -2
- package/dist/gates/llm.d.ts +6 -0
- package/dist/gates/llm.js +27 -8
- package/dist/gates/review.d.ts +6 -1
- package/dist/gates/review.js +122 -32
- package/dist/gates/run-gates.d.ts +54 -3
- package/dist/gates/run-gates.js +331 -45
- package/dist/gates/test-manifest.d.ts +42 -0
- package/dist/gates/test-manifest.js +69 -10
- package/dist/route/router.d.ts +23 -1
- package/dist/route/router.js +54 -16
- package/dist/run/consult.d.ts +3 -1
- package/dist/run/consult.js +4 -2
- package/dist/run/daemon.d.ts +2 -1
- package/dist/run/daemon.js +349 -79
- package/dist/run/interactive-seed.d.ts +4 -0
- package/dist/run/interactive-seed.js +35 -9
- package/dist/run/journal.d.ts +123 -1
- package/dist/run/journal.js +480 -17
- package/dist/run/lease.d.ts +13 -0
- package/dist/run/lease.js +45 -0
- package/dist/run/protocol.d.ts +15 -0
- package/dist/run/protocol.js +11 -1
- package/dist/run/receipt-resolver.d.ts +22 -0
- package/dist/run/receipt-resolver.js +40 -1
- package/dist/run/repair-selection.d.ts +11 -1
- package/dist/run/repair-selection.js +17 -9
- package/dist/run/wall-budget.d.ts +48 -0
- package/dist/run/wall-budget.js +280 -0
- package/dist/tui/cockpit/live-store.d.ts +6 -0
- package/dist/tui/cockpit/live-store.js +36 -11
- package/dist/tui/cockpit/run-cockpit.js +2 -2
- package/dist/tui/cockpit/run-view.d.ts +2 -1
- package/dist/tui/cockpit/run-view.js +13 -9
- package/dist/tui/cockpit/setup-cockpit.d.ts +2 -0
- package/dist/tui/cockpit/setup-cockpit.js +4 -0
- package/package.json +2 -1
- package/schema/config.schema.json +8 -1
- package/skills/tickmarkr-auto/SKILL.md +2 -2
- package/skills/tickmarkr-loop/SKILL.md +17 -5
- package/skills/tickmarkr-overseer/scripts/classify-vitest-log.sh +4 -1
package/dist/drivers/herdr.js
CHANGED
|
@@ -7,6 +7,7 @@ import { consumePaneLaunchIntent, PANE_IDENTITY_ENV, paneIdentityLine } from "..
|
|
|
7
7
|
import { createWorktree, sh } from "../run/git.js";
|
|
8
8
|
import { Journal } from "../run/journal.js";
|
|
9
9
|
import { readSupervision, readWatchBoard, requestWatchBoardStop, reserveWatchBoard, stopWatchBoard, WATCH_OWNER_ENV } from "../run/supervision.js";
|
|
10
|
+
import { canonicalWorktreePath } from "./orca.js";
|
|
10
11
|
import { herdrSealShellPrefix } from "./subprocess.js";
|
|
11
12
|
import { canonicalizeLegacyName, formatOwnedName, panesToClose, parseOwnedName } from "./types.js";
|
|
12
13
|
// VIS-09 P43-03: adopted safety floor from 43-MEASUREMENT.md (narrowest safe 53 → floor 108).
|
|
@@ -153,6 +154,8 @@ export class HerdrDriver {
|
|
|
153
154
|
deliverySerial = Promise.resolve();
|
|
154
155
|
dispatchLeases = new WeakMap();
|
|
155
156
|
deliveredPanes = new WeakMap();
|
|
157
|
+
/** OBS-1203: slots bound by adopt() — their reads are harvest evidence, so a failed read throws. */
|
|
158
|
+
adoptedSlots = new WeakSet();
|
|
156
159
|
inputBoxes = new WeakMap();
|
|
157
160
|
// OBS-253: the adapter-declared bootstrap this slot has already delivered. A fresh pane is a bare
|
|
158
161
|
// shell, so it is the only thing that can put a TUI back under a typed turn that has to move.
|
|
@@ -452,6 +455,49 @@ export class HerdrDriver {
|
|
|
452
455
|
// refcount, no groupSerial, no degrade latch — dedicated tabs have no shared state to guard (SUP-01).
|
|
453
456
|
return slot; // label undefined → defaults to name (today's behavior)
|
|
454
457
|
}
|
|
458
|
+
/**
|
|
459
|
+
* OBS-1203: bind THIS instance, READ-ONLY, to the pane an earlier daemon created for an owned slot,
|
|
460
|
+
* so a resumed daemon can read the interrupted attempt's trailer. slot() is never a substitute: it
|
|
461
|
+
* allocates a tab, reclaimStaleLabel closes the very pane holding the evidence, and it holds a
|
|
462
|
+
* dispatch lease only run() releases. Binding needs ownership evidence, never a label alone: exactly
|
|
463
|
+
* one pane carries the FULL owned label, herdr reports that pane's cwd as the journaled task
|
|
464
|
+
* checkout, and that path is a git checkout whose own top level it is. Zero, ambiguous or foreign
|
|
465
|
+
* candidates throw — the caller's decline. `pane list` and `git rev-parse` are the only calls made.
|
|
466
|
+
*/
|
|
467
|
+
async adopt(slot) {
|
|
468
|
+
if (!parseOwnedName(slot.name))
|
|
469
|
+
throw new Error(`herdr adopt: slot ${slot.name} carries no owned label — nothing to adopt`);
|
|
470
|
+
const listed = await this.herdr("pane list");
|
|
471
|
+
let panes;
|
|
472
|
+
try {
|
|
473
|
+
const parsed = JSON.parse(listed.stdout).result?.panes;
|
|
474
|
+
if (listed.code !== 0 || !Array.isArray(parsed))
|
|
475
|
+
throw new Error("no panes array");
|
|
476
|
+
panes = parsed;
|
|
477
|
+
}
|
|
478
|
+
catch {
|
|
479
|
+
throw new Error(`herdr adopt: pane list unreadable (exit ${listed.code}) — cannot prove ownership of ${slot.name}`);
|
|
480
|
+
}
|
|
481
|
+
const owned = panes.filter((p) => p.label === slot.name && typeof p.pane_id === "string" && p.pane_id);
|
|
482
|
+
if (owned.length !== 1) {
|
|
483
|
+
throw new Error(`herdr adopt: ${owned.length === 0 ? "no pane carries" : `${owned.length} panes carry`} the owned label ${slot.name}`);
|
|
484
|
+
}
|
|
485
|
+
const pane = owned[0];
|
|
486
|
+
const checkout = canonicalWorktreePath(slot.cwd);
|
|
487
|
+
if (typeof pane.cwd !== "string" || canonicalWorktreePath(pane.cwd) !== checkout) {
|
|
488
|
+
throw new Error(`herdr adopt: pane ${pane.pane_id} labelled ${slot.name} sits in ${pane.cwd ?? "an unreported cwd"}, not the task checkout ${checkout} — foreign`);
|
|
489
|
+
}
|
|
490
|
+
const top = await sh("git rev-parse --show-toplevel", checkout);
|
|
491
|
+
if (top.code !== 0 || canonicalWorktreePath(top.stdout.trim()) !== checkout) {
|
|
492
|
+
throw new Error(`herdr adopt: ${checkout} is not its own git checkout — no checkout proof for ${slot.name}`);
|
|
493
|
+
}
|
|
494
|
+
const adopted = { id: pane.pane_id, name: slot.name, cwd: slot.cwd,
|
|
495
|
+
...(pane.tab_id === undefined ? {} : { tabId: pane.tab_id }), ...(slot.group === undefined ? {} : { group: slot.group }) };
|
|
496
|
+
// Reads go to the pane ownership was proven on, never to a later re-resolution of the label.
|
|
497
|
+
this.deliveredPanes.set(adopted, pane.pane_id);
|
|
498
|
+
this.adoptedSlots.add(adopted);
|
|
499
|
+
return adopted;
|
|
500
|
+
}
|
|
455
501
|
// today's per-slot tab path, plus the VIS-04 orphan reap
|
|
456
502
|
// label defaults to the slot name; group tabs pass the STAGE name instead — a first-member label
|
|
457
503
|
// outlives its member once keepPanes reaps it (run-20260709-104447: the codex pane sat in a tab
|
|
@@ -1099,6 +1145,11 @@ export class HerdrDriver {
|
|
|
1099
1145
|
async read(slot, lines) {
|
|
1100
1146
|
const pane = await this.paneId(slot);
|
|
1101
1147
|
const r = await this.herdr(`pane read ${shq(pane)} --source recent-unwrapped --lines ${lines}`, slot.cwd);
|
|
1148
|
+
// OBS-1203 review: an adopted pane's read decides a resume harvest; a failed read proves neither a
|
|
1149
|
+
// trailer nor its absence, so it throws (the caller's decline) rather than read as an empty pane.
|
|
1150
|
+
// ponytail: other slots keep the historical empty-on-failure read; widen once seed/consult callers catch.
|
|
1151
|
+
if (r.code !== 0 && this.adoptedSlots.has(slot))
|
|
1152
|
+
throw new Error(`herdr pane read ${pane} failed (exit ${r.code}): ${r.stderr || r.stdout}`);
|
|
1102
1153
|
return r.stdout;
|
|
1103
1154
|
}
|
|
1104
1155
|
// v1.22 T5 / OBS-19: raw keystroke into the pane TUI (trust-dialog auto-answer). Resolves the pane
|
package/dist/drivers/orca.d.ts
CHANGED
|
@@ -274,7 +274,15 @@ export declare class OrcaDriver implements ExecutorDriver {
|
|
|
274
274
|
*/
|
|
275
275
|
private screenBehindBlindStream;
|
|
276
276
|
/** A rendered-frame liveness read. `--screen` and `--cursor` are mutually exclusive in Orca. */
|
|
277
|
-
private
|
|
277
|
+
private screenFrame;
|
|
278
|
+
/**
|
|
279
|
+
* OBS-1205: the public rendered-screen read a slot carries (Slot.readScreen). Kimi draws its trust
|
|
280
|
+
* modal with cursor addressing, so the stream holds the box rules and no `Trust` text while the
|
|
281
|
+
* painted frame shows `❯ Trust this folder`. Same ownership discipline as every read — bound
|
|
282
|
+
* runtime, addressed handle, running status — and a frame the runtime could not render is
|
|
283
|
+
* unreadable, never an empty screen a caller could read as "no modal".
|
|
284
|
+
*/
|
|
285
|
+
readScreen(slot: Slot): Promise<string>;
|
|
278
286
|
/** A single UNPAGED tail read — exactly what the caller asked for and nothing more. Markers split
|
|
279
287
|
* across cursor pages are not reassembled here; that is waitOutput's job. */
|
|
280
288
|
read(slot: Slot, lines: number): Promise<string>;
|
package/dist/drivers/orca.js
CHANGED
|
@@ -719,7 +719,8 @@ export class OrcaDriver {
|
|
|
719
719
|
this.pendingProjects.delete(owned.taskId);
|
|
720
720
|
}
|
|
721
721
|
}
|
|
722
|
-
|
|
722
|
+
const slot = { id, name: title, cwd: worktree, group: opts?.group, readScreen: () => this.readScreen(slot) };
|
|
723
|
+
return slot;
|
|
723
724
|
}
|
|
724
725
|
/**
|
|
725
726
|
* OBS-1109: bind THIS instance to a terminal an earlier daemon created for an OWNED slot, so a
|
|
@@ -1175,14 +1176,14 @@ export class OrcaDriver {
|
|
|
1175
1176
|
throw new OrcaUnavailableError(family, `exited-shaped stream page: show answered by runtime ${show.runtimeId}, not the bound ${st.runtimeId}`, show.raw, "exited");
|
|
1176
1177
|
}
|
|
1177
1178
|
this.liveShowTerm(family, st, show);
|
|
1178
|
-
const screen = await this.
|
|
1179
|
+
const screen = await this.screenFrame(st);
|
|
1179
1180
|
if (screen.source !== "screen") {
|
|
1180
1181
|
throw new OrcaUnavailableError(family, `exited-shaped stream page and no rendered screen for ${st.handle}`, blind.raw, "exited");
|
|
1181
1182
|
}
|
|
1182
1183
|
return screen.term;
|
|
1183
1184
|
}
|
|
1184
1185
|
/** A rendered-frame liveness read. `--screen` and `--cursor` are mutually exclusive in Orca. */
|
|
1185
|
-
async
|
|
1186
|
+
async screenFrame(st) {
|
|
1186
1187
|
const env = await this.terminalOp("status", st, (h) => this.call("read", [
|
|
1187
1188
|
"terminal", "read", "--terminal", h, "--screen",
|
|
1188
1189
|
], this.cliCwd(st)));
|
|
@@ -1193,6 +1194,20 @@ export class OrcaDriver {
|
|
|
1193
1194
|
}
|
|
1194
1195
|
return { term, source };
|
|
1195
1196
|
}
|
|
1197
|
+
/**
|
|
1198
|
+
* OBS-1205: the public rendered-screen read a slot carries (Slot.readScreen). Kimi draws its trust
|
|
1199
|
+
* modal with cursor addressing, so the stream holds the box rules and no `Trust` text while the
|
|
1200
|
+
* painted frame shows `❯ Trust this folder`. Same ownership discipline as every read — bound
|
|
1201
|
+
* runtime, addressed handle, running status — and a frame the runtime could not render is
|
|
1202
|
+
* unreadable, never an empty screen a caller could read as "no modal".
|
|
1203
|
+
*/
|
|
1204
|
+
async readScreen(slot) {
|
|
1205
|
+
const st = this.state(slot);
|
|
1206
|
+
const { term, source } = await this.screenFrame(st);
|
|
1207
|
+
if (source !== "screen")
|
|
1208
|
+
throw new OrcaUnavailableError("read", `no rendered screen for ${st.handle}`, "");
|
|
1209
|
+
return this.tailText("read", term, "");
|
|
1210
|
+
}
|
|
1196
1211
|
/** A single UNPAGED tail read — exactly what the caller asked for and nothing more. Markers split
|
|
1197
1212
|
* across cursor pages are not reassembled here; that is waitOutput's job. */
|
|
1198
1213
|
async read(slot, lines) {
|
|
@@ -1269,7 +1284,7 @@ export class OrcaDriver {
|
|
|
1269
1284
|
// reporting even when the show record alone would still look connected (show carries no
|
|
1270
1285
|
// status field of its own, so a terminal can report "unknown"/"exited" on read while its
|
|
1271
1286
|
// show row still says connected — the read leg is the only place that catches that).
|
|
1272
|
-
const screen = await this.
|
|
1287
|
+
const screen = await this.screenFrame(st);
|
|
1273
1288
|
if (`${st.runtimeId}:${st.handle}:${st.recoveries}` !== gen) {
|
|
1274
1289
|
continue;
|
|
1275
1290
|
}
|
|
@@ -1407,7 +1422,7 @@ export class OrcaDriver {
|
|
|
1407
1422
|
async sendKey(slot, key) {
|
|
1408
1423
|
const st = this.state(slot);
|
|
1409
1424
|
this.assertAvailable("send", st);
|
|
1410
|
-
if (key === "enter") {
|
|
1425
|
+
if (key === "enter" || key === "Enter") { // "Enter": every adapter's declared trust key (OBS-1205)
|
|
1411
1426
|
await this.sendText(st, "");
|
|
1412
1427
|
return;
|
|
1413
1428
|
}
|
|
@@ -1440,7 +1455,7 @@ export class OrcaDriver {
|
|
|
1440
1455
|
return true;
|
|
1441
1456
|
const deadline = this.time.now() + NUDGE_ECHO_TIMEOUT_MS;
|
|
1442
1457
|
for (;;) {
|
|
1443
|
-
const screen = await this.
|
|
1458
|
+
const screen = await this.screenFrame(st);
|
|
1444
1459
|
// ponytail: "composer emptied" is "the text is no longer on the frame" — a TUI that keeps the
|
|
1445
1460
|
// submitted turn on screen reads as undelivered until the timeout; the receipt stage is primary.
|
|
1446
1461
|
if (screen.source === "screen") {
|
|
@@ -1853,7 +1868,7 @@ export class OrcaDriver {
|
|
|
1853
1868
|
const st = this.slots.get(slot.id);
|
|
1854
1869
|
if (!st)
|
|
1855
1870
|
return;
|
|
1856
|
-
if (st.handle) {
|
|
1871
|
+
if (st.handle && !st.swept) {
|
|
1857
1872
|
await this.closeTerminal(st);
|
|
1858
1873
|
}
|
|
1859
1874
|
this.slots.delete(slot.id);
|
|
@@ -2022,6 +2037,13 @@ export class OrcaDriver {
|
|
|
2022
2037
|
recoveries: 0,
|
|
2023
2038
|
recovering: false,
|
|
2024
2039
|
});
|
|
2040
|
+
// A kept slot whose attempt the journal fold already retired (a seed readiness deadline
|
|
2041
|
+
// parks one) can still hold this exact terminal; its own close() must not re-address a
|
|
2042
|
+
// handle this driver just closed, or the run-end sweep latches on the vanished tab.
|
|
2043
|
+
for (const st of this.slots.values()) {
|
|
2044
|
+
if (st.handle === c.paneId && st.runtimeId === env.runtimeId)
|
|
2045
|
+
st.swept = true;
|
|
2046
|
+
}
|
|
2025
2047
|
}
|
|
2026
2048
|
catch { /* vanished, stale, or no longer provably ours — never a blind retry */ }
|
|
2027
2049
|
}
|
package/dist/drivers/types.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export interface Slot {
|
|
|
5
5
|
cwd: string;
|
|
6
6
|
tabId?: string;
|
|
7
7
|
group?: string;
|
|
8
|
+
readScreen?: () => Promise<string>;
|
|
8
9
|
}
|
|
9
10
|
export type NotifyTier = "routine" | "attention" | "decision";
|
|
10
11
|
export interface NotifyOpts {
|
|
@@ -92,6 +93,7 @@ export interface ExecutorDriver {
|
|
|
92
93
|
focus?(target: FocusTarget): Promise<FocusResult>;
|
|
93
94
|
describe?(slot: Slot): SlotPlacement | Promise<SlotPlacement> | undefined;
|
|
94
95
|
slot(cwd: string, name: string, opts?: SlotOpts): Promise<Slot>;
|
|
96
|
+
adopt?(slot: Slot): Promise<Slot>;
|
|
95
97
|
run(slot: Slot, cmd: string): Promise<void>;
|
|
96
98
|
waitOutput(slot: Slot, pattern: string, timeoutMs: number, opts?: {
|
|
97
99
|
regex?: boolean;
|
package/dist/drivers/types.js
CHANGED
|
@@ -63,8 +63,8 @@ export function canonicalizeLegacyName(name, runId) {
|
|
|
63
63
|
return { role: "worker", taskId: w[1], attempt: Number(w[2]), runId: `run-${w[3]}` };
|
|
64
64
|
const g = GATE_ROLE_RE.exec(name);
|
|
65
65
|
if (g) {
|
|
66
|
-
const
|
|
67
|
-
return { role: g[1], taskId:
|
|
66
|
+
const hop = /-r([1-9])$/.exec(g[2]);
|
|
67
|
+
return { role: g[1], taskId: hop ? g[2].slice(0, -hop[0].length) : g[2], attempt: hop ? Number(hop[1]) : 0, runId };
|
|
68
68
|
}
|
|
69
69
|
if (NARRATOR_RE.test(name))
|
|
70
70
|
return { role: "watch", taskId: "run", attempt: 0, runId };
|
|
@@ -55,6 +55,13 @@ export type AcceptanceCorpusAuditResult = {
|
|
|
55
55
|
};
|
|
56
56
|
};
|
|
57
57
|
export declare function auditAcceptanceCorpus(corpusRoot: string, listedTests: readonly VitestListedTest[]): AcceptanceCorpusAuditResult[];
|
|
58
|
+
/** OBS-1151: one criterion of a parsed judge verdict, as the contradiction check compares it. */
|
|
59
|
+
export interface JudgedCriterion {
|
|
60
|
+
id: string;
|
|
61
|
+
criterion: string;
|
|
62
|
+
met: boolean;
|
|
63
|
+
paths: string[];
|
|
64
|
+
}
|
|
58
65
|
export interface AcceptanceGateOpts {
|
|
59
66
|
testCmd?: string;
|
|
60
67
|
diffCap?: number;
|
package/dist/gates/acceptance.js
CHANGED
|
@@ -7,7 +7,7 @@ import { DEFAULT_DIFF_CAP } from "../config/config.js";
|
|
|
7
7
|
import { renderAcceptanceItem } from "../graph/schema.js";
|
|
8
8
|
import { sh } from "../run/git.js";
|
|
9
9
|
import { checkTaskDiffCaps, fetchTaskDiff, isProtectedEvidence, setAsideReceiptPath } from "./review.js";
|
|
10
|
-
import { appendAnchoredReview, COMPLETION_FAKING_CHECKLIST, extractVerdictJson, generateVerdictNonce, runLlm, verdictNonceLine } from "./llm.js";
|
|
10
|
+
import { appendAnchoredReview, COMPLETION_FAKING_CHECKLIST, extractVerdictJson, generateVerdictNonce, runLlm, SeatLaunchError, verdictNonceLine } from "./llm.js";
|
|
11
11
|
import { classifyVerdictCause } from "./verdict-cause.js";
|
|
12
12
|
import { reviewableLogicDiff } from "./artifact-manifest.js";
|
|
13
13
|
import { classifyFailureOutput } from "./baseline.js";
|
|
@@ -442,9 +442,19 @@ Each criteria[].evidence MUST be a structured citation {"path", "line"} whose "p
|
|
|
442
442
|
The top-level comments array is optional. Use it only for actionable line-anchored feedback.
|
|
443
443
|
`;
|
|
444
444
|
// OBS-1182: an unset seat passes no effort argument at all — the CLI default, and the call shape callers pin.
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
445
|
+
let raw;
|
|
446
|
+
try {
|
|
447
|
+
raw = await (judge.effort
|
|
448
|
+
? runLlm(judge.adapter, judge.model, prompt, worktree, via, JUDGE_TIMEOUT_MS, judge.effort)
|
|
449
|
+
: runLlm(judge.adapter, judge.model, prompt, worktree, via, JUDGE_TIMEOUT_MS));
|
|
450
|
+
}
|
|
451
|
+
catch (error) {
|
|
452
|
+
if (!(error instanceof SeatLaunchError))
|
|
453
|
+
throw error;
|
|
454
|
+
// OBS-1168(b): no seat, no verdict — run-gates re-routes the judge or parks infra; never a worker charge.
|
|
455
|
+
return { gate: "acceptance", pass: false, details: `${warn}${detBlock}judge dispatch failed — ${error.message} — failing closed`,
|
|
456
|
+
meta: { unparseable: true, cause: "seat-launch-failed", judge: channelKey({ adapter: judge.adapter.id, model: judge.model }) } };
|
|
457
|
+
}
|
|
448
458
|
const extracted = extractVerdictJson(raw, nonce);
|
|
449
459
|
if (!extracted) {
|
|
450
460
|
const cause = classifyVerdictCause(raw, nonce, "pass");
|
|
@@ -476,5 +486,17 @@ The top-level comments array is optional. Use it only for actionable line-anchor
|
|
|
476
486
|
lines.push("judge verdict pass=false");
|
|
477
487
|
lines.push(...inconsistencies);
|
|
478
488
|
const prose = warn + detBlock + (lines.join("\n") || "judge passed");
|
|
479
|
-
|
|
489
|
+
// OBS-1151: each criterion's canonical text, ruling and the paths its evidence cites. A legacy quote
|
|
490
|
+
// cites no path, so its criterion has no comparable subject (missing evidence is unknown). An
|
|
491
|
+
// inconsistent verdict (a duplicate row ruling both ways, a missing id) carries NO judgment: it is not
|
|
492
|
+
// a comparable subject, and as an adjudicator it can confirm nothing — run-gates parks it infra.
|
|
493
|
+
const judgment = inconsistencies.length ? undefined : v.criteria.flatMap((row) => {
|
|
494
|
+
const index = expectedIds.indexOf(row.criterion);
|
|
495
|
+
if (index < 0)
|
|
496
|
+
return [];
|
|
497
|
+
return [{ id: row.criterion, criterion: renderAcceptanceItem(judgeItems[index]), met: row.met,
|
|
498
|
+
paths: typeof row.evidence === "string" ? [] : [row.evidence.path] }];
|
|
499
|
+
});
|
|
500
|
+
return { gate: "acceptance", pass, details: appendAnchoredReview(prose, extracted),
|
|
501
|
+
meta: { judge: channelKey({ adapter: judge.adapter.id, model: judge.model }), ...(judgment ? { judgment } : {}) } };
|
|
480
502
|
}
|
package/dist/gates/baseline.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type EvidenceArtifact, type GateEvidenceReceipt, type ShellReceipt } from "../run/protocol.js";
|
|
2
|
-
import type
|
|
2
|
+
import { type TickmarkrConfig } from "../config/config.js";
|
|
3
3
|
import type { AcceptanceItem } from "../graph/schema.js";
|
|
4
4
|
import { type RunCapacity, type ShellOptions, type ShResult } from "../run/git.js";
|
|
5
5
|
import type { GateResult } from "./types.js";
|
|
@@ -31,8 +31,21 @@ export interface GateEvidenceOptions {
|
|
|
31
31
|
write?: (path: string, bytes: Buffer) => void;
|
|
32
32
|
}
|
|
33
33
|
export declare const EVIDENCE_TAIL_BYTES: number;
|
|
34
|
+
/** The default per-run quota; `gates.evidenceQuotaBytes` in config overrides it (OBS-1140). */
|
|
34
35
|
export declare const EVIDENCE_RUN_QUOTA_BYTES: number;
|
|
36
|
+
export type RedactionCounts = NonNullable<GateEvidenceReceipt["redaction"]["counts"]>;
|
|
37
|
+
/**
|
|
38
|
+
* OBS-1139: every category scans the ORIGINAL text on its own; overlapping spans then merge into one
|
|
39
|
+
* span counted once under its highest-priority member — token, assignment, secret environment, benign
|
|
40
|
+
* HOME/TMPDIR — so precedence never depends on which match starts first. Counts only: never a value.
|
|
41
|
+
*/
|
|
42
|
+
export declare function classifyGateOutput(text: string, env: NodeJS.ProcessEnv): {
|
|
43
|
+
text: string;
|
|
44
|
+
counts: RedactionCounts;
|
|
45
|
+
};
|
|
35
46
|
export declare function redactGateOutput(text: string, env: NodeJS.ProcessEnv): string;
|
|
47
|
+
/** Material means bytes were withheld; a benign location substitution withholds nothing. */
|
|
48
|
+
export declare const redactionMaterial: (c: RedactionCounts) => boolean;
|
|
36
49
|
/** Resolve a snapshot reference against actual bytes: eviction never fabricates a live artifact. */
|
|
37
50
|
export declare function resolveEvidenceArtifact(root: string, ref: EvidenceArtifact): EvidenceArtifact;
|
|
38
51
|
export declare function beginGateEvidence(cwd: string, gate: string, command: string, opts?: GateEvidenceOptions, nonce?: string): {
|
|
@@ -55,6 +68,12 @@ export declare function beginGateEvidence(cwd: string, gate: string, command: st
|
|
|
55
68
|
availability: "available" | "not-started" | "capture-failed" | "killed" | "expired" | "missing";
|
|
56
69
|
redaction: {
|
|
57
70
|
material: boolean;
|
|
71
|
+
counts?: {
|
|
72
|
+
token: number;
|
|
73
|
+
assignment: number;
|
|
74
|
+
secretEnv: number;
|
|
75
|
+
benignEnv: number;
|
|
76
|
+
} | undefined;
|
|
58
77
|
};
|
|
59
78
|
stdout: {
|
|
60
79
|
path: string;
|
package/dist/gates/baseline.js
CHANGED
|
@@ -1,25 +1,81 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import { execFileSync } from "node:child_process";
|
|
3
|
-
import {
|
|
3
|
+
import { EVICTION_TOMBSTONE_LIMIT, EVICTION_TOMBSTONES_FILE, parseEvictionTombstones } from "../run/receipt-resolver.js";
|
|
4
|
+
import { existsSync, readFileSync, mkdirSync, readdirSync, renameSync, statSync, unlinkSync, writeFileSync, rmdirSync } from "node:fs";
|
|
4
5
|
import { availableParallelism, loadavg, tmpdir } from "node:os";
|
|
5
|
-
import { join } from "node:path";
|
|
6
|
+
import { basename, join } from "node:path";
|
|
7
|
+
import { DEFAULT_EVIDENCE_QUOTA_BYTES } from "../config/config.js";
|
|
6
8
|
import { dependencyLinkRefusal, DEFAULT_SHELL_TIMEOUT_MS, describeCapacity, sameCapacity, sh } from "../run/git.js";
|
|
7
9
|
import { executionSignal } from "../run/execution-budget.js";
|
|
8
10
|
import { isVitestTestCommand, manifestFileCount } from "./test-manifest.js";
|
|
9
11
|
export const EVIDENCE_TAIL_BYTES = 16 * 1024;
|
|
10
|
-
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
/** The default per-run quota; `gates.evidenceQuotaBytes` in config overrides it (OBS-1140). */
|
|
13
|
+
export const EVIDENCE_RUN_QUOTA_BYTES = DEFAULT_EVIDENCE_QUOTA_BYTES;
|
|
14
|
+
/** Benign environment locations substituted by name; every other redaction is material. */
|
|
15
|
+
const BENIGN_ENV_KEYS = ["HOME", "TMPDIR"];
|
|
16
|
+
const REDACTION_CATEGORIES = ["token", "assignment", "secretEnv", "benignEnv"];
|
|
17
|
+
/**
|
|
18
|
+
* OBS-1139: every category scans the ORIGINAL text on its own; overlapping spans then merge into one
|
|
19
|
+
* span counted once under its highest-priority member — token, assignment, secret environment, benign
|
|
20
|
+
* HOME/TMPDIR — so precedence never depends on which match starts first. Counts only: never a value.
|
|
21
|
+
*/
|
|
22
|
+
export function classifyGateOutput(text, env) {
|
|
23
|
+
// HOME/TMPDIR substitute by name at any length; "/" alone would rewrite every path separator.
|
|
24
|
+
const benign = BENIGN_ENV_KEYS.flatMap(key => { const value = env[key]; return value && value.length > 1 ? [{ value, label: `$${key}` }] : []; });
|
|
14
25
|
// Short operational values (0, true, vi, test) occur throughout ordinary diagnostics.
|
|
15
|
-
// Only secret-named keys justify redacting short values.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
.
|
|
19
|
-
|
|
26
|
+
// Only secret-named keys justify redacting short values. A secret-named value that happens to equal
|
|
27
|
+
// HOME/TMPDIR stays a secret candidate: precedence decides the span, never candidate pruning.
|
|
28
|
+
const secrets = [...new Set(Object.entries(env)
|
|
29
|
+
.filter(([key, value]) => !!value && !BENIGN_ENV_KEYS.includes(key)
|
|
30
|
+
&& (value.length >= 8 || /KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|AUTH/i.test(key)))
|
|
31
|
+
.map(([, value]) => value))];
|
|
32
|
+
const literal = (v) => v.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
20
33
|
const tokens = String.raw `\b(?:sk-[A-Za-z0-9_-]{12,}|gh[pousr]_[A-Za-z0-9_]{16,}|github_pat_[A-Za-z0-9_]{16,}|AKIA[A-Z0-9]{16}|eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+)\b`;
|
|
21
34
|
const assignments = String.raw `(?:authorization\s*:\s*bearer|(?:api[_-]?key|token|password|secret)\s*[:=])\s*[^\s"']+`;
|
|
22
|
-
|
|
35
|
+
// Every value scans the original text on its own: an alternation would consume the first match and
|
|
36
|
+
// skip a longer overlapping value's tail, so precedence is resolved on the collected spans instead.
|
|
37
|
+
const scan = (source, rank, label) => [...text.matchAll(new RegExp(source, "gi"))].map(m => ({ start: m.index, end: m.index + m[0].length, rank, label }));
|
|
38
|
+
const spans = [
|
|
39
|
+
...scan(tokens, 0), ...scan(assignments, 1),
|
|
40
|
+
...secrets.flatMap(v => scan(literal(v), 2)),
|
|
41
|
+
...benign.flatMap(b => scan(literal(b.value), 3, b.label)),
|
|
42
|
+
].sort((a, b) => a.start - b.start || b.end - a.end);
|
|
43
|
+
const merged = [];
|
|
44
|
+
for (const span of spans) {
|
|
45
|
+
const last = merged.at(-1);
|
|
46
|
+
if (!last || span.start >= last.end) {
|
|
47
|
+
merged.push({ ...span });
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
// C-11 (D-673): a benign value nested in (or identical to) another is one location, named by the containing
|
|
51
|
+
// span, which sorts first. Only a PARTIAL overlap of two different benign values is ambiguous: withhold it.
|
|
52
|
+
if (last.rank === 3 && span.rank === 3 && last.label !== span.label && span.end > last.end)
|
|
53
|
+
last.rank = 2;
|
|
54
|
+
else
|
|
55
|
+
last.rank = Math.min(last.rank, span.rank);
|
|
56
|
+
last.end = Math.max(last.end, span.end);
|
|
57
|
+
}
|
|
58
|
+
const counts = { token: 0, assignment: 0, secretEnv: 0, benignEnv: 0 };
|
|
59
|
+
let out = "", at = 0;
|
|
60
|
+
for (const { start, end, rank, label } of merged) {
|
|
61
|
+
counts[REDACTION_CATEGORIES[rank]]++;
|
|
62
|
+
out += text.slice(at, start) + (rank === 3 ? label : "[REDACTED]");
|
|
63
|
+
at = end;
|
|
64
|
+
}
|
|
65
|
+
return { text: out + text.slice(at), counts };
|
|
66
|
+
}
|
|
67
|
+
export function redactGateOutput(text, env) {
|
|
68
|
+
return classifyGateOutput(text, env).text;
|
|
69
|
+
}
|
|
70
|
+
/** Material means bytes were withheld; a benign location substitution withholds nothing. */
|
|
71
|
+
export const redactionMaterial = (c) => c.token + c.assignment + c.secretEnv > 0;
|
|
72
|
+
function readEvictionTombstones(root) {
|
|
73
|
+
try {
|
|
74
|
+
return parseEvictionTombstones(readFileSync(join(root, EVICTION_TOMBSTONES_FILE)));
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
23
79
|
}
|
|
24
80
|
/** Resolve a snapshot reference against actual bytes: eviction never fabricates a live artifact. */
|
|
25
81
|
export function resolveEvidenceArtifact(root, ref) {
|
|
@@ -75,7 +131,12 @@ export function beginGateEvidence(cwd, gate, command, opts = {}, nonce) {
|
|
|
75
131
|
exitCode: observed?.exitCode ?? null, signal: observed?.signal ?? null,
|
|
76
132
|
timedOut: observed ? observed.outcome === "timed-out" : null,
|
|
77
133
|
};
|
|
78
|
-
const
|
|
134
|
+
const classified = [classifyGateOutput(stdout, env), classifyGateOutput(stderr, env)];
|
|
135
|
+
const clean = classified.map(c => c.text);
|
|
136
|
+
const counts = { token: 0, assignment: 0, secretEnv: 0, benignEnv: 0 };
|
|
137
|
+
for (const c of classified)
|
|
138
|
+
for (const k of Object.keys(counts))
|
|
139
|
+
counts[k] += c.counts[k];
|
|
79
140
|
const refs = clean.map((text, i) => {
|
|
80
141
|
const bytes = Buffer.from(text);
|
|
81
142
|
const tail = bytes.subarray(-EVIDENCE_TAIL_BYTES);
|
|
@@ -94,19 +155,38 @@ export function beginGateEvidence(cwd, gate, command, opts = {}, nonce) {
|
|
|
94
155
|
lock = join(dir, ".write-lock");
|
|
95
156
|
const quota = opts.quotaBytes !== undefined && Number.isFinite(opts.quotaBytes)
|
|
96
157
|
? Math.max(0, Math.floor(opts.quotaBytes)) : EVIDENCE_RUN_QUOTA_BYTES;
|
|
158
|
+
// An empty artifact costs no quota, so it is never evicted: every eviction frees bytes.
|
|
97
159
|
const files = readdirSync(dir).filter(f => /^[a-zA-Z0-9-]+-(stdout|stderr)\.log$/.test(f)).map(f => ({ path: join(dir, f), stat: statSync(join(dir, f)) }))
|
|
98
|
-
.sort((a, b) => a.stat.mtimeMs - b.stat.mtimeMs || a.path.localeCompare(b.path));
|
|
160
|
+
.filter(f => f.stat.size > 0).sort((a, b) => a.stat.mtimeMs - b.stat.mtimeMs || a.path.localeCompare(b.path));
|
|
99
161
|
let total = files.reduce((sum, f) => sum + f.stat.size, 0);
|
|
100
162
|
const incoming = refs.reduce((sum, ref) => sum + ref.retainedBytes, 0);
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
163
|
+
// OBS-1140: the quota binds what is already retained too, so a resumed run at zero quota keeps
|
|
164
|
+
// zero bytes. An invocation larger than the whole quota is expired at birth; older artifacts
|
|
165
|
+
// are evicted only as far as the quota itself demands, never for bytes that could not fit.
|
|
166
|
+
const fits = incoming <= quota;
|
|
167
|
+
const evicted = [];
|
|
168
|
+
while (files.length && total + (fits ? incoming : 0) > quota) {
|
|
104
169
|
const oldest = files.shift();
|
|
170
|
+
const bytes = readFileSync(oldest.path);
|
|
105
171
|
unlinkSync(oldest.path);
|
|
106
172
|
total -= oldest.stat.size;
|
|
173
|
+
evicted.push({ path: `gate-evidence/${basename(oldest.path)}`, sha256: createHash("sha256").update(bytes).digest("hex"), retainedBytes: bytes.length });
|
|
174
|
+
}
|
|
175
|
+
if (evicted.length) {
|
|
176
|
+
// Identity-bound (path + hash + length) and bounded: the newest 256 survive a restart.
|
|
177
|
+
const tombstones = join(root, EVICTION_TOMBSTONES_FILE);
|
|
178
|
+
writeFileSync(`${tombstones}.tmp`, JSON.stringify([...readEvictionTombstones(root), ...evicted].slice(-EVICTION_TOMBSTONE_LIMIT)));
|
|
179
|
+
renameSync(`${tombstones}.tmp`, tombstones);
|
|
107
180
|
}
|
|
108
|
-
|
|
109
|
-
|
|
181
|
+
if (!fits) {
|
|
182
|
+
availability = "expired";
|
|
183
|
+
for (const ref of refs)
|
|
184
|
+
ref.availability = "expired";
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
for (const [i, ref] of refs.entries()) {
|
|
188
|
+
(opts.write ?? writeFileSync)(join(root, ref.path), Buffer.from(clean[i]).subarray(-EVIDENCE_TAIL_BYTES));
|
|
189
|
+
}
|
|
110
190
|
}
|
|
111
191
|
}
|
|
112
192
|
catch {
|
|
@@ -131,7 +211,7 @@ export function beginGateEvidence(cwd, gate, command, opts = {}, nonce) {
|
|
|
131
211
|
}
|
|
132
212
|
return { invocationId, ...(nonce ? { nonce } : {}),
|
|
133
213
|
subject: { runId: opts.runId ?? "standalone", taskId: opts.taskId ?? null, attempt: opts.attempt ?? null, gate, subjectCommit },
|
|
134
|
-
termination, availability, redaction: { material:
|
|
214
|
+
termination, availability, redaction: { material: redactionMaterial(counts), counts }, stdout: refs[0], stderr: refs[1] };
|
|
135
215
|
},
|
|
136
216
|
};
|
|
137
217
|
}
|
package/dist/gates/cache.d.ts
CHANGED
|
@@ -9,8 +9,15 @@ export declare function lockfileHash(worktree: string): string;
|
|
|
9
9
|
export declare function canonicalJson(obj: unknown): string;
|
|
10
10
|
export declare function baselineIdentity(baseline?: Baseline): string;
|
|
11
11
|
export declare function getWorktreeTree(worktree: string): Promise<string>;
|
|
12
|
+
/** OBS-635: environment inputs a runner child reads that capacity and lifecycle do not already bind.
|
|
13
|
+
* ponytail: a named list, not the whole env (pids and terminal vars would defeat every reuse); extend
|
|
14
|
+
* it when another variable is shown to change what a runner executes. */
|
|
15
|
+
export declare const RUNNER_ENV_KEYS: readonly ["PATH", "NODE_OPTIONS", "NODE_PATH", "NODE_ENV", "TZ"];
|
|
16
|
+
export declare function runnerInputsHash(env?: NodeJS.ProcessEnv): string;
|
|
12
17
|
export interface GateEnvironmentInput {
|
|
13
18
|
nodeRuntime?: string;
|
|
19
|
+
/** the runner inputs hash; defaults to this process's (runnerInputsHash) */
|
|
20
|
+
runner?: string;
|
|
14
21
|
lockfile?: string;
|
|
15
22
|
worktree?: string;
|
|
16
23
|
capacity?: RunCapacity;
|
|
@@ -21,6 +28,7 @@ export interface GateEnvironmentInput {
|
|
|
21
28
|
}
|
|
22
29
|
export interface EnvironmentParts {
|
|
23
30
|
nodeRuntime: string;
|
|
31
|
+
runner: string;
|
|
24
32
|
lockfile: string;
|
|
25
33
|
capacity: RunCapacity;
|
|
26
34
|
selectedSet?: readonly string[];
|
package/dist/gates/cache.js
CHANGED
|
@@ -77,8 +77,17 @@ export async function getWorktreeTree(worktree) {
|
|
|
77
77
|
rmSync(scratch, { recursive: true, force: true });
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
|
+
/** OBS-635: environment inputs a runner child reads that capacity and lifecycle do not already bind.
|
|
81
|
+
* ponytail: a named list, not the whole env (pids and terminal vars would defeat every reuse); extend
|
|
82
|
+
* it when another variable is shown to change what a runner executes. */
|
|
83
|
+
export const RUNNER_ENV_KEYS = ["PATH", "NODE_OPTIONS", "NODE_PATH", "NODE_ENV", "TZ"];
|
|
84
|
+
export function runnerInputsHash(env = process.env) {
|
|
85
|
+
return createHash("sha256").update(canonicalJson(Object.fromEntries(RUNNER_ENV_KEYS.map((k) => [k, env[k] ?? null]))))
|
|
86
|
+
.digest("hex").slice(0, 16);
|
|
87
|
+
}
|
|
80
88
|
export function environmentFingerprint(env) {
|
|
81
89
|
const nodeRuntime = env.nodeRuntime ?? process.version;
|
|
90
|
+
const runner = env.runner ?? runnerInputsHash();
|
|
82
91
|
const lockfile = env.lockfile ?? (env.worktree ? lockfileHash(env.worktree) : "no-lockfile");
|
|
83
92
|
const cap = env.capacity ?? resolvedCapacity();
|
|
84
93
|
const capacity = { forkCap: cap.forkCap, cores: cap.cores };
|
|
@@ -97,6 +106,7 @@ export function environmentFingerprint(env) {
|
|
|
97
106
|
// explicit `false` and an npmrc `false` are the same policy for the child that ran.
|
|
98
107
|
const payload = canonicalJson({
|
|
99
108
|
nodeRuntime,
|
|
109
|
+
runner,
|
|
100
110
|
lockfile,
|
|
101
111
|
capacity,
|
|
102
112
|
resolution,
|
|
@@ -107,7 +117,7 @@ export function environmentFingerprint(env) {
|
|
|
107
117
|
const fingerprint = createHash("sha256").update(payload).digest("hex").slice(0, 16);
|
|
108
118
|
return {
|
|
109
119
|
fingerprint,
|
|
110
|
-
parts: { nodeRuntime, lockfile, capacity, selectedSet, verification },
|
|
120
|
+
parts: { nodeRuntime, runner, lockfile, capacity, selectedSet, verification },
|
|
111
121
|
};
|
|
112
122
|
}
|
|
113
123
|
export async function computeVerificationIdentity(params) {
|
|
@@ -151,7 +161,7 @@ export function verificationIdentityKey(id) {
|
|
|
151
161
|
export function formatReusedDetails(originalDetails, id) {
|
|
152
162
|
const unadorned = originalDetails.replace(/^reused verdict \(identity: [^)]+\):\s*/, "");
|
|
153
163
|
const envDesc = id.envParts
|
|
154
|
-
? ` [node=${id.envParts.nodeRuntime}, lockfile=${id.envParts.lockfile}, capacity=${describeCapacity(id.envParts.capacity)}${id.envParts.selectedSet ? `, selected=${id.envParts.selectedSet.join(",")}` : ""}, protocol=${id.envParts.verification.protocol}, lifecycle=${id.envParts.verification.lifecycle} (${id.envParts.verification.source})]`
|
|
164
|
+
? ` [node=${id.envParts.nodeRuntime}, runner=${id.envParts.runner}, lockfile=${id.envParts.lockfile}, capacity=${describeCapacity(id.envParts.capacity)}${id.envParts.selectedSet ? `, selected=${id.envParts.selectedSet.join(",")}` : ""}, protocol=${id.envParts.verification.protocol}, lifecycle=${id.envParts.verification.lifecycle} (${id.envParts.verification.source})]`
|
|
155
165
|
: "";
|
|
156
166
|
const gate = id.gate ?? "gate";
|
|
157
167
|
const prefix = `reused ${id.scope === "tip" ? "tip " : ""}verdict (identity: gate=${gate} tree=${id.tree}${id.worktree ? ` worktree=${id.worktree}` : ""} command=${id.command} baseline=${id.baseline} env=${id.environment}${envDesc})`;
|
package/dist/gates/llm.d.ts
CHANGED
|
@@ -69,6 +69,12 @@ export interface LlmRunResult {
|
|
|
69
69
|
}
|
|
70
70
|
export declare const PROMPT_GLYPHS: readonly ["➜", "❯", "$", "%", ">>", ">"];
|
|
71
71
|
export declare function reviewSeatOutput(raw: string, nonce: string, adapterBannerRows?: readonly string[]): string;
|
|
72
|
+
/** OBS-1168(b): a judge/review seat whose pane could not be created or launched. Typed so the gates can
|
|
73
|
+
* contain it as seat recovery (another seat) or an infra park — never as a worker failure. */
|
|
74
|
+
export declare class SeatLaunchError extends Error {
|
|
75
|
+
readonly seat: string;
|
|
76
|
+
constructor(seat: string, cause: unknown);
|
|
77
|
+
}
|
|
72
78
|
export declare const REVIEW_FIRST_LIVENESS_MS = 30000;
|
|
73
79
|
export declare const REVIEW_SILENT_BYTE_FLOOR = 64;
|
|
74
80
|
export declare function runHeadless(adapter: WorkerAdapter, model: string, prompt: string, cwd: string, timeoutMs?: number, effort?: Effort): Promise<string>;
|