tickmarkr 2.6.4 → 2.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -3
- package/dist/adapters/catalog-remote.d.ts +23 -0
- package/dist/adapters/catalog-remote.js +38 -0
- package/dist/adapters/claude-code.d.ts +1 -1
- package/dist/adapters/claude-code.js +4 -1
- package/dist/adapters/codex-commit-check.d.ts +36 -0
- package/dist/adapters/codex-commit-check.js +205 -0
- package/dist/adapters/codex.d.ts +1 -0
- package/dist/adapters/codex.js +12 -6
- package/dist/adapters/model-discovery-check.d.ts +33 -0
- package/dist/adapters/model-discovery-check.js +123 -0
- package/dist/adapters/model-lints.d.ts +1 -1
- package/dist/adapters/model-lints.js +4 -0
- package/dist/adapters/prompt.js +3 -2
- package/dist/adapters/types.d.ts +10 -0
- package/dist/cli/commands/approve.js +10 -4
- package/dist/cli/commands/doctor.d.ts +2 -0
- package/dist/cli/commands/doctor.js +17 -3
- package/dist/cli/commands/fleet.d.ts +10 -1
- package/dist/cli/commands/fleet.js +363 -106
- package/dist/cli/commands/init.js +10 -3
- package/dist/cli/commands/plan.js +76 -4
- package/dist/cli/commands/verify.d.ts +1 -1
- package/dist/cli/commands/verify.js +65 -15
- package/dist/config/fleet-overlay.d.ts +16 -1
- package/dist/config/fleet-overlay.js +230 -28
- package/dist/drivers/orca.d.ts +34 -2
- package/dist/drivers/orca.js +206 -32
- package/dist/gates/cache.d.ts +4 -0
- package/dist/gates/cache.js +10 -2
- package/dist/gates/llm.d.ts +4 -1
- package/dist/gates/llm.js +6 -1
- package/dist/gates/review.js +8 -3
- package/dist/gates/run-gates.d.ts +5 -3
- package/dist/gates/run-gates.js +112 -56
- package/dist/gates/test-manifest.d.ts +3 -0
- package/dist/gates/test-manifest.js +1 -1
- package/dist/gates/timeout-shaped.d.ts +3 -0
- package/dist/gates/timeout-shaped.js +10 -0
- package/dist/run/daemon.js +326 -130
- package/dist/run/git.js +9 -0
- package/dist/run/journal.d.ts +6 -2
- package/dist/run/journal.js +22 -7
- package/dist/run/launchservices-check.d.ts +56 -0
- package/dist/run/launchservices-check.js +117 -0
- package/dist/run/operator-state.d.ts +22 -2
- package/dist/run/operator-state.js +20 -3
- package/dist/run/title-environment.d.ts +45 -0
- package/dist/run/title-environment.js +127 -0
- package/dist/run/title-preload.cjs +38 -0
- package/dist/run/title-preload.d.cts +1 -0
- package/dist/tui/cockpit/board.js +12 -6
- package/dist/tui/cockpit/keys.d.ts +8 -0
- package/dist/tui/cockpit/keys.js +4 -1
- package/dist/tui/cockpit/layout.d.ts +2 -2
- package/dist/tui/cockpit/layout.js +2 -2
- package/dist/tui/cockpit/live-runtime.js +46 -20
- package/dist/tui/cockpit/live-store.d.ts +24 -1
- package/dist/tui/cockpit/live-store.js +99 -8
- package/dist/tui/cockpit/log-view.d.ts +28 -0
- package/dist/tui/cockpit/log-view.js +60 -0
- package/dist/tui/cockpit/run-view.js +4 -0
- package/dist/tui/cockpit/shell.d.ts +2 -1
- package/dist/tui/cockpit/shell.js +6 -4
- package/dist/tui/ink/fleet-app.d.ts +37 -10
- package/dist/tui/ink/fleet-app.js +65 -21
- package/package.json +1 -1
- package/skills/tickmarkr-auto/SKILL.md +32 -2
- package/skills/tickmarkr-loop/SKILL.md +46 -2
- package/skills/tickmarkr-overseer/SKILL.md +83 -8
- package/skills/tickmarkr-overseer/scripts/classify-vitest-log.sh +58 -16
- package/skills/tickmarkr-overseer/scripts/grade-ci.sh +19 -6
- package/skills/tickmarkr-overseer/scripts/watch-parks.sh +121 -64
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import { narrationRow } from "../../cli/commands/run.js";
|
|
4
|
+
import { STORE_LIMITS } from "./live-store.js";
|
|
5
|
+
/** The board uses the daemon's TTY narration, including its suppression and sanitization. */
|
|
6
|
+
export const renderLogLines = (events, runId, columns) => events.map(event => narrationRow(event, runId, columns)).filter((line) => line !== null);
|
|
7
|
+
const narrate = (rows, source) => rows.flatMap(row => row.event
|
|
8
|
+
? renderLogLines([row.event], source.runId, source.columns).map(text => ({ line: row.line, text })) : []);
|
|
9
|
+
/** Demand-page backwards to fill the viewport, including events outside the store's retained tail.
|
|
10
|
+
* Only the requested visible rows survive; suppressed events never consume viewport rows. */
|
|
11
|
+
export function readLogPage(source, count, end = source.journal.lines) {
|
|
12
|
+
let cursor = Math.min(end, source.journal.lines);
|
|
13
|
+
let rows = [];
|
|
14
|
+
try {
|
|
15
|
+
while (cursor > 0 && rows.length < count) {
|
|
16
|
+
const first = Math.max(1, cursor - STORE_LIMITS.history + 1);
|
|
17
|
+
const retained = source.journal.history.filter(row => row.line >= first && row.line <= cursor);
|
|
18
|
+
const page = retained.length === cursor - first + 1 ? retained
|
|
19
|
+
: source.page(first, cursor - first + 1, source.journal.generation);
|
|
20
|
+
rows = [...narrate(page, source), ...rows].slice(-count);
|
|
21
|
+
cursor = first - 1;
|
|
22
|
+
}
|
|
23
|
+
return { rows };
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
return { rows: [], error: error instanceof Error ? error.message : String(error) };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Move by visible narration rows. Undefined means follow the journal tail again. */
|
|
30
|
+
export function moveLogEnd(source, end, delta) {
|
|
31
|
+
const cursor = Math.min(end ?? source.journal.lines, source.journal.lines);
|
|
32
|
+
if (delta < 0) {
|
|
33
|
+
const rows = readLogPage(source, -delta + 1, cursor).rows;
|
|
34
|
+
return rows[Math.max(0, rows.length - 1 + delta)]?.line ?? end;
|
|
35
|
+
}
|
|
36
|
+
let first = cursor + 1, remaining = delta;
|
|
37
|
+
while (first <= source.journal.lines) {
|
|
38
|
+
const last = Math.min(source.journal.lines, first + STORE_LIMITS.history - 1);
|
|
39
|
+
let rows;
|
|
40
|
+
try {
|
|
41
|
+
rows = narrate(source.page(first, last - first + 1, source.journal.generation), source);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return end;
|
|
45
|
+
} // A stale journal observation must never terminate the board's key handler.
|
|
46
|
+
if (rows.length >= remaining) {
|
|
47
|
+
const line = rows[remaining - 1].line;
|
|
48
|
+
return line === source.journal.lines ? undefined : line;
|
|
49
|
+
}
|
|
50
|
+
remaining -= rows.length;
|
|
51
|
+
first = last + 1;
|
|
52
|
+
}
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
export function LogView({ page }) {
|
|
56
|
+
return _jsx(Box, { flexDirection: "column", children: page.error
|
|
57
|
+
? _jsxs(Text, { children: ["Log unavailable: ", page.error] })
|
|
58
|
+
: page.rows.length ? page.rows.map(row => _jsx(Text, { wrap: "truncate-end", children: row.text }, row.line))
|
|
59
|
+
: _jsx(Text, { children: "No narrated events" }) });
|
|
60
|
+
}
|
|
@@ -98,6 +98,10 @@ export function runGateCells(task, evidence, rows = []) {
|
|
|
98
98
|
else if (cell.state === "queued") {
|
|
99
99
|
labels.push(`queued — ${row?.event?.event ?? "wait"}${typeof data.count === "number" ? ` (${data.count} suites)` : ""}`);
|
|
100
100
|
}
|
|
101
|
+
else if (cell.state === "unknown" && row?.event?.event !== "gate-result") {
|
|
102
|
+
// I1: a start or wait row settled at a terminal boundary — no verdict ever measured it.
|
|
103
|
+
labels.push(`unknown — ${row?.event?.event ?? "start"} never finished`);
|
|
104
|
+
}
|
|
101
105
|
else if (data.disabled === true) {
|
|
102
106
|
labels.push("disabled by policy");
|
|
103
107
|
}
|
|
@@ -2,13 +2,14 @@ import { type ReactNode } from "react";
|
|
|
2
2
|
import { type ShellPlan } from "./layout.js";
|
|
3
3
|
import { SHELL_BINDINGS } from "./keys.js";
|
|
4
4
|
import type { LiveStoreSnapshot } from "./live-store.js";
|
|
5
|
-
export type ShellView = "home" | "run" | "evidence";
|
|
5
|
+
export type ShellView = "home" | "run" | "evidence" | "log";
|
|
6
6
|
export type ShellFocus = ShellPlan["focus"][number];
|
|
7
7
|
export interface ShellState {
|
|
8
8
|
view: ShellView;
|
|
9
9
|
focus: ShellFocus;
|
|
10
10
|
scroll: number;
|
|
11
11
|
help: boolean;
|
|
12
|
+
logEnd?: number;
|
|
12
13
|
evidenceSection?: number;
|
|
13
14
|
canOpen?: boolean;
|
|
14
15
|
shortcutColumns?: number;
|
|
@@ -13,7 +13,8 @@ export const shellHelp = (bindings = SHELL_BINDINGS) => [
|
|
|
13
13
|
"Esc closes deepest overlay; it never quits.",
|
|
14
14
|
"Text: q1? is literal; Enter applies; Esc cancels.",
|
|
15
15
|
"Confirm: y alone confirms; n/Esc cancels.",
|
|
16
|
-
"Tab now moves focus; view numbers are 1/4/5.",
|
|
16
|
+
"Tab now moves focus; view numbers are 1/4/5/7.",
|
|
17
|
+
"Log: arrows/page keys scroll; f toggles tail follow.",
|
|
17
18
|
"Fleet: tickmarkr fleet", "Plan: tickmarkr plan", "Health: tickmarkr doctor",
|
|
18
19
|
];
|
|
19
20
|
/** Read the Yoga geometry that actually painted, including clipped/scrolled leaves.
|
|
@@ -65,10 +66,11 @@ export function CockpitShell({ snapshot, state, columns, rows, version, children
|
|
|
65
66
|
const overlay = state.editor !== undefined ? [`${state.editorKind === "export" ? "Export destination" : "Filter"}: ${state.editor}`, "Enter Apply | Esc Cancel"] : state.overlay ?? (state.help ? shellHelp(bindings) : undefined);
|
|
66
67
|
const totalRows = overlay?.length ?? contentRows;
|
|
67
68
|
const displayOffset = overlay ? Math.min(state.overlayOffset, Math.max(0, totalRows - plan.bodyRows)) : scroll;
|
|
68
|
-
const
|
|
69
|
+
const logMode = state.view === "log" ? `${state.logEnd === undefined ? "FOLLOW" : "PAUSED"} | ` : "";
|
|
70
|
+
const position = `${logMode}${totalRows ? displayOffset + 1 : 0}–${Math.min(totalRows, displayOffset + plan.bodyRows)}/${totalRows} | ${Math.max(0, totalRows - plan.bodyRows)} hidden`;
|
|
69
71
|
const keybar = bindings.filter(b => ["focus", "open", "actions", "help", "quit"].includes(b.action) && (columns >= 60 || b.action !== "focus")).map(b => `${b.key} ${columns < 60 && b.action === "actions" ? "Act" : b.label}`).join(" | ");
|
|
70
72
|
const status = columns < 60 ? `${op.label} | tip ${op.currentTip.toUpperCase()}` : `${op.label} | MERGED ${op.merged}/${op.planned ?? "?"} | CURRENT TIP ${op.currentTip.toUpperCase()} | ${snapshot.freshness}`;
|
|
71
73
|
return _jsx(ShellPresentation.Provider, { value: true, children: _jsx(ShellTheme.Provider, { value: { mode: colour, palette }, children: _jsxs(Box, { ref: root, flexDirection: "column", width: columns, height: rows, flexShrink: 0, backgroundColor: ink(palette.surface), children: [line(`tickmarkr ${version} | ${state.view.toUpperCase()} | ${snapshot.journal.source.split("/").at(-2) ?? "run identity unknown"}`), line(columns < 60
|
|
72
|
-
? `1H 4R 5E | ${position}`
|
|
73
|
-
: `1 Home 4 Run 5 Evidence | ${state.focus} | ${position}`), line("─".repeat(columns), columns, palette.chrome), _jsxs(Box, { height: plan.bodyRows, flexShrink: 0, flexDirection: "row", overflow: "hidden", children: [_jsx(Box, { width: 1, flexDirection: "column", children: Array.from({ length: plan.bodyRows }, (_, i) => _jsx(Text, { color: ink(palette.chrome), children: "\u2502" }, i)) }), plan.rail > 0 && _jsxs(_Fragment, { children: [_jsx(Box, { width: plan.rail, flexDirection: "column", children: ["1 Home", "4 Run", "5 Evidence", "", "CLI:", "fleet", "plan", "doctor"].map((t, i) => _jsx(Text, { color: ink(palette.chrome), children: fitCells(t, plan.rail) }, i)) }), _jsx(Box, { width: 1, flexDirection: "column", children: Array.from({ length: plan.bodyRows }, (_, i) => _jsx(Text, { color: ink(palette.chrome), children: "\u2502" }, i)) })] }), _jsxs(Box, { width: plan.bodyColumns, height: plan.bodyRows, flexDirection: "column", overflow: "hidden", flexShrink: 0, children: [overlay?.slice(displayOffset, displayOffset + plan.bodyRows).map((t, i) => _jsx(Text, { wrap: "truncate-end", color: ink(palette.text), children: fitCells(t, plan.bodyColumns) }, i)), _jsx(Box, { ref: leaf, display: overlay ? "none" : "flex", flexDirection: "column", flexShrink: 0, marginTop: -scroll, children: children })] }), plan.shortcuts > 0 && _jsxs(_Fragment, { children: [_jsx(Box, { width: 1, flexDirection: "column", children: Array.from({ length: plan.bodyRows }, (_, i) => _jsx(Text, { color: ink(palette.chrome), children: "\u2502" }, i)) }), _jsx(Box, { width: plan.shortcuts, flexDirection: "column", children: bindings.map(b => _jsx(Text, { color: ink(palette.chrome), wrap: "truncate-end", children: fitCells(`${b.key} ${b.label}`, plan.shortcuts) }, b.key)) })] }), _jsx(Box, { width: 1, flexDirection: "column", children: Array.from({ length: plan.bodyRows }, (_, i) => _jsx(Text, { color: ink(palette.chrome), children: "\u2502" }, i)) })] }), line("─".repeat(columns), columns, palette.chrome), line(status), line(keybar), line("─".repeat(columns), columns, palette.chrome)] }) }) });
|
|
74
|
+
? `1H 4R 5E 7L | ${position}`
|
|
75
|
+
: `1 Home 4 Run 5 Evidence 7 Log | ${state.focus} | ${position}`), line("─".repeat(columns), columns, palette.chrome), _jsxs(Box, { height: plan.bodyRows, flexShrink: 0, flexDirection: "row", overflow: "hidden", children: [_jsx(Box, { width: 1, flexDirection: "column", children: Array.from({ length: plan.bodyRows }, (_, i) => _jsx(Text, { color: ink(palette.chrome), children: "\u2502" }, i)) }), plan.rail > 0 && _jsxs(_Fragment, { children: [_jsx(Box, { width: plan.rail, flexDirection: "column", children: ["1 Home", "4 Run", "5 Evidence", "7 Log", "", "CLI:", "fleet", "plan", "doctor"].map((t, i) => _jsx(Text, { color: ink(palette.chrome), children: fitCells(t, plan.rail) }, i)) }), _jsx(Box, { width: 1, flexDirection: "column", children: Array.from({ length: plan.bodyRows }, (_, i) => _jsx(Text, { color: ink(palette.chrome), children: "\u2502" }, i)) })] }), _jsxs(Box, { width: plan.bodyColumns, height: plan.bodyRows, flexDirection: "column", overflow: "hidden", flexShrink: 0, children: [overlay?.slice(displayOffset, displayOffset + plan.bodyRows).map((t, i) => _jsx(Text, { wrap: "truncate-end", color: ink(palette.text), children: fitCells(t, plan.bodyColumns) }, i)), _jsx(Box, { ref: leaf, display: overlay ? "none" : "flex", flexDirection: "column", flexShrink: 0, marginTop: -scroll, children: children })] }), plan.shortcuts > 0 && _jsxs(_Fragment, { children: [_jsx(Box, { width: 1, flexDirection: "column", children: Array.from({ length: plan.bodyRows }, (_, i) => _jsx(Text, { color: ink(palette.chrome), children: "\u2502" }, i)) }), _jsx(Box, { width: plan.shortcuts, flexDirection: "column", children: bindings.map(b => _jsx(Text, { color: ink(palette.chrome), wrap: "truncate-end", children: fitCells(`${b.key} ${b.label}`, plan.shortcuts) }, b.key)) })] }), _jsx(Box, { width: 1, flexDirection: "column", children: Array.from({ length: plan.bodyRows }, (_, i) => _jsx(Text, { color: ink(palette.chrome), children: "\u2502" }, i)) })] }), line("─".repeat(columns), columns, palette.chrome), line(status), line(keybar), line("─".repeat(columns), columns, palette.chrome)] }) }) });
|
|
74
76
|
}
|
|
@@ -2,9 +2,21 @@ import { type AuthHealth, type WorkerAdapter } from "../../adapters/types.js";
|
|
|
2
2
|
import { type DenyScopeKey, type InitialDenyProps, type MapEntry, type RoutingMode, type StagedDenyKey, type Tier, type TickmarkrConfig } from "../../config/config.js";
|
|
3
3
|
import { type Effort, type Shape } from "../../graph/schema.js";
|
|
4
4
|
export type FleetSteeringKey = "review" | "consult";
|
|
5
|
+
/** T10 (D-939): the routing.allow entry a classification staged this session excluded — the row's own
|
|
6
|
+
* entry in its ownAllow spelling, added to the staged complement so Space can admit it in-session.
|
|
7
|
+
* `model` is the classified id (the tiers key), `displayModel` the browser row it was staged from. */
|
|
8
|
+
export type FleetSeededAllow = {
|
|
9
|
+
adapter: string;
|
|
10
|
+
model: string;
|
|
11
|
+
displayModel: string;
|
|
12
|
+
identity?: string;
|
|
13
|
+
entry: string;
|
|
14
|
+
};
|
|
5
15
|
export type FleetEditorState = Record<DenyScopeKey, string[]> & {
|
|
6
16
|
/** OBS-1046: the staged routing.allow complement, distinct from the authored deny lists above */
|
|
7
17
|
allowOut?: string[];
|
|
18
|
+
/** T10: one seed per staged classification the current allow form left out */
|
|
19
|
+
seededAllowOut?: FleetSeededAllow[];
|
|
8
20
|
classifications: FleetClassification[];
|
|
9
21
|
/** OBS-1182: staged launch effort per adapter → model; absent = the CLI's own default */
|
|
10
22
|
efforts?: Record<string, Record<string, Effort>>;
|
|
@@ -16,8 +28,19 @@ export type FleetEditorState = Record<DenyScopeKey, string[]> & {
|
|
|
16
28
|
};
|
|
17
29
|
steering: Record<FleetSteeringKey, string[] | undefined>;
|
|
18
30
|
};
|
|
31
|
+
/** T8: the staged classifications and efforts every preview folds — the same metadata a review stages,
|
|
32
|
+
* so a preview ranks what w would write before the first review and after an escaped one. */
|
|
33
|
+
export type FleetStagedMetadata = Pick<FleetEditorState, "classifications" | "efforts" | "seededAllowOut">;
|
|
34
|
+
/** OBS-525: auto reverts the shape's WHOLE routing declaration — pool included, not just the pin.
|
|
35
|
+
* prefer survives: it biases auto routing and is valid without pin/pool. */
|
|
36
|
+
export declare function autoMapEntry(entry: MapEntry | undefined): MapEntry;
|
|
19
37
|
export type FleetOverlayReview = {
|
|
20
38
|
kind: "empty";
|
|
39
|
+
}
|
|
40
|
+
/** T10: a staged batch the one-destination review cannot write (mixed destinations, a stray seed) */
|
|
41
|
+
| {
|
|
42
|
+
kind: "refused";
|
|
43
|
+
reason: string;
|
|
21
44
|
} | {
|
|
22
45
|
kind: "diff";
|
|
23
46
|
before: string;
|
|
@@ -125,7 +148,7 @@ export type FleetStagedRouting = {
|
|
|
125
148
|
ok: false;
|
|
126
149
|
error: string;
|
|
127
150
|
};
|
|
128
|
-
export declare function FleetApp({ ageMs, agents, initialAllowOut, initialEfforts, modelGroups, initialMode, modeOptions, initialMap, modePreview, shapeRows, candidatesForShape, preferOptionsForShape, initialSteering, steeringOptionsFor, reviewAdvisories, reviewOverlay, reloadGuard, stagedRouting, entry, initialJudge, judgeSeats, viewRows, frameColumns, frameRows, ...initialDenyProps }: InitialDenyProps & {
|
|
151
|
+
export declare function FleetApp({ ageMs, agents, initialAllowOut, initialEfforts, modelGroups, initialMode, modeOptions, initialMap, modePreview, shapeRows, candidatesForShape, preferOptionsForShape, initialSteering, steeringOptionsFor, reviewAdvisories, reviewOverlay, reloadGuard, stagedRouting, holderOf, entry, initialJudge, judgeSeats, viewRows, frameColumns, frameRows, ...initialDenyProps }: InitialDenyProps & {
|
|
129
152
|
ageMs: number | null;
|
|
130
153
|
agents: AgentCli[];
|
|
131
154
|
/** OBS-1046: adapter ids and adapter:model keys routing.allow leaves out of the fleet */
|
|
@@ -136,9 +159,9 @@ export declare function FleetApp({ ageMs, agents, initialAllowOut, initialEffort
|
|
|
136
159
|
initialMode: RoutingMode;
|
|
137
160
|
modeOptions: FleetModeOption[];
|
|
138
161
|
initialMap: Record<string, MapEntry>;
|
|
139
|
-
modePreview: (mode: RoutingMode, map: Record<string, MapEntry>, deny: FleetStagedDeny) => string[];
|
|
140
|
-
shapeRows: (mode: RoutingMode, map: Record<string, MapEntry>, deny: FleetStagedDeny) => FleetShapeRow[];
|
|
141
|
-
candidatesForShape: (shape: Shape, mode: RoutingMode, map: Record<string, MapEntry>, deny: FleetStagedDeny) => {
|
|
162
|
+
modePreview: (mode: RoutingMode, map: Record<string, MapEntry>, deny: FleetStagedDeny, stage?: FleetStagedMetadata) => string[];
|
|
163
|
+
shapeRows: (mode: RoutingMode, map: Record<string, MapEntry>, deny: FleetStagedDeny, stage?: FleetStagedMetadata) => FleetShapeRow[];
|
|
164
|
+
candidatesForShape: (shape: Shape, mode: RoutingMode, map: Record<string, MapEntry>, deny: FleetStagedDeny, stage?: FleetStagedMetadata) => {
|
|
142
165
|
rows: FleetCandidateOption[];
|
|
143
166
|
excludedNote?: string;
|
|
144
167
|
ledger?: string[];
|
|
@@ -152,7 +175,9 @@ export declare function FleetApp({ ageMs, agents, initialAllowOut, initialEffort
|
|
|
152
175
|
reloadGuard: (bytes: string) => string | null;
|
|
153
176
|
/** the routing policy the staged deny sets load as (fleet: the config loader over the candidate
|
|
154
177
|
* bytes); absent ⇒ the four staged sets alone, with no allowlist */
|
|
155
|
-
stagedRouting?: (deny: FleetStagedDeny) => FleetStagedRouting;
|
|
178
|
+
stagedRouting?: (deny: FleetStagedDeny, stage?: FleetStagedMetadata) => FleetStagedRouting;
|
|
179
|
+
/** T10: the overlay `path:line` holding a dotted config scope in this repository, undefined = defaults */
|
|
180
|
+
holderOf?: (dotted: string) => string | undefined;
|
|
156
181
|
entry?: "presets" | "probe";
|
|
157
182
|
/** resolved config judge as "adapter:model" — shown on the (keep default) picker row */
|
|
158
183
|
initialJudge?: string;
|
|
@@ -165,7 +190,7 @@ export declare function FleetApp({ ageMs, agents, initialAllowOut, initialEffort
|
|
|
165
190
|
/** terminal height — set only on a real TTY so the footer sticks to the bottom */
|
|
166
191
|
frameRows?: number;
|
|
167
192
|
}): import("react").JSX.Element;
|
|
168
|
-
export declare function runFleetInkEditor({ ageMs, adapters, health, initialAllowOut, initialEfforts, modelGroups, initialMode, modeOptions, initialMap, modePreview, shapeRows, candidatesForShape, preferOptionsForShape, initialSteering, steeringOptionsFor, reviewAdvisories, reviewOverlay, reloadGuard, stagedRouting, entry, initialJudge, judgeSeats, initialInput, input, output, debug, ...initialDenyProps }: InitialDenyProps & {
|
|
193
|
+
export declare function runFleetInkEditor({ ageMs, adapters, health, initialAllowOut, initialEfforts, modelGroups, initialMode, modeOptions, initialMap, modePreview, shapeRows, candidatesForShape, preferOptionsForShape, initialSteering, steeringOptionsFor, reviewAdvisories, reviewOverlay, reloadGuard, stagedRouting, holderOf, entry, initialJudge, judgeSeats, initialInput, input, output, debug, ...initialDenyProps }: InitialDenyProps & {
|
|
169
194
|
ageMs: number | null;
|
|
170
195
|
adapters: WorkerAdapter[];
|
|
171
196
|
health: Record<string, AuthHealth>;
|
|
@@ -177,9 +202,9 @@ export declare function runFleetInkEditor({ ageMs, adapters, health, initialAllo
|
|
|
177
202
|
initialMode: RoutingMode;
|
|
178
203
|
modeOptions: FleetModeOption[];
|
|
179
204
|
initialMap: Record<string, MapEntry>;
|
|
180
|
-
modePreview: (mode: RoutingMode, map: Record<string, MapEntry>, deny: FleetStagedDeny) => string[];
|
|
181
|
-
shapeRows: (mode: RoutingMode, map: Record<string, MapEntry>, deny: FleetStagedDeny) => FleetShapeRow[];
|
|
182
|
-
candidatesForShape: (shape: Shape, mode: RoutingMode, map: Record<string, MapEntry>, deny: FleetStagedDeny) => {
|
|
205
|
+
modePreview: (mode: RoutingMode, map: Record<string, MapEntry>, deny: FleetStagedDeny, stage?: FleetStagedMetadata) => string[];
|
|
206
|
+
shapeRows: (mode: RoutingMode, map: Record<string, MapEntry>, deny: FleetStagedDeny, stage?: FleetStagedMetadata) => FleetShapeRow[];
|
|
207
|
+
candidatesForShape: (shape: Shape, mode: RoutingMode, map: Record<string, MapEntry>, deny: FleetStagedDeny, stage?: FleetStagedMetadata) => {
|
|
183
208
|
rows: FleetCandidateOption[];
|
|
184
209
|
excludedNote?: string;
|
|
185
210
|
ledger?: string[];
|
|
@@ -193,7 +218,9 @@ export declare function runFleetInkEditor({ ageMs, adapters, health, initialAllo
|
|
|
193
218
|
reloadGuard: (bytes: string) => string | null;
|
|
194
219
|
/** the routing policy the staged deny sets load as (fleet: the config loader over the candidate
|
|
195
220
|
* bytes); absent ⇒ the four staged sets alone, with no allowlist */
|
|
196
|
-
stagedRouting?: (deny: FleetStagedDeny) => FleetStagedRouting;
|
|
221
|
+
stagedRouting?: (deny: FleetStagedDeny, stage?: FleetStagedMetadata) => FleetStagedRouting;
|
|
222
|
+
/** T10: the overlay `path:line` holding a dotted config scope in this repository, undefined = defaults */
|
|
223
|
+
holderOf?: (dotted: string) => string | undefined;
|
|
197
224
|
/** "presets" = init's entry: Esc in the browser is HOME to the preset overlay, not quit */
|
|
198
225
|
entry?: "presets" | "probe";
|
|
199
226
|
initialJudge?: string;
|
|
@@ -12,6 +12,12 @@ import { windowRows } from "./components.js";
|
|
|
12
12
|
import { wrapCells } from "../cockpit/width.js";
|
|
13
13
|
import { clip, clipPathTail, ElisionMark, enterAltScreen, fmtCtx, fmtMs, fmtUsdPair, Glyph, INK, inkInput, inkOutput, KeyBar, OverlayPanel, padCell, padCellStart, Pointer, SearchRow, } from "./frame.js";
|
|
14
14
|
const STEERING_KEYS = ["review", "consult"];
|
|
15
|
+
/** OBS-525: auto reverts the shape's WHOLE routing declaration — pool included, not just the pin.
|
|
16
|
+
* prefer survives: it biases auto routing and is valid without pin/pool. */
|
|
17
|
+
export function autoMapEntry(entry) {
|
|
18
|
+
const { pin: _pin, pool: _pool, ...rest } = entry ?? {};
|
|
19
|
+
return rest;
|
|
20
|
+
}
|
|
15
21
|
/** every scope's staged list, empty — the "every policy scope open" probe the picker lifts against */
|
|
16
22
|
export const openStagedDeny = () => ({
|
|
17
23
|
...Object.fromEntries(DENY_SCOPES.map((scope) => [stagedDenyKeyOf(scope), []])),
|
|
@@ -43,7 +49,7 @@ const REACH_CHOICES = [
|
|
|
43
49
|
{ id: "out-workers", label: "out · workers", gloss: "workers skip it; judge/review/consult unaffected" },
|
|
44
50
|
{ id: "out-all", label: "out · all seats", gloss: "no seat routes here" },
|
|
45
51
|
];
|
|
46
|
-
export function FleetApp({ ageMs, agents, initialAllowOut = [], initialEfforts = {}, modelGroups, initialMode, modeOptions, initialMap, modePreview, shapeRows, candidatesForShape, preferOptionsForShape, initialSteering, steeringOptionsFor, reviewAdvisories = [], reviewOverlay, reloadGuard, stagedRouting, entry = "probe", initialJudge = "", judgeSeats = [], viewRows = Number.POSITIVE_INFINITY, frameColumns = 100, frameRows, ...initialDenyProps }) {
|
|
52
|
+
export function FleetApp({ ageMs, agents, initialAllowOut = [], initialEfforts = {}, modelGroups, initialMode, modeOptions, initialMap, modePreview, shapeRows, candidatesForShape, preferOptionsForShape, initialSteering, steeringOptionsFor, reviewAdvisories = [], reviewOverlay, reloadGuard, stagedRouting, holderOf, entry = "probe", initialJudge = "", judgeSeats = [], viewRows = Number.POSITIVE_INFINITY, frameColumns = 100, frameRows, ...initialDenyProps }) {
|
|
47
53
|
const { exit } = useApp();
|
|
48
54
|
const [, setRevision] = useState(0);
|
|
49
55
|
// the props are the fleet command's contract, one per schema-enumerated scope (initialDeny…),
|
|
@@ -65,6 +71,7 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], initialEfforts =
|
|
|
65
71
|
notice: "",
|
|
66
72
|
lastEdit: null,
|
|
67
73
|
allowOut: new Set(initialAllowOut),
|
|
74
|
+
seededAllowOut: [],
|
|
68
75
|
...Object.fromEntries(DENY_SCOPES.map((scope) => [scope.key, new Set(initialDeny[scope.key])])),
|
|
69
76
|
classifications: [],
|
|
70
77
|
efforts: structuredClone(initialEfforts),
|
|
@@ -133,12 +140,13 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], initialEfforts =
|
|
|
133
140
|
let routingMemo = null;
|
|
134
141
|
const stagedPolicy = () => {
|
|
135
142
|
const deny = stagedDeny();
|
|
136
|
-
const
|
|
143
|
+
const stage = stagedMetadata();
|
|
144
|
+
const key = JSON.stringify([deny, stage]);
|
|
137
145
|
if (routingMemo?.key !== key) {
|
|
138
146
|
routingMemo = {
|
|
139
147
|
key,
|
|
140
148
|
policy: stagedRouting
|
|
141
|
-
? stagedRouting(deny)
|
|
149
|
+
? stagedRouting(deny, stage)
|
|
142
150
|
: { ok: true, routing: { deny: denyBlockFrom(denyLists()) } },
|
|
143
151
|
};
|
|
144
152
|
}
|
|
@@ -157,7 +165,7 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], initialEfforts =
|
|
|
157
165
|
const reachFor = (adapter, model, identity) => {
|
|
158
166
|
const policy = stagedPolicy();
|
|
159
167
|
if (!policy.ok)
|
|
160
|
-
return { reach: "unknown", reasons: [`preview unavailable (${policy.error})`], uncoveredAlias: false };
|
|
168
|
+
return { reach: "unknown", reasons: [`preview unavailable (${policy.error})`], uncoveredAlias: false, held: [] };
|
|
161
169
|
const channel = { adapter, model, ...(identity !== undefined ? { identity } : {}) };
|
|
162
170
|
const worker = exclusionsOf(channel, policy.routing, "worker");
|
|
163
171
|
const allSeats = exclusionsOf(channel, policy.routing, "judge");
|
|
@@ -169,6 +177,10 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], initialEfforts =
|
|
|
169
177
|
reach,
|
|
170
178
|
reasons: worker.map(exclusionReason),
|
|
171
179
|
uncoveredAlias: identity !== undefined && !worker.some((scope) => scope.by === "deny"),
|
|
180
|
+
held: [...new Set(worker.map((scope) => scope.configPath))].flatMap((dotted) => {
|
|
181
|
+
const holder = holderOf?.(dotted);
|
|
182
|
+
return holder === undefined ? [] : [`${dotted} @ ${holder}`];
|
|
183
|
+
}),
|
|
172
184
|
};
|
|
173
185
|
};
|
|
174
186
|
// OBS-1099 review: a displayed row may fold several discovered gateway ids (foldedModels);
|
|
@@ -346,7 +358,7 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], initialEfforts =
|
|
|
346
358
|
...Object.fromEntries(DENY_SCOPES.map((scope) => [stagedDenyKeyOf(scope), [...ui[scope.key]].sort()])),
|
|
347
359
|
allowOut: [...ui.allowOut].sort(),
|
|
348
360
|
});
|
|
349
|
-
const shapeList = () => shapeRows(ui.selectedMode, ui.map, stagedDeny());
|
|
361
|
+
const shapeList = () => shapeRows(ui.selectedMode, ui.map, stagedDeny(), stagedMetadata());
|
|
350
362
|
const steeringList = () => [
|
|
351
363
|
...STEERING_KEYS.map((key) => ({
|
|
352
364
|
id: key,
|
|
@@ -369,9 +381,15 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], initialEfforts =
|
|
|
369
381
|
...modelGroups.map((_, index) => ({ kind: "adapter", index })),
|
|
370
382
|
];
|
|
371
383
|
// ── state transitions ──────────────────────────────────────────────────────
|
|
384
|
+
// T8: the previews fold the CURRENT staged metadata — never only what the last review saw
|
|
385
|
+
const stagedMetadata = () => {
|
|
386
|
+
const { classifications, efforts, seededAllowOut } = editorState();
|
|
387
|
+
return { classifications, efforts, seededAllowOut };
|
|
388
|
+
};
|
|
372
389
|
const editorState = () => ({
|
|
373
390
|
...denyLists(),
|
|
374
391
|
allowOut: [...ui.allowOut].sort(),
|
|
392
|
+
seededAllowOut: structuredClone(ui.seededAllowOut),
|
|
375
393
|
classifications: ui.classifications.map((classification) => classification.vendor && classification.channel
|
|
376
394
|
? {
|
|
377
395
|
...classification,
|
|
@@ -419,9 +437,38 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], initialEfforts =
|
|
|
419
437
|
finish({ kind: "no-changes" });
|
|
420
438
|
return;
|
|
421
439
|
}
|
|
440
|
+
if (nextReview.kind === "refused") {
|
|
441
|
+
ui.notice = `w refused — ${nextReview.reason}`;
|
|
442
|
+
bump();
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
422
445
|
setOverlay({ kind: "review", review: nextReview, scroll: 0 });
|
|
423
446
|
};
|
|
447
|
+
// T10 (D-925/D-939): a model the current allow form leaves out gets its OWN allow entry staged beside
|
|
448
|
+
// its classification — the ownAllow spelling Space clears — so it can be admitted in this session.
|
|
449
|
+
// Re-staging a classification replaces its seed; a row the allow form admits seeds nothing. A row a
|
|
450
|
+
// flat deny ALSO excludes still seeds, so successive owning lifts (deny, then allow) admit it.
|
|
451
|
+
const seedAllow = (adapter, model, displayModel) => {
|
|
452
|
+
// ponytail: a collapsed variant row's allow entry would name no channel (its display name is not the real
|
|
453
|
+
// model), so it is not seeded for same-session admission — classify, save, then admit next session;
|
|
454
|
+
// full grouped-row membership (collapse + catalog fold) is 2.6.7 (T10 owed review F1, D-1016)
|
|
455
|
+
if (model !== displayModel)
|
|
456
|
+
return;
|
|
457
|
+
const identity = groupOf(adapter)?.rows.find((row) => row.model === displayModel)?.evidence?.identity;
|
|
458
|
+
const self = { adapter, model: displayModel, ...(identity !== undefined ? { identity } : {}) };
|
|
459
|
+
const policy = stagedPolicy();
|
|
460
|
+
if (!policy.ok || !exclusionsOf(self, policy.routing, "judge").some((scope) => scope.by === "allow"))
|
|
461
|
+
return;
|
|
462
|
+
const entry = selectOwn(ui.allowOut, self) ?? `${adapter}:${displayModel}`;
|
|
463
|
+
ui.allowOut = new Set([...ui.allowOut, entry]);
|
|
464
|
+
ui.seededAllowOut = [
|
|
465
|
+
...ui.seededAllowOut.filter((seed) => seed.adapter !== adapter || seed.model !== model),
|
|
466
|
+
{ adapter, model, displayModel, ...(identity !== undefined ? { identity } : {}), entry },
|
|
467
|
+
];
|
|
468
|
+
};
|
|
424
469
|
const stageSuggested = (adapter, rows, firstTouch) => {
|
|
470
|
+
for (const row of rows)
|
|
471
|
+
seedAllow(adapter, row.model, row.displayModel);
|
|
425
472
|
ui.classifications = [
|
|
426
473
|
...ui.classifications,
|
|
427
474
|
...rows.map((row) => ({
|
|
@@ -476,6 +523,7 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], initialEfforts =
|
|
|
476
523
|
};
|
|
477
524
|
const applyClassification = (overlay) => {
|
|
478
525
|
const answered = overlay.vendor ? ui.channelByAdapter[overlay.adapter] : undefined;
|
|
526
|
+
seedAllow(overlay.adapter, overlay.model, overlay.displayModel ?? overlay.model);
|
|
479
527
|
ui.classifications = [
|
|
480
528
|
...ui.classifications,
|
|
481
529
|
{
|
|
@@ -733,7 +781,7 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], initialEfforts =
|
|
|
733
781
|
// channel must be OFFERED at or above the floor once every policy scope is open — a failed
|
|
734
782
|
// model probe (dropped by discovery), a pin or a pool keep it out of that list and a floor
|
|
735
783
|
// marks it belowFloor, so nothing here lifts it.
|
|
736
|
-
const eligible = candidatesForShape(overlay.shape, ui.selectedMode, ui.map, openStagedDeny()).rows
|
|
784
|
+
const eligible = candidatesForShape(overlay.shape, ui.selectedMode, ui.map, openStagedDeny(), stagedMetadata()).rows
|
|
737
785
|
.some((row) => row.id === id && !row.belowFloor);
|
|
738
786
|
if (!eligible) {
|
|
739
787
|
// the ledger line spells its reasons; drop the row label and the reach segment for the notice
|
|
@@ -756,7 +804,7 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], initialEfforts =
|
|
|
756
804
|
const text = `lift: cleared ${found.entry} from ${found.scope}${after.reach === "in" ? "" : ` — still out: ${after.reasons.join("; ")}`}`;
|
|
757
805
|
ui.lastEdit = { id, text };
|
|
758
806
|
ui.notice = text;
|
|
759
|
-
const picked = candidatesForShape(overlay.shape, ui.selectedMode, ui.map, stagedDeny());
|
|
807
|
+
const picked = candidatesForShape(overlay.shape, ui.selectedMode, ui.map, stagedDeny(), stagedMetadata());
|
|
760
808
|
ui.overlay = { ...overlay, rows: picked.rows, excludedNote: picked.excludedNote, ledger: picked.ledger ?? [], at: 0 };
|
|
761
809
|
bump();
|
|
762
810
|
};
|
|
@@ -1540,7 +1588,7 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], initialEfforts =
|
|
|
1540
1588
|
const group = modelGroups[ui.adapterAt];
|
|
1541
1589
|
const suggested = rows
|
|
1542
1590
|
.filter((candidate) => candidate.adapter === group.adapter && !candidate.tier && candidate.suggestion !== undefined)
|
|
1543
|
-
.map((candidate) => ({ model: candidate.classifyModel ?? candidate.model, suggestion: candidate.suggestion }));
|
|
1591
|
+
.map((candidate) => ({ model: candidate.classifyModel ?? candidate.model, displayModel: candidate.model, suggestion: candidate.suggestion }));
|
|
1544
1592
|
if (!suggested.length) {
|
|
1545
1593
|
ui.notice = "no catalog tier suggestions among the visible unclassified models — evidence comes from the cached catalogs (AA index / API pricing)";
|
|
1546
1594
|
bump();
|
|
@@ -1590,17 +1638,12 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], initialEfforts =
|
|
|
1590
1638
|
if (!shape)
|
|
1591
1639
|
return;
|
|
1592
1640
|
if (hotkey === "a") {
|
|
1593
|
-
|
|
1594
|
-
// the pin. prefer survives: it biases auto routing and is valid without pin/pool.
|
|
1595
|
-
const nextEntry = { ...ui.map[shape] };
|
|
1596
|
-
delete nextEntry.pin;
|
|
1597
|
-
delete nextEntry.pool;
|
|
1598
|
-
ui.map = { ...ui.map, [shape]: nextEntry };
|
|
1641
|
+
ui.map = { ...ui.map, [shape]: autoMapEntry(ui.map[shape]) };
|
|
1599
1642
|
bump();
|
|
1600
1643
|
return;
|
|
1601
1644
|
}
|
|
1602
1645
|
if (key.return || hotkey === "p") {
|
|
1603
|
-
const picked = candidatesForShape(shape, ui.selectedMode, ui.map, stagedDeny());
|
|
1646
|
+
const picked = candidatesForShape(shape, ui.selectedMode, ui.map, stagedDeny(), stagedMetadata());
|
|
1604
1647
|
setOverlay({
|
|
1605
1648
|
kind: "candidates",
|
|
1606
1649
|
shape,
|
|
@@ -1677,7 +1720,8 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], initialEfforts =
|
|
|
1677
1720
|
const reachDetail = (row) => {
|
|
1678
1721
|
if (row.evidence?.unauthed !== undefined)
|
|
1679
1722
|
return "unauthed — re-probe with tickmarkr doctor (Space does not toggle reach)";
|
|
1680
|
-
|
|
1723
|
+
// T10: the reasons, then where each scope is held — the overlay a reviewed w writes
|
|
1724
|
+
const reasons = `${row.reasons.join("; ")}${row.held.length ? ` — held: ${row.held.join("; ")}` : ""}`;
|
|
1681
1725
|
if (row.reach === "out-all")
|
|
1682
1726
|
return `reach: out · all seats — ${reasons} — Space picks in or out · workers`;
|
|
1683
1727
|
if (row.reach === "out-allow")
|
|
@@ -1791,7 +1835,7 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], initialEfforts =
|
|
|
1791
1835
|
if (!overlay)
|
|
1792
1836
|
return null;
|
|
1793
1837
|
if (overlay.kind === "presets") {
|
|
1794
|
-
const details = modeOptions[overlay.at] ? modePreview(modeOptions[overlay.at].id, ui.map, stagedDeny()) : [];
|
|
1838
|
+
const details = modeOptions[overlay.at] ? modePreview(modeOptions[overlay.at].id, ui.map, stagedDeny(), stagedMetadata()) : [];
|
|
1795
1839
|
return (_jsxs(OverlayPanel, { title: overlay.home ? "routing preset" : "routing mode", width: bodyW, children: [_jsx(Text, { dimColor: true, children: overlay.home ? "a preset routes every shape — custom opens the browser" : "floors move with the mode; the browser edits the rest" }), _jsx(Text, { children: " " }), modeOptions.map((option, index) => (_jsxs(Text, { children: [_jsx(Pointer, { on: overlay.at === index }), option.id === ui.selectedMode ? _jsx(Glyph, { kind: "on" }) : _jsx(Text, { children: " " }), _jsx(Text, { bold: overlay.at === index, children: ` ${padCell(option.id, 12)}` }), _jsx(Text, { dimColor: true, children: clip(option.gloss, bodyW - 20) })] }, option.id))), overlay.home && (_jsxs(Text, { children: [_jsx(Pointer, { on: overlay.at === modeOptions.length }), _jsx(Text, { children: " " }), _jsx(Text, { bold: overlay.at === modeOptions.length, children: ` ${padCell("custom", 12)}` }), _jsx(Text, { dimColor: true, children: clip("open the fleet browser (models · shapes · steering)", bodyW - 20) })] })), _jsx(Text, { children: " " }), details.map((line) => _jsx(Text, { dimColor: true, children: line }, line))] }));
|
|
1796
1840
|
}
|
|
1797
1841
|
if (overlay.kind === "review") {
|
|
@@ -1936,8 +1980,8 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], initialEfforts =
|
|
|
1936
1980
|
lines.push(coveringDetail(row));
|
|
1937
1981
|
if (!row.tier) {
|
|
1938
1982
|
lines.push(row.suggestion
|
|
1939
|
-
? `catalog suggests ${row.suggestion.tier} — Space/Enter classifies with the note pre-typed · s stages every visible suggestion`
|
|
1940
|
-
: "unclassified — Space/Enter classifies; unclassified models are never routed");
|
|
1983
|
+
? `catalog suggests ${row.suggestion.tier} — Space/Enter classifies with the note pre-typed (t too) · s stages every visible suggestion`
|
|
1984
|
+
: "unclassified — Space/Enter classifies (t too), then Space sets its reach in this session; unclassified models are never routed");
|
|
1941
1985
|
}
|
|
1942
1986
|
}
|
|
1943
1987
|
return lines;
|
|
@@ -2054,7 +2098,7 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], initialEfforts =
|
|
|
2054
2098
|
? _jsx(Text, { color: INK.warn, children: ` ! ${clip(ui.notice, width - 4)}` })
|
|
2055
2099
|
: details.map((line) => _jsx(Text, { dimColor: true, children: ` ${clip(line, width - 2)}` }, line)), _jsx(KeyBar, { binds: keyBinds() })] }));
|
|
2056
2100
|
}
|
|
2057
|
-
export async function runFleetInkEditor({ ageMs, adapters, health, initialAllowOut = [], initialEfforts, modelGroups, initialMode, modeOptions, initialMap, modePreview, shapeRows, candidatesForShape, preferOptionsForShape, initialSteering, steeringOptionsFor, reviewAdvisories = [], reviewOverlay, reloadGuard, stagedRouting, entry = "probe", initialJudge = "", judgeSeats = [], initialInput = [], input, output, debug = false, ...initialDenyProps }) {
|
|
2101
|
+
export async function runFleetInkEditor({ ageMs, adapters, health, initialAllowOut = [], initialEfforts, modelGroups, initialMode, modeOptions, initialMap, modePreview, shapeRows, candidatesForShape, preferOptionsForShape, initialSteering, steeringOptionsFor, reviewAdvisories = [], reviewOverlay, reloadGuard, stagedRouting, holderOf, entry = "probe", initialJudge = "", judgeSeats = [], initialInput = [], input, output, debug = false, ...initialDenyProps }) {
|
|
2058
2102
|
const agents = adapters.flatMap((adapter) => {
|
|
2059
2103
|
const state = health[adapter.id];
|
|
2060
2104
|
if (!state?.installed)
|
|
@@ -2078,7 +2122,7 @@ export async function runFleetInkEditor({ ageMs, adapters, health, initialAllowO
|
|
|
2078
2122
|
// rebuilt from output.columns/rows so a pane resize re-renders at the new size. Same root
|
|
2079
2123
|
// element type on every rerender ⇒ React keeps the component instance, so staged edits,
|
|
2080
2124
|
// cursor, and overlays all survive the resize.
|
|
2081
|
-
const editorElement = () => (_jsx(FleetApp, { ageMs: ageMs, agents: agents, ...initialDenyProps, initialAllowOut: initialAllowOut, initialEfforts: initialEfforts, modelGroups: declaredModelGroups, initialMode: initialMode, modeOptions: modeOptions, initialMap: initialMap, modePreview: modePreview, shapeRows: shapeRows, candidatesForShape: candidatesForShape, preferOptionsForShape: preferOptionsForShape, initialSteering: initialSteering, steeringOptionsFor: steeringOptionsFor, reviewAdvisories: reviewAdvisories, reviewOverlay: reviewOverlay, reloadGuard: reloadGuard, stagedRouting: stagedRouting, entry: entry, initialJudge: initialJudge, judgeSeats: judgeSeats,
|
|
2125
|
+
const editorElement = () => (_jsx(FleetApp, { ageMs: ageMs, agents: agents, ...initialDenyProps, initialAllowOut: initialAllowOut, initialEfforts: initialEfforts, modelGroups: declaredModelGroups, initialMode: initialMode, modeOptions: modeOptions, initialMap: initialMap, modePreview: modePreview, shapeRows: shapeRows, candidatesForShape: candidatesForShape, preferOptionsForShape: preferOptionsForShape, initialSteering: initialSteering, steeringOptionsFor: steeringOptionsFor, reviewAdvisories: reviewAdvisories, reviewOverlay: reviewOverlay, reloadGuard: reloadGuard, stagedRouting: stagedRouting, holderOf: holderOf, entry: entry, initialJudge: initialJudge, judgeSeats: judgeSeats,
|
|
2082
2126
|
// v1.90.9: cap every list to the terminal minus chrome so the cursor and header can
|
|
2083
2127
|
// never scroll off-screen (omp's 218-model list did exactly that pre-cap).
|
|
2084
2128
|
// OBS-523: chrome is ≤12 rows worst case (header, borders, padding, scope+search,
|
package/package.json
CHANGED
|
@@ -12,7 +12,7 @@ Use the canonical loop skill's [cockpit, parked decisions and printed twins](../
|
|
|
12
12
|
for each target: delivered views are **1 Home, 4 Run, 5 Evidence**; Fleet/Bootstrap and
|
|
13
13
|
Plan/Health remain follow-ons with CLI entries. The recorded 1/3 merged, human T2, blocked
|
|
14
14
|
T3 case stays PARTIAL despite tip pass. Review the Run confirmation, read back the appended
|
|
15
|
-
`task-approved` receipt, and explicitly `tickmarkr resume <runId>` when no live owner exists;
|
|
15
|
+
`task-approved` receipt, and explicitly launch `tickmarkr resume <runId>` with the host-owned split form below when no live owner exists;
|
|
16
16
|
approval records permission and never dispatches work itself. Non-TTY readers keep `status`,
|
|
17
17
|
`report` and default-watch line output, with `--watch --plain` as the TTY fallback.
|
|
18
18
|
Keep `name: tickmarkr-auto` and the installed links to canonical `skills/` intact.
|
|
@@ -92,7 +92,37 @@ After sending, **confirm delivery** by reading the target pane and verifying the
|
|
|
92
92
|
1. **Prepare** — confirm the target list. Run the [binary preflight](#binary-preflight-before-compile-or-run). Check `git status`, confirm no tickmarkr run is active, and work from a non-main branch.
|
|
93
93
|
2. **Compile** — run `tickmarkr compile <spec-or-directory>`. Fix source-spec defects instead of editing the generated graph.
|
|
94
94
|
3. **Plan** — run `tickmarkr plan`. Review routes, capability-floor warnings, and human gates before execution.
|
|
95
|
-
4. **Run** —
|
|
95
|
+
4. **Run** — launch `tickmarkr run` using the [host-owned split form](#host-owned-daemon-and-detached-beats), and start detached beats with their run-end stop items. A watch ending the seat's turn is no watch: keep a **blocking journal consumer** alive for the run's terminal events — the shipped watcher below, or a foreground `until grep` on the run's terminal events — and ensure it is re-armed at most every twenty minutes. Never rely on a `Monitor`-only wake. Watch the run journal rather than polling agents, using the shipped watcher — `.claude/skills/tickmarkr-overseer/scripts/watch-journal.sh <state-dir>/runs 20 28800` — which takes a line baseline at arm time, then wakes ONCE on `run-end`, `task-human`, `task-failed` or `consult-verdict` and grades the run-end summary against every execution clause for you; the debt clause is yours — read CURRENT `tickmarkr status <runId>` (step 5). Re-arm after every wake. ⛔ Never `tail -F | grep -m1` (run-end is the journal's last line, so tail never notices the broken pipe and the watcher hangs forever) and never a pane-level done wait (it fires on every agent turn end, not mission end). ⚠ A bare whole-file `grep -q '"event":"run-end"'` is the trap the watcher exists to avoid: on a resume it matches the PREVIOUS run's run-end and returns instantly, so a re-armed watcher reads as coverage that does not exist. Resolve blocked interactions in the relevant agent session.
|
|
96
96
|
5. **Verify and consolidate** — continue only after a green run. A run is green when the run-end event exists in the journal, the tip verify is not "failed", the summary's `failed`, `human`, `blocked` and `pending` buckets are all empty, and CURRENT `tickmarkr status <runId>` reads its owed checks outstanding empty AND known (`outstanding 0`) — a run with a parked task is partial, not green. Empty execution buckets alone are not green either (D-660): every bucket empty and the tip passed, but an operator waived one review, leaves one accepted-risk review check owed — status reads `outstanding 1 (T7 review)` and `run`/`resume` still exit 0 on execution alone, so the run is execution complete, not green. It turns green only when a `tickmarkr verify --record <runId>` discharge moves CURRENT status to `outstanding 0`, including a discharge landing after run-end — the historical run-end record keeps the old count, so never read debt from it; `outstanding unknown` is never green. Tickmarkr consolidates accepted work on `tickmarkr/<runId>` and never signs off to the main branch. A human controls any later release merge.
|
|
97
97
|
6. **Record** — `tickmarkr report <runId> --md` prints Markdown to stdout; redirect explicitly beside the spec (for example `tickmarkr report <runId> --md > feature.record.md`) and commit the execution record when the repository tracks those records.
|
|
98
98
|
7. **Continue** — move to the next requested target. If a target fails or is parked, stop with the journal evidence rather than silently skipping it.
|
|
99
|
+
|
|
100
|
+
## Host-owned daemon and detached beats
|
|
101
|
+
|
|
102
|
+
Launch the daemon as a SPLIT of the orchestrator's own pane in the ORCH tab, owned by the
|
|
103
|
+
host PTY. Never launch it as an agent harness background task or in a separate tab. The daemon
|
|
104
|
+
self-places its board from that split, keeping both daemon and board in the ORCH tab.
|
|
105
|
+
|
|
106
|
+
- **On Orca (`TERM_PROGRAM=Orca` and non-empty `ORCA_TERMINAL_HANDLE`)**:
|
|
107
|
+
`orca terminal split --terminal "$ORCA_TERMINAL_HANDLE" --direction vertical --command "tickmarkr run"`.
|
|
108
|
+
- **On herdr (`HERDR_ENV=1`)**: use `herdr pane split` on the orchestrator's own pane, then
|
|
109
|
+
`herdr pane run <new> "tickmarkr run"` in the returned split.
|
|
110
|
+
|
|
111
|
+
For `resume`, use the same host-owned split form with `tickmarkr resume <runId>` in place of
|
|
112
|
+
`tickmarkr run`. After either launch, read this repository's lock pid and walk its ppid chain
|
|
113
|
+
in the process table to the host PTY; verify no agent session is an ancestor. If an agent is
|
|
114
|
+
an ancestor, stop that launch and relaunch through the host split before continuing.
|
|
115
|
+
|
|
116
|
+
Start `tickmarkr beat <tier> --seat <seat> --loop` as a DETACHED background process, one per
|
|
117
|
+
(tier, seat), from the repository root. Use setsid plus nohup (or the host's equivalent detached
|
|
118
|
+
session facility), redirect output to a file, and record a pidfile under the repository's state
|
|
119
|
+
dir. Verify ppid 1 and no agent-session ancestor. Never put beats in a harness background task
|
|
120
|
+
or a visible tab. The lifecycle must be idempotent: start checks the pidfile against the live
|
|
121
|
+
process payload and returns an "already running" no-op for the same (repo, tier, seat); status
|
|
122
|
+
checks that payload and beat freshness; stop sends `--stand-down` for the same tier and seat,
|
|
123
|
+
verifies the loop exits within one interval, then removes its pidfile. A stale or reused pid
|
|
124
|
+
must never be killed without matching its live payload. Include a run-end stop item for every
|
|
125
|
+
beat started, also on failure, park or handoff; verify DISARMED and remove retired beat files.
|
|
126
|
+
|
|
127
|
+
Keep only watchers that must WAKE the seat in the harness. Re-arm them on each wake and at
|
|
128
|
+
their harness cap; daemon and beat lifetimes must not depend on that cap.
|
|
@@ -88,10 +88,40 @@ When spawning consultants (agents gathering synthesis input for decisions like S
|
|
|
88
88
|
1. **Prepare** — start from the requested spec. Run the [binary preflight](#binary-preflight-before-compile-or-run). Check `git status`, confirm no tickmarkr run is active, and work from a non-main branch.
|
|
89
89
|
2. **Compile** — run `tickmarkr compile <spec>`. Correct compilation errors in the spec, never in the generated graph.
|
|
90
90
|
3. **Plan** — run `tickmarkr plan`. Review the routing table, capability-floor warnings, and every human gate, including work that each gate blocks.
|
|
91
|
-
4. **Run** —
|
|
91
|
+
4. **Run** — launch `tickmarkr run` using the [host-owned split form](#host-owned-daemon-and-detached-beats), and start detached beats with their run-end stop items. A watch ending the seat's turn is no watch: keep a **blocking journal consumer** alive for the run's terminal events — the shipped watcher below, or a foreground `until grep` on the run's terminal events — and ensure it is re-armed at most every twenty minutes. Never rely on a `Monitor`-only wake. Watch the run journal rather than polling agents, using the shipped watcher — `.claude/skills/tickmarkr-overseer/scripts/watch-journal.sh <state-dir>/runs 20 28800` — which takes a line baseline at arm time, then wakes ONCE on `run-end`, `task-human`, `task-failed` or `consult-verdict` and grades the run-end summary against every execution clause for you; the debt clause is yours — read CURRENT `tickmarkr status <runId>` (step 5). Re-arm after every wake. ⛔ Never `tail -F | grep -m1` (run-end is the journal's last line, so tail never notices the broken pipe and the watcher hangs forever) and never a pane-level done wait (it fires on every agent turn end, not mission end). ⚠ A bare whole-file `grep -q '"event":"run-end"'` is the trap the watcher exists to avoid: on a resume it matches the PREVIOUS run's run-end and returns instantly, so a re-armed watcher reads as coverage that does not exist. Resolve blocked interactions in the agent session; do not turn them into proxy questions.
|
|
92
92
|
5. **Verify and consolidate** — accept only a green run. A run is green when the run-end event exists in the journal, the tip verify is not "failed", the summary's `failed`, `human`, `blocked` and `pending` buckets are all empty, and CURRENT `tickmarkr status <runId>` reads its owed checks outstanding empty AND known (`outstanding 0`) — a run with a parked task is partial, not green. Empty execution buckets alone are not green either (D-660): every bucket empty and the tip passed, but an operator waived one review, leaves one accepted-risk review check owed — status reads `outstanding 1 (T7 review)` and `run`/`resume` still exit 0 on execution alone, so the run is execution complete, not green. It turns green only when a `tickmarkr verify --record <runId>` discharge moves CURRENT status to `outstanding 0`, including a discharge landing after run-end — the historical run-end record keeps the old count, so never read debt from it; `outstanding unknown` is never green. Tickmarkr consolidates accepted task work on `tickmarkr/<runId>`; it never signs off to the main branch. A human may later merge that integration branch through the repository's normal release process.
|
|
93
93
|
6. **Record** — `tickmarkr report <runId> --md` prints Markdown to stdout. Redirect it explicitly beside the source spec (for example `tickmarkr report <runId> --md > feature.record.md`) and commit the execution record when the repository tracks those records. Then [stand down](#stand-down-mission-end-and-retirement).
|
|
94
94
|
|
|
95
|
+
## Host-owned daemon and detached beats
|
|
96
|
+
|
|
97
|
+
Launch the daemon as a SPLIT of the orchestrator's own pane in the ORCH tab, owned by the
|
|
98
|
+
host PTY. Never launch it as an agent harness background task or in a separate tab. The daemon
|
|
99
|
+
self-places its board from that split, keeping both daemon and board in the ORCH tab.
|
|
100
|
+
|
|
101
|
+
- **On Orca (`TERM_PROGRAM=Orca` and non-empty `ORCA_TERMINAL_HANDLE`)**:
|
|
102
|
+
`orca terminal split --terminal "$ORCA_TERMINAL_HANDLE" --direction vertical --command "tickmarkr run"`.
|
|
103
|
+
- **On herdr (`HERDR_ENV=1`)**: use `herdr pane split` on the orchestrator's own pane, then
|
|
104
|
+
`herdr pane run <new> "tickmarkr run"` in the returned split.
|
|
105
|
+
|
|
106
|
+
For `resume`, use the same host-owned split form with `tickmarkr resume <runId>` in place of
|
|
107
|
+
`tickmarkr run`. After either launch, read this repository's lock pid and walk its ppid chain
|
|
108
|
+
in the process table to the host PTY; verify no agent session is an ancestor. If an agent is
|
|
109
|
+
an ancestor, stop that launch and relaunch through the host split before continuing.
|
|
110
|
+
|
|
111
|
+
Start `tickmarkr beat <tier> --seat <seat> --loop` as a DETACHED background process, one per
|
|
112
|
+
(tier, seat), from the repository root. Use setsid plus nohup (or the host's equivalent detached
|
|
113
|
+
session facility), redirect output to a file, and record a pidfile under the repository's state
|
|
114
|
+
dir. Verify ppid 1 and no agent-session ancestor. Never put beats in a harness background task
|
|
115
|
+
or a visible tab. The lifecycle must be idempotent: start checks the pidfile against the live
|
|
116
|
+
process payload and returns an "already running" no-op for the same (repo, tier, seat); status
|
|
117
|
+
checks that payload and beat freshness; stop sends `--stand-down` for the same tier and seat,
|
|
118
|
+
verifies the loop exits within one interval, then removes its pidfile. A stale or reused pid
|
|
119
|
+
must never be killed without matching its live payload. Include a run-end stop item for every
|
|
120
|
+
beat started, also on failure, park or handoff; verify DISARMED and remove retired beat files.
|
|
121
|
+
|
|
122
|
+
Keep only watchers that must WAKE the seat in the harness. Re-arm them on each wake and at
|
|
123
|
+
their harness cap; daemon and beat lifetimes must not depend on that cap.
|
|
124
|
+
|
|
95
125
|
## Cockpit, parked decisions and printed twins
|
|
96
126
|
|
|
97
127
|
The shipped cockpit is **1 Home, 4 Run, 5 Evidence**: `tickmarkr ui [runId]` defaults to
|
|
@@ -107,7 +137,7 @@ run/task, original park `#L`, actor/reason, exact argv, consequence and enactor.
|
|
|
107
137
|
confirms; `n`/Esc cancel; Enter never confirms. Read the receipt's appended `task-approved`
|
|
108
138
|
line and actor/reason back from the journal. Approval records permission, never dispatch,
|
|
109
139
|
a passed gate or task completion. With no live owner it says **approved; resume required**:
|
|
110
|
-
exit the observer and
|
|
140
|
+
exit the observer and launch `tickmarkr resume <runId>` explicitly with the same host-owned split form above. A matching live daemon
|
|
111
141
|
enacts at its next task boundary; if a different live run owns the repository lock, wait
|
|
112
142
|
for that run to end before resuming this one.
|
|
113
143
|
|
|
@@ -177,6 +207,20 @@ repair and catalog refresh; default doctor and `--fix` still probe. `scope <inte
|
|
|
177
207
|
--preview` performs no probes, model calls or writes; authoring requires confirmation or
|
|
178
208
|
`--yes`. These actions remain CLI entries until their follow-on views ship.
|
|
179
209
|
|
|
210
|
+
**macOS host failure: a launchservicesd restart (D-787/D-789).** If GUI apps suddenly read "The
|
|
211
|
+
application … is not open anymore" during a local suite, launchservicesd crashed and respawned with an
|
|
212
|
+
empty app registry. That was measured on 2026-09-30: `0x1000600b` Mach port exhaustion under node
|
|
213
|
+
`process.title` check-ins, crash at 17:15:00Z and respawn at 17:15:09Z. `tickmarkr doctor` and standalone
|
|
214
|
+
`tickmarkr verify` record the daemon's pid/start through the separate `src/run/launchservices-check.ts`
|
|
215
|
+
module: one bounded `/bin/ps` read with a 5000 ms kill ceiling. doctor's wiring is five lines in
|
|
216
|
+
`src/cli/commands/doctor.ts`. The identity lives on the first `doctor.json` record, and one advisory
|
|
217
|
+
prints and persists when it changed. Unknown evidence keeps the prior identity, and verdicts and retries
|
|
218
|
+
never change. Confirm the identity with `/bin/ps -o pid=,lstart= -p "$(pgrep -x launchservicesd)"`: it
|
|
219
|
+
is young beside Dock and Finder. Confirm the log with `/usr/bin/log show --start '<that lstart minus two
|
|
220
|
+
minutes>'`, looking for `0x1000600b` then `Successfully spawned launchservicesd`. Type the full path: a bare
|
|
221
|
+
`log` is a zsh builtin that prints nothing. Then quit and relaunch the affected apps, or log out /
|
|
222
|
+
restart once the run is stood down; a restart ends agent sessions and wipes `/private/tmp`.
|
|
223
|
+
|
|
180
224
|
Keep this skill's canonical identity `tickmarkr-loop`; sibling skills link to this
|
|
181
225
|
walkthrough. In this repository `skills/` is canonical and installed `.claude/skills/`
|
|
182
226
|
files resolve to it; change the canonical source, never create a second help skill.
|