tickmarkr 2.5.4 → 2.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/adapters/registry.js +6 -1
- package/dist/adapters/types.d.ts +3 -0
- package/dist/cli/commands/approve.d.ts +1 -0
- package/dist/cli/commands/approve.js +118 -9
- package/dist/cli/commands/doctor.d.ts +10 -0
- package/dist/cli/commands/doctor.js +44 -29
- package/dist/cli/commands/fleet.js +199 -33
- package/dist/cli/commands/init.js +196 -6
- package/dist/cli/commands/plan.js +20 -4
- package/dist/cli/commands/resume.js +4 -2
- package/dist/cli/commands/run.js +11 -2
- package/dist/cli/commands/verify.js +102 -81
- package/dist/cli/help.d.ts +2 -0
- package/dist/cli/help.js +3 -1
- package/dist/config/config.d.ts +25 -8
- package/dist/config/config.js +41 -29
- package/dist/config/fleet-overlay.d.ts +3 -9
- package/dist/config/fleet-overlay.js +114 -19
- package/dist/config/fleet-why.d.ts +7 -0
- package/dist/config/fleet-why.js +5 -0
- package/dist/drivers/index.d.ts +15 -1
- package/dist/drivers/index.js +39 -10
- package/dist/drivers/orca.d.ts +119 -10
- package/dist/drivers/orca.js +781 -110
- package/dist/gates/baseline.d.ts +17 -3
- package/dist/gates/baseline.js +63 -15
- package/dist/gates/cache.d.ts +101 -0
- package/dist/gates/cache.js +401 -0
- package/dist/gates/llm.d.ts +3 -0
- package/dist/gates/llm.js +11 -0
- package/dist/gates/review.d.ts +28 -3
- package/dist/gates/review.js +106 -14
- package/dist/gates/run-gates.d.ts +9 -1
- package/dist/gates/run-gates.js +407 -102
- package/dist/gates/test-manifest.d.ts +128 -0
- package/dist/gates/test-manifest.js +463 -0
- package/dist/gates/test-reporter.d.ts +4 -0
- package/dist/gates/test-reporter.js +57 -0
- package/dist/graph/graph.d.ts +2 -0
- package/dist/graph/graph.js +45 -1
- package/dist/route/preference.d.ts +22 -1
- package/dist/route/preference.js +123 -25
- package/dist/route/router.js +31 -6
- package/dist/run/consult.js +5 -4
- package/dist/run/daemon.d.ts +15 -0
- package/dist/run/daemon.js +638 -167
- package/dist/run/execution-budget.d.ts +25 -0
- package/dist/run/execution-budget.js +142 -0
- package/dist/run/git.d.ts +50 -1
- package/dist/run/git.js +131 -12
- package/dist/run/journal.d.ts +16 -2
- package/dist/run/journal.js +115 -25
- package/dist/run/lease.d.ts +58 -0
- package/dist/run/lease.js +310 -0
- package/dist/run/merge.d.ts +2 -0
- package/dist/run/merge.js +91 -3
- package/dist/run/operator-state.d.ts +11 -0
- package/dist/run/operator-state.js +17 -3
- package/dist/run/recovery.d.ts +8 -0
- package/dist/run/recovery.js +25 -0
- package/dist/run/repair-selection.d.ts +12 -0
- package/dist/run/repair-selection.js +56 -0
- package/dist/run/stall.d.ts +6 -1
- package/dist/run/stall.js +60 -3
- package/dist/tui/cockpit/board.d.ts +96 -0
- package/dist/tui/cockpit/board.js +346 -0
- package/dist/tui/cockpit/decision-actions.js +2 -0
- package/dist/tui/cockpit/layout.d.ts +5 -1
- package/dist/tui/cockpit/layout.js +8 -3
- package/dist/tui/cockpit/live-runtime.js +71 -21
- package/dist/tui/cockpit/run-view.d.ts +7 -5
- package/dist/tui/cockpit/run-view.js +12 -11
- package/dist/tui/ink/fleet-app.d.ts +49 -27
- package/dist/tui/ink/fleet-app.js +229 -38
- package/package.json +2 -2
- package/skills/tickmarkr-overseer/SKILL.md +173 -113
- package/skills/tickmarkr-overseer/scripts/grade-ci.sh +29 -2
|
@@ -4,6 +4,8 @@ import { useRef, useState } from "react";
|
|
|
4
4
|
import { MODEL_ID_RE } from "../../adapters/types.js";
|
|
5
5
|
import { retiredModelReason } from "../../adapters/model-lints.js";
|
|
6
6
|
import { fleetFirstTouchProvenance } from "../../config/fleet-overlay.js";
|
|
7
|
+
import { exclusionReason } from "../../config/fleet-why.js";
|
|
8
|
+
import { entryMatchesChannel, exclusionCollector } from "../../route/preference.js";
|
|
7
9
|
import { TIERS } from "../../graph/schema.js";
|
|
8
10
|
import { windowRows } from "./components.js";
|
|
9
11
|
import { clip, clipPathTail, ElisionMark, enterAltScreen, fmtCtx, fmtMs, fmtUsdPair, Glyph, INK, inkInput, inkOutput, KeyBar, OverlayPanel, padCell, padCellStart, Pointer, SearchRow, } from "./frame.js";
|
|
@@ -23,7 +25,7 @@ function splitSeat(seat) {
|
|
|
23
25
|
const at = seat.indexOf(":");
|
|
24
26
|
return { adapter: seat.slice(0, at), model: seat.slice(at + 1) };
|
|
25
27
|
}
|
|
26
|
-
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, frameColumns = 100, frameRows, }) {
|
|
28
|
+
export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels, initialAllowOut = [], initialDenyWorkersAdapters = [], initialDenyWorkersModels = [], modelGroups, initialMode, modeOptions, initialMap, modePreview, shapeRows, candidatesForShape, preferOptionsForShape, initialSteering, steeringOptionsFor, reviewOverlay, reloadGuard, stagedRouting, entry = "probe", initialJudge = "", judgeSeats = [], viewRows = Number.POSITIVE_INFINITY, frameColumns = 100, frameRows, }) {
|
|
27
29
|
const { exit } = useApp();
|
|
28
30
|
const [, setRevision] = useState(0);
|
|
29
31
|
const uiRef = useRef(null);
|
|
@@ -40,8 +42,12 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
40
42
|
showAll: false,
|
|
41
43
|
presetsSeen: false,
|
|
42
44
|
notice: "",
|
|
45
|
+
lastEdit: null,
|
|
43
46
|
deny: new Set(initialDenyAdapters),
|
|
44
47
|
denyModels: new Set(initialDenyModels),
|
|
48
|
+
allowOut: new Set(initialAllowOut),
|
|
49
|
+
denyWorkersAdapters: new Set(initialDenyWorkersAdapters),
|
|
50
|
+
denyWorkersModels: new Set(initialDenyWorkersModels),
|
|
45
51
|
classifications: [],
|
|
46
52
|
selectedMode: initialMode,
|
|
47
53
|
map: structuredClone(initialMap),
|
|
@@ -59,6 +65,9 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
59
65
|
// chip, and the empty-`w` notice all read it. Counts staged EDITS, not diff hunks.
|
|
60
66
|
const initialDenySet = new Set(initialDenyAdapters);
|
|
61
67
|
const initialDenyModelSet = new Set(initialDenyModels);
|
|
68
|
+
const initialAllowOutSet = new Set(initialAllowOut);
|
|
69
|
+
const initialDenyWorkersAdapterSet = new Set(initialDenyWorkersAdapters);
|
|
70
|
+
const initialDenyWorkersModelSet = new Set(initialDenyWorkersModels);
|
|
62
71
|
const stagedCount = () => {
|
|
63
72
|
let n = ui.classifications.length
|
|
64
73
|
+ (ui.selectedMode !== initialMode ? 1 : 0)
|
|
@@ -75,6 +84,21 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
75
84
|
for (const model of initialDenyModelSet)
|
|
76
85
|
if (!ui.denyModels.has(model))
|
|
77
86
|
n += 1;
|
|
87
|
+
for (const key of initialAllowOutSet)
|
|
88
|
+
if (!ui.allowOut.has(key))
|
|
89
|
+
n += 1;
|
|
90
|
+
for (const adapter of ui.denyWorkersAdapters)
|
|
91
|
+
if (!initialDenyWorkersAdapterSet.has(adapter))
|
|
92
|
+
n += 1;
|
|
93
|
+
for (const adapter of initialDenyWorkersAdapterSet)
|
|
94
|
+
if (!ui.denyWorkersAdapters.has(adapter))
|
|
95
|
+
n += 1;
|
|
96
|
+
for (const model of ui.denyWorkersModels)
|
|
97
|
+
if (!initialDenyWorkersModelSet.has(model))
|
|
98
|
+
n += 1;
|
|
99
|
+
for (const model of initialDenyWorkersModelSet)
|
|
100
|
+
if (!ui.denyWorkersModels.has(model))
|
|
101
|
+
n += 1;
|
|
78
102
|
for (const shape of new Set([...Object.keys(initialMap), ...Object.keys(ui.map)])) {
|
|
79
103
|
// {} ≡ absent: pin-then-auto leaves an empty entry that writes nothing — not staged work
|
|
80
104
|
if (JSON.stringify(initialMap[shape] ?? {}) !== JSON.stringify(ui.map[shape] ?? {}))
|
|
@@ -87,17 +111,55 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
87
111
|
return n;
|
|
88
112
|
};
|
|
89
113
|
// ── derived data ───────────────────────────────────────────────────────────
|
|
90
|
-
|
|
114
|
+
// OBS-994/FL-1 repair: a flatly (or workers-) denied adapter's channels stay VISIBLE here —
|
|
115
|
+
// every channel needs a reach cell, and Space below can only cycle a scope it can see.
|
|
116
|
+
const enabledGroups = () => modelGroups;
|
|
91
117
|
const scopedGroups = () => {
|
|
92
118
|
if (ui.adapterAt === -1)
|
|
93
119
|
return enabledGroups();
|
|
94
120
|
const group = modelGroups[ui.adapterAt];
|
|
95
|
-
return group
|
|
121
|
+
return group ? [group] : [];
|
|
122
|
+
};
|
|
123
|
+
// LEG2-T3 finding 1: a channel's reach and every reason come from the exclusion collector over
|
|
124
|
+
// the STAGED policy (fleet loads the candidate bytes; see stagedRouting) and the recorded
|
|
125
|
+
// identity — never literal membership of the staged sets, which missed identity, bare-model and
|
|
126
|
+
// family entries and could not tell an allowlist exclusion from a deny.
|
|
127
|
+
let routingMemo = null;
|
|
128
|
+
const stagedPolicy = () => {
|
|
129
|
+
const deny = stagedDeny();
|
|
130
|
+
const key = JSON.stringify(deny);
|
|
131
|
+
if (routingMemo?.key !== key) {
|
|
132
|
+
routingMemo = {
|
|
133
|
+
key,
|
|
134
|
+
policy: stagedRouting
|
|
135
|
+
? stagedRouting(deny)
|
|
136
|
+
: { ok: true, routing: { deny: { adapters: deny.adapters, models: deny.models, workers: { adapters: deny.workersAdapters, models: deny.workersModels } } } },
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
return routingMemo.policy;
|
|
140
|
+
};
|
|
141
|
+
const reachFor = (adapter, model, identity) => {
|
|
142
|
+
const policy = stagedPolicy();
|
|
143
|
+
if (!policy.ok)
|
|
144
|
+
return { reach: "unknown", reasons: [`preview unavailable (${policy.error})`], uncoveredAlias: false };
|
|
145
|
+
const channel = { adapter, model, ...(identity !== undefined ? { identity } : {}) };
|
|
146
|
+
const worker = exclusionCollector(channel, policy.routing, "worker");
|
|
147
|
+
const allSeats = exclusionCollector(channel, policy.routing, "judge");
|
|
148
|
+
const reach = allSeats.some((scope) => scope.by === "deny") ? "out-all"
|
|
149
|
+
: allSeats.length ? "out-allow"
|
|
150
|
+
: worker.length ? "out-workers"
|
|
151
|
+
: "in";
|
|
152
|
+
return {
|
|
153
|
+
reach,
|
|
154
|
+
reasons: worker.map(exclusionReason),
|
|
155
|
+
uncoveredAlias: identity !== undefined && !worker.some((scope) => scope.by === "deny"),
|
|
156
|
+
};
|
|
96
157
|
};
|
|
97
158
|
const groupRows = (group) => {
|
|
98
159
|
const rows = group.rows.map((row) => {
|
|
99
160
|
const staged = ui.classifications.find((classification) => classification.adapter === group.adapter
|
|
100
161
|
&& (classification.model === row.model || classification.model === row.classifyModel));
|
|
162
|
+
const reach = reachFor(group.adapter, row.model, row.evidence?.identity);
|
|
101
163
|
return {
|
|
102
164
|
adapter: group.adapter,
|
|
103
165
|
model: row.model,
|
|
@@ -110,18 +172,21 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
110
172
|
variants: row.variants,
|
|
111
173
|
foldedModels: row.foldedModels,
|
|
112
174
|
score: row.score,
|
|
113
|
-
denied:
|
|
175
|
+
denied: reach.reach !== "in",
|
|
176
|
+
...reach,
|
|
114
177
|
};
|
|
115
178
|
});
|
|
116
179
|
const known = new Set(rows.flatMap((row) => row.classifyModel ? [row.model, row.classifyModel] : [row.model]));
|
|
117
180
|
for (const staged of ui.classifications) {
|
|
118
181
|
if (staged.adapter === group.adapter && !known.has(staged.model)) {
|
|
182
|
+
const reach = reachFor(group.adapter, staged.model);
|
|
119
183
|
rows.push({
|
|
120
184
|
adapter: group.adapter,
|
|
121
185
|
model: staged.model,
|
|
122
186
|
tier: staged.tier,
|
|
123
187
|
channel: group.channel,
|
|
124
|
-
denied:
|
|
188
|
+
denied: reach.reach !== "in",
|
|
189
|
+
...reach,
|
|
125
190
|
});
|
|
126
191
|
}
|
|
127
192
|
}
|
|
@@ -151,8 +216,15 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
151
216
|
return groups.flatMap(groupRows)
|
|
152
217
|
.filter((row) => ui.showAll || row.tier !== undefined || retiredModelReason(row.model) === null).length;
|
|
153
218
|
};
|
|
154
|
-
// three preview callbacks share it in lockstep — the staged deny truth every preview ranks under
|
|
155
|
-
|
|
219
|
+
// three preview callbacks share it in lockstep — the staged deny truth every preview ranks under.
|
|
220
|
+
// LEG2-T3 finding 2: all four scopes travel distinct; the preview loads them as written.
|
|
221
|
+
const stagedDeny = () => ({
|
|
222
|
+
adapters: [...ui.deny].sort(),
|
|
223
|
+
models: [...ui.denyModels].sort(),
|
|
224
|
+
workersAdapters: [...ui.denyWorkersAdapters].sort(),
|
|
225
|
+
workersModels: [...ui.denyWorkersModels].sort(),
|
|
226
|
+
allowOut: [...ui.allowOut].sort(),
|
|
227
|
+
});
|
|
156
228
|
const shapeList = () => shapeRows(ui.selectedMode, ui.map, stagedDeny());
|
|
157
229
|
const steeringList = () => [
|
|
158
230
|
...STEERING_KEYS.map((key) => ({
|
|
@@ -179,6 +251,9 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
179
251
|
const editorState = () => ({
|
|
180
252
|
denyAdapters: [...ui.deny].sort(),
|
|
181
253
|
denyModels: [...ui.denyModels].sort(),
|
|
254
|
+
allowOut: [...ui.allowOut].sort(),
|
|
255
|
+
denyWorkersAdapters: [...ui.denyWorkersAdapters].sort(),
|
|
256
|
+
denyWorkersModels: [...ui.denyWorkersModels].sort(),
|
|
182
257
|
classifications: ui.classifications.map((classification) => classification.vendor && classification.channel
|
|
183
258
|
? {
|
|
184
259
|
...classification,
|
|
@@ -605,12 +680,13 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
605
680
|
}
|
|
606
681
|
if (overlay.kind === "candidates") {
|
|
607
682
|
const rows = overlay.rows.filter((candidate) => matches(candidate.label, ui.filter));
|
|
683
|
+
const ledger = overlay.ledger.filter((line) => matches(line, ui.filter));
|
|
608
684
|
if (key.escape) {
|
|
609
685
|
setOverlay(null);
|
|
610
686
|
return;
|
|
611
687
|
}
|
|
612
688
|
if (key.downArrow) {
|
|
613
|
-
overlay.at = Math.min(overlay.at + 1, Math.max(rows.length - 1, 0));
|
|
689
|
+
overlay.at = Math.min(overlay.at + 1, Math.max(rows.length + ledger.length - 1, 0));
|
|
614
690
|
bump();
|
|
615
691
|
return;
|
|
616
692
|
}
|
|
@@ -644,7 +720,8 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
644
720
|
}
|
|
645
721
|
return;
|
|
646
722
|
}
|
|
647
|
-
editFilter((f) => overlay.rows.filter((candidate) => matches(candidate.label, f)).length
|
|
723
|
+
editFilter((f) => overlay.rows.filter((candidate) => matches(candidate.label, f)).length
|
|
724
|
+
+ overlay.ledger.filter((line) => matches(line, f)).length, (max) => {
|
|
648
725
|
overlay.at = Math.min(overlay.at, max);
|
|
649
726
|
});
|
|
650
727
|
return;
|
|
@@ -870,12 +947,18 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
870
947
|
if (agents.find((agent) => agent.id === id)?.authed === false) {
|
|
871
948
|
ui.notice = `${id} is not authed — Space only toggles fleet membership; re-auth the ${id} CLI, then run tickmarkr doctor`;
|
|
872
949
|
}
|
|
873
|
-
|
|
874
|
-
if (
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
950
|
+
// OBS-1046: an adapter routing.allow leaves out comes back in by clearing THAT reason
|
|
951
|
+
if (ui.allowOut.has(id)) {
|
|
952
|
+
ui.allowOut = new Set([...ui.allowOut].filter((key) => key !== id));
|
|
953
|
+
}
|
|
954
|
+
else {
|
|
955
|
+
const next = new Set(ui.deny);
|
|
956
|
+
if (next.has(id))
|
|
957
|
+
next.delete(id);
|
|
958
|
+
else
|
|
959
|
+
next.add(id);
|
|
960
|
+
ui.deny = next;
|
|
961
|
+
}
|
|
879
962
|
clampList();
|
|
880
963
|
bump();
|
|
881
964
|
return;
|
|
@@ -931,13 +1014,76 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
931
1014
|
beginClassification(row.adapter, row.model);
|
|
932
1015
|
return;
|
|
933
1016
|
}
|
|
1017
|
+
// OBS-994/FL-1: membership can't fix an auth failure either — the row's reach stays
|
|
1018
|
+
// whatever it is and the operator is pointed at doctor instead of a toggle that lies.
|
|
1019
|
+
if (row.evidence?.unauthed !== undefined) {
|
|
1020
|
+
ui.notice = `${row.adapter}:${row.model} is unauthed — re-probe with tickmarkr doctor before its fleet reach can change`;
|
|
1021
|
+
bump();
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
934
1024
|
const id = `${row.adapter}:${row.model}`;
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
1025
|
+
// OBS-994/FL-1, LEG2-T3 finding 3: Space cycles in → out(workers) → out(all) → in by editing
|
|
1026
|
+
// only THIS channel's own entries in the per-model scopes — its adapter:model key, or an
|
|
1027
|
+
// identity/bare-model entry no other browser row matches. An adapter-wide or shared entry
|
|
1028
|
+
// is never promoted or cleared from a channel row: that would move sibling channels and
|
|
1029
|
+
// wipe reasons that are not this row's to clear. The row's detail line names the entry each
|
|
1030
|
+
// press edited; a refused press names the shared entry in the notice instead.
|
|
1031
|
+
const self = { adapter: row.adapter, model: row.model, identity: row.evidence?.identity };
|
|
1032
|
+
const others = modelGroups.flatMap((group) => group.rows
|
|
1033
|
+
.filter((other) => group.adapter !== row.adapter || other.model !== row.model)
|
|
1034
|
+
.map((other) => ({ adapter: group.adapter, model: other.model, identity: other.evidence?.identity })));
|
|
1035
|
+
// Judge c3 (R107): exactly ONE selected entry per press — the channel's adapter:model key when
|
|
1036
|
+
// it is a candidate, else the first candidate in sorted order. The cycle is three states over
|
|
1037
|
+
// two scopes, so the out-workers → out-all press re-scopes that one entry from
|
|
1038
|
+
// routing.deny.workers.models to routing.deny.models; every other press adds or clears one
|
|
1039
|
+
// entry in one scope. The channel's other entries keep their reasons for later presses.
|
|
1040
|
+
// LEG2-T3 round 2 finding 1: promotion widens the selected ban as spelled — a bare model id
|
|
1041
|
+
// then excludes every seat serving that model — while a CLEAR only takes an entry no other
|
|
1042
|
+
// row matches, so no sibling is ever admitted by a channel toggle.
|
|
1043
|
+
const select = (entries, ownOnly) => {
|
|
1044
|
+
const candidates = [...entries].filter((entry) => entryMatchesChannel(entry, self, true)
|
|
1045
|
+
&& (!ownOnly || !others.some((other) => entryMatchesChannel(entry, other, true))));
|
|
1046
|
+
return candidates.includes(id) ? id : candidates.sort()[0];
|
|
1047
|
+
};
|
|
1048
|
+
if (row.reach === "unknown") {
|
|
1049
|
+
ui.notice = `${id} reach is unknown — Space does not toggle it until the staged overlay loads: ${row.reasons.join("; ")}`;
|
|
1050
|
+
bump();
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
if (row.reach === "in") {
|
|
1054
|
+
ui.denyWorkersModels = new Set([...ui.denyWorkersModels, id]);
|
|
1055
|
+
ui.lastEdit = { id, text: `space: added ${id} to routing.deny.workers.models` };
|
|
1056
|
+
}
|
|
1057
|
+
else if (row.reach === "out-workers") {
|
|
1058
|
+
const entry = select(ui.denyWorkersModels, false) ?? id;
|
|
1059
|
+
ui.denyWorkersModels = new Set([...ui.denyWorkersModels].filter((staged) => staged !== entry));
|
|
1060
|
+
ui.denyModels = new Set([...ui.denyModels, entry]);
|
|
1061
|
+
ui.lastEdit = { id, text: `space: moved ${entry} to routing.deny.models` };
|
|
1062
|
+
}
|
|
1063
|
+
else {
|
|
1064
|
+
// round 2 finding 2: either flat list may carry this channel's own reason; OBS-1046: an
|
|
1065
|
+
// allow exclusion is the LAST reason cleared, and never together with an authored deny
|
|
1066
|
+
const fromModels = select(ui.denyModels, true);
|
|
1067
|
+
const fromAdapters = fromModels === undefined ? select(ui.deny, true) : undefined;
|
|
1068
|
+
const fromAllow = fromModels === undefined && fromAdapters === undefined ? select(ui.allowOut, true) : undefined;
|
|
1069
|
+
const entry = fromModels ?? fromAdapters ?? fromAllow;
|
|
1070
|
+
if (entry === undefined) {
|
|
1071
|
+
const shared = ui.deny.has(row.adapter) || ui.allowOut.has(row.adapter)
|
|
1072
|
+
? `every ${row.adapter} channel is out together — take the adapter back in on the rail`
|
|
1073
|
+
: "a shared entry covers other channels too";
|
|
1074
|
+
ui.notice = `${id} stays out — ${row.reasons.join("; ")} — Space edits only this channel's own entries; ${shared}`;
|
|
1075
|
+
bump();
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
1078
|
+
if (fromModels !== undefined)
|
|
1079
|
+
ui.denyModels = new Set([...ui.denyModels].filter((staged) => staged !== entry));
|
|
1080
|
+
else if (fromAdapters !== undefined)
|
|
1081
|
+
ui.deny = new Set([...ui.deny].filter((staged) => staged !== entry));
|
|
1082
|
+
else
|
|
1083
|
+
ui.allowOut = new Set([...ui.allowOut].filter((staged) => staged !== entry));
|
|
1084
|
+
const scope = fromModels !== undefined ? "routing.deny.models" : fromAdapters !== undefined ? "routing.deny.adapters" : "routing.allow";
|
|
1085
|
+
ui.lastEdit = { id, text: `space: cleared ${entry} from ${scope}` };
|
|
1086
|
+
}
|
|
941
1087
|
bump();
|
|
942
1088
|
return;
|
|
943
1089
|
}
|
|
@@ -1051,6 +1197,7 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
1051
1197
|
shape,
|
|
1052
1198
|
rows: picked.rows,
|
|
1053
1199
|
excludedNote: picked.excludedNote,
|
|
1200
|
+
ledger: picked.ledger ?? [],
|
|
1054
1201
|
// OBS-525: an existing pool round-trips — reopening the picker starts from the staged
|
|
1055
1202
|
// channels instead of an empty selection that would silently replace the pool with a pin.
|
|
1056
1203
|
chain: ui.map[shape]?.pool?.channels.slice() ?? [],
|
|
@@ -1116,10 +1263,37 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
1116
1263
|
return "sub";
|
|
1117
1264
|
return usd;
|
|
1118
1265
|
};
|
|
1266
|
+
// OBS-994/FL-1: the reach line — every collector scope that excludes this row, each with its
|
|
1267
|
+
// config path and matching entry (an unauthed row instead points at the doctor re-probe).
|
|
1268
|
+
const reachDetail = (row) => {
|
|
1269
|
+
if (row.evidence?.unauthed !== undefined)
|
|
1270
|
+
return "unauthed — re-probe with tickmarkr doctor (Space does not toggle reach)";
|
|
1271
|
+
const reasons = row.reasons.join("; ");
|
|
1272
|
+
if (row.reach === "out-all")
|
|
1273
|
+
return `reach: out · all seats — ${reasons}`;
|
|
1274
|
+
if (row.reach === "out-allow")
|
|
1275
|
+
return `reach: out · all seats · allow — ${reasons}`;
|
|
1276
|
+
if (row.reach === "out-workers")
|
|
1277
|
+
return `reach: out · workers only (judge/review/consult unaffected) — ${reasons}`;
|
|
1278
|
+
if (row.reach === "unknown")
|
|
1279
|
+
return `reach: unknown — ${reasons}`;
|
|
1280
|
+
return "reach: in — Space cycles to out · workers";
|
|
1281
|
+
};
|
|
1282
|
+
const reachCell = (row) => row.reach === "in" || row.reach === "unknown" ? row.reach : `out ${row.reach === "out-workers" ? "workers" : row.reach.slice(4)}`;
|
|
1283
|
+
const identityDetail = (row) => {
|
|
1284
|
+
if (row.evidence?.identity === undefined)
|
|
1285
|
+
return "";
|
|
1286
|
+
return row.uncoveredAlias
|
|
1287
|
+
? `resolved identity ${row.evidence.identity} — no deny entry covers it`
|
|
1288
|
+
: `resolved identity ${row.evidence.identity}`;
|
|
1289
|
+
};
|
|
1119
1290
|
const modelDetail = (row) => {
|
|
1120
1291
|
const parts = [
|
|
1121
1292
|
`${row.adapter}:${row.model}`,
|
|
1122
1293
|
row.tier ?? "unclassified",
|
|
1294
|
+
ui.lastEdit?.id === `${row.adapter}:${row.model}` ? ui.lastEdit.text : "",
|
|
1295
|
+
identityDetail(row),
|
|
1296
|
+
row.tier ? reachDetail(row) : "",
|
|
1123
1297
|
row.variants?.length ? `variants ${row.variants.join(", ")}` : "",
|
|
1124
1298
|
row.classifyModel ? `classify writes ${row.classifyModel}` : "",
|
|
1125
1299
|
row.foldedModels?.length ? `${row.foldedModels.length} gateway ids folded: ${row.foldedModels.join(", ")}` : "",
|
|
@@ -1138,20 +1312,22 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
1138
1312
|
const tier = tierCell(row);
|
|
1139
1313
|
const showProbe = bodyW >= 76;
|
|
1140
1314
|
const showPrice = bodyW >= 60;
|
|
1141
|
-
// every cell accounted for: pointer 2 + glyph 1 + gap 1 + tier 11 + ctx 6 (+ price 12 + probe 7)
|
|
1142
|
-
const nameW = bodyW - 4 - 11 - 6 - (showPrice ? 12 : 0) - (showProbe ? 7 : 0);
|
|
1315
|
+
// every cell accounted for: pointer 2 + glyph 1 + gap 1 + tier 11 + reach 12 + ctx 6 (+ price 12 + probe 7)
|
|
1316
|
+
const nameW = bodyW - 4 - 11 - 12 - 6 - (showPrice ? 12 : 0) - (showProbe ? 7 : 0);
|
|
1143
1317
|
// OBS-531: deep router ids (omp/prime-agent) clip tail-preserving — the LAST segment is the
|
|
1144
1318
|
// distinguishing half; end-clipping rendered ten identical "prime-agent/anthropic/claude-…" rows.
|
|
1145
1319
|
const folded = row.foldedModels?.length ?? 1;
|
|
1146
1320
|
const name = clipPathTail(`${row.adapter}/${row.model}${folded > 1 ? ` ×${folded}` : ""}`, nameW);
|
|
1147
1321
|
const prefixLen = Math.min(name.length, row.adapter.length + 1);
|
|
1148
|
-
return (_jsxs(Text, { wrap: "truncate", children: [_jsx(Pointer, { on: selected }), row.
|
|
1322
|
+
return (_jsxs(Text, { wrap: "truncate", children: [_jsx(Pointer, { on: selected }), row.reach === "out-all" || row.reach === "out-allow"
|
|
1149
1323
|
? _jsx(Glyph, { kind: "off" })
|
|
1150
|
-
: row.evidence?.unauthed !== undefined
|
|
1324
|
+
: row.evidence?.unauthed !== undefined || row.reach === "unknown"
|
|
1151
1325
|
? _jsx(Glyph, { kind: "fail" })
|
|
1152
|
-
: row.
|
|
1153
|
-
? _jsx(Glyph, { kind: "
|
|
1154
|
-
:
|
|
1326
|
+
: row.reach === "out-workers"
|
|
1327
|
+
? _jsx(Glyph, { kind: "warn" })
|
|
1328
|
+
: row.tier
|
|
1329
|
+
? _jsx(Glyph, { kind: "on" })
|
|
1330
|
+
: _jsx(Glyph, { kind: "unknown" }), _jsx(Text, { children: " " }), _jsxs(Text, { dimColor: row.denied, children: [_jsx(Text, { dimColor: true, children: name.slice(0, prefixLen) }), _jsx(Text, { bold: selected, children: name.slice(prefixLen).padEnd(Math.max(nameW - prefixLen, 0)) })] }), _jsx(Text, { color: tier.color, dimColor: tier.dim, children: padCellStart(tier.text, 11) }), _jsx(Text, { dimColor: row.reach === "in", children: padCellStart(row.tier ? reachCell(row) : "", 12) }), _jsx(Text, { dimColor: true, children: padCellStart(fmtCtx(row.evidence?.contextWindow), 6) }), showPrice && _jsx(Text, { dimColor: priceCell(row) === "sub", children: padCellStart(priceCell(row), 12) }), showProbe && _jsx(Text, { dimColor: true, children: padCellStart(fmtMs(row.evidence?.probeMs), 7) })] }, `${row.adapter}:${row.model}`));
|
|
1155
1331
|
};
|
|
1156
1332
|
const renderPlainRow = (id, label, selected, rowW = bodyW - 2) => (_jsxs(Text, { wrap: "truncate", children: [_jsx(Pointer, { on: selected }), _jsx(Text, { bold: selected, children: clip(label, rowW) })] }, id));
|
|
1157
1333
|
const renderShapeRow = (row, selected, rowW = bodyW - 2) => {
|
|
@@ -1175,7 +1351,7 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
1175
1351
|
const railAdapter = (index, railIndex) => {
|
|
1176
1352
|
const group = modelGroups[index];
|
|
1177
1353
|
const agent = agents.find((candidate) => candidate.id === group.adapter);
|
|
1178
|
-
const denied = ui.deny.has(group.adapter);
|
|
1354
|
+
const denied = ui.deny.has(group.adapter) || ui.allowOut.has(group.adapter);
|
|
1179
1355
|
const active = ui.view === "models" && ui.adapterAt === index;
|
|
1180
1356
|
const selected = ui.focus === "rail" && ui.railAt === railIndex;
|
|
1181
1357
|
return (_jsxs(Text, { children: [selected ? _jsx(Text, { color: INK.brand, children: "❯ " }) : _jsx(Text, { children: " " }), denied ? _jsx(Glyph, { kind: "off" }) : agent?.authed === false ? _jsx(Glyph, { kind: "fail" }) : _jsx(Glyph, { kind: "on" }), _jsx(Text, { children: " " }), _jsx(Text, { bold: active, color: active ? INK.brand : undefined, dimColor: denied, children: padCell(group.adapter, 12) }), _jsx(Text, { dimColor: true, children: padCellStart(String(visibleCount(index)), 4) })] }, group.adapter));
|
|
@@ -1213,12 +1389,24 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
1213
1389
|
}
|
|
1214
1390
|
if (overlay.kind === "candidates") {
|
|
1215
1391
|
const rows = overlay.rows.filter((candidate) => matches(candidate.label, ui.filter));
|
|
1216
|
-
|
|
1392
|
+
// the shape-wide captions, bounded to half the list; per-channel ledger rows are list items below
|
|
1393
|
+
const notes = overlay.excludedNote?.split("\n") ?? [];
|
|
1394
|
+
const noteRoom = Math.max(2, Math.floor(capacity / 2));
|
|
1395
|
+
const noteLines = notes.length > noteRoom
|
|
1396
|
+
? [...notes.slice(0, noteRoom - 1), `… +${notes.length - noteRoom + 1} more notes`]
|
|
1397
|
+
: notes;
|
|
1398
|
+
const items = [
|
|
1399
|
+
...rows.map((candidate) => ({ candidate, line: candidate.label })),
|
|
1400
|
+
...overlay.ledger.filter((line) => matches(line, ui.filter)).map((line) => ({ candidate: undefined, line })),
|
|
1401
|
+
];
|
|
1402
|
+
const { visible, start, above, below } = windowRows(items, overlay.at, Math.max(1, capacity - noteLines.length + 1));
|
|
1217
1403
|
const chained = overlay.chain.length > 0;
|
|
1218
|
-
return (_jsxs(OverlayPanel, { title: chained ? `pool · ${overlay.shape}` : `pin · ${overlay.shape}`, width: bodyW, children: [_jsx(Text, { dimColor: true, wrap: "truncate", children: clip("Enter pins one channel · Space selects a pool in order — Enter then asks its mode (pool replaces pin)", bodyW - 4) }), _jsx(SearchRow, { filter: ui.filter, active: true }),
|
|
1219
|
-
&& _jsx(Text, { dimColor: true, wrap: "truncate", children: clip(overlay.excludedNote, bodyW - 4) }), above > 0 && _jsx(ElisionMark, { count: above, side: "above" }), visible.map((candidate, index) => {
|
|
1220
|
-
const at = overlay.chain.indexOf(candidate.id);
|
|
1404
|
+
return (_jsxs(OverlayPanel, { title: chained ? `pool · ${overlay.shape}` : `pin · ${overlay.shape}`, width: bodyW, children: [_jsx(Text, { dimColor: true, wrap: "truncate", children: clip("Enter pins one channel · Space selects a pool in order — Enter then asks its mode (pool replaces pin)", bodyW - 4) }), _jsx(SearchRow, { filter: ui.filter, active: true }), noteLines.map((line, index) => _jsx(Text, { dimColor: true, wrap: "truncate", children: clip(line, bodyW - 4) }, `excluded-${index}`)), above > 0 && _jsx(ElisionMark, { count: above, side: "above" }), visible.map(({ candidate, line }, index) => {
|
|
1221
1405
|
const selected = start + index === overlay.at;
|
|
1406
|
+
if (candidate === undefined) {
|
|
1407
|
+
return (_jsxs(Text, { wrap: "truncate", children: [_jsx(Pointer, { on: selected }), _jsx(Text, { dimColor: true, children: "✗ " }), _jsx(Text, { bold: selected, dimColor: !selected, children: clip(line, bodyW - 10) })] }, `ledger-${start + index}`));
|
|
1408
|
+
}
|
|
1409
|
+
const at = overlay.chain.indexOf(candidate.id);
|
|
1222
1410
|
return (_jsxs(Text, { wrap: "truncate", children: [_jsx(Pointer, { on: selected }), at === -1 ? _jsx(Text, { dimColor: true, children: "· " }) : _jsx(Text, { color: INK.brand, children: `${at + 1} ` }), _jsx(Text, { bold: selected, children: clip(candidate.label, bodyW - 10) })] }, candidate.id));
|
|
1223
1411
|
}), below > 0 && _jsx(ElisionMark, { count: below, side: "below" })] }));
|
|
1224
1412
|
}
|
|
@@ -1263,8 +1451,9 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
1263
1451
|
const scopeLabel = ui.adapterAt === -1
|
|
1264
1452
|
? "All models"
|
|
1265
1453
|
: modelGroups[ui.adapterAt]?.adapter ?? "";
|
|
1266
|
-
|
|
1267
|
-
|
|
1454
|
+
// OBS-994/FL-1 repair: an adapter-wide deny (flat or workers) no longer hides this
|
|
1455
|
+
// scope's rows — every channel still needs its own reach cell, toggleable right here.
|
|
1456
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, children: scopeLabel }), _jsx(Text, { dimColor: true, children: ` ${rows.length}` })] }), _jsx(SearchRow, { filter: ui.filter, active: ui.searching, hint: "/ to search" }), modelVisibilityLine() && _jsx(Text, { dimColor: true, children: ` ${modelVisibilityLine()}` }), above > 0 && _jsx(ElisionMark, { count: above, side: "above" }), visible.map((row, index) => renderModelRow(row, ui.focus === "list" && start + index === ui.listAt)), below > 0 && _jsx(ElisionMark, { count: below, side: "below", hint: "/ to search" }), rows.length === 0 && _jsx(Text, { dimColor: true, children: " no models match" })] }));
|
|
1268
1457
|
}
|
|
1269
1458
|
if (ui.view === "shapes") {
|
|
1270
1459
|
const rows = shapeList();
|
|
@@ -1281,7 +1470,9 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
1281
1470
|
if (overlay) {
|
|
1282
1471
|
if (overlay.kind === "candidates") {
|
|
1283
1472
|
const rows = overlay.rows.filter((candidate) => matches(candidate.label, ui.filter));
|
|
1284
|
-
|
|
1473
|
+
const ledger = overlay.ledger.filter((line) => matches(line, ui.filter));
|
|
1474
|
+
const line = rows[overlay.at]?.label ?? ledger[overlay.at - rows.length];
|
|
1475
|
+
return line ? [line] : [];
|
|
1285
1476
|
}
|
|
1286
1477
|
if (overlay.kind === "prefer") {
|
|
1287
1478
|
return overlay.chain.length ? [`chain: ${overlay.chain.join(" → ")}`] : [];
|
|
@@ -1405,7 +1596,7 @@ export function FleetApp({ ageMs, agents, initialDenyAdapters, initialDenyModels
|
|
|
1405
1596
|
? _jsx(Text, { color: INK.warn, children: ` ! ${clip(ui.notice, width - 4)}` })
|
|
1406
1597
|
: details.map((line) => _jsx(Text, { dimColor: true, children: ` ${clip(line, width - 2)}` }, line)), _jsx(KeyBar, { binds: keyBinds() })] }));
|
|
1407
1598
|
}
|
|
1408
|
-
export async function runFleetInkEditor({ ageMs, adapters, health, initialDenyAdapters, initialDenyModels, modelGroups, initialMode, modeOptions, initialMap, modePreview, shapeRows, candidatesForShape, preferOptionsForShape, initialSteering, steeringOptionsFor, reviewOverlay, reloadGuard, entry = "probe", initialJudge = "", judgeSeats = [], initialInput = [], input, output, debug = false, }) {
|
|
1599
|
+
export async function runFleetInkEditor({ ageMs, adapters, health, initialDenyAdapters, initialDenyModels, initialAllowOut = [], initialDenyWorkersAdapters = [], initialDenyWorkersModels = [], modelGroups, initialMode, modeOptions, initialMap, modePreview, shapeRows, candidatesForShape, preferOptionsForShape, initialSteering, steeringOptionsFor, reviewOverlay, reloadGuard, stagedRouting, entry = "probe", initialJudge = "", judgeSeats = [], initialInput = [], input, output, debug = false, }) {
|
|
1409
1600
|
const agents = adapters.flatMap((adapter) => {
|
|
1410
1601
|
const state = health[adapter.id];
|
|
1411
1602
|
if (!state?.installed)
|
|
@@ -1429,7 +1620,7 @@ export async function runFleetInkEditor({ ageMs, adapters, health, initialDenyAd
|
|
|
1429
1620
|
// rebuilt from output.columns/rows so a pane resize re-renders at the new size. Same root
|
|
1430
1621
|
// element type on every rerender ⇒ React keeps the component instance, so staged edits,
|
|
1431
1622
|
// cursor, and overlays all survive the resize.
|
|
1432
|
-
const editorElement = () => (_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,
|
|
1623
|
+
const editorElement = () => (_jsx(FleetApp, { ageMs: ageMs, agents: agents, initialDenyAdapters: initialDenyAdapters, initialDenyModels: initialDenyModels, initialAllowOut: initialAllowOut, initialDenyWorkersAdapters: initialDenyWorkersAdapters, initialDenyWorkersModels: initialDenyWorkersModels, modelGroups: declaredModelGroups, initialMode: initialMode, modeOptions: modeOptions, initialMap: initialMap, modePreview: modePreview, shapeRows: shapeRows, candidatesForShape: candidatesForShape, preferOptionsForShape: preferOptionsForShape, initialSteering: initialSteering, steeringOptionsFor: steeringOptionsFor, reviewOverlay: reviewOverlay, reloadGuard: reloadGuard, stagedRouting: stagedRouting, entry: entry, initialJudge: initialJudge, judgeSeats: judgeSeats,
|
|
1433
1624
|
// v1.90.9: cap every list to the terminal minus chrome so the cursor and header can
|
|
1434
1625
|
// never scroll off-screen (omp's 218-model list did exactly that pre-cap).
|
|
1435
1626
|
// OBS-523: chrome is ≤12 rows worst case (header, borders, padding, scope+search,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tickmarkr",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.6",
|
|
4
4
|
"description": "Spec in, verified work out.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"homepage": "https://github.com/alzahrani-khalid/tickmarkr",
|
|
19
19
|
"engines": {
|
|
20
|
-
"node": ">=20"
|
|
20
|
+
"node": ">=20.3"
|
|
21
21
|
},
|
|
22
22
|
"bin": {
|
|
23
23
|
"tickmarkr": "dist/cli/index.js",
|