tickmarkr 1.72.0 → 1.74.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/approve.js +21 -3
- package/dist/cli/commands/resume.js +10 -2
- package/dist/cli/commands/status.d.ts +2 -0
- package/dist/cli/commands/status.js +260 -41
- package/dist/cli/commands/ui.d.ts +6 -2
- package/dist/cli/commands/ui.js +2 -4
- package/dist/config/config.d.ts +4 -0
- package/dist/config/config.js +6 -1
- package/dist/drivers/herdr.d.ts +5 -0
- package/dist/drivers/herdr.js +100 -10
- package/dist/drivers/subprocess.d.ts +3 -3
- package/dist/drivers/subprocess.js +10 -5
- package/dist/gates/acceptance.js +19 -1
- package/dist/gates/llm.d.ts +4 -0
- package/dist/gates/llm.js +16 -3
- package/dist/gates/run-gates.d.ts +1 -1
- package/dist/gates/run-gates.js +25 -3
- package/dist/route/preference.d.ts +2 -1
- package/dist/route/preference.js +8 -2
- package/dist/run/daemon.d.ts +1 -0
- package/dist/run/daemon.js +139 -4
- package/dist/run/git.d.ts +2 -0
- package/dist/run/git.js +55 -0
- package/dist/run/journal.d.ts +21 -1
- package/dist/run/journal.js +83 -5
- package/dist/tui/ink/components.d.ts +18 -0
- package/dist/tui/ink/components.js +30 -2
- package/dist/tui/ink/studio-app.d.ts +59 -0
- package/dist/tui/ink/studio-app.js +320 -0
- package/package.json +1 -1
- package/dist/tui/app.d.ts +0 -72
- package/dist/tui/app.js +0 -334
- package/dist/tui/engine.d.ts +0 -52
- package/dist/tui/engine.js +0 -92
- package/dist/tui/frame.d.ts +0 -22
- package/dist/tui/frame.js +0 -56
- package/dist/tui/input.d.ts +0 -41
- package/dist/tui/input.js +0 -100
- package/dist/tui/views/consult-dossier.d.ts +0 -49
- package/dist/tui/views/consult-dossier.js +0 -169
- package/dist/tui/views/diff-modal.d.ts +0 -9
- package/dist/tui/views/diff-modal.js +0 -19
- package/dist/tui/views/fleet-view.d.ts +0 -91
- package/dist/tui/views/fleet-view.js +0 -337
- package/dist/tui/views/preview-view.d.ts +0 -7
- package/dist/tui/views/preview-view.js +0 -80
- package/dist/tui/views/profile-view.d.ts +0 -15
- package/dist/tui/views/profile-view.js +0 -93
- package/dist/tui/views/routing-view.d.ts +0 -48
- package/dist/tui/views/routing-view.js +0 -234
- package/dist/tui/views/runs-view.d.ts +0 -70
- package/dist/tui/views/runs-view.js +0 -387
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
import { channelsFromConfig } from "../../adapters/types.js";
|
|
2
|
-
import { candidateRow, shapeCandidates } from "../../cli/commands/fleet-picker.js";
|
|
3
|
-
import { DEFAULT_CONFIG, fleetEditableFromConfig, INTEGRITY_FLOOR_SHAPES, loadConfigWithMode, ROUTING_MODES, TIER_RANK, } from "../../config/config.js";
|
|
4
|
-
import { SHAPES } from "../../graph/schema.js";
|
|
5
|
-
import { FleetStaging } from "../staging.js";
|
|
6
|
-
// Same preview task fleet.ts's picker ranks (one per shape, complexity 3): the inspector must
|
|
7
|
-
// rank the exact inputs the fleet editor ranks, or the two surfaces could disagree.
|
|
8
|
-
export function routingPreviewTask(shape) {
|
|
9
|
-
return {
|
|
10
|
-
id: "routing-preview",
|
|
11
|
-
title: "routing preview",
|
|
12
|
-
goal: "preview",
|
|
13
|
-
shape,
|
|
14
|
-
complexity: 3,
|
|
15
|
-
acceptance: ["done"],
|
|
16
|
-
deps: [],
|
|
17
|
-
files: [],
|
|
18
|
-
context: [],
|
|
19
|
-
gates: ["build", "test", "lint", "evidence", "scope", "acceptance", "review"],
|
|
20
|
-
humanGate: false,
|
|
21
|
-
status: "pending",
|
|
22
|
-
evidence: { commits: [], artifacts: [], gateResults: [] },
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
const isIntegrity = (shape) => INTEGRITY_FLOOR_SHAPES.includes(shape);
|
|
26
|
-
// Staged-cell glyph — the same ● the shell status bar counts staged changes with (app.ts).
|
|
27
|
-
const STAGED_MARK = "●";
|
|
28
|
-
const lowerTier = (t) => (t === "frontier" ? "mid" : "cheap");
|
|
29
|
-
const maxTier = (a, b) => (TIER_RANK[a] >= TIER_RANK[b] ? a : b);
|
|
30
|
-
// config.ts presetFloor mirrored (module-private there): a mode compiles into floors at load,
|
|
31
|
-
// so staging a mode stages exactly this table.
|
|
32
|
-
function presetFloor(mode, shape) {
|
|
33
|
-
const dflt = DEFAULT_CONFIG.routing.floors[shape] ?? "cheap";
|
|
34
|
-
if (mode === "partner-led")
|
|
35
|
-
return "frontier";
|
|
36
|
-
if (mode === "staff-led")
|
|
37
|
-
return maxTier(lowerTier(dflt), isIntegrity(shape) ? "frontier" : "cheap");
|
|
38
|
-
return dflt;
|
|
39
|
-
}
|
|
40
|
-
function floorsEqual(a, b) {
|
|
41
|
-
const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
42
|
-
for (const k of keys)
|
|
43
|
-
if (a[k] !== b[k])
|
|
44
|
-
return false;
|
|
45
|
-
return true;
|
|
46
|
-
}
|
|
47
|
-
/** The header mode is DERIVED from the buffer, never stored: clean floors show the loaded mode;
|
|
48
|
-
* floors matching a preset show that mode (staged); anything else reads "custom". */
|
|
49
|
-
function deriveMode(loadedMode, loaded, buffer) {
|
|
50
|
-
if (floorsEqual(loaded.floors, buffer.floors))
|
|
51
|
-
return { mode: loadedMode, staged: false };
|
|
52
|
-
for (const mode of ROUTING_MODES) {
|
|
53
|
-
if (SHAPES.every((s) => buffer.floors[s] === presetFloor(mode, s)))
|
|
54
|
-
return { mode, staged: true };
|
|
55
|
-
}
|
|
56
|
-
return { mode: "custom", staged: true };
|
|
57
|
-
}
|
|
58
|
-
/** The config the router would see with the staged policy applied: map + floors ride the buffer
|
|
59
|
-
* (deny/tier edits are the fleet view's lane, T3). The buffer snapshot is already detached. */
|
|
60
|
-
function effectiveConfig(cfg, buffer) {
|
|
61
|
-
return { ...cfg, routing: { ...cfg.routing, map: buffer.map, floors: buffer.floors } };
|
|
62
|
-
}
|
|
63
|
-
// No-arg shell path (app.ts constructs views without deps): load the operator's resolved config
|
|
64
|
-
// and derive channels from the configured tier table. Best-effort — a load failure degrades the
|
|
65
|
-
// view to an explanatory note instead of breaking the studio.
|
|
66
|
-
function loadDefaultData(repoRoot) {
|
|
67
|
-
try {
|
|
68
|
-
const { cfg, mode } = loadConfigWithMode(repoRoot);
|
|
69
|
-
const channels = Object.keys(cfg.tiers).flatMap((id) => channelsFromConfig(id, cfg));
|
|
70
|
-
return { cfg, channels, mode: mode.mode };
|
|
71
|
-
}
|
|
72
|
-
catch {
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
export function createRoutingView(deps = {}) {
|
|
77
|
-
const data = deps.data ?? loadDefaultData(deps.repoRoot ?? process.cwd());
|
|
78
|
-
// The one edit authority: every mutating method below is a staging.apply()/revert() over this
|
|
79
|
-
// buffer, so a staged change can never hide in private view state.
|
|
80
|
-
const staging = deps.staging ??
|
|
81
|
-
new FleetStaging(data ? fleetEditableFromConfig(data.cfg) : { denyAdapters: [], denyModels: [], tiers: {}, map: {}, floors: {} });
|
|
82
|
-
let cursor = 0;
|
|
83
|
-
let inspector = null;
|
|
84
|
-
const rankInspector = (shape) => {
|
|
85
|
-
if (!data)
|
|
86
|
-
return [];
|
|
87
|
-
// THE parity seam: rank through the same shared picker glue the fleet editor uses — no
|
|
88
|
-
// comparator lives here. Clean buffer ⇒ the injected config by reference (the delegation
|
|
89
|
-
// tests pin that identity); staged edits ⇒ the effective config, what the router would
|
|
90
|
-
// choose given the staged inputs.
|
|
91
|
-
const cfgNow = staging.isDirty ? effectiveConfig(data.cfg, staging.current) : data.cfg;
|
|
92
|
-
return shapeCandidates(routingPreviewTask(shape), cfgNow, data.channels, data.profile);
|
|
93
|
-
};
|
|
94
|
-
const rowOf = (c) => candidateRow(c, data?.cfg.pricing ?? {});
|
|
95
|
-
// Re-rank the open inspector after a staging mutation changed the ranking inputs.
|
|
96
|
-
const refreshInspector = () => {
|
|
97
|
-
if (!inspector)
|
|
98
|
-
return;
|
|
99
|
-
inspector.ranked = rankInspector(inspector.shape);
|
|
100
|
-
inspector.cursor = Math.min(inspector.cursor, Math.max(inspector.ranked.length - 1, 0));
|
|
101
|
-
};
|
|
102
|
-
return {
|
|
103
|
-
id: "routing",
|
|
104
|
-
label: "Routing",
|
|
105
|
-
get selectedShape() {
|
|
106
|
-
return SHAPES[cursor];
|
|
107
|
-
},
|
|
108
|
-
get selectedMode() {
|
|
109
|
-
const loadedMode = data?.mode ?? data?.cfg.routing.mode ?? "risk-based";
|
|
110
|
-
return deriveMode(loadedMode, staging.loadedState, staging.current).mode;
|
|
111
|
-
},
|
|
112
|
-
get staging() {
|
|
113
|
-
return staging;
|
|
114
|
-
},
|
|
115
|
-
moveCursor(delta) {
|
|
116
|
-
cursor = (cursor + delta + SHAPES.length) % SHAPES.length;
|
|
117
|
-
},
|
|
118
|
-
moveInspectorCursor(delta) {
|
|
119
|
-
if (!inspector || inspector.ranked.length === 0)
|
|
120
|
-
return;
|
|
121
|
-
const n = inspector.ranked.length;
|
|
122
|
-
inspector.cursor = (inspector.cursor + delta + n) % n;
|
|
123
|
-
},
|
|
124
|
-
openInspector(shape) {
|
|
125
|
-
if (!data)
|
|
126
|
-
return;
|
|
127
|
-
const target = shape ?? SHAPES[cursor];
|
|
128
|
-
inspector = { shape: target, cursor: 0, ranked: rankInspector(target) };
|
|
129
|
-
},
|
|
130
|
-
closeInspector() {
|
|
131
|
-
inspector = null;
|
|
132
|
-
},
|
|
133
|
-
stageInspectorPin() {
|
|
134
|
-
if (!data || !inspector)
|
|
135
|
-
return;
|
|
136
|
-
const target = inspector.shape;
|
|
137
|
-
const chosen = inspector.ranked[inspector.cursor];
|
|
138
|
-
if (!chosen)
|
|
139
|
-
return;
|
|
140
|
-
const { adapter, model } = chosen.assignment;
|
|
141
|
-
staging.apply((buffer) => {
|
|
142
|
-
buffer.map[target] = { ...buffer.map[target], pin: { via: adapter, model } };
|
|
143
|
-
});
|
|
144
|
-
// No re-rank: shapeCandidates strips the shape's own pin from the ranking inputs.
|
|
145
|
-
},
|
|
146
|
-
stageFloor(tier) {
|
|
147
|
-
if (!data)
|
|
148
|
-
return;
|
|
149
|
-
const target = SHAPES[cursor];
|
|
150
|
-
staging.apply((buffer) => {
|
|
151
|
-
buffer.floors[target] = tier;
|
|
152
|
-
});
|
|
153
|
-
refreshInspector();
|
|
154
|
-
},
|
|
155
|
-
stagePrefer(chain) {
|
|
156
|
-
if (!data)
|
|
157
|
-
return;
|
|
158
|
-
const target = SHAPES[cursor];
|
|
159
|
-
staging.apply((buffer) => {
|
|
160
|
-
const entry = { ...buffer.map[target] };
|
|
161
|
-
if (chain.length)
|
|
162
|
-
entry.prefer = [...chain];
|
|
163
|
-
else
|
|
164
|
-
delete entry.prefer;
|
|
165
|
-
// an emptied entry leaves no phantom delta behind
|
|
166
|
-
if (Object.keys(entry).length)
|
|
167
|
-
buffer.map[target] = entry;
|
|
168
|
-
else
|
|
169
|
-
delete buffer.map[target];
|
|
170
|
-
});
|
|
171
|
-
refreshInspector();
|
|
172
|
-
},
|
|
173
|
-
stageMode(mode) {
|
|
174
|
-
if (!data)
|
|
175
|
-
return;
|
|
176
|
-
staging.apply((buffer) => {
|
|
177
|
-
for (const shape of SHAPES)
|
|
178
|
-
buffer.floors[shape] = presetFloor(mode, shape);
|
|
179
|
-
});
|
|
180
|
-
refreshInspector();
|
|
181
|
-
},
|
|
182
|
-
revert() {
|
|
183
|
-
staging.revert();
|
|
184
|
-
refreshInspector();
|
|
185
|
-
},
|
|
186
|
-
inspectorRows() {
|
|
187
|
-
return inspector ? inspector.ranked.map(rowOf) : null;
|
|
188
|
-
},
|
|
189
|
-
render: () => {
|
|
190
|
-
const lines = ["Routing view — per-shape policy grid"];
|
|
191
|
-
if (!data) {
|
|
192
|
-
lines.push("routing data unavailable — see `tickmarkr fleet --print` for the line-mode surface");
|
|
193
|
-
return lines;
|
|
194
|
-
}
|
|
195
|
-
const { cfg } = data;
|
|
196
|
-
const buffer = staging.current;
|
|
197
|
-
const loaded = staging.loadedState;
|
|
198
|
-
const loadedMode = data.mode ?? cfg.routing.mode ?? "risk-based";
|
|
199
|
-
const { mode, staged: modeStaged } = deriveMode(loadedMode, loaded, buffer);
|
|
200
|
-
lines.push(`mode: ${mode}${modeStaged ? ` ${STAGED_MARK} (staged)` : ""}`);
|
|
201
|
-
lines.push(` ${"shape".padEnd(10)}${"floor".padEnd(10)}${"pin".padEnd(20)}prefer`);
|
|
202
|
-
for (const [i, shape] of SHAPES.entries()) {
|
|
203
|
-
const entry = buffer.map[shape];
|
|
204
|
-
const loadedEntry = loaded.map[shape];
|
|
205
|
-
const floor = buffer.floors[shape] ?? "—";
|
|
206
|
-
const floorStaged = loaded.floors[shape] !== buffer.floors[shape];
|
|
207
|
-
const marker = isIntegrity(shape) ? "*" : "";
|
|
208
|
-
const pin = entry?.pin ? `${entry.pin.via}:${entry.pin.model}` : "—";
|
|
209
|
-
const prefer = entry?.prefer?.length ? entry.prefer.join(", ") : "—";
|
|
210
|
-
const pinStaged = JSON.stringify(loadedEntry?.pin ?? null) !== JSON.stringify(entry?.pin ?? null);
|
|
211
|
-
const preferStaged = JSON.stringify(loadedEntry?.prefer ?? null) !== JSON.stringify(entry?.prefer ?? null);
|
|
212
|
-
const sel = i === cursor ? "❯" : " ";
|
|
213
|
-
const floorCell = `${floor}${marker}${floorStaged ? STAGED_MARK : ""}`.padEnd(10);
|
|
214
|
-
const pinCell = `${pin}${pinStaged ? STAGED_MARK : ""}`.padEnd(20);
|
|
215
|
-
const preferCell = `${prefer}${preferStaged ? STAGED_MARK : ""}`;
|
|
216
|
-
lines.push(`${sel} ${shape.padEnd(10)}${floorCell}${pinCell}${preferCell}`);
|
|
217
|
-
}
|
|
218
|
-
lines.push("* integrity set: never below frontier");
|
|
219
|
-
if (staging.isDirty)
|
|
220
|
-
lines.push(`${STAGED_MARK} staged — not saved`);
|
|
221
|
-
lines.push(`pick: ${SHAPES[cursor]} — enter opens the candidate inspector`);
|
|
222
|
-
if (inspector) {
|
|
223
|
-
lines.push(`─ candidates: ${inspector.shape} — the fleet picker ranking for the same inputs ──`);
|
|
224
|
-
if (inspector.ranked.length === 0)
|
|
225
|
-
lines.push(" (no routable candidates)");
|
|
226
|
-
for (const [j, c] of inspector.ranked.entries()) {
|
|
227
|
-
lines.push(`${j === inspector.cursor ? "❯ " : " "}${rowOf(c)}`);
|
|
228
|
-
}
|
|
229
|
-
lines.push("select stages the highlighted candidate as this shape's pin");
|
|
230
|
-
}
|
|
231
|
-
return lines;
|
|
232
|
-
},
|
|
233
|
-
};
|
|
234
|
-
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { type RunGraph, type Task, type TaskStatus } from "../../graph/schema.js";
|
|
2
|
-
import { type JournalEvent, type TelemetryRow, type WorkerResultCause } from "../../run/journal.js";
|
|
3
|
-
import { type GateState } from "../../cli/commands/status.js";
|
|
4
|
-
import type { View } from "../app.js";
|
|
5
|
-
export type AttemptRecord = {
|
|
6
|
-
attempt: number;
|
|
7
|
-
channel: string;
|
|
8
|
-
outcome: "clean" | "failed" | "in-flight" | "aborted";
|
|
9
|
-
/** typed worker-result failure reason when the attempt did not finish cleanly */
|
|
10
|
-
cause?: WorkerResultCause;
|
|
11
|
-
/** worker-result summary or consult-verdict reason, when present */
|
|
12
|
-
note?: string;
|
|
13
|
-
};
|
|
14
|
-
export type RunsTask = {
|
|
15
|
-
task: Task;
|
|
16
|
-
status: TaskStatus;
|
|
17
|
-
states: GateState[];
|
|
18
|
-
channel: string;
|
|
19
|
-
ctx?: number;
|
|
20
|
-
activity?: string;
|
|
21
|
-
attempts: AttemptRecord[];
|
|
22
|
-
};
|
|
23
|
-
/** Everything the Runs cockpit renders — loaded by the caller, never by the render path. */
|
|
24
|
-
export type RunsViewData = {
|
|
25
|
-
runId?: string;
|
|
26
|
-
events: JournalEvent[];
|
|
27
|
-
graph: RunGraph;
|
|
28
|
-
/** Whether the journal is comparable with the loaded graph. Defaults to true. */
|
|
29
|
-
comparable?: boolean;
|
|
30
|
-
/** Per-tier per-task pricing from config, rendered ONLY through the shared costSignal formatter. */
|
|
31
|
-
pricing?: Record<string, number>;
|
|
32
|
-
/** This run's observed usage rows (telemetry.jsonl), folded per channel by the cost ticker. */
|
|
33
|
-
telemetry?: TelemetryRow[];
|
|
34
|
-
};
|
|
35
|
-
/** Options for creating a Runs view. Backward-compatible: plain `createRunsView(data)` still works. */
|
|
36
|
-
export type RunsViewOptions = {
|
|
37
|
-
data?: RunsViewData;
|
|
38
|
-
repoRoot?: string;
|
|
39
|
-
/** Notify the caller of a single-line notice change (confirmation, refusal, progress, result). */
|
|
40
|
-
onNotice?: (message: string | null) => void;
|
|
41
|
-
/** Refresh injected data after a mutation. Called after a successful approval. */
|
|
42
|
-
reload?: () => RunsViewData | Promise<RunsViewData>;
|
|
43
|
-
};
|
|
44
|
-
export type RunsView = View & {
|
|
45
|
-
/** Handle a decoded key name ("up" | "down" | "a" | "y"). */
|
|
46
|
-
key(name: string): void;
|
|
47
|
-
/** Index of the cursor in the task list. */
|
|
48
|
-
cursor: number;
|
|
49
|
-
/** The currently selected task, if any. */
|
|
50
|
-
selectedTask(): RunsTask | undefined;
|
|
51
|
-
/** Promise of an in-flight approval, exposed for tests. */
|
|
52
|
-
approval?: Promise<void>;
|
|
53
|
-
};
|
|
54
|
-
/** Build the injected raw data into task rows. Pure function, no filesystem access. */
|
|
55
|
-
export declare function foldRunsTasks(data: RunsViewData): RunsTask[];
|
|
56
|
-
export type CostTickerRow = {
|
|
57
|
-
/** adapter:model */
|
|
58
|
-
key: string;
|
|
59
|
-
/** verbatim output of the shared cost-signal formatter */
|
|
60
|
-
signal: string;
|
|
61
|
-
/** distinct tasks dispatched on this channel */
|
|
62
|
-
tasks: number;
|
|
63
|
-
/** summed observed token usage for this channel; undefined when nothing was metered */
|
|
64
|
-
tokens?: number;
|
|
65
|
-
};
|
|
66
|
-
/** Sum observed token usage per channel for the loaded run. Dispatch events name the channel's
|
|
67
|
-
* full assignment (billing channel + tier); telemetry rows contribute the metered tokens. A
|
|
68
|
-
* telemetry-only channel degrades costSignal to "api metered" — never an invented price. */
|
|
69
|
-
export declare function foldCostTicker(data: RunsViewData): CostTickerRow[];
|
|
70
|
-
export declare function createRunsView(data?: RunsViewData, opts?: Omit<RunsViewOptions, "data">): RunsView;
|