tickmarkr 2.6.0 → 2.6.2
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 +14 -3
- package/dist/adapters/catalog-remote.js +89 -47
- package/dist/adapters/claude-code.js +9 -6
- package/dist/adapters/codex.js +7 -4
- package/dist/adapters/prompt.d.ts +3 -1
- package/dist/adapters/prompt.js +21 -3
- package/dist/adapters/registry.js +3 -3
- package/dist/adapters/types.d.ts +13 -4
- package/dist/adapters/types.js +16 -0
- package/dist/cli/commands/approve.d.ts +5 -1
- package/dist/cli/commands/approve.js +66 -23
- package/dist/cli/commands/compile.js +13 -3
- package/dist/cli/commands/doctor.d.ts +2 -0
- package/dist/cli/commands/doctor.js +11 -3
- package/dist/cli/commands/fleet.js +71 -8
- package/dist/cli/commands/plan.js +7 -3
- package/dist/cli/commands/report.js +18 -2
- package/dist/cli/commands/resume.js +4 -2
- package/dist/cli/commands/status.js +37 -22
- package/dist/cli/help.d.ts +4 -0
- package/dist/cli/help.js +11 -2
- package/dist/config/config.d.ts +20 -0
- package/dist/config/config.js +47 -8
- package/dist/config/fleet-overlay.d.ts +1 -0
- package/dist/config/fleet-overlay.js +56 -0
- package/dist/drivers/orca.d.ts +26 -1
- package/dist/drivers/orca.js +199 -61
- package/dist/eval/canary.d.ts +2 -1
- package/dist/eval/canary.js +2 -2
- package/dist/eval/dispatch.js +1 -0
- package/dist/gates/acceptance.d.ts +2 -1
- package/dist/gates/acceptance.js +7 -2
- package/dist/gates/baseline.d.ts +12 -1
- package/dist/gates/baseline.js +11 -4
- package/dist/gates/cache.d.ts +3 -1
- package/dist/gates/cache.js +10 -3
- package/dist/gates/llm.d.ts +5 -4
- package/dist/gates/llm.js +17 -14
- package/dist/gates/review.d.ts +8 -0
- package/dist/gates/review.js +47 -15
- package/dist/gates/run-gates.d.ts +6 -1
- package/dist/gates/run-gates.js +34 -17
- package/dist/gates/test-manifest.d.ts +14 -0
- package/dist/gates/test-manifest.js +33 -6
- package/dist/graph/graph.d.ts +6 -2
- package/dist/graph/graph.js +15 -4
- package/dist/graph/schema.d.ts +2 -0
- package/dist/graph/schema.js +2 -0
- package/dist/plan/scope.js +2 -2
- package/dist/route/preference.d.ts +20 -2
- package/dist/route/preference.js +48 -13
- package/dist/route/role-pick.d.ts +16 -0
- package/dist/route/role-pick.js +15 -0
- package/dist/route/router.d.ts +14 -0
- package/dist/route/router.js +39 -16
- package/dist/run/consult.d.ts +13 -1
- package/dist/run/consult.js +19 -14
- package/dist/run/daemon.d.ts +46 -2
- package/dist/run/daemon.js +964 -190
- package/dist/run/git.d.ts +44 -1
- package/dist/run/git.js +103 -5
- package/dist/run/host-health.d.ts +20 -0
- package/dist/run/host-health.js +64 -0
- package/dist/run/journal.d.ts +126 -3
- package/dist/run/journal.js +418 -36
- package/dist/run/merge.d.ts +3 -1
- package/dist/run/merge.js +3 -2
- package/dist/run/operator-state.d.ts +24 -2
- package/dist/run/operator-state.js +41 -5
- package/dist/run/operator-summary.d.ts +3 -0
- package/dist/run/operator-summary.js +3 -1
- package/dist/run/protocol.d.ts +31 -1
- package/dist/run/protocol.js +3 -1
- package/dist/run/stall.d.ts +38 -2
- package/dist/run/stall.js +276 -6
- package/dist/run/supervision.d.ts +7 -1
- package/dist/run/supervision.js +5 -2
- package/dist/tui/cockpit/board.d.ts +1 -1
- package/dist/tui/cockpit/board.js +30 -22
- package/dist/tui/cockpit/decision-actions.d.ts +8 -5
- package/dist/tui/cockpit/decision-actions.js +55 -32
- package/dist/tui/cockpit/derive.d.ts +2 -0
- package/dist/tui/cockpit/derive.js +17 -2
- package/dist/tui/cockpit/live-runtime.d.ts +10 -0
- package/dist/tui/cockpit/live-runtime.js +50 -3
- package/dist/tui/cockpit/live-store.d.ts +1 -0
- package/dist/tui/cockpit/live-store.js +31 -8
- package/dist/tui/cockpit/run-cockpit.d.ts +3 -0
- package/dist/tui/cockpit/run-cockpit.js +28 -2
- package/dist/tui/cockpit/run-view.d.ts +11 -7
- package/dist/tui/cockpit/run-view.js +69 -15
- package/dist/tui/cockpit/setup-cockpit.d.ts +4 -0
- package/dist/tui/cockpit/setup-cockpit.js +6 -3
- package/dist/tui/ink/fleet-app.d.ts +15 -3
- package/dist/tui/ink/fleet-app.js +91 -22
- package/package.json +2 -1
- package/schema/config.schema.json +818 -0
- package/skills/tickmarkr-loop/SKILL.md +8 -2
- package/skills/tickmarkr-overseer/SKILL.md +85 -6
- package/skills/tickmarkr-overseer/scripts/classify-vitest-log.sh +88 -0
- package/skills/tickmarkr-overseer/scripts/context-statusline.sh +81 -0
- package/skills/tickmarkr-overseer/scripts/grade-ci.sh +36 -34
- package/skills/tickmarkr-overseer/scripts/watch-journal.sh +6 -4
package/dist/graph/schema.d.ts
CHANGED
|
@@ -4,12 +4,14 @@ export declare const GRAPH_ROUTING_MODES: readonly ["partner-led", "risk-based",
|
|
|
4
4
|
export declare const STATUSES: readonly ["pending", "running", "gated", "failed", "done", "human"];
|
|
5
5
|
export declare const GATE_NAMES: readonly ["build", "test", "lint", "evidence", "scope", "acceptance", "review"];
|
|
6
6
|
export declare const TIERS: readonly ["cheap", "mid", "frontier"];
|
|
7
|
+
export declare const EFFORTS: readonly ["low", "medium", "high"];
|
|
7
8
|
export declare const SPEC_SOURCES: readonly ["speckit", "gsd", "prd", "native"];
|
|
8
9
|
export declare const ORACLES: readonly ["command", "test", "judge"];
|
|
9
10
|
export type Shape = (typeof SHAPES)[number];
|
|
10
11
|
export type TaskStatus = (typeof STATUSES)[number];
|
|
11
12
|
export type GateName = (typeof GATE_NAMES)[number];
|
|
12
13
|
export type Oracle = (typeof ORACLES)[number];
|
|
14
|
+
export type Effort = (typeof EFFORTS)[number];
|
|
13
15
|
export type SpecSource = (typeof SPEC_SOURCES)[number];
|
|
14
16
|
export declare const AcceptanceItemSchema: z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
15
17
|
oracle: z.ZodLiteral<"command">;
|
package/dist/graph/schema.js
CHANGED
|
@@ -7,6 +7,8 @@ export const STATUSES = ["pending", "running", "gated", "failed", "done", "human
|
|
|
7
7
|
export const GATE_NAMES = ["build", "test", "lint", "evidence", "scope", "acceptance", "review"];
|
|
8
8
|
const MANDATORY_GATES = ["build", "test", "lint", "evidence", "scope"];
|
|
9
9
|
export const TIERS = ["cheap", "mid", "frontier"];
|
|
10
|
+
// OBS-1182: launch effort — channel metadata beside tier, never part of model identity or channelKey.
|
|
11
|
+
export const EFFORTS = ["low", "medium", "high"];
|
|
10
12
|
export const SPEC_SOURCES = ["speckit", "gsd", "prd", "native"];
|
|
11
13
|
// v1.19 acceptance oracles: command (exit code), test (named test), judge (LLM, free-text rubric).
|
|
12
14
|
// A plain string is the read-old/write-new compat form — semantically a judge oracle (spec §2).
|
package/dist/plan/scope.js
CHANGED
|
@@ -169,7 +169,7 @@ function bindCandidate(candidate, channels) {
|
|
|
169
169
|
`(doctor found: ${channels.map((ch) => `${ch.adapter}:${ch.model}`).join(", ") || "(nothing)"}) — ` +
|
|
170
170
|
"re-run scope --preview and confirm again");
|
|
171
171
|
}
|
|
172
|
-
return { adapter: c.adapter, model: c.model, channel: c.channel, tier: c.tier };
|
|
172
|
+
return { adapter: c.adapter, model: c.model, channel: c.channel, tier: c.tier, ...(c.effort ? { effort: c.effort } : {}) };
|
|
173
173
|
}
|
|
174
174
|
// Adapter-level probes establish the current installation/auth state, but shipped adapters do not
|
|
175
175
|
// return per-model verdicts from probe(). Preserve cached verdicts only where that fresh snapshot is
|
|
@@ -202,7 +202,7 @@ export async function scopeIntent(intentFile, repoRoot, options) {
|
|
|
202
202
|
driver, name: `scope-${name}-${attempts}-${adapter.id}`, label: "SCOPE",
|
|
203
203
|
keep: options.cfg.visibility.keepPanes === "forever",
|
|
204
204
|
} : undefined;
|
|
205
|
-
const draft = extractDraft(await runLlm(adapter, assignment.model, prompt, repoRoot, via));
|
|
205
|
+
const draft = extractDraft(await runLlm(adapter, assignment.model, prompt, repoRoot, via, undefined, assignment.effort));
|
|
206
206
|
let tasks;
|
|
207
207
|
try {
|
|
208
208
|
tasks = validateDraft(draft);
|
|
@@ -11,6 +11,16 @@ export declare const routingModelProvider: typeof modelProvider;
|
|
|
11
11
|
export declare const modelRouteIdentity: (model: string, fallback?: string) => string;
|
|
12
12
|
export declare const channelRouteIdentity: (key: string, fallback?: string) => string;
|
|
13
13
|
export declare function routingEntrySeatLines(cfg: TickmarkrConfig): string[];
|
|
14
|
+
export declare const observedIdentity: (health: Record<string, AuthHealth> | null | undefined, adapter: string, model: string) => string | undefined;
|
|
15
|
+
export declare const observedSeat: (health: Record<string, AuthHealth> | null | undefined, adapter: string, model: string) => {
|
|
16
|
+
adapter: string;
|
|
17
|
+
model: string;
|
|
18
|
+
identity: string;
|
|
19
|
+
} | {
|
|
20
|
+
adapter: string;
|
|
21
|
+
model: string;
|
|
22
|
+
identity?: undefined;
|
|
23
|
+
};
|
|
14
24
|
export declare function excludedChannels(cfg: TickmarkrConfig, adapters: {
|
|
15
25
|
id: string;
|
|
16
26
|
}[] | string[], health: Record<string, AuthHealth>): {
|
|
@@ -59,6 +69,14 @@ export interface DenyPreferCollision {
|
|
|
59
69
|
detail: string;
|
|
60
70
|
disallowed: Disallowed;
|
|
61
71
|
}
|
|
62
|
-
export declare function preferEntryDenied(p: string, cfg: TickmarkrConfig): Disallowed | null;
|
|
63
|
-
export declare function denyPreferCollisions(cfg: TickmarkrConfig, shapes?: Iterable<string>): DenyPreferCollision[];
|
|
72
|
+
export declare function preferEntryDenied(p: string, cfg: TickmarkrConfig, health?: Record<string, AuthHealth> | null): Disallowed | null;
|
|
73
|
+
export declare function denyPreferCollisions(cfg: TickmarkrConfig, shapes?: Iterable<string>, health?: Record<string, AuthHealth> | null): DenyPreferCollision[];
|
|
74
|
+
export interface DeadPoolEntry {
|
|
75
|
+
shape: string;
|
|
76
|
+
entry: string;
|
|
77
|
+
disallowed: Disallowed;
|
|
78
|
+
admitted: string[];
|
|
79
|
+
}
|
|
80
|
+
export declare function deadPoolEntries(cfg: TickmarkrConfig, shapes?: Iterable<string>, health?: Record<string, AuthHealth> | null): DeadPoolEntry[];
|
|
81
|
+
export declare function deadPoolEntryLine({ shape, entry, disallowed, admitted }: DeadPoolEntry): string;
|
|
64
82
|
export declare function denyPreferCollisionLine({ kind, shape, detail, disallowed }: DenyPreferCollision): string;
|
package/dist/route/preference.js
CHANGED
|
@@ -47,6 +47,13 @@ export function routingEntrySeatLines(cfg) {
|
|
|
47
47
|
add("routing.deny.workers.models", cfg.routing.deny?.workers?.models, ["worker"]);
|
|
48
48
|
return lines;
|
|
49
49
|
}
|
|
50
|
+
// OBS-1143: the probed identity doctor cached for adapter:model — the one discoverChannels routes
|
|
51
|
+
// with. Absent ⇒ unknown, and the deny matcher stays conservative (alias-family match).
|
|
52
|
+
export const observedIdentity = (health, adapter, model) => health?.[adapter]?.modelAuth?.[model]?.identity ?? health?.[adapter]?.modelIdentities?.[model];
|
|
53
|
+
export const observedSeat = (health, adapter, model) => {
|
|
54
|
+
const identity = observedIdentity(health, adapter, model);
|
|
55
|
+
return identity ? { adapter, model, identity } : { adapter, model };
|
|
56
|
+
};
|
|
50
57
|
const adapterIds = (adapters) => typeof adapters[0] === "string" ? adapters : adapters.map((a) => a.id);
|
|
51
58
|
export function excludedChannels(cfg, adapters, health) {
|
|
52
59
|
const { allow, deny } = cfg.routing;
|
|
@@ -57,9 +64,7 @@ export function excludedChannels(cfg, adapters, health) {
|
|
|
57
64
|
if (!health[id]?.installed || !health[id]?.authed)
|
|
58
65
|
continue;
|
|
59
66
|
for (const c of channelsFromConfig(id, cfg)) {
|
|
60
|
-
const
|
|
61
|
-
const chan = identity ? { ...c, identity } : c;
|
|
62
|
-
const d = disallowedBy(chan, cfg.routing);
|
|
67
|
+
const d = disallowedBy(observedSeat(health, c.adapter, c.model), cfg.routing);
|
|
63
68
|
if (d)
|
|
64
69
|
out.push({ key: channelKey(c), d });
|
|
65
70
|
}
|
|
@@ -175,13 +180,20 @@ const disallowedFromPreferError = (msg) => {
|
|
|
175
180
|
const m = msg.match(/prefer entry .+ is disallowed by routing\.(deny|allow) \(([^)]+)\)/);
|
|
176
181
|
return m ? { by: m[1], entry: m[2] } : null;
|
|
177
182
|
};
|
|
178
|
-
|
|
183
|
+
// OBS-1143: `health` is doctor's CACHED verdict (never a probe); its identities ride the channels
|
|
184
|
+
// route() reads them from, so the preflight answers exactly as the router would.
|
|
185
|
+
export function preferEntryDenied(p, cfg, health) {
|
|
179
186
|
if (!cfg.routing.allow && !cfg.routing.deny)
|
|
180
187
|
return null;
|
|
181
188
|
const probe = structuredClone(cfg);
|
|
182
189
|
probe.routing.map = { ...probe.routing.map, [PREFLIGHT_SHAPE]: { prefer: [p] } };
|
|
190
|
+
const observed = Object.keys(cfg.tiers).flatMap((id) => channelsFromConfig(id, cfg))
|
|
191
|
+
.flatMap((c) => {
|
|
192
|
+
const identity = observedIdentity(health, c.adapter, c.model);
|
|
193
|
+
return identity ? [{ ...c, identity }] : [];
|
|
194
|
+
});
|
|
183
195
|
try {
|
|
184
|
-
route(preflightTask, probe,
|
|
196
|
+
route(preflightTask, probe, observed);
|
|
185
197
|
return null;
|
|
186
198
|
}
|
|
187
199
|
catch (e) {
|
|
@@ -194,7 +206,9 @@ export function preferEntryDenied(p, cfg) {
|
|
|
194
206
|
// route — resume hands it the loaded graph's shape set, so a collision on a shape no resumed task
|
|
195
207
|
// uses can no longer refuse the only crash-recovery path. Omitted ⇒ the whole routing map: doctor
|
|
196
208
|
// audits the config itself, which has no graph to be scoped by.
|
|
197
|
-
|
|
209
|
+
// OBS-1143: `health` (doctor's cached verdict) supplies each alias's observed identity; omitted or
|
|
210
|
+
// unrecorded ⇒ unknown, which stays conservative.
|
|
211
|
+
export function denyPreferCollisions(cfg, shapes, health) {
|
|
198
212
|
if (!cfg.routing.allow && !cfg.routing.deny)
|
|
199
213
|
return [];
|
|
200
214
|
const inGraph = shapes === undefined ? undefined : new Set(shapes);
|
|
@@ -203,7 +217,7 @@ export function denyPreferCollisions(cfg, shapes) {
|
|
|
203
217
|
if (inGraph && !inGraph.has(shape))
|
|
204
218
|
continue;
|
|
205
219
|
if (entry.pin) {
|
|
206
|
-
const d = disallowedBy(
|
|
220
|
+
const d = disallowedBy(observedSeat(health, entry.pin.via, entry.pin.model), cfg.routing);
|
|
207
221
|
if (d) {
|
|
208
222
|
out.push({
|
|
209
223
|
kind: "pin",
|
|
@@ -214,20 +228,17 @@ export function denyPreferCollisions(cfg, shapes) {
|
|
|
214
228
|
}
|
|
215
229
|
}
|
|
216
230
|
const prefer = entry.prefer ?? [];
|
|
217
|
-
if (prefer.length && prefer.every((p) => preferEntryDenied(p, cfg) !== null)) {
|
|
231
|
+
if (prefer.length && prefer.every((p) => preferEntryDenied(p, cfg, health) !== null)) {
|
|
218
232
|
out.push({
|
|
219
233
|
kind: "prefer",
|
|
220
234
|
shape,
|
|
221
235
|
detail: prefer.join(" > "),
|
|
222
|
-
disallowed: preferEntryDenied(prefer[0], cfg),
|
|
236
|
+
disallowed: preferEntryDenied(prefer[0], cfg, health),
|
|
223
237
|
});
|
|
224
238
|
}
|
|
225
239
|
const pool = entry.pool;
|
|
226
240
|
if (pool) {
|
|
227
|
-
const denied = pool.channels.map((p) =>
|
|
228
|
-
const i = p.indexOf(":");
|
|
229
|
-
return disallowedBy({ adapter: p.slice(0, i), model: p.slice(i + 1) }, cfg.routing);
|
|
230
|
-
});
|
|
241
|
+
const denied = pool.channels.map((p) => poolEntryDisallowed(p, cfg, health));
|
|
231
242
|
// only a FULLY dead pool collides — a partial deny still leaves live members to route
|
|
232
243
|
if (denied.every((d) => d !== null)) {
|
|
233
244
|
out.push({ kind: "pool", shape, detail: pool.channels.join(" > "), disallowed: denied[0] });
|
|
@@ -236,6 +247,30 @@ export function denyPreferCollisions(cfg, shapes) {
|
|
|
236
247
|
}
|
|
237
248
|
return out;
|
|
238
249
|
}
|
|
250
|
+
const poolEntryDisallowed = (p, cfg, health) => {
|
|
251
|
+
const i = p.indexOf(":");
|
|
252
|
+
return disallowedBy(observedSeat(health, p.slice(0, i), p.slice(i + 1)), cfg.routing);
|
|
253
|
+
};
|
|
254
|
+
// OBS-1144: every disallowed entry a pool carries, beside the admitted remainder the router routes
|
|
255
|
+
// instead — the router skips the entry; an empty remainder is the exhausted pool it refuses.
|
|
256
|
+
export function deadPoolEntries(cfg, shapes, health) {
|
|
257
|
+
if (!cfg.routing.allow && !cfg.routing.deny)
|
|
258
|
+
return [];
|
|
259
|
+
const inGraph = shapes === undefined ? undefined : new Set(shapes);
|
|
260
|
+
return Object.entries(cfg.routing.map).flatMap(([shape, entry]) => {
|
|
261
|
+
if (!entry.pool || (inGraph && !inGraph.has(shape)))
|
|
262
|
+
return [];
|
|
263
|
+
const judged = entry.pool.channels.map((p) => ({ p, d: poolEntryDisallowed(p, cfg, health) }));
|
|
264
|
+
const admitted = judged.filter(({ d }) => d === null).map(({ p }) => p);
|
|
265
|
+
return judged.flatMap(({ p, d }) => (d ? [{ shape, entry: p, disallowed: d, admitted }] : []));
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
export function deadPoolEntryLine({ shape, entry, disallowed, admitted }) {
|
|
269
|
+
const verdict = admitted.length
|
|
270
|
+
? `skipped — routes the admitted remainder ${admitted.join(" > ")}`
|
|
271
|
+
: "the pool is exhausted — no admitted entry remains, so the shape is unroutable";
|
|
272
|
+
return `routing.map.${shape}.pool entry ${entry} is disallowed by routing.${disallowed.by} (${disallowed.entry}) — ${verdict}`;
|
|
273
|
+
}
|
|
239
274
|
export function denyPreferCollisionLine({ kind, shape, detail, disallowed }) {
|
|
240
275
|
const verb = kind === "pin" ? "is disallowed" : "fully disallowed";
|
|
241
276
|
return `deny∩prefer: routing.map.${shape}.${kind} ${detail} ${verb} by routing.${disallowed.by} (${disallowed.entry}) — remove the ${disallowed.by} entry or adjust ${kind}`;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type AuthHealth, type BillingChannel, type WorkerAdapter } from "../adapters/types.js";
|
|
2
|
+
import { type TickmarkrConfig } from "../config/config.js";
|
|
3
|
+
import type { PreferenceRole } from "./preference.js";
|
|
4
|
+
export { preferIndex, rankPreferredChannels, reviewPreferenceTieBreak } from "./router.js";
|
|
5
|
+
export type RolePick = {
|
|
6
|
+
ok: true;
|
|
7
|
+
channel: BillingChannel;
|
|
8
|
+
} | {
|
|
9
|
+
ok: false;
|
|
10
|
+
reason: "missing-prefer" | "no-eligible-preferred-channel";
|
|
11
|
+
};
|
|
12
|
+
/** Strict role selection: no task, author, pin, or default seat is invented. */
|
|
13
|
+
export declare function pickRole(role: PreferenceRole, cfg: TickmarkrConfig, adapters: WorkerAdapter[], health: Record<string, AuthHealth>, opts?: {
|
|
14
|
+
excludeVendors?: ReadonlySet<string>;
|
|
15
|
+
eligible?: (channel: BillingChannel) => boolean;
|
|
16
|
+
}): RolePick;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { discoverChannels } from "../adapters/registry.js";
|
|
2
|
+
import { rankPreferredChannels, reviewPreferenceTieBreak } from "./router.js";
|
|
3
|
+
export { preferIndex, rankPreferredChannels, reviewPreferenceTieBreak } from "./router.js";
|
|
4
|
+
/** Strict role selection: no task, author, pin, or default seat is invented. */
|
|
5
|
+
export function pickRole(role, cfg, adapters, health, opts = {}) {
|
|
6
|
+
// Worker preferences belong to shapes; judge currently has only an explicit pin.
|
|
7
|
+
const prefer = role === "review" ? cfg.review.prefer : role === "consult" ? cfg.consult.prefer : undefined;
|
|
8
|
+
if (!prefer?.length)
|
|
9
|
+
return { ok: false, reason: "missing-prefer" };
|
|
10
|
+
const pool = discoverChannels(cfg, adapters, health, role).filter((c) => !opts.excludeVendors?.has(c.vendor) && (opts.eligible?.(c) ?? true));
|
|
11
|
+
const channel = rankPreferredChannels(pool, prefer, {
|
|
12
|
+
tieBreak: role === "review" ? reviewPreferenceTieBreak : undefined,
|
|
13
|
+
})[0];
|
|
14
|
+
return channel ? { ok: true, channel } : { ok: false, reason: "no-eligible-preferred-channel" };
|
|
15
|
+
}
|
package/dist/route/router.d.ts
CHANGED
|
@@ -31,6 +31,19 @@ export declare class RoutingError extends Error {
|
|
|
31
31
|
constructor(msg: string);
|
|
32
32
|
}
|
|
33
33
|
export declare function marginalCostRank(c: BillingChannel): number;
|
|
34
|
+
type ChannelSeat = {
|
|
35
|
+
adapter: string;
|
|
36
|
+
model: string;
|
|
37
|
+
};
|
|
38
|
+
/** Shared adapter | adapter:model grammar; unmatched channels follow every declared entry. */
|
|
39
|
+
export declare function preferIndex(c: ChannelSeat, prefer?: readonly string[]): number;
|
|
40
|
+
/** Rank an already eligible pool. Review alone retains its legacy unpreferred tail. */
|
|
41
|
+
export declare function rankPreferredChannels<C extends ChannelSeat>(channels: readonly C[], prefer?: readonly string[], opts?: {
|
|
42
|
+
includeUnpreferred?: boolean;
|
|
43
|
+
tieBreak?: (a: C, b: C) => number;
|
|
44
|
+
}): C[];
|
|
45
|
+
/** Review preserves its established tier-then-cost order inside a preference band. */
|
|
46
|
+
export declare const reviewPreferenceTieBreak: (a: BillingChannel, b: BillingChannel) => number;
|
|
34
47
|
export declare function route(task: Task, cfg: TickmarkrConfig, channels: BillingChannel[], profile?: RoutingProfile, preferCtx?: RoutingPreferContext, exclude?: ReadonlySet<string>, exploreCtx?: ExploreContext): Route;
|
|
35
48
|
export declare function nextChannel(current: Assignment, task: Task, cfg: TickmarkrConfig, channels: BillingChannel[], tried: string[], profile?: RoutingProfile, exclude?: ReadonlySet<string>): Assignment | null;
|
|
36
49
|
export type ClimbPick = Assignment & {
|
|
@@ -46,3 +59,4 @@ export type ClimbResult = ClimbPick;
|
|
|
46
59
|
* returns nextChannel's same-or-higher pick flagged climbed: false naming the reason.
|
|
47
60
|
*/
|
|
48
61
|
export declare function climbChannel(current: Assignment, task: Task, cfg: TickmarkrConfig, channels: BillingChannel[], tried: string[], profile?: RoutingProfile, exclude?: ReadonlySet<string>): ClimbPick | null;
|
|
62
|
+
export {};
|
package/dist/route/router.js
CHANGED
|
@@ -44,6 +44,7 @@ export function marginalCostRank(c) {
|
|
|
44
44
|
}
|
|
45
45
|
const toAssignment = (c) => ({
|
|
46
46
|
adapter: c.adapter, model: c.model, channel: c.channel, tier: c.tier,
|
|
47
|
+
...(c.effort ? { effort: c.effort } : {}),
|
|
47
48
|
});
|
|
48
49
|
function resolvePin(pin, channels) {
|
|
49
50
|
const c = channels.find((c) => c.adapter === pin.via && c.model === pin.model);
|
|
@@ -52,10 +53,18 @@ function resolvePin(pin, channels) {
|
|
|
52
53
|
}
|
|
53
54
|
return c;
|
|
54
55
|
}
|
|
55
|
-
|
|
56
|
+
/** Shared adapter | adapter:model grammar; unmatched channels follow every declared entry. */
|
|
57
|
+
export function preferIndex(c, prefer = []) {
|
|
56
58
|
const i = prefer.findIndex((p) => p === c.adapter || p === channelKey(c));
|
|
57
59
|
return i === -1 ? prefer.length : i;
|
|
58
60
|
}
|
|
61
|
+
/** Rank an already eligible pool. Review alone retains its legacy unpreferred tail. */
|
|
62
|
+
export function rankPreferredChannels(channels, prefer = [], opts = {}) {
|
|
63
|
+
return channels.filter((c) => opts.includeUnpreferred || preferIndex(c, prefer) < prefer.length)
|
|
64
|
+
.sort((a, b) => preferIndex(a, prefer) - preferIndex(b, prefer) || opts.tieBreak?.(a, b) || 0);
|
|
65
|
+
}
|
|
66
|
+
/** Review preserves its established tier-then-cost order inside a preference band. */
|
|
67
|
+
export const reviewPreferenceTieBreak = (a, b) => TIER_RANK[b.tier] - TIER_RANK[a.tier] || Number(a.channel === "api") - Number(b.channel === "api");
|
|
59
68
|
function ladderFor(task, entry) {
|
|
60
69
|
const escalate = task.routingHints?.escalate ?? entry?.escalate ?? true;
|
|
61
70
|
return escalate ? ["retry", "escalate", "consult", "human"] : ["retry", "consult", "human"];
|
|
@@ -142,6 +151,13 @@ const maybeSlaLint = (lints, task, profile, slaMinutes, c) => {
|
|
|
142
151
|
lints.push(`${task.id} (${task.shape}): median ${medianMin}m exceeds sla ${slaMinutes}m — learned perf term references sla ${slaMinutes}m ref`);
|
|
143
152
|
};
|
|
144
153
|
export function route(task, cfg, channels, profile, preferCtx, exclude, exploreCtx) {
|
|
154
|
+
// OBS-1143: deny checks below read the probed identity the offered channels carry — the identity
|
|
155
|
+
// discovery admitted them under. A seat no channel vouches for stays unknown (conservative).
|
|
156
|
+
const observed = new Map(channels.flatMap((c) => (c.identity ? [[channelKey(c), c.identity]] : [])));
|
|
157
|
+
const seat = (adapter, model) => {
|
|
158
|
+
const identity = observed.get(channelKey({ adapter, model }));
|
|
159
|
+
return identity ? { adapter, model, identity } : { adapter, model };
|
|
160
|
+
};
|
|
145
161
|
channels = withoutExcluded(channels, exclude);
|
|
146
162
|
const lints = [];
|
|
147
163
|
const advisoryFloor = cfg.routing.floors[task.shape];
|
|
@@ -153,7 +169,7 @@ export function route(task, cfg, channels, profile, preferCtx, exclude, exploreC
|
|
|
153
169
|
const prefer = effectivePrefer(entry);
|
|
154
170
|
const prefActive = !!(cfg.routing.allow || cfg.routing.deny);
|
|
155
171
|
const disallowedPin = (via, model, kind) => {
|
|
156
|
-
const d = disallowedBy(
|
|
172
|
+
const d = disallowedBy(seat(via, model), cfg.routing);
|
|
157
173
|
if (d) {
|
|
158
174
|
throw new RoutingError(`${task.id}: ${kind} ${via}:${model} is disallowed by routing.${d.by} (${d.entry}) — remove the ${d.by} entry or re-pin to an allowed channel`);
|
|
159
175
|
}
|
|
@@ -166,8 +182,8 @@ export function route(task, cfg, channels, profile, preferCtx, exclude, exploreC
|
|
|
166
182
|
}
|
|
167
183
|
if (cfg.tiers[p]) {
|
|
168
184
|
const expanded = channelsFromConfig(p, cfg);
|
|
169
|
-
if (expanded.length && expanded.every((c) => disallowedBy(c, cfg.routing) !== null)) {
|
|
170
|
-
const d = disallowedBy(expanded[0], cfg.routing);
|
|
185
|
+
if (expanded.length && expanded.every((c) => disallowedBy(seat(c.adapter, c.model), cfg.routing) !== null)) {
|
|
186
|
+
const d = disallowedBy(seat(expanded[0].adapter, expanded[0].model), cfg.routing);
|
|
171
187
|
throw new RoutingError(`${task.id}: prefer entry ${p} is disallowed by routing.${d.by} (${d.entry}) — remove the ${d.by} entry or re-pin to an allowed channel`);
|
|
172
188
|
}
|
|
173
189
|
return;
|
|
@@ -220,21 +236,28 @@ export function route(task, cfg, channels, profile, preferCtx, exclude, exploreC
|
|
|
220
236
|
// single-channel pool) and the open floor/auto path. Exhaustion stays fail-loud (pin precedent).
|
|
221
237
|
if (entry?.pool) {
|
|
222
238
|
const pool = entry.pool;
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
239
|
+
// OBS-1144: a disallowed entry is skipped, named in provenance, never fatal for the pool; only an
|
|
240
|
+
// empty admitted remainder refuses. Filtering keeps declaration order, so ordered stays ordered.
|
|
241
|
+
const skipped = [];
|
|
242
|
+
const admitted = pool.channels.filter((p) => {
|
|
243
|
+
if (!prefActive)
|
|
244
|
+
return true;
|
|
245
|
+
const i = p.indexOf(":");
|
|
246
|
+
const d = disallowedBy(seat(p.slice(0, i), p.slice(i + 1)), cfg.routing);
|
|
247
|
+
if (d)
|
|
248
|
+
skipped.push(`${p} (disallowed by routing.${d.by} (${d.entry}))`);
|
|
249
|
+
return d === null;
|
|
250
|
+
});
|
|
251
|
+
const skippedNote = skipped.length ? `; skipped ${skipped.join(", ")}` : "";
|
|
252
|
+
if (!admitted.length) {
|
|
253
|
+
throw new RoutingError(`${task.id}: routing.map.${task.shape}.pool is exhausted — no admitted entry remains: ${skipped.join(", ")} — remove the deny/allow entry or add an admitted channel to the pool`);
|
|
231
254
|
}
|
|
232
255
|
// live preserves pool declaration order — ordered mode and any-mode stable-sort ties depend on it
|
|
233
|
-
const live =
|
|
256
|
+
const live = admitted
|
|
234
257
|
.map((id) => channels.find((c) => channelKey(c) === id))
|
|
235
258
|
.filter((c) => c !== undefined && (!taskFloor || TIER_RANK[c.tier] >= TIER_RANK[taskFloor]));
|
|
236
259
|
if (!live.length) {
|
|
237
|
-
throw new RoutingError(`${task.id}: routing.map.${task.shape}.pool has no live channel${taskFloor ? ` at task floor ${taskFloor}` : ""} — declared: ${pool.channels.join(", ")}; doctor found: ${channels.map(channelKey).join(", ") || "(nothing)"}`);
|
|
260
|
+
throw new RoutingError(`${task.id}: routing.map.${task.shape}.pool has no live channel${taskFloor ? ` at task floor ${taskFloor}` : ""} — declared: ${pool.channels.join(", ")}${skippedNote}; doctor found: ${channels.map(channelKey).join(", ") || "(nothing)"}`);
|
|
238
261
|
}
|
|
239
262
|
// No learned/explore inside pools: any spreads deterministically; ordered keeps declaration order.
|
|
240
263
|
const chosen = pool.mode === "ordered"
|
|
@@ -242,7 +265,7 @@ export function route(task, cfg, channels, profile, preferCtx, exclude, exploreC
|
|
|
242
265
|
: rankAnyPool(live, task)[0];
|
|
243
266
|
lintMapPinFloor(chosen.tier, "pool");
|
|
244
267
|
maybeSlaLint(lints, task, profile, slaMinutes, chosen);
|
|
245
|
-
return { assignment: toAssignment(chosen), ladder: ladderFor(task, entry), lints, provenance: `${degraded}pool ${pool.mode} ${channelKey(chosen)} (config routing.map)` };
|
|
268
|
+
return { assignment: toAssignment(chosen), ladder: ladderFor(task, entry), lints, provenance: `${degraded}pool ${pool.mode} ${channelKey(chosen)} (config routing.map${skippedNote})` };
|
|
246
269
|
}
|
|
247
270
|
const baseTier = floor ?? "cheap";
|
|
248
271
|
// A task floor is hard on pool and floor/auto paths; the map-pin branch above stays supreme.
|
|
@@ -260,7 +283,7 @@ export function route(task, cfg, channels, profile, preferCtx, exclude, exploreC
|
|
|
260
283
|
let msg = `${task.id}: no channel at tier>=${minTier}; available: ${channels.map(channelKey).join(", ") || "(none)"}`;
|
|
261
284
|
if (prefActive) {
|
|
262
285
|
const excluded = Object.keys(cfg.tiers).flatMap((id) => channelsFromConfig(id, cfg))
|
|
263
|
-
.filter((c) => disallowedBy(c, cfg.routing) !== null && TIER_RANK[c.tier] >= TIER_RANK[minTier])
|
|
286
|
+
.filter((c) => disallowedBy(seat(c.adapter, c.model), cfg.routing) !== null && TIER_RANK[c.tier] >= TIER_RANK[minTier])
|
|
264
287
|
.map(channelKey);
|
|
265
288
|
if (excluded.length)
|
|
266
289
|
msg += `; ${excluded.length} channel(s) excluded by routing.allow/deny: ${excluded.join(", ")}`;
|
package/dist/run/consult.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type WorkerAdapter } from "../adapters/types.js";
|
|
2
2
|
import type { TickmarkrConfig } from "../config/config.js";
|
|
3
|
+
import type { Effort } from "../graph/schema.js";
|
|
3
4
|
import type { ExecutorDriver, Slot } from "../drivers/types.js";
|
|
4
5
|
import type { GateResult } from "../gates/types.js";
|
|
5
6
|
import { type VerdictUnparseableCause } from "../gates/verdict-cause.js";
|
|
@@ -13,6 +14,16 @@ export interface ConsultVerdict {
|
|
|
13
14
|
adapter?: string;
|
|
14
15
|
model?: string;
|
|
15
16
|
vendor?: string;
|
|
17
|
+
effort?: Effort;
|
|
18
|
+
invocations?: ConsultInvocation[];
|
|
19
|
+
}
|
|
20
|
+
export interface ConsultInvocation {
|
|
21
|
+
adapter: string;
|
|
22
|
+
model: string;
|
|
23
|
+
vendor: string;
|
|
24
|
+
effort?: Effort;
|
|
25
|
+
outcome: "completed" | "failed";
|
|
26
|
+
reason: string;
|
|
16
27
|
}
|
|
17
28
|
export declare function renderRetryGuidance(v: ConsultVerdict): string;
|
|
18
29
|
export declare function augmentRetryBrief(feedback: string, opts: {
|
|
@@ -37,6 +48,7 @@ export declare function buildDossierPrompt(d: Dossier, nonce: string): string;
|
|
|
37
48
|
export declare function consult(d: Dossier, cfg: TickmarkrConfig, adapters: WorkerAdapter[], driver: ExecutorDriver, cwd: string, runDir: string, opts?: {
|
|
38
49
|
keep?: boolean;
|
|
39
50
|
onSlot?: (slot: Slot) => void;
|
|
51
|
+
onInvocation?: (inv: ConsultInvocation) => void;
|
|
40
52
|
runId?: string;
|
|
41
53
|
channels?: Array<{
|
|
42
54
|
adapter: string;
|
package/dist/run/consult.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { getAdapter } from "../adapters/registry.js";
|
|
4
|
+
import { configuredEffort } from "../adapters/types.js";
|
|
4
5
|
import { bannerShell, paneDispatchCommand } from "../brand.js";
|
|
5
6
|
import { dewrapPaneVerdict, extractVerdictJson, gateExitTrailer, gatePaneName, generateVerdictNonce, verdictNonceLine } from "../gates/llm.js";
|
|
6
7
|
import { classifyVerdictCause } from "../gates/verdict-cause.js";
|
|
8
|
+
import { rankPreferredChannels } from "../route/role-pick.js";
|
|
7
9
|
import { disallowedBy, routingModelProvider } from "../route/preference.js";
|
|
8
10
|
import { sh } from "./git.js";
|
|
9
11
|
import { redactSecrets } from "./redact.js";
|
|
@@ -144,6 +146,8 @@ export async function consult(d, cfg, adapters, driver, cwd, runDir,
|
|
|
144
146
|
// via SlotOpts.owned; without it the legacy gatePaneName shape survives (non-daemon callers/tests).
|
|
145
147
|
// v1.54 T1: channels — the daemon's doctor-filtered live channel list; prefer-seat liveness is
|
|
146
148
|
// judged against it only (never rebuilt from config, which would select installed-but-unauthed seats).
|
|
149
|
+
// OBS-1182: onInvocation hears every launched seat — its identity, effort and outcome — failed seats
|
|
150
|
+
// included, so a seat that never produced the verdict still reaches the journal.
|
|
147
151
|
opts = {}) {
|
|
148
152
|
const n = ++consultSeq;
|
|
149
153
|
const nonce = generateVerdictNonce();
|
|
@@ -156,11 +160,11 @@ opts = {}) {
|
|
|
156
160
|
// One seat = the WHOLE invoke-and-parse unit, both visibility branches (OBS-69 class: a headless-only
|
|
157
161
|
// failover would leave the production pane path hard-failing on seat one). A null verdict retains
|
|
158
162
|
// the classifier's cause when extraction failed; parsed content rejections deliberately have none.
|
|
159
|
-
const invokeSeat = async (seatAdapter, seatModel, seatIdx) => {
|
|
163
|
+
const invokeSeat = async (seatAdapter, seatModel, seatIdx, effort) => {
|
|
160
164
|
const adapter = getAdapter(seatAdapter, adapters);
|
|
161
165
|
let out;
|
|
162
166
|
if (cfg.visibility.llm === "headless") {
|
|
163
|
-
const r = await sh(adapter.headlessCommand(promptFile, seatModel), cwd, cfg.consult.stallMinutes * 60_000);
|
|
167
|
+
const r = await sh(adapter.headlessCommand(promptFile, seatModel, effort), cwd, cfg.consult.stallMinutes * 60_000);
|
|
164
168
|
out = r.stdout + r.stderr;
|
|
165
169
|
}
|
|
166
170
|
else {
|
|
@@ -168,7 +172,7 @@ opts = {}) {
|
|
|
168
172
|
// but never exits, so the exit trailer after it never runs, the wait runs to the stall cap and
|
|
169
173
|
// the 300-line read is a TUI frame. The headless command exits, the trailer prints, and the
|
|
170
174
|
// pane is harvested through dewrap exactly like a judge or review seat.
|
|
171
|
-
const command = adapter.headlessCommand(promptFile, seatModel);
|
|
175
|
+
const command = adapter.headlessCommand(promptFile, seatModel, effort);
|
|
172
176
|
// T8: role-first pane name for fleet visibility (consult · T2); consultSeq stays on the dossier artifact only
|
|
173
177
|
const slot = await driver.slot(cwd, gatePaneName("consult", d.taskId), {
|
|
174
178
|
label: `CONSULT ${d.taskId}`,
|
|
@@ -212,15 +216,10 @@ opts = {}) {
|
|
|
212
216
|
}
|
|
213
217
|
return parsed;
|
|
214
218
|
};
|
|
215
|
-
//
|
|
216
|
-
//
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
// only WHICH seat answers — per-seat parsing and the fail-safe human action below are untouched.
|
|
220
|
-
const live = new Set((opts.channels ?? []).map((c) => c.adapter));
|
|
221
|
-
const seats = (cfg.consult.prefer ?? [])
|
|
222
|
-
.map((entry) => ({ adapter: entry.slice(0, entry.indexOf(":")), model: entry.slice(entry.indexOf(":") + 1) }))
|
|
223
|
-
.filter((s) => live.has(s.adapter));
|
|
219
|
+
// Rank only exact live models from the daemon's role-scoped discovery pool.
|
|
220
|
+
// The explicit pin remains a caller-owned fallback, outside strict preference selection.
|
|
221
|
+
const live = (opts.channels ?? []).filter((c) => typeof c.model === "string");
|
|
222
|
+
const seats = rankPreferredChannels(live.filter((c) => disallowedBy(c, cfg.routing, "consult") === null), cfg.consult.prefer);
|
|
224
223
|
seats.push({ adapter: cfg.consult.adapter, model: cfg.consult.model });
|
|
225
224
|
// v1.87 T2: a consult seat reads the code, so it passes through the operator's policy exactly like
|
|
226
225
|
// a worker does. The filter runs AFTER the pin is pushed, so the final pinned seat is checked by
|
|
@@ -246,8 +245,12 @@ opts = {}) {
|
|
|
246
245
|
};
|
|
247
246
|
}
|
|
248
247
|
for (const [i, seat] of allowedSeats.entries()) {
|
|
248
|
+
// OBS-1182: each seat launches at its own configured effort, never the worker's.
|
|
249
|
+
const effort = configuredEffort(cfg, seat);
|
|
250
|
+
const record = (outcome, reason) => opts.onInvocation?.({ ...seatIdentity(seat), ...(effort ? { effort } : {}), outcome, reason });
|
|
249
251
|
try {
|
|
250
|
-
const parsed = await invokeSeat(seat.adapter, seat.model, i);
|
|
252
|
+
const parsed = await invokeSeat(seat.adapter, seat.model, i, effort);
|
|
253
|
+
record(parsed.verdict ? "completed" : "failed", parsed.verdict ? "verdict" : `verdict unparseable${parsed.cause ? ` (${parsed.cause})` : ""}`);
|
|
251
254
|
if (parsed.verdict) {
|
|
252
255
|
const excludeProvider = parsed.verdict.excludeAdapter
|
|
253
256
|
? excludedProviderFromDossier(d, parsed.verdict.excludeAdapter)
|
|
@@ -259,11 +262,13 @@ opts = {}) {
|
|
|
259
262
|
notes: `${parsed.verdict.notes} — excluded provider ${excludeProvider}`,
|
|
260
263
|
} : {}),
|
|
261
264
|
...seatIdentity(seat),
|
|
265
|
+
...(effort ? { effort } : {}),
|
|
262
266
|
};
|
|
263
267
|
}
|
|
264
268
|
}
|
|
265
|
-
catch {
|
|
269
|
+
catch (e) {
|
|
266
270
|
// failed seat (unknown adapter, dead driver/pane, shell error) — fall to the next entry
|
|
271
|
+
record("failed", `seat error: ${e instanceof Error ? e.message : String(e)}`.slice(0, 200));
|
|
267
272
|
}
|
|
268
273
|
}
|
|
269
274
|
return {
|
package/dist/run/daemon.d.ts
CHANGED
|
@@ -2,11 +2,14 @@ import { type WorkerAdapter } from "../adapters/types.js";
|
|
|
2
2
|
import { type ModeResolution, type RoutingMode, type TickmarkrConfig } from "../config/config.js";
|
|
3
3
|
import { type DriverChoice } from "../drivers/index.js";
|
|
4
4
|
import { type ExecutorDriver, type Slot } from "../drivers/types.js";
|
|
5
|
-
import { type Baseline } from "../gates/baseline.js";
|
|
5
|
+
import { type Baseline, type BaselineProvenance } from "../gates/baseline.js";
|
|
6
6
|
import type { GateResult } from "../gates/types.js";
|
|
7
7
|
import { type RunGraph } from "../graph/schema.js";
|
|
8
|
+
import { type PreserveProducer } from "./git.js";
|
|
8
9
|
import { Journal, type JournalEvent } from "./journal.js";
|
|
9
10
|
export declare function closeLiveSlot(liveSlots: Set<Slot>, driver: Pick<ExecutorDriver, "close">, slot: Slot): Promise<void>;
|
|
11
|
+
export declare function setContactUnreadableDeadlineMsForTests(ms: number): void;
|
|
12
|
+
export declare function resetContactUnreadableDeadlineMsForTests(): void;
|
|
10
13
|
/** Optional authoritative transport receipt. Screen text can prove execution, never nonacceptance. */
|
|
11
14
|
export interface DispatchObservation {
|
|
12
15
|
slotId: string;
|
|
@@ -66,7 +69,35 @@ export interface RunSummary {
|
|
|
66
69
|
approvalDisposition?: "complete" | "outstanding";
|
|
67
70
|
/** the accepted approvals that never reached a dispatch — named, never left to the park buckets */
|
|
68
71
|
outstandingApprovals?: string[];
|
|
72
|
+
/** OBS-1123: every baseline-recorded red a standing green carried, with the capture that recorded it */
|
|
73
|
+
forgiven?: ForgivenFingerprints[];
|
|
69
74
|
}
|
|
75
|
+
/** OBS-1123: one forgiveness — the fingerprints a green carried and the capture that recorded them. */
|
|
76
|
+
export interface ForgivenFingerprints {
|
|
77
|
+
/** the task whose gate forgave them; absent for the integration tip */
|
|
78
|
+
taskId?: string;
|
|
79
|
+
gate: string;
|
|
80
|
+
/** absent: a legacy row that named no fingerprints */
|
|
81
|
+
fingerprints?: string[];
|
|
82
|
+
/** absent: a legacy capture or row that recorded no provenance — rendered unknown, never dated */
|
|
83
|
+
baseline?: BaselineProvenance;
|
|
84
|
+
}
|
|
85
|
+
/** Journal rows are untrusted input: a malformed field reads as not recorded, never as a value. */
|
|
86
|
+
export declare const fingerprintsOf: (v: unknown) => string[] | undefined;
|
|
87
|
+
export declare const baselineProvenanceOf: (v: unknown) => BaselineProvenance | undefined;
|
|
88
|
+
/** A forgiven battery green: the structured field, or the battery's own "(forgiven)" wording on a legacy row. */
|
|
89
|
+
export declare const forgivenGateRow: (data: Record<string, unknown>) => boolean;
|
|
90
|
+
/**
|
|
91
|
+
* OBS-1123: the forgiveness standing at this point in the journal — each task gate's LATEST verdict
|
|
92
|
+
* where it is a forgiven green, then the latest verification cycle's forgiven tip rows. A legacy row
|
|
93
|
+
* keeps what it never recorded absent, so the reader says "unknown" instead of borrowing a date.
|
|
94
|
+
*/
|
|
95
|
+
export declare function forgivenFingerprints(events: readonly JournalEvent[]): ForgivenFingerprints[];
|
|
96
|
+
/** The capture a forgiveness rests on, or an explicit unknown — never a time read off another clock. */
|
|
97
|
+
export declare function formatBaselineProvenance(p: BaselineProvenance | undefined): string;
|
|
98
|
+
export declare function formatFingerprints(fingerprints: string[] | undefined): string;
|
|
99
|
+
export declare function formatForgiven(f: ForgivenFingerprints): string;
|
|
100
|
+
export declare function pendingDaemonApprovalActions(events: JournalEvent[]): Map<string, import("./journal.js").PendingApprovalAction>;
|
|
70
101
|
/**
|
|
71
102
|
* T14, amended by v2.2 T3: approvals the run accepted and never acted on. `approved` above is still
|
|
72
103
|
* built ONCE at startup — replay determinism depends on it — but a live approval is no longer inert:
|
|
@@ -126,6 +157,12 @@ export declare const APPROVAL_POLL_MS = 250;
|
|
|
126
157
|
export declare const APPROVAL_WINDOW_MS = 120000;
|
|
127
158
|
export declare const setApprovalWindowForTests: (ms: number) => void;
|
|
128
159
|
export declare const resetApprovalWindowForTests: () => void;
|
|
160
|
+
/** Test seam — shrink the capacity backoff without minute-long sleeps. */
|
|
161
|
+
export declare function setCapacityBackoffMsForTests(ms: number): void;
|
|
162
|
+
export declare function resetCapacityBackoffMsForTests(): void;
|
|
163
|
+
/** Test seam — shrink the bootstrap backoff. */
|
|
164
|
+
export declare function setBootstrapBackoffMsForTests(ms: number): void;
|
|
165
|
+
export declare function resetBootstrapBackoffMsForTests(): void;
|
|
129
166
|
export declare const EARLY_LAUNCH_LIVENESS_MS = 60000;
|
|
130
167
|
/** Test seam — lowers the empty-pane liveness window without sleeping 60s per case. */
|
|
131
168
|
export declare function setEarlyLaunchLivenessMsForTests(ms: number): void;
|
|
@@ -149,8 +186,11 @@ export interface StartupFailureEvidence {
|
|
|
149
186
|
/** One seat, one startup prefix. Once execution or its input box is seen, later panes are ineligible. */
|
|
150
187
|
export declare class StartupFailureDetector {
|
|
151
188
|
private readonly adapter;
|
|
189
|
+
private readonly pattern;
|
|
190
|
+
private readonly wholePrefix;
|
|
152
191
|
private closed;
|
|
153
|
-
constructor(adapter: Pick<WorkerAdapter, "inputBox" | "harnessBannerRows"
|
|
192
|
+
constructor(adapter: Pick<WorkerAdapter, "inputBox" | "harnessBannerRows">, pattern?: RegExp, // OBS-1169: or known bootstrap text
|
|
193
|
+
wholePrefix?: boolean);
|
|
154
194
|
sample(output: string, launchedAt: number | undefined): StartupFailureEvidence | undefined;
|
|
155
195
|
}
|
|
156
196
|
/** Test seam — shrink the quota-banner silence gate without minute-long sleeps. */
|
|
@@ -218,5 +258,9 @@ export declare function liveSuiteCount(repoRoot: string): Promise<number>;
|
|
|
218
258
|
/** Test seam — exercise the production observer's total read bound with a small real tree. */
|
|
219
259
|
export declare function setObserveBudgetBytesForTests(bytes: number): void;
|
|
220
260
|
export declare function resetObserveBudgetBytesForTests(): void;
|
|
261
|
+
/** The attempt whose worker last launched into the task's current checkout; "unknown" once a
|
|
262
|
+
* recreation replaced that tree without a new launch (recheck, gate-only restore) or when no
|
|
263
|
+
* dispatch assignment is on record. Never the newest dispatch by itself. */
|
|
264
|
+
export declare function knownProducer(events: readonly JournalEvent[], taskId: string): PreserveProducer;
|
|
221
265
|
export declare function recordFatalRunEnd(journal: Journal, runId: string, branch: string, err: unknown, graph?: RunGraph, phase?: string): TipProof | undefined;
|
|
222
266
|
export declare function runDaemon(repoRoot: string, opts?: RunOptions): Promise<RunSummary>;
|