tickmarkr 1.90.7 → 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 +15 -0
- package/dist/cli/commands/doctor.js +61 -14
- package/dist/cli/commands/fleet.js +31 -3
- package/dist/cli/commands/init.js +18 -16
- 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 +194 -55
- package/dist/tui/ink/init-app.js +2 -2
- 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,18 +339,39 @@ 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
|
}
|
|
354
|
+
// v1.90.8 (operator field report): inside the init journey, one Esc must never discard the
|
|
355
|
+
// whole walk — from any walk screen it returns HOME to the presets screen; Esc there (or
|
|
356
|
+
// `q`/ctrl+c anywhere) remains the real quit. Sub-screen Escs above keep their tighter backs.
|
|
357
|
+
if (key.escape && entry === "presets" && screenRef.current !== "modes") {
|
|
358
|
+
setReview(null);
|
|
359
|
+
presetPickRef.current = true;
|
|
360
|
+
showScreen("modes");
|
|
361
|
+
return;
|
|
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 : "";
|
|
317
368
|
const typing = screenRef.current === "provenance" || screenRef.current === "add-model";
|
|
318
|
-
if (key.escape || (!typing &&
|
|
369
|
+
if (key.escape || (!typing && letterAlias === "q") || (key.ctrl && input === "c")) {
|
|
319
370
|
finish({ kind: "quit" });
|
|
320
371
|
return;
|
|
321
372
|
}
|
|
322
373
|
if (screenRef.current === "probe") {
|
|
323
|
-
if (
|
|
374
|
+
if (letterAlias === "r") {
|
|
324
375
|
finish({ kind: "refresh" });
|
|
325
376
|
}
|
|
326
377
|
else if (key.return) {
|
|
@@ -329,13 +380,13 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
329
380
|
return;
|
|
330
381
|
}
|
|
331
382
|
if (screenRef.current === "agents") {
|
|
332
|
-
if (key.downArrow ||
|
|
383
|
+
if (key.downArrow || letterAlias === "j") {
|
|
333
384
|
const next = Math.min(cursorRef.current + 1, Math.max(agents.length - 1, 0));
|
|
334
385
|
cursorRef.current = next;
|
|
335
386
|
setCursor(next);
|
|
336
387
|
return;
|
|
337
388
|
}
|
|
338
|
-
if (key.upArrow ||
|
|
389
|
+
if (key.upArrow || letterAlias === "k") {
|
|
339
390
|
const next = Math.max(cursorRef.current - 1, 0);
|
|
340
391
|
cursorRef.current = next;
|
|
341
392
|
setCursor(next);
|
|
@@ -362,14 +413,14 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
362
413
|
}
|
|
363
414
|
if (screenRef.current === "models") {
|
|
364
415
|
const rows = filteredModelRows();
|
|
365
|
-
if (key.downArrow ||
|
|
416
|
+
if (key.downArrow || letterAlias === "j") {
|
|
366
417
|
const next = Math.min(modelCursorRef.current + 1, Math.max(rows.length - 1, 0));
|
|
367
418
|
modelCursorRef.current = next;
|
|
368
419
|
setModelCursor(next);
|
|
369
420
|
setNotice("");
|
|
370
421
|
return;
|
|
371
422
|
}
|
|
372
|
-
if (key.upArrow ||
|
|
423
|
+
if (key.upArrow || letterAlias === "k") {
|
|
373
424
|
const next = Math.max(modelCursorRef.current - 1, 0);
|
|
374
425
|
modelCursorRef.current = next;
|
|
375
426
|
setModelCursor(next);
|
|
@@ -378,7 +429,14 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
378
429
|
}
|
|
379
430
|
const group = enabledModelGroups()[modelGroupRef.current];
|
|
380
431
|
const row = rows[modelCursorRef.current];
|
|
381
|
-
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
|
+
}
|
|
382
440
|
const id = `${group.adapter}:${row.model}`;
|
|
383
441
|
const next = new Set(denyModelsRef.current);
|
|
384
442
|
if (next.has(id))
|
|
@@ -389,7 +447,10 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
389
447
|
setDenyModels(next);
|
|
390
448
|
return;
|
|
391
449
|
}
|
|
392
|
-
|
|
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) {
|
|
393
454
|
if (row.tier) {
|
|
394
455
|
setNotice("fleet: tier reassignment on classified models is not supported in v1 — edit config directly");
|
|
395
456
|
return;
|
|
@@ -397,13 +458,53 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
397
458
|
beginClassification(group, row.model);
|
|
398
459
|
return;
|
|
399
460
|
}
|
|
400
|
-
if (
|
|
461
|
+
if (letterAlias === "n" && group) {
|
|
401
462
|
modelIdRef.current = "";
|
|
402
463
|
setModelId("");
|
|
403
464
|
setNotice("");
|
|
404
465
|
showScreen("add-model");
|
|
405
466
|
return;
|
|
406
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
|
+
}
|
|
407
508
|
if (key.return) {
|
|
408
509
|
if (modelGroupRef.current + 1 < enabledModelGroups().length) {
|
|
409
510
|
const next = modelGroupRef.current + 1;
|
|
@@ -455,43 +556,60 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
455
556
|
return;
|
|
456
557
|
}
|
|
457
558
|
if (screenRef.current === "channel") {
|
|
458
|
-
if (key.downArrow ||
|
|
559
|
+
if (key.downArrow || letterAlias === "j") {
|
|
459
560
|
const next = Math.min(channelCursorRef.current + 1, CHANNELS.length - 1);
|
|
460
561
|
channelCursorRef.current = next;
|
|
461
562
|
setChannelCursor(next);
|
|
462
563
|
return;
|
|
463
564
|
}
|
|
464
|
-
if (key.upArrow ||
|
|
565
|
+
if (key.upArrow || letterAlias === "k") {
|
|
465
566
|
const next = Math.max(channelCursorRef.current - 1, 0);
|
|
466
567
|
channelCursorRef.current = next;
|
|
467
568
|
setChannelCursor(next);
|
|
468
569
|
return;
|
|
469
570
|
}
|
|
470
|
-
if (key.return
|
|
571
|
+
if (key.return) {
|
|
471
572
|
const channel = CHANNELS[channelCursorRef.current];
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
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
|
+
}
|
|
475
587
|
}
|
|
476
588
|
return;
|
|
477
589
|
}
|
|
478
590
|
if (screenRef.current === "tiers") {
|
|
479
|
-
if (key.downArrow ||
|
|
591
|
+
if (key.downArrow || letterAlias === "j") {
|
|
480
592
|
const next = Math.min(tierCursorRef.current + 1, TIERS.length - 1);
|
|
481
593
|
tierCursorRef.current = next;
|
|
482
594
|
setTierCursor(next);
|
|
483
595
|
return;
|
|
484
596
|
}
|
|
485
|
-
if (key.upArrow ||
|
|
597
|
+
if (key.upArrow || letterAlias === "k") {
|
|
486
598
|
const next = Math.max(tierCursorRef.current - 1, 0);
|
|
487
599
|
tierCursorRef.current = next;
|
|
488
600
|
setTierCursor(next);
|
|
489
601
|
return;
|
|
490
602
|
}
|
|
491
603
|
if (key.return && pendingClassificationRef.current) {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
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);
|
|
495
613
|
setNotice("");
|
|
496
614
|
showScreen("provenance");
|
|
497
615
|
}
|
|
@@ -510,6 +628,7 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
510
628
|
setClassificationRevision((revision) => revision + 1);
|
|
511
629
|
}
|
|
512
630
|
pendingClassificationRef.current = null;
|
|
631
|
+
pendingSuggestionRef.current = null;
|
|
513
632
|
setNotice("");
|
|
514
633
|
showScreen("models");
|
|
515
634
|
return;
|
|
@@ -530,13 +649,13 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
530
649
|
// preset entry adds one final `custom` row after the modes — the full-walk escape hatch
|
|
531
650
|
const rowCount = modeOptions.length + (presetPickRef.current ? 1 : 0);
|
|
532
651
|
const currentModeCursor = modeCursorRef.current ?? 0;
|
|
533
|
-
if (key.downArrow ||
|
|
652
|
+
if (key.downArrow || letterAlias === "j") {
|
|
534
653
|
const next = Math.min(currentModeCursor + 1, Math.max(rowCount - 1, 0));
|
|
535
654
|
modeCursorRef.current = next;
|
|
536
655
|
setModeCursor(next);
|
|
537
656
|
return;
|
|
538
657
|
}
|
|
539
|
-
if (key.upArrow ||
|
|
658
|
+
if (key.upArrow || letterAlias === "k") {
|
|
540
659
|
const next = Math.max(currentModeCursor - 1, 0);
|
|
541
660
|
modeCursorRef.current = next;
|
|
542
661
|
setModeCursor(next);
|
|
@@ -562,20 +681,20 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
562
681
|
}
|
|
563
682
|
if (screenRef.current === "shapes") {
|
|
564
683
|
const rows = shapeRows(selectedModeRef.current, stagedMap());
|
|
565
|
-
if (key.downArrow ||
|
|
684
|
+
if (key.downArrow || letterAlias === "j") {
|
|
566
685
|
const next = Math.min(shapeCursorRef.current + 1, Math.max(rows.length - 1, 0));
|
|
567
686
|
shapeCursorRef.current = next;
|
|
568
687
|
setShapeCursor(next);
|
|
569
688
|
return;
|
|
570
689
|
}
|
|
571
|
-
if (key.upArrow ||
|
|
690
|
+
if (key.upArrow || letterAlias === "k") {
|
|
572
691
|
const next = Math.max(shapeCursorRef.current - 1, 0);
|
|
573
692
|
shapeCursorRef.current = next;
|
|
574
693
|
setShapeCursor(next);
|
|
575
694
|
return;
|
|
576
695
|
}
|
|
577
696
|
const shape = rows[shapeCursorRef.current]?.id;
|
|
578
|
-
if (
|
|
697
|
+
if (letterAlias === "a" && shape) {
|
|
579
698
|
const nextEntry = { ...stagedMap()[shape] };
|
|
580
699
|
delete nextEntry.pin;
|
|
581
700
|
const nextMap = { ...stagedMap(), [shape]: nextEntry };
|
|
@@ -583,14 +702,14 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
583
702
|
setMap(nextMap);
|
|
584
703
|
return;
|
|
585
704
|
}
|
|
586
|
-
if (
|
|
705
|
+
if (letterAlias === "p" && shape) {
|
|
587
706
|
candidatesRef.current = candidatesForShape(shape, selectedModeRef.current, stagedMap());
|
|
588
707
|
candidateCursorRef.current = 0;
|
|
589
708
|
setCandidateCursor(0);
|
|
590
709
|
showScreen("candidates");
|
|
591
710
|
return;
|
|
592
711
|
}
|
|
593
|
-
if (
|
|
712
|
+
if (letterAlias === "f" && shape) {
|
|
594
713
|
const current = stagedMap()[shape]?.prefer ?? [];
|
|
595
714
|
preferRowsRef.current = preferOptionsForShape(shape, current);
|
|
596
715
|
preferChainRef.current = current.slice();
|
|
@@ -605,13 +724,13 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
605
724
|
}
|
|
606
725
|
if (screenRef.current === "candidates") {
|
|
607
726
|
const rows = filteredCandidates();
|
|
608
|
-
if (key.downArrow ||
|
|
727
|
+
if (key.downArrow || letterAlias === "j") {
|
|
609
728
|
const next = Math.min(candidateCursorRef.current + 1, Math.max(rows.length - 1, 0));
|
|
610
729
|
candidateCursorRef.current = next;
|
|
611
730
|
setCandidateCursor(next);
|
|
612
731
|
return;
|
|
613
732
|
}
|
|
614
|
-
if (key.upArrow ||
|
|
733
|
+
if (key.upArrow || letterAlias === "k") {
|
|
615
734
|
const next = Math.max(candidateCursorRef.current - 1, 0);
|
|
616
735
|
candidateCursorRef.current = next;
|
|
617
736
|
setCandidateCursor(next);
|
|
@@ -632,13 +751,13 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
632
751
|
return;
|
|
633
752
|
}
|
|
634
753
|
if (screenRef.current === "shape-prefer") {
|
|
635
|
-
if (key.downArrow ||
|
|
754
|
+
if (key.downArrow || letterAlias === "j") {
|
|
636
755
|
const next = Math.min(preferCursorRef.current + 1, Math.max(preferRowsRef.current.length - 1, 0));
|
|
637
756
|
preferCursorRef.current = next;
|
|
638
757
|
setPreferCursor(next);
|
|
639
758
|
return;
|
|
640
759
|
}
|
|
641
|
-
if (key.upArrow ||
|
|
760
|
+
if (key.upArrow || letterAlias === "k") {
|
|
642
761
|
const next = Math.max(preferCursorRef.current - 1, 0);
|
|
643
762
|
preferCursorRef.current = next;
|
|
644
763
|
setPreferCursor(next);
|
|
@@ -672,19 +791,19 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
672
791
|
}
|
|
673
792
|
if (screenRef.current === "steering") {
|
|
674
793
|
// review.prefer + consult.prefer rows plus the single judge seat as the final row
|
|
675
|
-
if (key.downArrow ||
|
|
794
|
+
if (key.downArrow || letterAlias === "j") {
|
|
676
795
|
const next = Math.min(steeringCursorRef.current + 1, STEERING_KEYS.length);
|
|
677
796
|
steeringCursorRef.current = next;
|
|
678
797
|
setSteeringCursor(next);
|
|
679
798
|
return;
|
|
680
799
|
}
|
|
681
|
-
if (key.upArrow ||
|
|
800
|
+
if (key.upArrow || letterAlias === "k") {
|
|
682
801
|
const next = Math.max(steeringCursorRef.current - 1, 0);
|
|
683
802
|
steeringCursorRef.current = next;
|
|
684
803
|
setSteeringCursor(next);
|
|
685
804
|
return;
|
|
686
805
|
}
|
|
687
|
-
if (
|
|
806
|
+
if (letterAlias === "f") {
|
|
688
807
|
if (steeringCursorRef.current === STEERING_KEYS.length) {
|
|
689
808
|
judgeCursorRef.current = 0;
|
|
690
809
|
setJudgeCursor(0);
|
|
@@ -709,13 +828,13 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
709
828
|
}
|
|
710
829
|
if (screenRef.current === "judge-pick") {
|
|
711
830
|
const rows = filteredJudgeRows();
|
|
712
|
-
if (key.downArrow ||
|
|
831
|
+
if (key.downArrow || letterAlias === "j") {
|
|
713
832
|
const next = Math.min(judgeCursorRef.current + 1, Math.max(rows.length - 1, 0));
|
|
714
833
|
judgeCursorRef.current = next;
|
|
715
834
|
setJudgeCursor(next);
|
|
716
835
|
return;
|
|
717
836
|
}
|
|
718
|
-
if (key.upArrow ||
|
|
837
|
+
if (key.upArrow || letterAlias === "k") {
|
|
719
838
|
const next = Math.max(judgeCursorRef.current - 1, 0);
|
|
720
839
|
judgeCursorRef.current = next;
|
|
721
840
|
setJudgeCursor(next);
|
|
@@ -735,13 +854,13 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
735
854
|
}
|
|
736
855
|
if (screenRef.current === "steering-prefer") {
|
|
737
856
|
const rows = filteredSteeringRows();
|
|
738
|
-
if (key.downArrow ||
|
|
857
|
+
if (key.downArrow || letterAlias === "j") {
|
|
739
858
|
const next = Math.min(steeringPickerCursorRef.current + 1, Math.max(rows.length - 1, 0));
|
|
740
859
|
steeringPickerCursorRef.current = next;
|
|
741
860
|
setSteeringPickerCursor(next);
|
|
742
861
|
return;
|
|
743
862
|
}
|
|
744
|
-
if (key.upArrow ||
|
|
863
|
+
if (key.upArrow || letterAlias === "k") {
|
|
745
864
|
const next = Math.max(steeringPickerCursorRef.current - 1, 0);
|
|
746
865
|
steeringPickerCursorRef.current = next;
|
|
747
866
|
setSteeringPickerCursor(next);
|
|
@@ -804,25 +923,42 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
804
923
|
content: (_jsxs(_Fragment, { children: [_jsx(ToggleMark, { active: active }), _jsx(Text, { children: ` ${agent.id} ${agent.version} ${agent.authed ? "authed" : "unauthed"}` })] })),
|
|
805
924
|
};
|
|
806
925
|
});
|
|
807
|
-
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 }));
|
|
808
927
|
}
|
|
809
928
|
const group = enabledModelGroups()[modelGroup];
|
|
810
929
|
if (screen === "models") {
|
|
811
|
-
const
|
|
930
|
+
const filtered = filteredModelRows();
|
|
931
|
+
const rows = filtered.map((row) => {
|
|
812
932
|
const denied = group ? denyModels.has(`${group.adapter}:${row.model}`) : false;
|
|
813
933
|
return {
|
|
814
934
|
id: row.model,
|
|
815
|
-
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` : ""}` }),
|
|
816
936
|
};
|
|
817
937
|
});
|
|
818
|
-
|
|
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 }));
|
|
819
955
|
}
|
|
820
956
|
if (screen === "add-model")
|
|
821
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] : [] }));
|
|
822
958
|
if (screen === "channel")
|
|
823
|
-
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 }));
|
|
824
960
|
if (screen === "tiers") {
|
|
825
|
-
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 }));
|
|
826
962
|
}
|
|
827
963
|
if (screen === "provenance")
|
|
828
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] : [] }));
|
|
@@ -838,7 +974,7 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
838
974
|
content: (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Text, { children: ` ${"custom".padEnd(11)} walk the full editor (agents → models → shapes → seats)` })] })),
|
|
839
975
|
});
|
|
840
976
|
}
|
|
841
|
-
return (_jsx(FleetListScreen, { title: presetPick ? "fleet · routing presets" : "step 4/6 · routing mode", legend: presetPick ? "↑↓ to move · Enter to apply preset ·
|
|
977
|
+
return (_jsx(FleetListScreen, { title: presetPick ? "tickmarkr init · act 3 of 3 — fleet · routing presets" : "step 4/6 · routing mode", legend: presetPick ? "↑↓ to move · Enter to apply preset · Enter on custom to walk the editor · Esc inside the walk returns here · Esc here closes" : "↑↓/jk move · enter select · esc/q quit", rows: rows, cursor: modeCursor, details: modeOptions[modeCursor] ? modePreview(modeOptions[modeCursor].id, map) : [] }));
|
|
842
978
|
}
|
|
843
979
|
const renderedShapes = shapeRows(selectedModeRef.current, map);
|
|
844
980
|
if (screen === "shapes") {
|
|
@@ -846,13 +982,13 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
846
982
|
}
|
|
847
983
|
const shape = renderedShapes[shapeCursor]?.id;
|
|
848
984
|
if (screen === "candidates") {
|
|
849
|
-
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) => ({
|
|
850
986
|
id: candidate.id,
|
|
851
987
|
content: _jsx(Text, { children: candidate.label }),
|
|
852
988
|
})), cursor: candidateCursor, filter: filter }));
|
|
853
989
|
}
|
|
854
990
|
if (screen === "shape-prefer")
|
|
855
|
-
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) => {
|
|
856
992
|
const at = preferChainRef.current.indexOf(option);
|
|
857
993
|
return { id: option, content: _jsx(Text, { children: `${at === -1 ? "·" : String(at + 1)} ${option}` }) };
|
|
858
994
|
}), cursor: preferCursor }));
|
|
@@ -870,14 +1006,14 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
870
1006
|
}
|
|
871
1007
|
if (screen === "judge-pick") {
|
|
872
1008
|
const selected = judgeSeat ?? judgeKeepRow;
|
|
873
|
-
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) => ({
|
|
874
1010
|
id: label,
|
|
875
1011
|
content: (_jsxs(_Fragment, { children: [label === selected ? _jsx(ToggleMark, { active: true }) : _jsx(Text, { children: " " }), _jsx(Text, { children: ` ${label}` })] })),
|
|
876
1012
|
})), cursor: judgeCursor, filter: filter }));
|
|
877
1013
|
}
|
|
878
1014
|
if (screen === "steering-prefer") {
|
|
879
1015
|
const which = steeringPickerRef.current;
|
|
880
|
-
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) => {
|
|
881
1017
|
const at = steeringChainRef.current.indexOf(option);
|
|
882
1018
|
return { id: option, content: _jsx(Text, { children: `${at === -1 ? "·" : String(at + 1)} ${option}` }) };
|
|
883
1019
|
}), cursor: steeringPickerCursor, filter: filter }));
|
|
@@ -902,7 +1038,10 @@ export async function runFleetInkEditor({ ageMs, adapters, health, initialDenyAd
|
|
|
902
1038
|
}));
|
|
903
1039
|
const bridgedInput = inkInput(input, initialInput);
|
|
904
1040
|
const legacyOutput = typeof output.on !== "function" || typeof output.off !== "function";
|
|
905
|
-
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) }), {
|
|
906
1045
|
// FleetIO's injected stream predates Ink and did not require ref/unref.
|
|
907
1046
|
// Real terminal streams pass through unchanged; the compatibility facade
|
|
908
1047
|
// adds only those lifecycle methods and leaves input decoding to Ink.
|
package/dist/tui/ink/init-app.js
CHANGED
|
@@ -101,8 +101,8 @@ function inkInput(input, initialInput) {
|
|
|
101
101
|
// Three fields, one toggle, one action — a descriptor array, deliberately not a forms framework.
|
|
102
102
|
function buildRows(offerSkills) {
|
|
103
103
|
const rows = [
|
|
104
|
-
{ id: "driver", section: "Run", label: "Driver", desc: "auto:
|
|
105
|
-
{ id: "concurrency", section: "Run", label: "Concurrency", desc: "parallel task batteries per run" },
|
|
104
|
+
{ id: "driver", section: "Run", label: "Driver", desc: "auto: herdr when HERDR_ENV=1, else subprocess · herdr: visible panes · subprocess: headless child processes" },
|
|
105
|
+
{ id: "concurrency", section: "Run", label: "Concurrency", desc: "parallel task batteries per run — min 1; an empty or zero entry reverts on leave" },
|
|
106
106
|
{
|
|
107
107
|
id: "visibility",
|
|
108
108
|
section: "Run",
|