tickmarkr 2.4.2 → 2.5.1
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 +110 -24
- package/dist/adapters/fake.d.ts +1 -0
- package/dist/adapters/fake.js +2 -1
- package/dist/adapters/opencode.d.ts +1 -0
- package/dist/adapters/opencode.js +4 -1
- package/dist/adapters/types.d.ts +14 -0
- package/dist/adapters/types.js +25 -0
- package/dist/cli/commands/approve.d.ts +42 -0
- package/dist/cli/commands/approve.js +80 -13
- package/dist/cli/commands/doctor.d.ts +234 -0
- package/dist/cli/commands/doctor.js +139 -5
- package/dist/cli/commands/init.d.ts +2 -0
- package/dist/cli/commands/init.js +45 -5
- package/dist/cli/commands/plan.js +58 -6
- package/dist/cli/commands/report.js +18 -45
- package/dist/cli/commands/run.js +3 -0
- package/dist/cli/commands/scope.js +36 -6
- package/dist/cli/commands/stats.d.ts +2 -0
- package/dist/cli/commands/stats.js +42 -23
- package/dist/cli/commands/status.js +20 -4
- package/dist/cli/commands/ui.js +42 -53
- package/dist/cli/commands/unlock.d.ts +1 -1
- package/dist/cli/commands/unlock.js +59 -9
- package/dist/cli/help.d.ts +219 -0
- package/dist/cli/help.js +212 -0
- package/dist/cli/index.d.ts +42 -2
- package/dist/cli/index.js +23 -8
- package/dist/drivers/herdr.d.ts +7 -2
- package/dist/drivers/herdr.js +78 -48
- package/dist/drivers/orca.d.ts +3 -2
- package/dist/drivers/orca.js +43 -4
- package/dist/drivers/subprocess.d.ts +1 -0
- package/dist/drivers/subprocess.js +3 -0
- package/dist/drivers/types.d.ts +14 -0
- package/dist/gates/artifact-manifest.d.ts +50 -0
- package/dist/gates/artifact-manifest.js +23 -0
- package/dist/gates/llm.d.ts +5 -0
- package/dist/gates/llm.js +188 -3
- package/dist/gates/review.d.ts +6 -3
- package/dist/gates/review.js +79 -33
- package/dist/gates/run-gates.d.ts +3 -0
- package/dist/gates/run-gates.js +14 -4
- package/dist/plan/scope.d.ts +25 -0
- package/dist/plan/scope.js +92 -12
- package/dist/report/operator-record.d.ts +49 -0
- package/dist/report/operator-record.js +137 -0
- package/dist/run/daemon.d.ts +5 -5
- package/dist/run/daemon.js +166 -44
- package/dist/run/lock.d.ts +59 -2
- package/dist/run/lock.js +184 -26
- package/dist/run/operator-state.d.ts +86 -0
- package/dist/run/operator-state.js +165 -0
- package/dist/run/supervision.d.ts +32 -0
- package/dist/run/supervision.js +138 -17
- package/dist/tui/cockpit/capture.d.ts +19 -0
- package/dist/tui/cockpit/capture.js +89 -1
- package/dist/tui/cockpit/components.d.ts +15 -1
- package/dist/tui/cockpit/components.js +79 -9
- package/dist/tui/cockpit/decision-actions.d.ts +147 -0
- package/dist/tui/cockpit/decision-actions.js +315 -0
- package/dist/tui/cockpit/derive.d.ts +1 -1
- package/dist/tui/cockpit/derive.js +2 -0
- package/dist/tui/cockpit/evidence-view.d.ts +119 -0
- package/dist/tui/cockpit/evidence-view.js +210 -0
- package/dist/tui/cockpit/home-view.d.ts +88 -0
- package/dist/tui/cockpit/home-view.js +240 -0
- package/dist/tui/cockpit/keys.d.ts +125 -0
- package/dist/tui/cockpit/keys.js +31 -0
- package/dist/tui/cockpit/layout.d.ts +14 -0
- package/dist/tui/cockpit/layout.js +15 -0
- package/dist/tui/cockpit/live-runtime.d.ts +46 -0
- package/dist/tui/cockpit/live-runtime.js +683 -0
- package/dist/tui/cockpit/live-store.d.ts +289 -0
- package/dist/tui/cockpit/live-store.js +308 -0
- package/dist/tui/cockpit/live.d.ts +21 -1
- package/dist/tui/cockpit/live.js +12 -1
- package/dist/tui/cockpit/run-view.d.ts +100 -0
- package/dist/tui/cockpit/run-view.js +202 -0
- package/dist/tui/cockpit/shell.d.ts +50 -0
- package/dist/tui/cockpit/shell.js +74 -0
- package/dist/tui/cockpit/theme.d.ts +27 -0
- package/dist/tui/cockpit/theme.js +21 -0
- package/dist/tui/ink/init-app.d.ts +4 -0
- package/dist/tui/ink/init-app.js +18 -7
- package/package.json +1 -1
- package/skills/tickmarkr-auto/SKILL.md +10 -1
- package/skills/tickmarkr-loop/SKILL.md +72 -1
- package/skills/tickmarkr-overseer/SKILL.md +12 -0
- package/skills/tickmarkr-overseer/scripts/watch-context.sh +13 -2
package/dist/drivers/herdr.js
CHANGED
|
@@ -6,7 +6,7 @@ import { declaredInputBoxForWorkerName, matchesEmptyInputBox, matchesInputBox, m
|
|
|
6
6
|
import { consumePaneLaunchIntent, PANE_IDENTITY_ENV, paneIdentityLine } from "../brand.js";
|
|
7
7
|
import { createWorktree, sh } from "../run/git.js";
|
|
8
8
|
import { Journal } from "../run/journal.js";
|
|
9
|
-
import { readSupervision } from "../run/supervision.js";
|
|
9
|
+
import { readSupervision, readWatchBoard, reserveWatchBoard, stopWatchBoard, WATCH_OWNER_ENV } from "../run/supervision.js";
|
|
10
10
|
import { herdrSealShellPrefix } from "./subprocess.js";
|
|
11
11
|
import { canonicalizeLegacyName, formatOwnedName, panesToClose, parseOwnedName } from "./types.js";
|
|
12
12
|
// VIS-09 P43-03: adopted safety floor from 43-MEASUREMENT.md (narrowest safe 53 → floor 108).
|
|
@@ -103,6 +103,7 @@ export function boardSplitPlan(_callerCols) {
|
|
|
103
103
|
* `role:"other", taskId:"<the whole name>"` for any unrecognised string, so keying on taskId alone
|
|
104
104
|
* makes EVERY one-off pane its own "task" and gives it a group tab. `watch` is excluded for the same
|
|
105
105
|
* reason in the other direction — its taskId is the literal "run", which is a board, not a task. */
|
|
106
|
+
const shortTitle = (text) => [...text].slice(0, 20).join("");
|
|
106
107
|
const TASK_TAB_ROLES = new Set(["worker", "judge", "review", "consult"]);
|
|
107
108
|
export function taskGroupOf(name) {
|
|
108
109
|
const { role, taskId } = canonicalizeLegacyName(name, "");
|
|
@@ -123,10 +124,10 @@ export function taskGroupOf(name) {
|
|
|
123
124
|
export function tabLabelFor(name) {
|
|
124
125
|
const { role, taskId, attempt } = canonicalizeLegacyName(name, "");
|
|
125
126
|
if (!TASK_TAB_ROLES.has(role) || !taskId.trim())
|
|
126
|
-
return name;
|
|
127
|
+
return shortTitle(name);
|
|
127
128
|
if (role !== "worker")
|
|
128
|
-
return `${role.toUpperCase()} ${taskId}
|
|
129
|
-
return attempt > 0 ? `${taskId}↻${attempt}` : taskId;
|
|
129
|
+
return shortTitle(`${role.toUpperCase()} ${taskId}`);
|
|
130
|
+
return shortTitle(attempt > 0 ? `${taskId}↻${attempt}` : taskId);
|
|
130
131
|
}
|
|
131
132
|
/** Gate panes ride with the task they belong to and never consume the tab cap; everything else does.
|
|
132
133
|
* Scoped to the three GATE roles deliberately — an earlier cut of this said "not a worker", which let
|
|
@@ -168,6 +169,7 @@ export class HerdrDriver {
|
|
|
168
169
|
ws = process.env.HERDR_WORKSPACE_ID;
|
|
169
170
|
callerPane = process.env.HERDR_PANE_ID;
|
|
170
171
|
watches = new Map();
|
|
172
|
+
watchTokens = new WeakMap();
|
|
171
173
|
constructor(bin = "herdr", workersPerTab = 3, time = SYSTEM_TIME, journal) {
|
|
172
174
|
this.bin = bin;
|
|
173
175
|
this.workersPerTab = workersPerTab;
|
|
@@ -469,7 +471,7 @@ export class HerdrDriver {
|
|
|
469
471
|
// the worktree — that root pane IS the worker pane. The old one-shot `agent start … -- bash` verb
|
|
470
472
|
// (which named a fresh bash pane) was removed; 0.7.5's `agent start` only ATTACHES to a DETECTED
|
|
471
473
|
// agent CLI, so tickmarkr names the bash pane itself via `pane rename` and types the worker command in.
|
|
472
|
-
const t = await this.herdr(`tab create --label ${shq(label)} --no-focus --workspace ${shq(this.ws)} --cwd ${shq(cwd)}`);
|
|
474
|
+
const t = await this.herdr(`tab create --label ${shq(shortTitle(label))} --no-focus --workspace ${shq(this.ws)} --cwd ${shq(cwd)}`);
|
|
473
475
|
if (t.code !== 0)
|
|
474
476
|
throw new Error(`herdr tab create failed (exit ${t.code}, refusing untargeted placement): ${t.stderr || t.stdout}`);
|
|
475
477
|
let res;
|
|
@@ -597,7 +599,7 @@ export class HerdrDriver {
|
|
|
597
599
|
const label = !token ? entry.label
|
|
598
600
|
: entry.label === token ? `${token}${glyph}`
|
|
599
601
|
: `${entry.label} · ${token}${glyph}`;
|
|
600
|
-
const cmd = `tab rename ${shq(entry.tabId)} ${shq(label)}`;
|
|
602
|
+
const cmd = `tab rename ${shq(entry.tabId)} ${shq(shortTitle(label))}`;
|
|
601
603
|
const ok = async () => (await this.herdr(cmd)).code === 0;
|
|
602
604
|
if (await ok() || await ok())
|
|
603
605
|
return;
|
|
@@ -1113,12 +1115,10 @@ export class HerdrDriver {
|
|
|
1113
1115
|
await this.herdr(`notification show ${shq(msg)} --sound ${opts?.tier === "attention" ? "request" : opts?.sound ?? "request"}`);
|
|
1114
1116
|
}
|
|
1115
1117
|
async close(slot) {
|
|
1116
|
-
if (
|
|
1118
|
+
if (parseOwnedName(slot.name)?.role === "watch") {
|
|
1119
|
+
await this.retireWatch(slot);
|
|
1117
1120
|
this.watches.delete(slot.name);
|
|
1118
|
-
|
|
1119
|
-
if (pane)
|
|
1120
|
-
await this.herdr(`pane close ${shq(pane)}`);
|
|
1121
|
-
return; // run-end reconcile may already have reaped it; never close a compacted stale id
|
|
1121
|
+
return;
|
|
1122
1122
|
}
|
|
1123
1123
|
if (slot.group && this.groups.has(slot.group)) {
|
|
1124
1124
|
return this.serial(() => this.closeGrouped(slot));
|
|
@@ -1158,29 +1158,63 @@ export class HerdrDriver {
|
|
|
1158
1158
|
this.groups.delete(slot.group); // group dies when all generations gone
|
|
1159
1159
|
}
|
|
1160
1160
|
}
|
|
1161
|
-
|
|
1162
|
-
// this run's own name alike. Both are retired before a new board opens (narrator): what a pane this
|
|
1163
|
-
// process did not create is actually RUNNING cannot be read back, and the pre-v1.94 implementation
|
|
1164
|
-
// launched a bare `tickmarkr status --watch`, which follows the newest journal.
|
|
1165
|
-
async ownedWatchPanes() {
|
|
1161
|
+
async watchPanes(name) {
|
|
1166
1162
|
if (!this.ws)
|
|
1167
1163
|
throw new Error("herdr watch placement requires HERDR_WORKSPACE_ID — refusing unseeded pane");
|
|
1168
1164
|
const list = await this.herdr("pane list");
|
|
1169
1165
|
if (list.code !== 0)
|
|
1170
1166
|
throw new Error(`herdr pane list failed: ${list.stderr || list.stdout}`);
|
|
1167
|
+
const panes = JSON.parse(list.stdout).result?.panes;
|
|
1168
|
+
if (!Array.isArray(panes))
|
|
1169
|
+
throw new Error("herdr pane list returned no panes");
|
|
1170
|
+
return panes.filter(p => p.workspace_id === this.ws && p.label === name);
|
|
1171
|
+
}
|
|
1172
|
+
/** Name collisions never confer repository ownership. Unknown boards stay protected. */
|
|
1173
|
+
async retireWatch(slot) {
|
|
1174
|
+
const runId = parseOwnedName(slot.name)?.runId;
|
|
1175
|
+
const owner = runId ? readWatchBoard(slot.cwd, runId) : undefined;
|
|
1176
|
+
const matches = await this.watchPanes(slot.name);
|
|
1177
|
+
if (matches.length === 0)
|
|
1178
|
+
throw new Error(`watch ${slot.name} closed without presence acknowledgement; cleanup unconfirmed`);
|
|
1179
|
+
if (!owner || owner.driver !== this.id || owner.name !== slot.name || owner.workspace !== this.ws ||
|
|
1180
|
+
(this.watchTokens.has(slot) && this.watchTokens.get(slot) !== owner.token) ||
|
|
1181
|
+
owner.pane !== slot.id || matches.length !== 1 || matches[0]?.pane_id !== owner.pane) {
|
|
1182
|
+
throw new Error(`watch ownership unknown or foreign for ${slot.name}; existing board protected`);
|
|
1183
|
+
}
|
|
1184
|
+
await stopWatchBoard(owner, this.time);
|
|
1185
|
+
const verified = await this.watchPanes(slot.name);
|
|
1186
|
+
if (verified.length !== 1 || verified[0]?.pane_id !== owner.pane)
|
|
1187
|
+
throw new Error("watch target changed after acknowledgement; pane protected");
|
|
1188
|
+
const closed = await this.herdr(`pane close ${shq(owner.pane)}`);
|
|
1189
|
+
if (closed.code !== 0 || await this.paneStillOpen(owner.pane))
|
|
1190
|
+
throw new Error(`watch ${slot.name} survived acknowledged close`);
|
|
1191
|
+
}
|
|
1192
|
+
async focus(target) {
|
|
1193
|
+
const { slot, runId, taskId, attempt } = target;
|
|
1194
|
+
if (slot.name !== formatOwnedName({ role: "worker", taskId, attempt, runId }) || !target.workspace || target.workspace !== this.ws) {
|
|
1195
|
+
return { status: "foreign", reason: "Recorded run/task/attempt or workspace does not match this driver" };
|
|
1196
|
+
}
|
|
1197
|
+
const r = await this.herdr("pane list", slot.cwd);
|
|
1198
|
+
if (r.code !== 0)
|
|
1199
|
+
return { status: "unsupported", reason: "Cannot verify the live pane list" };
|
|
1171
1200
|
let panes;
|
|
1172
1201
|
try {
|
|
1173
|
-
panes = JSON.parse(
|
|
1202
|
+
panes = JSON.parse(r.stdout).result?.panes;
|
|
1174
1203
|
}
|
|
1175
1204
|
catch {
|
|
1176
|
-
|
|
1205
|
+
return { status: "unsupported", reason: "Unreadable pane list" };
|
|
1177
1206
|
}
|
|
1178
1207
|
if (!Array.isArray(panes))
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
return
|
|
1183
|
-
|
|
1208
|
+
return { status: "unsupported", reason: "Missing pane list" };
|
|
1209
|
+
const named = panes.filter(p => p.label === slot.name && p.workspace_id === target.workspace);
|
|
1210
|
+
if (!named.length)
|
|
1211
|
+
return { status: panes.some(p => p.pane_id === slot.id) ? "foreign" : "closed", reason: "Recorded pane is no longer owned by this attempt; open task evidence" };
|
|
1212
|
+
if (named.length !== 1 || named[0]?.pane_id !== slot.id || (slot.tabId && named[0]?.tab_id !== slot.tabId)) {
|
|
1213
|
+
return { status: "foreign", reason: "Live pane identity differs from the recorded task attempt" };
|
|
1214
|
+
}
|
|
1215
|
+
const focused = await this.herdr(`pane focus ${shq(slot.id)}`, slot.cwd);
|
|
1216
|
+
return focused.code === 0 ? { status: "focused", reason: `Verified ${slot.name} in ${target.workspace}` }
|
|
1217
|
+
: { status: "unsupported", reason: focused.stderr || focused.stdout || "Host refused pane focus" };
|
|
1184
1218
|
}
|
|
1185
1219
|
// T2: the watch is a sibling of the daemon's own pane, never a separate tab — placed to the RIGHT
|
|
1186
1220
|
// of it, always, whatever the terminal measures. Its durable owned name is how a
|
|
@@ -1243,38 +1277,34 @@ export class HerdrDriver {
|
|
|
1243
1277
|
}
|
|
1244
1278
|
throw new Error(orphan === null ? why : `${why} — and the split pane ${pane} survived its close (${orphan})`);
|
|
1245
1279
|
}
|
|
1246
|
-
|
|
1247
|
-
// a board this driver instance itself opened is reused (this.watches); any other surviving board —
|
|
1248
|
-
// a prior run's, or one already carrying this run's canonical name after a resume — is retired and
|
|
1249
|
-
// re-split, because adoption cannot restart or even read the process inside it and a pre-v1.94 pane
|
|
1250
|
-
// is running the bare `tickmarkr status --watch`, which narrates the newest journal instead of this
|
|
1251
|
-
// run. The retirement is VERIFIED gone before the replacement splits: reconcile is no backstop here
|
|
1252
|
-
// (panesToClose skips role "watch" by design, types.ts:92), so an unverified close would leave two
|
|
1253
|
-
// boards bound to different runs. Failures propagate — the daemon swallows.
|
|
1280
|
+
/** Only this repository's matching run may replace its acknowledged board. */
|
|
1254
1281
|
async narrator(cwd, command, runId) {
|
|
1255
|
-
|
|
1282
|
+
if (!this.ws)
|
|
1283
|
+
throw new Error("herdr watch placement requires HERDR_WORKSPACE_ID");
|
|
1284
|
+
if (!runId)
|
|
1285
|
+
throw new Error("herdr narrator requires a run identity");
|
|
1286
|
+
const name = formatOwnedName({ role: "watch", taskId: "run", attempt: 0, runId });
|
|
1256
1287
|
return this.serial(async () => {
|
|
1288
|
+
const matches = await this.watchPanes(name);
|
|
1257
1289
|
const cached = this.watches.get(name);
|
|
1258
|
-
if (cached
|
|
1290
|
+
if (cached && matches.length === 1 && matches[0]?.pane_id === cached.id &&
|
|
1291
|
+
readWatchBoard(cwd, runId)?.token === this.watchTokens.get(cached))
|
|
1259
1292
|
return cached;
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
}
|
|
1268
|
-
const s = await this.watchSlot(cwd, name);
|
|
1269
|
-
this.watches.set(name, s);
|
|
1293
|
+
if (matches.length > 1)
|
|
1294
|
+
throw new Error(`watch ownership ambiguous for ${name}; existing boards protected`);
|
|
1295
|
+
if (matches.length === 1)
|
|
1296
|
+
await this.retireWatch({ id: matches[0].pane_id, name, cwd });
|
|
1297
|
+
const slot = await this.watchSlot(cwd, name);
|
|
1298
|
+
const owner = reserveWatchBoard({ repo: cwd, runId, driver: this.id, workspace: this.ws, pane: slot.id, name });
|
|
1299
|
+
this.watchTokens.set(slot, owner.token);
|
|
1270
1300
|
try {
|
|
1271
|
-
await this.deliverPersistentShellCommand(
|
|
1301
|
+
await this.deliverPersistentShellCommand(slot, `${WATCH_OWNER_ENV}=${shq(owner.token)} ${command}`);
|
|
1302
|
+
this.watches.set(name, slot);
|
|
1303
|
+
return slot;
|
|
1272
1304
|
}
|
|
1273
|
-
catch (
|
|
1274
|
-
|
|
1275
|
-
throw err;
|
|
1305
|
+
catch (error) {
|
|
1306
|
+
throw new Error(`watch launch unconfirmed for ${name}; pane protected: ${String(error)}`);
|
|
1276
1307
|
}
|
|
1277
|
-
return s;
|
|
1278
1308
|
});
|
|
1279
1309
|
}
|
|
1280
1310
|
// OBS-17 T2 / v1.22b T1: close THIS RUN'S OWN tickmarkr-owned panes that should not exist
|
package/dist/drivers/orca.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ShResult } from "../run/git.js";
|
|
2
2
|
import { type JournalEvent } from "../run/journal.js";
|
|
3
|
-
import { type ExecutorDriver, type NotifyOpts, type Slot, type SlotOpts } from "./types.js";
|
|
3
|
+
import { type ExecutorDriver, type FocusTarget, type FocusResult, type NotifyOpts, type Slot, type SlotOpts } from "./types.js";
|
|
4
4
|
/** The response families the ONE shared envelope parser serves. There is no second JSON seam. */
|
|
5
5
|
export declare const ORCA_RESPONSE_FAMILIES: readonly ["status", "create", "list", "read", "send", "wait", "show", "close", "worktree-current", "worktree-set", "hooks-status"];
|
|
6
6
|
export type OrcaFamily = (typeof ORCA_RESPONSE_FAMILIES)[number];
|
|
@@ -200,7 +200,8 @@ export declare class OrcaDriver implements ExecutorDriver {
|
|
|
200
200
|
waitAgentStatus(slot: Slot, status: string, timeoutMs: number): Promise<boolean>;
|
|
201
201
|
sendKey(slot: Slot, key: string): Promise<void>;
|
|
202
202
|
nudge(slot: Slot, message: string): Promise<boolean>;
|
|
203
|
-
narrator(
|
|
203
|
+
narrator(_cwd: string, _command: string, runId?: string): Promise<Slot>;
|
|
204
|
+
focus(target: FocusTarget): Promise<FocusResult>;
|
|
204
205
|
project(taskId: string, state: "in-progress" | "in-review" | "completed"): Promise<void>;
|
|
205
206
|
private setWorkspaceStatus;
|
|
206
207
|
notify(msg: string, opts?: NotifyOpts): Promise<void>;
|
package/dist/drivers/orca.js
CHANGED
|
@@ -997,12 +997,51 @@ export class OrcaDriver {
|
|
|
997
997
|
return false;
|
|
998
998
|
}
|
|
999
999
|
}
|
|
1000
|
-
async narrator(
|
|
1000
|
+
async narrator(_cwd, _command, runId) {
|
|
1001
1001
|
if (!runId)
|
|
1002
1002
|
throw new OrcaError("create", "Orca narrator requires a run identity", "");
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1003
|
+
// The recorded Orca API can create a tab but provides no right/no-focus
|
|
1004
|
+
// placement receipt. Creating one would advertise a board we did not place.
|
|
1005
|
+
throw new OrcaError("create", "Orca narrator placement unsupported: right/no-focus board placement is not available", "");
|
|
1006
|
+
}
|
|
1007
|
+
async focus(target) {
|
|
1008
|
+
const { slot, runId, taskId, attempt } = target;
|
|
1009
|
+
const cwd = canonicalWorktreePath(slot.cwd);
|
|
1010
|
+
if (slot.name !== formatOwnedName({ role: "worker", taskId, attempt, runId })) {
|
|
1011
|
+
return { status: "foreign", reason: "Recorded run/task/attempt ownership does not match" };
|
|
1012
|
+
}
|
|
1013
|
+
try {
|
|
1014
|
+
const env = await this.listAll(cwd);
|
|
1015
|
+
const rows = env.result.terminals;
|
|
1016
|
+
const layouts = env.result.visualLayouts;
|
|
1017
|
+
if (!Array.isArray(rows) || !Array.isArray(layouts))
|
|
1018
|
+
return { status: "unsupported", reason: "Cannot verify Orca terminal ownership" };
|
|
1019
|
+
const handles = [];
|
|
1020
|
+
for (const layout of layouts) {
|
|
1021
|
+
if (typeof layout !== "object" || layout === null)
|
|
1022
|
+
continue;
|
|
1023
|
+
const lo = layout;
|
|
1024
|
+
if (!sameWorktree(terminalWorktree(lo), cwd))
|
|
1025
|
+
continue;
|
|
1026
|
+
const tabs = [];
|
|
1027
|
+
collectTabs(lo.root, tabs);
|
|
1028
|
+
for (const tab of tabs) {
|
|
1029
|
+
if (typeof tab === "object" && tab !== null && str(tab.title) === slot.name)
|
|
1030
|
+
collectPaneHandles(tab.panes, handles);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
if (handles.length !== 1)
|
|
1034
|
+
return { status: rows.length ? "foreign" : "closed", reason: "No unique owned terminal in the recorded worktree" };
|
|
1035
|
+
const matches = rows.filter(row => typeof row === "object" && row !== null && str(row.handle) === handles[0] && sameWorktree(terminalWorktree(row), cwd));
|
|
1036
|
+
if (matches.length !== 1)
|
|
1037
|
+
return { status: "foreign", reason: "Terminal worktree ownership is unverified" };
|
|
1038
|
+
if (matches[0].connected === false || matches[0].orphaned === true)
|
|
1039
|
+
return { status: "closed", reason: "Recorded terminal is no longer running; open task evidence" };
|
|
1040
|
+
return { status: "unsupported", reason: "Owned terminal verified; this Orca API has no focus operation. Open task evidence with Enter" };
|
|
1041
|
+
}
|
|
1042
|
+
catch (error) {
|
|
1043
|
+
return { status: "unsupported", reason: `Cannot verify Orca focus target: ${String(error)}` };
|
|
1044
|
+
}
|
|
1006
1045
|
}
|
|
1007
1046
|
async project(taskId, state) {
|
|
1008
1047
|
const worktree = this.taskWorktrees.get(taskId);
|
|
@@ -24,6 +24,7 @@ export declare class SubprocessDriver implements ExecutorDriver {
|
|
|
24
24
|
waitAgentStatus(slot: Slot, status: string, timeoutMs: number): Promise<boolean>;
|
|
25
25
|
status(_slot: Slot): Promise<string>;
|
|
26
26
|
read(slot: Slot, lines: number): Promise<string>;
|
|
27
|
+
focus(): Promise<import("./types.js").FocusResult>;
|
|
27
28
|
notify(msg: string, _opts?: NotifyOpts): Promise<void>;
|
|
28
29
|
close(slot: Slot): Promise<void>;
|
|
29
30
|
worktree(repo: string, branch: string, baseRef: string): Promise<string>;
|
|
@@ -107,6 +107,9 @@ export class SubprocessDriver {
|
|
|
107
107
|
async read(slot, lines) {
|
|
108
108
|
return this.state(slot).buf.split("\n").slice(-lines).join("\n");
|
|
109
109
|
}
|
|
110
|
+
async focus() {
|
|
111
|
+
return { status: "unsupported", reason: "Subprocess workers have no visible pane; open task evidence with Enter" };
|
|
112
|
+
}
|
|
110
113
|
async notify(msg, _opts) {
|
|
111
114
|
if (_opts?.tier === "routine")
|
|
112
115
|
return;
|
package/dist/drivers/types.d.ts
CHANGED
|
@@ -66,6 +66,18 @@ export declare function panesToClose(agents: FleetAgent[], desired: Set<string>,
|
|
|
66
66
|
tabId?: string;
|
|
67
67
|
}[];
|
|
68
68
|
export declare function canonicalizeLegacyName(name: string, runId: string): OwnedName;
|
|
69
|
+
export interface FocusTarget {
|
|
70
|
+
repo: string;
|
|
71
|
+
runId: string;
|
|
72
|
+
taskId: string;
|
|
73
|
+
attempt: number;
|
|
74
|
+
slot: Slot;
|
|
75
|
+
workspace?: string;
|
|
76
|
+
}
|
|
77
|
+
export type FocusResult = {
|
|
78
|
+
status: "focused" | "foreign" | "closed" | "unsupported";
|
|
79
|
+
reason: string;
|
|
80
|
+
};
|
|
69
81
|
export interface SlotPlacement {
|
|
70
82
|
surface?: string;
|
|
71
83
|
hostPlatform?: string;
|
|
@@ -76,6 +88,8 @@ export interface ExecutorDriver {
|
|
|
76
88
|
/** The exact terminal read surface used for liveness evidence. */
|
|
77
89
|
readSource?: string;
|
|
78
90
|
/** Placement facts returned by drivers whose terminal host exposes them. */
|
|
91
|
+
/** Verify the recorded identity against the live host before any focus mutation. */
|
|
92
|
+
focus?(target: FocusTarget): Promise<FocusResult>;
|
|
79
93
|
describe?(slot: Slot): SlotPlacement | Promise<SlotPlacement> | undefined;
|
|
80
94
|
slot(cwd: string, name: string, opts?: SlotOpts): Promise<Slot>;
|
|
81
95
|
run(slot: Slot, cmd: string): Promise<void>;
|
|
@@ -23,6 +23,27 @@ export type CaptureArtifactManifest = {
|
|
|
23
23
|
readonly artifacts: readonly CaptureArtifactManifestEntry[];
|
|
24
24
|
};
|
|
25
25
|
export declare const CAPTURE_PRODUCERS: readonly [{
|
|
26
|
+
readonly id: "screen-soak";
|
|
27
|
+
readonly provenance: {
|
|
28
|
+
readonly source: "tests/fixtures/screen-soak/soak.mjs";
|
|
29
|
+
readonly entrypoint: "sample";
|
|
30
|
+
readonly revision: "C6-four-hour-production-v1";
|
|
31
|
+
};
|
|
32
|
+
}, {
|
|
33
|
+
readonly id: "screen-soak-archive";
|
|
34
|
+
readonly provenance: {
|
|
35
|
+
readonly source: "tests/fixtures/screen-soak/archive.mjs";
|
|
36
|
+
readonly entrypoint: "archiveRecord";
|
|
37
|
+
readonly revision: "C6-lossless-gzip-v1";
|
|
38
|
+
};
|
|
39
|
+
}, {
|
|
40
|
+
readonly id: "cockpit-final-shell";
|
|
41
|
+
readonly provenance: {
|
|
42
|
+
readonly source: "src/tui/cockpit/capture.ts";
|
|
43
|
+
readonly entrypoint: "captureShellOutput";
|
|
44
|
+
readonly revision: "final-shell-v1";
|
|
45
|
+
};
|
|
46
|
+
}, {
|
|
26
47
|
readonly id: "cockpit-golden-frames";
|
|
27
48
|
readonly provenance: {
|
|
28
49
|
readonly source: "src/tui/cockpit/capture.ts";
|
|
@@ -40,6 +61,27 @@ export declare const CAPTURE_PRODUCERS: readonly [{
|
|
|
40
61
|
export declare const CAPTURE_ARTIFACT_MANIFEST: {
|
|
41
62
|
readonly version: 1;
|
|
42
63
|
readonly producers: readonly [{
|
|
64
|
+
readonly id: "screen-soak";
|
|
65
|
+
readonly provenance: {
|
|
66
|
+
readonly source: "tests/fixtures/screen-soak/soak.mjs";
|
|
67
|
+
readonly entrypoint: "sample";
|
|
68
|
+
readonly revision: "C6-four-hour-production-v1";
|
|
69
|
+
};
|
|
70
|
+
}, {
|
|
71
|
+
readonly id: "screen-soak-archive";
|
|
72
|
+
readonly provenance: {
|
|
73
|
+
readonly source: "tests/fixtures/screen-soak/archive.mjs";
|
|
74
|
+
readonly entrypoint: "archiveRecord";
|
|
75
|
+
readonly revision: "C6-lossless-gzip-v1";
|
|
76
|
+
};
|
|
77
|
+
}, {
|
|
78
|
+
readonly id: "cockpit-final-shell";
|
|
79
|
+
readonly provenance: {
|
|
80
|
+
readonly source: "src/tui/cockpit/capture.ts";
|
|
81
|
+
readonly entrypoint: "captureShellOutput";
|
|
82
|
+
readonly revision: "final-shell-v1";
|
|
83
|
+
};
|
|
84
|
+
}, {
|
|
43
85
|
readonly id: "cockpit-golden-frames";
|
|
44
86
|
readonly provenance: {
|
|
45
87
|
readonly source: "src/tui/cockpit/capture.ts";
|
|
@@ -62,6 +104,14 @@ export declare const CAPTURE_ARTIFACT_MANIFEST: {
|
|
|
62
104
|
path: string;
|
|
63
105
|
producer: string;
|
|
64
106
|
provenance: CaptureProducerProvenance;
|
|
107
|
+
} | {
|
|
108
|
+
path: string;
|
|
109
|
+
producer: string;
|
|
110
|
+
provenance: CaptureProducerProvenance;
|
|
111
|
+
} | {
|
|
112
|
+
path: string;
|
|
113
|
+
producer: string;
|
|
114
|
+
provenance: CaptureProducerProvenance;
|
|
65
115
|
})[];
|
|
66
116
|
};
|
|
67
117
|
/** Compatibility name for the pre-manifest gate API; now derived from one manifest. */
|
|
@@ -13,7 +13,19 @@ const COLOUR_PROVENANCE = {
|
|
|
13
13
|
entrypoint: "regenerateColourFrames",
|
|
14
14
|
revision: "colour-frame-v1",
|
|
15
15
|
};
|
|
16
|
+
const SHELL_PROVENANCE = {
|
|
17
|
+
source: "src/tui/cockpit/capture.ts", entrypoint: "captureShellOutput", revision: "final-shell-v1",
|
|
18
|
+
};
|
|
19
|
+
const SOAK_PROVENANCE = {
|
|
20
|
+
source: "tests/fixtures/screen-soak/soak.mjs", entrypoint: "sample", revision: "C6-four-hour-production-v1",
|
|
21
|
+
};
|
|
22
|
+
const SOAK_ARCHIVE_PROVENANCE = {
|
|
23
|
+
source: "tests/fixtures/screen-soak/archive.mjs", entrypoint: "archiveRecord", revision: "C6-lossless-gzip-v1",
|
|
24
|
+
};
|
|
16
25
|
export const CAPTURE_PRODUCERS = [
|
|
26
|
+
{ id: "screen-soak", provenance: SOAK_PROVENANCE },
|
|
27
|
+
{ id: "screen-soak-archive", provenance: SOAK_ARCHIVE_PROVENANCE },
|
|
28
|
+
{ id: "cockpit-final-shell", provenance: SHELL_PROVENANCE },
|
|
17
29
|
{ id: "cockpit-golden-frames", provenance: GOLDEN_PROVENANCE },
|
|
18
30
|
{ id: "cockpit-colour-frames", provenance: COLOUR_PROVENANCE },
|
|
19
31
|
];
|
|
@@ -49,6 +61,17 @@ export const CAPTURE_ARTIFACT_MANIFEST = {
|
|
|
49
61
|
version: 1,
|
|
50
62
|
producers: CAPTURE_PRODUCERS,
|
|
51
63
|
artifacts: [
|
|
64
|
+
// Exact measured artifacts only. Neighbouring harnesses, notes and unknown
|
|
65
|
+
// records remain logic; duration evidence never grants a directory exemption.
|
|
66
|
+
...["static", "growth", "cutover-static", "cutover-growth", "final-static", "final-growth", "retry-static", "retry-growth"].flatMap(attempt => ["build.json", "metadata.json", "samples.jsonl", "last-frame.ansi", "journal.jsonl.gz", "result.json.gz"].map(file => ({
|
|
67
|
+
path: `tests/fixtures/screen-soak/records/${attempt}/${file}`,
|
|
68
|
+
producer: file.endsWith(".gz") ? "screen-soak-archive" : "screen-soak",
|
|
69
|
+
provenance: provenanceCopy(file.endsWith(".gz") ? SOAK_ARCHIVE_PROVENANCE : SOAK_PROVENANCE),
|
|
70
|
+
}))),
|
|
71
|
+
...["home", "run", "evidence"].flatMap(view => ["120x40", "80x24"].map(size => ({
|
|
72
|
+
path: `tests/fixtures/cockpit/final/${view}.${size}.txt`,
|
|
73
|
+
producer: "cockpit-final-shell", provenance: provenanceCopy(SHELL_PROVENANCE),
|
|
74
|
+
}))),
|
|
52
75
|
...goldenFrameNames.map((fixture) => ({
|
|
53
76
|
path: `tests/fixtures/cockpit/frames/${fixture}`,
|
|
54
77
|
producer: "cockpit-golden-frames",
|
package/dist/gates/llm.d.ts
CHANGED
|
@@ -61,7 +61,12 @@ export interface LlmRunResult {
|
|
|
61
61
|
output: string;
|
|
62
62
|
exitCode?: number;
|
|
63
63
|
timedOut: boolean;
|
|
64
|
+
launchNeverStarted?: boolean;
|
|
65
|
+
seatAuthoredBytes?: number;
|
|
64
66
|
}
|
|
67
|
+
export declare const PROMPT_GLYPHS: readonly ["➜", "❯", "$", "%", ">>", ">"];
|
|
68
|
+
export declare function reviewSeatOutput(raw: string, nonce: string): string;
|
|
69
|
+
export declare const REVIEW_FIRST_LIVENESS_MS = 30000;
|
|
65
70
|
export declare function runHeadless(adapter: WorkerAdapter, model: string, prompt: string, cwd: string, timeoutMs?: number): Promise<string>;
|
|
66
71
|
export declare function runViaDriver(adapter: WorkerAdapter, model: string, prompt: string, cwd: string, via: LlmVia, timeoutMs?: number): Promise<string>;
|
|
67
72
|
export declare function dewrapPaneVerdict(out: string, nonce: string): string;
|