tickmarkr 2.5.3 → 2.5.5
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/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 +54 -6
- package/dist/cli/commands/doctor.d.ts +4 -0
- package/dist/cli/commands/doctor.js +44 -29
- package/dist/cli/commands/fleet.js +195 -33
- package/dist/cli/commands/init.js +196 -6
- package/dist/cli/commands/plan.js +144 -3
- package/dist/cli/commands/resume.js +6 -2
- package/dist/cli/commands/run.js +21 -2
- package/dist/cli/commands/verify.js +1 -1
- package/dist/cli/help.d.ts +2 -0
- package/dist/cli/help.js +3 -1
- package/dist/compile/collateral.d.ts +2 -0
- package/dist/compile/collateral.js +50 -9
- package/dist/compile/ownership.d.ts +7 -0
- package/dist/compile/ownership.js +59 -22
- package/dist/config/config.d.ts +23 -8
- package/dist/config/config.js +42 -28
- package/dist/config/fleet-overlay.d.ts +3 -9
- package/dist/config/fleet-overlay.js +68 -11
- package/dist/config/fleet-why.d.ts +7 -0
- package/dist/config/fleet-why.js +5 -0
- package/dist/drivers/herdr.d.ts +5 -0
- package/dist/drivers/herdr.js +14 -3
- package/dist/drivers/index.d.ts +15 -1
- package/dist/drivers/index.js +38 -10
- package/dist/drivers/orca.d.ts +99 -10
- package/dist/drivers/orca.js +586 -97
- package/dist/drivers/types.d.ts +1 -0
- package/dist/gates/baseline.d.ts +3 -0
- package/dist/gates/baseline.js +2 -1
- package/dist/gates/cache.d.ts +100 -0
- package/dist/gates/cache.js +389 -0
- package/dist/gates/review.d.ts +35 -2
- package/dist/gates/review.js +64 -16
- package/dist/gates/run-gates.d.ts +5 -0
- package/dist/gates/run-gates.js +134 -18
- package/dist/gates/test-manifest.d.ts +99 -0
- package/dist/gates/test-manifest.js +389 -0
- package/dist/gates/test-reporter.d.ts +4 -0
- package/dist/gates/test-reporter.js +49 -0
- package/dist/route/preference.d.ts +22 -1
- package/dist/route/preference.js +123 -25
- package/dist/route/router.d.ts +13 -0
- package/dist/route/router.js +95 -17
- package/dist/run/daemon.d.ts +13 -0
- package/dist/run/daemon.js +398 -96
- package/dist/run/git.d.ts +35 -1
- package/dist/run/git.js +111 -10
- package/dist/run/journal.d.ts +14 -2
- package/dist/run/journal.js +78 -10
- package/dist/run/lease.d.ts +14 -0
- package/dist/run/lease.js +87 -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/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 +83 -31
- 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 +41 -27
- package/dist/tui/ink/fleet-app.js +204 -31
- package/package.json +1 -1
- package/skills/tickmarkr-overseer/SKILL.md +180 -99
package/dist/route/router.d.ts
CHANGED
|
@@ -33,3 +33,16 @@ export declare class RoutingError extends Error {
|
|
|
33
33
|
export declare function marginalCostRank(c: BillingChannel): number;
|
|
34
34
|
export declare function route(task: Task, cfg: TickmarkrConfig, channels: BillingChannel[], profile?: RoutingProfile, preferCtx?: RoutingPreferContext, exclude?: ReadonlySet<string>, exploreCtx?: ExploreContext): Route;
|
|
35
35
|
export declare function nextChannel(current: Assignment, task: Task, cfg: TickmarkrConfig, channels: BillingChannel[], tried: string[], profile?: RoutingProfile, exclude?: ReadonlySet<string>): Assignment | null;
|
|
36
|
+
export type ClimbPick = Assignment & {
|
|
37
|
+
climbed: boolean;
|
|
38
|
+
reason?: string;
|
|
39
|
+
};
|
|
40
|
+
export type ClimbResult = ClimbPick;
|
|
41
|
+
/**
|
|
42
|
+
* v2.5.4 OBS-986 (ES-1): climb one tier on request when untried channels exist in higher tiers.
|
|
43
|
+
* With routing.escalateTier on, returns the cheapest untried live channel strictly above current.tier,
|
|
44
|
+
* ordered as nextChannel orders (tier, then marginal cost, learned score strictly last), flagged climbed: true.
|
|
45
|
+
* When the knob is off, when current is frontier, or when no higher tier has an untried channel,
|
|
46
|
+
* returns nextChannel's same-or-higher pick flagged climbed: false naming the reason.
|
|
47
|
+
*/
|
|
48
|
+
export declare function climbChannel(current: Assignment, task: Task, cfg: TickmarkrConfig, channels: BillingChannel[], tried: string[], profile?: RoutingProfile, exclude?: ReadonlySet<string>): ClimbPick | null;
|
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 ?? [])
|
|
@@ -328,15 +353,8 @@ export function route(task, cfg, channels, profile, preferCtx, exclude, exploreC
|
|
|
328
353
|
maybeSlaLint(lints, task, profile, slaMinutes, eligible[0]);
|
|
329
354
|
return { assignment: toAssignment(eligible[0]), ladder: ladderFor(task, entry), lints, provenance: `${degraded}${bound}, marginal-cost auto (${chosenBy})`, ...(deviation ? { deviation } : {}) };
|
|
330
355
|
}
|
|
331
|
-
|
|
356
|
+
function candidatePool(current, channels, tried, tierPredicate, exclude) {
|
|
332
357
|
channels = withoutExcluded(channels, exclude);
|
|
333
|
-
// already cheapest-sufficient: TIER_RANK asc is the PRIMARY key so escalation climbs one band at a time.
|
|
334
|
-
// Do NOT "unify" this onto route()'s key order (marginal-cost first) — that reverses climb-one-band on mixed fleets (ROUTE-02, D2).
|
|
335
|
-
// ROUTE-13: learnedScore is the STRICTLY-LAST key — within-band tiebreak only. Precomputed
|
|
336
|
-
// outside the comparator (Pitfall 1), never arithmetic-combined with the band keys, no
|
|
337
|
-
// profile-dependent filter. NO exploration bonus here (route():110 has one; a probe on the
|
|
338
|
-
// failure path would spend a real retry). Absent profile ⇒ every score is 0 ⇒ third key
|
|
339
|
-
// all-ties ⇒ the stable sort preserves the exact v1.7 candidate ORDER.
|
|
340
358
|
const triedKeys = new Set(tried);
|
|
341
359
|
const triedIdentities = new Set(tried.map((key) => {
|
|
342
360
|
const channel = channels.find((c) => channelKey(c) === key);
|
|
@@ -348,11 +366,71 @@ export function nextChannel(current, task, cfg, channels, tried, profile, exclud
|
|
|
348
366
|
&& channels.filter((c) => c.adapter === current.adapter).every((c) => triedKeys.has(channelKey(c)));
|
|
349
367
|
const currentChannel = channels.find((c) => c.adapter === current.adapter && c.model === current.model);
|
|
350
368
|
const excludedProvider = currentAdapterExcluded ? routingModelProvider(current.model, currentChannel?.vendor) : undefined;
|
|
351
|
-
|
|
369
|
+
return channels.filter((c) => !triedIdentities.has(modelRouteIdentity(c.model, c.vendor))
|
|
352
370
|
&& (!excludedProvider || routingModelProvider(c.model, c.vendor) !== excludedProvider)
|
|
353
|
-
&&
|
|
371
|
+
&& tierPredicate(c.tier));
|
|
372
|
+
}
|
|
373
|
+
function rankFailoverCandidates(pool, task, cfg, profile) {
|
|
354
374
|
const scores = new Map(pool.map((c) => [channelKey(c), profile ? learnedScore(profile, task.shape, channelKey(c), c.channel, { availWeight: cfg.routing.learnedTuning?.availWeight }) : 0]));
|
|
355
375
|
const scoreOf = (c) => scores.get(channelKey(c));
|
|
356
|
-
|
|
376
|
+
return pool.sort((a, b) => TIER_RANK[a.tier] - TIER_RANK[b.tier] || marginalCostRank(a) - marginalCostRank(b) || scoreOf(b) - scoreOf(a));
|
|
377
|
+
}
|
|
378
|
+
export function nextChannel(current, task, cfg, channels, tried, profile, exclude) {
|
|
379
|
+
// already cheapest-sufficient: TIER_RANK asc is the PRIMARY key so escalation climbs one band at a time.
|
|
380
|
+
// Do NOT "unify" this onto route()'s key order (marginal-cost first) — that reverses climb-one-band on mixed fleets (ROUTE-02, D2).
|
|
381
|
+
// ROUTE-13: learnedScore is the STRICTLY-LAST key — within-band tiebreak only. Precomputed
|
|
382
|
+
// outside the comparator (Pitfall 1), never arithmetic-combined with the band keys, no
|
|
383
|
+
// profile-dependent filter. NO exploration bonus here (route():110 has one; a probe on the
|
|
384
|
+
// failure path would spend a real retry). Absent profile ⇒ every score is 0 ⇒ third key
|
|
385
|
+
// all-ties ⇒ the stable sort preserves the exact v1.7 candidate ORDER.
|
|
386
|
+
const pool = candidatePool(current, channels, tried, (tier) => TIER_RANK[tier] >= TIER_RANK[current.tier], exclude);
|
|
387
|
+
const candidates = rankFailoverCandidates(pool, task, cfg, profile);
|
|
357
388
|
return candidates.length ? toAssignment(candidates[0]) : null;
|
|
358
389
|
}
|
|
390
|
+
function makeClimbPick(assignment, climbed, reason) {
|
|
391
|
+
const pick = {
|
|
392
|
+
adapter: assignment.adapter,
|
|
393
|
+
model: assignment.model,
|
|
394
|
+
channel: assignment.channel,
|
|
395
|
+
tier: assignment.tier,
|
|
396
|
+
climbed,
|
|
397
|
+
...(climbed ? {} : { reason }),
|
|
398
|
+
};
|
|
399
|
+
Object.defineProperty(pick, "assignment", {
|
|
400
|
+
get() {
|
|
401
|
+
return {
|
|
402
|
+
adapter: this.adapter,
|
|
403
|
+
model: this.model,
|
|
404
|
+
channel: this.channel,
|
|
405
|
+
tier: this.tier,
|
|
406
|
+
};
|
|
407
|
+
},
|
|
408
|
+
enumerable: false,
|
|
409
|
+
});
|
|
410
|
+
return pick;
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* v2.5.4 OBS-986 (ES-1): climb one tier on request when untried channels exist in higher tiers.
|
|
414
|
+
* With routing.escalateTier on, returns the cheapest untried live channel strictly above current.tier,
|
|
415
|
+
* ordered as nextChannel orders (tier, then marginal cost, learned score strictly last), flagged climbed: true.
|
|
416
|
+
* When the knob is off, when current is frontier, or when no higher tier has an untried channel,
|
|
417
|
+
* returns nextChannel's same-or-higher pick flagged climbed: false naming the reason.
|
|
418
|
+
*/
|
|
419
|
+
export function climbChannel(current, task, cfg, channels, tried, profile, exclude) {
|
|
420
|
+
const escalateOn = cfg.routing.escalateTier !== "off";
|
|
421
|
+
if (!escalateOn) {
|
|
422
|
+
const pick = nextChannel(current, task, cfg, channels, tried, profile, exclude);
|
|
423
|
+
return pick ? makeClimbPick(pick, false, "routing.escalateTier knob is off") : null;
|
|
424
|
+
}
|
|
425
|
+
if (current.tier === "frontier") {
|
|
426
|
+
const pick = nextChannel(current, task, cfg, channels, tried, profile, exclude);
|
|
427
|
+
return pick ? makeClimbPick(pick, false, "no higher tier") : null;
|
|
428
|
+
}
|
|
429
|
+
const higherPool = candidatePool(current, channels, tried, (tier) => TIER_RANK[tier] > TIER_RANK[current.tier], exclude);
|
|
430
|
+
if (higherPool.length > 0) {
|
|
431
|
+
const candidates = rankFailoverCandidates(higherPool, task, cfg, profile);
|
|
432
|
+
return makeClimbPick(toAssignment(candidates[0]), true);
|
|
433
|
+
}
|
|
434
|
+
const pick = nextChannel(current, task, cfg, channels, tried, profile, exclude);
|
|
435
|
+
return pick ? makeClimbPick(pick, false, "no higher tier has an untried channel") : null;
|
|
436
|
+
}
|
package/dist/run/daemon.d.ts
CHANGED
|
@@ -126,6 +126,19 @@ export declare const WORKER_STARTUP_WINDOW_BYTES: number;
|
|
|
126
126
|
/** Test seam — exercises both sides of the startup boundary without a production-length fixture. */
|
|
127
127
|
export declare function setWorkerStartupWindowMsForTests(ms: number): void;
|
|
128
128
|
export declare function resetWorkerStartupWindowMsForTests(): void;
|
|
129
|
+
export interface StartupFailureEvidence {
|
|
130
|
+
matchedBytes: string;
|
|
131
|
+
offset: number;
|
|
132
|
+
row: string;
|
|
133
|
+
rowNumber: number;
|
|
134
|
+
}
|
|
135
|
+
/** One seat, one startup prefix. Once execution or its input box is seen, later panes are ineligible. */
|
|
136
|
+
export declare class StartupFailureDetector {
|
|
137
|
+
private readonly adapter;
|
|
138
|
+
private closed;
|
|
139
|
+
constructor(adapter: Pick<WorkerAdapter, "inputBox" | "harnessBannerRows">);
|
|
140
|
+
sample(output: string, launchedAt: number | undefined): StartupFailureEvidence | undefined;
|
|
141
|
+
}
|
|
129
142
|
/** Test seam — shrink the quota-banner silence gate without minute-long sleeps. */
|
|
130
143
|
export declare function setQuotaBannerSilentMsForTests(ms: number): void;
|
|
131
144
|
export declare function resetQuotaBannerSilentMsForTests(): void;
|