tickmarkr 1.84.0 → 1.86.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 +96 -0
- package/dist/adapters/catalog.js +176 -0
- package/dist/adapters/claude-code.d.ts +1 -0
- package/dist/adapters/claude-code.js +59 -1
- package/dist/adapters/fake.js +42 -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 +25 -26
- package/dist/adapters/registry.js +173 -110
- package/dist/adapters/types.d.ts +3 -0
- package/dist/adapters/types.js +36 -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/status.js +202 -46
- package/dist/compile/collateral.d.ts +86 -2
- package/dist/compile/collateral.js +294 -3
- 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 +161 -12
- package/dist/config/config.d.ts +82 -5
- package/dist/config/config.js +253 -66
- 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 +21 -3
- package/dist/drivers/herdr.js +344 -110
- package/dist/gates/acceptance.js +7 -2
- package/dist/gates/baseline.d.ts +1 -0
- package/dist/gates/baseline.js +91 -13
- package/dist/gates/llm.d.ts +0 -1
- package/dist/gates/llm.js +5 -30
- package/dist/gates/review.d.ts +9 -1
- package/dist/gates/review.js +105 -10
- package/dist/gates/run-gates.d.ts +9 -0
- package/dist/gates/run-gates.js +285 -41
- 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/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 +35 -25
- package/dist/run/daemon.d.ts +48 -2
- package/dist/run/daemon.js +1488 -330
- package/dist/run/journal.d.ts +56 -3
- package/dist/run/journal.js +358 -4
- package/dist/run/stall.d.ts +35 -1
- package/dist/run/stall.js +118 -8
- package/dist/tui/cockpit/capture.d.ts +12 -0
- package/dist/tui/cockpit/capture.js +37 -1
- package/dist/tui/cockpit/components.d.ts +2 -0
- package/dist/tui/cockpit/components.js +8 -8
- package/dist/tui/cockpit/derive.d.ts +29 -2
- package/dist/tui/cockpit/derive.js +219 -23
- package/dist/tui/cockpit/run-cockpit.js +128 -27
- 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/sample.native.md +1 -1
- package/package.json +1 -1
- package/skills/tickmarkr-overseer/SKILL.md +354 -34
- package/skills/tickmarkr-overseer/scripts/watch-artifacts.sh +70 -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/stall.js
CHANGED
|
@@ -18,6 +18,62 @@ const ELAPSED_RE = /(?<![\w.])\d+(?:\.\d+)?(?:ms|[hms])(?!\w)/g;
|
|
|
18
18
|
export function normalizeStallSnapshot(text) {
|
|
19
19
|
return text.replace(ANSI_RE, "").replace(SPINNER_RE, "").replace(ELAPSED_RE, "");
|
|
20
20
|
}
|
|
21
|
+
// T1 (OBS-262): the rescue nudge's adapter scope — claude-code only (steering path proven,
|
|
22
|
+
// OBS-122). Widening it is a future fixture-capture chore (an occupied-frame capture per adapter,
|
|
23
|
+
// OBS-181 scar), never a drive-by edit. Lives in the stall module so the watchdog's policy and its
|
|
24
|
+
// scope constant cannot drift apart.
|
|
25
|
+
export const NUDGEABLE_ADAPTERS = new Set(["claude-code"]);
|
|
26
|
+
// T1 (OBS-263): a LIVE provider banner is the last thing the pane printed — the worker stopped
|
|
27
|
+
// underneath it. A "quota"/"rate limit" mention inside the task prompt, a diff hunk, or earlier
|
|
28
|
+
// output sits ABOVE the transcript tail and must never fail an attempt over, so the in-loop quota
|
|
29
|
+
// classifier reads only this many trailing non-empty rows instead of the whole retained snapshot.
|
|
30
|
+
// ponytail: rows, not a banner grammar — the ceiling is a worker frozen with a quota mention as its
|
|
31
|
+
// literal last output; the two-consecutive-slices + tracker-silence gates bound that cost to one
|
|
32
|
+
// failover within the routing floor. Upgrade path is a per-adapter banner fixture if it ever bites.
|
|
33
|
+
export const QUOTA_BANNER_TAIL_ROWS = 12;
|
|
34
|
+
export function stallSnapshotTail(text, rows = QUOTA_BANNER_TAIL_ROWS) {
|
|
35
|
+
return normalizeStallSnapshot(text)
|
|
36
|
+
.split("\n")
|
|
37
|
+
.filter((line) => line.trim().length > 0)
|
|
38
|
+
.slice(-rows)
|
|
39
|
+
.join("\n");
|
|
40
|
+
}
|
|
41
|
+
// T1 review (chrome-blind-matcher class, OBS-152/155): the tail of a RENDERED TUI frame is not
|
|
42
|
+
// "what the pane printed last" — its bottom rows are fixed composer/welcome chrome. Codex pins
|
|
43
|
+
// "• You have 3 usage limit resets available." there, so a raw-tail QUOTA_RE match fires on every
|
|
44
|
+
// frame of a wedged pane (verified against all 8 frames of tests/fixtures/codex-mcp-spinner) and
|
|
45
|
+
// would fail a live worker over mid-work. Filter the KNOWN chrome instead of everything on screen
|
|
46
|
+
// at some anchor: a novelty baseline cannot distinguish "chrome that was already there" from "a
|
|
47
|
+
// real banner the CLI printed before the first poll read" — a channel throttled at launch paints
|
|
48
|
+
// its banner inside the first BLOCKED_POLL_MS slice, so the banner BECOMES the baseline and is
|
|
49
|
+
// exculpated forever (proven by execution: banner-from-the-first-loop-read fails over on shipped
|
|
50
|
+
// 843328b0, parks human under the baseline). This line is semantically the opposite of exhaustion
|
|
51
|
+
// — resets AVAILABLE — so matching it out can never hide a real banner. Closed allowlist, same
|
|
52
|
+
// philosophy as the normalizer's: a new adapter's quota-flavored chrome is a fixture-capture
|
|
53
|
+
// chore, never a drive-by edit.
|
|
54
|
+
const QUOTA_CHROME_RE = /usage limit resets? available/i;
|
|
55
|
+
export function stallSnapshotBannerRows(text, rows = QUOTA_BANNER_TAIL_ROWS) {
|
|
56
|
+
return stallSnapshotTail(text, rows)
|
|
57
|
+
.split("\n")
|
|
58
|
+
.filter((line) => !QUOTA_CHROME_RE.test(line))
|
|
59
|
+
.join("\n");
|
|
60
|
+
}
|
|
61
|
+
// T1 (OBS-262/263, speed-spec §2): past fifteen minutes with a FLAT token-usage counter, row
|
|
62
|
+
// growth alone no longer re-arms the inactivity window — cosmetic repaint rows are not paid work.
|
|
63
|
+
// Token usage is the paid-work signal the tracker already samples; token growth always re-arms.
|
|
64
|
+
export const ROW_REARM_TOKEN_FLAT_MS = 15 * 60_000;
|
|
65
|
+
// Test seam, same pattern as the daemon's timing seams: production reads the constant.
|
|
66
|
+
let rowRearmTokenFlatMs = ROW_REARM_TOKEN_FLAT_MS;
|
|
67
|
+
export function setRowRearmTokenFlatMsForTests(ms) {
|
|
68
|
+
rowRearmTokenFlatMs = ms;
|
|
69
|
+
}
|
|
70
|
+
export function resetRowRearmTokenFlatMsForTests() {
|
|
71
|
+
rowRearmTokenFlatMs = ROW_REARM_TOKEN_FLAT_MS;
|
|
72
|
+
}
|
|
73
|
+
// T1 review (read-ceiling blindness): the daemon samples panes through `driver.read(slot, N)` —
|
|
74
|
+
// a bounded window. This constant IS that N, and the daemon's pane reads must use it (never a
|
|
75
|
+
// literal) so the tracker's saturation check below cannot drift away from the real read depth.
|
|
76
|
+
export const PANE_READ_ROWS = 1000;
|
|
21
77
|
/**
|
|
22
78
|
* Monotonic worker-progress measure for the stall watchdog.
|
|
23
79
|
*
|
|
@@ -25,32 +81,86 @@ export function normalizeStallSnapshot(text) {
|
|
|
25
81
|
* evidence of work. A rendered transcript is only known to have grown when it occupies more
|
|
26
82
|
* non-empty rows than any prior sample. Same-row rewrites are deliberately ambiguous and do not
|
|
27
83
|
* advance the clock: a recoverable early consult is safer than silencing the watchdog forever.
|
|
84
|
+
*
|
|
85
|
+
* CEILING: `transcriptRows` is a monotone high-water over the daemon's bounded pane read
|
|
86
|
+
* (PANE_READ_ROWS lines), so it is a one-way ratchet whose signal goes blind once the pane's
|
|
87
|
+
* content exceeds the read window — the same full window slides and `observe()` can never
|
|
88
|
+
* report row growth again. Past that point a `false` return means "unmeasurable", not "no
|
|
89
|
+
* output" — consumers making a kill decision (the dead-channel fast-kill) must check
|
|
90
|
+
* `rowSignalSaturated` and stand down on it.
|
|
28
91
|
*/
|
|
29
92
|
export class StallProgressTracker {
|
|
30
|
-
transcriptRows = 0;
|
|
93
|
+
transcriptRows = 0; // non-empty high-water over the bounded read — the growth signal
|
|
94
|
+
rawWindowLines = 0; // raw-line high-water — the SATURATION signal (see the getter)
|
|
31
95
|
seedSubmitted = false;
|
|
32
96
|
contextTokens;
|
|
33
|
-
|
|
34
|
-
|
|
97
|
+
lastTokenGrowthAt; // undefined until the first token sample anchors the flat-clock
|
|
98
|
+
rowGrowthAt; // raw row-growth clock — NEVER suppressed by the flat-token rule
|
|
99
|
+
/** True once a sample FILLED the bounded read window on RAW lines (blanks and chrome-only
|
|
100
|
+
* rows included): the pane's real extent is then unknown — genuinely new content scrolls out
|
|
101
|
+
* of the read and the row high-water can never advance again — so a flat tracker is blindness,
|
|
102
|
+
* not silence. The raw window is the saturation signal, NOT the normalized non-empty count: a
|
|
103
|
+
* production `read(slot, PANE_READ_ROWS)` returns at most PANE_READ_ROWS lines including blank
|
|
104
|
+
* and chrome-only rows (measured 730 non-empty of 1000 on the codex-mcp-spinner fixture), so
|
|
105
|
+
* comparing the non-empty high-water against PANE_READ_ROWS could never engage and the
|
|
106
|
+
* fast-kill's stand-down was unreachable. Sticky by construction (the high-water never
|
|
107
|
+
* decreases). */
|
|
108
|
+
get rowSignalSaturated() {
|
|
109
|
+
return this.rawWindowLines >= PANE_READ_ROWS;
|
|
110
|
+
}
|
|
111
|
+
/** Raw row-growth clock: the last observe() that advanced the row high-water, recorded even
|
|
112
|
+
* when the flat-token rule suppresses the progress REPORT (observe returns false). T1 review:
|
|
113
|
+
* the dead-channel fast-kill's "no output growth" leg must read this, not the suppressed
|
|
114
|
+
* progress clock — a metered adapter whose sticky token counter freezes the report while the
|
|
115
|
+
* pane keeps streaming rows is alive, and only this clock sees it. */
|
|
116
|
+
get lastRowGrowthAt() {
|
|
117
|
+
return this.rowGrowthAt;
|
|
118
|
+
}
|
|
119
|
+
observe(sample, now = Date.now()) {
|
|
120
|
+
let rowsAdvanced = false;
|
|
121
|
+
// raw window high-water first — this is the saturation signal (rowSignalSaturated), and it
|
|
122
|
+
// must see the sample exactly as the bounded read returned it, blanks and chrome included.
|
|
123
|
+
const rawLines = sample.paneText.split("\n").length;
|
|
124
|
+
if (rawLines > this.rawWindowLines)
|
|
125
|
+
this.rawWindowLines = rawLines;
|
|
35
126
|
const rows = normalizeStallSnapshot(sample.paneText)
|
|
36
127
|
.split("\n")
|
|
37
128
|
.filter((line) => line.trim().length > 0)
|
|
38
129
|
.length;
|
|
39
130
|
if (rows > this.transcriptRows) {
|
|
40
131
|
this.transcriptRows = rows;
|
|
41
|
-
|
|
132
|
+
rowsAdvanced = true;
|
|
133
|
+
this.rowGrowthAt = now; // raw signal — advances even when the report below is suppressed
|
|
42
134
|
}
|
|
135
|
+
let seedAdvanced = false;
|
|
43
136
|
if (sample.seedSubmitted && !this.seedSubmitted) {
|
|
44
137
|
this.seedSubmitted = true;
|
|
45
|
-
|
|
138
|
+
seedAdvanced = true;
|
|
46
139
|
}
|
|
140
|
+
let tokensAdvanced = false;
|
|
47
141
|
const tokens = sample.contextTokens;
|
|
48
142
|
if (tokens !== undefined && Number.isFinite(tokens)) {
|
|
49
143
|
if (tokens > (this.contextTokens ?? 0))
|
|
50
|
-
|
|
51
|
-
|
|
144
|
+
tokensAdvanced = true;
|
|
145
|
+
// T1 review: ANY movement re-anchors the flat-clock, not just a new high-water mark — a
|
|
146
|
+
// context compaction drops the counter, and the climb back below the old peak is still paid
|
|
147
|
+
// work. A high-water comparison would freeze the anchor forever on the first decrease. (The
|
|
148
|
+
// first token sample always counts as movement: contextTokens starts undefined.)
|
|
149
|
+
if (tokens !== this.contextTokens)
|
|
150
|
+
this.lastTokenGrowthAt = now;
|
|
151
|
+
this.contextTokens = tokens;
|
|
152
|
+
}
|
|
153
|
+
if (tokensAdvanced || seedAdvanced)
|
|
154
|
+
return true;
|
|
155
|
+
if (rowsAdvanced) {
|
|
156
|
+
// T1: row growth past the flat-token cap is cosmetic — the paid-work counter has not moved,
|
|
157
|
+
// so the inactivity window must NOT re-arm on it. A tracker that never sees a token sample
|
|
158
|
+
// (unmetered adapter) keeps the old row-growth behavior.
|
|
159
|
+
if (this.lastTokenGrowthAt !== undefined && now - this.lastTokenGrowthAt >= rowRearmTokenFlatMs)
|
|
160
|
+
return false;
|
|
161
|
+
return true;
|
|
52
162
|
}
|
|
53
|
-
return
|
|
163
|
+
return false;
|
|
54
164
|
}
|
|
55
165
|
}
|
|
56
166
|
// ─── v1.65 T2: LLM-bound transcript filter ──────────────────────────────────────────────────────
|
|
@@ -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
|
}
|
|
@@ -39,6 +39,8 @@ export declare const KEYBAR_KEYS: {
|
|
|
39
39
|
export type JournalRow = {
|
|
40
40
|
readonly id: string;
|
|
41
41
|
readonly time: string;
|
|
42
|
+
/** The complete recorded instant behind `time`; absent only on non-event/legacy rows. */
|
|
43
|
+
readonly timestamp?: string;
|
|
42
44
|
readonly state: ComponentState;
|
|
43
45
|
readonly text: string;
|
|
44
46
|
};
|
|
@@ -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
|
}
|
|
@@ -3,7 +3,14 @@ import type { RunViewId } from "./views.js";
|
|
|
3
3
|
export declare const SPARKLINE_BUCKET_WINDOW = 12;
|
|
4
4
|
export declare function selectSparklineBucketWidthMs(elapsedMs: number): number;
|
|
5
5
|
type MetricSample = number | null;
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* What the run is, in one word. `parked` is first-class beside the rest because
|
|
8
|
+
* a run holding an unresolved `human` task owes the operator a verb — it is not
|
|
9
|
+
* done, and calling it done is the render OBS-252 caught. The task-level word
|
|
10
|
+
* for the same fact stays `human` (TaskState): that is the event the journal
|
|
11
|
+
* writes, and `parked` is what the fold of it means for the run.
|
|
12
|
+
*/
|
|
13
|
+
export type RunStatus = "done" | "failed" | "interrupted" | "parked" | "running";
|
|
7
14
|
export type RunCockpitData = {
|
|
8
15
|
readonly binaryVersion: string;
|
|
9
16
|
readonly runId: string;
|
|
@@ -80,12 +87,30 @@ export type TaskRow = {
|
|
|
80
87
|
readonly attempts?: number;
|
|
81
88
|
readonly actor?: string;
|
|
82
89
|
readonly lastEventTime?: string;
|
|
90
|
+
/** Complete UTC instant paired with `lastEventTime`; additive for existing consumers. */
|
|
91
|
+
readonly lastEventTimestamp?: string;
|
|
83
92
|
readonly title?: string;
|
|
93
|
+
/**
|
|
94
|
+
* The park the task is standing in right now, as the daemon recorded it
|
|
95
|
+
* (`task-human` data.kind). A resolved park has no kind at all: the badge
|
|
96
|
+
* retires with the state it belonged to, so a merged task never wears the
|
|
97
|
+
* `gate-fail` it was released from an hour earlier (OBS-256).
|
|
98
|
+
*/
|
|
99
|
+
readonly parkKind?: string;
|
|
100
|
+
/**
|
|
101
|
+
* Recorded merged, and only then. A task the journal carried to `done` has
|
|
102
|
+
* finished its work; a task the journal recorded a `merge` for has landed.
|
|
103
|
+
* The two are different rows, so a consumer drawing a check-mark has the fact
|
|
104
|
+
* that earns one rather than having to read `done` as if it were the same.
|
|
105
|
+
*/
|
|
106
|
+
readonly merged?: true;
|
|
84
107
|
};
|
|
85
108
|
/** One recorded gate result. `details` is the record's own text, verbatim. */
|
|
86
109
|
export type GateRow = {
|
|
87
110
|
readonly id: string;
|
|
88
111
|
readonly time: string;
|
|
112
|
+
/** Complete UTC instant paired with `time`; additive for existing consumers. */
|
|
113
|
+
readonly timestamp?: string;
|
|
89
114
|
readonly state: ComponentState;
|
|
90
115
|
readonly gate?: string;
|
|
91
116
|
readonly taskId?: string;
|
|
@@ -99,8 +124,10 @@ export type FleetRow = {
|
|
|
99
124
|
readonly model: string;
|
|
100
125
|
readonly dispatches?: number;
|
|
101
126
|
readonly lastEventTime?: string;
|
|
127
|
+
/** Complete UTC instant paired with `lastEventTime`; additive for existing consumers. */
|
|
128
|
+
readonly lastEventTimestamp?: string;
|
|
102
129
|
};
|
|
103
|
-
export type TaskState = "done" | "failed" | "human" | "pending" | "running" | "interrupted";
|
|
130
|
+
export type TaskState = "completed" | "done" | "failed" | "human" | "pending" | "running" | "interrupted";
|
|
104
131
|
/**
|
|
105
132
|
* The source identities the named view's rows carry, in draw order. Selection
|
|
106
133
|
* is repaired against these, so it follows a row across a refresh instead of
|