tickmarkr 1.90.8 → 1.91.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/catalog-remote.d.ts +11 -1
- package/dist/adapters/catalog-remote.js +169 -19
- package/dist/adapters/catalog.js +59 -0
- package/dist/adapters/model-lints.d.ts +36 -2
- package/dist/adapters/model-lints.js +151 -35
- package/dist/adapters/registry.js +32 -6
- package/dist/cli/commands/doctor.d.ts +12 -0
- package/dist/cli/commands/doctor.js +57 -15
- package/dist/cli/commands/fleet.js +31 -3
- package/dist/compile/native.js +5 -4
- package/dist/config/config.js +5 -0
- package/dist/config/fleet-overlay.js +20 -4
- package/dist/gates/acceptance.js +13 -4
- package/dist/run/daemon.js +1 -1
- package/dist/tui/ink/components.d.ts +16 -1
- package/dist/tui/ink/components.js +16 -2
- package/dist/tui/ink/fleet-app.d.ts +8 -1
- package/dist/tui/ink/fleet-app.js +184 -54
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@ import { PassThrough } from "node:stream";
|
|
|
4
4
|
import { render, Text, useApp, useInput } from "ink";
|
|
5
5
|
import { useRef, useState } from "react";
|
|
6
6
|
import { MODEL_ID_RE } from "../../adapters/types.js";
|
|
7
|
+
import { retiredModelReason } from "../../adapters/model-lints.js";
|
|
7
8
|
import { fleetFirstTouchProvenance } from "../../config/fleet-overlay.js";
|
|
8
9
|
import { TIERS } from "../../graph/schema.js";
|
|
9
10
|
import { FleetListScreen, FleetReviewScreen, ToggleMark } from "./components.js";
|
|
@@ -108,7 +109,7 @@ function splitSeat(seat) {
|
|
|
108
109
|
const at = seat.indexOf(":");
|
|
109
110
|
return { adapter: seat.slice(0, at), model: seat.slice(at + 1) };
|
|
110
111
|
}
|
|
111
|
-
export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels, modelGroups, initialMode, modeOptions, initialMap, modePreview, shapeRows, candidatesForShape, preferOptionsForShape, initialSteering, steeringOptionsFor, reviewOverlay, reloadGuard, entry = "probe", initialJudge = "", judgeSeats = [], }) {
|
|
112
|
+
export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels, modelGroups, initialMode, modeOptions, initialMap, modePreview, shapeRows, candidatesForShape, preferOptionsForShape, initialSteering, steeringOptionsFor, reviewOverlay, reloadGuard, entry = "probe", initialJudge = "", judgeSeats = [], viewRows = Number.POSITIVE_INFINITY, }) {
|
|
112
113
|
const { exit } = useApp();
|
|
113
114
|
const [screen, setScreen] = useState(entry === "presets" ? "modes" : "probe");
|
|
114
115
|
const screenRef = useRef(screen);
|
|
@@ -134,6 +135,10 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
134
135
|
const tierCursorRef = useRef(0);
|
|
135
136
|
const [tierCursor, setTierCursor] = useState(0);
|
|
136
137
|
const pendingClassificationRef = useRef(null);
|
|
138
|
+
// OBS-508: the suggestion riding the in-flight classification (prefills tier + note), and the
|
|
139
|
+
// rows a bulk `s` staged while waiting on the one first-touch channel answer.
|
|
140
|
+
const pendingSuggestionRef = useRef(null);
|
|
141
|
+
const bulkRef = useRef(null);
|
|
137
142
|
const noteRef = useRef("");
|
|
138
143
|
const [note, setNote] = useState("");
|
|
139
144
|
const [notice, setNotice] = useState("");
|
|
@@ -178,6 +183,9 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
178
183
|
// type-to-search on the long lists — reset on every screen change (per-screen-visit filter)
|
|
179
184
|
const filterRef = useRef("");
|
|
180
185
|
const [filter, setFilter] = useState("");
|
|
186
|
+
// `a` on the models screen reveals retired/preview/non-worker shapes; sticky for the session.
|
|
187
|
+
const showAllModelsRef = useRef(false);
|
|
188
|
+
const [, setShowAllModels] = useState(false);
|
|
181
189
|
const enabledModelGroups = () => modelGroups.filter((group) => !denyRef.current?.has(group.adapter));
|
|
182
190
|
const stagedMap = () => mapRef.current;
|
|
183
191
|
const currentModelRows = () => {
|
|
@@ -199,7 +207,12 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
199
207
|
// type-to-search over the long lists: plain case-insensitive substring on the row's visible
|
|
200
208
|
// label. Each filterable screen derives its rows through these so input and render agree.
|
|
201
209
|
const matches = (label, f = filterRef.current) => f === "" || label.toLowerCase().includes(f.toLowerCase());
|
|
202
|
-
|
|
210
|
+
// Operator directive 2026-08-13: retired shapes (dated snapshots, previews, non-worker SKUs,
|
|
211
|
+
// legacy families) hide by DEFAULT — omp reports 218 ids and most can never carry a worker.
|
|
212
|
+
// `a` reveals them; a CLASSIFIED row is never hidden (a tiered dated snapshot was meant).
|
|
213
|
+
const filteredModelRows = (f = filterRef.current) => currentModelRows().filter((row) => matches(row.model, f)
|
|
214
|
+
&& (showAllModelsRef.current || row.tier !== undefined || retiredModelReason(row.model) === null));
|
|
215
|
+
const hiddenModelCount = (f = filterRef.current) => currentModelRows().filter((row) => matches(row.model, f)).length - filteredModelRows(f).length;
|
|
203
216
|
const filteredCandidates = (f = filterRef.current) => candidatesRef.current.filter((candidate) => matches(candidate.label, f));
|
|
204
217
|
const filteredSteeringRows = (f = filterRef.current) => steeringRowsRef.current.filter((option) => matches(option, f));
|
|
205
218
|
const judgeKeepRow = initialJudge ? `(keep default) ${initialJudge}` : "(keep default)";
|
|
@@ -266,9 +279,26 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
266
279
|
setNotice("");
|
|
267
280
|
showScreen("review");
|
|
268
281
|
};
|
|
282
|
+
const stageSuggested = (group, rows, firstTouch) => {
|
|
283
|
+
const staged = rows.map((row) => ({
|
|
284
|
+
adapter: group.adapter,
|
|
285
|
+
model: row.model,
|
|
286
|
+
tier: row.suggestion.tier,
|
|
287
|
+
note: row.suggestion.note,
|
|
288
|
+
...firstTouch,
|
|
289
|
+
}));
|
|
290
|
+
classificationsRef.current = [...classificationsRef.current, ...staged];
|
|
291
|
+
setClassificationRevision((revision) => revision + 1);
|
|
292
|
+
setNotice(`fleet: staged ${staged.length} catalog-suggested classification(s) — nothing writes before the review diff`);
|
|
293
|
+
};
|
|
269
294
|
const beginClassification = (group, model) => {
|
|
270
|
-
|
|
271
|
-
|
|
295
|
+
// OBS-508: catalog evidence prefills the flow — tier cursor lands on the suggested band and
|
|
296
|
+
// (when the operator keeps that band) the provenance note arrives pre-typed. Free to override.
|
|
297
|
+
const suggestion = group.rows.find((row) => row.model === model)?.suggestion ?? null;
|
|
298
|
+
pendingSuggestionRef.current = suggestion;
|
|
299
|
+
const tierAt = suggestion ? Math.max(TIERS.indexOf(suggestion.tier), 0) : 0;
|
|
300
|
+
tierCursorRef.current = tierAt;
|
|
301
|
+
setTierCursor(tierAt);
|
|
272
302
|
const firstTouch = !group.rows.some((row) => row.tier !== undefined);
|
|
273
303
|
const pending = {
|
|
274
304
|
adapter: group.adapter,
|
|
@@ -309,8 +339,15 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
309
339
|
showScreen("steering");
|
|
310
340
|
return;
|
|
311
341
|
}
|
|
312
|
-
|
|
342
|
+
// The classification sub-flow (add-model → channel → tier → provenance) cancels back to the
|
|
343
|
+
// models list — before this, provenance's own "esc cancel" legend lied: Esc fell through to
|
|
344
|
+
// the walk-home branch in init entry and to QUIT in fleet entry, discarding staged work.
|
|
345
|
+
if (key.escape && (screenRef.current === "add-model" || screenRef.current === "channel" || screenRef.current === "tiers" || screenRef.current === "provenance")) {
|
|
346
|
+
noteRef.current = "";
|
|
347
|
+
setNote("");
|
|
313
348
|
setNotice("");
|
|
349
|
+
pendingSuggestionRef.current = null;
|
|
350
|
+
bulkRef.current = null;
|
|
314
351
|
showScreen("models");
|
|
315
352
|
return;
|
|
316
353
|
}
|
|
@@ -323,13 +360,18 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
323
360
|
showScreen("modes");
|
|
324
361
|
return;
|
|
325
362
|
}
|
|
363
|
+
// While a type-to-search filter is LIVE, every printable char belongs to the search box:
|
|
364
|
+
// the letter aliases (j/k navigation, q quit, models' t/n/a) go dormant so "fake-n" or
|
|
365
|
+
// "qwen" can actually be typed — the k moved the cursor and the q quit the editor before
|
|
366
|
+
// this (operator field session, 2026-08-13). Arrows, Enter, Space, and Esc keep their roles.
|
|
367
|
+
const letterAlias = filterRef.current === "" ? input : "";
|
|
326
368
|
const typing = screenRef.current === "provenance" || screenRef.current === "add-model";
|
|
327
|
-
if (key.escape || (!typing &&
|
|
369
|
+
if (key.escape || (!typing && letterAlias === "q") || (key.ctrl && input === "c")) {
|
|
328
370
|
finish({ kind: "quit" });
|
|
329
371
|
return;
|
|
330
372
|
}
|
|
331
373
|
if (screenRef.current === "probe") {
|
|
332
|
-
if (
|
|
374
|
+
if (letterAlias === "r") {
|
|
333
375
|
finish({ kind: "refresh" });
|
|
334
376
|
}
|
|
335
377
|
else if (key.return) {
|
|
@@ -338,13 +380,13 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
338
380
|
return;
|
|
339
381
|
}
|
|
340
382
|
if (screenRef.current === "agents") {
|
|
341
|
-
if (key.downArrow ||
|
|
383
|
+
if (key.downArrow || letterAlias === "j") {
|
|
342
384
|
const next = Math.min(cursorRef.current + 1, Math.max(agents.length - 1, 0));
|
|
343
385
|
cursorRef.current = next;
|
|
344
386
|
setCursor(next);
|
|
345
387
|
return;
|
|
346
388
|
}
|
|
347
|
-
if (key.upArrow ||
|
|
389
|
+
if (key.upArrow || letterAlias === "k") {
|
|
348
390
|
const next = Math.max(cursorRef.current - 1, 0);
|
|
349
391
|
cursorRef.current = next;
|
|
350
392
|
setCursor(next);
|
|
@@ -371,14 +413,14 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
371
413
|
}
|
|
372
414
|
if (screenRef.current === "models") {
|
|
373
415
|
const rows = filteredModelRows();
|
|
374
|
-
if (key.downArrow ||
|
|
416
|
+
if (key.downArrow || letterAlias === "j") {
|
|
375
417
|
const next = Math.min(modelCursorRef.current + 1, Math.max(rows.length - 1, 0));
|
|
376
418
|
modelCursorRef.current = next;
|
|
377
419
|
setModelCursor(next);
|
|
378
420
|
setNotice("");
|
|
379
421
|
return;
|
|
380
422
|
}
|
|
381
|
-
if (key.upArrow ||
|
|
423
|
+
if (key.upArrow || letterAlias === "k") {
|
|
382
424
|
const next = Math.max(modelCursorRef.current - 1, 0);
|
|
383
425
|
modelCursorRef.current = next;
|
|
384
426
|
setModelCursor(next);
|
|
@@ -387,7 +429,14 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
387
429
|
}
|
|
388
430
|
const group = enabledModelGroups()[modelGroupRef.current];
|
|
389
431
|
const row = rows[modelCursorRef.current];
|
|
390
|
-
if (input === " " && group && row
|
|
432
|
+
if (input === " " && group && row) {
|
|
433
|
+
if (!row.tier) {
|
|
434
|
+
// v1.90.9 (operator field report: "not selectable"): an unclassified model has no tier
|
|
435
|
+
// to route on — selecting IS classifying, so Space opens the same channel → tier →
|
|
436
|
+
// provenance flow as `t` instead of dying silently on a row drawn like a checkbox.
|
|
437
|
+
beginClassification(group, row.model);
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
391
440
|
const id = `${group.adapter}:${row.model}`;
|
|
392
441
|
const next = new Set(denyModelsRef.current);
|
|
393
442
|
if (next.has(id))
|
|
@@ -398,7 +447,10 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
398
447
|
setDenyModels(next);
|
|
399
448
|
return;
|
|
400
449
|
}
|
|
401
|
-
|
|
450
|
+
// Letter hotkeys yield to an ACTIVE search (letterAlias above): with a filter live, every
|
|
451
|
+
// printable char belongs to the search box — otherwise typing "anthropic" toggles show-all
|
|
452
|
+
// at the `a` and opens classify at the `t`.
|
|
453
|
+
if (letterAlias === "t" && group && row) {
|
|
402
454
|
if (row.tier) {
|
|
403
455
|
setNotice("fleet: tier reassignment on classified models is not supported in v1 — edit config directly");
|
|
404
456
|
return;
|
|
@@ -406,13 +458,53 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
406
458
|
beginClassification(group, row.model);
|
|
407
459
|
return;
|
|
408
460
|
}
|
|
409
|
-
if (
|
|
461
|
+
if (letterAlias === "n" && group) {
|
|
410
462
|
modelIdRef.current = "";
|
|
411
463
|
setModelId("");
|
|
412
464
|
setNotice("");
|
|
413
465
|
showScreen("add-model");
|
|
414
466
|
return;
|
|
415
467
|
}
|
|
468
|
+
if (letterAlias === "s" && group) {
|
|
469
|
+
// OBS-508: bulk-stage every VISIBLE unclassified model carrying a catalog suggestion —
|
|
470
|
+
// visible means the type-to-search filter and the retired-hide both scope the batch. The
|
|
471
|
+
// staging is consent-preserving: it lands in the same classifications list the single
|
|
472
|
+
// flow feeds, and only the review diff (y) ever writes.
|
|
473
|
+
const suggested = filteredModelRows()
|
|
474
|
+
.filter((candidate) => !candidate.tier && candidate.suggestion !== undefined);
|
|
475
|
+
if (!suggested.length) {
|
|
476
|
+
setNotice("fleet: no catalog tier suggestions among the visible unclassified models — evidence comes from the cached catalogs (AA index / API pricing)");
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
const firstTouch = !group.rows.some((candidate) => candidate.tier !== undefined);
|
|
480
|
+
if (!firstTouch) {
|
|
481
|
+
stageSuggested(group, suggested);
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
if (!group.vendor) {
|
|
485
|
+
setNotice(`fleet: ${group.adapter} has no vendor declaration — classification cannot be saved`);
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
const answered = channelByAdapterRef.current[group.adapter];
|
|
489
|
+
if (answered) {
|
|
490
|
+
stageSuggested(group, suggested, { vendor: group.vendor, channel: answered });
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
bulkRef.current = { group, rows: suggested };
|
|
494
|
+
channelCursorRef.current = 0;
|
|
495
|
+
setChannelCursor(0);
|
|
496
|
+
setNotice("");
|
|
497
|
+
showScreen("channel");
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
if (letterAlias === "a") {
|
|
501
|
+
showAllModelsRef.current = !showAllModelsRef.current;
|
|
502
|
+
setShowAllModels(showAllModelsRef.current);
|
|
503
|
+
const clamped = Math.min(modelCursorRef.current, Math.max(filteredModelRows().length - 1, 0));
|
|
504
|
+
modelCursorRef.current = clamped;
|
|
505
|
+
setModelCursor(clamped);
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
416
508
|
if (key.return) {
|
|
417
509
|
if (modelGroupRef.current + 1 < enabledModelGroups().length) {
|
|
418
510
|
const next = modelGroupRef.current + 1;
|
|
@@ -464,43 +556,60 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
464
556
|
return;
|
|
465
557
|
}
|
|
466
558
|
if (screenRef.current === "channel") {
|
|
467
|
-
if (key.downArrow ||
|
|
559
|
+
if (key.downArrow || letterAlias === "j") {
|
|
468
560
|
const next = Math.min(channelCursorRef.current + 1, CHANNELS.length - 1);
|
|
469
561
|
channelCursorRef.current = next;
|
|
470
562
|
setChannelCursor(next);
|
|
471
563
|
return;
|
|
472
564
|
}
|
|
473
|
-
if (key.upArrow ||
|
|
565
|
+
if (key.upArrow || letterAlias === "k") {
|
|
474
566
|
const next = Math.max(channelCursorRef.current - 1, 0);
|
|
475
567
|
channelCursorRef.current = next;
|
|
476
568
|
setChannelCursor(next);
|
|
477
569
|
return;
|
|
478
570
|
}
|
|
479
|
-
if (key.return
|
|
571
|
+
if (key.return) {
|
|
480
572
|
const channel = CHANNELS[channelCursorRef.current];
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
573
|
+
// OBS-508: a bulk stage waiting on the one first-touch channel answer resumes here.
|
|
574
|
+
const bulk = bulkRef.current;
|
|
575
|
+
if (bulk?.group.vendor) {
|
|
576
|
+
channelByAdapterRef.current[bulk.group.adapter] = channel;
|
|
577
|
+
stageSuggested(bulk.group, bulk.rows, { vendor: bulk.group.vendor, channel });
|
|
578
|
+
bulkRef.current = null;
|
|
579
|
+
showScreen("models");
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
if (pendingClassificationRef.current) {
|
|
583
|
+
channelByAdapterRef.current[pendingClassificationRef.current.adapter] = channel;
|
|
584
|
+
pendingClassificationRef.current.channel = channel;
|
|
585
|
+
showScreen("tiers");
|
|
586
|
+
}
|
|
484
587
|
}
|
|
485
588
|
return;
|
|
486
589
|
}
|
|
487
590
|
if (screenRef.current === "tiers") {
|
|
488
|
-
if (key.downArrow ||
|
|
591
|
+
if (key.downArrow || letterAlias === "j") {
|
|
489
592
|
const next = Math.min(tierCursorRef.current + 1, TIERS.length - 1);
|
|
490
593
|
tierCursorRef.current = next;
|
|
491
594
|
setTierCursor(next);
|
|
492
595
|
return;
|
|
493
596
|
}
|
|
494
|
-
if (key.upArrow ||
|
|
597
|
+
if (key.upArrow || letterAlias === "k") {
|
|
495
598
|
const next = Math.max(tierCursorRef.current - 1, 0);
|
|
496
599
|
tierCursorRef.current = next;
|
|
497
600
|
setTierCursor(next);
|
|
498
601
|
return;
|
|
499
602
|
}
|
|
500
603
|
if (key.return && pendingClassificationRef.current) {
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
604
|
+
const chosen = TIERS[tierCursorRef.current];
|
|
605
|
+
pendingClassificationRef.current.tier = chosen;
|
|
606
|
+
// OBS-508: keep the suggested band → the evidence note arrives pre-typed (Enter applies
|
|
607
|
+
// it as-is); override the band → the note starts empty, because the suggestion's text
|
|
608
|
+
// argues for a DIFFERENT tier than the one being recorded.
|
|
609
|
+
const suggestion = pendingSuggestionRef.current;
|
|
610
|
+
const prefill = suggestion && suggestion.tier === chosen ? suggestion.note : "";
|
|
611
|
+
noteRef.current = prefill;
|
|
612
|
+
setNote(prefill);
|
|
504
613
|
setNotice("");
|
|
505
614
|
showScreen("provenance");
|
|
506
615
|
}
|
|
@@ -519,6 +628,7 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
519
628
|
setClassificationRevision((revision) => revision + 1);
|
|
520
629
|
}
|
|
521
630
|
pendingClassificationRef.current = null;
|
|
631
|
+
pendingSuggestionRef.current = null;
|
|
522
632
|
setNotice("");
|
|
523
633
|
showScreen("models");
|
|
524
634
|
return;
|
|
@@ -539,13 +649,13 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
539
649
|
// preset entry adds one final `custom` row after the modes — the full-walk escape hatch
|
|
540
650
|
const rowCount = modeOptions.length + (presetPickRef.current ? 1 : 0);
|
|
541
651
|
const currentModeCursor = modeCursorRef.current ?? 0;
|
|
542
|
-
if (key.downArrow ||
|
|
652
|
+
if (key.downArrow || letterAlias === "j") {
|
|
543
653
|
const next = Math.min(currentModeCursor + 1, Math.max(rowCount - 1, 0));
|
|
544
654
|
modeCursorRef.current = next;
|
|
545
655
|
setModeCursor(next);
|
|
546
656
|
return;
|
|
547
657
|
}
|
|
548
|
-
if (key.upArrow ||
|
|
658
|
+
if (key.upArrow || letterAlias === "k") {
|
|
549
659
|
const next = Math.max(currentModeCursor - 1, 0);
|
|
550
660
|
modeCursorRef.current = next;
|
|
551
661
|
setModeCursor(next);
|
|
@@ -571,20 +681,20 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
571
681
|
}
|
|
572
682
|
if (screenRef.current === "shapes") {
|
|
573
683
|
const rows = shapeRows(selectedModeRef.current, stagedMap());
|
|
574
|
-
if (key.downArrow ||
|
|
684
|
+
if (key.downArrow || letterAlias === "j") {
|
|
575
685
|
const next = Math.min(shapeCursorRef.current + 1, Math.max(rows.length - 1, 0));
|
|
576
686
|
shapeCursorRef.current = next;
|
|
577
687
|
setShapeCursor(next);
|
|
578
688
|
return;
|
|
579
689
|
}
|
|
580
|
-
if (key.upArrow ||
|
|
690
|
+
if (key.upArrow || letterAlias === "k") {
|
|
581
691
|
const next = Math.max(shapeCursorRef.current - 1, 0);
|
|
582
692
|
shapeCursorRef.current = next;
|
|
583
693
|
setShapeCursor(next);
|
|
584
694
|
return;
|
|
585
695
|
}
|
|
586
696
|
const shape = rows[shapeCursorRef.current]?.id;
|
|
587
|
-
if (
|
|
697
|
+
if (letterAlias === "a" && shape) {
|
|
588
698
|
const nextEntry = { ...stagedMap()[shape] };
|
|
589
699
|
delete nextEntry.pin;
|
|
590
700
|
const nextMap = { ...stagedMap(), [shape]: nextEntry };
|
|
@@ -592,14 +702,14 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
592
702
|
setMap(nextMap);
|
|
593
703
|
return;
|
|
594
704
|
}
|
|
595
|
-
if (
|
|
705
|
+
if (letterAlias === "p" && shape) {
|
|
596
706
|
candidatesRef.current = candidatesForShape(shape, selectedModeRef.current, stagedMap());
|
|
597
707
|
candidateCursorRef.current = 0;
|
|
598
708
|
setCandidateCursor(0);
|
|
599
709
|
showScreen("candidates");
|
|
600
710
|
return;
|
|
601
711
|
}
|
|
602
|
-
if (
|
|
712
|
+
if (letterAlias === "f" && shape) {
|
|
603
713
|
const current = stagedMap()[shape]?.prefer ?? [];
|
|
604
714
|
preferRowsRef.current = preferOptionsForShape(shape, current);
|
|
605
715
|
preferChainRef.current = current.slice();
|
|
@@ -614,13 +724,13 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
614
724
|
}
|
|
615
725
|
if (screenRef.current === "candidates") {
|
|
616
726
|
const rows = filteredCandidates();
|
|
617
|
-
if (key.downArrow ||
|
|
727
|
+
if (key.downArrow || letterAlias === "j") {
|
|
618
728
|
const next = Math.min(candidateCursorRef.current + 1, Math.max(rows.length - 1, 0));
|
|
619
729
|
candidateCursorRef.current = next;
|
|
620
730
|
setCandidateCursor(next);
|
|
621
731
|
return;
|
|
622
732
|
}
|
|
623
|
-
if (key.upArrow ||
|
|
733
|
+
if (key.upArrow || letterAlias === "k") {
|
|
624
734
|
const next = Math.max(candidateCursorRef.current - 1, 0);
|
|
625
735
|
candidateCursorRef.current = next;
|
|
626
736
|
setCandidateCursor(next);
|
|
@@ -641,13 +751,13 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
641
751
|
return;
|
|
642
752
|
}
|
|
643
753
|
if (screenRef.current === "shape-prefer") {
|
|
644
|
-
if (key.downArrow ||
|
|
754
|
+
if (key.downArrow || letterAlias === "j") {
|
|
645
755
|
const next = Math.min(preferCursorRef.current + 1, Math.max(preferRowsRef.current.length - 1, 0));
|
|
646
756
|
preferCursorRef.current = next;
|
|
647
757
|
setPreferCursor(next);
|
|
648
758
|
return;
|
|
649
759
|
}
|
|
650
|
-
if (key.upArrow ||
|
|
760
|
+
if (key.upArrow || letterAlias === "k") {
|
|
651
761
|
const next = Math.max(preferCursorRef.current - 1, 0);
|
|
652
762
|
preferCursorRef.current = next;
|
|
653
763
|
setPreferCursor(next);
|
|
@@ -681,19 +791,19 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
681
791
|
}
|
|
682
792
|
if (screenRef.current === "steering") {
|
|
683
793
|
// review.prefer + consult.prefer rows plus the single judge seat as the final row
|
|
684
|
-
if (key.downArrow ||
|
|
794
|
+
if (key.downArrow || letterAlias === "j") {
|
|
685
795
|
const next = Math.min(steeringCursorRef.current + 1, STEERING_KEYS.length);
|
|
686
796
|
steeringCursorRef.current = next;
|
|
687
797
|
setSteeringCursor(next);
|
|
688
798
|
return;
|
|
689
799
|
}
|
|
690
|
-
if (key.upArrow ||
|
|
800
|
+
if (key.upArrow || letterAlias === "k") {
|
|
691
801
|
const next = Math.max(steeringCursorRef.current - 1, 0);
|
|
692
802
|
steeringCursorRef.current = next;
|
|
693
803
|
setSteeringCursor(next);
|
|
694
804
|
return;
|
|
695
805
|
}
|
|
696
|
-
if (
|
|
806
|
+
if (letterAlias === "f") {
|
|
697
807
|
if (steeringCursorRef.current === STEERING_KEYS.length) {
|
|
698
808
|
judgeCursorRef.current = 0;
|
|
699
809
|
setJudgeCursor(0);
|
|
@@ -718,13 +828,13 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
718
828
|
}
|
|
719
829
|
if (screenRef.current === "judge-pick") {
|
|
720
830
|
const rows = filteredJudgeRows();
|
|
721
|
-
if (key.downArrow ||
|
|
831
|
+
if (key.downArrow || letterAlias === "j") {
|
|
722
832
|
const next = Math.min(judgeCursorRef.current + 1, Math.max(rows.length - 1, 0));
|
|
723
833
|
judgeCursorRef.current = next;
|
|
724
834
|
setJudgeCursor(next);
|
|
725
835
|
return;
|
|
726
836
|
}
|
|
727
|
-
if (key.upArrow ||
|
|
837
|
+
if (key.upArrow || letterAlias === "k") {
|
|
728
838
|
const next = Math.max(judgeCursorRef.current - 1, 0);
|
|
729
839
|
judgeCursorRef.current = next;
|
|
730
840
|
setJudgeCursor(next);
|
|
@@ -744,13 +854,13 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
744
854
|
}
|
|
745
855
|
if (screenRef.current === "steering-prefer") {
|
|
746
856
|
const rows = filteredSteeringRows();
|
|
747
|
-
if (key.downArrow ||
|
|
857
|
+
if (key.downArrow || letterAlias === "j") {
|
|
748
858
|
const next = Math.min(steeringPickerCursorRef.current + 1, Math.max(rows.length - 1, 0));
|
|
749
859
|
steeringPickerCursorRef.current = next;
|
|
750
860
|
setSteeringPickerCursor(next);
|
|
751
861
|
return;
|
|
752
862
|
}
|
|
753
|
-
if (key.upArrow ||
|
|
863
|
+
if (key.upArrow || letterAlias === "k") {
|
|
754
864
|
const next = Math.max(steeringPickerCursorRef.current - 1, 0);
|
|
755
865
|
steeringPickerCursorRef.current = next;
|
|
756
866
|
setSteeringPickerCursor(next);
|
|
@@ -813,25 +923,42 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
813
923
|
content: (_jsxs(_Fragment, { children: [_jsx(ToggleMark, { active: active }), _jsx(Text, { children: ` ${agent.id} ${agent.version} ${agent.authed ? "authed" : "unauthed"}` })] })),
|
|
814
924
|
};
|
|
815
925
|
});
|
|
816
|
-
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 }));
|
|
926
|
+
return (_jsx(FleetListScreen, { title: "step 2/6 \u00B7 agent CLIs", viewRows: viewRows, legend: "\u2191\u2193/jk move \u00B7 space toggle \u00B7 enter next \u00B7 esc/q quit", rows: rows, cursor: cursor }));
|
|
817
927
|
}
|
|
818
928
|
const group = enabledModelGroups()[modelGroup];
|
|
819
929
|
if (screen === "models") {
|
|
820
|
-
const
|
|
930
|
+
const filtered = filteredModelRows();
|
|
931
|
+
const rows = filtered.map((row) => {
|
|
821
932
|
const denied = group ? denyModels.has(`${group.adapter}:${row.model}`) : false;
|
|
822
933
|
return {
|
|
823
934
|
id: row.model,
|
|
824
|
-
content: row.tier ? (_jsxs(_Fragment, { children: [_jsx(ToggleMark, { active: !denied }), _jsx(Text, { children: ` ${row.model} ${row.tier} ${denied ? "denied" : "allowed"}` })] })) : _jsx(Text, { children:
|
|
935
|
+
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}${row.suggestion ? ` → ${row.suggestion.tier} suggested` : ""}` }),
|
|
825
936
|
};
|
|
826
937
|
});
|
|
827
|
-
|
|
938
|
+
// The remedy renders ONCE, for the row under the cursor — 218 identical "Space or t to
|
|
939
|
+
// classify" suffixes drowned the model names and wrapped long ids (operator screenshot,
|
|
940
|
+
// 2026-08-13). A notice always wins the line.
|
|
941
|
+
const focused = filtered[modelCursor];
|
|
942
|
+
const hidden = hiddenModelCount();
|
|
943
|
+
const detail = [
|
|
944
|
+
...(notice
|
|
945
|
+
? [notice]
|
|
946
|
+
: focused && !focused.tier
|
|
947
|
+
? [focused.suggestion
|
|
948
|
+
? `? ${focused.suggestion.tier} suggested from catalog evidence — Space accepts prefilled, s stages every visible suggestion${focused.detectedAt ? ` (detected ${focused.detectedAt})` : ""}`
|
|
949
|
+
: `? unclassified — Space or t to classify${focused.detectedAt ? ` (detected ${focused.detectedAt})` : ""}; unclassified models are never routed`]
|
|
950
|
+
: []),
|
|
951
|
+
...(hidden > 0 ? [`… ${hidden} retired/preview/non-worker hidden — a shows all`] : []),
|
|
952
|
+
...(showAllModelsRef.current ? ["showing retired models — a hides them again"] : []),
|
|
953
|
+
];
|
|
954
|
+
return (_jsx(FleetListScreen, { title: `step 3/6 · models · ${group?.adapter ?? ""}`, legend: "\u2191\u2193 move \u00B7 Space toggle/classify \u00B7 s stage suggested \u00B7 t tier \u00B7 n add \u00B7 a all \u00B7 Enter next \u00B7 Type to search \u00B7 Esc quit", viewRows: viewRows, rows: rows, cursor: modelCursor, details: detail, filter: filter }));
|
|
828
955
|
}
|
|
829
956
|
if (screen === "add-model")
|
|
830
957
|
return (_jsx(FleetListScreen, { title: `add model · ${group?.adapter ?? ""}`, legend: "type model id \u00B7 enter classify \u00B7 esc cancel", rows: [{ id: "model", content: _jsx(Text, { children: modelId || "model id (required):" }) }], cursor: 0, details: notice ? [notice] : [] }));
|
|
831
958
|
if (screen === "channel")
|
|
832
|
-
return (_jsx(FleetListScreen, { title: `channel · ${pendingClassificationRef.current?.adapter ?? ""}`, legend: "\u2191\u2193/jk move \u00B7 enter select \u00B7 esc
|
|
959
|
+
return (_jsx(FleetListScreen, { title: `channel · ${pendingClassificationRef.current?.adapter ?? ""}`, legend: "\u2191\u2193/jk move \u00B7 enter select \u00B7 esc cancel \u00B7 q quit", rows: CHANNELS.map((channel) => ({ id: channel, content: _jsx(Text, { children: channel }) })), cursor: channelCursor }));
|
|
833
960
|
if (screen === "tiers") {
|
|
834
|
-
return (_jsx(FleetListScreen, { title: `pick · tier · ${pendingClassificationRef.current?.adapter}:${pendingClassificationRef.current?.model}`, legend: "\u2191\u2193/jk move \u00B7 enter select \u00B7 esc
|
|
961
|
+
return (_jsx(FleetListScreen, { title: `pick · tier · ${pendingClassificationRef.current?.adapter}:${pendingClassificationRef.current?.model}`, legend: "\u2191\u2193/jk move \u00B7 enter select \u00B7 esc cancel \u00B7 q quit", rows: TIERS.map((tier) => ({ id: tier, content: _jsx(Text, { children: tier }) })), cursor: tierCursor }));
|
|
835
962
|
}
|
|
836
963
|
if (screen === "provenance")
|
|
837
964
|
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] : [] }));
|
|
@@ -855,13 +982,13 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
855
982
|
}
|
|
856
983
|
const shape = renderedShapes[shapeCursor]?.id;
|
|
857
984
|
if (screen === "candidates") {
|
|
858
|
-
return (_jsx(FleetListScreen, { title: `pick · ${shape}`, legend: "\u2191\u2193 to move \u00B7 Enter to pin \u00B7 Type to search \u00B7 Esc to cancel \u00B7 q to quit", rows: filteredCandidates().map((candidate) => ({
|
|
985
|
+
return (_jsx(FleetListScreen, { title: `pick · ${shape}`, viewRows: viewRows, legend: "\u2191\u2193 to move \u00B7 Enter to pin \u00B7 Type to search \u00B7 Esc to cancel \u00B7 q to quit", rows: filteredCandidates().map((candidate) => ({
|
|
859
986
|
id: candidate.id,
|
|
860
987
|
content: _jsx(Text, { children: candidate.label }),
|
|
861
988
|
})), cursor: candidateCursor, filter: filter }));
|
|
862
989
|
}
|
|
863
990
|
if (screen === "shape-prefer")
|
|
864
|
-
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) => {
|
|
991
|
+
return (_jsx(FleetListScreen, { title: `pick · ${shape}.prefer`, viewRows: viewRows, 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) => {
|
|
865
992
|
const at = preferChainRef.current.indexOf(option);
|
|
866
993
|
return { id: option, content: _jsx(Text, { children: `${at === -1 ? "·" : String(at + 1)} ${option}` }) };
|
|
867
994
|
}), cursor: preferCursor }));
|
|
@@ -879,14 +1006,14 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
879
1006
|
}
|
|
880
1007
|
if (screen === "judge-pick") {
|
|
881
1008
|
const selected = judgeSeat ?? judgeKeepRow;
|
|
882
|
-
return (_jsx(FleetListScreen, { title: "pick \u00B7 judge", legend: "\u2191\u2193 to move \u00B7 Enter to select \u00B7 Type to search \u00B7 Esc to cancel \u00B7 q to quit", rows: filteredJudgeRows().map((label) => ({
|
|
1009
|
+
return (_jsx(FleetListScreen, { title: "pick \u00B7 judge", viewRows: viewRows, legend: "\u2191\u2193 to move \u00B7 Enter to select \u00B7 Type to search \u00B7 Esc to cancel \u00B7 q to quit", rows: filteredJudgeRows().map((label) => ({
|
|
883
1010
|
id: label,
|
|
884
1011
|
content: (_jsxs(_Fragment, { children: [label === selected ? _jsx(ToggleMark, { active: true }) : _jsx(Text, { children: " " }), _jsx(Text, { children: ` ${label}` })] })),
|
|
885
1012
|
})), cursor: judgeCursor, filter: filter }));
|
|
886
1013
|
}
|
|
887
1014
|
if (screen === "steering-prefer") {
|
|
888
1015
|
const which = steeringPickerRef.current;
|
|
889
|
-
return (_jsx(FleetListScreen, { title: `pick · ${which}.prefer`, legend: "\u2191\u2193 to move \u00B7 Space to add/drop \u00B7 Enter to apply (empty clears) \u00B7 Type to search \u00B7 Esc to cancel \u00B7 q to quit", rows: filteredSteeringRows().map((option) => {
|
|
1016
|
+
return (_jsx(FleetListScreen, { title: `pick · ${which}.prefer`, viewRows: viewRows, legend: "\u2191\u2193 to move \u00B7 Space to add/drop \u00B7 Enter to apply (empty clears) \u00B7 Type to search \u00B7 Esc to cancel \u00B7 q to quit", rows: filteredSteeringRows().map((option) => {
|
|
890
1017
|
const at = steeringChainRef.current.indexOf(option);
|
|
891
1018
|
return { id: option, content: _jsx(Text, { children: `${at === -1 ? "·" : String(at + 1)} ${option}` }) };
|
|
892
1019
|
}), cursor: steeringPickerCursor, filter: filter }));
|
|
@@ -911,7 +1038,10 @@ export async function runFleetInkEditor({ ageMs, adapters, health, initialDenyAd
|
|
|
911
1038
|
}));
|
|
912
1039
|
const bridgedInput = inkInput(input, initialInput);
|
|
913
1040
|
const legacyOutput = typeof output.on !== "function" || typeof output.off !== "function";
|
|
914
|
-
const app = render(_jsx(FleetApp, { ageMs: ageMs, agents: agents, initialDenyAdapters: initialDenyAdapters, initialDenyModels: initialDenyModels, modelGroups: declaredModelGroups, initialMode: initialMode, modeOptions: modeOptions, initialMap: initialMap, modePreview: modePreview, shapeRows: shapeRows, candidatesForShape: candidatesForShape, preferOptionsForShape: preferOptionsForShape, initialSteering: initialSteering, steeringOptionsFor: steeringOptionsFor, reviewOverlay: reviewOverlay, reloadGuard: reloadGuard, entry: entry, initialJudge: initialJudge, judgeSeats: judgeSeats
|
|
1041
|
+
const app = render(_jsx(FleetApp, { ageMs: ageMs, agents: agents, initialDenyAdapters: initialDenyAdapters, initialDenyModels: initialDenyModels, modelGroups: declaredModelGroups, initialMode: initialMode, modeOptions: modeOptions, initialMap: initialMap, modePreview: modePreview, shapeRows: shapeRows, candidatesForShape: candidatesForShape, preferOptionsForShape: preferOptionsForShape, initialSteering: initialSteering, steeringOptionsFor: steeringOptionsFor, reviewOverlay: reviewOverlay, reloadGuard: reloadGuard, entry: entry, initialJudge: initialJudge, judgeSeats: judgeSeats,
|
|
1042
|
+
// v1.90.9: omp's 218-model list rendered taller than the terminal and scrolled the cursor
|
|
1043
|
+
// and chrome off-screen — cap every list to the terminal, minus title/legend/markers/details.
|
|
1044
|
+
viewRows: Math.max(8, (output.rows ?? 40) - 10) }), {
|
|
915
1045
|
// FleetIO's injected stream predates Ink and did not require ref/unref.
|
|
916
1046
|
// Real terminal streams pass through unchanged; the compatibility facade
|
|
917
1047
|
// adds only those lifecycle methods and leaves input decoding to Ink.
|