tickmarkr 2.6.1 → 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 +1 -0
- package/dist/adapters/prompt.js +14 -6
- package/dist/adapters/registry.js +3 -3
- package/dist/adapters/types.d.ts +12 -4
- package/dist/adapters/types.js +6 -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 +45 -7
- package/dist/cli/commands/report.js +18 -2
- package/dist/cli/commands/resume.js +4 -2
- package/dist/cli/commands/status.js +24 -19
- package/dist/cli/help.d.ts +2 -0
- package/dist/cli/help.js +9 -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 +193 -60
- 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/llm.d.ts +5 -4
- package/dist/gates/llm.js +13 -13
- package/dist/gates/review.d.ts +8 -0
- package/dist/gates/review.js +40 -4
- package/dist/gates/run-gates.d.ts +2 -1
- package/dist/gates/run-gates.js +27 -13
- package/dist/gates/test-manifest.d.ts +3 -1
- package/dist/gates/test-manifest.js +9 -2
- 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/router.js +30 -15
- package/dist/run/consult.d.ts +13 -1
- package/dist/run/consult.js +14 -5
- package/dist/run/daemon.d.ts +37 -2
- package/dist/run/daemon.js +579 -141
- package/dist/run/git.d.ts +8 -0
- package/dist/run/git.js +14 -0
- package/dist/run/journal.d.ts +126 -3
- package/dist/run/journal.js +410 -37
- package/dist/run/merge.d.ts +3 -1
- package/dist/run/merge.js +3 -2
- 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/supervision.d.ts +7 -1
- package/dist/run/supervision.js +5 -2
- package/dist/tui/cockpit/board.js +3 -3
- package/dist/tui/cockpit/decision-actions.d.ts +8 -5
- package/dist/tui/cockpit/decision-actions.js +55 -32
- package/dist/tui/cockpit/derive.js +13 -2
- package/dist/tui/cockpit/live-runtime.d.ts +10 -0
- package/dist/tui/cockpit/live-runtime.js +50 -3
- package/dist/tui/cockpit/run-cockpit.d.ts +3 -0
- package/dist/tui/cockpit/run-cockpit.js +26 -1
- package/dist/tui/cockpit/run-view.d.ts +9 -3
- package/dist/tui/cockpit/run-view.js +60 -7
- 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 +42 -0
- 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,7 +5,7 @@ import { GLYPHS, PLAIN_COMPACT_LOCKUP, } from "../../brand.js";
|
|
|
5
5
|
import { allocateBandColumns, BandLines, BodyText, CockpitGrid, composeBandLine, JournalRowPanel, keyRosterLines, Panel, PANEL_CHROME_ROWS, ProgressMeter, Sparkline, StatTile, StatusStrip, } from "./components.js";
|
|
6
6
|
import { authorsNote } from "../../run/operator-state.js";
|
|
7
7
|
import { fieldReading } from "./derive.js";
|
|
8
|
-
import { STALL_MARKER } from "./run-view.js";
|
|
8
|
+
import { MISSING_EVIDENCE, STALL_MARKER } from "./run-view.js";
|
|
9
9
|
import { initialRunInteractionState, projectRunKeyEntries, reconcileRunInteraction, RUN_SIDE_RAIL_COLUMN_FLOOR, runPanelFocusOrder, runSideRailVisible, selectableRunViewRowIds, } from "./keys.js";
|
|
10
10
|
import { DECISIONS_TAB_TITLE, RUN_VIEWS, runViewById, setupSectionAt, SETUP_SECTIONS, } from "./views.js";
|
|
11
11
|
import { FRAME_HEADER_GAP, FRAME_NESTED_ROW_BANDS, FULL_JOURNAL_ROWS, planFrame, RUN_PANEL_HORIZONTAL_CHROME, SIDEBAR_COLUMN_FLOOR, } from "./layout.js";
|
|
@@ -309,8 +309,13 @@ function promotedViewRows(data, viewId) {
|
|
|
309
309
|
* task row — identity, phase, build evidence, blocker, next action — each unrecorded field saying so,
|
|
310
310
|
* each recorded one citing the journal line it was read from, and the OBS-1048 stall marker when the
|
|
311
311
|
* newest attempt's harvest is suspect. No attempt label is printed here: the ruler is the row's own.
|
|
312
|
+
* A task with no recorded dispatch folds identity, phase, and build into one missing-evidence clause
|
|
313
|
+
* (OBS-1104). A recorded blocker or next action — a pre-dispatch park, a dependency wait — stays,
|
|
314
|
+
* cited to the journal row that produced it.
|
|
312
315
|
*/
|
|
313
316
|
export function taskProjectionText(row, journal = []) {
|
|
317
|
+
if ((row.attempts ?? 0) === 0)
|
|
318
|
+
return undispatchedProjectionText(row, journal);
|
|
314
319
|
const identity = row.identities?.at(-1);
|
|
315
320
|
const blocker = row.summary?.blocker;
|
|
316
321
|
const harvest = row.harvests?.at(-1);
|
|
@@ -331,6 +336,26 @@ export function taskProjectionText(row, journal = []) {
|
|
|
331
336
|
...(harvest?.suspectedStalledHarvest ? [`${STALL_MARKER} · launch ${at(evidence.launch)} · ${harvest.nudgeFailures} nudge failed ${at(evidence.nudge)} · ${harvest.pageCount} paged ${at(evidence.page)} · no worker-result`] : []),
|
|
332
337
|
].reduce((text, part) => `${text} · ${part}`);
|
|
333
338
|
}
|
|
339
|
+
/**
|
|
340
|
+
* Identity, phase, and build are unrecorded until a dispatch. Append the blocker and next action
|
|
341
|
+
* only when the summary recorded one — an unknown placeholder with no journal row is the missing clause.
|
|
342
|
+
*/
|
|
343
|
+
function undispatchedProjectionText(row, journal) {
|
|
344
|
+
const blocker = row.summary?.blocker;
|
|
345
|
+
if (blocker == null)
|
|
346
|
+
return MISSING_EVIDENCE;
|
|
347
|
+
const evidence = projectionEvidence(row, journal);
|
|
348
|
+
const at = (line) => line === undefined ? "(no journal row)" : `#L${line}`;
|
|
349
|
+
const showBlocker = blocker.kind !== "unknown" || evidence.blocker !== undefined;
|
|
350
|
+
const showNext = blocker.nextAction != null;
|
|
351
|
+
if (!showBlocker && !showNext)
|
|
352
|
+
return MISSING_EVIDENCE;
|
|
353
|
+
return [
|
|
354
|
+
MISSING_EVIDENCE,
|
|
355
|
+
...(showBlocker ? [`blocker ${blocker.kind} ${at(evidence.blocker)}`] : []),
|
|
356
|
+
...(showNext ? [`next ${blocker.nextAction} ${at(evidence.nextAction)}`] : []),
|
|
357
|
+
].reduce((text, part) => `${text} · ${part}`);
|
|
358
|
+
}
|
|
334
359
|
/**
|
|
335
360
|
* Correlate each already-derived field with the row that can have produced that value. This is
|
|
336
361
|
* intentionally not a "newest row with a plausible name" lookup: launch identity is paired by
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type ReactElement } from "react";
|
|
2
2
|
import type { ApprovalRunOwner } from "../../cli/commands/approve.js";
|
|
3
3
|
import { type GateName, type RunGraph } from "../../graph/schema.js";
|
|
4
|
-
import type
|
|
4
|
+
import { type JournalEvent } from "../../run/journal.js";
|
|
5
5
|
import { type GateOutcome } from "../../run/outcome.js";
|
|
6
6
|
import type { OperatorGateState, OperatorSnapshot, OperatorTask } from "../../run/operator-state.js";
|
|
7
7
|
import { type DecisionCommand, type DecisionPreview, type DecisionSession, type RunDecision } from "./decision-actions.js";
|
|
@@ -93,8 +93,9 @@ export interface TaskProjection {
|
|
|
93
93
|
readonly build: ProjectionField;
|
|
94
94
|
readonly blocker: ProjectionField;
|
|
95
95
|
readonly nextAction: ProjectionField;
|
|
96
|
-
/** OBS-1048: present only when the newest launched attempt has failed nudges, pages and no worker-result. */
|
|
96
|
+
/** OBS-1048: present only when the newest launched attempt has failed nudges, pages and no worker-result. OBS-1104: neverDispatched collapses that task's line. */
|
|
97
97
|
readonly stalled?: ProjectionField;
|
|
98
|
+
readonly neverDispatched?: true;
|
|
98
99
|
}
|
|
99
100
|
export declare const STALL_MARKER = "\u26A0 stalled harvest suspected";
|
|
100
101
|
/**
|
|
@@ -103,7 +104,12 @@ export declare const STALL_MARKER = "\u26A0 stalled harvest suspected";
|
|
|
103
104
|
* Nothing is inferred from a title, a provider or a task id: an unrecorded field says so.
|
|
104
105
|
*/
|
|
105
106
|
export declare function projectRunTasks(snapshot: OperatorSnapshot, rows: readonly RunEvidenceRow[], graph: RunGraph | undefined, decisions: readonly RunDecision[], runId: string): readonly TaskProjection[];
|
|
106
|
-
/**
|
|
107
|
+
/**
|
|
108
|
+
* OBS-1104: a never-dispatched task has no identity, phase, or build row. Those fold into one
|
|
109
|
+
* missing-evidence clause. A substantive or journal-cited blocker and next action stay — a
|
|
110
|
+
* pre-dispatch park and a dependency wait are recorded facts, and dropping them hides the park token.
|
|
111
|
+
*/
|
|
112
|
+
export declare const MISSING_EVIDENCE = "missing evidence";
|
|
107
113
|
export declare const projectionLine: (p: TaskProjection) => string;
|
|
108
114
|
export interface PaneLocator {
|
|
109
115
|
readonly status: "recorded" | "unavailable";
|
|
@@ -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";
|
|
@@ -122,7 +123,7 @@ export function runGateCells(task, evidence, rows = []) {
|
|
|
122
123
|
const e = later.event;
|
|
123
124
|
if (e.event === "gate-reused" && e.data.gate === gate)
|
|
124
125
|
labels.push(`inherited from ${str(e.data.commit) ?? "unknown commit"} · #L${later.line}`);
|
|
125
|
-
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))
|
|
126
127
|
labels.push(`satisfied by approval #L${later.line}`);
|
|
127
128
|
}
|
|
128
129
|
const verdict = row?.event?.event === "gate-result" && typeof data?.details === "string" ? data.details.split("\n") : [];
|
|
@@ -191,7 +192,7 @@ const readLabel = (f) => `${f.label} ${f.line === undefined ? `(${NO_ROW})` : `#
|
|
|
191
192
|
* Nothing is inferred from a title, a provider or a task id: an unrecorded field says so.
|
|
192
193
|
*/
|
|
193
194
|
export function projectRunTasks(snapshot, rows, graph, decisions, runId) {
|
|
194
|
-
const ordered =
|
|
195
|
+
const ordered = decidedRows(rows);
|
|
195
196
|
const graphTask = (id) => graph?.tasks.find((t) => t.id === id);
|
|
196
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 }));
|
|
197
198
|
const tracked = trackJournalRows(runId, ordered.map((r) => ({ raw: r.event, sourceIndex: r.line })));
|
|
@@ -208,7 +209,16 @@ export function projectRunTasks(snapshot, rows, graph, decisions, runId) {
|
|
|
208
209
|
...(own.length === 0 ? {} : { phase: activity.get(t.id)?.state, lastEvidenceAt: own.at(-1).event.ts }),
|
|
209
210
|
...(responsibility ? { responsible: { role: str(responsibility.event.data.role), agent: str(responsibility.event.data.agent) } } : {}),
|
|
210
211
|
};
|
|
211
|
-
}), 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]));
|
|
212
222
|
return snapshot.tasks.map((t) => {
|
|
213
223
|
const own = byTask(t.id);
|
|
214
224
|
const act = activity.get(t.id);
|
|
@@ -272,7 +282,7 @@ export function projectRunTasks(snapshot, rows, graph, decisions, runId) {
|
|
|
272
282
|
const stalled = launched !== undefined && !returned && nudges.length > 0 && pages.length > 0
|
|
273
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 }
|
|
274
284
|
: undefined;
|
|
275
|
-
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 } : {}) };
|
|
276
286
|
});
|
|
277
287
|
}
|
|
278
288
|
/** The line at which each projected value last changed, folding the task's rows (and run-level resets) prefix by prefix. */
|
|
@@ -299,8 +309,16 @@ function acceptedChanges(runId, tracked, taskId, task) {
|
|
|
299
309
|
}
|
|
300
310
|
return out;
|
|
301
311
|
}
|
|
302
|
-
/**
|
|
303
|
-
|
|
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);
|
|
304
322
|
/**
|
|
305
323
|
* Where the current attempt's pane is recorded — and only that. The reading is exact-name: the
|
|
306
324
|
* launch row's slot name must be the owned name of THIS task, attempt and run. No terminal is ever
|
|
@@ -358,6 +376,29 @@ export function paneLocator(task, rows, runId) {
|
|
|
358
376
|
}
|
|
359
377
|
/** The board's footer keys merged with the cockpit's own — every one of them handled by this view or the shell. */
|
|
360
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
|
+
}
|
|
361
402
|
/** The Run body: the approved board (BD-1) over the fold, then the selected task's detail panels. */
|
|
362
403
|
export function RunView({ snapshot, rows, page, graph, decisions, session, columns, run, now = Date.now }) {
|
|
363
404
|
const inner = Math.max(1, Math.floor(columns) - 4);
|
|
@@ -383,9 +424,21 @@ export function RunView({ snapshot, rows, page, graph, decisions, session, colum
|
|
|
383
424
|
const { menu, confirming, receipt, notice } = session.decisions;
|
|
384
425
|
const blocked = task === undefined ? [] : graph?.tasks.filter((g) => g.deps.includes(task.id)).map((g) => g.id) ?? decision?.blocks ?? [];
|
|
385
426
|
const board = renderBoardLines({ runId: run.runId, snapshot, graph, now: now(), live: run.live, selection: task?.id, keys: false, colour }, width);
|
|
386
|
-
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
|
|
387
428
|
? _jsx(BodyText, { children: fit("park evidence unavailable — refresh before deciding") })
|
|
388
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
|
|
389
430
|
? _jsx(BodyText, { children: fit(`no decision — ${decision?.diagnostic ?? "no verb for this park"}`) })
|
|
390
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) })] }));
|
|
391
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
|