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
package/dist/run/daemon.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { createHash, randomBytes } from "node:crypto";
|
|
2
2
|
import { shq } from "../adapters/types.js";
|
|
3
|
-
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { appendFileSync, closeSync, existsSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { stringify } from "yaml";
|
|
7
7
|
import { classifyDeadChannel, NO_TRAILER_SUMMARY, trailerPattern, UNPARSEABLE_TRAILER_SUMMARY, writePrompt } from "../adapters/prompt.js";
|
|
8
|
-
import { allAdapters,
|
|
8
|
+
import { allAdapters, getAdapter, probeAll, readDoctor, rolePools } from "../adapters/registry.js";
|
|
9
9
|
import { addUsage, channelKey, matchesTrustDialog, QUOTA_RE } from "../adapters/types.js";
|
|
10
10
|
import { bannerShell, paneDispatchCommand } from "../brand.js";
|
|
11
11
|
import { DEFAULT_DIFF_CAP, globalConfigDir, loadConfigWithMode, readOverlayFile, repoOverlayPath, } from "../config/config.js";
|
|
@@ -631,6 +631,71 @@ async function cherryPickCommits(wt, commits) {
|
|
|
631
631
|
}
|
|
632
632
|
return carried;
|
|
633
633
|
}
|
|
634
|
+
// T7 (v1.86): a first run-end append that fails AFTER partial bytes landed leaves a torn tail at
|
|
635
|
+
// EOF with no newline; a blind retry would glue the run-end line onto those bytes and readJsonl's
|
|
636
|
+
// torn-line tolerance would drop the retry too — no terminal record despite a successful write.
|
|
637
|
+
// Terminating the torn fragment keeps it on disk (dropped as a malformed line, never truncated) so
|
|
638
|
+
// the retried run-end lands on a line of its own.
|
|
639
|
+
const terminateTornJournalTail = (journalPath) => {
|
|
640
|
+
if (!existsSync(journalPath))
|
|
641
|
+
return;
|
|
642
|
+
const size = statSync(journalPath).size;
|
|
643
|
+
if (size === 0)
|
|
644
|
+
return;
|
|
645
|
+
const fd = openSync(journalPath, "r");
|
|
646
|
+
try {
|
|
647
|
+
const tail = Buffer.alloc(1);
|
|
648
|
+
if (readSync(fd, tail, 0, 1, size - 1) === 1 && tail[0] !== 0x0a)
|
|
649
|
+
appendFileSync(journalPath, "\n");
|
|
650
|
+
}
|
|
651
|
+
finally {
|
|
652
|
+
closeSync(fd);
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
// T7 (v1.86): the fatal handler must never eat the error it reports. Both journal calls are guarded,
|
|
656
|
+
// so a sink failure is reported ALONGSIDE the original error (console.error — the dispatcher's
|
|
657
|
+
// operator-visible line stays the one-line form), never instead of it: a read failure degrades the
|
|
658
|
+
// duplicate-run-end check to "unknown" and fails toward recording; the append is retried ONCE; and
|
|
659
|
+
// a persistently unwritable sink reports a crash naming the journal path and carrying NO terminal
|
|
660
|
+
// record, rather than fabricating an ended run on evidence the harness could not write. (OBS-313:
|
|
661
|
+
// with the sink dead the crash CAUSE is unrecordable — recorded as an observation, not papered over.)
|
|
662
|
+
function recordFatalRunEnd(journal, runId, branch, err) {
|
|
663
|
+
const original = err instanceof Error ? err.message : String(err);
|
|
664
|
+
try {
|
|
665
|
+
if (journal.read().some((e) => e.event === "run-end"))
|
|
666
|
+
return; // already terminal — nothing to add
|
|
667
|
+
}
|
|
668
|
+
catch (readErr) {
|
|
669
|
+
console.error(`tickmarkr ${runId}: journal read failed while recording the fatal run-end (${readErr instanceof Error ? readErr.message : String(readErr)}) — original error: ${original}`);
|
|
670
|
+
}
|
|
671
|
+
const record = {
|
|
672
|
+
runId,
|
|
673
|
+
branch,
|
|
674
|
+
done: [],
|
|
675
|
+
failed: [],
|
|
676
|
+
human: [],
|
|
677
|
+
blocked: [],
|
|
678
|
+
pending: [],
|
|
679
|
+
phase: "setup",
|
|
680
|
+
fatal: true,
|
|
681
|
+
error: original,
|
|
682
|
+
};
|
|
683
|
+
try {
|
|
684
|
+
journal.append("run-end", undefined, record);
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
catch {
|
|
688
|
+
// one retry below — the first failure is reported only if the retry also fails
|
|
689
|
+
}
|
|
690
|
+
const journalPath = join(journal.dir, "journal.jsonl");
|
|
691
|
+
try {
|
|
692
|
+
terminateTornJournalTail(journalPath);
|
|
693
|
+
journal.append("run-end", undefined, record);
|
|
694
|
+
}
|
|
695
|
+
catch (retryErr) {
|
|
696
|
+
console.error(`tickmarkr ${runId}: run crashed — no terminal record written; journal sink unwritable at ${journalPath} (${retryErr instanceof Error ? retryErr.message : String(retryErr)}) — original error: ${original}`);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
634
699
|
export async function runDaemon(repoRoot, opts = {}) {
|
|
635
700
|
// v1.51 T2 / OBS-89 (v1.60): retired --quality env seam. Mode resolution owns premium routing;
|
|
636
701
|
// route() no longer reads the retired env at all, so the old entrypoint scrub is gone with it.
|
|
@@ -664,7 +729,12 @@ export async function runDaemon(repoRoot, opts = {}) {
|
|
|
664
729
|
// v1.51 T4: every dispatch provenance line begins with the mode and its source; when a pin won
|
|
665
730
|
// the route (the final "→ " segment is a pin, not a degraded-to-auto tail) it names the mode it bypassed.
|
|
666
731
|
const dispatchProvenance = (p) => `mode ${rm.mode.mode} (${rm.source})${p.split("→ ").pop().startsWith("pin ") ? ` — pin bypasses mode ${rm.mode.mode}` : ""} · ${p}`;
|
|
667
|
-
|
|
732
|
+
// v1.87 T2: one pool per seat role, built once. `channels` stays the WORKER pool — every routing
|
|
733
|
+
// call below reads it exactly as before — while the judge, review and consult seats each receive
|
|
734
|
+
// the pool their own deny scope allows, so routing.deny.workers benches a channel for dispatch
|
|
735
|
+
// without also removing it from the seats that verify the work.
|
|
736
|
+
const pools = rolePools(cfg, adapters, health);
|
|
737
|
+
const channels = pools.worker;
|
|
668
738
|
// v1.6 ROUTE-06: build the learned profile ONCE at startup (never per task, never in the comparator).
|
|
669
739
|
// No preview — the daemon honors routing.learned:off and gets undefined; this snapshot is immutable
|
|
670
740
|
// for the run, so this run's own telemetry never feeds back into its own routing.
|
|
@@ -1135,7 +1205,7 @@ export async function runDaemon(repoRoot, opts = {}) {
|
|
|
1135
1205
|
// D-07: consult panes self-clean when the verdict is read (keepLlm) — only "forever" keeps them.
|
|
1136
1206
|
// v1.54 T1: channels = this run's doctor-filtered live list — consult.prefer seat liveness
|
|
1137
1207
|
// is judged against it, never rebuilt from config (installed-but-unauthed seats would stall).
|
|
1138
|
-
{ keep: keepLlm, onSlot: keepLlm ? (s) => keptSlots.push(s) : undefined, runId, channels });
|
|
1208
|
+
{ keep: keepLlm, onSlot: keepLlm ? (s) => keptSlots.push(s) : undefined, runId, channels: pools.consult });
|
|
1139
1209
|
};
|
|
1140
1210
|
// returns true → continue attempting, false → task is terminal (parked)
|
|
1141
1211
|
// trigger (why the consult ran) is threaded in so the decompose/human park keeps its cause —
|
|
@@ -1281,7 +1351,7 @@ export async function runDaemon(repoRoot, opts = {}) {
|
|
|
1281
1351
|
journal.phaseStart(t.id, "gates");
|
|
1282
1352
|
const { results } = await runGates(resumedTask, {
|
|
1283
1353
|
worktree: wt, baseRef: taskBase, result: priorResult, author: gateAuthor,
|
|
1284
|
-
commands, baseline, channels, adapters, cfg, artifactDir: journal.dir,
|
|
1354
|
+
commands, baseline, channels: pools.review, judgeChannels: pools.judge, adapters, cfg, artifactDir: journal.dir,
|
|
1285
1355
|
// a recheck re-verifies a human's release: it never selects tests down, it runs the suite.
|
|
1286
1356
|
pipeline: "v185",
|
|
1287
1357
|
via: cfg.visibility.llm === "pane"
|
|
@@ -2441,7 +2511,7 @@ export async function runDaemon(repoRoot, opts = {}) {
|
|
|
2441
2511
|
journal.phaseStart(t.id, "gates");
|
|
2442
2512
|
({ results, commits } = await runGates(t, {
|
|
2443
2513
|
worktree: wt, baseRef: taskBase, result, author: assignment,
|
|
2444
|
-
commands, baseline, channels, adapters, cfg, artifactDir: journal.dir,
|
|
2514
|
+
commands, baseline, channels: pools.review, judgeChannels: pools.judge, adapters, cfg, artifactDir: journal.dir,
|
|
2445
2515
|
pipeline: "v185", selectTests: !testGateFailed,
|
|
2446
2516
|
via: cfg.visibility.llm === "pane"
|
|
2447
2517
|
? {
|
|
@@ -2719,20 +2789,11 @@ export async function runDaemon(repoRoot, opts = {}) {
|
|
|
2719
2789
|
return summary;
|
|
2720
2790
|
}
|
|
2721
2791
|
catch (err) {
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
failed: [],
|
|
2728
|
-
human: [],
|
|
2729
|
-
blocked: [],
|
|
2730
|
-
pending: [],
|
|
2731
|
-
phase: "setup",
|
|
2732
|
-
fatal: true,
|
|
2733
|
-
error: err instanceof Error ? err.message : String(err),
|
|
2734
|
-
});
|
|
2735
|
-
}
|
|
2792
|
+
// T7 (v1.86): guarded — a journal read/append failure while recording the fatal run-end is
|
|
2793
|
+
// reported alongside err, never instead of it; recordFatalRunEnd never throws, so the original
|
|
2794
|
+
// error (message, stack, cause) always reaches the caller verbatim.
|
|
2795
|
+
if (runStarted && !taskLoopStarted)
|
|
2796
|
+
recordFatalRunEnd(journal, runId, branch, err);
|
|
2736
2797
|
throw err;
|
|
2737
2798
|
}
|
|
2738
2799
|
finally {
|
package/dist/run/journal.js
CHANGED
|
@@ -195,7 +195,7 @@ export function structuredFindings(gate, details, _scopeFiles = []) {
|
|
|
195
195
|
const VOLATILE_TOKENS = [
|
|
196
196
|
[/\u001b\[[0-9;]*[a-zA-Z]/g, ""], // ANSI styling
|
|
197
197
|
[/\b\d{4}-\d{2}-\d{2}[T ][\d:]+(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?\b/g, "<ts>"], // timestamps
|
|
198
|
-
[/\brun-\d{8}-\d{6}
|
|
198
|
+
[/\brun-\d{8}-\d{6}(?:-\d{16})?\b/g, "<run>"], // run identifiers
|
|
199
199
|
[/\b0x[0-9a-fA-F]+\b/g, "<addr>"], // memory addresses
|
|
200
200
|
// An absolute path INTO the repo keeps its repo-relative tail — that tail IS identity (a defect in
|
|
201
201
|
// daemon.ts is not a defect in journal.ts); only the machine/worktree prefix ahead of it is volatile.
|
|
@@ -227,15 +227,67 @@ const eraseVolatile = (text) => VOLATILE_TOKENS.reduce((out, [re, replacement])
|
|
|
227
227
|
// difference and banned a retry that was never redundant. Newlines survive (payload spans cannot
|
|
228
228
|
// cross one) and the line-wise trim below finishes the job.
|
|
229
229
|
const collapseRuns = (text) => text.replace(/[^\S\n]+/g, " ");
|
|
230
|
+
// v1.85 T34: the runner's TALLY moves with the base, not with the defect. When another task merges
|
|
231
|
+
// ahead and adds test files, vitest re-counts the suite — `192 passed (197)` becomes `193 passed
|
|
232
|
+
// (198)` — while the FAIL headlines name the same defect in the same order, so a substantively
|
|
233
|
+
// identical gate failure re-fingerprinted and gate-fingerprint-cap never counted the repeat
|
|
234
|
+
// (measured on T21 in run-20260805-164546: details at 17:33:02 and 18:11:01 differ only so).
|
|
235
|
+
//
|
|
236
|
+
// Why provenance AND shape, and why not digits as a class: a blanket \d+ collapse also erases counts
|
|
237
|
+
// that ARE identity — the failed-assertion count, the failing-suite count, an exit status, an error
|
|
238
|
+
// code — and the rule above governs: a missed cap costs one extra round, a false cap bans a
|
|
239
|
+
// legitimate retry. So the mask is gated on PROVENANCE first: only lines inside the `failing tests:`
|
|
240
|
+
// block baseline.ts:209-219 emits (up to its blank-line/`new failure fingerprints` boundary) are
|
|
241
|
+
// eligible, which keeps review prose, quoted diffs and assertion payloads structurally unreachable —
|
|
242
|
+
// and that block's own `FAIL … > test name` headlines are excluded by SHAPE: only a line that starts
|
|
243
|
+
// with the runner's own `Tests`/`Test Files` summary token is a tally line.
|
|
244
|
+
//
|
|
245
|
+
// Masked tally FIELDS, exactly: the passed count, the skipped and todo counts, and the derived
|
|
246
|
+
// parenthesized total immediately following the tally sequence. Deliberately KEPT (a reader uses
|
|
247
|
+
// each of them to tell two failures apart): every `N failed` count on the line (which suites and how
|
|
248
|
+
// many assertions actually broke — the defect's identity), everything after the derived total (an
|
|
249
|
+
// appended `| exit 1`, an appended `(404)` code), and every other number anywhere.
|
|
250
|
+
const RUNNER_TALLY_LINE = /^([ \t]*(?:Test Files|Tests)[ \t]+)((?:\d+ (?:failed|passed|skipped|todo)[ \t]*\|[ \t]*)*\d+ (?:failed|passed|skipped|todo))([ \t]*\(\d+\))?(.*)$/;
|
|
251
|
+
const TALLY_MOVED_FIELD = /\d+ (?=passed|skipped|todo)/g;
|
|
252
|
+
const maskTallyLine = (line) => {
|
|
253
|
+
const m = RUNNER_TALLY_LINE.exec(line);
|
|
254
|
+
if (!m)
|
|
255
|
+
return line;
|
|
256
|
+
const fields = m[2].replace(TALLY_MOVED_FIELD, "#");
|
|
257
|
+
const total = (m[3] ?? "").replace(/\d+/, "#");
|
|
258
|
+
return m[1] + fields + total + m[4];
|
|
259
|
+
};
|
|
260
|
+
// Computed on the FULL details text BEFORE any payload split: every recorded tally-bearing detail
|
|
261
|
+
// carries the `failing tests:` header, so gating on it closes the false-line-start axis (the mask
|
|
262
|
+
// never sees a slice boundary) and the wrong-provenance axis in one place.
|
|
263
|
+
const maskRunnerTallies = (text) => {
|
|
264
|
+
let inBlock = false;
|
|
265
|
+
return text
|
|
266
|
+
.split("\n")
|
|
267
|
+
.map((line) => {
|
|
268
|
+
if (!inBlock) {
|
|
269
|
+
if (line.trim() === "failing tests:")
|
|
270
|
+
inBlock = true;
|
|
271
|
+
return line;
|
|
272
|
+
}
|
|
273
|
+
if (line.trim() === "" || line.startsWith("new failure fingerprints")) {
|
|
274
|
+
inBlock = false;
|
|
275
|
+
return line;
|
|
276
|
+
}
|
|
277
|
+
return maskTallyLine(line);
|
|
278
|
+
})
|
|
279
|
+
.join("\n");
|
|
280
|
+
};
|
|
230
281
|
/** Normalized identity of a gate failure: the same defect, seen twice, normalizes to the same bytes. */
|
|
231
282
|
export function normalizeGateFailure(details) {
|
|
283
|
+
const masked = maskRunnerTallies(details);
|
|
232
284
|
let out = "";
|
|
233
285
|
let last = 0;
|
|
234
|
-
for (const m of
|
|
235
|
-
out += collapseRuns(eraseVolatile(
|
|
286
|
+
for (const m of masked.matchAll(PAYLOAD_SPAN)) {
|
|
287
|
+
out += collapseRuns(eraseVolatile(masked.slice(last, m.index))) + m[0];
|
|
236
288
|
last = m.index + m[0].length;
|
|
237
289
|
}
|
|
238
|
-
out += collapseRuns(eraseVolatile(
|
|
290
|
+
out += collapseRuns(eraseVolatile(masked.slice(last)));
|
|
239
291
|
return out.split("\n").map((l) => l.trim()).filter(Boolean).join("\n");
|
|
240
292
|
}
|
|
241
293
|
// Two normalized-identical failures of one gate on one task buy no more rounds (the ladder cannot fix
|
|
@@ -538,9 +590,37 @@ export function engagementComparable(events, loadedHash) {
|
|
|
538
590
|
return { comparable: false, reason: "unbound" };
|
|
539
591
|
return recorded === loadedHash ? { comparable: true, recorded } : { comparable: false, reason: "mismatch", recorded };
|
|
540
592
|
}
|
|
593
|
+
const RUN_SEQUENCE_WIDTH = 16;
|
|
594
|
+
// The daemon mints before acquiring graph.lock, and each CLI invocation has fresh module state. Claim
|
|
595
|
+
// a repository-state ticket with mkdir's atomic EEXIST boundary instead: a losing process advances and
|
|
596
|
+
// retries, while the winning directory remains as the durable high-water evidence for later invocations.
|
|
597
|
+
function claimRunSequence(repoRoot = process.cwd()) {
|
|
598
|
+
const dir = join(tickmarkrDir(repoRoot), "run-id-sequence");
|
|
599
|
+
mkdirSync(dir, { recursive: true });
|
|
600
|
+
const allocated = readdirSync(dir).filter((name) => /^\d{16}$/.test(name));
|
|
601
|
+
let candidate = allocated.reduce((max, name) => {
|
|
602
|
+
const value = BigInt(name);
|
|
603
|
+
return value > max ? value : max;
|
|
604
|
+
}, 0n) + 1n;
|
|
605
|
+
while (true) {
|
|
606
|
+
const suffix = candidate.toString().padStart(RUN_SEQUENCE_WIDTH, "0");
|
|
607
|
+
if (suffix.length > RUN_SEQUENCE_WIDTH)
|
|
608
|
+
throw new Error("run id sequence exhausted");
|
|
609
|
+
try {
|
|
610
|
+
mkdirSync(join(dir, suffix));
|
|
611
|
+
return suffix;
|
|
612
|
+
}
|
|
613
|
+
catch (error) {
|
|
614
|
+
if (error.code !== "EEXIST")
|
|
615
|
+
throw error;
|
|
616
|
+
candidate += 1n;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
541
620
|
export function newRunId(now = new Date()) {
|
|
542
621
|
const p = (n, w = 2) => String(n).padStart(w, "0");
|
|
543
|
-
|
|
622
|
+
const instant = `${now.getUTCFullYear()}${p(now.getUTCMonth() + 1)}${p(now.getUTCDate())}-${p(now.getUTCHours())}${p(now.getUTCMinutes())}${p(now.getUTCSeconds())}`;
|
|
623
|
+
return `run-${instant}-${claimRunSequence()}`;
|
|
544
624
|
}
|
|
545
625
|
// Sol #4: one strict parser for every journal open/create path — generated run-… ids plus test
|
|
546
626
|
// suffix chars only; forbid path separators, dot-segments, and empty ids.
|
|
@@ -578,7 +658,7 @@ function readJsonl(path) {
|
|
|
578
658
|
return out;
|
|
579
659
|
}
|
|
580
660
|
// Cross-run telemetry for Phase-12 profile derivation: the last K runs' rows, each
|
|
581
|
-
// tagged with its runId (runIds are zero-padded run-
|
|
661
|
+
// tagged with its runId (runIds are zero-padded run-UTCYYYYMMDD-HHMMSS-sequence ⇒ plain .sort() is
|
|
582
662
|
// chronological, same as latestRunId). Rows are facts, not classifications — Phase 12
|
|
583
663
|
// owns the quality-denominator/reward policy. A safeParse failure drops that one row
|
|
584
664
|
// (same posture as a torn line); a garbage row must never crash profile derivation.
|
|
@@ -589,7 +669,7 @@ export function readAllTelemetry(repoRoot, lastK, opts = {}) {
|
|
|
589
669
|
if (!existsSync(dir))
|
|
590
670
|
return [];
|
|
591
671
|
let runIds = readdirSync(dir).filter((d) => d.startsWith("run-")).sort();
|
|
592
|
-
// VIS-03 reset cursor:
|
|
672
|
+
// VIS-03 reset cursor: UTC clock fields and a fixed-width sequence make string > chronological
|
|
593
673
|
if (opts.after)
|
|
594
674
|
runIds = runIds.filter((id) => id > opts.after);
|
|
595
675
|
runIds = runIds.slice(-lastK);
|
|
@@ -209,6 +209,18 @@ export type RegeneratedGoldenFrame = GoldenFrameCase & {
|
|
|
209
209
|
readonly emitted: string;
|
|
210
210
|
};
|
|
211
211
|
export declare function regenerateGoldenFrames(binaryVersion?: string): Promise<readonly RegeneratedGoldenFrame[]>;
|
|
212
|
+
/**
|
|
213
|
+
* Canonicalize the one release-owned token in an ACTIVE golden frame.
|
|
214
|
+
*
|
|
215
|
+
* The grammar is deliberately positional: plain output owns the whole first
|
|
216
|
+
* line, while a setup frame owns the right-anchored `v<semver> · binary ✓`
|
|
217
|
+
* suffix. The latter preserves the suffix's right edge, so a longer version
|
|
218
|
+
* does not turn renderer padding into drift while any actual alignment change
|
|
219
|
+
* still does. Retired rendered frames never enter this exception.
|
|
220
|
+
*/
|
|
221
|
+
export declare function normalizeGoldenFrameVersionForComparison(frame: GoldenFrameCase, bytes: string): string;
|
|
222
|
+
/** An absent committed frame is a mismatch; it is never an empty agreement. */
|
|
223
|
+
export declare function goldenFrameMatchesCommitted(frame: RegeneratedGoldenFrame, committed: string | undefined): boolean;
|
|
212
224
|
export declare function findGoldenFrameMismatches(regenerated: readonly RegeneratedGoldenFrame[], committed: ReadonlyMap<string, string>): string[];
|
|
213
225
|
export type RegeneratedColourFrame = ColourFrameCase & {
|
|
214
226
|
readonly output: string;
|
|
@@ -564,13 +564,49 @@ export async function regenerateGoldenFrames(binaryVersion) {
|
|
|
564
564
|
}
|
|
565
565
|
return regenerated;
|
|
566
566
|
}
|
|
567
|
+
const SEMVER_TOKEN = String.raw `\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?`;
|
|
568
|
+
const PLAIN_VERSION_HEADER = new RegExp(`^(tickmarkr v)(${SEMVER_TOKEN})$`, "u");
|
|
569
|
+
const FRAME_VERSION_HEADER = new RegExp(`^(.*\\S)( +)v(${SEMVER_TOKEN})( · binary ✓)$`, "u");
|
|
570
|
+
const COMPARISON_VERSION = "<binary-version>";
|
|
571
|
+
/**
|
|
572
|
+
* Canonicalize the one release-owned token in an ACTIVE golden frame.
|
|
573
|
+
*
|
|
574
|
+
* The grammar is deliberately positional: plain output owns the whole first
|
|
575
|
+
* line, while a setup frame owns the right-anchored `v<semver> · binary ✓`
|
|
576
|
+
* suffix. The latter preserves the suffix's right edge, so a longer version
|
|
577
|
+
* does not turn renderer padding into drift while any actual alignment change
|
|
578
|
+
* still does. Retired rendered frames never enter this exception.
|
|
579
|
+
*/
|
|
580
|
+
export function normalizeGoldenFrameVersionForComparison(frame, bytes) {
|
|
581
|
+
if (isRetiredGoldenFrame(frame))
|
|
582
|
+
return bytes;
|
|
583
|
+
const lineBreak = bytes.indexOf("\n");
|
|
584
|
+
const header = lineBreak === -1 ? bytes : bytes.slice(0, lineBreak);
|
|
585
|
+
const rest = lineBreak === -1 ? "" : bytes.slice(lineBreak);
|
|
586
|
+
let normalized = header;
|
|
587
|
+
if (!frame.interactive || frame.ci) {
|
|
588
|
+
normalized = header.replace(PLAIN_VERSION_HEADER, `$1${COMPARISON_VERSION}`);
|
|
589
|
+
}
|
|
590
|
+
else {
|
|
591
|
+
normalized = header.replace(FRAME_VERSION_HEADER, (_match, prefix, gap, versionToken, suffix) => `${prefix}${" ".repeat(gap.length + versionToken.length)}v${COMPARISON_VERSION}${suffix}`);
|
|
592
|
+
}
|
|
593
|
+
return normalized === header ? bytes : `${normalized}${rest}`;
|
|
594
|
+
}
|
|
595
|
+
/** An absent committed frame is a mismatch; it is never an empty agreement. */
|
|
596
|
+
export function goldenFrameMatchesCommitted(frame, committed) {
|
|
597
|
+
return committed !== undefined
|
|
598
|
+
&& normalizeGoldenFrameVersionForComparison(frame, committed)
|
|
599
|
+
=== normalizeGoldenFrameVersionForComparison(frame, frame.output);
|
|
600
|
+
}
|
|
567
601
|
export function findGoldenFrameMismatches(regenerated, committed) {
|
|
568
602
|
// Membership is judged against the whole shipped manifest, retired cases
|
|
569
603
|
// included: a retired fixture is claimed, so only its bytes are exempt.
|
|
570
604
|
// Anything committed that no case claims is still named.
|
|
571
605
|
const expected = new Set(GOLDEN_FRAME_CASES.map((frame) => frame.fixture));
|
|
572
606
|
return [
|
|
573
|
-
...regenerated.flatMap((frame) => committed.get(frame.fixture)
|
|
607
|
+
...regenerated.flatMap((frame) => goldenFrameMatchesCommitted(frame, committed.get(frame.fixture))
|
|
608
|
+
? []
|
|
609
|
+
: [frame.fixture]),
|
|
574
610
|
...[...committed.keys()].filter((fixture) => !expected.has(fixture)),
|
|
575
611
|
].sort();
|
|
576
612
|
}
|
|
@@ -170,24 +170,24 @@ function statePresentation(state) {
|
|
|
170
170
|
case "active":
|
|
171
171
|
return {
|
|
172
172
|
glyph: GLYPHS.toggleActive,
|
|
173
|
-
color:
|
|
173
|
+
color: COCKPIT_DATA_RAMP[1].hex,
|
|
174
174
|
};
|
|
175
175
|
case "inactive":
|
|
176
176
|
return { glyph: GLYPHS.toggleInactive, dimmed: true };
|
|
177
177
|
case "pass":
|
|
178
178
|
return {
|
|
179
179
|
glyph: GLYPHS.pass,
|
|
180
|
-
color:
|
|
180
|
+
color: COCKPIT_DATA_RAMP[1].hex,
|
|
181
181
|
};
|
|
182
182
|
case "fail":
|
|
183
183
|
return {
|
|
184
184
|
glyph: GLYPHS.fail,
|
|
185
|
-
color:
|
|
185
|
+
color: COCKPIT_INKS.failure.hex,
|
|
186
186
|
};
|
|
187
187
|
case "warn":
|
|
188
188
|
return {
|
|
189
189
|
glyph: GLYPHS.attention,
|
|
190
|
-
color:
|
|
190
|
+
color: COCKPIT_INKS.warning.hex,
|
|
191
191
|
};
|
|
192
192
|
case "neutral":
|
|
193
193
|
return { glyph: GLYPHS.neutral, dimmed: true };
|
|
@@ -201,7 +201,7 @@ export function Panel({ title, focused = false, width, flexGrow, children, }) {
|
|
|
201
201
|
const content = Children.map(children, (child) => typeof child === "string" || typeof child === "number"
|
|
202
202
|
? _jsx(BodyText, { children: child })
|
|
203
203
|
: child);
|
|
204
|
-
return (_jsxs(Box, { flexDirection: "column", flexGrow: flexGrow, width: width, paddingX: 1, borderStyle: focused ? "double" : "round", borderColor:
|
|
204
|
+
return (_jsxs(Box, { flexDirection: "column", flexGrow: flexGrow, width: width, paddingX: 1, borderStyle: focused ? "double" : "round", borderColor: focused ? COCKPIT_DATA_RAMP[0].hex : COCKPIT_DATA_RAMP[3].hex, borderDimColor: !focused, children: [_jsx(Box, { children: focused ? (_jsxs(_Fragment, { children: [_jsx(Text, { color: COCKPIT_DATA_RAMP[0].hex, children: GLYPHS.pointer }), _jsxs(BodyText, { emphasis: "strong", children: [" ", title] })] })) : (_jsx(BodyText, { emphasis: "strong", children: title })) }), _jsx(Box, { flexDirection: "column", children: content })] }));
|
|
205
205
|
}
|
|
206
206
|
export function CockpitGrid({ children, columns, columnContents, allocation, }) {
|
|
207
207
|
const items = Children.toArray(children);
|
|
@@ -229,7 +229,7 @@ export function Sparkline({ samples, }) {
|
|
|
229
229
|
const level = sparklineLevel(sample, minimum, maximum);
|
|
230
230
|
const rampIndex = COCKPIT_DATA_RAMP.length - 1 - Math.round((level / (SPARKLINE_GLYPHS.length - 1)) * (COCKPIT_DATA_RAMP.length - 1));
|
|
231
231
|
const ink = COCKPIT_DATA_RAMP[rampIndex] ?? COCKPIT_DATA_RAMP[0];
|
|
232
|
-
return (_jsx(Text, { color:
|
|
232
|
+
return (_jsx(Text, { color: ink.hex, children: SPARKLINE_GLYPHS[level] }, `${index}:${sample}`));
|
|
233
233
|
}) }));
|
|
234
234
|
}
|
|
235
235
|
export function StatTile({ label, value, samples, focused, width, }) {
|
|
@@ -242,7 +242,7 @@ export function ProgressMeter({ value, width = 24, }) {
|
|
|
242
242
|
const valueLabel = Number.isInteger(percentage)
|
|
243
243
|
? percentage.toFixed(0)
|
|
244
244
|
: percentage.toFixed(1);
|
|
245
|
-
return (_jsxs(Box, { flexDirection: "row", flexWrap: "nowrap", height: 1, overflow: "hidden", "aria-role": "progressbar", "aria-label": `${valueLabel}% complete`, children: [_jsx(Text, { color:
|
|
245
|
+
return (_jsxs(Box, { flexDirection: "row", flexWrap: "nowrap", height: 1, overflow: "hidden", "aria-role": "progressbar", "aria-label": `${valueLabel}% complete`, children: [_jsx(Text, { color: COCKPIT_DATA_RAMP[0].hex, children: "█".repeat(filled) }), _jsx(Text, { dimColor: true, children: "░".repeat(safeWidth - filled) }), _jsxs(BodyText, { emphasis: "strong", children: [" ", valueLabel, "%"] })] }));
|
|
246
246
|
}
|
|
247
247
|
function OneLineItems({ children, width, label, }) {
|
|
248
248
|
return (_jsx(Box, { flexDirection: "row", flexWrap: "nowrap", width: width, height: 1, overflow: "hidden", "aria-label": label, children: children }));
|
|
@@ -285,6 +285,6 @@ export function StatusStrip({ items, width, }) {
|
|
|
285
285
|
}
|
|
286
286
|
export function JournalRowPanel({ rows, width, focused, title = "JOURNAL", selection, hover, }) {
|
|
287
287
|
return (_jsx(Panel, { title: title, focused: focused, width: width, children: rows.map((row, index) => (_jsxs(Box, { flexDirection: "row", flexWrap: "nowrap", height: 1, overflow: "hidden", children: [_jsx(BodyText, { emphasis: "dim", children: row.time }), _jsx(BodyText, { children: " " }), _jsx(StateGlyph, { state: row.state }), _jsx(BodyText, { emphasis: index === hover ? "strong" : "normal", children: index === hover ? HOVER_MARKER : " " }), index === selection && (_jsxs(BodyText, { emphasis: "strong", children: [GLYPHS.pointer, " "] })), index === hover
|
|
288
|
-
? (_jsx(Text, { inverse: true, color:
|
|
288
|
+
? (_jsx(Text, { inverse: true, color: COCKPIT_DATA_RAMP[0].hex, children: row.text }))
|
|
289
289
|
: _jsx(BodyText, { children: row.text })] }, row.id))) }));
|
|
290
290
|
}
|
|
@@ -7,19 +7,25 @@ export type ColourValue = {
|
|
|
7
7
|
export declare const LOGO_RAMP: readonly [84, 78, 41, 35];
|
|
8
8
|
/** Near-black panel fill against which every data shade must remain legible. */
|
|
9
9
|
export declare const COCKPIT_SURFACE: "#1a1a19";
|
|
10
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* Bright-to-deep, single-hue data ink. Depth communicates magnitude, never tile identity.
|
|
12
|
+
* Anchored on xterm 114 (#87d787) — operator-chosen 2026-08-03 — with the hue (OKLab 144.2°)
|
|
13
|
+
* held constant down the ramp; steps descend in even OKLab lightness (0.094) and were searched
|
|
14
|
+
* and verified through guardDataRamp below. hex is the rendered ink (truecolor terminals);
|
|
15
|
+
* xterm is the nearest-distinct 256-colour fallback, documentation not render source.
|
|
16
|
+
*/
|
|
11
17
|
export declare const COCKPIT_DATA_RAMP: readonly [{
|
|
12
|
-
readonly xterm:
|
|
13
|
-
readonly hex: "#
|
|
18
|
+
readonly xterm: 114;
|
|
19
|
+
readonly hex: "#87d787";
|
|
14
20
|
}, {
|
|
15
|
-
readonly xterm:
|
|
16
|
-
readonly hex: "#
|
|
21
|
+
readonly xterm: 71;
|
|
22
|
+
readonly hex: "#5bbc5e";
|
|
17
23
|
}, {
|
|
18
|
-
readonly xterm:
|
|
19
|
-
readonly hex: "#
|
|
24
|
+
readonly xterm: 65;
|
|
25
|
+
readonly hex: "#4f9a51";
|
|
20
26
|
}, {
|
|
21
27
|
readonly xterm: 29;
|
|
22
|
-
readonly hex: "#
|
|
28
|
+
readonly hex: "#437944";
|
|
23
29
|
}];
|
|
24
30
|
/** Documented near-misses retained as guard fixtures, not special cases in the guard. */
|
|
25
31
|
export declare const EXCLUDED_DATA_SHADES: {
|
|
@@ -38,17 +44,17 @@ export declare const EXCLUDED_DATA_SHADES: {
|
|
|
38
44
|
*/
|
|
39
45
|
export declare const COCKPIT_INKS: {
|
|
40
46
|
readonly dataRamp: readonly [{
|
|
41
|
-
readonly xterm:
|
|
42
|
-
readonly hex: "#
|
|
47
|
+
readonly xterm: 114;
|
|
48
|
+
readonly hex: "#87d787";
|
|
43
49
|
}, {
|
|
44
|
-
readonly xterm:
|
|
45
|
-
readonly hex: "#
|
|
50
|
+
readonly xterm: 71;
|
|
51
|
+
readonly hex: "#5bbc5e";
|
|
46
52
|
}, {
|
|
47
|
-
readonly xterm:
|
|
48
|
-
readonly hex: "#
|
|
53
|
+
readonly xterm: 65;
|
|
54
|
+
readonly hex: "#4f9a51";
|
|
49
55
|
}, {
|
|
50
56
|
readonly xterm: 29;
|
|
51
|
-
readonly hex: "#
|
|
57
|
+
readonly hex: "#437944";
|
|
52
58
|
}];
|
|
53
59
|
readonly warning: {
|
|
54
60
|
readonly xterm: 214;
|
|
@@ -65,17 +71,17 @@ export declare const COCKPIT_INKS: {
|
|
|
65
71
|
};
|
|
66
72
|
/** Every drawable cockpit ink; the panel surface and text emphasis are not inks. */
|
|
67
73
|
export declare const COCKPIT_INK_VOCABULARY: readonly [{
|
|
68
|
-
readonly xterm:
|
|
69
|
-
readonly hex: "#
|
|
74
|
+
readonly xterm: 114;
|
|
75
|
+
readonly hex: "#87d787";
|
|
70
76
|
}, {
|
|
71
|
-
readonly xterm:
|
|
72
|
-
readonly hex: "#
|
|
77
|
+
readonly xterm: 71;
|
|
78
|
+
readonly hex: "#5bbc5e";
|
|
73
79
|
}, {
|
|
74
|
-
readonly xterm:
|
|
75
|
-
readonly hex: "#
|
|
80
|
+
readonly xterm: 65;
|
|
81
|
+
readonly hex: "#4f9a51";
|
|
76
82
|
}, {
|
|
77
83
|
readonly xterm: 29;
|
|
78
|
-
readonly hex: "#
|
|
84
|
+
readonly hex: "#437944";
|
|
79
85
|
}, {
|
|
80
86
|
readonly xterm: 214;
|
|
81
87
|
readonly hex: "#ffaf00";
|
|
@@ -105,16 +111,16 @@ export declare const TEXT_EMPHASIS_TOKENS: {
|
|
|
105
111
|
/** Deliberate 16-colour choices. Renderers select this map; they do not infer one. */
|
|
106
112
|
export declare const REDUCED_COLOUR_MAPPING: {
|
|
107
113
|
readonly dataRamp: readonly [{
|
|
108
|
-
readonly from: "#
|
|
114
|
+
readonly from: "#87d787";
|
|
109
115
|
readonly to: "bright-green";
|
|
110
116
|
}, {
|
|
111
|
-
readonly from: "#
|
|
117
|
+
readonly from: "#5bbc5e";
|
|
112
118
|
readonly to: "green";
|
|
113
119
|
}, {
|
|
114
|
-
readonly from: "#
|
|
120
|
+
readonly from: "#4f9a51";
|
|
115
121
|
readonly to: "green";
|
|
116
122
|
}, {
|
|
117
|
-
readonly from: "#
|
|
123
|
+
readonly from: "#437944";
|
|
118
124
|
readonly to: "dim-green";
|
|
119
125
|
}];
|
|
120
126
|
readonly warning: {
|
|
@@ -3,12 +3,18 @@ import { BRAND_RAMP, GLYPHS } from "../../brand.js";
|
|
|
3
3
|
export const LOGO_RAMP = BRAND_RAMP;
|
|
4
4
|
/** Near-black panel fill against which every data shade must remain legible. */
|
|
5
5
|
export const COCKPIT_SURFACE = "#1a1a19";
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* Bright-to-deep, single-hue data ink. Depth communicates magnitude, never tile identity.
|
|
8
|
+
* Anchored on xterm 114 (#87d787) — operator-chosen 2026-08-03 — with the hue (OKLab 144.2°)
|
|
9
|
+
* held constant down the ramp; steps descend in even OKLab lightness (0.094) and were searched
|
|
10
|
+
* and verified through guardDataRamp below. hex is the rendered ink (truecolor terminals);
|
|
11
|
+
* xterm is the nearest-distinct 256-colour fallback, documentation not render source.
|
|
12
|
+
*/
|
|
7
13
|
export const COCKPIT_DATA_RAMP = [
|
|
8
|
-
{ xterm:
|
|
9
|
-
{ xterm:
|
|
10
|
-
{ xterm:
|
|
11
|
-
{ xterm: 29, hex: "#
|
|
14
|
+
{ xterm: 114, hex: "#87d787" },
|
|
15
|
+
{ xterm: 71, hex: "#5bbc5e" },
|
|
16
|
+
{ xterm: 65, hex: "#4f9a51" },
|
|
17
|
+
{ xterm: 29, hex: "#437944" },
|
|
12
18
|
];
|
|
13
19
|
/** Documented near-misses retained as guard fixtures, not special cases in the guard. */
|
|
14
20
|
export const EXCLUDED_DATA_SHADES = {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
import { type SaveTarget } from "../save.js";
|
|
3
2
|
export type FleetListRow = {
|
|
4
3
|
id: string;
|
|
5
4
|
content: ReactNode;
|
|
@@ -22,17 +21,3 @@ export declare function FleetReviewScreen({ title, legend, diff, }: {
|
|
|
22
21
|
legend: string;
|
|
23
22
|
diff: string;
|
|
24
23
|
}): import("react").JSX.Element;
|
|
25
|
-
export type DiffConfirmationProps = {
|
|
26
|
-
target: SaveTarget;
|
|
27
|
-
targetPath: string;
|
|
28
|
-
diff: string;
|
|
29
|
-
liveRun: boolean;
|
|
30
|
-
};
|
|
31
|
-
export declare function DiffConfirmationScreen(props: DiffConfirmationProps): import("react").JSX.Element;
|
|
32
|
-
export declare function StudioScreen({ tabs, active, lines, status, children, }: {
|
|
33
|
-
tabs: string[];
|
|
34
|
-
active: number;
|
|
35
|
-
lines: string[];
|
|
36
|
-
status?: string;
|
|
37
|
-
children?: ReactNode;
|
|
38
|
-
}): import("react").JSX.Element;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
3
|
import { GLYPHS } from "../../brand.js";
|
|
4
|
-
import { RELOAD_GUARD_NOTICE } from "../save.js";
|
|
5
4
|
function keyedLines(lines) {
|
|
6
5
|
const occurrences = new Map();
|
|
7
6
|
return lines.map((text) => {
|
|
@@ -24,19 +23,3 @@ export function FleetListScreen({ title, legend, rows, cursor, details = [], })
|
|
|
24
23
|
export function FleetReviewScreen({ title, legend, diff, }) {
|
|
25
24
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: title }), _jsx(Text, { dimColor: true, children: legend }), _jsx(Text, { children: diff })] }));
|
|
26
25
|
}
|
|
27
|
-
export function DiffConfirmationScreen(props) {
|
|
28
|
-
return _jsx(TextLines, { lines: [
|
|
29
|
-
`Save overlay — ${props.targetPath} (${props.target === "repo" ? "repository" : "global"} target)`,
|
|
30
|
-
"t toggle target · y confirm · n/esc cancel",
|
|
31
|
-
...(props.liveRun ? [RELOAD_GUARD_NOTICE] : []),
|
|
32
|
-
"─ diff ─",
|
|
33
|
-
props.diff || "(no overlay changes)",
|
|
34
|
-
] });
|
|
35
|
-
}
|
|
36
|
-
export function StudioScreen({ tabs, active, lines, status = "no staged changes", children, }) {
|
|
37
|
-
const tabBar = [
|
|
38
|
-
"tickmarkr ui ──",
|
|
39
|
-
...tabs.map((label, index) => `[${index + 1}]${index === active ? "*" : ""}${label}`),
|
|
40
|
-
].join(" ");
|
|
41
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: tabBar }), children ?? _jsx(TextLines, { lines: lines }), _jsx(Text, { dimColor: true, children: status })] }));
|
|
42
|
-
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type AuthHealth, type WorkerAdapter } from "../../adapters/types.js";
|
|
2
2
|
import type { MapEntry, RoutingMode, Tier } from "../../config/config.js";
|
|
3
3
|
import { type Shape } from "../../graph/schema.js";
|
|
4
4
|
export type FleetSteeringKey = "review" | "consult";
|
|
@@ -40,6 +40,7 @@ type AgentCli = {
|
|
|
40
40
|
};
|
|
41
41
|
export type FleetModelGroup = {
|
|
42
42
|
adapter: string;
|
|
43
|
+
vendor?: string;
|
|
43
44
|
rows: Array<{
|
|
44
45
|
model: string;
|
|
45
46
|
tier?: Tier;
|
|
@@ -51,6 +52,8 @@ export type FleetClassification = {
|
|
|
51
52
|
model: string;
|
|
52
53
|
tier: Tier;
|
|
53
54
|
note: string;
|
|
55
|
+
vendor?: string;
|
|
56
|
+
channel?: "sub" | "api";
|
|
54
57
|
};
|
|
55
58
|
export type FleetModeOption = {
|
|
56
59
|
id: RoutingMode;
|