tickmarkr 2.5.4 → 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.js +44 -29
- package/dist/cli/commands/fleet.js +195 -33
- package/dist/cli/commands/init.js +196 -6
- package/dist/cli/commands/resume.js +4 -2
- package/dist/cli/commands/run.js +11 -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/config/config.d.ts +14 -8
- package/dist/config/config.js +29 -26
- 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/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/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/run-gates.d.ts +3 -0
- package/dist/gates/run-gates.js +117 -10
- 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.js +31 -6
- package/dist/run/daemon.d.ts +13 -0
- package/dist/run/daemon.js +160 -70
- package/dist/run/git.d.ts +10 -1
- package/dist/run/git.js +43 -9
- 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 +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 +41 -27
- package/dist/tui/ink/fleet-app.js +204 -31
- package/package.json +1 -1
- package/skills/tickmarkr-overseer/SKILL.md +173 -113
|
@@ -9,7 +9,8 @@ import { render, useInput, useStdin } from "ink";
|
|
|
9
9
|
import { useLayoutEffect, useSyncExternalStore } from "react";
|
|
10
10
|
import { createLiveStore } from "./live-store.js";
|
|
11
11
|
import { HomeView, deriveHomeView, selectNeedsYouTarget } from "./home-view.js";
|
|
12
|
-
import { RunView, applyRunViewKey, initialRunViewSession, runGateCells, evidenceLookup
|
|
12
|
+
import { RunView, applyRunViewKey, initialRunViewSession, runGateCells, evidenceLookup } from "./run-view.js";
|
|
13
|
+
import { renderBoardLines, stripBoardAnsi } from "./board.js";
|
|
13
14
|
import { EvidenceView, deriveEvidenceView, defaultExportPath, planEvidenceExport, writeEvidenceExport } from "./evidence-view.js";
|
|
14
15
|
import { ShellGridFlow, ShellJournalSelection } from "./components.js";
|
|
15
16
|
import { CockpitShell, initialShellState } from "./shell.js";
|
|
@@ -18,12 +19,11 @@ import { shellBindings } from "./keys.js";
|
|
|
18
19
|
import { createPointerReportReader, POINTER_TRACKING_ON, POINTER_TRACKING_OFF } from "./pointer.js";
|
|
19
20
|
import { deriveRunDecisions, previewDecision, executeDecision, withDecisionPreview, withDecisionReceipt, decisionConfirmLines } from "./decision-actions.js";
|
|
20
21
|
import { approvalRunOwner } from "../../cli/commands/approve.js";
|
|
21
|
-
import { GATE_NAMES } from "../../graph/schema.js";
|
|
22
22
|
import { GLYPHS } from "../../brand.js";
|
|
23
23
|
import { Journal } from "../../run/journal.js";
|
|
24
|
-
import { observeNamedRun } from "../../run/supervision.js";
|
|
24
|
+
import { observeNamedRun, WATCH_OWNER_ENV } from "../../run/supervision.js";
|
|
25
25
|
import { formatOwnedName } from "../../drivers/types.js";
|
|
26
|
-
import { cellWidth,
|
|
26
|
+
import { cellWidth, wrapCells } from "./width.js";
|
|
27
27
|
import { resolveShellColourMode } from "./theme.js";
|
|
28
28
|
let timelineUsers = 0;
|
|
29
29
|
let timelineObserver;
|
|
@@ -100,7 +100,10 @@ export async function runConsolidatedCockpit(options) {
|
|
|
100
100
|
const source = store;
|
|
101
101
|
if (options.observeRun !== false)
|
|
102
102
|
observation = observeNamedRun(cwd, runId, options.environment);
|
|
103
|
-
|
|
103
|
+
// BD-1 (RULING-231-19 §2): the daemon-placed board carries the watch owner token and mounts
|
|
104
|
+
// rail-less — a zero shortcut budget the plan honours at every width. The manual cockpit keeps its rail.
|
|
105
|
+
const railless = Boolean((options.environment ?? process.env)[WATCH_OWNER_ENV]);
|
|
106
|
+
let state = { ...initialShellState(), view: options.initialView ?? "home", ...(railless ? { shortcutColumns: 0 } : {}) };
|
|
104
107
|
let runSession = initialRunViewSession();
|
|
105
108
|
if (options.initialParks) {
|
|
106
109
|
runSession = { ...runSession, selection: Math.max(0, source.snapshot().operator.tasks.findIndex(t => t.state === "human" || t.state === "blocked")) };
|
|
@@ -172,6 +175,29 @@ export async function runConsolidatedCockpit(options) {
|
|
|
172
175
|
state = { ...state, overlay: [result.status === "focused" ? `Focused ${task.id}` : `Pane ${result.status}`, result.reason, "Enter opens task evidence after Esc"], overlayOffset: 0 };
|
|
173
176
|
publish();
|
|
174
177
|
};
|
|
178
|
+
// The board is taller than a small body; a selection moved by keys or a park opened from Home
|
|
179
|
+
// scrolls the shell's viewport just far enough that the selected row (and the rows that wrap
|
|
180
|
+
// under it) is seen. The rows are the very lines the Run view draws at this body width.
|
|
181
|
+
const followSelection = () => {
|
|
182
|
+
const snap = source.snapshot();
|
|
183
|
+
const task = snap.operator.tasks[runSession.selection];
|
|
184
|
+
if (!task)
|
|
185
|
+
return;
|
|
186
|
+
const g = geometry();
|
|
187
|
+
const lines = renderBoardLines({ runId, snapshot: snap.operator, graph: snap.graph.value, now: (options.now ?? Date.now)(), selection: task.id, keys: false, colour: false }, g.bodyColumns);
|
|
188
|
+
const first = lines.findIndex(l => l.startsWith(" ❯ "));
|
|
189
|
+
if (first < 0)
|
|
190
|
+
return;
|
|
191
|
+
let last = first;
|
|
192
|
+
while (last + 1 < lines.length && lines[last + 1].startsWith(" ".repeat(9)))
|
|
193
|
+
last++;
|
|
194
|
+
const top = first + 1, bottom = last + 1; // the RUN summary line sits above the board
|
|
195
|
+
const scroll = state.scroll;
|
|
196
|
+
if (top < scroll)
|
|
197
|
+
state = { ...state, scroll: top };
|
|
198
|
+
else if (bottom >= scroll + g.bodyRows)
|
|
199
|
+
state = { ...state, scroll: bottom - g.bodyRows + 1 };
|
|
200
|
+
};
|
|
175
201
|
const openDetail = () => {
|
|
176
202
|
const snap = source.snapshot();
|
|
177
203
|
const task = state.view === "run" ? snap.operator.tasks[runSession.selection] : undefined;
|
|
@@ -189,6 +215,7 @@ export async function runConsolidatedCockpit(options) {
|
|
|
189
215
|
// from the snapshot. The guard prevents the shell from dispatching its echo.
|
|
190
216
|
let leafInput;
|
|
191
217
|
let forwarding = false;
|
|
218
|
+
let lastRawInput = "";
|
|
192
219
|
const forwardLeaf = (bytes) => {
|
|
193
220
|
forwarding = true;
|
|
194
221
|
try {
|
|
@@ -347,12 +374,15 @@ export async function runConsolidatedCockpit(options) {
|
|
|
347
374
|
const task = snap.operator.tasks[runSession.selection];
|
|
348
375
|
const cells = task ? runGateCells(task, evidenceLookup(snap.journal.history, source.page)) : [];
|
|
349
376
|
runSession = applyRunViewKey(runSession, event, { tasks: snap.operator.tasks, decisions, verdictLines: cells[runSession.verdictGate]?.verdict.length ?? 0 }).session;
|
|
377
|
+
if (k.upArrow || k.downArrow)
|
|
378
|
+
followSelection();
|
|
350
379
|
if (k.pageDown || k.pageUp)
|
|
351
380
|
state = {
|
|
352
381
|
...state,
|
|
353
|
-
// The Run leaf owns verdict paging; the shell only moves its
|
|
354
|
-
//
|
|
355
|
-
|
|
382
|
+
// The Run leaf owns verdict paging; the shell only moves its viewport so that leaf's
|
|
383
|
+
// verdict panel is seen — it sits below the board and the selected task's cells, so the
|
|
384
|
+
// viewport goes to the leaf's end (clamped by the shell), not one page down.
|
|
385
|
+
scroll: k.pageDown ? Number.MAX_SAFE_INTEGER : 0,
|
|
356
386
|
};
|
|
357
387
|
if (runSession.decisions.menu)
|
|
358
388
|
state = { ...state, overlay: ["Actions — Enter reviews; Esc cancels", ...runSession.decisions.menu.verbs], overlayOffset: 0 };
|
|
@@ -501,16 +531,35 @@ export async function runConsolidatedCockpit(options) {
|
|
|
501
531
|
useSyncExternalStore(listener => { listeners.add(listener); return () => { listeners.delete(listener); }; }, () => revision);
|
|
502
532
|
const stdinContext = useStdin();
|
|
503
533
|
useLayoutEffect(() => {
|
|
504
|
-
|
|
505
|
-
|
|
534
|
+
const emitter = stdinContext.internal_eventEmitter;
|
|
535
|
+
leafInput = emitter;
|
|
536
|
+
// Ink strips an escape sequence's ESC before useInput sees it, so an unnamed CSI and typed
|
|
537
|
+
// "[..." look alike there. Its parser emits every escape sequence as its own event, so the RAW
|
|
538
|
+
// event says which it was. Registered in a layout effect, this listener runs before useInput's.
|
|
539
|
+
const raw = (data) => { lastRawInput = data; };
|
|
540
|
+
emitter?.on("input", raw);
|
|
541
|
+
return () => { leafInput = undefined; emitter?.off("input", raw); };
|
|
506
542
|
}, [stdinContext.internal_eventEmitter]);
|
|
507
543
|
const renderedView = state.view;
|
|
508
544
|
const leafActive = state.editor === undefined && !state.overlay && !state.help && !state.reviewing;
|
|
509
545
|
useInput((text, k) => {
|
|
510
546
|
if (forwarding || (leafActive && renderedView !== "run" && (k.return || (renderedView === "evidence" && text === "e"))))
|
|
511
547
|
return;
|
|
548
|
+
// OBS-1002: Ink hands a coalesced chunk ("a\r", "na\r" — a paste, key repeat under load, a
|
|
549
|
+
// `pane run` message) to the handler as ONE string with no key flags, and a single-key map
|
|
550
|
+
// ignores it. Split every non-escape chunk into one event per code point; an escape sequence
|
|
551
|
+
// (arrows, page keys) is one key and already carries its flags.
|
|
552
|
+
const flagged = k.ctrl || k.meta || k.upArrow || k.downArrow || k.leftArrow || k.rightArrow || k.pageUp || k.pageDown || k.return || k.escape || k.tab || k.backspace || k.delete;
|
|
553
|
+
const points = !flagged && !lastRawInput.startsWith("\x1b") && [...text].length > 1 ? [...text] : [text];
|
|
512
554
|
try {
|
|
513
|
-
|
|
555
|
+
for (const point of points) {
|
|
556
|
+
const event = points.length === 1 ? { input: text, key: k }
|
|
557
|
+
: point === "\r" || point === "\n" ? { input: "", key: { return: true } }
|
|
558
|
+
: point === "\t" ? { input: "", key: { tab: true } }
|
|
559
|
+
: point === "\x1b" ? { input: "", key: { escape: true } }
|
|
560
|
+
: { input: point, key: { shift: /[A-Z]/.test(point) } };
|
|
561
|
+
key(event, true);
|
|
562
|
+
}
|
|
514
563
|
}
|
|
515
564
|
catch (error) {
|
|
516
565
|
stop(error);
|
|
@@ -564,10 +613,9 @@ export async function runConsolidatedCockpit(options) {
|
|
|
564
613
|
return _jsx(CockpitShell, { snapshot: options.observeRun === false ? { ...snap, journal: { ...snap.journal, source: "no run/journal.jsonl" } } : snap, state: state, columns: p.columns, rows: p.rows, version: options.binaryVersion, colour: colour, onCommit: plan => {
|
|
565
614
|
committed = plan;
|
|
566
615
|
const targets = [];
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
}));
|
|
616
|
+
// A board row opens with the prototype's four-cell indent or the focus marker, then the id;
|
|
617
|
+
// the first painted row naming a task is that task's target (the effort fold names it again, later).
|
|
618
|
+
const boardRowId = (text) => /^(?: {4}| {2}❯ )(\S+)/u.exec(stripBoardAnsi(text))?.[1];
|
|
571
619
|
const usedTasks = new Set();
|
|
572
620
|
for (const row of plan.paintedRows) {
|
|
573
621
|
if (row.row < plan.bodyRow && (row.text.startsWith("1 Home") || row.text.startsWith("1H"))) {
|
|
@@ -599,10 +647,12 @@ export async function runConsolidatedCockpit(options) {
|
|
|
599
647
|
continue;
|
|
600
648
|
}
|
|
601
649
|
if (state.view === "run") {
|
|
602
|
-
const
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
650
|
+
const drawn = boardRowId(row.text);
|
|
651
|
+
// A narrow body clips the row inside a long id; the drawn prefix still names one task.
|
|
652
|
+
const id = drawn === undefined ? undefined : (snap.operator.tasks.find(task => task.id === drawn) ?? snap.operator.tasks.find(task => task.id.startsWith(drawn)))?.id;
|
|
653
|
+
if (id !== undefined && !usedTasks.has(id)) {
|
|
654
|
+
usedTasks.add(id);
|
|
655
|
+
targets.push({ ...row, kind: "task", id });
|
|
606
656
|
}
|
|
607
657
|
}
|
|
608
658
|
else {
|
|
@@ -625,8 +675,8 @@ export async function runConsolidatedCockpit(options) {
|
|
|
625
675
|
}
|
|
626
676
|
}
|
|
627
677
|
committedTargets = targets;
|
|
628
|
-
}, children: state.view === "home" ? _jsx(ShellGridFlow, { width: p.bodyColumns, columns: p.columns < 120 ? 2 : 3, rowHeight: p.rows < 20 ? 1 : p.columns < 120 ? 2 : 3, children: _jsx(HomeView, { width: p.bodyColumns, model: homeModel, focused: focused, onOpenPark: id => { runSession = { ...runSession, selection: Math.max(0, snap.operator.tasks.findIndex(t => t.id === id)) }; state = { ...state, view: "run", scroll: 0 }; publish(); }, onOpenDiagnostic: id => { state = { ...state, overlay: [id], overlayOffset: 0 }; publish(); }, onOpenEvidence: openEvidence, onSelectNeedsYou: reportHomeNeedsYouSelection }, String(homeModel.needsYou.length > 0)) }) :
|
|
629
|
-
state.view === "run" ? _jsx(RunView, { snapshot: snap.operator, rows: snap.journal.history, page: source.page, graph: snap.graph.value, decisions: visibleDecisions, session: runSession, columns: p.bodyColumns, run: approvalRunOwner(cwd, runId) }) :
|
|
678
|
+
}, children: state.view === "home" ? _jsx(ShellGridFlow, { width: p.bodyColumns, columns: p.columns < 120 ? 2 : 3, rowHeight: p.rows < 20 ? 1 : p.columns < 120 ? 2 : 3, children: _jsx(HomeView, { width: p.bodyColumns, model: homeModel, focused: focused, onOpenPark: id => { runSession = { ...runSession, selection: Math.max(0, snap.operator.tasks.findIndex(t => t.id === id)) }; state = { ...state, view: "run", scroll: 0 }; followSelection(); publish(); }, onOpenDiagnostic: id => { state = { ...state, overlay: [id], overlayOffset: 0 }; publish(); }, onOpenEvidence: openEvidence, onSelectNeedsYou: reportHomeNeedsYouSelection }, String(homeModel.needsYou.length > 0)) }) :
|
|
679
|
+
state.view === "run" ? _jsx(RunView, { snapshot: snap.operator, rows: snap.journal.history, page: source.page, graph: snap.graph.value, decisions: visibleDecisions, session: runSession, columns: p.bodyColumns, run: approvalRunOwner(cwd, runId), now: options.now }) :
|
|
630
680
|
_jsx(ShellJournalSelection.Provider, { value: reportEvidenceSelection, children: _jsx(EvidenceView, { width: p.bodyColumns, model: { ...model, journal: evidenceRows }, focusEvidence: selectedEvidence, focused: focused, onExport: beginExport, onSelect: id => { selectedEvidence = id; openDetail(); publish(); } }, `${state.query}:${evidenceNavigation}`) }) });
|
|
631
681
|
}
|
|
632
682
|
app = render(_jsx(App, {}), { stdin, stdout: output, debug: options.debug, exitOnCtrlC: false, patchConsole: false });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ReactElement } from "react";
|
|
2
2
|
import type { ApprovalRunOwner } from "../../cli/commands/approve.js";
|
|
3
3
|
import { type GateName, type RunGraph } from "../../graph/schema.js";
|
|
4
4
|
import type { JournalEvent } from "../../run/journal.js";
|
|
@@ -83,8 +83,8 @@ export declare function applyRunViewKey(session: RunViewSession, event: RunViewK
|
|
|
83
83
|
* a closed run whose record lacks a bucket says so rather than reading absence as empty.
|
|
84
84
|
*/
|
|
85
85
|
export declare function runSummaryLine(s: OperatorSnapshot): string;
|
|
86
|
-
/**
|
|
87
|
-
export declare
|
|
86
|
+
/** The board's footer keys merged with the cockpit's own — every one of them handled by this view or the shell. */
|
|
87
|
+
export declare const RUN_VIEW_KEYS: readonly string[];
|
|
88
88
|
export interface RunViewProps {
|
|
89
89
|
readonly snapshot: OperatorSnapshot;
|
|
90
90
|
/** Journal rows the store retains (C2 `journal.history`); evidence outside them reaches `page`. */
|
|
@@ -95,6 +95,8 @@ export interface RunViewProps {
|
|
|
95
95
|
readonly session: RunViewSession;
|
|
96
96
|
readonly columns: number;
|
|
97
97
|
readonly run: ApprovalRunOwner;
|
|
98
|
+
/** The clock the board header reads; injectable so a pinned frame does not drift. */
|
|
99
|
+
readonly now?: () => number;
|
|
98
100
|
}
|
|
99
|
-
/** The Run body
|
|
100
|
-
export declare function RunView({ snapshot, rows, page, graph, decisions, session, columns, run }: RunViewProps): ReactElement;
|
|
101
|
+
/** The Run body: the approved board (BD-1) over the fold, then the selected task's detail panels. */
|
|
102
|
+
export declare function RunView({ snapshot, rows, page, graph, decisions, session, columns, run, now }: RunViewProps): ReactElement;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Box } from "ink";
|
|
3
|
+
import { useContext } from "react";
|
|
3
4
|
import { GLYPHS } from "../../brand.js";
|
|
4
5
|
import { GATE_NAMES } from "../../graph/schema.js";
|
|
5
6
|
import { normalizeGateOutcome } from "../../run/outcome.js";
|
|
6
|
-
import {
|
|
7
|
+
import { BOARD_KEYS, boardFooter, clipBoard, renderBoardLines } from "./board.js";
|
|
8
|
+
import { BodyText, Panel, ShellTheme } from "./components.js";
|
|
7
9
|
import { applyDecisionKey, decisionConfirmLines, decisionKeybar, decisionReceiptLines, initialDecisionSession, } from "./decision-actions.js";
|
|
8
10
|
import { fitCells, wrapCells } from "./width.js";
|
|
9
11
|
/** Bounded history first, then the store's page accessor for an evicted line. */
|
|
@@ -170,15 +172,13 @@ export function runSummaryLine(s) {
|
|
|
170
172
|
}
|
|
171
173
|
const attemptLabel = (t) => (t.attempt !== undefined ? `attempt ${t.attempt}` : t.dispatches === 0 ? "never dispatched" : "attempt unknown");
|
|
172
174
|
const parkOf = (t) => (t.state === "human" ? `human · ${t.parkKind ?? "unknown kind"}` : t.state);
|
|
173
|
-
/**
|
|
174
|
-
export
|
|
175
|
-
|
|
176
|
-
}
|
|
177
|
-
const MATRIX_HEADER = `${"Task".padEnd(4)} ${"state".padEnd(20)} ${GATE_NAMES.map((g) => g.slice(0, 2)).join(" ")} latest attempt`;
|
|
178
|
-
const MATRIX_LEGEND = "P pass · F fail · R running · - not run · D disabled · ? unknown | declaration order, not a timeline · acceptance/review concurrent, optional";
|
|
179
|
-
/** The Run body. Every line is measured through the width module; nothing overflows its column. */
|
|
180
|
-
export function RunView({ snapshot, rows, page, graph, decisions, session, columns, run }) {
|
|
175
|
+
/** The board's footer keys merged with the cockpit's own — every one of them handled by this view or the shell. */
|
|
176
|
+
export const RUN_VIEW_KEYS = [...BOARD_KEYS.slice(0, 2), "←→ verdict gate", "PgUp/PgDn page", "x outcome", "a actions", "o pane", "Tab focus", "? keys", BOARD_KEYS[2]];
|
|
177
|
+
/** The Run body: the approved board (BD-1) over the fold, then the selected task's detail panels. */
|
|
178
|
+
export function RunView({ snapshot, rows, page, graph, decisions, session, columns, run, now = Date.now }) {
|
|
181
179
|
const inner = Math.max(1, Math.floor(columns) - 4);
|
|
180
|
+
const colour = useContext(ShellTheme).mode !== "none";
|
|
181
|
+
const width = Math.max(1, Math.floor(columns));
|
|
182
182
|
const fit = (text) => fitCells(text, inner);
|
|
183
183
|
const decisionLines = (lines) => lines.flatMap((line) => wrapCells(line, inner));
|
|
184
184
|
const lookup = evidenceLookup(rows, page);
|
|
@@ -194,9 +194,10 @@ export function RunView({ snapshot, rows, page, graph, decisions, session, colum
|
|
|
194
194
|
const decision = task === undefined ? undefined : decisions.find((d) => d.taskId === task.id);
|
|
195
195
|
const { menu, confirming, receipt, notice } = session.decisions;
|
|
196
196
|
const blocked = task === undefined ? [] : graph?.tasks.filter((g) => g.deps.includes(task.id)).map((g) => g.id) ?? decision?.blocks ?? [];
|
|
197
|
-
|
|
197
|
+
const board = renderBoardLines({ runId: run.runId, snapshot, graph, now: now(), live: run.live, selection: task?.id, keys: false, colour }, width);
|
|
198
|
+
return (_jsxs(Box, { flexDirection: "column", width: columns, children: [_jsx(BodyText, { emphasis: "dim", children: fitCells(`RUN / ${snapshot.lifecycle} · ${runSummaryLine(snapshot)}`, width) }), board.map((line, i) => _jsx(BodyText, { children: line || " " }, `${i}:${line}`)), tasks.length === 0 && _jsx(BodyText, { children: fitCells("no tasks recorded — no plan", width) }), task !== undefined && (_jsxs(Panel, { title: `SELECTED / ${task.id}${task.title ? ` · ${task.title}` : ""}`, children: [_jsx(BodyText, { children: fit(`state ${parkOf(task)} · ${attemptLabel(task)} · dispatches ${task.dispatches} · path ${task.path ?? "unknown"} · pane ${task.pane ?? "unknown"} · alarm ${task.alarmMs === undefined ? "unknown" : `${task.alarmMs}ms`}${task.mergeEvidence ? ` · merged #L${task.mergeEvidence.line}` : ""}`) }), _jsx(BodyText, { emphasis: "dim", children: fit(`outcome filter ${session.outcomeFilter} (x cycles) · ${shown.length} of ${cells.length} gates shown`) }), shown.map((c) => (_jsx(BodyText, { children: fit(`${c.letter} ${c.gate.padEnd(10)} ${c.labels.join(" · ")}${c.line === undefined ? "" : ` · #L${c.line}`}`) }, c.gate)))] })), verdictCell !== undefined && (_jsxs(Panel, { title: `VERDICT / ${verdictCell.gate}${verdictCell.line === undefined ? "" : ` #L${verdictCell.line}`} · ${verdict.length === 0 ? "no verdict text" : `lines ${from + 1}–${from + window.length} of ${verdict.length}`}`, children: [window.map((line, i) => _jsx(BodyText, { children: fit(`${String(from + i + 1).padStart(4)} ${line}`) }, `${from + i}:${line}`)), _jsx(BodyText, { emphasis: "dim", children: fit("←→ gate · PageUp/PageDown page") })] })), task?.state === "human" && (_jsx(Panel, { title: `PARK / ${task.id}`, children: decision === undefined
|
|
198
199
|
? _jsx(BodyText, { children: fit("park evidence unavailable — refresh before deciding") })
|
|
199
200
|
: (_jsxs(_Fragment, { children: [_jsx(BodyText, { children: fit(`#L${decision.park.line} ${decision.park.kind ?? "unknown kind"}${decision.park.failedGate ? ` · failed gate ${decision.park.failedGate}` : ""} · ${decision.park.reason ?? "no reason recorded"}`) }), _jsx(BodyText, { children: fit(`blocks ${blocked.length === 0 ? "nothing" : blocked.join(", ")} · attempts ${decision.attempts}`) }), _jsx(BodyText, { children: fit(decision.verbs.length === 0 ? `diagnostic: ${decision.diagnostic ?? "no verb"}` : `decisions: ${decision.verbs.join(" · ")} (a Actions)`) }), _jsx(BodyText, { emphasis: "dim", children: fit(run.live ? "matching live daemon enacts a release at its next task boundary" : run.blockingRunId ? `other live run ${run.blockingRunId} holds the lock; resume after it ends` : "no live owner: a release records permission; resume dispatches") })] })) })), menu !== null && (_jsx(Panel, { title: `ACTIONS / ${menu.taskId}`, focused: true, children: menu.verbs.length === 0
|
|
200
201
|
? _jsx(BodyText, { children: fit(`no decision — ${decision?.diagnostic ?? "no verb for this park"}`) })
|
|
201
|
-
: menu.verbs.map((verb, i) => _jsx(BodyText, { emphasis: i === menu.selection ? "strong" : "normal", children: fit(`${i === menu.selection ? `${GLYPHS.pointer} ` : " "}${verb}`) }, verb)) })), confirming !== null && (_jsx(Panel, { title: `CONFIRM ${confirming.command.verb.toUpperCase()}`, focused: true, children: decisionLines(decisionConfirmLines(confirming)).map((line, i) => _jsx(BodyText, { children: line }, `${i}:${line}`)) })), receipt !== null && (_jsx(Panel, { title: receipt.ok ? "RECEIPT · appended" : "RECEIPT · refused", children: decisionLines(decisionReceiptLines(receipt)).map((line, i) => _jsx(BodyText, { children: line }, `${i}:${line}`)) })), notice !== null && _jsx(BodyText, { children: notice }), _jsx(BodyText, { emphasis: "dim", children:
|
|
202
|
+
: menu.verbs.map((verb, i) => _jsx(BodyText, { emphasis: i === menu.selection ? "strong" : "normal", children: fit(`${i === menu.selection ? `${GLYPHS.pointer} ` : " "}${verb}`) }, verb)) })), confirming !== null && (_jsx(Panel, { title: `CONFIRM ${confirming.command.verb.toUpperCase()}`, focused: true, children: decisionLines(decisionConfirmLines(confirming)).map((line, i) => _jsx(BodyText, { children: line }, `${i}:${line}`)) })), receipt !== null && (_jsx(Panel, { title: receipt.ok ? "RECEIPT · appended" : "RECEIPT · refused", children: decisionLines(decisionReceiptLines(receipt)).map((line, i) => _jsx(BodyText, { children: line }, `${i}:${line}`)) })), notice !== null && _jsx(BodyText, { children: notice }), _jsx(BodyText, { emphasis: "dim", children: clipBoard(boardFooter([...RUN_VIEW_KEYS, decisionKeybar(session.decisions, decision)].filter(Boolean), colour), width) })] }));
|
|
202
203
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { type AuthHealth, type WorkerAdapter } from "../../adapters/types.js";
|
|
2
|
-
import type { MapEntry, RoutingMode, Tier } from "../../config/config.js";
|
|
2
|
+
import type { MapEntry, RoutingMode, Tier, TickmarkrConfig } from "../../config/config.js";
|
|
3
3
|
import { type Shape } from "../../graph/schema.js";
|
|
4
4
|
export type FleetSteeringKey = "review" | "consult";
|
|
5
5
|
export type FleetEditorState = {
|
|
6
6
|
denyAdapters: string[];
|
|
7
7
|
denyModels: string[];
|
|
8
|
+
denyWorkersAdapters: string[];
|
|
9
|
+
denyWorkersModels: string[];
|
|
8
10
|
classifications: FleetClassification[];
|
|
9
11
|
selectedMode: RoutingMode;
|
|
10
12
|
map: Record<string, MapEntry>;
|
|
@@ -56,6 +58,15 @@ export type FleetModelEvidence = {
|
|
|
56
58
|
probeMs?: number;
|
|
57
59
|
/** OBS-519: doctor's failed model-auth verdict reason — the row must not render like a healthy one */
|
|
58
60
|
unauthed?: string;
|
|
61
|
+
/** OBS-972/FL-1: a floating alias's resolved concrete model id (e.g. "opus" → "claude-opus-5") */
|
|
62
|
+
identity?: string;
|
|
63
|
+
};
|
|
64
|
+
/** The four staged deny sets every preview and the staged routing policy are computed from. */
|
|
65
|
+
export type FleetStagedDeny = {
|
|
66
|
+
adapters: string[];
|
|
67
|
+
models: string[];
|
|
68
|
+
workersAdapters: string[];
|
|
69
|
+
workersModels: string[];
|
|
59
70
|
};
|
|
60
71
|
export type FleetModelGroup = {
|
|
61
72
|
adapter: string;
|
|
@@ -99,35 +110,41 @@ export type FleetCandidateOption = {
|
|
|
99
110
|
};
|
|
100
111
|
};
|
|
101
112
|
export declare function formatDoctorAge(ageMs: number | null): string;
|
|
102
|
-
export
|
|
113
|
+
export type FleetReach = "in" | "out-workers" | "out-all" | "out-allow" | "unknown";
|
|
114
|
+
/** The staged routing policy, or the loader's refusal of the staged overlay. */
|
|
115
|
+
export type FleetStagedRouting = {
|
|
116
|
+
ok: true;
|
|
117
|
+
routing: TickmarkrConfig["routing"];
|
|
118
|
+
} | {
|
|
119
|
+
ok: false;
|
|
120
|
+
error: string;
|
|
121
|
+
};
|
|
122
|
+
export declare function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels, initialDenyWorkersAdapters, initialDenyWorkersModels, modelGroups, initialMode, modeOptions, initialMap, modePreview, shapeRows, candidatesForShape, preferOptionsForShape, initialSteering, steeringOptionsFor, reviewOverlay, reloadGuard, stagedRouting, entry, initialJudge, judgeSeats, viewRows, frameColumns, frameRows, }: {
|
|
103
123
|
ageMs: number | null;
|
|
104
124
|
agents: AgentCli[];
|
|
105
125
|
initialDenyAdapters: string[];
|
|
106
126
|
initialDenyModels: string[];
|
|
127
|
+
initialDenyWorkersAdapters?: string[];
|
|
128
|
+
initialDenyWorkersModels?: string[];
|
|
107
129
|
modelGroups: FleetModelGroup[];
|
|
108
130
|
initialMode: RoutingMode;
|
|
109
131
|
modeOptions: FleetModeOption[];
|
|
110
132
|
initialMap: Record<string, MapEntry>;
|
|
111
|
-
modePreview: (mode: RoutingMode, map: Record<string, MapEntry>, deny:
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}) => string[];
|
|
115
|
-
shapeRows: (mode: RoutingMode, map: Record<string, MapEntry>, deny: {
|
|
116
|
-
adapters: string[];
|
|
117
|
-
models: string[];
|
|
118
|
-
}) => FleetShapeRow[];
|
|
119
|
-
candidatesForShape: (shape: Shape, mode: RoutingMode, map: Record<string, MapEntry>, deny: {
|
|
120
|
-
adapters: string[];
|
|
121
|
-
models: string[];
|
|
122
|
-
}) => {
|
|
133
|
+
modePreview: (mode: RoutingMode, map: Record<string, MapEntry>, deny: FleetStagedDeny) => string[];
|
|
134
|
+
shapeRows: (mode: RoutingMode, map: Record<string, MapEntry>, deny: FleetStagedDeny) => FleetShapeRow[];
|
|
135
|
+
candidatesForShape: (shape: Shape, mode: RoutingMode, map: Record<string, MapEntry>, deny: FleetStagedDeny) => {
|
|
123
136
|
rows: FleetCandidateOption[];
|
|
124
137
|
excludedNote?: string;
|
|
138
|
+
ledger?: string[];
|
|
125
139
|
};
|
|
126
140
|
preferOptionsForShape: (shape: Shape, current: string[]) => string[];
|
|
127
141
|
initialSteering: Record<FleetSteeringKey, string[] | undefined>;
|
|
128
142
|
steeringOptionsFor: (which: FleetSteeringKey, current: string[]) => string[];
|
|
129
143
|
reviewOverlay: (state: FleetEditorState) => FleetOverlayReview;
|
|
130
144
|
reloadGuard: (bytes: string) => string | null;
|
|
145
|
+
/** the routing policy the staged deny sets load as (fleet: the config loader over the candidate
|
|
146
|
+
* bytes); absent ⇒ the four staged sets alone, with no allowlist */
|
|
147
|
+
stagedRouting?: (deny: FleetStagedDeny) => FleetStagedRouting;
|
|
131
148
|
entry?: "presets" | "probe";
|
|
132
149
|
/** resolved config judge as "adapter:model" — shown on the (keep default) picker row */
|
|
133
150
|
initialJudge?: string;
|
|
@@ -140,36 +157,33 @@ export declare function FleetApp({ ageMs, agents, initialDenyAdapters, initialDe
|
|
|
140
157
|
/** terminal height — set only on a real TTY so the footer sticks to the bottom */
|
|
141
158
|
frameRows?: number;
|
|
142
159
|
}): import("react").JSX.Element;
|
|
143
|
-
export declare function runFleetInkEditor({ ageMs, adapters, health, initialDenyAdapters, initialDenyModels, modelGroups, initialMode, modeOptions, initialMap, modePreview, shapeRows, candidatesForShape, preferOptionsForShape, initialSteering, steeringOptionsFor, reviewOverlay, reloadGuard, entry, initialJudge, judgeSeats, initialInput, input, output, debug, }: {
|
|
160
|
+
export declare function runFleetInkEditor({ ageMs, adapters, health, initialDenyAdapters, initialDenyModels, initialDenyWorkersAdapters, initialDenyWorkersModels, modelGroups, initialMode, modeOptions, initialMap, modePreview, shapeRows, candidatesForShape, preferOptionsForShape, initialSteering, steeringOptionsFor, reviewOverlay, reloadGuard, stagedRouting, entry, initialJudge, judgeSeats, initialInput, input, output, debug, }: {
|
|
144
161
|
ageMs: number | null;
|
|
145
162
|
adapters: WorkerAdapter[];
|
|
146
163
|
health: Record<string, AuthHealth>;
|
|
147
164
|
initialDenyAdapters: string[];
|
|
148
165
|
initialDenyModels: string[];
|
|
166
|
+
initialDenyWorkersAdapters?: string[];
|
|
167
|
+
initialDenyWorkersModels?: string[];
|
|
149
168
|
modelGroups: FleetModelGroup[];
|
|
150
169
|
initialMode: RoutingMode;
|
|
151
170
|
modeOptions: FleetModeOption[];
|
|
152
171
|
initialMap: Record<string, MapEntry>;
|
|
153
|
-
modePreview: (mode: RoutingMode, map: Record<string, MapEntry>, deny:
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}) => string[];
|
|
157
|
-
shapeRows: (mode: RoutingMode, map: Record<string, MapEntry>, deny: {
|
|
158
|
-
adapters: string[];
|
|
159
|
-
models: string[];
|
|
160
|
-
}) => FleetShapeRow[];
|
|
161
|
-
candidatesForShape: (shape: Shape, mode: RoutingMode, map: Record<string, MapEntry>, deny: {
|
|
162
|
-
adapters: string[];
|
|
163
|
-
models: string[];
|
|
164
|
-
}) => {
|
|
172
|
+
modePreview: (mode: RoutingMode, map: Record<string, MapEntry>, deny: FleetStagedDeny) => string[];
|
|
173
|
+
shapeRows: (mode: RoutingMode, map: Record<string, MapEntry>, deny: FleetStagedDeny) => FleetShapeRow[];
|
|
174
|
+
candidatesForShape: (shape: Shape, mode: RoutingMode, map: Record<string, MapEntry>, deny: FleetStagedDeny) => {
|
|
165
175
|
rows: FleetCandidateOption[];
|
|
166
176
|
excludedNote?: string;
|
|
177
|
+
ledger?: string[];
|
|
167
178
|
};
|
|
168
179
|
preferOptionsForShape: (shape: Shape, current: string[]) => string[];
|
|
169
180
|
initialSteering: Record<FleetSteeringKey, string[] | undefined>;
|
|
170
181
|
steeringOptionsFor: (which: FleetSteeringKey, current: string[]) => string[];
|
|
171
182
|
reviewOverlay: (state: FleetEditorState) => FleetOverlayReview;
|
|
172
183
|
reloadGuard: (bytes: string) => string | null;
|
|
184
|
+
/** the routing policy the staged deny sets load as (fleet: the config loader over the candidate
|
|
185
|
+
* bytes); absent ⇒ the four staged sets alone, with no allowlist */
|
|
186
|
+
stagedRouting?: (deny: FleetStagedDeny) => FleetStagedRouting;
|
|
173
187
|
/** "presets" = init's entry: Esc in the browser is HOME to the preset overlay, not quit */
|
|
174
188
|
entry?: "presets" | "probe";
|
|
175
189
|
initialJudge?: string;
|