tickmarkr 2.6.0 → 2.6.2
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 +14 -3
- package/dist/adapters/catalog-remote.js +89 -47
- package/dist/adapters/claude-code.js +9 -6
- package/dist/adapters/codex.js +7 -4
- package/dist/adapters/prompt.d.ts +3 -1
- package/dist/adapters/prompt.js +21 -3
- package/dist/adapters/registry.js +3 -3
- package/dist/adapters/types.d.ts +13 -4
- package/dist/adapters/types.js +16 -0
- package/dist/cli/commands/approve.d.ts +5 -1
- package/dist/cli/commands/approve.js +66 -23
- package/dist/cli/commands/compile.js +13 -3
- package/dist/cli/commands/doctor.d.ts +2 -0
- package/dist/cli/commands/doctor.js +11 -3
- package/dist/cli/commands/fleet.js +71 -8
- package/dist/cli/commands/plan.js +7 -3
- package/dist/cli/commands/report.js +18 -2
- package/dist/cli/commands/resume.js +4 -2
- package/dist/cli/commands/status.js +37 -22
- package/dist/cli/help.d.ts +4 -0
- package/dist/cli/help.js +11 -2
- package/dist/config/config.d.ts +20 -0
- package/dist/config/config.js +47 -8
- package/dist/config/fleet-overlay.d.ts +1 -0
- package/dist/config/fleet-overlay.js +56 -0
- package/dist/drivers/orca.d.ts +26 -1
- package/dist/drivers/orca.js +199 -61
- package/dist/eval/canary.d.ts +2 -1
- package/dist/eval/canary.js +2 -2
- package/dist/eval/dispatch.js +1 -0
- package/dist/gates/acceptance.d.ts +2 -1
- package/dist/gates/acceptance.js +7 -2
- package/dist/gates/baseline.d.ts +12 -1
- package/dist/gates/baseline.js +11 -4
- package/dist/gates/cache.d.ts +3 -1
- package/dist/gates/cache.js +10 -3
- package/dist/gates/llm.d.ts +5 -4
- package/dist/gates/llm.js +17 -14
- package/dist/gates/review.d.ts +8 -0
- package/dist/gates/review.js +47 -15
- package/dist/gates/run-gates.d.ts +6 -1
- package/dist/gates/run-gates.js +34 -17
- package/dist/gates/test-manifest.d.ts +14 -0
- package/dist/gates/test-manifest.js +33 -6
- package/dist/graph/graph.d.ts +6 -2
- package/dist/graph/graph.js +15 -4
- package/dist/graph/schema.d.ts +2 -0
- package/dist/graph/schema.js +2 -0
- package/dist/plan/scope.js +2 -2
- package/dist/route/preference.d.ts +20 -2
- package/dist/route/preference.js +48 -13
- package/dist/route/role-pick.d.ts +16 -0
- package/dist/route/role-pick.js +15 -0
- package/dist/route/router.d.ts +14 -0
- package/dist/route/router.js +39 -16
- package/dist/run/consult.d.ts +13 -1
- package/dist/run/consult.js +19 -14
- package/dist/run/daemon.d.ts +46 -2
- package/dist/run/daemon.js +964 -190
- package/dist/run/git.d.ts +44 -1
- package/dist/run/git.js +103 -5
- package/dist/run/host-health.d.ts +20 -0
- package/dist/run/host-health.js +64 -0
- package/dist/run/journal.d.ts +126 -3
- package/dist/run/journal.js +418 -36
- package/dist/run/merge.d.ts +3 -1
- package/dist/run/merge.js +3 -2
- package/dist/run/operator-state.d.ts +24 -2
- package/dist/run/operator-state.js +41 -5
- package/dist/run/operator-summary.d.ts +3 -0
- package/dist/run/operator-summary.js +3 -1
- package/dist/run/protocol.d.ts +31 -1
- package/dist/run/protocol.js +3 -1
- package/dist/run/stall.d.ts +38 -2
- package/dist/run/stall.js +276 -6
- package/dist/run/supervision.d.ts +7 -1
- package/dist/run/supervision.js +5 -2
- package/dist/tui/cockpit/board.d.ts +1 -1
- package/dist/tui/cockpit/board.js +30 -22
- package/dist/tui/cockpit/decision-actions.d.ts +8 -5
- package/dist/tui/cockpit/decision-actions.js +55 -32
- package/dist/tui/cockpit/derive.d.ts +2 -0
- package/dist/tui/cockpit/derive.js +17 -2
- package/dist/tui/cockpit/live-runtime.d.ts +10 -0
- package/dist/tui/cockpit/live-runtime.js +50 -3
- package/dist/tui/cockpit/live-store.d.ts +1 -0
- package/dist/tui/cockpit/live-store.js +31 -8
- package/dist/tui/cockpit/run-cockpit.d.ts +3 -0
- package/dist/tui/cockpit/run-cockpit.js +28 -2
- package/dist/tui/cockpit/run-view.d.ts +11 -7
- package/dist/tui/cockpit/run-view.js +69 -15
- package/dist/tui/cockpit/setup-cockpit.d.ts +4 -0
- package/dist/tui/cockpit/setup-cockpit.js +6 -3
- package/dist/tui/ink/fleet-app.d.ts +15 -3
- package/dist/tui/ink/fleet-app.js +91 -22
- package/package.json +2 -1
- package/schema/config.schema.json +818 -0
- package/skills/tickmarkr-loop/SKILL.md +8 -2
- package/skills/tickmarkr-overseer/SKILL.md +85 -6
- package/skills/tickmarkr-overseer/scripts/classify-vitest-log.sh +88 -0
- package/skills/tickmarkr-overseer/scripts/context-statusline.sh +81 -0
- package/skills/tickmarkr-overseer/scripts/grade-ci.sh +36 -34
- package/skills/tickmarkr-overseer/scripts/watch-journal.sh +6 -4
|
@@ -5,6 +5,7 @@ import { GLYPHS } from "../../brand.js";
|
|
|
5
5
|
import { formatOwnedName, parseOwnedName } from "../../drivers/types.js";
|
|
6
6
|
import { GATE_NAMES } from "../../graph/schema.js";
|
|
7
7
|
import { projectActivity } from "../../run/activity.js";
|
|
8
|
+
import { effectiveDecisions, withPhysicalLine } from "../../run/journal.js";
|
|
8
9
|
import { projectOperatorSummary } from "../../run/operator-summary.js";
|
|
9
10
|
import { trackJournalRows } from "../../run/protocol.js";
|
|
10
11
|
import { normalizeGateOutcome } from "../../run/outcome.js";
|
|
@@ -53,7 +54,7 @@ export function evidenceLookup(rows, page) {
|
|
|
53
54
|
return lookup;
|
|
54
55
|
}
|
|
55
56
|
export const GATE_CELL_LETTERS = {
|
|
56
|
-
passed: "P", failed: "F", running: "R", "not-run": "-", disabled: "D", unknown: "?",
|
|
57
|
+
passed: "P", failed: "F", queued: "Q", running: "R", "not-run": "-", disabled: "D", unknown: "?",
|
|
57
58
|
};
|
|
58
59
|
/** The outcome selector's vocabulary — a classification of the row, never a word search. */
|
|
59
60
|
export const OUTCOME_FILTERS = ["all", "infra failure", "work failure", "pass", "unknown"];
|
|
@@ -81,10 +82,8 @@ function outcomeLabel(outcome) {
|
|
|
81
82
|
case "unavailable": return `unknown — ${outcome.reason}`;
|
|
82
83
|
}
|
|
83
84
|
}
|
|
84
|
-
/**
|
|
85
|
-
*
|
|
86
|
-
* that give a cell its inherited/satisfied label; only rows for this task after the evidence line count.
|
|
87
|
-
*/
|
|
85
|
+
/** Current-attempt cells in declaration order; later task rows supply inherited/satisfied labels.
|
|
86
|
+
* Only rows after each cell's own evidence line count. */
|
|
88
87
|
export function runGateCells(task, evidence, rows = []) {
|
|
89
88
|
return GATE_NAMES.map((gate) => {
|
|
90
89
|
const cell = task.gates[gate] ?? { state: "unknown" };
|
|
@@ -94,12 +93,15 @@ export function runGateCells(task, evidence, rows = []) {
|
|
|
94
93
|
const labels = [];
|
|
95
94
|
let outcome;
|
|
96
95
|
if (data === undefined) {
|
|
97
|
-
labels.push(line === undefined ? { "not-run": "not run", disabled: "disabled by policy", running: "running", unknown: "unknown", passed: "passed", failed: "failed" }[cell.state] : `evidence #L${line} unavailable`);
|
|
96
|
+
labels.push(line === undefined ? { "not-run": "not run", disabled: "disabled by policy", queued: "queued", running: "running", unknown: "unknown", passed: "passed", failed: "failed" }[cell.state] : `evidence #L${line} unavailable`);
|
|
97
|
+
}
|
|
98
|
+
else if (cell.state === "queued") {
|
|
99
|
+
labels.push(`queued — ${row?.event?.event ?? "wait"}${typeof data.count === "number" ? ` (${data.count} suites)` : ""}`);
|
|
98
100
|
}
|
|
99
101
|
else if (data.disabled === true) {
|
|
100
102
|
labels.push("disabled by policy");
|
|
101
103
|
}
|
|
102
|
-
else if (row?.event?.event
|
|
104
|
+
else if (["gate-start", "gate-phase-start", "phase-start"].includes(row?.event?.event ?? "")) {
|
|
103
105
|
labels.push("running");
|
|
104
106
|
}
|
|
105
107
|
else {
|
|
@@ -121,10 +123,10 @@ export function runGateCells(task, evidence, rows = []) {
|
|
|
121
123
|
const e = later.event;
|
|
122
124
|
if (e.event === "gate-reused" && e.data.gate === gate)
|
|
123
125
|
labels.push(`inherited from ${str(e.data.commit) ?? "unknown commit"} · #L${later.line}`);
|
|
124
|
-
if (e.event === "task-approved" && e.data.release === "gate-satisfied" && e.data.gate === gate)
|
|
126
|
+
if (e.event === "task-approved" && e.data.release === "gate-satisfied" && e.data.gate === gate && !undecidedLines(evidence.later(0)).has(later.line))
|
|
125
127
|
labels.push(`satisfied by approval #L${later.line}`);
|
|
126
128
|
}
|
|
127
|
-
const verdict = typeof data?.details === "string" ? data.details.split("\n") : [];
|
|
129
|
+
const verdict = row?.event?.event === "gate-result" && typeof data?.details === "string" ? data.details.split("\n") : [];
|
|
128
130
|
return { gate, state: cell.state, letter: GATE_CELL_LETTERS[cell.state], ...(line === undefined ? {} : { line }), ...(outcome ? { outcome } : {}), outcomeClass: outcomeClassOf(outcome, cell.state), labels, verdict };
|
|
129
131
|
});
|
|
130
132
|
}
|
|
@@ -190,7 +192,7 @@ const readLabel = (f) => `${f.label} ${f.line === undefined ? `(${NO_ROW})` : `#
|
|
|
190
192
|
* Nothing is inferred from a title, a provider or a task id: an unrecorded field says so.
|
|
191
193
|
*/
|
|
192
194
|
export function projectRunTasks(snapshot, rows, graph, decisions, runId) {
|
|
193
|
-
const ordered =
|
|
195
|
+
const ordered = decidedRows(rows);
|
|
194
196
|
const graphTask = (id) => graph?.tasks.find((t) => t.id === id);
|
|
195
197
|
const activityTasks = snapshot.tasks.map((t) => ({ id: t.id, gates: graphTask(t.id)?.gates ?? [...GATE_NAMES], deps: graphTask(t.id)?.deps ?? [], status: t.state }));
|
|
196
198
|
const tracked = trackJournalRows(runId, ordered.map((r) => ({ raw: r.event, sourceIndex: r.line })));
|
|
@@ -207,7 +209,16 @@ export function projectRunTasks(snapshot, rows, graph, decisions, runId) {
|
|
|
207
209
|
...(own.length === 0 ? {} : { phase: activity.get(t.id)?.state, lastEvidenceAt: own.at(-1).event.ts }),
|
|
208
210
|
...(responsibility ? { responsible: { role: str(responsibility.event.data.role), agent: str(responsibility.event.data.agent) } } : {}),
|
|
209
211
|
};
|
|
210
|
-
}), decisions.map((d) => ({
|
|
212
|
+
}), decisions.map((d) => ({
|
|
213
|
+
taskId: d.taskId,
|
|
214
|
+
park: {
|
|
215
|
+
kind: d.park.kind, tombstone: d.park.tombstone, line: d.park.line,
|
|
216
|
+
...(d.park.ts === undefined ? {} : { ts: d.park.ts }),
|
|
217
|
+
...(d.park.reason === undefined ? {} : { reason: d.park.reason }),
|
|
218
|
+
},
|
|
219
|
+
verbs: d.verbs,
|
|
220
|
+
...(d.diagnostic === undefined ? {} : { diagnostic: d.diagnostic }),
|
|
221
|
+
}))).map((s) => [s.taskId, s]));
|
|
211
222
|
return snapshot.tasks.map((t) => {
|
|
212
223
|
const own = byTask(t.id);
|
|
213
224
|
const act = activity.get(t.id);
|
|
@@ -271,7 +282,7 @@ export function projectRunTasks(snapshot, rows, graph, decisions, runId) {
|
|
|
271
282
|
const stalled = launched !== undefined && !returned && nudges.length > 0 && pages.length > 0
|
|
272
283
|
? { label: `${STALL_MARKER} · launch #L${launched} · nudge failed ${nudges.map((l) => `#L${l}`).join(",")} · paged ${pages.map((l) => `#L${l}`).join(",")} · no worker-result`, line: launched }
|
|
273
284
|
: undefined;
|
|
274
|
-
return { taskId: t.id, identity, phase, build, blocker, nextAction, ...(stalled ? { stalled } : {}) };
|
|
285
|
+
return { taskId: t.id, identity, phase, build, blocker, nextAction, ...(stalled ? { stalled } : {}), ...(t.dispatches === 0 ? { neverDispatched: true } : {}) };
|
|
275
286
|
});
|
|
276
287
|
}
|
|
277
288
|
/** The line at which each projected value last changed, folding the task's rows (and run-level resets) prefix by prefix. */
|
|
@@ -298,8 +309,16 @@ function acceptedChanges(runId, tracked, taskId, task) {
|
|
|
298
309
|
}
|
|
299
310
|
return out;
|
|
300
311
|
}
|
|
301
|
-
/**
|
|
302
|
-
|
|
312
|
+
/**
|
|
313
|
+
* OBS-1104: a never-dispatched task has no identity, phase, or build row. Those fold into one
|
|
314
|
+
* missing-evidence clause. A substantive or journal-cited blocker and next action stay — a
|
|
315
|
+
* pre-dispatch park and a dependency wait are recorded facts, and dropping them hides the park token.
|
|
316
|
+
*/
|
|
317
|
+
export const MISSING_EVIDENCE = "missing evidence";
|
|
318
|
+
const UNRECORDED_BLOCKER = new Set(["blocker none", "blocker unknown"]);
|
|
319
|
+
const UNRECORDED_NEXT = new Set(["next none"]);
|
|
320
|
+
const keptWhenUndispatched = (field, unrecorded) => field.line !== undefined || !unrecorded.has(field.label);
|
|
321
|
+
export const projectionLine = (p) => p.neverDispatched === true ? undispatchedProjectionLine(p) : recordedProjectionLine(p);
|
|
303
322
|
/**
|
|
304
323
|
* Where the current attempt's pane is recorded — and only that. The reading is exact-name: the
|
|
305
324
|
* launch row's slot name must be the owned name of THIS task, attempt and run. No terminal is ever
|
|
@@ -357,6 +376,29 @@ export function paneLocator(task, rows, runId) {
|
|
|
357
376
|
}
|
|
358
377
|
/** The board's footer keys merged with the cockpit's own — every one of them handled by this view or the shell. */
|
|
359
378
|
export const RUN_VIEW_KEYS = [...BOARD_KEYS.slice(0, 2), "←→ verdict gate", "PgUp/PgDn page", "x outcome", "a actions", "o pane", "Tab focus", "? keys", BOARD_KEYS[2]];
|
|
379
|
+
function recordedProjectionLine(p) {
|
|
380
|
+
return [p.taskId, readLabel(p.identity), readLabel(p.phase), readLabel(p.build), readLabel(p.blocker), readLabel(p.nextAction), ...(p.stalled ? [p.stalled.label] : [])].join(" · ");
|
|
381
|
+
}
|
|
382
|
+
function undispatchedProjectionLine(p) {
|
|
383
|
+
return [
|
|
384
|
+
p.taskId,
|
|
385
|
+
MISSING_EVIDENCE,
|
|
386
|
+
...(keptWhenUndispatched(p.blocker, UNRECORDED_BLOCKER) ? [readLabel(p.blocker)] : []),
|
|
387
|
+
...(keptWhenUndispatched(p.nextAction, UNRECORDED_NEXT) ? [readLabel(p.nextAction)] : []),
|
|
388
|
+
...(p.stalled ? [p.stalled.label] : []),
|
|
389
|
+
].join(" · ");
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Rows the projection panel paints. A never-dispatched clause is shorter than the field line it
|
|
393
|
+
* replaces; the shell's content counter is the panel's wrapped height, and the pinned run frames
|
|
394
|
+
* record that counter. Blank rows keep the block as tall as the field lines were.
|
|
395
|
+
*/
|
|
396
|
+
function projectionBlockRows(projections, wrap) {
|
|
397
|
+
const shown = projections.flatMap((p) => wrap(projectionLine(p)).map((line, i) => ({ key: `${p.taskId}:${i}`, line, strong: p.stalled !== undefined && i === 0 })));
|
|
398
|
+
const prior = projections.flatMap((p) => wrap(recordedProjectionLine(p)));
|
|
399
|
+
const pad = Math.max(0, prior.length - shown.length);
|
|
400
|
+
return [...shown, ...Array.from({ length: pad }, (_, i) => ({ key: `projection-pad:${i}`, line: " ", strong: false }))];
|
|
401
|
+
}
|
|
360
402
|
/** The Run body: the approved board (BD-1) over the fold, then the selected task's detail panels. */
|
|
361
403
|
export function RunView({ snapshot, rows, page, graph, decisions, session, columns, run, now = Date.now }) {
|
|
362
404
|
const inner = Math.max(1, Math.floor(columns) - 4);
|
|
@@ -382,9 +424,21 @@ export function RunView({ snapshot, rows, page, graph, decisions, session, colum
|
|
|
382
424
|
const { menu, confirming, receipt, notice } = session.decisions;
|
|
383
425
|
const blocked = task === undefined ? [] : graph?.tasks.filter((g) => g.deps.includes(task.id)).map((g) => g.id) ?? decision?.blocks ?? [];
|
|
384
426
|
const board = renderBoardLines({ runId: run.runId, snapshot, graph, now: now(), live: run.live, selection: task?.id, keys: false, colour }, width);
|
|
385
|
-
return (_jsxs(Box, { flexDirection: "column", width: columns, children: [_jsx(BodyText, { emphasis: "dim", children: fitCells(`RUN / ${snapshot.lifecycle} · ${runSummaryLine(snapshot)}`, width) }), board.map((line, i) => _jsx(BodyText, { children: line || " " }, `${i}:${line}`)), tasks.length === 0 && _jsx(BodyText, { children: fitCells("no tasks recorded — no plan", width) }), task !== undefined && (_jsxs(Panel, { title: `SELECTED / ${task.id}${task.title ? ` · ${task.title}` : ""}`, children: [wrap(`state ${parkOf(task)} · ${attemptLabel(task)} · dispatches ${task.dispatches} · path ${task.path ?? "unknown"} · pane ${task.pane ?? "unknown"} · alarm ${task.alarmMs === undefined ? "unknown" : `${task.alarmMs}ms`}${task.mergeEvidence ? ` · merged #L${task.mergeEvidence.line}` : ""} · locator ${(() => { const l = paneLocator(task, allRows, run.runId); return l.status === "recorded" ? `recorded ${l.reason}` : `unavailable — ${l.reason}`; })()}`).map((line, i) => _jsx(BodyText, { children: line }, `${i}:${line}`)), _jsx(BodyText, { emphasis: "dim", children: fit(`outcome filter ${session.outcomeFilter} (x cycles) · ${shown.length} of ${cells.length} gates shown`) }), shown.map((c) => (_jsx(BodyText, { children: fit(`${c.letter} ${c.gate.padEnd(10)} ${c.labels.join(" · ")}${c.line === undefined ? "" : ` · #L${c.line}`}`) }, c.gate)))] })), projections.length > 0 && (_jsx(Panel, { title: "PROJECTION / every task \u00B7 identity \u00B7 phase \u00B7 build \u00B7 blocker \u00B7 next action, each with its journal line", children: projections
|
|
427
|
+
return (_jsxs(Box, { flexDirection: "column", width: columns, children: [_jsx(BodyText, { emphasis: "dim", children: fitCells(`RUN / ${snapshot.lifecycle} · ${runSummaryLine(snapshot)}`, width) }), board.map((line, i) => _jsx(BodyText, { children: line || " " }, `${i}:${line}`)), tasks.length === 0 && _jsx(BodyText, { children: fitCells("no tasks recorded — no plan", width) }), task !== undefined && (_jsxs(Panel, { title: `SELECTED / ${task.id}${task.title ? ` · ${task.title}` : ""}`, children: [wrap(`state ${parkOf(task)} · ${attemptLabel(task)} · dispatches ${task.dispatches} · path ${task.path ?? "unknown"} · pane ${task.pane ?? "unknown"} · alarm ${task.alarmMs === undefined ? "unknown" : `${task.alarmMs}ms`}${task.mergeEvidence ? ` · merged #L${task.mergeEvidence.line}` : ""} · locator ${(() => { const l = paneLocator(task, allRows, run.runId); return l.status === "recorded" ? `recorded ${l.reason}` : `unavailable — ${l.reason}`; })()}`).map((line, i) => _jsx(BodyText, { children: line }, `${i}:${line}`)), _jsx(BodyText, { emphasis: "dim", children: fit(`outcome filter ${session.outcomeFilter} (x cycles) · ${shown.length} of ${cells.length} gates shown`) }), shown.map((c) => (_jsx(BodyText, { children: fit(`${c.letter} ${c.gate.padEnd(10)} ${c.labels.join(" · ")}${c.line === undefined ? "" : ` · #L${c.line}`}`) }, c.gate)))] })), projections.length > 0 && (_jsx(Panel, { title: "PROJECTION / every task \u00B7 identity \u00B7 phase \u00B7 build \u00B7 blocker \u00B7 next action, each with its journal line", children: projectionBlockRows(projections, wrap).map((row) => _jsx(BodyText, { emphasis: row.strong ? "strong" : "normal", children: row.line }, row.key)) })), verdictCell !== undefined && (_jsxs(Panel, { title: `VERDICT / ${verdictCell.gate}${verdictCell.line === undefined ? "" : ` #L${verdictCell.line}`} · ${verdict.length === 0 ? "no verdict text" : `lines ${from + 1}–${from + window.length} of ${verdict.length}`}`, children: [window.map((line, i) => _jsx(BodyText, { children: fit(`${String(from + i + 1).padStart(4)} ${line}`) }, `${from + i}:${line}`)), _jsx(BodyText, { emphasis: "dim", children: fit("←→ gate · PageUp/PageDown page") })] })), task?.state === "human" && (_jsx(Panel, { title: `PARK / ${task.id}`, children: decision === undefined
|
|
386
428
|
? _jsx(BodyText, { children: fit("park evidence unavailable — refresh before deciding") })
|
|
387
429
|
: (_jsxs(_Fragment, { children: [wrap(`#L${decision.park.line} ${decision.park.kind ?? "unknown kind"}${decision.park.failedGate ? ` · failed gate ${decision.park.failedGate}` : ""} · ${decision.park.reason ?? "no reason recorded"}`).map((line, i) => _jsx(BodyText, { children: line }, `park:${i}`)), _jsx(BodyText, { children: fit(`blocks ${blocked.length === 0 ? "nothing" : blocked.join(", ")} · attempts ${decision.attempts}`) }), wrap(decision.verbs.length === 0 ? `diagnostic: ${decision.diagnostic ?? "no verb"}` : `decisions: ${decision.verbs.join(" · ")} (a Actions)`).map((line, i) => _jsx(BodyText, { children: line }, `verbs:${i}`)), _jsx(BodyText, { emphasis: "dim", children: fit(run.live ? "matching live daemon enacts a release at its next task boundary" : run.blockingRunId ? `other live run ${run.blockingRunId} holds the lock; resume after it ends` : "no live owner: a release records permission; resume dispatches") })] })) })), menu !== null && (_jsx(Panel, { title: `ACTIONS / ${menu.taskId}`, focused: true, children: menu.verbs.length === 0
|
|
388
430
|
? _jsx(BodyText, { children: fit(`no decision — ${decision?.diagnostic ?? "no verb for this park"}`) })
|
|
389
431
|
: menu.verbs.map((verb, i) => _jsx(BodyText, { emphasis: i === menu.selection ? "strong" : "normal", children: fit(`${i === menu.selection ? `${GLYPHS.pointer} ` : " "}${verb}`) }, verb)) })), confirming !== null && (_jsx(Panel, { title: `CONFIRM ${confirming.command.verb.toUpperCase()}`, focused: true, children: decisionLines(decisionConfirmLines(confirming)).map((line, i) => _jsx(BodyText, { children: line }, `${i}:${line}`)) })), receipt !== null && (_jsx(Panel, { title: receipt.ok ? "RECEIPT · appended" : "RECEIPT · refused", children: decisionLines(decisionReceiptLines(receipt)).map((line, i) => _jsx(BodyText, { children: line }, `${i}:${line}`)) })), notice !== null && _jsx(BodyText, { children: notice }), _jsx(BodyText, { emphasis: "dim", children: clipBoard(boardFooter([...RUN_VIEW_KEYS, decisionKeybar(session.decisions, decision)].filter(Boolean), colour), width) })] }));
|
|
390
432
|
}
|
|
433
|
+
// OBS-1178: a refused or unsound decision released and satisfied nothing, so neither the projections nor
|
|
434
|
+
// a gate cell's satisfied label read it; the one decision fold judges every row by its physical line.
|
|
435
|
+
function undecidedLines(rows) {
|
|
436
|
+
const ordered = rows.filter((r) => r.event !== undefined).sort((a, b) => a.line - b.line);
|
|
437
|
+
const effective = effectiveDecisions(ordered.map((r) => withPhysicalLine(r.event, r.line)));
|
|
438
|
+
return new Set(ordered.filter((r) => r.event.event === "task-approved" && !effective.has(r.line)).map((r) => r.line));
|
|
439
|
+
}
|
|
440
|
+
/** Readable rows in physical-line order, minus the decisions that are not effective. */
|
|
441
|
+
function decidedRows(rows) {
|
|
442
|
+
const undecided = undecidedLines(rows);
|
|
443
|
+
return rows.filter((r) => r.event !== undefined && !undecided.has(r.line)).sort((a, b) => a.line - b.line);
|
|
444
|
+
}
|
|
@@ -71,6 +71,8 @@ export type ParkedDecision = {
|
|
|
71
71
|
* so it is drawn read-only and neither verb is offered on it.
|
|
72
72
|
*/
|
|
73
73
|
readonly tombstone: boolean;
|
|
74
|
+
/** OBS-1178: the park's `<line>@<ts>` token — the confirmed write binds to it via `--park`. */
|
|
75
|
+
readonly park?: string;
|
|
74
76
|
};
|
|
75
77
|
/** The parks a verb can release — the rows the surface draws live verbs on. */
|
|
76
78
|
export declare function actionableDecisions(decisions: readonly ParkedDecision[]): readonly ParkedDecision[];
|
|
@@ -80,6 +82,8 @@ export type SetupDecisionVerb = "approve" | "waive" | "uphold" | "recheck";
|
|
|
80
82
|
export type SetupDecisionCommand = {
|
|
81
83
|
readonly verb: SetupDecisionVerb;
|
|
82
84
|
readonly taskId: string;
|
|
85
|
+
/** OBS-1178: the park token the confirm inset displayed — approve refuses it once a newer park opens. */
|
|
86
|
+
readonly park?: string;
|
|
83
87
|
};
|
|
84
88
|
/** What the journal file gained this session — one entry per confirmed write. */
|
|
85
89
|
export type SetupDecisionWrite = {
|
|
@@ -5,7 +5,7 @@ import { join } from "node:path";
|
|
|
5
5
|
import { cloneElement, createElement, useState, } from "react";
|
|
6
6
|
import { parse } from "yaml";
|
|
7
7
|
import { approvalDispositionForRelease, approvalEnactment, approvalRunOwner, approve, } from "../../cli/commands/approve.js";
|
|
8
|
-
import { ATTEMPT_CAP_RELEASE, GATE_SATISFIED_RELEASE, Journal, RECHECK_RELEASE, REVIEW_UPHELD_RELEASE, } from "../../run/journal.js";
|
|
8
|
+
import { ATTEMPT_CAP_RELEASE, bindingToken, GATE_SATISFIED_RELEASE, Journal, RECHECK_RELEASE, REVIEW_UPHELD_RELEASE, } from "../../run/journal.js";
|
|
9
9
|
import { BANNER, GLYPHS, PLAIN_BANNER, } from "../../brand.js";
|
|
10
10
|
import { DEFAULT_CONFIG, INTEGRITY_FLOOR_SHAPES, TIER_RANK, TickmarkrConfigSchema, unifiedYamlDiff, } from "../../config/config.js";
|
|
11
11
|
import { GATE_NAMES, SHAPES } from "../../graph/schema.js";
|
|
@@ -576,7 +576,7 @@ function failedGateForNewestPark(events, taskId, parkedIndex) {
|
|
|
576
576
|
}
|
|
577
577
|
export function deriveParkedDecisions(journal) {
|
|
578
578
|
const statuses = journal.replayStatuses();
|
|
579
|
-
const events = journal.
|
|
579
|
+
const { events, lines } = journal.readSourced();
|
|
580
580
|
const decisions = [];
|
|
581
581
|
for (const [taskId, status] of statuses) {
|
|
582
582
|
if (status !== "human")
|
|
@@ -606,6 +606,7 @@ export function deriveParkedDecisions(journal) {
|
|
|
606
606
|
attempts,
|
|
607
607
|
failedGate,
|
|
608
608
|
tombstone: isTombstonePark(kind, reason),
|
|
609
|
+
...(typeof parked?.ts === "string" ? { park: bindingToken({ line: lines[parkedIndex], ts: parked.ts }) } : {}),
|
|
609
610
|
});
|
|
610
611
|
}
|
|
611
612
|
return decisions;
|
|
@@ -677,7 +678,7 @@ export function applySetupDecisionsKey(session, event, parked) {
|
|
|
677
678
|
session: {
|
|
678
679
|
...session,
|
|
679
680
|
selection,
|
|
680
|
-
confirming: { verb, taskId: decision.taskId },
|
|
681
|
+
confirming: { verb, taskId: decision.taskId, ...(decision.park === undefined ? {} : { park: decision.park }) },
|
|
681
682
|
},
|
|
682
683
|
};
|
|
683
684
|
}
|
|
@@ -704,6 +705,7 @@ export async function executeSetupDecision(command, { cwd, runId, by }) {
|
|
|
704
705
|
runId,
|
|
705
706
|
command.taskId,
|
|
706
707
|
...decisionFlag(command.verb),
|
|
708
|
+
...(command.park === undefined ? [] : ["--park", command.park]),
|
|
707
709
|
"--by",
|
|
708
710
|
by,
|
|
709
711
|
], cwd);
|
|
@@ -800,6 +802,7 @@ function decisionEffectLines(command, decision, run) {
|
|
|
800
802
|
export function setupDecisionConfirmLines(command, decision, actor, journalFile, run) {
|
|
801
803
|
return [
|
|
802
804
|
`task ${command.taskId}${decision === undefined ? "" : ` · ${decision.kind} · attempt ${decision.attempts}`}`,
|
|
805
|
+
...(command.park === undefined ? [] : [`park ${command.park} · bound; refused if a newer park opens`]),
|
|
803
806
|
`actor ${actor}`,
|
|
804
807
|
...decisionEffectLines(command, decision, run),
|
|
805
808
|
`file ${journalFile} · append only`,
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { type AuthHealth, type WorkerAdapter } from "../../adapters/types.js";
|
|
2
2
|
import { type DenyScopeKey, type InitialDenyProps, type MapEntry, type RoutingMode, type StagedDenyKey, type Tier, type TickmarkrConfig } from "../../config/config.js";
|
|
3
|
-
import { type Shape } from "../../graph/schema.js";
|
|
3
|
+
import { type Effort, type Shape } from "../../graph/schema.js";
|
|
4
4
|
export type FleetSteeringKey = "review" | "consult";
|
|
5
5
|
export type FleetEditorState = Record<DenyScopeKey, string[]> & {
|
|
6
6
|
/** OBS-1046: the staged routing.allow complement, distinct from the authored deny lists above */
|
|
7
7
|
allowOut?: string[];
|
|
8
8
|
classifications: FleetClassification[];
|
|
9
|
+
/** OBS-1182: staged launch effort per adapter → model; absent = the CLI's own default */
|
|
10
|
+
efforts?: Record<string, Record<string, Effort>>;
|
|
9
11
|
selectedMode: RoutingMode;
|
|
10
12
|
map: Record<string, MapEntry>;
|
|
11
13
|
judgeSeat?: {
|
|
@@ -22,6 +24,8 @@ export type FleetOverlayReview = {
|
|
|
22
24
|
after: string;
|
|
23
25
|
diff: string;
|
|
24
26
|
path: string;
|
|
27
|
+
/** OBS-1144: dead pool entries the written config would carry, each with its reason. */
|
|
28
|
+
notes?: string[];
|
|
25
29
|
};
|
|
26
30
|
export type FleetEditorResult = {
|
|
27
31
|
kind: "write";
|
|
@@ -121,11 +125,13 @@ export type FleetStagedRouting = {
|
|
|
121
125
|
ok: false;
|
|
122
126
|
error: string;
|
|
123
127
|
};
|
|
124
|
-
export declare function FleetApp({ ageMs, agents, initialAllowOut, modelGroups, initialMode, modeOptions, initialMap, modePreview, shapeRows, candidatesForShape, preferOptionsForShape, initialSteering, steeringOptionsFor, reviewOverlay, reloadGuard, stagedRouting, entry, initialJudge, judgeSeats, viewRows, frameColumns, frameRows, ...initialDenyProps }: InitialDenyProps & {
|
|
128
|
+
export declare function FleetApp({ ageMs, agents, initialAllowOut, initialEfforts, modelGroups, initialMode, modeOptions, initialMap, modePreview, shapeRows, candidatesForShape, preferOptionsForShape, initialSteering, steeringOptionsFor, reviewAdvisories, reviewOverlay, reloadGuard, stagedRouting, entry, initialJudge, judgeSeats, viewRows, frameColumns, frameRows, ...initialDenyProps }: InitialDenyProps & {
|
|
125
129
|
ageMs: number | null;
|
|
126
130
|
agents: AgentCli[];
|
|
127
131
|
/** OBS-1046: adapter ids and adapter:model keys routing.allow leaves out of the fleet */
|
|
128
132
|
initialAllowOut?: string[];
|
|
133
|
+
/** OBS-1182: configured launch effort per adapter → model */
|
|
134
|
+
initialEfforts?: Record<string, Record<string, Effort>>;
|
|
129
135
|
modelGroups: FleetModelGroup[];
|
|
130
136
|
initialMode: RoutingMode;
|
|
131
137
|
modeOptions: FleetModeOption[];
|
|
@@ -140,6 +146,8 @@ export declare function FleetApp({ ageMs, agents, initialAllowOut, modelGroups,
|
|
|
140
146
|
preferOptionsForShape: (shape: Shape, current: string[]) => string[];
|
|
141
147
|
initialSteering: Record<FleetSteeringKey, string[] | undefined>;
|
|
142
148
|
steeringOptionsFor: (which: FleetSteeringKey, current: string[]) => string[];
|
|
149
|
+
/** OBS-1052(3): advisory review no-verdict history lines — display only, never a routing input */
|
|
150
|
+
reviewAdvisories?: string[];
|
|
143
151
|
reviewOverlay: (state: FleetEditorState) => FleetOverlayReview;
|
|
144
152
|
reloadGuard: (bytes: string) => string | null;
|
|
145
153
|
/** the routing policy the staged deny sets load as (fleet: the config loader over the candidate
|
|
@@ -157,12 +165,14 @@ export declare function FleetApp({ ageMs, agents, initialAllowOut, modelGroups,
|
|
|
157
165
|
/** terminal height — set only on a real TTY so the footer sticks to the bottom */
|
|
158
166
|
frameRows?: number;
|
|
159
167
|
}): import("react").JSX.Element;
|
|
160
|
-
export declare function runFleetInkEditor({ ageMs, adapters, health, initialAllowOut, modelGroups, initialMode, modeOptions, initialMap, modePreview, shapeRows, candidatesForShape, preferOptionsForShape, initialSteering, steeringOptionsFor, reviewOverlay, reloadGuard, stagedRouting, entry, initialJudge, judgeSeats, initialInput, input, output, debug, ...initialDenyProps }: InitialDenyProps & {
|
|
168
|
+
export declare function runFleetInkEditor({ ageMs, adapters, health, initialAllowOut, initialEfforts, modelGroups, initialMode, modeOptions, initialMap, modePreview, shapeRows, candidatesForShape, preferOptionsForShape, initialSteering, steeringOptionsFor, reviewAdvisories, reviewOverlay, reloadGuard, stagedRouting, entry, initialJudge, judgeSeats, initialInput, input, output, debug, ...initialDenyProps }: InitialDenyProps & {
|
|
161
169
|
ageMs: number | null;
|
|
162
170
|
adapters: WorkerAdapter[];
|
|
163
171
|
health: Record<string, AuthHealth>;
|
|
164
172
|
/** OBS-1046: adapter ids and adapter:model keys routing.allow leaves out of the fleet */
|
|
165
173
|
initialAllowOut?: string[];
|
|
174
|
+
/** OBS-1182: configured launch effort per adapter → model */
|
|
175
|
+
initialEfforts?: Record<string, Record<string, Effort>>;
|
|
166
176
|
modelGroups: FleetModelGroup[];
|
|
167
177
|
initialMode: RoutingMode;
|
|
168
178
|
modeOptions: FleetModeOption[];
|
|
@@ -177,6 +187,8 @@ export declare function runFleetInkEditor({ ageMs, adapters, health, initialAllo
|
|
|
177
187
|
preferOptionsForShape: (shape: Shape, current: string[]) => string[];
|
|
178
188
|
initialSteering: Record<FleetSteeringKey, string[] | undefined>;
|
|
179
189
|
steeringOptionsFor: (which: FleetSteeringKey, current: string[]) => string[];
|
|
190
|
+
/** OBS-1052(3): advisory review no-verdict history lines — display only, never a routing input */
|
|
191
|
+
reviewAdvisories?: string[];
|
|
180
192
|
reviewOverlay: (state: FleetEditorState) => FleetOverlayReview;
|
|
181
193
|
reloadGuard: (bytes: string) => string | null;
|
|
182
194
|
/** the routing policy the staged deny sets load as (fleet: the config loader over the candidate
|
|
@@ -3,12 +3,13 @@ import { Box, render, Text, useApp, useInput } from "ink";
|
|
|
3
3
|
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
|
-
import { DENY_SCOPES, denyBlockFrom, denyEntriesAt, initialDenyPropOf, stagedDenyKeyOf } from "../../config/config.js";
|
|
6
|
+
import { DENY_SCOPES, EFFORT_ADAPTERS, denyBlockFrom, denyEntriesAt, initialDenyPropOf, stagedDenyKeyOf } from "../../config/config.js";
|
|
7
7
|
import { fleetFirstTouchProvenance } from "../../config/fleet-overlay.js";
|
|
8
8
|
import { exclusionReason } from "../../config/fleet-why.js";
|
|
9
9
|
import { entryMatchesChannel, exclusionCollector } from "../../route/preference.js";
|
|
10
|
-
import { TIERS } from "../../graph/schema.js";
|
|
10
|
+
import { EFFORTS, TIERS } from "../../graph/schema.js";
|
|
11
11
|
import { windowRows } from "./components.js";
|
|
12
|
+
import { wrapCells } from "../cockpit/width.js";
|
|
12
13
|
import { clip, clipPathTail, ElisionMark, enterAltScreen, fmtCtx, fmtMs, fmtUsdPair, Glyph, INK, inkInput, inkOutput, KeyBar, OverlayPanel, padCell, padCellStart, Pointer, SearchRow, } from "./frame.js";
|
|
13
14
|
const STEERING_KEYS = ["review", "consult"];
|
|
14
15
|
/** every scope's staged list, empty — the "every policy scope open" probe the picker lifts against */
|
|
@@ -31,12 +32,18 @@ function splitSeat(seat) {
|
|
|
31
32
|
const at = seat.indexOf(":");
|
|
32
33
|
return { adapter: seat.slice(0, at), model: seat.slice(at + 1) };
|
|
33
34
|
}
|
|
35
|
+
// a picker ledger row is labelled "adapter/model — …" (fleet.ts channelLedger); this is its seat id
|
|
36
|
+
function ledgerChannelId(line) {
|
|
37
|
+
const head = line.split(" — ")[0];
|
|
38
|
+
const slash = head.indexOf("/");
|
|
39
|
+
return slash === -1 ? undefined : `${head.slice(0, slash)}:${head.slice(slash + 1)}`;
|
|
40
|
+
}
|
|
34
41
|
const REACH_CHOICES = [
|
|
35
42
|
{ id: "in", label: "in", gloss: "every seat may route here" },
|
|
36
43
|
{ id: "out-workers", label: "out · workers", gloss: "workers skip it; judge/review/consult unaffected" },
|
|
37
44
|
{ id: "out-all", label: "out · all seats", gloss: "no seat routes here" },
|
|
38
45
|
];
|
|
39
|
-
export function FleetApp({ ageMs, agents, initialAllowOut = [], modelGroups, initialMode, modeOptions, initialMap, modePreview, shapeRows, candidatesForShape, preferOptionsForShape, initialSteering, steeringOptionsFor, reviewOverlay, reloadGuard, stagedRouting, entry = "probe", initialJudge = "", judgeSeats = [], viewRows = Number.POSITIVE_INFINITY, frameColumns = 100, frameRows, ...initialDenyProps }) {
|
|
46
|
+
export function FleetApp({ ageMs, agents, initialAllowOut = [], initialEfforts = {}, modelGroups, initialMode, modeOptions, initialMap, modePreview, shapeRows, candidatesForShape, preferOptionsForShape, initialSteering, steeringOptionsFor, reviewAdvisories = [], reviewOverlay, reloadGuard, stagedRouting, entry = "probe", initialJudge = "", judgeSeats = [], viewRows = Number.POSITIVE_INFINITY, frameColumns = 100, frameRows, ...initialDenyProps }) {
|
|
40
47
|
const { exit } = useApp();
|
|
41
48
|
const [, setRevision] = useState(0);
|
|
42
49
|
// the props are the fleet command's contract, one per schema-enumerated scope (initialDeny…),
|
|
@@ -60,6 +67,7 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], modelGroups, ini
|
|
|
60
67
|
allowOut: new Set(initialAllowOut),
|
|
61
68
|
...Object.fromEntries(DENY_SCOPES.map((scope) => [scope.key, new Set(initialDeny[scope.key])])),
|
|
62
69
|
classifications: [],
|
|
70
|
+
efforts: structuredClone(initialEfforts),
|
|
63
71
|
selectedMode: initialMode,
|
|
64
72
|
map: structuredClone(initialMap),
|
|
65
73
|
steering: structuredClone(initialSteering),
|
|
@@ -100,6 +108,12 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], modelGroups, ini
|
|
|
100
108
|
if (JSON.stringify(ui.steering[key]) !== JSON.stringify(initialSteering[key]))
|
|
101
109
|
n += 1;
|
|
102
110
|
}
|
|
111
|
+
for (const adapter of new Set([...Object.keys(initialEfforts), ...Object.keys(ui.efforts)])) {
|
|
112
|
+
for (const model of new Set([...Object.keys(initialEfforts[adapter] ?? {}), ...Object.keys(ui.efforts[adapter] ?? {})])) {
|
|
113
|
+
if (initialEfforts[adapter]?.[model] !== ui.efforts[adapter]?.[model])
|
|
114
|
+
n += 1;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
103
117
|
return n;
|
|
104
118
|
};
|
|
105
119
|
// ── derived data ───────────────────────────────────────────────────────────
|
|
@@ -367,6 +381,7 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], modelGroups, ini
|
|
|
367
381
|
}),
|
|
368
382
|
}
|
|
369
383
|
: classification),
|
|
384
|
+
efforts: structuredClone(ui.efforts),
|
|
370
385
|
selectedMode: ui.selectedMode,
|
|
371
386
|
map: ui.map,
|
|
372
387
|
...(ui.judgeSeat ? { judgeSeat: splitSeat(ui.judgeSeat) } : {}),
|
|
@@ -704,10 +719,9 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], modelGroups, ini
|
|
|
704
719
|
// same picker reopens over the recomputed policy — a shared or adapter-wide entry is refused by
|
|
705
720
|
// name and the row stays greyed
|
|
706
721
|
const liftFromPicker = (overlay, line) => {
|
|
707
|
-
const
|
|
708
|
-
const
|
|
709
|
-
|
|
710
|
-
: displayedChannels().find((channel) => channel.adapter === head.slice(0, slash) && channel.model === head.slice(slash + 1));
|
|
722
|
+
const lineId = ledgerChannelId(line);
|
|
723
|
+
const self = lineId === undefined ? undefined
|
|
724
|
+
: displayedChannels().find((channel) => `${channel.adapter}:${channel.model}` === lineId);
|
|
711
725
|
if (!self) {
|
|
712
726
|
ui.notice = `nothing to lift on this row — ${line}`;
|
|
713
727
|
bump();
|
|
@@ -747,7 +761,12 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], modelGroups, ini
|
|
|
747
761
|
bump();
|
|
748
762
|
};
|
|
749
763
|
const reviewWindow = (overlay) => {
|
|
750
|
-
|
|
764
|
+
// OBS-1144: notes are pre-wrapped to the diff's width and lead the SAME scrolled buffer, so a
|
|
765
|
+
// note never sits past the terminal's last row, unreachable, while y still saves.
|
|
766
|
+
const notes = (overlay.review.notes ?? [])
|
|
767
|
+
.flatMap((note) => wrapCells(`! ${note}`, bodyW - 6, { continuationPrefix: " " }))
|
|
768
|
+
.map((text) => ({ text, note: true }));
|
|
769
|
+
const lines = [...notes, ...overlay.review.diff.split("\n").map((text) => ({ text, note: false }))];
|
|
751
770
|
const cap = reviewCapacity();
|
|
752
771
|
const maxScroll = Math.max(lines.length - cap, 0);
|
|
753
772
|
const scroll = Math.min(Math.max(overlay.scroll, 0), maxScroll);
|
|
@@ -1059,16 +1078,39 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], modelGroups, ini
|
|
|
1059
1078
|
bump();
|
|
1060
1079
|
return;
|
|
1061
1080
|
}
|
|
1062
|
-
if (input === " "
|
|
1063
|
-
const
|
|
1081
|
+
if (input === " ") {
|
|
1082
|
+
const offered = rows[overlay.at];
|
|
1083
|
+
const greyed = offered ? undefined : ledger[overlay.at - rows.length];
|
|
1084
|
+
const id = offered?.id ?? (greyed === undefined ? undefined : ledgerChannelId(greyed));
|
|
1085
|
+
if (id === undefined)
|
|
1086
|
+
return;
|
|
1064
1087
|
const at = overlay.chain.indexOf(id);
|
|
1065
|
-
|
|
1088
|
+
// OBS-1145 add.1: Space drops ANY carried member, admitted or not, and leaves its deny
|
|
1089
|
+
// alone; only an offered row joins — a greyed one still enters through its reach (Enter)
|
|
1090
|
+
if (at !== -1)
|
|
1091
|
+
overlay.chain.splice(at, 1);
|
|
1092
|
+
else if (offered)
|
|
1066
1093
|
overlay.chain.push(id);
|
|
1067
1094
|
else
|
|
1068
|
-
|
|
1095
|
+
ui.notice = `${id} is not offered — Enter lifts it first; Space pools offered rows only`;
|
|
1069
1096
|
bump();
|
|
1070
1097
|
return;
|
|
1071
1098
|
}
|
|
1099
|
+
// a selection is a POOL, and its mode is a decision, not a default — the tiny
|
|
1100
|
+
// poolmode overlay asks it; Esc there restores this picker with the chain intact
|
|
1101
|
+
// OBS-525: an existing pool's mode seeds the cursor so a round-trip keeps it
|
|
1102
|
+
const askPoolMode = () => setOverlay({ kind: "poolmode", picker: overlay, at: ui.map[overlay.shape]?.pool?.mode === "ordered" ? 1 : 0 });
|
|
1103
|
+
// OBS-1145 add.1: Tab applies the chain from ANY row — in an all-greyed picker every row is
|
|
1104
|
+
// ✗ and Enter lifts it, so a Space drop there had no commit path; Tab lifts no deny
|
|
1105
|
+
if (key.tab) {
|
|
1106
|
+
if (overlay.chain.length)
|
|
1107
|
+
askPoolMode();
|
|
1108
|
+
else {
|
|
1109
|
+
ui.notice = `the ${overlay.shape} pool is empty — Esc keeps it; a on the shape reverts it to auto`;
|
|
1110
|
+
bump();
|
|
1111
|
+
}
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1072
1114
|
if (key.return) {
|
|
1073
1115
|
const greyed = ledger[overlay.at - rows.length];
|
|
1074
1116
|
if (!rows[overlay.at] && greyed !== undefined) {
|
|
@@ -1076,10 +1118,7 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], modelGroups, ini
|
|
|
1076
1118
|
return;
|
|
1077
1119
|
}
|
|
1078
1120
|
if (overlay.chain.length) {
|
|
1079
|
-
|
|
1080
|
-
// poolmode overlay asks it; Esc there restores this picker with the chain intact
|
|
1081
|
-
// OBS-525: an existing pool's mode seeds the cursor so a round-trip keeps it
|
|
1082
|
-
setOverlay({ kind: "poolmode", picker: overlay, at: ui.map[overlay.shape]?.pool?.mode === "ordered" ? 1 : 0 });
|
|
1121
|
+
askPoolMode();
|
|
1083
1122
|
return;
|
|
1084
1123
|
}
|
|
1085
1124
|
if (rows[overlay.at]) {
|
|
@@ -1448,6 +1487,29 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], modelGroups, ini
|
|
|
1448
1487
|
setOverlay({ kind: "lift", id: `${row.adapter}:${row.model}`, covering: row.covering });
|
|
1449
1488
|
return;
|
|
1450
1489
|
}
|
|
1490
|
+
if (hotkey === "e" && row) {
|
|
1491
|
+
// OBS-1182: e cycles launch effort default → low → medium → high → default; the seat's
|
|
1492
|
+
// identity (adapter:model) never changes, only tiers.<adapter>.modelOverrides.<model>.effort
|
|
1493
|
+
if (!EFFORT_ADAPTERS.includes(row.adapter)) {
|
|
1494
|
+
ui.notice = `${row.adapter} takes no effort level — effort is launch metadata only ${EFFORT_ADAPTERS.join(" and ")} accept`;
|
|
1495
|
+
}
|
|
1496
|
+
else if (!row.tier) {
|
|
1497
|
+
ui.notice = `${row.adapter}:${row.model} is unclassified — classify it before setting its effort`;
|
|
1498
|
+
}
|
|
1499
|
+
else {
|
|
1500
|
+
const current = ui.efforts[row.adapter]?.[row.model];
|
|
1501
|
+
const next = EFFORTS[current === undefined ? 0 : EFFORTS.indexOf(current) + 1];
|
|
1502
|
+
const models = { ...ui.efforts[row.adapter] };
|
|
1503
|
+
if (next === undefined)
|
|
1504
|
+
delete models[row.model];
|
|
1505
|
+
else
|
|
1506
|
+
models[row.model] = next;
|
|
1507
|
+
ui.efforts = { ...ui.efforts, [row.adapter]: models };
|
|
1508
|
+
ui.notice = `${row.adapter}:${row.model} effort → ${next ?? "CLI default"} — w writes tiers.${row.adapter}.modelOverrides.${row.model}.effort`;
|
|
1509
|
+
}
|
|
1510
|
+
bump();
|
|
1511
|
+
return;
|
|
1512
|
+
}
|
|
1451
1513
|
if (hotkey === "t" && row) {
|
|
1452
1514
|
if (row.tier) {
|
|
1453
1515
|
ui.notice = "tier reassignment on classified models is not supported in v1 — edit config directly";
|
|
@@ -1657,6 +1719,7 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], modelGroups, ini
|
|
|
1657
1719
|
const parts = [
|
|
1658
1720
|
`${row.adapter}:${row.model}`,
|
|
1659
1721
|
row.tier ?? "unclassified",
|
|
1722
|
+
ui.efforts[row.adapter]?.[row.model] ? `effort ${ui.efforts[row.adapter][row.model]}` : "",
|
|
1660
1723
|
ui.lastEdit?.id === `${row.adapter}:${row.model}` ? ui.lastEdit.text : "",
|
|
1661
1724
|
identityDetail(row),
|
|
1662
1725
|
row.tier ? reachDetail(row) : "",
|
|
@@ -1734,7 +1797,7 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], modelGroups, ini
|
|
|
1734
1797
|
if (overlay.kind === "review") {
|
|
1735
1798
|
const { visible, scroll, cap, lines } = reviewWindow(overlay);
|
|
1736
1799
|
const below = lines.length - scroll - visible.length;
|
|
1737
|
-
return (_jsxs(OverlayPanel, { title: `review · ${overlay.review.path}`, width: bodyW, children: [_jsx(Text, { dimColor: true, wrap: "truncate", children: clip("everything staged lands in this one diff — y writes, n discards, ↑↓ scroll, Esc keeps editing", bodyW - 4) }), scroll > 0 && _jsx(ElisionMark, { count: scroll, side: "above" }), visible.map((line, index) => (_jsx(Text, { wrap: "truncate", color: line.startsWith("+") ? INK.brand : line.startsWith("-") ? INK.fail : undefined, dimColor: line.startsWith("@") || line.startsWith("---") || line.startsWith("+++"), children: clip(line, bodyW - 6) || " " }, `${scroll + index}:${line}`))), below > 0 && _jsx(ElisionMark, { count: below, side: "below", hint: `↓ scrolls · PgDn jumps ${cap} lines` })] }));
|
|
1800
|
+
return (_jsxs(OverlayPanel, { title: clipPathTail(`review · ${overlay.review.path}`, bodyW - 4), width: bodyW, children: [_jsx(Text, { dimColor: true, wrap: "truncate", children: clip("everything staged lands in this one diff — y writes, n discards, ↑↓ scroll, Esc keeps editing", bodyW - 4) }), scroll > 0 && _jsx(ElisionMark, { count: scroll, side: "above" }), visible.map(({ text: line, note }, index) => (_jsx(Text, { wrap: "truncate", color: note ? INK.warn : line.startsWith("+") ? INK.brand : line.startsWith("-") ? INK.fail : undefined, dimColor: !note && (line.startsWith("@") || line.startsWith("---") || line.startsWith("+++")), children: clip(line, bodyW - 6) || " " }, `${scroll + index}:${line}`))), below > 0 && _jsx(ElisionMark, { count: below, side: "below", hint: `↓ scrolls · PgDn jumps ${cap} lines` })] }));
|
|
1738
1801
|
}
|
|
1739
1802
|
if (overlay.kind === "classify") {
|
|
1740
1803
|
const subject = overlay.bulk
|
|
@@ -1767,10 +1830,13 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], modelGroups, ini
|
|
|
1767
1830
|
];
|
|
1768
1831
|
const { visible, start, above, below } = windowRows(items, overlay.at, Math.max(1, capacity - noteLines.length + 1));
|
|
1769
1832
|
const chained = overlay.chain.length > 0;
|
|
1770
|
-
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
|
|
1833
|
+
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 pools in order · Tab applies the pool from any row — Enter on an offered row too (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) => {
|
|
1771
1834
|
const selected = start + index === overlay.at;
|
|
1772
1835
|
if (candidate === undefined) {
|
|
1773
|
-
|
|
1836
|
+
// OBS-1145 add.1: a carried chain member the picker cannot offer keeps its ordinal
|
|
1837
|
+
const id = ledgerChannelId(line);
|
|
1838
|
+
const at = id === undefined ? -1 : overlay.chain.indexOf(id);
|
|
1839
|
+
return (_jsxs(Text, { wrap: "truncate", children: [_jsx(Pointer, { on: selected }), at !== -1 && _jsx(Text, { color: INK.brand, children: `${at + 1} ` }), _jsx(Text, { dimColor: true, children: "✗ " }), _jsx(Text, { bold: selected, dimColor: !selected, children: clip(line, bodyW - (at === -1 ? 10 : 12)) })] }, `ledger-${start + index}`));
|
|
1774
1840
|
}
|
|
1775
1841
|
const at = overlay.chain.indexOf(candidate.id);
|
|
1776
1842
|
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));
|
|
@@ -1840,7 +1906,8 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], modelGroups, ini
|
|
|
1840
1906
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, children: "Shapes" }), _jsx(Text, { dimColor: true, children: ` routed under ${ui.selectedMode}` })] }), _jsx(Text, { children: " " }), rows.map((row, index) => renderShapeRow(row, ui.focus === "list" && index === ui.listAt))] }));
|
|
1841
1907
|
}
|
|
1842
1908
|
const rows = steeringList();
|
|
1843
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, children: "Steering" }), _jsx(Text, { dimColor: true, children: " review · consult · judge" })] }), _jsx(Text, { children: " " }), rows.map((row, index) => renderPlainRow(row.id, row.label, ui.focus === "list" && index === ui.listAt))
|
|
1909
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, children: "Steering" }), _jsx(Text, { dimColor: true, children: " review · consult · judge" })] }), _jsx(Text, { children: " " }), rows.map((row, index) => renderPlainRow(row.id, row.label, ui.focus === "list" && index === ui.listAt)), reviewAdvisories.length ? _jsx(Text, { children: " " }) : null, reviewAdvisories.flatMap((line) => wrapCells(`! ${line}`, bodyW - 4, { continuationPrefix: " " }))
|
|
1910
|
+
.map((text, index) => _jsx(Text, { color: INK.warn, children: text }, `advisory-${index}`))] }));
|
|
1844
1911
|
})();
|
|
1845
1912
|
// ── footer ─────────────────────────────────────────────────────────────────
|
|
1846
1913
|
const detailLines = () => {
|
|
@@ -1911,6 +1978,7 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], modelGroups, ini
|
|
|
1911
1978
|
if (overlay.kind === "candidates") {
|
|
1912
1979
|
return [
|
|
1913
1980
|
{ key: "Enter", label: overlay.chain.length ? "apply pool" : "pin · lift greyed" },
|
|
1981
|
+
...(overlay.chain.length ? [{ key: "Tab", label: "apply from any row" }] : []),
|
|
1914
1982
|
{ key: "Space", label: "pool in order" },
|
|
1915
1983
|
{ key: "type", label: "search" },
|
|
1916
1984
|
{ key: "Esc", label: "cancel" },
|
|
@@ -1955,6 +2023,7 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], modelGroups, ini
|
|
|
1955
2023
|
{ key: "Enter", label: "assign/classify" },
|
|
1956
2024
|
{ key: "Space", label: "reach picker" },
|
|
1957
2025
|
{ key: "l", label: "lift entry" },
|
|
2026
|
+
{ key: "e", label: "effort" },
|
|
1958
2027
|
{ key: "m", label: "presets" },
|
|
1959
2028
|
{ key: "w", label: "review + write" },
|
|
1960
2029
|
{ key: "←", label: "rail" },
|
|
@@ -1985,7 +2054,7 @@ export function FleetApp({ ageMs, agents, initialAllowOut = [], modelGroups, ini
|
|
|
1985
2054
|
? _jsx(Text, { color: INK.warn, children: ` ! ${clip(ui.notice, width - 4)}` })
|
|
1986
2055
|
: details.map((line) => _jsx(Text, { dimColor: true, children: ` ${clip(line, width - 2)}` }, line)), _jsx(KeyBar, { binds: keyBinds() })] }));
|
|
1987
2056
|
}
|
|
1988
|
-
export async function runFleetInkEditor({ ageMs, adapters, health, initialAllowOut = [], modelGroups, initialMode, modeOptions, initialMap, modePreview, shapeRows, candidatesForShape, preferOptionsForShape, initialSteering, steeringOptionsFor, reviewOverlay, reloadGuard, stagedRouting, entry = "probe", initialJudge = "", judgeSeats = [], initialInput = [], input, output, debug = false, ...initialDenyProps }) {
|
|
2057
|
+
export async function runFleetInkEditor({ ageMs, adapters, health, initialAllowOut = [], initialEfforts, modelGroups, initialMode, modeOptions, initialMap, modePreview, shapeRows, candidatesForShape, preferOptionsForShape, initialSteering, steeringOptionsFor, reviewAdvisories = [], reviewOverlay, reloadGuard, stagedRouting, entry = "probe", initialJudge = "", judgeSeats = [], initialInput = [], input, output, debug = false, ...initialDenyProps }) {
|
|
1989
2058
|
const agents = adapters.flatMap((adapter) => {
|
|
1990
2059
|
const state = health[adapter.id];
|
|
1991
2060
|
if (!state?.installed)
|
|
@@ -2009,7 +2078,7 @@ export async function runFleetInkEditor({ ageMs, adapters, health, initialAllowO
|
|
|
2009
2078
|
// rebuilt from output.columns/rows so a pane resize re-renders at the new size. Same root
|
|
2010
2079
|
// element type on every rerender ⇒ React keeps the component instance, so staged edits,
|
|
2011
2080
|
// cursor, and overlays all survive the resize.
|
|
2012
|
-
const editorElement = () => (_jsx(FleetApp, { ageMs: ageMs, agents: agents, ...initialDenyProps, initialAllowOut: initialAllowOut, 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,
|
|
2081
|
+
const editorElement = () => (_jsx(FleetApp, { ageMs: ageMs, agents: agents, ...initialDenyProps, initialAllowOut: initialAllowOut, initialEfforts: initialEfforts, modelGroups: declaredModelGroups, initialMode: initialMode, modeOptions: modeOptions, initialMap: initialMap, modePreview: modePreview, shapeRows: shapeRows, candidatesForShape: candidatesForShape, preferOptionsForShape: preferOptionsForShape, initialSteering: initialSteering, steeringOptionsFor: steeringOptionsFor, reviewAdvisories: reviewAdvisories, reviewOverlay: reviewOverlay, reloadGuard: reloadGuard, stagedRouting: stagedRouting, entry: entry, initialJudge: initialJudge, judgeSeats: judgeSeats,
|
|
2013
2082
|
// v1.90.9: cap every list to the terminal minus chrome so the cursor and header can
|
|
2014
2083
|
// never scroll off-screen (omp's 218-model list did exactly that pre-cap).
|
|
2015
2084
|
// 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.6.
|
|
3
|
+
"version": "2.6.2",
|
|
4
4
|
"description": "Spec in, verified work out.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -80,6 +80,7 @@
|
|
|
80
80
|
"scripts/assert-test-file-count.sh",
|
|
81
81
|
"scripts/emit-schema.ts",
|
|
82
82
|
"scripts/probe-rig.mjs",
|
|
83
|
+
"scripts/run-ci-vitest.sh",
|
|
83
84
|
"specs/export-selftest.spec.md"
|
|
84
85
|
],
|
|
85
86
|
"prefixes": [
|