tickmarkr 1.85.0 → 1.87.0
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 +4 -2
- package/dist/adapters/catalog-remote.d.ts +64 -0
- package/dist/adapters/catalog-remote.js +287 -0
- package/dist/adapters/catalog.d.ts +108 -0
- package/dist/adapters/catalog.js +189 -0
- package/dist/adapters/claude-code.js +5 -3
- package/dist/adapters/fake.js +33 -4
- package/dist/adapters/model-lints.d.ts +25 -5
- package/dist/adapters/model-lints.js +184 -50
- package/dist/adapters/model-windows.d.ts +31 -0
- package/dist/adapters/model-windows.js +69 -0
- package/dist/adapters/prompt.d.ts +5 -1
- package/dist/adapters/prompt.js +13 -4
- package/dist/adapters/registry.d.ts +34 -27
- package/dist/adapters/registry.js +215 -112
- package/dist/adapters/types.js +15 -3
- package/dist/brand.d.ts +5 -1
- package/dist/brand.js +18 -2
- package/dist/cli/commands/doctor.d.ts +3 -0
- package/dist/cli/commands/doctor.js +43 -21
- package/dist/cli/commands/fleet.d.ts +7 -0
- package/dist/cli/commands/fleet.js +94 -74
- package/dist/cli/commands/init.js +118 -5
- package/dist/cli/commands/plan.js +11 -1
- package/dist/cli/commands/resume.js +7 -1
- package/dist/cli/commands/status.js +44 -18
- package/dist/compile/gsd.d.ts +2 -1
- package/dist/compile/gsd.js +68 -2
- package/dist/compile/native.d.ts +14 -0
- package/dist/compile/native.js +168 -12
- package/dist/config/config.d.ts +20 -5
- package/dist/config/config.js +96 -64
- package/dist/config/fleet-overlay.d.ts +25 -20
- package/dist/config/fleet-overlay.js +195 -77
- package/dist/config/fleet-why.d.ts +23 -0
- package/dist/config/fleet-why.js +42 -0
- package/dist/drivers/herdr.d.ts +1 -0
- package/dist/drivers/herdr.js +70 -19
- package/dist/gates/acceptance.js +7 -2
- package/dist/gates/llm.d.ts +0 -1
- package/dist/gates/llm.js +5 -30
- package/dist/gates/review.d.ts +2 -1
- package/dist/gates/review.js +9 -7
- package/dist/gates/run-gates.d.ts +1 -0
- package/dist/gates/run-gates.js +21 -2
- package/dist/gates/verdict-cause.d.ts +4 -0
- package/dist/gates/verdict-cause.js +63 -0
- package/dist/graph/schema.d.ts +6 -0
- package/dist/graph/schema.js +8 -5
- package/dist/route/preference.d.ts +1 -1
- package/dist/route/preference.js +8 -1
- package/dist/route/router.d.ts +0 -5
- package/dist/route/router.js +16 -20
- package/dist/run/consult.d.ts +6 -0
- package/dist/run/consult.js +49 -26
- package/dist/run/daemon.js +81 -20
- package/dist/run/journal.js +87 -7
- package/dist/tui/cockpit/capture.d.ts +12 -0
- package/dist/tui/cockpit/capture.js +37 -1
- package/dist/tui/cockpit/components.js +8 -8
- package/dist/tui/cockpit/theme.d.ts +32 -26
- package/dist/tui/cockpit/theme.js +11 -5
- package/dist/tui/ink/components.d.ts +0 -15
- package/dist/tui/ink/components.js +0 -17
- package/dist/tui/ink/fleet-app.d.ts +4 -1
- package/dist/tui/ink/fleet-app.js +134 -13
- package/fixtures/gateway-models.json +1 -0
- package/fixtures/sample.native.md +1 -1
- package/package.json +1 -1
- package/skills/tickmarkr-overseer/SKILL.md +464 -34
- package/skills/tickmarkr-overseer/scripts/watch-artifacts.sh +86 -0
- package/skills/tickmarkr-overseer/scripts/watch-panes.sh +1 -1
- package/dist/tui/ink/studio-app.d.ts +0 -59
- package/dist/tui/ink/studio-app.js +0 -320
- package/dist/tui/save.d.ts +0 -38
- package/dist/tui/save.js +0 -96
- package/dist/tui/staging.d.ts +0 -29
- package/dist/tui/staging.js +0 -78
|
@@ -89,9 +89,11 @@ export const CLAUDE_INPUT_BOX = declareInputBox("claude-code", {
|
|
|
89
89
|
fingerprint: "❯\u00A0",
|
|
90
90
|
match: (paneText) => matchesClaudeEditor(paneText, false),
|
|
91
91
|
emptyMatch: (paneText) => matchesClaudeEditor(paneText, true),
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
|
|
92
|
+
// OBS-342: daemon-built launches carry intent through paneDispatchCommand; their wrapper bytes are
|
|
93
|
+
// never re-parsed here. The first-delivery fact keeps direct driver consumers on the same honest
|
|
94
|
+
// lifecycle: a fresh claude-code worker slot is a shell awaiting its launch, every later delivery
|
|
95
|
+
// is a TUI turn awaiting this box. Both interactive and resume builders share that contract.
|
|
96
|
+
firstDeliveryIsLaunch: true,
|
|
95
97
|
// The 2026-08-03 capture reached a painted editor ~10s after the trust answer on a warm install.
|
|
96
98
|
readinessTimeoutMs: 30_000,
|
|
97
99
|
});
|
package/dist/adapters/fake.js
CHANGED
|
@@ -49,6 +49,7 @@ export class FakeAdapter {
|
|
|
49
49
|
catch {
|
|
50
50
|
// unreadable promptFile: can't detect role; serve static values (legacy headless-call behavior)
|
|
51
51
|
}
|
|
52
|
+
const nonce = this.nonceFor(promptFile);
|
|
52
53
|
const serve = (key) => {
|
|
53
54
|
if (role !== undefined && key !== role)
|
|
54
55
|
return join(dirname(this.scriptPath), `${key}.json`);
|
|
@@ -58,7 +59,9 @@ export class FakeAdapter {
|
|
|
58
59
|
val = val[Math.min(this.judgeIdx, val.length - 1)];
|
|
59
60
|
this.judgeIdx++;
|
|
60
61
|
}
|
|
61
|
-
|
|
62
|
+
// Legacy zero-token judge scripts may omit per-criterion evidence. Shape that scripted response
|
|
63
|
+
// here, where the fake authors it, instead of teaching the shared LLM transport about adapter ids.
|
|
64
|
+
if (key === "judge" && prompt.startsWith("TICKMARKR-JUDGE") && val && typeof val === "object" && !Array.isArray(val)) {
|
|
62
65
|
const verdict = val;
|
|
63
66
|
if (verdict.pass === true && Array.isArray(verdict.criteria) && verdict.criteria.length === 0) {
|
|
64
67
|
const items = prompt.match(/## Acceptance criteria \(judge\)\n([\s\S]*?)\n\n## Diff/)?.[1]
|
|
@@ -66,8 +69,31 @@ export class FakeAdapter {
|
|
|
66
69
|
val = { ...verdict, criteria: items.map((criterion) => ({ criterion, met: true, reason: "scripted fake pass" })) };
|
|
67
70
|
}
|
|
68
71
|
}
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
if (key === "judge" && isJudge && val && typeof val === "object" && !Array.isArray(val)) {
|
|
73
|
+
const verdict = val;
|
|
74
|
+
const line = /```diff\n([\s\S]*?)```/.exec(prompt)?.[1].split("\n").find((candidate) => candidate.trim());
|
|
75
|
+
if (line && Array.isArray(verdict.criteria)) {
|
|
76
|
+
val = {
|
|
77
|
+
...verdict,
|
|
78
|
+
criteria: verdict.criteria.map((row) => row && typeof row === "object" && !("evidence" in row) ? { ...row, evidence: line } : row),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// The configured fake adapter binds the nonce read from this prompt before one object is emitted.
|
|
83
|
+
// Renamed subclasses model other CLIs and retain their own producer contract. Missing nonces stay
|
|
84
|
+
// missing so the classifier can distinguish silence from participation.
|
|
85
|
+
if (this.id === "fake" && nonce && val && typeof val === "object" && !Array.isArray(val)) {
|
|
86
|
+
const { nonce: _scriptedNonce, ...verdict } = val;
|
|
87
|
+
val = { nonce, ...verdict };
|
|
88
|
+
}
|
|
89
|
+
// Concurrent gates share one FakeAdapter script. A nonce-specific response path keeps one call's
|
|
90
|
+
// responder-authored bytes from being replaced by a sibling between command construction and cat.
|
|
91
|
+
const responseNonce = this.id === "fake" ? nonce : "";
|
|
92
|
+
const p = join(dirname(this.scriptPath), `${key}${responseNonce ? `-${responseNonce}` : ""}.json`);
|
|
93
|
+
const json = JSON.stringify(val ?? {}, null, 1)
|
|
94
|
+
.split("\n").map((line) => line.trim()).join(" ")
|
|
95
|
+
.replace(/^\{ /, "{").replace(/ \}$/, "}");
|
|
96
|
+
writeFileSync(p, json);
|
|
71
97
|
return p;
|
|
72
98
|
};
|
|
73
99
|
return `bash -c 'grep -Eq "TICKMARKR-(JUDGE|SCOPE)" ${shq(promptFile)} && cat ${shq(serve("judge"))}; grep -q TICKMARKR-REVIEW ${shq(promptFile)} && cat ${shq(serve("review"))}; grep -q TICKMARKR-CONSULT ${shq(promptFile)} && cat ${shq(serve("consult"))}; true'`;
|
|
@@ -120,7 +146,10 @@ export class FakeAdapter {
|
|
|
120
146
|
// parseWorkerResult(output, nonce) succeeds exactly as before the nonce existed
|
|
121
147
|
nonceFor(promptFile) {
|
|
122
148
|
try {
|
|
123
|
-
|
|
149
|
+
const prompt = readFileSync(promptFile, "utf8");
|
|
150
|
+
return /VERDICT_NONCE:\s*([0-9a-f]+)/i.exec(prompt)?.[1]
|
|
151
|
+
?? /TICKMARKR_RESULT_([0-9a-z]+)/.exec(prompt)?.[1]
|
|
152
|
+
?? "";
|
|
124
153
|
}
|
|
125
154
|
catch {
|
|
126
155
|
return "";
|
|
@@ -1,14 +1,31 @@
|
|
|
1
|
-
import { type TickmarkrConfig } from "../config/config.js";
|
|
1
|
+
import { type TickmarkrConfig, type Tier } from "../config/config.js";
|
|
2
2
|
import type { Task } from "../graph/schema.js";
|
|
3
3
|
import { type AuthHealth, type WorkerAdapter } from "./types.js";
|
|
4
|
+
import { type CatalogModelEvidence, type CatalogReadResult } from "./catalog-remote.js";
|
|
4
5
|
export declare const SEED_STAMPED = "2026-07-09";
|
|
5
6
|
export declare const MODEL_STALE_DAYS = 30;
|
|
6
7
|
export declare const ttyVisual: () => boolean;
|
|
7
|
-
|
|
8
|
+
export interface CatalogModelAdvisory {
|
|
9
|
+
coverage: "covered" | "uncovered";
|
|
10
|
+
evidence?: CatalogModelEvidence;
|
|
11
|
+
suggestion?: {
|
|
12
|
+
tier: Tier;
|
|
13
|
+
kind: "inference";
|
|
14
|
+
basis: "intelligence" | "price";
|
|
15
|
+
provenanceNote: string;
|
|
16
|
+
};
|
|
17
|
+
display: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function catalogModelAdvisory(cfg: TickmarkrConfig, catalog: CatalogReadResult, adapter: string, model: string, resolvedModel?: string): CatalogModelAdvisory;
|
|
20
|
+
/**
|
|
21
|
+
* Whether the doctor should add its optional window column. Keep non-TTY default output stable for
|
|
22
|
+
* machine consumers; an interactive seed-only matrix shows T14's fleet windows, and any explicit
|
|
23
|
+
* non-seed/overridden window keeps the historical operator-configured column behavior.
|
|
24
|
+
*/
|
|
8
25
|
export declare function hasWindowsConfig(cfg: TickmarkrConfig): boolean;
|
|
9
26
|
export declare function declaredModelWindow(cfg: TickmarkrConfig, adapter: string, model: string): number | undefined;
|
|
10
|
-
/**
|
|
11
|
-
export declare function estimateTaskPayloadTokens(task: Task, repoRoot: string, feedback?: string): number;
|
|
27
|
+
/** A numeric estimate exists only when every payload path is measurable from the worker-visible tree. */
|
|
28
|
+
export declare function estimateTaskPayloadTokens(task: Task, repoRoot: string, feedback?: string): number | undefined;
|
|
12
29
|
export type RoutedAssignment = {
|
|
13
30
|
taskId: string;
|
|
14
31
|
adapter: string;
|
|
@@ -28,7 +45,10 @@ export declare function formatModelAuthLine(excluded: {
|
|
|
28
45
|
reason: string;
|
|
29
46
|
probedAt: string;
|
|
30
47
|
}[], tty?: boolean, stateDir?: string): string;
|
|
31
|
-
export declare function suggestOverlay(cfg: TickmarkrConfig, health: Record<string, AuthHealth>, adapters: WorkerAdapter[], stateDir?: string
|
|
48
|
+
export declare function suggestOverlay(cfg: TickmarkrConfig, health: Record<string, AuthHealth>, adapters: WorkerAdapter[], stateDir?: string, opts?: {
|
|
49
|
+
catalog?: CatalogReadResult;
|
|
50
|
+
resolvedModel?: (adapter: string, model: string) => string | undefined;
|
|
51
|
+
}): string;
|
|
32
52
|
/** Unclassified models surfaced for fleet screen 2 (doctor matrix math, no tier fabrication). */
|
|
33
53
|
export declare function fleetUnclassifiedModels(cfg: TickmarkrConfig, health: Record<string, AuthHealth>, adapters: WorkerAdapter[]): {
|
|
34
54
|
adapter: string;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
2
3
|
import { join } from "node:path";
|
|
3
4
|
import { DEFAULT_CONFIG, TIER_RANK } from "../config/config.js";
|
|
4
5
|
import { buildTaskPrompt } from "./prompt.js";
|
|
5
6
|
import { MODEL_ID_RE } from "./types.js";
|
|
7
|
+
import { resolveCatalogModel } from "./catalog-remote.js";
|
|
6
8
|
export const SEED_STAMPED = "2026-07-09";
|
|
7
9
|
// knowledge past this age gets a "rerun tickmarkr doctor" nudge (BLOCKED_POLL_MS-style named constant).
|
|
8
10
|
export const MODEL_STALE_DAYS = 30;
|
|
@@ -15,41 +17,154 @@ const LINT_CAP = 5;
|
|
|
15
17
|
const TTY_LINT_CAP = 3;
|
|
16
18
|
const DEFAULT_STATE_DIR = ".tickmarkr";
|
|
17
19
|
const doctorJsonRef = (stateDir) => ` — see ${stateDir}/doctor.json`;
|
|
20
|
+
const DECLARED_SEED_PREFER_DISPOSITION = "no declared preference overrides it";
|
|
21
|
+
const LEGACY_DOCTOR_SEED_PREFER_DISPOSITION = "auto-prefer is routing around it";
|
|
18
22
|
export const ttyVisual = () => process.stdout.isTTY === true && process.env.NO_COLOR === undefined;
|
|
19
23
|
// ponytail: chars/4 token heuristic — good enough for advisory plan lint; no tokenizer dep.
|
|
20
24
|
const CHARS_PER_TOKEN = 4;
|
|
21
|
-
|
|
22
|
-
|
|
25
|
+
const intelligenceTier = (index) => index >= 65 ? "frontier" : index >= 40 ? "mid" : "cheap";
|
|
26
|
+
const priceTier = (outputPerMtok) => outputPerMtok >= 12 ? "frontier" : outputPerMtok >= 2 ? "mid" : "cheap";
|
|
27
|
+
function catalogEvidenceNote(evidence, catalog) {
|
|
28
|
+
const cost = evidence.inputCostPerMtok !== undefined && evidence.outputCostPerMtok !== undefined
|
|
29
|
+
? `$${evidence.inputCostPerMtok}/$${evidence.outputCostPerMtok} per Mtok`
|
|
30
|
+
: "not reported";
|
|
31
|
+
const features = evidence.features.length ? evidence.features.join(",") : "none reported";
|
|
32
|
+
const intelligence = evidence.intelligenceIndex !== undefined
|
|
33
|
+
? `; Artificial Analysis Intelligence Index=${evidence.intelligenceIndex}${evidence.intelligenceIndexVersion ? ` (version ${evidence.intelligenceIndexVersion})` : ""}`
|
|
34
|
+
: "";
|
|
35
|
+
const freshness = catalog.stale ? `${catalog.source} stale cache` : catalog.source;
|
|
36
|
+
return `models.dev id=${evidence.modelId}; cost=${cost}; context=${evidence.contextWindow ?? "not reported"}; features=${features}${intelligence}; catalog=${freshness}; fetchedAt=${catalog.catalog.fetchedAt}`;
|
|
37
|
+
}
|
|
38
|
+
export function catalogModelAdvisory(cfg, catalog, adapter, model, resolvedModel) {
|
|
39
|
+
const entry = cfg.tiers[adapter];
|
|
40
|
+
const evidence = resolveCatalogModel(catalog.catalog, {
|
|
41
|
+
// A tier vendor is NULLABLE (config.ts:64) — null means "declared as having none at tier level, so
|
|
42
|
+
// each model must declare its own". `resolveCatalogModel` takes an optional provider HINT
|
|
43
|
+
// (catalog-remote.ts:218), and "no hint" is `undefined` there, so null normalizes to undefined rather
|
|
44
|
+
// than travelling as a third state. Neither T17 nor T19 could see this seam: their files[] are
|
|
45
|
+
// disjoint, so no ordering edge was derived, they dispatched 7ms apart, and each worktree was cut
|
|
46
|
+
// before the other's change existed. Both gates passed truthfully; only the merged pair fails to
|
|
47
|
+
// compile (OBS-371).
|
|
48
|
+
provider: entry?.vendor ?? undefined,
|
|
49
|
+
model,
|
|
50
|
+
...(resolvedModel ? { resolvedModel } : {}),
|
|
51
|
+
});
|
|
52
|
+
if (!evidence) {
|
|
53
|
+
return {
|
|
54
|
+
coverage: "uncovered",
|
|
55
|
+
display: `${model} — uncovered by ${catalog.source === "cache" ? "cached catalogs" : "vendored catalog"}; no tier suggestion`,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const note = catalogEvidenceNote(evidence, catalog);
|
|
59
|
+
const basis = evidence.intelligenceIndex !== undefined
|
|
60
|
+
? { basis: "intelligence", tier: intelligenceTier(evidence.intelligenceIndex) }
|
|
61
|
+
: entry?.channel === "api" && evidence.outputCostPerMtok !== undefined
|
|
62
|
+
? { basis: "price", tier: priceTier(evidence.outputCostPerMtok) }
|
|
63
|
+
: undefined;
|
|
64
|
+
if (!basis) {
|
|
65
|
+
const reason = entry?.channel === "sub" && evidence.outputCostPerMtok !== undefined
|
|
66
|
+
? "subscription billing; no price-derived suggestion"
|
|
67
|
+
: "no tier suggestion from available evidence";
|
|
68
|
+
return { coverage: "covered", evidence, display: `${model} → ${note}; ${reason}` };
|
|
69
|
+
}
|
|
70
|
+
const provenanceNote = `SUGGESTED ${basis.tier} (${basis.basis} inference, not a measurement) — ${note}; operator confirmation required`;
|
|
71
|
+
return {
|
|
72
|
+
coverage: "covered",
|
|
73
|
+
evidence,
|
|
74
|
+
suggestion: { tier: basis.tier, kind: "inference", basis: basis.basis, provenanceNote },
|
|
75
|
+
display: `${model} → ${note}; ${provenanceNote}`,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function hasAnyWindows(cfg) {
|
|
23
79
|
return Object.values(cfg.tiers).some((t) => t.windows && Object.keys(t.windows).length > 0);
|
|
24
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Whether the doctor should add its optional window column. Keep non-TTY default output stable for
|
|
83
|
+
* machine consumers; an interactive seed-only matrix shows T14's fleet windows, and any explicit
|
|
84
|
+
* non-seed/overridden window keeps the historical operator-configured column behavior.
|
|
85
|
+
*/
|
|
86
|
+
export function hasWindowsConfig(cfg) {
|
|
87
|
+
const hasOperatorWindow = Object.entries(cfg.tiers).some(([adapter, entry]) => Object.entries(entry.windows ?? {}).some(([model, window]) => DEFAULT_CONFIG.tiers[adapter]?.windows?.[model] !== window));
|
|
88
|
+
if (hasOperatorWindow)
|
|
89
|
+
return true;
|
|
90
|
+
const seedOnly = Object.keys(cfg.tiers).every((adapter) => adapter in DEFAULT_CONFIG.tiers);
|
|
91
|
+
return ttyVisual() && seedOnly && hasAnyWindows(cfg);
|
|
92
|
+
}
|
|
25
93
|
export function declaredModelWindow(cfg, adapter, model) {
|
|
26
94
|
return cfg.tiers[adapter]?.windows?.[model];
|
|
27
95
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
96
|
+
const GLOB_CHARS = /[*?{[]/;
|
|
97
|
+
// T13's visibility oracle is the committed base tree: workers are created from HEAD, not from the
|
|
98
|
+
// author's checkout or index. Keep --full-tree load-bearing for callers below the repository root.
|
|
99
|
+
function baseTreeAtHead(dir) {
|
|
100
|
+
const git = (...args) => spawnSync("git", ["-C", dir, ...args], { encoding: "utf8", maxBuffer: 1 << 28 });
|
|
101
|
+
const top = git("rev-parse", "--show-toplevel");
|
|
102
|
+
const tree = git("ls-tree", "--full-tree", "-r", "--name-only", "HEAD");
|
|
103
|
+
if (top.status !== 0 || tree.status !== 0 || typeof top.stdout !== "string" || typeof tree.stdout !== "string")
|
|
104
|
+
return undefined;
|
|
105
|
+
return { root: top.stdout.trim(), tracked: new Set(tree.stdout.split("\n").filter(Boolean)) };
|
|
106
|
+
}
|
|
107
|
+
function fileBytes(repoRoot, rel, tree = baseTreeAtHead(repoRoot)) {
|
|
108
|
+
if (GLOB_CHARS.test(rel))
|
|
109
|
+
return { status: "unreadable", reason: "glob" };
|
|
110
|
+
if (!tree)
|
|
111
|
+
return { status: "unreadable", reason: "base-tree-unavailable" };
|
|
112
|
+
const path = rel.replace(/\/+$/, "");
|
|
113
|
+
if (!tree.tracked.has(path)) {
|
|
114
|
+
if ([...tree.tracked].some((tracked) => tracked.startsWith(`${path}/`))) {
|
|
115
|
+
return { status: "unreadable", reason: "directory" };
|
|
116
|
+
}
|
|
117
|
+
return { status: "unreadable", reason: existsSync(join(tree.root, path)) ? "untracked" : "absent" };
|
|
39
118
|
}
|
|
119
|
+
// Read the blob size from HEAD too: an unstaged/staged checkout edit is not what the worker receives.
|
|
120
|
+
const size = spawnSync("git", ["-C", tree.root, "cat-file", "-s", `HEAD:${path}`], { encoding: "utf8" });
|
|
121
|
+
const bytes = Number.parseInt(typeof size.stdout === "string" ? size.stdout.trim() : "", 10);
|
|
122
|
+
return size.status === 0 && Number.isSafeInteger(bytes) && bytes >= 0
|
|
123
|
+
? { status: "measured", bytes }
|
|
124
|
+
: { status: "unreadable", reason: "base-tree-read-failed" };
|
|
40
125
|
}
|
|
41
|
-
|
|
42
|
-
export function estimateTaskPayloadTokens(task, repoRoot, feedback = "") {
|
|
126
|
+
function taskPayloadResolution(task, repoRoot, feedback) {
|
|
43
127
|
let bytes = buildTaskPrompt(task, feedback).length;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
128
|
+
const unreadable = [];
|
|
129
|
+
const tree = baseTreeAtHead(repoRoot);
|
|
130
|
+
const add = (field, path) => {
|
|
131
|
+
const measured = fileBytes(repoRoot, path, tree);
|
|
132
|
+
if (measured.status === "measured")
|
|
133
|
+
bytes += measured.bytes;
|
|
134
|
+
else
|
|
135
|
+
unreadable.push({ field, path, reason: measured.reason });
|
|
136
|
+
};
|
|
137
|
+
for (const path of task.context)
|
|
138
|
+
add("context", path);
|
|
139
|
+
for (const path of task.files)
|
|
140
|
+
add("files", path);
|
|
141
|
+
return { tokens: Math.ceil(bytes / CHARS_PER_TOKEN), unreadable };
|
|
142
|
+
}
|
|
143
|
+
/** A numeric estimate exists only when every payload path is measurable from the worker-visible tree. */
|
|
144
|
+
export function estimateTaskPayloadTokens(task, repoRoot, feedback = "") {
|
|
145
|
+
const estimate = taskPayloadResolution(task, repoRoot, feedback);
|
|
146
|
+
return estimate.unreadable.length === 0 ? estimate.tokens : undefined;
|
|
147
|
+
}
|
|
148
|
+
function unreadablePayloadLint(taskId, unreadable) {
|
|
149
|
+
const describe = ({ field, path, reason }) => {
|
|
150
|
+
const prefix = `${field} ${JSON.stringify(path)}`;
|
|
151
|
+
if (reason === "untracked")
|
|
152
|
+
return `${prefix} is present in the checkout but not in the base tree`;
|
|
153
|
+
if (reason === "absent")
|
|
154
|
+
return `${prefix} is absent from the base tree and checkout`;
|
|
155
|
+
if (reason === "glob")
|
|
156
|
+
return `${prefix} is a glob and cannot be measured`;
|
|
157
|
+
if (reason === "directory")
|
|
158
|
+
return `${prefix} is a directory and cannot be measured as one file`;
|
|
159
|
+
if (reason === "base-tree-unavailable")
|
|
160
|
+
return `${prefix} cannot be checked because the base tree is unavailable`;
|
|
161
|
+
return `${prefix} could not be read from the base tree`;
|
|
162
|
+
};
|
|
163
|
+
return `${taskId}: payload unreadable — ${unreadable.map(describe).join(", ")}; context-window comparison skipped`;
|
|
49
164
|
}
|
|
50
165
|
/** Advisory only — absent windows config or undeclared model window ⇒ no lint. */
|
|
51
166
|
export function contextWindowLints(tasks, assignments, cfg, repoRoot) {
|
|
52
|
-
if (!
|
|
167
|
+
if (!hasAnyWindows(cfg))
|
|
53
168
|
return [];
|
|
54
169
|
const byId = new Map(assignments.map((a) => [a.taskId, a]));
|
|
55
170
|
const lints = [];
|
|
@@ -60,9 +175,13 @@ export function contextWindowLints(tasks, assignments, cfg, repoRoot) {
|
|
|
60
175
|
const window = declaredModelWindow(cfg, a.adapter, a.model);
|
|
61
176
|
if (window === undefined)
|
|
62
177
|
continue;
|
|
63
|
-
const
|
|
64
|
-
if (
|
|
65
|
-
lints.push(
|
|
178
|
+
const estimate = taskPayloadResolution(t, repoRoot, "");
|
|
179
|
+
if (estimate.unreadable.length > 0) {
|
|
180
|
+
lints.push(unreadablePayloadLint(t.id, estimate.unreadable));
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (estimate.tokens > window) {
|
|
184
|
+
lints.push(`${t.id}: payload ~${estimate.tokens} tokens exceeds ${a.adapter}:${a.model} window ${window}`);
|
|
66
185
|
}
|
|
67
186
|
}
|
|
68
187
|
return lints;
|
|
@@ -78,8 +197,7 @@ const adapterHasAuthedChannel = (adapterId, shape, cfg, health, adapters) => {
|
|
|
78
197
|
return true; // no per-model probe data — compat, not dead
|
|
79
198
|
return a.channels(cfg).some((c) => TIER_RANK[c.tier] >= TIER_RANK[minTier] && h.modelAuth?.[c.model]?.authed === true);
|
|
80
199
|
};
|
|
81
|
-
|
|
82
|
-
export function seedPreferLints(cfg, health, adapters, overlayPreferShapes = new Set()) {
|
|
200
|
+
function collectSeedPreferLints(cfg, health, adapters, overlayPreferShapes, disposition) {
|
|
83
201
|
const lints = [];
|
|
84
202
|
for (const shape of Object.keys(cfg.routing.map)) {
|
|
85
203
|
if (overlayPreferShapes.has(shape))
|
|
@@ -89,18 +207,23 @@ export function seedPreferLints(cfg, health, adapters, overlayPreferShapes = new
|
|
|
89
207
|
if (!cfg.tiers[adapterId])
|
|
90
208
|
continue;
|
|
91
209
|
if (!adapterHasAuthedChannel(adapterId, shape, cfg, health, adapters)) {
|
|
92
|
-
lints.push(`routing seed names dead adapter '${adapterId}' for shape '${shape}' —
|
|
210
|
+
lints.push(`routing seed names dead adapter '${adapterId}' for shape '${shape}' — ${disposition}`);
|
|
93
211
|
}
|
|
94
212
|
}
|
|
95
213
|
}
|
|
96
214
|
return lints;
|
|
97
215
|
}
|
|
216
|
+
// OBS-30 T2 / v1.86 T3: warn when a built-in seed prefer names an adapter with zero authed
|
|
217
|
+
// channels and no operator-declared preference overrides that seed for the shape.
|
|
218
|
+
export function seedPreferLints(cfg, health, adapters, overlayPreferShapes = new Set()) {
|
|
219
|
+
return collectSeedPreferLints(cfg, health, adapters, overlayPreferShapes, DECLARED_SEED_PREFER_DISPOSITION);
|
|
220
|
+
}
|
|
98
221
|
// v1.54 T3: dead-steering sweep — operator prefer entries (routing.map overlay shapes, review.prefer,
|
|
99
222
|
// consult.prefer) that can never match an installed channel are named at plan time; v1.53 T2 pins the
|
|
100
223
|
// no-match case as a silent no-op, which makes a typo invisible. Advisory only: reads config + doctor
|
|
101
|
-
// health (no live probes), never touches routing. Seed map prefers stay seedPreferLints' turf
|
|
102
|
-
//
|
|
103
|
-
//
|
|
224
|
+
// health (no live probes), never touches routing. Seed map prefers stay seedPreferLints' turf;
|
|
225
|
+
// mapShapes limits this sweep to operator-authored entries so a bare default fleet isn't double-linted.
|
|
226
|
+
// Entry grammar mirrors preferIndex: adapter | adapter:model (first colon).
|
|
104
227
|
export function preferEntryLints(cfg, health, mapShapes = new Set()) {
|
|
105
228
|
const lints = [];
|
|
106
229
|
const sweep = (surface, entries) => {
|
|
@@ -122,17 +245,15 @@ export function preferEntryLints(cfg, health, mapShapes = new Set()) {
|
|
|
122
245
|
sweep("consult.prefer", cfg.consult.prefer);
|
|
123
246
|
return lints;
|
|
124
247
|
}
|
|
125
|
-
// Diffs detected models (doctor.json) against configured tiers, both directions, per adapter
|
|
248
|
+
// Diffs detected models (doctor.json) against configured tiers, both directions, per installed adapter.
|
|
126
249
|
// No ` ! ` prefix here — the consumer (doctor rows / plan lints) owns that. Pre-v1.5 doctor.json (models:[], no
|
|
127
250
|
// modelsDetectedAt) is the compat baseline: `?.`/`?? []` everywhere, no zod (would reject old files).
|
|
128
251
|
export function modelLints(cfg, health, adapters, opts) {
|
|
129
252
|
const cap = opts?.tty ? TTY_LINT_CAP : LINT_CAP;
|
|
130
253
|
const doctorRef = opts?.tty ? doctorJsonRef(opts.stateDir ?? DEFAULT_STATE_DIR) : "";
|
|
131
254
|
const lints = [];
|
|
132
|
-
for (const
|
|
133
|
-
const
|
|
134
|
-
if (!adapter)
|
|
135
|
-
continue; // fake/overlay-only tier entry with no adapter — nothing to diff against
|
|
255
|
+
for (const adapter of adapters) {
|
|
256
|
+
const id = adapter.id;
|
|
136
257
|
if (!adapter.listModels) {
|
|
137
258
|
lints.push(`${id}: no model-list surface — seeds stamped ${SEED_STAMPED}; verify manually`);
|
|
138
259
|
continue;
|
|
@@ -144,7 +265,7 @@ export function modelLints(cfg, health, adapters, opts) {
|
|
|
144
265
|
lints.push(`${id}: no detection data — run tickmarkr doctor`);
|
|
145
266
|
continue; // no data to diff or age
|
|
146
267
|
}
|
|
147
|
-
const configured = Object.keys(cfg.tiers[id]
|
|
268
|
+
const configured = Object.keys(cfg.tiers[id]?.models ?? {});
|
|
148
269
|
for (const model of configured) {
|
|
149
270
|
if (!detected.includes(model)) {
|
|
150
271
|
lints.push(`${id}: tiers lists ${model} — CLI no longer reports it; tombstone it (${model}: null overlay) or verify the id`);
|
|
@@ -163,7 +284,13 @@ export function modelLints(cfg, health, adapters, opts) {
|
|
|
163
284
|
lints.push(`${id}: model knowledge is ${days} days old — rerun tickmarkr doctor`);
|
|
164
285
|
}
|
|
165
286
|
}
|
|
166
|
-
|
|
287
|
+
// v1.34 T3 byte-pins non-TTY doctor output. Doctor consumers are the ones that provide stateDir;
|
|
288
|
+
// keep that machine-facing compatibility surface stable while plan and direct lint callers state
|
|
289
|
+
// the current declared-preference mechanism truthfully.
|
|
290
|
+
const seedDisposition = opts?.stateDir !== undefined && opts.tty !== true
|
|
291
|
+
? LEGACY_DOCTOR_SEED_PREFER_DISPOSITION
|
|
292
|
+
: DECLARED_SEED_PREFER_DISPOSITION;
|
|
293
|
+
lints.push(...collectSeedPreferLints(cfg, health, adapters, opts?.overlayPreferShapes ?? new Set(), seedDisposition));
|
|
167
294
|
return lints;
|
|
168
295
|
}
|
|
169
296
|
// T2/T6: one lint per exclusion, naming the probe reason and date. TTY truncates reasons to 60 chars and
|
|
@@ -183,17 +310,17 @@ export function formatModelAuthLine(excluded, tty, stateDir = DEFAULT_STATE_DIR)
|
|
|
183
310
|
// machine never fabricates one — auto-tiering reopens the NaN-routing class). Removals render as LIVE
|
|
184
311
|
// `<id>: null` tombstones (deepMerge deletes the key). Pure function: no fs, no routing contact.
|
|
185
312
|
// Returns "" when no adapter has a delta. Mirrors modelLints' per-adapter guards exactly.
|
|
186
|
-
export function suggestOverlay(cfg, health, adapters, stateDir = DEFAULT_STATE_DIR) {
|
|
313
|
+
export function suggestOverlay(cfg, health, adapters, stateDir = DEFAULT_STATE_DIR, opts = {}) {
|
|
187
314
|
const blocks = [];
|
|
188
|
-
for (const
|
|
189
|
-
const
|
|
190
|
-
if (!adapter
|
|
191
|
-
continue; // no
|
|
315
|
+
for (const adapter of adapters) {
|
|
316
|
+
const id = adapter.id;
|
|
317
|
+
if (!adapter.listModels)
|
|
318
|
+
continue; // no list surface → nothing to diff (mirror modelLints)
|
|
192
319
|
const h = health[id];
|
|
193
320
|
const detected = h?.models ?? [];
|
|
194
321
|
if (detected.length === 0)
|
|
195
322
|
continue; // no detection data → don't guess a delta
|
|
196
|
-
const configured = Object.keys(cfg.tiers[id]
|
|
323
|
+
const configured = Object.keys(cfg.tiers[id]?.models ?? {});
|
|
197
324
|
const date = h?.modelsDetectedAt?.split("T")[0]; // best-effort day stamp
|
|
198
325
|
const detNote = date ? ` (detected ${date})` : "";
|
|
199
326
|
const lines = [];
|
|
@@ -220,11 +347,17 @@ export function suggestOverlay(cfg, health, adapters, stateDir = DEFAULT_STATE_D
|
|
|
220
347
|
for (const model of detected) {
|
|
221
348
|
if (configured.includes(model) || !MODEL_ID_RE.test(model) || LINT_VARIANT_RE.test(model))
|
|
222
349
|
continue;
|
|
223
|
-
if (!cfgPrefixes.has(providerPrefix(model)) && !cfgCanon.has(canonical(model))) {
|
|
350
|
+
if (configured.length > 0 && !cfgPrefixes.has(providerPrefix(model)) && !cfgCanon.has(canonical(model))) {
|
|
224
351
|
omitted++;
|
|
225
352
|
continue;
|
|
226
353
|
}
|
|
227
|
-
|
|
354
|
+
const advisory = opts.catalog
|
|
355
|
+
? catalogModelAdvisory(cfg, opts.catalog, id, model, opts.resolvedModel?.(id, model))
|
|
356
|
+
: undefined;
|
|
357
|
+
const guidance = advisory?.suggestion
|
|
358
|
+
? `provenance note (operator confirmation required): ${advisory.suggestion.provenanceNote}; choose a tier, then uncomment`
|
|
359
|
+
: `classify per benchmark policy (AA Index + SWE-bench Pro, dated), then uncomment${advisory ? ` — ${advisory.display}` : ""}`;
|
|
360
|
+
lines.push(` # ${model}: ??? #${date ? ` detected ${date} —` : ""} ${guidance}`);
|
|
228
361
|
}
|
|
229
362
|
if (omitted)
|
|
230
363
|
lines.push(` # (+${omitted} other detected id${omitted === 1 ? "" : "s"} not related to your configured models — see ${stateDir}/doctor.json)`);
|
|
@@ -258,14 +391,15 @@ function referenceWarning(cfg, adapterId, model) {
|
|
|
258
391
|
/** Unclassified models surfaced for fleet screen 2 (doctor matrix math, no tier fabrication). */
|
|
259
392
|
export function fleetUnclassifiedModels(cfg, health, adapters) {
|
|
260
393
|
const out = [];
|
|
261
|
-
for (const
|
|
262
|
-
|
|
263
|
-
continue;
|
|
394
|
+
for (const adapter of adapters) {
|
|
395
|
+
const id = adapter.id;
|
|
264
396
|
const h = health[id];
|
|
397
|
+
if (!h?.installed)
|
|
398
|
+
continue;
|
|
265
399
|
const detected = h?.models ?? [];
|
|
266
400
|
if (!detected.length)
|
|
267
401
|
continue;
|
|
268
|
-
const configured = new Set(Object.keys(cfg.tiers[id]
|
|
402
|
+
const configured = new Set(Object.keys(cfg.tiers[id]?.models ?? {}));
|
|
269
403
|
const date = h?.modelsDetectedAt?.split("T")[0];
|
|
270
404
|
for (const model of detected) {
|
|
271
405
|
if (configured.has(model) || LINT_VARIANT_RE.test(model))
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
declare const ModelWindowClaimSchema: z.ZodObject<{
|
|
3
|
+
modelId: z.ZodString;
|
|
4
|
+
window: z.ZodNumber;
|
|
5
|
+
source: z.ZodString;
|
|
6
|
+
readDate: z.ZodISODate;
|
|
7
|
+
}, z.core.$strict>;
|
|
8
|
+
export type ModelWindowClaim = Readonly<z.infer<typeof ModelWindowClaimSchema>>;
|
|
9
|
+
export type ModelWindowResolution = {
|
|
10
|
+
status: "declared";
|
|
11
|
+
claim: ModelWindowClaim;
|
|
12
|
+
} | {
|
|
13
|
+
status: "unknown";
|
|
14
|
+
modelId: string;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Validate cited model-window claims before production code can consume them.
|
|
18
|
+
* These values are vendor-published claims, not measurements made by tickmarkr.
|
|
19
|
+
*/
|
|
20
|
+
export declare function loadModelWindowClaims(input: unknown): readonly ModelWindowClaim[];
|
|
21
|
+
/** The closed, production-readable catalog of cited context-window claims. */
|
|
22
|
+
export declare const CITED_MODEL_WINDOWS: readonly Readonly<{
|
|
23
|
+
modelId: string;
|
|
24
|
+
window: number;
|
|
25
|
+
source: string;
|
|
26
|
+
readDate: string;
|
|
27
|
+
}>[];
|
|
28
|
+
export declare function resolveModelWindowClaim(modelId: string, input?: unknown): ModelWindowResolution;
|
|
29
|
+
/** Fail when either side contains a model id absent from the other side. */
|
|
30
|
+
export declare function assertModelWindowClaimsMatchSeededModels(seededModelIds: readonly string[], input?: unknown): void;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const ModelWindowClaimSchema = z
|
|
3
|
+
.object({
|
|
4
|
+
modelId: z.string().min(1),
|
|
5
|
+
window: z.number().int().positive(),
|
|
6
|
+
source: z.string().url(),
|
|
7
|
+
readDate: z.iso.date(),
|
|
8
|
+
})
|
|
9
|
+
.strict();
|
|
10
|
+
/**
|
|
11
|
+
* Validate cited model-window claims before production code can consume them.
|
|
12
|
+
* These values are vendor-published claims, not measurements made by tickmarkr.
|
|
13
|
+
*/
|
|
14
|
+
export function loadModelWindowClaims(input) {
|
|
15
|
+
const claims = z.array(ModelWindowClaimSchema).parse(input);
|
|
16
|
+
const seen = new Set();
|
|
17
|
+
for (const claim of claims) {
|
|
18
|
+
if (seen.has(claim.modelId))
|
|
19
|
+
throw new Error(`duplicate model id ${claim.modelId}`);
|
|
20
|
+
seen.add(claim.modelId);
|
|
21
|
+
}
|
|
22
|
+
return Object.freeze(claims.map((claim) => Object.freeze(claim)));
|
|
23
|
+
}
|
|
24
|
+
const ANTHROPIC_SOURCE = "https://platform.claude.com/docs/en/about-claude/models/overview";
|
|
25
|
+
const OPENAI_SOURCE = "https://developers.openai.com/api/docs/models";
|
|
26
|
+
const OPENAI_GPT_55_SOURCE = "https://developers.openai.com/api/docs/models/gpt-5.5";
|
|
27
|
+
const CURSOR_SOURCE = "https://cursor.com/help/ai-features/max-mode";
|
|
28
|
+
const ZAI_SOURCE = "https://z.ai/blog/glm-5.2";
|
|
29
|
+
const XAI_SOURCE = "https://docs.x.ai/developers/models/grok-4.5";
|
|
30
|
+
const KIMI_SOURCE = "https://www.kimi.com/code/docs/en/kimi-code-cli/configuration/config-files.html";
|
|
31
|
+
const READ_DATE = "2026-08-05";
|
|
32
|
+
const VENDORED_MODEL_WINDOW_CLAIMS = [
|
|
33
|
+
{ modelId: "fable", window: 1_000_000, source: ANTHROPIC_SOURCE, readDate: READ_DATE },
|
|
34
|
+
{ modelId: "opus", window: 1_000_000, source: ANTHROPIC_SOURCE, readDate: READ_DATE },
|
|
35
|
+
{ modelId: "sonnet", window: 1_000_000, source: ANTHROPIC_SOURCE, readDate: READ_DATE },
|
|
36
|
+
{ modelId: "haiku", window: 200_000, source: ANTHROPIC_SOURCE, readDate: READ_DATE },
|
|
37
|
+
{ modelId: "gpt-5.6-sol", window: 1_050_000, source: OPENAI_SOURCE, readDate: READ_DATE },
|
|
38
|
+
{ modelId: "gpt-5.5", window: 1_050_000, source: OPENAI_GPT_55_SOURCE, readDate: READ_DATE },
|
|
39
|
+
{ modelId: "gpt-5.6-terra", window: 1_050_000, source: OPENAI_SOURCE, readDate: READ_DATE },
|
|
40
|
+
{ modelId: "gpt-5.6-luna", window: 1_050_000, source: OPENAI_SOURCE, readDate: READ_DATE },
|
|
41
|
+
{ modelId: "composer-2.5", window: 200_000, source: CURSOR_SOURCE, readDate: READ_DATE },
|
|
42
|
+
{ modelId: "composer-2.5-fast", window: 200_000, source: CURSOR_SOURCE, readDate: READ_DATE },
|
|
43
|
+
{ modelId: "zai-coding-plan/glm-5.2", window: 1_000_000, source: ZAI_SOURCE, readDate: READ_DATE },
|
|
44
|
+
{ modelId: "zai/glm-5.2", window: 1_000_000, source: ZAI_SOURCE, readDate: READ_DATE },
|
|
45
|
+
{ modelId: "grok-4.5", window: 500_000, source: XAI_SOURCE, readDate: READ_DATE },
|
|
46
|
+
{ modelId: "grok-composer-2.5-fast", window: 200_000, source: CURSOR_SOURCE, readDate: READ_DATE },
|
|
47
|
+
{ modelId: "kimi-code/k3", window: 1_048_576, source: KIMI_SOURCE, readDate: READ_DATE },
|
|
48
|
+
{ modelId: "kimi-code/kimi-for-coding", window: 262_144, source: KIMI_SOURCE, readDate: READ_DATE },
|
|
49
|
+
{ modelId: "kimi-code/kimi-for-coding-highspeed", window: 262_144, source: KIMI_SOURCE, readDate: READ_DATE },
|
|
50
|
+
];
|
|
51
|
+
/** The closed, production-readable catalog of cited context-window claims. */
|
|
52
|
+
export const CITED_MODEL_WINDOWS = loadModelWindowClaims(VENDORED_MODEL_WINDOW_CLAIMS);
|
|
53
|
+
export function resolveModelWindowClaim(modelId, input = CITED_MODEL_WINDOWS) {
|
|
54
|
+
const claim = loadModelWindowClaims(input).find((entry) => entry.modelId === modelId);
|
|
55
|
+
return claim === undefined ? { status: "unknown", modelId } : { status: "declared", claim };
|
|
56
|
+
}
|
|
57
|
+
/** Fail when either side contains a model id absent from the other side. */
|
|
58
|
+
export function assertModelWindowClaimsMatchSeededModels(seededModelIds, input = CITED_MODEL_WINDOWS) {
|
|
59
|
+
const tableIds = new Set(loadModelWindowClaims(input).map((claim) => claim.modelId));
|
|
60
|
+
const seedIds = new Set(seededModelIds);
|
|
61
|
+
const tableOnly = [...tableIds].filter((id) => !seedIds.has(id)).sort();
|
|
62
|
+
const seedOnly = [...seedIds].filter((id) => !tableIds.has(id)).sort();
|
|
63
|
+
const differences = [
|
|
64
|
+
tableOnly.length ? `table-only model ids: ${tableOnly.join(", ")}` : "",
|
|
65
|
+
seedOnly.length ? `seed-only model ids: ${seedOnly.join(", ")}` : "",
|
|
66
|
+
].filter(Boolean);
|
|
67
|
+
if (differences.length)
|
|
68
|
+
throw new Error(differences.join("; "));
|
|
69
|
+
}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { type Task } from "../graph/schema.js";
|
|
2
|
+
import { type VerdictUnparseableCause } from "../gates/verdict-cause.js";
|
|
2
3
|
import type { WorkerResult } from "./types.js";
|
|
3
4
|
export declare function buildTaskPrompt(task: Task, feedback?: string, nonce?: string): string;
|
|
4
5
|
export declare function writePrompt(dir: string, task: Task, attempt: number, feedback?: string, nonce?: string): string;
|
|
5
6
|
export declare function trailerPattern(nonce: string): string;
|
|
6
7
|
export declare const NO_TRAILER_SUMMARY = "worker produced no TICKMARKR_RESULT trailer";
|
|
7
8
|
export declare const UNPARSEABLE_TRAILER_SUMMARY = "unparseable TICKMARKR_RESULT trailer";
|
|
8
|
-
export
|
|
9
|
+
export type ClassifiedWorkerResult = WorkerResult & {
|
|
10
|
+
cause?: VerdictUnparseableCause;
|
|
11
|
+
};
|
|
12
|
+
export declare function parseWorkerResult(raw: string, nonce: string): ClassifiedWorkerResult;
|
|
9
13
|
export type DeadChannelReason = "auth-required" | "setup-required" | "provider-outage" | "timeout";
|
|
10
14
|
export declare function classifyDeadChannel(result: WorkerResult): DeadChannelReason | undefined;
|