tickmarkr 2.5.4 → 2.5.6
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 +3 -1
- package/dist/adapters/registry.js +6 -1
- package/dist/adapters/types.d.ts +3 -0
- package/dist/cli/commands/approve.d.ts +1 -0
- package/dist/cli/commands/approve.js +118 -9
- package/dist/cli/commands/doctor.d.ts +10 -0
- package/dist/cli/commands/doctor.js +44 -29
- package/dist/cli/commands/fleet.js +199 -33
- package/dist/cli/commands/init.js +196 -6
- package/dist/cli/commands/plan.js +20 -4
- package/dist/cli/commands/resume.js +4 -2
- package/dist/cli/commands/run.js +11 -2
- package/dist/cli/commands/verify.js +102 -81
- package/dist/cli/help.d.ts +2 -0
- package/dist/cli/help.js +3 -1
- package/dist/config/config.d.ts +25 -8
- package/dist/config/config.js +41 -29
- package/dist/config/fleet-overlay.d.ts +3 -9
- package/dist/config/fleet-overlay.js +114 -19
- package/dist/config/fleet-why.d.ts +7 -0
- package/dist/config/fleet-why.js +5 -0
- package/dist/drivers/index.d.ts +15 -1
- package/dist/drivers/index.js +39 -10
- package/dist/drivers/orca.d.ts +119 -10
- package/dist/drivers/orca.js +781 -110
- package/dist/gates/baseline.d.ts +17 -3
- package/dist/gates/baseline.js +63 -15
- package/dist/gates/cache.d.ts +101 -0
- package/dist/gates/cache.js +401 -0
- package/dist/gates/llm.d.ts +3 -0
- package/dist/gates/llm.js +11 -0
- package/dist/gates/review.d.ts +28 -3
- package/dist/gates/review.js +106 -14
- package/dist/gates/run-gates.d.ts +9 -1
- package/dist/gates/run-gates.js +407 -102
- package/dist/gates/test-manifest.d.ts +128 -0
- package/dist/gates/test-manifest.js +463 -0
- package/dist/gates/test-reporter.d.ts +4 -0
- package/dist/gates/test-reporter.js +57 -0
- package/dist/graph/graph.d.ts +2 -0
- package/dist/graph/graph.js +45 -1
- package/dist/route/preference.d.ts +22 -1
- package/dist/route/preference.js +123 -25
- package/dist/route/router.js +31 -6
- package/dist/run/consult.js +5 -4
- package/dist/run/daemon.d.ts +15 -0
- package/dist/run/daemon.js +638 -167
- package/dist/run/execution-budget.d.ts +25 -0
- package/dist/run/execution-budget.js +142 -0
- package/dist/run/git.d.ts +50 -1
- package/dist/run/git.js +131 -12
- package/dist/run/journal.d.ts +16 -2
- package/dist/run/journal.js +115 -25
- package/dist/run/lease.d.ts +58 -0
- package/dist/run/lease.js +310 -0
- package/dist/run/merge.d.ts +2 -0
- package/dist/run/merge.js +91 -3
- package/dist/run/operator-state.d.ts +11 -0
- package/dist/run/operator-state.js +17 -3
- package/dist/run/recovery.d.ts +8 -0
- package/dist/run/recovery.js +25 -0
- package/dist/run/repair-selection.d.ts +12 -0
- package/dist/run/repair-selection.js +56 -0
- package/dist/run/stall.d.ts +6 -1
- package/dist/run/stall.js +60 -3
- package/dist/tui/cockpit/board.d.ts +96 -0
- package/dist/tui/cockpit/board.js +346 -0
- package/dist/tui/cockpit/decision-actions.js +2 -0
- package/dist/tui/cockpit/layout.d.ts +5 -1
- package/dist/tui/cockpit/layout.js +8 -3
- package/dist/tui/cockpit/live-runtime.js +71 -21
- package/dist/tui/cockpit/run-view.d.ts +7 -5
- package/dist/tui/cockpit/run-view.js +12 -11
- package/dist/tui/ink/fleet-app.d.ts +49 -27
- package/dist/tui/ink/fleet-app.js +229 -38
- package/package.json +2 -2
- package/skills/tickmarkr-overseer/SKILL.md +173 -113
- package/skills/tickmarkr-overseer/scripts/grade-ci.sh +29 -2
package/dist/graph/graph.js
CHANGED
|
@@ -156,9 +156,53 @@ export function addEvidence(g, id, patch) {
|
|
|
156
156
|
: t),
|
|
157
157
|
};
|
|
158
158
|
}
|
|
159
|
+
// OBS-1018: chain depth = number of downstream edges on the longest dependency path from a task to
|
|
160
|
+
// a leaf below it (a task nothing depends on is 0). Counted over the whole graph, status-blind: a
|
|
161
|
+
// root's critical path does not shrink because part of it already ran.
|
|
162
|
+
export function chainDepth(g) {
|
|
163
|
+
const dependents = new Map(g.tasks.map((t) => [t.id, []]));
|
|
164
|
+
for (const t of g.tasks)
|
|
165
|
+
for (const d of t.deps)
|
|
166
|
+
dependents.get(d)?.push(t.id);
|
|
167
|
+
const depth = new Map();
|
|
168
|
+
const visit = (id) => {
|
|
169
|
+
const known = depth.get(id);
|
|
170
|
+
if (known !== undefined)
|
|
171
|
+
return known;
|
|
172
|
+
const below = dependents.get(id) ?? [];
|
|
173
|
+
const value = below.length ? 1 + Math.max(...below.map(visit)) : 0; // acyclic: validateGraph rejects cycles
|
|
174
|
+
depth.set(id, value);
|
|
175
|
+
return value;
|
|
176
|
+
};
|
|
177
|
+
for (const t of g.tasks)
|
|
178
|
+
visit(t.id);
|
|
179
|
+
return depth;
|
|
180
|
+
}
|
|
181
|
+
// OBS-1018: admission is critical-path order — deepest chain root first, ties in declaration order.
|
|
182
|
+
// The daemon's dispatch loop slices this list unchanged. Stated limit: resume-restore of previously
|
|
183
|
+
// in-flight attempts keeps its own precedence (src/run/daemon.ts); only fresh admission is ordered here.
|
|
159
184
|
export function readyTasks(g) {
|
|
160
185
|
const done = new Set(g.tasks.filter((t) => t.status === "done").map((t) => t.id));
|
|
161
|
-
|
|
186
|
+
const depth = chainDepth(g);
|
|
187
|
+
return g.tasks
|
|
188
|
+
.filter((t) => t.status === "pending" && t.deps.every((d) => done.has(d)))
|
|
189
|
+
.sort((a, b) => depth.get(b.id) - depth.get(a.id)); // Array#sort is stable: equal depths keep declaration order
|
|
190
|
+
}
|
|
191
|
+
// OBS-1018: the dispatch wave each pending task would enter at `concurrency` slots if every wave
|
|
192
|
+
// took one tick — computed by draining readyTasks, so plan and daemon can never disagree on order.
|
|
193
|
+
// Tasks already past pending carry no wave.
|
|
194
|
+
export function dispatchWaves(g, concurrency) {
|
|
195
|
+
const waves = new Map();
|
|
196
|
+
let sim = g;
|
|
197
|
+
for (let wave = 1;; wave++) {
|
|
198
|
+
const batch = readyTasks(sim).slice(0, Math.max(1, concurrency));
|
|
199
|
+
if (!batch.length)
|
|
200
|
+
return waves;
|
|
201
|
+
for (const t of batch) {
|
|
202
|
+
waves.set(t.id, wave);
|
|
203
|
+
sim = setStatus(sim, t.id, "done");
|
|
204
|
+
}
|
|
205
|
+
}
|
|
162
206
|
}
|
|
163
207
|
export function isComplete(g) {
|
|
164
208
|
return g.tasks.every((t) => t.status === "done");
|
|
@@ -28,10 +28,31 @@ export declare function preferRanks(c: {
|
|
|
28
28
|
shape: string;
|
|
29
29
|
rank: number;
|
|
30
30
|
}[];
|
|
31
|
+
export declare function isExplicitIdOfAliasFamily(candidateModel: string, alias: string): boolean;
|
|
32
|
+
export declare function entryMatchesChannel(entry: string, c: {
|
|
33
|
+
adapter: string;
|
|
34
|
+
model: string;
|
|
35
|
+
identity?: string;
|
|
36
|
+
}, allowFamilyMatching?: boolean): boolean;
|
|
37
|
+
export interface ExclusionScope {
|
|
38
|
+
scope: string;
|
|
39
|
+
path: string;
|
|
40
|
+
configPath: string;
|
|
41
|
+
entry: string;
|
|
42
|
+
by: "deny" | "allow";
|
|
43
|
+
}
|
|
44
|
+
export declare function exclusionCollector(c: {
|
|
45
|
+
adapter: string;
|
|
46
|
+
model: string;
|
|
47
|
+
identity?: string;
|
|
48
|
+
}, routingOrCfg: TickmarkrConfig["routing"] | TickmarkrConfig, role?: PreferenceRole): ExclusionScope[];
|
|
49
|
+
export declare const collectExclusions: typeof exclusionCollector;
|
|
50
|
+
export declare const exclusionProvenance: typeof exclusionCollector;
|
|
31
51
|
export declare function disallowedBy(c: {
|
|
32
52
|
adapter: string;
|
|
33
53
|
model: string;
|
|
34
|
-
|
|
54
|
+
identity?: string;
|
|
55
|
+
}, routingOrCfg: TickmarkrConfig["routing"] | TickmarkrConfig, role?: PreferenceRole): Disallowed | null;
|
|
35
56
|
export interface DenyPreferCollision {
|
|
36
57
|
kind: "prefer" | "pin" | "pool";
|
|
37
58
|
shape: string;
|
package/dist/route/preference.js
CHANGED
|
@@ -5,19 +5,24 @@ const PREFERENCE_ROLES = ["worker", "judge", "review", "consult"];
|
|
|
5
5
|
/** Provider identity comes from the served model, not a gateway adapter's stamped vendor. */
|
|
6
6
|
export function modelProvider(model, fallback = "unknown") {
|
|
7
7
|
const id = model.toLowerCase();
|
|
8
|
-
const
|
|
9
|
-
|
|
8
|
+
const sepIdx = id.search(/[/:\s]/);
|
|
9
|
+
const prefix = sepIdx !== -1 ? id.slice(0, sepIdx) : "";
|
|
10
|
+
const remainder = sepIdx !== -1 ? id.slice(sepIdx + 1) : id;
|
|
11
|
+
if (prefix === "openai" || prefix === "openai-codex" || /^(?:gpt|o\d)/.test(remainder))
|
|
10
12
|
return "openai";
|
|
11
|
-
if (prefix === "anthropic" || /^(?:claude|opus|sonnet|haiku|fable)(?:-|$)/.test(
|
|
13
|
+
if (prefix === "anthropic" || /^(?:claude|opus|sonnet|haiku|fable)(?:-|$)/.test(remainder))
|
|
12
14
|
return "anthropic";
|
|
13
|
-
if (prefix === "google" || /^gemini(?:-|$)/.test(
|
|
15
|
+
if (prefix === "google" || /^gemini(?:-|$)/.test(remainder))
|
|
14
16
|
return "google";
|
|
15
|
-
if (prefix === "xai" || /^grok(?:-|$)/.test(id))
|
|
17
|
+
if (prefix === "xai" || prefix === "xai-oauth" || /^(?:grok|xai)(?:-|$)/.test(remainder) || id.startsWith("xai-oauth"))
|
|
16
18
|
return "xai";
|
|
17
|
-
if (["zai", "zhipu", "zai-coding-plan"].includes(prefix) || /^glm(?:-|$)/.test(
|
|
19
|
+
if (["zai", "zhipu", "zai-coding-plan"].includes(prefix) || /^glm(?:-|$)/.test(remainder))
|
|
18
20
|
return "zhipu";
|
|
19
|
-
if (["kimi-code", "moonshot"].includes(prefix) || /^kimi(?:-|$)/.test(
|
|
21
|
+
if (["kimi-code", "moonshot"].includes(prefix) || /^kimi(?:-|$)/.test(remainder))
|
|
20
22
|
return "moonshot";
|
|
23
|
+
const normalizedFallback = fallback.toLowerCase();
|
|
24
|
+
if (normalizedFallback === "xai-oauth" || normalizedFallback === "xai")
|
|
25
|
+
return "xai";
|
|
21
26
|
return fallback;
|
|
22
27
|
}
|
|
23
28
|
// Router naming remains explicit while sharing the review gate's exact function object.
|
|
@@ -51,7 +56,9 @@ export function excludedChannels(cfg, adapters, health) {
|
|
|
51
56
|
if (!health[id]?.installed || !health[id]?.authed)
|
|
52
57
|
continue;
|
|
53
58
|
for (const c of channelsFromConfig(id, cfg)) {
|
|
54
|
-
const
|
|
59
|
+
const identity = health[id]?.modelAuth?.[c.model]?.identity ?? health[id]?.modelIdentities?.[c.model];
|
|
60
|
+
const chan = identity ? { ...c, identity } : c;
|
|
61
|
+
const d = disallowedBy(chan, cfg.routing);
|
|
55
62
|
if (d)
|
|
56
63
|
out.push({ key: channelKey(c), d });
|
|
57
64
|
}
|
|
@@ -75,25 +82,116 @@ export function preferRanks(c, cfg) {
|
|
|
75
82
|
}
|
|
76
83
|
return out;
|
|
77
84
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (
|
|
90
|
-
return
|
|
85
|
+
export function isExplicitIdOfAliasFamily(candidateModel, alias) {
|
|
86
|
+
if (candidateModel === alias)
|
|
87
|
+
return true;
|
|
88
|
+
const tokens = candidateModel.toLowerCase().split(/[^a-z0-9]+/);
|
|
89
|
+
return tokens.includes(alias.toLowerCase());
|
|
90
|
+
}
|
|
91
|
+
export function entryMatchesChannel(entry, c, allowFamilyMatching = false) {
|
|
92
|
+
if (entry === c.adapter)
|
|
93
|
+
return true;
|
|
94
|
+
if (entry === c.model)
|
|
95
|
+
return true;
|
|
96
|
+
if (entry === channelKey(c))
|
|
97
|
+
return true;
|
|
98
|
+
if (c.identity) {
|
|
99
|
+
if (entry === c.identity)
|
|
100
|
+
return true;
|
|
101
|
+
if (entry === `${c.adapter}:${c.identity}`)
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
if (!c.identity && allowFamilyMatching) {
|
|
105
|
+
const colon = entry.indexOf(":");
|
|
106
|
+
if (colon !== -1) {
|
|
107
|
+
const entryAdapter = entry.slice(0, colon);
|
|
108
|
+
const entryModel = entry.slice(colon + 1);
|
|
109
|
+
if (entryAdapter === c.adapter && isExplicitIdOfAliasFamily(entryModel, c.model)) {
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
if (isExplicitIdOfAliasFamily(entry, c.model)) {
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
export function exclusionCollector(c, routingOrCfg, role = "worker") {
|
|
122
|
+
const routing = "routing" in routingOrCfg ? routingOrCfg.routing : routingOrCfg;
|
|
123
|
+
const out = [];
|
|
124
|
+
const { allow, deny } = routing ?? {};
|
|
125
|
+
for (const entry of deny?.adapters ?? []) {
|
|
126
|
+
if (entryMatchesChannel(entry, c, true)) {
|
|
127
|
+
out.push({
|
|
128
|
+
scope: "routing.deny.adapters",
|
|
129
|
+
path: "routing.deny.adapters",
|
|
130
|
+
configPath: "routing.deny.adapters",
|
|
131
|
+
entry,
|
|
132
|
+
by: "deny",
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
for (const entry of deny?.models ?? []) {
|
|
137
|
+
if (entryMatchesChannel(entry, c, true)) {
|
|
138
|
+
out.push({
|
|
139
|
+
scope: "routing.deny.models",
|
|
140
|
+
path: "routing.deny.models",
|
|
141
|
+
configPath: "routing.deny.models",
|
|
142
|
+
entry,
|
|
143
|
+
by: "deny",
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (role === "worker") {
|
|
148
|
+
for (const entry of deny?.workers?.adapters ?? []) {
|
|
149
|
+
if (entryMatchesChannel(entry, c, true)) {
|
|
150
|
+
out.push({
|
|
151
|
+
scope: "routing.deny.workers.adapters",
|
|
152
|
+
path: "routing.deny.workers.adapters",
|
|
153
|
+
configPath: "routing.deny.workers.adapters",
|
|
154
|
+
entry,
|
|
155
|
+
by: "deny",
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
for (const entry of deny?.workers?.models ?? []) {
|
|
160
|
+
if (entryMatchesChannel(entry, c, true)) {
|
|
161
|
+
out.push({
|
|
162
|
+
scope: "routing.deny.workers.models",
|
|
163
|
+
path: "routing.deny.workers.models",
|
|
164
|
+
configPath: "routing.deny.workers.models",
|
|
165
|
+
entry,
|
|
166
|
+
by: "deny",
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
91
171
|
if (allow) {
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
172
|
+
const allAllow = [...(allow.adapters ?? []), ...(allow.models ?? [])];
|
|
173
|
+
const admitted = allAllow.some((e) => entryMatchesChannel(e, c, false));
|
|
174
|
+
if (!admitted) {
|
|
175
|
+
out.push({
|
|
176
|
+
scope: "routing.allow",
|
|
177
|
+
path: "routing.allow",
|
|
178
|
+
configPath: "routing.allow",
|
|
179
|
+
entry: allAllow.join(", ") || "(empty allowlist)",
|
|
180
|
+
by: "allow",
|
|
181
|
+
});
|
|
182
|
+
}
|
|
95
183
|
}
|
|
96
|
-
return
|
|
184
|
+
return out;
|
|
185
|
+
}
|
|
186
|
+
export const collectExclusions = exclusionCollector;
|
|
187
|
+
export const exclusionProvenance = exclusionCollector;
|
|
188
|
+
// entries in either list accept adapter id, model id, or adapter:model (grammar A1)
|
|
189
|
+
export function disallowedBy(c, routingOrCfg, role = "worker") {
|
|
190
|
+
const exclusions = exclusionCollector(c, routingOrCfg, role);
|
|
191
|
+
if (exclusions.length === 0)
|
|
192
|
+
return null;
|
|
193
|
+
const first = exclusions[0];
|
|
194
|
+
return { by: first.by, entry: first.entry };
|
|
97
195
|
}
|
|
98
196
|
// Delegates to route()'s preflightPrefer — doctor/resume preflight reuses the router grammar,
|
|
99
197
|
// not a second colon/tier/model parser of the same config.
|
package/dist/route/router.js
CHANGED
|
@@ -103,6 +103,32 @@ function withoutExcluded(channels, exclude) {
|
|
|
103
103
|
// OBS-57: in-run demotion after consecutive no-trailer windows — route around poisoned channels for the rest of the run.
|
|
104
104
|
return channels.filter((c) => !exclude.has(channelKey(c)));
|
|
105
105
|
}
|
|
106
|
+
// Pools spread providers before tier, independently within each marginal-cost run. Stable
|
|
107
|
+
// cost sorting preserves declaration order when forming groups; gateways share the served
|
|
108
|
+
// model's provider, so adding a second transport does not buy that provider another turn.
|
|
109
|
+
function rankAnyPool(live, task) {
|
|
110
|
+
const sorted = [...live].sort((a, b) => marginalCostRank(a) - marginalCostRank(b));
|
|
111
|
+
const out = [];
|
|
112
|
+
for (let i = 0; i < sorted.length;) {
|
|
113
|
+
let j = i + 1;
|
|
114
|
+
while (j < sorted.length && marginalCostRank(sorted[j]) === marginalCostRank(sorted[i]))
|
|
115
|
+
j++;
|
|
116
|
+
const groups = new Map();
|
|
117
|
+
for (const c of sorted.slice(i, j)) {
|
|
118
|
+
const provider = routingModelProvider(c.model, c.vendor);
|
|
119
|
+
const group = groups.get(provider) ?? [];
|
|
120
|
+
group.push(c);
|
|
121
|
+
groups.set(provider, group);
|
|
122
|
+
}
|
|
123
|
+
const ordered = [...groups.values()];
|
|
124
|
+
const k = spreadOffset(task) % ordered.length;
|
|
125
|
+
for (const group of [...ordered.slice(k), ...ordered.slice(0, k)]) {
|
|
126
|
+
out.push(...group.sort((a, b) => TIER_RANK[a.tier] - TIER_RANK[b.tier]));
|
|
127
|
+
}
|
|
128
|
+
i = j;
|
|
129
|
+
}
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
106
132
|
const maybeSlaLint = (lints, task, profile, slaMinutes, c) => {
|
|
107
133
|
if (slaMinutes === undefined || !profile)
|
|
108
134
|
return;
|
|
@@ -206,21 +232,20 @@ export function route(task, cfg, channels, profile, preferCtx, exclude, exploreC
|
|
|
206
232
|
// live preserves pool declaration order — ordered mode and any-mode stable-sort ties depend on it
|
|
207
233
|
const live = pool.channels
|
|
208
234
|
.map((id) => channels.find((c) => channelKey(c) === id))
|
|
209
|
-
.filter((c) => c !== undefined);
|
|
235
|
+
.filter((c) => c !== undefined && (!taskFloor || TIER_RANK[c.tier] >= TIER_RANK[taskFloor]));
|
|
210
236
|
if (!live.length) {
|
|
211
|
-
throw new RoutingError(`${task.id}: routing.map.${task.shape}.pool has no live channel — declared: ${pool.channels.join(", ")}; doctor found: ${channels.map(channelKey).join(", ") || "(nothing)"}`);
|
|
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)"}`);
|
|
212
238
|
}
|
|
213
|
-
//
|
|
214
|
-
// upgrade path is feeding the pool-filtered set through the learned path below.
|
|
239
|
+
// No learned/explore inside pools: any spreads deterministically; ordered keeps declaration order.
|
|
215
240
|
const chosen = pool.mode === "ordered"
|
|
216
241
|
? live[0]
|
|
217
|
-
:
|
|
242
|
+
: rankAnyPool(live, task)[0];
|
|
218
243
|
lintMapPinFloor(chosen.tier, "pool");
|
|
219
244
|
maybeSlaLint(lints, task, profile, slaMinutes, chosen);
|
|
220
245
|
return { assignment: toAssignment(chosen), ladder: ladderFor(task, entry), lints, provenance: `${degraded}pool ${pool.mode} ${channelKey(chosen)} (config routing.map)` };
|
|
221
246
|
}
|
|
222
247
|
const baseTier = floor ?? "cheap";
|
|
223
|
-
//
|
|
248
|
+
// A task floor is hard on pool and floor/auto paths; the map-pin branch above stays supreme.
|
|
224
249
|
const minTier = taskFloor && TIER_RANK[taskFloor] > TIER_RANK[baseTier] ? taskFloor : baseTier;
|
|
225
250
|
if (prefActive)
|
|
226
251
|
for (const p of prefer ?? [])
|
package/dist/run/consult.js
CHANGED
|
@@ -164,10 +164,11 @@ opts = {}) {
|
|
|
164
164
|
out = r.stdout + r.stderr;
|
|
165
165
|
}
|
|
166
166
|
else {
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
|
|
170
|
-
|
|
167
|
+
// OBS-1009: the pane runs the HEADLESS command. A seeded interactive session answers on screen
|
|
168
|
+
// but never exits, so the exit trailer after it never runs, the wait runs to the stall cap and
|
|
169
|
+
// the 300-line read is a TUI frame. The headless command exits, the trailer prints, and the
|
|
170
|
+
// pane is harvested through dewrap exactly like a judge or review seat.
|
|
171
|
+
const command = adapter.headlessCommand(promptFile, seatModel);
|
|
171
172
|
// T8: role-first pane name for fleet visibility (consult · T2); consultSeq stays on the dossier artifact only
|
|
172
173
|
const slot = await driver.slot(cwd, gatePaneName("consult", d.taskId), {
|
|
173
174
|
label: `CONSULT ${d.taskId}`,
|
package/dist/run/daemon.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ import { type Baseline } from "../gates/baseline.js";
|
|
|
6
6
|
import type { GateResult } from "../gates/types.js";
|
|
7
7
|
import { Journal, type JournalEvent } from "./journal.js";
|
|
8
8
|
export declare function closeLiveSlot(liveSlots: Set<Slot>, driver: Pick<ExecutorDriver, "close">, slot: Slot): Promise<void>;
|
|
9
|
+
export declare function setAttemptHardTimeoutMsForTests(ms: number): void;
|
|
10
|
+
export declare function resetAttemptHardTimeoutMsForTests(): void;
|
|
9
11
|
export { harvestCpuFlatWindowMs, resetHarvestCpuFlatMsForTests, setHarvestCpuFlatMsForTests, workerTreeCpuMs, } from "./stall.js";
|
|
10
12
|
export interface RunOptions {
|
|
11
13
|
runId?: string;
|
|
@@ -126,6 +128,19 @@ export declare const WORKER_STARTUP_WINDOW_BYTES: number;
|
|
|
126
128
|
/** Test seam — exercises both sides of the startup boundary without a production-length fixture. */
|
|
127
129
|
export declare function setWorkerStartupWindowMsForTests(ms: number): void;
|
|
128
130
|
export declare function resetWorkerStartupWindowMsForTests(): void;
|
|
131
|
+
export interface StartupFailureEvidence {
|
|
132
|
+
matchedBytes: string;
|
|
133
|
+
offset: number;
|
|
134
|
+
row: string;
|
|
135
|
+
rowNumber: number;
|
|
136
|
+
}
|
|
137
|
+
/** One seat, one startup prefix. Once execution or its input box is seen, later panes are ineligible. */
|
|
138
|
+
export declare class StartupFailureDetector {
|
|
139
|
+
private readonly adapter;
|
|
140
|
+
private closed;
|
|
141
|
+
constructor(adapter: Pick<WorkerAdapter, "inputBox" | "harnessBannerRows">);
|
|
142
|
+
sample(output: string, launchedAt: number | undefined): StartupFailureEvidence | undefined;
|
|
143
|
+
}
|
|
129
144
|
/** Test seam — shrink the quota-banner silence gate without minute-long sleeps. */
|
|
130
145
|
export declare function setQuotaBannerSilentMsForTests(ms: number): void;
|
|
131
146
|
export declare function resetQuotaBannerSilentMsForTests(): void;
|