tickmarkr 1.71.0 → 1.73.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/adapters/codex.js +13 -3
- package/dist/cli/commands/approve.js +21 -3
- package/dist/cli/commands/fleet.d.ts +2 -0
- package/dist/cli/commands/fleet.js +212 -449
- package/dist/cli/commands/resume.js +10 -2
- package/dist/cli/commands/status.d.ts +2 -0
- package/dist/cli/commands/status.js +219 -30
- 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 +6 -0
- package/dist/drivers/herdr.js +169 -71
- package/dist/drivers/subprocess.d.ts +3 -3
- package/dist/drivers/subprocess.js +10 -5
- package/dist/gates/acceptance.js +41 -17
- 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 +6 -1
- package/dist/run/journal.js +28 -1
- package/dist/tui/ink/components.d.ts +38 -0
- package/dist/tui/ink/components.js +42 -0
- package/dist/tui/ink/fleet-app.d.ts +113 -0
- package/dist/tui/ink/fleet-app.js +678 -0
- package/dist/tui/ink/studio-app.d.ts +59 -0
- package/dist/tui/ink/studio-app.js +320 -0
- package/package.json +4 -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
|
@@ -0,0 +1,678 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { emitKeypressEvents } from "node:readline";
|
|
3
|
+
import { PassThrough } from "node:stream";
|
|
4
|
+
import { render, Text, useApp, useInput } from "ink";
|
|
5
|
+
import { useRef, useState } from "react";
|
|
6
|
+
import { TIERS } from "../../graph/schema.js";
|
|
7
|
+
import { FleetListScreen, FleetReviewScreen, ToggleMark } from "./components.js";
|
|
8
|
+
const STEERING_KEYS = ["review", "consult"];
|
|
9
|
+
const escape = String.fromCharCode(27);
|
|
10
|
+
const inkBookkeepingWrites = new Set(["", `${escape}[?25l`, `${escape}[?25h`, `${escape}[?2026h`, `${escape}[?2026l`]);
|
|
11
|
+
function inkOutput(output) {
|
|
12
|
+
if (typeof output.on === "function" && typeof output.off === "function")
|
|
13
|
+
return output;
|
|
14
|
+
const facade = Object.create(output);
|
|
15
|
+
const write = output.write.bind(output);
|
|
16
|
+
facade.write = ((chunk, ...args) => {
|
|
17
|
+
const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
|
|
18
|
+
if (inkBookkeepingWrites.has(text))
|
|
19
|
+
return true;
|
|
20
|
+
return Reflect.apply(write, output, [chunk, ...args]);
|
|
21
|
+
});
|
|
22
|
+
facade.on = () => facade;
|
|
23
|
+
facade.off = () => facade;
|
|
24
|
+
return facade;
|
|
25
|
+
}
|
|
26
|
+
function inkInput(input, initialInput) {
|
|
27
|
+
const productionInput = typeof input.ref === "function" && typeof input.unref === "function";
|
|
28
|
+
// Isolate Ink's listeners on a bridge so every editor exit can detach the
|
|
29
|
+
// one listener it owns from the operator's terminal. Older injected FleetIO
|
|
30
|
+
// streams can arrive as decoded keypress events, while real TTYs forward raw
|
|
31
|
+
// data and leave decoding entirely to Ink.
|
|
32
|
+
const stream = new PassThrough();
|
|
33
|
+
stream.isTTY = input.isTTY;
|
|
34
|
+
stream.setRawMode = input.setRawMode?.bind(input);
|
|
35
|
+
stream.ref = () => {
|
|
36
|
+
if (productionInput)
|
|
37
|
+
input.ref();
|
|
38
|
+
return stream;
|
|
39
|
+
};
|
|
40
|
+
stream.unref = () => {
|
|
41
|
+
if (productionInput)
|
|
42
|
+
input.unref();
|
|
43
|
+
return stream;
|
|
44
|
+
};
|
|
45
|
+
const queued = [...initialInput];
|
|
46
|
+
let active = true;
|
|
47
|
+
let scheduled;
|
|
48
|
+
const pump = () => {
|
|
49
|
+
scheduled = undefined;
|
|
50
|
+
if (!active)
|
|
51
|
+
return;
|
|
52
|
+
const next = queued.shift();
|
|
53
|
+
if (next === undefined)
|
|
54
|
+
return;
|
|
55
|
+
stream.write(next);
|
|
56
|
+
scheduled = setTimeout(pump, 0);
|
|
57
|
+
};
|
|
58
|
+
const onData = (chunk) => {
|
|
59
|
+
queued.push(typeof chunk === "string" ? chunk : chunk.toString("utf8"));
|
|
60
|
+
scheduled ??= setTimeout(pump, 0);
|
|
61
|
+
};
|
|
62
|
+
const onKeypress = (sequence, key) => {
|
|
63
|
+
const token = key?.sequence ?? sequence;
|
|
64
|
+
if (token === undefined)
|
|
65
|
+
return;
|
|
66
|
+
queued.push(token);
|
|
67
|
+
scheduled ??= setTimeout(pump, 0);
|
|
68
|
+
};
|
|
69
|
+
if (productionInput) {
|
|
70
|
+
input.on("data", onData);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
emitKeypressEvents(input);
|
|
74
|
+
input.on("keypress", onKeypress);
|
|
75
|
+
}
|
|
76
|
+
input.resume();
|
|
77
|
+
if (queued.length > 0)
|
|
78
|
+
scheduled = setTimeout(pump, 0);
|
|
79
|
+
return {
|
|
80
|
+
stream: stream,
|
|
81
|
+
stop() {
|
|
82
|
+
active = false;
|
|
83
|
+
if (scheduled)
|
|
84
|
+
clearTimeout(scheduled);
|
|
85
|
+
if (productionInput)
|
|
86
|
+
input.off("data", onData);
|
|
87
|
+
else
|
|
88
|
+
input.off("keypress", onKeypress);
|
|
89
|
+
input.pause();
|
|
90
|
+
stream.end();
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
export function formatDoctorAge(ageMs) {
|
|
95
|
+
if (ageMs === null)
|
|
96
|
+
return "no probe data";
|
|
97
|
+
const mins = Math.floor(ageMs / 60_000);
|
|
98
|
+
if (mins < 60)
|
|
99
|
+
return `${mins}m old`;
|
|
100
|
+
return `${Math.floor(mins / 60)}h old`;
|
|
101
|
+
}
|
|
102
|
+
export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels, modelGroups, initialMode, modeOptions, initialMap, modePreview, shapeRows, candidatesForShape, preferOptionsForShape, initialSteering, steeringOptionsFor, reviewOverlay, reloadGuard, }) {
|
|
103
|
+
const { exit } = useApp();
|
|
104
|
+
const [screen, setScreen] = useState("probe");
|
|
105
|
+
const screenRef = useRef(screen);
|
|
106
|
+
const [cursor, setCursor] = useState(0);
|
|
107
|
+
const cursorRef = useRef(cursor);
|
|
108
|
+
const denyRef = useRef(undefined);
|
|
109
|
+
denyRef.current ??= new Set(initialDenyAdapters);
|
|
110
|
+
const [denyAdapters, setDenyAdapters] = useState(() => new Set(initialDenyAdapters));
|
|
111
|
+
const denyModelsRef = useRef(undefined);
|
|
112
|
+
denyModelsRef.current ??= new Set(initialDenyModels);
|
|
113
|
+
const [denyModels, setDenyModels] = useState(() => new Set(initialDenyModels));
|
|
114
|
+
const classificationsRef = useRef([]);
|
|
115
|
+
const [, setClassificationRevision] = useState(0);
|
|
116
|
+
const modelGroupRef = useRef(0);
|
|
117
|
+
const [modelGroup, setModelGroup] = useState(0);
|
|
118
|
+
const modelCursorRef = useRef(0);
|
|
119
|
+
const [modelCursor, setModelCursor] = useState(0);
|
|
120
|
+
const tierCursorRef = useRef(0);
|
|
121
|
+
const [tierCursor, setTierCursor] = useState(0);
|
|
122
|
+
const pendingClassificationRef = useRef(null);
|
|
123
|
+
const noteRef = useRef("");
|
|
124
|
+
const [note, setNote] = useState("");
|
|
125
|
+
const [notice, setNotice] = useState("");
|
|
126
|
+
const modeCursorRef = useRef(undefined);
|
|
127
|
+
modeCursorRef.current ??= Math.max(modeOptions.findIndex((option) => option.id === initialMode), 0);
|
|
128
|
+
const [modeCursor, setModeCursor] = useState(modeCursorRef.current);
|
|
129
|
+
const selectedModeRef = useRef(initialMode);
|
|
130
|
+
const mapRef = useRef(undefined);
|
|
131
|
+
mapRef.current ??= structuredClone(initialMap);
|
|
132
|
+
const [map, setMap] = useState(() => structuredClone(initialMap));
|
|
133
|
+
const shapeCursorRef = useRef(0);
|
|
134
|
+
const [shapeCursor, setShapeCursor] = useState(0);
|
|
135
|
+
const candidateCursorRef = useRef(0);
|
|
136
|
+
const [candidateCursor, setCandidateCursor] = useState(0);
|
|
137
|
+
const candidatesRef = useRef([]);
|
|
138
|
+
const preferCursorRef = useRef(0);
|
|
139
|
+
const [preferCursor, setPreferCursor] = useState(0);
|
|
140
|
+
const preferRowsRef = useRef([]);
|
|
141
|
+
const preferChainRef = useRef([]);
|
|
142
|
+
const [, setPreferRevision] = useState(0);
|
|
143
|
+
const steeringRef = useRef(undefined);
|
|
144
|
+
steeringRef.current ??= structuredClone(initialSteering);
|
|
145
|
+
const [steering, setSteering] = useState(() => structuredClone(initialSteering));
|
|
146
|
+
const steeringCursorRef = useRef(0);
|
|
147
|
+
const [steeringCursor, setSteeringCursor] = useState(0);
|
|
148
|
+
const steeringPickerRef = useRef("review");
|
|
149
|
+
const steeringRowsRef = useRef([]);
|
|
150
|
+
const steeringChainRef = useRef([]);
|
|
151
|
+
const steeringPickerCursorRef = useRef(0);
|
|
152
|
+
const [steeringPickerCursor, setSteeringPickerCursor] = useState(0);
|
|
153
|
+
const [, setSteeringPickerRevision] = useState(0);
|
|
154
|
+
const reviewRef = useRef(null);
|
|
155
|
+
const [review, setReview] = useState(null);
|
|
156
|
+
const doneRef = useRef(false);
|
|
157
|
+
const enabledModelGroups = () => modelGroups.filter((group) => !denyRef.current?.has(group.adapter));
|
|
158
|
+
const stagedMap = () => mapRef.current;
|
|
159
|
+
const currentModelRows = () => {
|
|
160
|
+
const group = enabledModelGroups()[modelGroupRef.current];
|
|
161
|
+
if (!group)
|
|
162
|
+
return [];
|
|
163
|
+
return group.rows.map((row) => {
|
|
164
|
+
const staged = classificationsRef.current.find((classification) => classification.adapter === group.adapter && classification.model === row.model);
|
|
165
|
+
return staged ? { ...row, tier: staged.tier } : row;
|
|
166
|
+
});
|
|
167
|
+
};
|
|
168
|
+
const editorState = () => ({
|
|
169
|
+
denyAdapters: [...(denyRef.current ?? [])].sort(),
|
|
170
|
+
denyModels: [...(denyModelsRef.current ?? [])].sort(),
|
|
171
|
+
classifications: classificationsRef.current,
|
|
172
|
+
selectedMode: selectedModeRef.current,
|
|
173
|
+
map: stagedMap(),
|
|
174
|
+
steering: structuredClone(steeringRef.current),
|
|
175
|
+
});
|
|
176
|
+
const finish = (result) => {
|
|
177
|
+
if (doneRef.current)
|
|
178
|
+
return;
|
|
179
|
+
doneRef.current = true;
|
|
180
|
+
exit(result);
|
|
181
|
+
};
|
|
182
|
+
const finishEditor = () => {
|
|
183
|
+
showScreen("steering");
|
|
184
|
+
};
|
|
185
|
+
const showScreen = (next) => {
|
|
186
|
+
screenRef.current = next;
|
|
187
|
+
setScreen(next);
|
|
188
|
+
};
|
|
189
|
+
useInput((input, key) => {
|
|
190
|
+
if (doneRef.current)
|
|
191
|
+
return;
|
|
192
|
+
if (key.escape && (screenRef.current === "candidates" || screenRef.current === "shape-prefer")) {
|
|
193
|
+
showScreen("shapes");
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (key.escape && screenRef.current === "steering-prefer") {
|
|
197
|
+
showScreen("steering");
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (key.escape || (screenRef.current !== "provenance" && input === "q") || (key.ctrl && input === "c")) {
|
|
201
|
+
finish({ kind: "quit" });
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (screenRef.current === "probe") {
|
|
205
|
+
if (input === "r") {
|
|
206
|
+
finish({ kind: "refresh" });
|
|
207
|
+
}
|
|
208
|
+
else if (key.return) {
|
|
209
|
+
showScreen("agents");
|
|
210
|
+
}
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (screenRef.current === "agents") {
|
|
214
|
+
if (key.downArrow || input === "j") {
|
|
215
|
+
const next = Math.min(cursorRef.current + 1, Math.max(agents.length - 1, 0));
|
|
216
|
+
cursorRef.current = next;
|
|
217
|
+
setCursor(next);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (key.upArrow || input === "k") {
|
|
221
|
+
const next = Math.max(cursorRef.current - 1, 0);
|
|
222
|
+
cursorRef.current = next;
|
|
223
|
+
setCursor(next);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
if (input === " " && agents.length > 0) {
|
|
227
|
+
const next = new Set(denyRef.current);
|
|
228
|
+
const id = agents[cursorRef.current].id;
|
|
229
|
+
if (next.has(id))
|
|
230
|
+
next.delete(id);
|
|
231
|
+
else
|
|
232
|
+
next.add(id);
|
|
233
|
+
denyRef.current = next;
|
|
234
|
+
setDenyAdapters(next);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (key.return) {
|
|
238
|
+
if (enabledModelGroups().length === 0)
|
|
239
|
+
showScreen("modes");
|
|
240
|
+
else
|
|
241
|
+
showScreen("models");
|
|
242
|
+
}
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
if (screenRef.current === "models") {
|
|
246
|
+
const rows = currentModelRows();
|
|
247
|
+
if (key.downArrow || input === "j") {
|
|
248
|
+
const next = Math.min(modelCursorRef.current + 1, Math.max(rows.length - 1, 0));
|
|
249
|
+
modelCursorRef.current = next;
|
|
250
|
+
setModelCursor(next);
|
|
251
|
+
setNotice("");
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (key.upArrow || input === "k") {
|
|
255
|
+
const next = Math.max(modelCursorRef.current - 1, 0);
|
|
256
|
+
modelCursorRef.current = next;
|
|
257
|
+
setModelCursor(next);
|
|
258
|
+
setNotice("");
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const group = enabledModelGroups()[modelGroupRef.current];
|
|
262
|
+
const row = rows[modelCursorRef.current];
|
|
263
|
+
if (input === " " && group && row?.tier) {
|
|
264
|
+
const id = `${group.adapter}:${row.model}`;
|
|
265
|
+
const next = new Set(denyModelsRef.current);
|
|
266
|
+
if (next.has(id))
|
|
267
|
+
next.delete(id);
|
|
268
|
+
else
|
|
269
|
+
next.add(id);
|
|
270
|
+
denyModelsRef.current = next;
|
|
271
|
+
setDenyModels(next);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
if (input === "t" && group && row) {
|
|
275
|
+
if (row.tier) {
|
|
276
|
+
setNotice("fleet: tier reassignment on classified models is not supported in v1 — edit config directly");
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
tierCursorRef.current = 0;
|
|
280
|
+
setTierCursor(0);
|
|
281
|
+
pendingClassificationRef.current = {
|
|
282
|
+
adapter: group.adapter,
|
|
283
|
+
model: row.model,
|
|
284
|
+
tier: TIERS[0],
|
|
285
|
+
};
|
|
286
|
+
showScreen("tiers");
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (key.return) {
|
|
290
|
+
if (modelGroupRef.current + 1 < enabledModelGroups().length) {
|
|
291
|
+
const next = modelGroupRef.current + 1;
|
|
292
|
+
modelGroupRef.current = next;
|
|
293
|
+
setModelGroup(next);
|
|
294
|
+
modelCursorRef.current = 0;
|
|
295
|
+
setModelCursor(0);
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
showScreen("modes");
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (screenRef.current === "tiers") {
|
|
304
|
+
if (key.downArrow || input === "j") {
|
|
305
|
+
const next = Math.min(tierCursorRef.current + 1, TIERS.length - 1);
|
|
306
|
+
tierCursorRef.current = next;
|
|
307
|
+
setTierCursor(next);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (key.upArrow || input === "k") {
|
|
311
|
+
const next = Math.max(tierCursorRef.current - 1, 0);
|
|
312
|
+
tierCursorRef.current = next;
|
|
313
|
+
setTierCursor(next);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (key.return && pendingClassificationRef.current) {
|
|
317
|
+
pendingClassificationRef.current.tier = TIERS[tierCursorRef.current];
|
|
318
|
+
noteRef.current = "";
|
|
319
|
+
setNote("");
|
|
320
|
+
setNotice("");
|
|
321
|
+
showScreen("provenance");
|
|
322
|
+
}
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
if (screenRef.current === "provenance") {
|
|
326
|
+
if (key.return) {
|
|
327
|
+
if (!noteRef.current.trim()) {
|
|
328
|
+
setNotice("fleet: a typed benchmark-provenance note is required");
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
const pending = pendingClassificationRef.current;
|
|
332
|
+
if (pending) {
|
|
333
|
+
const next = [...classificationsRef.current, { ...pending, note: noteRef.current.trim() }];
|
|
334
|
+
classificationsRef.current = next;
|
|
335
|
+
setClassificationRevision((revision) => revision + 1);
|
|
336
|
+
}
|
|
337
|
+
pendingClassificationRef.current = null;
|
|
338
|
+
setNotice("");
|
|
339
|
+
showScreen("models");
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
if (key.backspace || key.delete) {
|
|
343
|
+
const next = noteRef.current.slice(0, -1);
|
|
344
|
+
noteRef.current = next;
|
|
345
|
+
setNote(next);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (input && !key.ctrl && !key.meta) {
|
|
349
|
+
const next = noteRef.current + input;
|
|
350
|
+
noteRef.current = next;
|
|
351
|
+
setNote(next);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (screenRef.current === "modes") {
|
|
355
|
+
const currentModeCursor = modeCursorRef.current ?? 0;
|
|
356
|
+
if (key.downArrow || input === "j") {
|
|
357
|
+
const next = Math.min(currentModeCursor + 1, Math.max(modeOptions.length - 1, 0));
|
|
358
|
+
modeCursorRef.current = next;
|
|
359
|
+
setModeCursor(next);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
if (key.upArrow || input === "k") {
|
|
363
|
+
const next = Math.max(currentModeCursor - 1, 0);
|
|
364
|
+
modeCursorRef.current = next;
|
|
365
|
+
setModeCursor(next);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
if (key.return && modeOptions[currentModeCursor]) {
|
|
369
|
+
selectedModeRef.current = modeOptions[currentModeCursor].id;
|
|
370
|
+
showScreen("shapes");
|
|
371
|
+
}
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
if (screenRef.current === "shapes") {
|
|
375
|
+
const rows = shapeRows(selectedModeRef.current, stagedMap());
|
|
376
|
+
if (key.downArrow || input === "j") {
|
|
377
|
+
const next = Math.min(shapeCursorRef.current + 1, Math.max(rows.length - 1, 0));
|
|
378
|
+
shapeCursorRef.current = next;
|
|
379
|
+
setShapeCursor(next);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
if (key.upArrow || input === "k") {
|
|
383
|
+
const next = Math.max(shapeCursorRef.current - 1, 0);
|
|
384
|
+
shapeCursorRef.current = next;
|
|
385
|
+
setShapeCursor(next);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
const shape = rows[shapeCursorRef.current]?.id;
|
|
389
|
+
if (input === "a" && shape) {
|
|
390
|
+
const nextEntry = { ...stagedMap()[shape] };
|
|
391
|
+
delete nextEntry.pin;
|
|
392
|
+
const nextMap = { ...stagedMap(), [shape]: nextEntry };
|
|
393
|
+
mapRef.current = nextMap;
|
|
394
|
+
setMap(nextMap);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
if (input === "p" && shape) {
|
|
398
|
+
candidatesRef.current = candidatesForShape(shape, selectedModeRef.current, stagedMap());
|
|
399
|
+
candidateCursorRef.current = 0;
|
|
400
|
+
setCandidateCursor(0);
|
|
401
|
+
showScreen("candidates");
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
if (input === "f" && shape) {
|
|
405
|
+
const current = stagedMap()[shape]?.prefer ?? [];
|
|
406
|
+
preferRowsRef.current = preferOptionsForShape(shape, current);
|
|
407
|
+
preferChainRef.current = current.slice();
|
|
408
|
+
preferCursorRef.current = 0;
|
|
409
|
+
setPreferCursor(0);
|
|
410
|
+
showScreen("shape-prefer");
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
if (key.return)
|
|
414
|
+
finishEditor();
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
if (screenRef.current === "candidates") {
|
|
418
|
+
if (key.downArrow || input === "j") {
|
|
419
|
+
const next = Math.min(candidateCursorRef.current + 1, Math.max(candidatesRef.current.length - 1, 0));
|
|
420
|
+
candidateCursorRef.current = next;
|
|
421
|
+
setCandidateCursor(next);
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
if (key.upArrow || input === "k") {
|
|
425
|
+
const next = Math.max(candidateCursorRef.current - 1, 0);
|
|
426
|
+
candidateCursorRef.current = next;
|
|
427
|
+
setCandidateCursor(next);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
if (key.return && candidatesRef.current[candidateCursorRef.current]) {
|
|
431
|
+
const shape = shapeRows(selectedModeRef.current, stagedMap())[shapeCursorRef.current]?.id;
|
|
432
|
+
if (shape) {
|
|
433
|
+
const picked = candidatesRef.current[candidateCursorRef.current];
|
|
434
|
+
const nextMap = { ...stagedMap(), [shape]: { pin: picked.pin } };
|
|
435
|
+
mapRef.current = nextMap;
|
|
436
|
+
setMap(nextMap);
|
|
437
|
+
}
|
|
438
|
+
showScreen("shapes");
|
|
439
|
+
}
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
if (screenRef.current === "shape-prefer") {
|
|
443
|
+
if (key.downArrow || input === "j") {
|
|
444
|
+
const next = Math.min(preferCursorRef.current + 1, Math.max(preferRowsRef.current.length - 1, 0));
|
|
445
|
+
preferCursorRef.current = next;
|
|
446
|
+
setPreferCursor(next);
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
if (key.upArrow || input === "k") {
|
|
450
|
+
const next = Math.max(preferCursorRef.current - 1, 0);
|
|
451
|
+
preferCursorRef.current = next;
|
|
452
|
+
setPreferCursor(next);
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
if (input === " " && preferRowsRef.current[preferCursorRef.current]) {
|
|
456
|
+
const option = preferRowsRef.current[preferCursorRef.current];
|
|
457
|
+
const at = preferChainRef.current.indexOf(option);
|
|
458
|
+
if (at === -1)
|
|
459
|
+
preferChainRef.current.push(option);
|
|
460
|
+
else
|
|
461
|
+
preferChainRef.current.splice(at, 1);
|
|
462
|
+
setPreferRevision((revision) => revision + 1);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
if (key.return) {
|
|
466
|
+
const shape = shapeRows(selectedModeRef.current, stagedMap())[shapeCursorRef.current]?.id;
|
|
467
|
+
if (shape) {
|
|
468
|
+
const nextEntry = { ...stagedMap()[shape] };
|
|
469
|
+
if (preferChainRef.current.length)
|
|
470
|
+
nextEntry.prefer = preferChainRef.current.slice();
|
|
471
|
+
else
|
|
472
|
+
delete nextEntry.prefer;
|
|
473
|
+
const nextMap = { ...stagedMap(), [shape]: nextEntry };
|
|
474
|
+
mapRef.current = nextMap;
|
|
475
|
+
setMap(nextMap);
|
|
476
|
+
}
|
|
477
|
+
showScreen("shapes");
|
|
478
|
+
}
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
if (screenRef.current === "steering") {
|
|
482
|
+
if (key.downArrow || input === "j") {
|
|
483
|
+
const next = Math.min(steeringCursorRef.current + 1, STEERING_KEYS.length - 1);
|
|
484
|
+
steeringCursorRef.current = next;
|
|
485
|
+
setSteeringCursor(next);
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
if (key.upArrow || input === "k") {
|
|
489
|
+
const next = Math.max(steeringCursorRef.current - 1, 0);
|
|
490
|
+
steeringCursorRef.current = next;
|
|
491
|
+
setSteeringCursor(next);
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
if (input === "f") {
|
|
495
|
+
const which = STEERING_KEYS[steeringCursorRef.current];
|
|
496
|
+
const current = steeringRef.current?.[which] ?? [];
|
|
497
|
+
steeringPickerRef.current = which;
|
|
498
|
+
steeringRowsRef.current = steeringOptionsFor(which, current);
|
|
499
|
+
steeringChainRef.current = current.slice();
|
|
500
|
+
steeringPickerCursorRef.current = 0;
|
|
501
|
+
setSteeringPickerCursor(0);
|
|
502
|
+
setNotice("");
|
|
503
|
+
showScreen("steering-prefer");
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
if (key.return) {
|
|
507
|
+
const nextReview = reviewOverlay(editorState());
|
|
508
|
+
if (nextReview.kind === "empty") {
|
|
509
|
+
finish({ kind: "no-changes" });
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
reviewRef.current = nextReview;
|
|
513
|
+
setReview(nextReview);
|
|
514
|
+
setNotice("");
|
|
515
|
+
showScreen("review");
|
|
516
|
+
}
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
if (screenRef.current === "steering-prefer") {
|
|
520
|
+
if (key.downArrow || input === "j") {
|
|
521
|
+
const next = Math.min(steeringPickerCursorRef.current + 1, Math.max(steeringRowsRef.current.length - 1, 0));
|
|
522
|
+
steeringPickerCursorRef.current = next;
|
|
523
|
+
setSteeringPickerCursor(next);
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
if (key.upArrow || input === "k") {
|
|
527
|
+
const next = Math.max(steeringPickerCursorRef.current - 1, 0);
|
|
528
|
+
steeringPickerCursorRef.current = next;
|
|
529
|
+
setSteeringPickerCursor(next);
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
if (input === " " && steeringRowsRef.current[steeringPickerCursorRef.current]) {
|
|
533
|
+
const option = steeringRowsRef.current[steeringPickerCursorRef.current];
|
|
534
|
+
const at = steeringChainRef.current.indexOf(option);
|
|
535
|
+
if (at === -1)
|
|
536
|
+
steeringChainRef.current.push(option);
|
|
537
|
+
else
|
|
538
|
+
steeringChainRef.current.splice(at, 1);
|
|
539
|
+
setSteeringPickerRevision((revision) => revision + 1);
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
if (key.return) {
|
|
543
|
+
const which = steeringPickerRef.current;
|
|
544
|
+
const next = {
|
|
545
|
+
...steeringRef.current,
|
|
546
|
+
[which]: steeringChainRef.current.length ? steeringChainRef.current.slice() : undefined,
|
|
547
|
+
};
|
|
548
|
+
steeringRef.current = next;
|
|
549
|
+
setSteering(next);
|
|
550
|
+
setNotice("");
|
|
551
|
+
showScreen("steering");
|
|
552
|
+
}
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
if (screenRef.current === "review") {
|
|
556
|
+
if (input === "n") {
|
|
557
|
+
finish({ kind: "discard" });
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
if (input === "y" && reviewRef.current) {
|
|
561
|
+
const loadError = reloadGuard(reviewRef.current.after);
|
|
562
|
+
if (loadError) {
|
|
563
|
+
setNotice(`fleet: config loader rejects the proposed overlay — ${loadError}`);
|
|
564
|
+
reviewRef.current = null;
|
|
565
|
+
setReview(null);
|
|
566
|
+
showScreen("steering");
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
finish({ kind: "write", review: reviewRef.current });
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
if (screen === "probe") {
|
|
574
|
+
return (_jsx(FleetListScreen, { title: "step 1/6 \u00B7 probe data", legend: "enter continue \u00B7 r refresh via doctor \u00B7 esc/q quit", rows: [{
|
|
575
|
+
id: "doctor",
|
|
576
|
+
content: _jsx(Text, { children: `probe data: ${formatDoctorAge(ageMs)} (.tickmarkr/doctor.json)` }),
|
|
577
|
+
}], cursor: 0 }));
|
|
578
|
+
}
|
|
579
|
+
if (screen === "agents") {
|
|
580
|
+
const rows = agents.map((agent) => {
|
|
581
|
+
const active = !denyAdapters.has(agent.id);
|
|
582
|
+
return {
|
|
583
|
+
id: agent.id,
|
|
584
|
+
content: (_jsxs(_Fragment, { children: [_jsx(ToggleMark, { active: active }), _jsx(Text, { children: ` ${agent.id} ${agent.version} ${agent.authed ? "authed" : "unauthed"}` })] })),
|
|
585
|
+
};
|
|
586
|
+
});
|
|
587
|
+
return (_jsx(FleetListScreen, { title: "step 2/6 \u00B7 agent CLIs", legend: "\u2191\u2193/jk move \u00B7 space toggle \u00B7 enter next \u00B7 esc/q quit", rows: rows, cursor: cursor }));
|
|
588
|
+
}
|
|
589
|
+
const group = enabledModelGroups()[modelGroup];
|
|
590
|
+
const modelRows = currentModelRows();
|
|
591
|
+
if (screen === "models") {
|
|
592
|
+
const rows = modelRows.map((row) => {
|
|
593
|
+
const denied = group ? denyModels.has(`${group.adapter}:${row.model}`) : false;
|
|
594
|
+
return {
|
|
595
|
+
id: row.model,
|
|
596
|
+
content: row.tier ? (_jsxs(_Fragment, { children: [_jsx(ToggleMark, { active: !denied }), _jsx(Text, { children: ` ${row.model} ${row.tier} ${denied ? "denied" : "allowed"}` })] })) : _jsx(Text, { children: `( ) ${row.model} ??? unclassified${row.detectedAt ? ` (${row.detectedAt})` : ""}` }),
|
|
597
|
+
};
|
|
598
|
+
});
|
|
599
|
+
return (_jsx(FleetListScreen, { title: `step 3/6 · models · ${group?.adapter ?? ""}`, legend: "\u2191\u2193/jk move \u00B7 space toggle \u00B7 t tier \u00B7 enter next \u00B7 esc/q quit", rows: rows, cursor: modelCursor, details: notice ? [notice] : [] }));
|
|
600
|
+
}
|
|
601
|
+
if (screen === "tiers") {
|
|
602
|
+
return (_jsx(FleetListScreen, { title: `pick · tier · ${pendingClassificationRef.current?.adapter}:${pendingClassificationRef.current?.model}`, legend: "\u2191\u2193/jk move \u00B7 enter select \u00B7 esc/q quit", rows: TIERS.map((tier) => ({ id: tier, content: _jsx(Text, { children: tier }) })), cursor: tierCursor }));
|
|
603
|
+
}
|
|
604
|
+
if (screen === "provenance")
|
|
605
|
+
return (_jsx(FleetListScreen, { title: `benchmark provenance · ${pendingClassificationRef.current?.adapter}:${pendingClassificationRef.current?.model}`, legend: "type note \u00B7 enter apply \u00B7 esc cancel", rows: [{ id: "note", content: _jsx(Text, { children: note || "benchmark provenance note (required):" }) }], cursor: 0, details: notice ? [notice] : [] }));
|
|
606
|
+
if (screen === "modes") {
|
|
607
|
+
return (_jsx(FleetListScreen, { title: "step 4/6 \u00B7 routing mode", legend: "\u2191\u2193/jk move \u00B7 enter select \u00B7 esc/q quit", rows: modeOptions.map((option) => ({
|
|
608
|
+
id: option.id,
|
|
609
|
+
content: (_jsxs(_Fragment, { children: [option.id === initialMode ? _jsx(ToggleMark, { active: true }) : _jsx(Text, { children: " " }), _jsx(Text, { children: ` ${option.id.padEnd(11)} ${option.gloss}` })] })),
|
|
610
|
+
})), cursor: modeCursor, details: modePreview(modeOptions[modeCursor].id, map) }));
|
|
611
|
+
}
|
|
612
|
+
const renderedShapes = shapeRows(selectedModeRef.current, map);
|
|
613
|
+
if (screen === "shapes") {
|
|
614
|
+
return (_jsx(FleetListScreen, { title: "step 5/6 \u00B7 shape routing", legend: "\u2191\u2193/jk move \u00B7 a auto \u00B7 p pin \u00B7 f prefer \u00B7 enter next \u00B7 esc/q quit", rows: renderedShapes.map((row) => ({ id: row.id, content: _jsx(Text, { children: row.label }) })), cursor: shapeCursor }));
|
|
615
|
+
}
|
|
616
|
+
const shape = renderedShapes[shapeCursor]?.id;
|
|
617
|
+
if (screen === "candidates") {
|
|
618
|
+
return (_jsx(FleetListScreen, { title: `pick · ${shape}`, legend: "\u2191\u2193/jk move \u00B7 enter pin \u00B7 esc cancel \u00B7 q quit", rows: candidatesRef.current.map((candidate) => ({
|
|
619
|
+
id: candidate.id,
|
|
620
|
+
content: _jsx(Text, { children: candidate.label }),
|
|
621
|
+
})), cursor: candidateCursor }));
|
|
622
|
+
}
|
|
623
|
+
if (screen === "shape-prefer")
|
|
624
|
+
return (_jsx(FleetListScreen, { title: `pick · ${shape}.prefer`, legend: "\u2191\u2193/jk move \u00B7 space add/drop \u00B7 enter apply (empty clears) \u00B7 esc cancel \u00B7 q quit", rows: preferRowsRef.current.map((option) => {
|
|
625
|
+
const at = preferChainRef.current.indexOf(option);
|
|
626
|
+
return { id: option, content: _jsx(Text, { children: `${at === -1 ? "·" : String(at + 1)} ${option}` }) };
|
|
627
|
+
}), cursor: preferCursor }));
|
|
628
|
+
if (screen === "steering") {
|
|
629
|
+
return (_jsx(FleetListScreen, { title: "step 6/6 \u00B7 steering", legend: "\u2191\u2193/jk move \u00B7 f prefer \u00B7 enter review \u00B7 esc/q quit", rows: STEERING_KEYS.map((key) => ({
|
|
630
|
+
id: key,
|
|
631
|
+
content: _jsx(Text, { children: `${key}.prefer → ${steering[key]?.join(", ") ?? "(none)"}` }),
|
|
632
|
+
})), cursor: steeringCursor, details: notice ? [notice] : [] }));
|
|
633
|
+
}
|
|
634
|
+
if (screen === "steering-prefer") {
|
|
635
|
+
const which = steeringPickerRef.current;
|
|
636
|
+
return (_jsx(FleetListScreen, { title: `pick · ${which}.prefer`, legend: "\u2191\u2193/jk move \u00B7 space add/drop \u00B7 enter apply (empty clears) \u00B7 esc cancel \u00B7 q quit", rows: steeringRowsRef.current.map((option) => {
|
|
637
|
+
const at = steeringChainRef.current.indexOf(option);
|
|
638
|
+
return { id: option, content: _jsx(Text, { children: `${at === -1 ? "·" : String(at + 1)} ${option}` }) };
|
|
639
|
+
}), cursor: steeringPickerCursor }));
|
|
640
|
+
}
|
|
641
|
+
return (_jsx(FleetReviewScreen, { title: "review \u00B7 overlay diff", legend: "y write \u00B7 n discard \u00B7 esc/q quit", diff: review?.diff ?? "" }));
|
|
642
|
+
}
|
|
643
|
+
export async function runFleetInkEditor({ ageMs, adapters, health, initialDenyAdapters, initialDenyModels, modelGroups, initialMode, modeOptions, initialMap, modePreview, shapeRows, candidatesForShape, preferOptionsForShape, initialSteering, steeringOptionsFor, reviewOverlay, reloadGuard, initialInput = [], input, output, debug = false, }) {
|
|
644
|
+
const agents = adapters.flatMap((adapter) => {
|
|
645
|
+
const state = health[adapter.id];
|
|
646
|
+
if (!state?.installed)
|
|
647
|
+
return [];
|
|
648
|
+
return [{
|
|
649
|
+
id: adapter.id,
|
|
650
|
+
version: state.version ?? "installed",
|
|
651
|
+
authed: state.authed,
|
|
652
|
+
}];
|
|
653
|
+
});
|
|
654
|
+
const bridgedInput = inkInput(input, initialInput);
|
|
655
|
+
const legacyOutput = typeof output.on !== "function" || typeof output.off !== "function";
|
|
656
|
+
const app = render(_jsx(FleetApp, { ageMs: ageMs, agents: agents, initialDenyAdapters: initialDenyAdapters, initialDenyModels: initialDenyModels, modelGroups: modelGroups, initialMode: initialMode, modeOptions: modeOptions, initialMap: initialMap, modePreview: modePreview, shapeRows: shapeRows, candidatesForShape: candidatesForShape, preferOptionsForShape: preferOptionsForShape, initialSteering: initialSteering, steeringOptionsFor: steeringOptionsFor, reviewOverlay: reviewOverlay, reloadGuard: reloadGuard }), {
|
|
657
|
+
// FleetIO's injected stream predates Ink and did not require ref/unref.
|
|
658
|
+
// Real terminal streams pass through unchanged; the compatibility facade
|
|
659
|
+
// adds only those lifecycle methods and leaves input decoding to Ink.
|
|
660
|
+
stdin: bridgedInput.stream,
|
|
661
|
+
stdout: inkOutput(output),
|
|
662
|
+
exitOnCtrlC: false,
|
|
663
|
+
patchConsole: false,
|
|
664
|
+
// Legacy FleetIO outputs collected one complete frame per keypress before the Ink
|
|
665
|
+
// migration. Disable Ink's render throttling only for that injected facade so a
|
|
666
|
+
// cold-start byte sequence cannot collapse intermediate compatibility frames.
|
|
667
|
+
debug: debug || legacyOutput,
|
|
668
|
+
});
|
|
669
|
+
let result;
|
|
670
|
+
try {
|
|
671
|
+
result = await app.waitUntilExit();
|
|
672
|
+
return result;
|
|
673
|
+
}
|
|
674
|
+
finally {
|
|
675
|
+
app.unmount();
|
|
676
|
+
bridgedInput.stop();
|
|
677
|
+
}
|
|
678
|
+
}
|