@mrciphersmith/keryx 0.2.122 → 0.2.123

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.
Files changed (2) hide show
  1. package/dist/cli.js +1761 -474
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -4194,7 +4194,19 @@ var init_exfil = __esm(() => {
4194
4194
 
4195
4195
  // src/lib/fs.ts
4196
4196
  import { randomUUID as randomUUID2 } from "crypto";
4197
+ import {
4198
+ chmodSync as chmodSync2,
4199
+ mkdirSync as mkdirSync2,
4200
+ readFileSync as readFileSync3,
4201
+ renameSync as renameSync2,
4202
+ rmdirSync,
4203
+ rmSync,
4204
+ statSync as statSync2,
4205
+ utimesSync,
4206
+ writeFileSync as writeFileSync2
4207
+ } from "fs";
4197
4208
  import { access, mkdir, readFile, rename, rm, stat, utimes, writeFile } from "fs/promises";
4209
+ import { hostname } from "os";
4198
4210
  import path8 from "path";
4199
4211
  async function pathExists(filePath) {
4200
4212
  try {
@@ -4319,7 +4331,197 @@ function processIsAlive(pid) {
4319
4331
  function delay(ms) {
4320
4332
  return new Promise((resolve) => setTimeout(resolve, ms));
4321
4333
  }
4322
- var DEFAULT_LOCK_STALE_MS = 30000;
4334
+ function readLeaseOwnerSync(lockPath) {
4335
+ try {
4336
+ const value = JSON.parse(readFileSync3(path8.join(lockPath, LEASE_OWNER_FILE), "utf8"));
4337
+ if (value === null || typeof value !== "object")
4338
+ return;
4339
+ const record = value;
4340
+ const wellFormed = typeof record.token === "string" && record.token.length > 0 && Number.isSafeInteger(record.pid) && Number(record.pid) > 0 && typeof record.host === "string" && typeof record.heartbeatAt === "string" && !Number.isNaN(Date.parse(record.heartbeatAt));
4341
+ return wellFormed ? record : undefined;
4342
+ } catch {
4343
+ return;
4344
+ }
4345
+ }
4346
+ function inspectLeaseSync(lockPath, opts) {
4347
+ const now = (opts.now ?? Date.now)();
4348
+ let dirMtimeMs;
4349
+ try {
4350
+ dirMtimeMs = statSync2(lockPath).mtimeMs;
4351
+ } catch (error) {
4352
+ if (isNotFound(error))
4353
+ return { state: "free" };
4354
+ throw error;
4355
+ }
4356
+ const holder = readLeaseOwnerSync(lockPath);
4357
+ if (holder === undefined) {
4358
+ return { state: now - dirMtimeMs <= opts.staleMs ? "live" : "gone", holder: undefined };
4359
+ }
4360
+ if (opts.reclaimIf?.(holder) === true) {
4361
+ return { state: "gone", holder };
4362
+ }
4363
+ if (now - Date.parse(holder.heartbeatAt) <= opts.staleMs) {
4364
+ return { state: "live", holder };
4365
+ }
4366
+ const thisHost = opts.host ?? hostname();
4367
+ const isAlive = opts.isAlive ?? processIsAlive;
4368
+ return { state: holder.host === thisHost && isAlive(holder.pid) ? "stale" : "gone", holder };
4369
+ }
4370
+ function acquireLeaseSync(lockPath, owner, opts) {
4371
+ return acquireOrReclaim(lockPath, owner, opts, false);
4372
+ }
4373
+ function reclaimStaleLeaseSync(lockPath, owner, opts) {
4374
+ return acquireOrReclaim(lockPath, owner, opts, true);
4375
+ }
4376
+ function acquireOrReclaim(lockPath, owner, opts, takeStale) {
4377
+ const now = opts.now ?? Date.now;
4378
+ mkdirSync2(path8.dirname(lockPath), { recursive: true, mode: 448 });
4379
+ for (let attempt = 0;attempt < MAX_LEASE_ATTEMPTS; attempt += 1) {
4380
+ const createdIno = tryCreateLease(lockPath, owner, opts.raceHook);
4381
+ if (createdIno !== undefined) {
4382
+ return { ok: true, handle: createLeaseHandle(lockPath, owner, createdIno, now, opts.raceHook) };
4383
+ }
4384
+ const seen = inspectLeaseSync(lockPath, opts);
4385
+ if (seen.state === "free")
4386
+ continue;
4387
+ if (seen.state === "live" || seen.state === "stale" && !takeStale) {
4388
+ return { ok: false, holder: seen.holder, state: seen.state };
4389
+ }
4390
+ removeLeaseDirIfUnchanged(lockPath, seen.holder?.token);
4391
+ }
4392
+ const last = inspectLeaseSync(lockPath, opts);
4393
+ return {
4394
+ ok: false,
4395
+ holder: last.state === "free" ? undefined : last.holder,
4396
+ state: last.state === "stale" ? "stale" : "live"
4397
+ };
4398
+ }
4399
+ function tryCreateLease(lockPath, owner, raceHook) {
4400
+ try {
4401
+ mkdirSync2(lockPath, { mode: 448 });
4402
+ } catch (error) {
4403
+ if (isAlreadyExistsError(error))
4404
+ return;
4405
+ throw error;
4406
+ }
4407
+ let createdIno;
4408
+ try {
4409
+ createdIno = statSync2(lockPath).ino;
4410
+ } catch (error) {
4411
+ if (isNotFound(error))
4412
+ return;
4413
+ throw error;
4414
+ }
4415
+ raceHook?.("create");
4416
+ const ownerPath = path8.join(lockPath, LEASE_OWNER_FILE);
4417
+ try {
4418
+ chmodSync2(lockPath, 448);
4419
+ writeFileSync2(ownerPath, JSON.stringify(owner), { encoding: "utf8", flag: "wx", mode: 384 });
4420
+ } catch (error) {
4421
+ const stillOurs = inodeOf(lockPath) === createdIno;
4422
+ if (stillOurs) {
4423
+ if (!isAlreadyExistsError(error))
4424
+ rmSync(ownerPath, { force: true });
4425
+ try {
4426
+ rmdirSync(lockPath);
4427
+ } catch {}
4428
+ }
4429
+ if (!stillOurs || isAlreadyExistsError(error))
4430
+ return;
4431
+ throw error;
4432
+ }
4433
+ return createdIno;
4434
+ }
4435
+ function inodeOf(target) {
4436
+ try {
4437
+ return statSync2(target).ino;
4438
+ } catch {
4439
+ return;
4440
+ }
4441
+ }
4442
+ function moveLeaseAside(lockPath, removeIf, keepOnConflict) {
4443
+ const aside = `${lockPath}.aside.${process.pid}.${randomUUID2()}`;
4444
+ try {
4445
+ renameSync2(lockPath, aside);
4446
+ } catch {
4447
+ return;
4448
+ }
4449
+ if (!removeIf(readLeaseOwnerSync(aside))) {
4450
+ try {
4451
+ renameSync2(aside, lockPath);
4452
+ return;
4453
+ } catch {
4454
+ if (keepOnConflict)
4455
+ return;
4456
+ }
4457
+ }
4458
+ rmSync(aside, { recursive: true, force: true });
4459
+ }
4460
+ function removeLeaseDirIfUnchanged(lockPath, expectedToken) {
4461
+ moveLeaseAside(lockPath, (moved) => moved === undefined || moved.token === expectedToken, false);
4462
+ }
4463
+ function createLeaseHandle(lockPath, initial, createdIno, now, raceHook) {
4464
+ let current = { ...initial };
4465
+ let released = false;
4466
+ const ownerPath = path8.join(lockPath, LEASE_OWNER_FILE);
4467
+ const ownership = () => {
4468
+ let ino;
4469
+ try {
4470
+ ino = statSync2(lockPath).ino;
4471
+ } catch (error) {
4472
+ const code = error?.code;
4473
+ return code === "ENOENT" || code === "ENOTDIR" ? "lost" : "unknown";
4474
+ }
4475
+ if (ino !== createdIno)
4476
+ return "lost";
4477
+ const record = readLeaseOwnerSync(lockPath);
4478
+ if (record === undefined)
4479
+ return "unknown";
4480
+ return record.token === initial.token ? "held" : "lost";
4481
+ };
4482
+ const holds = () => ownership() === "held";
4483
+ return {
4484
+ get owner() {
4485
+ return current;
4486
+ },
4487
+ refresh(patch) {
4488
+ if (released || !holds())
4489
+ return false;
4490
+ raceHook?.("refresh");
4491
+ const next = { ...current, ...patch, token: initial.token, heartbeatAt: new Date(now()).toISOString() };
4492
+ const tmp = path8.join(lockPath, `.${LEASE_OWNER_FILE}.${randomUUID2()}.tmp`);
4493
+ try {
4494
+ writeFileSync2(tmp, JSON.stringify(next), { encoding: "utf8", flag: "wx", mode: 384 });
4495
+ if (!holds()) {
4496
+ rmSync(tmp, { force: true });
4497
+ return false;
4498
+ }
4499
+ renameSync2(tmp, ownerPath);
4500
+ } catch {
4501
+ rmSync(tmp, { force: true });
4502
+ return false;
4503
+ }
4504
+ current = next;
4505
+ try {
4506
+ const touched = new Date;
4507
+ utimesSync(lockPath, touched, touched);
4508
+ } catch {}
4509
+ return true;
4510
+ },
4511
+ holds,
4512
+ ownership,
4513
+ release() {
4514
+ if (released)
4515
+ return;
4516
+ released = true;
4517
+ if (!holds())
4518
+ return;
4519
+ raceHook?.("release");
4520
+ moveLeaseAside(lockPath, (moved) => moved?.token === initial.token, true);
4521
+ }
4522
+ };
4523
+ }
4524
+ var DEFAULT_LOCK_STALE_MS = 30000, LEASE_OWNER_FILE = "owner.json", MAX_LEASE_ATTEMPTS = 3;
4323
4525
  var init_fs = () => {};
4324
4526
 
4325
4527
  // src/lib/contained-path.ts
@@ -6599,7 +6801,7 @@ var init_self_protect = __esm(() => {
6599
6801
  });
6600
6802
 
6601
6803
  // src/lib/descriptor-read.ts
6602
- import { constants, fstatSync, lstatSync as lstatSync2, statSync as statSync2 } from "fs";
6804
+ import { constants, fstatSync, lstatSync as lstatSync2, statSync as statSync3 } from "fs";
6603
6805
  import path18 from "path";
6604
6806
  import { dlopen, FFIType, ptr } from "bun:ffi";
6605
6807
  function readDescriptorChain(workspaceRoot, absolutePath, options = {}) {
@@ -6637,7 +6839,7 @@ function readDescriptorChain(workspaceRoot, absolutePath, options = {}) {
6637
6839
  const name = components.at(-1);
6638
6840
  if (!name)
6639
6841
  throw new DescriptorReadError("invalid-path", "safe source path has no final component");
6640
- const expected = options.expectedTargetIdentity ?? statSync2(absolutePath);
6842
+ const expected = options.expectedTargetIdentity ?? statSync3(absolutePath);
6641
6843
  const finalFlags = constants.O_RDONLY | constants.O_NOFOLLOW | (options.requireRegularFile ? constants.O_NONBLOCK ?? 0 : 0);
6642
6844
  const file = openAt(libc, directoryFd, name, finalFlags);
6643
6845
  opened.push(file);
@@ -25250,12 +25452,12 @@ var init_guarded_owner_writer = () => {};
25250
25452
 
25251
25453
  // src/session/store.ts
25252
25454
  import {
25253
- chmodSync as chmodSync3,
25455
+ chmodSync as chmodSync4,
25254
25456
  existsSync as existsSync15,
25255
- mkdirSync as mkdirSync4,
25457
+ mkdirSync as mkdirSync5,
25256
25458
  readdirSync,
25257
- renameSync as renameSync3,
25258
- writeFileSync as writeFileSync5
25459
+ renameSync as renameSync4,
25460
+ writeFileSync as writeFileSync6
25259
25461
  } from "fs";
25260
25462
  import path98 from "path";
25261
25463
  import { randomUUID as randomUUID9 } from "crypto";
@@ -25336,7 +25538,7 @@ function ensureDir(dir, dataDir) {
25336
25538
  if (shared) {
25337
25539
  ensureKeryxConfigDir();
25338
25540
  }
25339
- mkdirSync4(dir, { recursive: true, mode: 448 });
25541
+ mkdirSync5(dir, { recursive: true, mode: 448 });
25340
25542
  if (process.platform === "win32") {
25341
25543
  return;
25342
25544
  }
@@ -25355,13 +25557,13 @@ function ensureDir(dir, dataDir) {
25355
25557
  }
25356
25558
  function tighten(target) {
25357
25559
  try {
25358
- chmodSync3(target, 448);
25560
+ chmodSync4(target, 448);
25359
25561
  } catch {}
25360
25562
  }
25361
25563
  function atomicWriteText(file, body) {
25362
25564
  const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
25363
- writeFileSync5(tmp, body, { encoding: "utf8", mode: 384 });
25364
- renameSync3(tmp, file);
25565
+ writeFileSync6(tmp, body, { encoding: "utf8", mode: 384 });
25566
+ renameSync4(tmp, file);
25365
25567
  }
25366
25568
  function atomicWriteJson(file, value) {
25367
25569
  atomicWriteText(file, `${JSON.stringify(value, null, 2)}
@@ -28474,23 +28676,48 @@ async function openSlate(opts) {
28474
28676
  async function closeSlate(dir, mintAttemptId) {
28475
28677
  await archiveSlate(dir, mintAttemptId());
28476
28678
  }
28679
+ function detachSlateSession(ref) {
28680
+ if (ref !== undefined) {
28681
+ ref.detached = true;
28682
+ }
28683
+ }
28684
+ function isSlateSessionDetached(ref) {
28685
+ return ref.detached === true;
28686
+ }
28687
+ function slateSessionDir(ref) {
28688
+ return isSlateSessionDetached(ref) ? undefined : ref.dir;
28689
+ }
28690
+ async function writeSlateSession(ref, update) {
28691
+ if (isSlateSessionDetached(ref)) {
28692
+ return;
28693
+ }
28694
+ return writeSlate(ref.dir, update);
28695
+ }
28696
+ async function readSlateSession(ref) {
28697
+ if (isSlateSessionDetached(ref)) {
28698
+ return;
28699
+ }
28700
+ return readSlate(ref.dir);
28701
+ }
28477
28702
  async function ensureSlateOpened(ref, mintAttemptId, runtime) {
28703
+ if (isSlateSessionDetached(ref)) {
28704
+ return;
28705
+ }
28478
28706
  if (ref.opened) {
28479
28707
  const live = await readSlate(ref.dir);
28480
28708
  if (live !== undefined) {
28481
28709
  return;
28482
28710
  }
28483
28711
  }
28484
- await openSlate({
28485
- dir: ref.dir,
28486
- cwd: ref.cwd,
28487
- mintAttemptId,
28488
- ...runtime !== undefined ? { runtime } : {}
28489
- });
28712
+ const anchors = await computeAnchors({ cwd: ref.cwd, ...runtime !== undefined ? { runtime } : {} });
28713
+ if (isSlateSessionDetached(ref)) {
28714
+ return;
28715
+ }
28716
+ await openSlateAtomic(ref.dir, mintAttemptId, () => ({ anchors, course: {}, seeds: [] }));
28490
28717
  ref.opened = true;
28491
28718
  }
28492
28719
  async function closeSlateSession(ref, mintAttemptId) {
28493
- if (ref === undefined) {
28720
+ if (ref === undefined || isSlateSessionDetached(ref)) {
28494
28721
  return;
28495
28722
  }
28496
28723
  await closeSlate(ref.dir, mintAttemptId);
@@ -28540,6 +28767,12 @@ async function recordSlateTouch(dir, touched, extra) {
28540
28767
  });
28541
28768
  return { changed, slate };
28542
28769
  }
28770
+ async function recordSlateSessionTouch(ref, touched, extra) {
28771
+ if (isSlateSessionDetached(ref)) {
28772
+ return;
28773
+ }
28774
+ return recordSlateTouch(ref.dir, touched, extra);
28775
+ }
28543
28776
  function mintTimestampAttemptId(now = new Date) {
28544
28777
  timestampMintCounter += 1;
28545
28778
  return `${now.toISOString().replace(/:/g, "-")}-${timestampMintCounter}`;
@@ -29024,7 +29257,7 @@ async function resolveTerminalStateSnapshots(options) {
29024
29257
  const ref = options.slateSession;
29025
29258
  if (ref !== undefined && ref.opened) {
29026
29259
  try {
29027
- const slate = await readSlate(ref.dir);
29260
+ const slate = await readSlateSession(ref);
29028
29261
  if (slate !== undefined) {
29029
29262
  return { courseSnapshot: slate.course, anchorsSnapshot: slate.anchors };
29030
29263
  }
@@ -29044,9 +29277,10 @@ async function emitTerminalState(io, deps, options, reason) {
29044
29277
  };
29045
29278
  io.onTerminalState?.(state);
29046
29279
  const ref = options.slateSession;
29047
- if (ref !== undefined && ref.opened) {
29280
+ const terminalDir = ref !== undefined && ref.opened ? slateSessionDir(ref) : undefined;
29281
+ if (terminalDir !== undefined) {
29048
29282
  try {
29049
- await writeTerminalState(ref.dir, state);
29283
+ await writeTerminalState(terminalDir, state);
29050
29284
  } catch {}
29051
29285
  }
29052
29286
  const block = renderTerminalStateBlock(state);
@@ -29082,7 +29316,7 @@ async function closeSlateOnFlowDone(io, deps, options) {
29082
29316
  return;
29083
29317
  }
29084
29318
  try {
29085
- const slate = await readSlate(ref.dir);
29319
+ const slate = await readSlateSession(ref);
29086
29320
  const course = await courseFromSlate(ref.cwd, slate);
29087
29321
  if (isCourseDone(course)) {
29088
29322
  if (slate !== undefined) {
@@ -29147,7 +29381,7 @@ async function runAgentTurnCore(io, deps, history, userLine, options = {}) {
29147
29381
  if (options.slateSession !== undefined) {
29148
29382
  try {
29149
29383
  if (options.skipCloseTrigger !== true && isClosePhrase(userLine)) {
29150
- const liveSlate = await readSlate(options.slateSession.dir);
29384
+ const liveSlate = await readSlateSession(options.slateSession);
29151
29385
  if (liveSlate !== undefined) {
29152
29386
  await dispatchWrapUpBestEffort(io, options, "explicit", options.slateSession.cwd, options.slateSession.dir, liveSlate);
29153
29387
  }
@@ -29159,7 +29393,7 @@ async function runAgentTurnCore(io, deps, history, userLine, options = {}) {
29159
29393
  model: deps.modelId
29160
29394
  });
29161
29395
  if (!wasOpened && options.slateSession.opened) {
29162
- const freshSlate = await readSlate(options.slateSession.dir);
29396
+ const freshSlate = await readSlateSession(options.slateSession);
29163
29397
  if (freshSlate !== undefined) {
29164
29398
  history.push({ role: "user", content: renderAnchorsBlock(freshSlate.anchors), provenance: "project", ts: now() });
29165
29399
  io.onHistoryChange?.("tool");
@@ -29577,10 +29811,10 @@ ${modelOutput}` : modelOutput,
29577
29811
  if (options.slateSession !== undefined && options.slateSession.opened === true) {
29578
29812
  try {
29579
29813
  const touchedPaths = extractTouchedFromToolInput(call.name, parseToolInput3(call.input));
29580
- const touch = await recordSlateTouch(options.slateSession.dir, touchedPaths, {
29814
+ const touch = await recordSlateSessionTouch(options.slateSession, touchedPaths, {
29581
29815
  runtime: { provider: deps.providerId, model: deps.modelId }
29582
29816
  });
29583
- if (touch.changed) {
29817
+ if (touch?.changed === true) {
29584
29818
  anchorsToAnnounce = touch.slate.anchors;
29585
29819
  }
29586
29820
  } catch (err) {
@@ -40654,7 +40888,7 @@ var init_deep_enrich = __esm(() => {
40654
40888
  });
40655
40889
 
40656
40890
  // src/wiki/enrich.ts
40657
- import { existsSync as existsSync23, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "fs";
40891
+ import { existsSync as existsSync23, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "fs";
40658
40892
  import { readFile as readFile77, writeFile as writeFile42 } from "fs/promises";
40659
40893
  import path137 from "path";
40660
40894
  function resolveEnrichProviderModel(input) {
@@ -40672,7 +40906,7 @@ function loadResumeState(cwd) {
40672
40906
  if (!existsSync23(file)) {
40673
40907
  return { updatedAt: new Date().toISOString(), completed: [], failed: [] };
40674
40908
  }
40675
- const raw = JSON.parse(readFileSync6(file, "utf8"));
40909
+ const raw = JSON.parse(readFileSync7(file, "utf8"));
40676
40910
  if (raw === null || typeof raw !== "object") {
40677
40911
  return { updatedAt: new Date().toISOString(), completed: [], failed: [] };
40678
40912
  }
@@ -40695,8 +40929,8 @@ function isStringRecord(value) {
40695
40929
  function saveResumeState(cwd, state) {
40696
40930
  try {
40697
40931
  const file = resumeStatePath(cwd);
40698
- mkdirSync5(path137.dirname(file), { recursive: true });
40699
- writeFileSync6(file, `${JSON.stringify({ ...state, updatedAt: new Date().toISOString() }, null, 2)}
40932
+ mkdirSync6(path137.dirname(file), { recursive: true });
40933
+ writeFileSync7(file, `${JSON.stringify({ ...state, updatedAt: new Date().toISOString() }, null, 2)}
40700
40934
  `, "utf8");
40701
40935
  } catch {}
40702
40936
  }
@@ -43570,9 +43804,9 @@ var init_login = __esm(() => {
43570
43804
  });
43571
43805
 
43572
43806
  // src/tui/debug-watcher.ts
43573
- import { spawn as spawn6, spawnSync as spawnSync3 } from "child_process";
43574
- import { closeSync as closeSync3, constants as constants2, openSync as openSync3, readdirSync as readdirSync3, readFileSync as readFileSync9, readlinkSync, statSync as statSync6 } from "fs";
43575
- import path195 from "path";
43807
+ import { spawn as spawn6, spawnSync as spawnSync4 } from "child_process";
43808
+ import { closeSync as closeSync3, constants as constants2, openSync as openSync4, readdirSync as readdirSync3, readFileSync as readFileSync11, readlinkSync as readlinkSync3, statSync as statSync7 } from "fs";
43809
+ import path196 from "path";
43576
43810
  function parseEpollFdinfo(text) {
43577
43811
  const out = [];
43578
43812
  for (const line of text.split(`
@@ -43616,7 +43850,7 @@ function isAlive(pid) {
43616
43850
  }
43617
43851
  function readText(file) {
43618
43852
  try {
43619
- return readFileSync9(file, "utf8");
43853
+ return readFileSync11(file, "utf8");
43620
43854
  } catch {
43621
43855
  return;
43622
43856
  }
@@ -43633,7 +43867,7 @@ function listFds(pid) {
43633
43867
  const fd = Number(name);
43634
43868
  let link;
43635
43869
  try {
43636
- link = readlinkSync(`/proc/${pid}/fd/${name}`);
43870
+ link = readlinkSync3(`/proc/${pid}/fd/${name}`);
43637
43871
  } catch {
43638
43872
  continue;
43639
43873
  }
@@ -43659,26 +43893,77 @@ function threads(pid) {
43659
43893
  return { tid, comm: (readText(`${base}/comm`) ?? "").trim(), wchan: (readText(`${base}/wchan`) ?? "").trim(), state };
43660
43894
  });
43661
43895
  }
43662
- async function makePendingReader() {
43896
+ function ttyHolders(ttyPath) {
43897
+ const out = [];
43898
+ let pids;
43899
+ try {
43900
+ pids = readdirSync3("/proc").filter((name) => /^\d+$/.test(name));
43901
+ } catch {
43902
+ return out;
43903
+ }
43904
+ for (const pid of pids) {
43905
+ let fdNames;
43906
+ try {
43907
+ fdNames = readdirSync3(`/proc/${pid}/fd`);
43908
+ } catch {
43909
+ continue;
43910
+ }
43911
+ const fds = [];
43912
+ for (const fd of fdNames) {
43913
+ try {
43914
+ if (readlinkSync3(`/proc/${pid}/fd/${fd}`) === ttyPath)
43915
+ fds.push(Number(fd));
43916
+ } catch {}
43917
+ }
43918
+ if (fds.length === 0)
43919
+ continue;
43920
+ const cmd = (readText(`/proc/${pid}/cmdline`) ?? "").split("\x00").join(" ").trim().slice(0, 300);
43921
+ const ppid = /\)\s+\S\s+(\d+)/.exec(readText(`/proc/${pid}/stat`) ?? "")?.[1];
43922
+ out.push({ pid: Number(pid), ...ppid !== undefined ? { ppid } : {}, cmd, fds });
43923
+ }
43924
+ return out;
43925
+ }
43926
+ function decodeTermios(buf) {
43927
+ const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
43928
+ const iflag = view.getUint32(0, true);
43929
+ const lflag = view.getUint32(12, true);
43930
+ return {
43931
+ iflag,
43932
+ lflag,
43933
+ vmin: buf[CC_OFFSET + VMIN] ?? 0,
43934
+ vtime: buf[CC_OFFSET + VTIME] ?? 0,
43935
+ icanon: (lflag & ICANON) !== 0,
43936
+ echo: (lflag & ECHO) !== 0
43937
+ };
43938
+ }
43939
+ async function makeTtyProbes() {
43940
+ const none = { pending: () => {
43941
+ return;
43942
+ }, termios: () => {
43943
+ return;
43944
+ } };
43663
43945
  if (process.platform !== "linux") {
43664
- return () => {
43665
- return;
43666
- };
43946
+ return none;
43667
43947
  }
43668
43948
  try {
43669
43949
  const ffi = await import("bun:ffi");
43670
43950
  const libc = ffi.dlopen("libc.so.6", {
43671
- ioctl: { args: [ffi.FFIType.i32, ffi.FFIType.u64, ffi.FFIType.ptr], returns: ffi.FFIType.i32 }
43951
+ ioctl: { args: [ffi.FFIType.i32, ffi.FFIType.u64, ffi.FFIType.ptr], returns: ffi.FFIType.i32 },
43952
+ tcgetattr: { args: [ffi.FFIType.i32, ffi.FFIType.ptr], returns: ffi.FFIType.i32 }
43672
43953
  });
43673
- return (fd) => {
43674
- const out = new Int32Array(1);
43675
- const rc = libc.symbols.ioctl(fd, FIONREAD, ffi.ptr(out));
43676
- return rc === 0 ? out[0] ?? 0 : undefined;
43954
+ return {
43955
+ pending: (fd) => {
43956
+ const out = new Int32Array(1);
43957
+ const rc = libc.symbols.ioctl(fd, FIONREAD, ffi.ptr(out));
43958
+ return rc === 0 ? out[0] ?? 0 : undefined;
43959
+ },
43960
+ termios: (fd) => {
43961
+ const buf = new Uint8Array(TERMIOS_SIZE);
43962
+ return libc.symbols.tcgetattr(fd, ffi.ptr(buf)) === 0 ? decodeTermios(buf) : undefined;
43963
+ }
43677
43964
  };
43678
43965
  } catch {
43679
- return () => {
43680
- return;
43681
- };
43966
+ return none;
43682
43967
  }
43683
43968
  }
43684
43969
  function spawnDebugWatcher(run) {
@@ -43710,22 +43995,23 @@ async function runDebugWatcher(argv) {
43710
43995
  process.exitCode = 2;
43711
43996
  return;
43712
43997
  }
43713
- const logFile = path195.join(dirArg, "watcher.ndjson");
43714
- const shellLog = path195.join(dirArg, "shell.ndjson");
43998
+ const logFile = path196.join(dirArg, "watcher.ndjson");
43999
+ const shellLog = path196.join(dirArg, "shell.ndjson");
43715
44000
  const log = (kind, data = {}) => {
43716
44001
  try {
43717
44002
  appendOwnerOnlyLine(logFile, JSON.stringify({ ts: new Date().toISOString(), watcher: process.pid, target: pid, kind, ...data }));
43718
44003
  } catch {}
43719
44004
  };
43720
44005
  const linux = process.platform === "linux" && isAlive(pid) && readText(`/proc/${pid}/stat`) !== undefined;
43721
- const pendingOf = await makePendingReader();
44006
+ const probes = await makeTtyProbes();
44007
+ const pendingOf = probes.pending;
43722
44008
  let ttyPath;
43723
44009
  let ttyFd;
43724
44010
  if (linux) {
43725
44011
  try {
43726
- ttyPath = readlinkSync(`/proc/${pid}/fd/0`);
44012
+ ttyPath = readlinkSync3(`/proc/${pid}/fd/0`);
43727
44013
  if (isTtyLink(ttyPath)) {
43728
- ttyFd = openSync3(ttyPath, constants2.O_RDONLY | constants2.O_NONBLOCK | constants2.O_NOCTTY);
44014
+ ttyFd = openSync4(ttyPath, constants2.O_RDONLY | constants2.O_NONBLOCK | constants2.O_NOCTTY);
43729
44015
  }
43730
44016
  } catch (error) {
43731
44017
  log("tty.open-failed", { error: String(error) });
@@ -43734,13 +44020,14 @@ async function runDebugWatcher(argv) {
43734
44020
  log("watcher.start", { linux, ttyPath, fionread: ttyFd !== undefined && pendingOf(ttyFd) !== undefined });
43735
44021
  const history = [];
43736
44022
  let lastLogged = "";
44023
+ let lastTermios = "";
43737
44024
  let lastPeriodic = 0;
43738
44025
  let lastSignal = 0;
43739
44026
  const fullSnapshot = () => {
43740
44027
  const fds = listFds(pid);
43741
44028
  let stty;
43742
44029
  if (ttyPath !== undefined) {
43743
- const res = spawnSync3("stty", ["-a", "-F", ttyPath], { encoding: "utf8", timeout: 2000 });
44030
+ const res = spawnSync4("stty", ["-a", "-F", ttyPath], { encoding: "utf8", timeout: 2000 });
43744
44031
  stty = (res.stdout ?? "").trim() || (res.stderr ?? "").trim();
43745
44032
  }
43746
44033
  return {
@@ -43766,7 +44053,7 @@ async function runDebugWatcher(argv) {
43766
44053
  sample.pending = pendingOf(ttyFd);
43767
44054
  }
43768
44055
  try {
43769
- sample.heartbeatAgeMs = Date.now() - statSync6(shellLog).mtimeMs;
44056
+ sample.heartbeatAgeMs = Date.now() - statSync7(shellLog).mtimeMs;
43770
44057
  } catch {
43771
44058
  sample.heartbeatAgeMs = undefined;
43772
44059
  }
@@ -43802,10 +44089,25 @@ async function runDebugWatcher(argv) {
43802
44089
  }
43803
44090
  return true;
43804
44091
  };
44092
+ const termiosTimer = ttyFd === undefined ? undefined : setInterval(() => {
44093
+ const termios = probes.termios(ttyFd);
44094
+ const key = JSON.stringify(termios);
44095
+ if (termios !== undefined && key !== lastTermios) {
44096
+ const suspicious = termios.vmin === 0 || termios.icanon;
44097
+ log("termios", {
44098
+ ...termios,
44099
+ suspicious,
44100
+ ...suspicious && lastTermios !== "" && ttyPath !== undefined ? { ttyHolders: ttyHolders(ttyPath) } : {}
44101
+ });
44102
+ lastTermios = key;
44103
+ }
44104
+ }, 50);
43805
44105
  await new Promise((resolve) => {
43806
44106
  const timer = setInterval(() => {
43807
44107
  if (!tick()) {
43808
44108
  clearInterval(timer);
44109
+ if (termiosTimer !== undefined)
44110
+ clearInterval(termiosTimer);
43809
44111
  resolve();
43810
44112
  }
43811
44113
  }, 1000);
@@ -43817,7 +44119,7 @@ async function runDebugWatcher(argv) {
43817
44119
  }
43818
44120
  log("watcher.stop");
43819
44121
  }
43820
- var EPOLLIN = 1, FIONREAD = 21531, isTtyLink = (link) => link.startsWith("/dev/pts/") || /^\/dev\/tty/.test(link);
44122
+ var EPOLLIN = 1, FIONREAD = 21531, isTtyLink = (link) => link.startsWith("/dev/pts/") || /^\/dev\/tty/.test(link), TERMIOS_SIZE = 60, CC_OFFSET = 17, VTIME = 5, VMIN = 6, ICANON = 2, ECHO = 8;
43821
44123
  var init_debug_watcher = __esm(() => {
43822
44124
  init_config_dir();
43823
44125
  });
@@ -43918,23 +44220,23 @@ import path24 from "path";
43918
44220
  // src/lib/project-registry.ts
43919
44221
  init_config_dir();
43920
44222
  import {
43921
- chmodSync as chmodSync2,
44223
+ chmodSync as chmodSync3,
43922
44224
  closeSync as closeSync2,
43923
44225
  copyFileSync,
43924
44226
  existsSync as existsSync4,
43925
44227
  fsyncSync,
43926
44228
  openSync as openSync2,
43927
44229
  realpathSync as realpathSync2,
43928
- renameSync as renameSync2,
43929
- statSync as statSync4,
44230
+ renameSync as renameSync3,
44231
+ statSync as statSync5,
43930
44232
  unlinkSync as unlinkSync2,
43931
- writeFileSync as writeFileSync3
44233
+ writeFileSync as writeFileSync4
43932
44234
  } from "fs";
43933
44235
  import path23 from "path";
43934
44236
  import { randomUUID as randomUUID4 } from "crypto";
43935
44237
 
43936
44238
  // src/lib/file-lock.ts
43937
- import { closeSync, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync3, rmSync, statSync as statSync3, writeFileSync as writeFileSync2 } from "fs";
44239
+ import { closeSync, mkdirSync as mkdirSync3, openSync, readFileSync as readFileSync4, rmSync as rmSync2, statSync as statSync4, writeFileSync as writeFileSync3 } from "fs";
43938
44240
  import { randomUUID as randomUUID3 } from "crypto";
43939
44241
  import path22 from "path";
43940
44242
  var LOCK_STALE_MS = 1e4;
@@ -43950,7 +44252,7 @@ function sleepSync(ms) {
43950
44252
  }
43951
44253
  function withFileLock2(lockPath, fn, options = {}) {
43952
44254
  try {
43953
- mkdirSync2(path22.dirname(lockPath), { recursive: true, mode: 448 });
44255
+ mkdirSync3(path22.dirname(lockPath), { recursive: true, mode: 448 });
43954
44256
  } catch {
43955
44257
  return null;
43956
44258
  }
@@ -43966,18 +44268,18 @@ function withFileLock2(lockPath, fn, options = {}) {
43966
44268
  try {
43967
44269
  handle = openSync(lockPath, "wx", 384);
43968
44270
  try {
43969
- writeFileSync2(handle, nonce, { encoding: "utf8" });
44271
+ writeFileSync3(handle, nonce, { encoding: "utf8" });
43970
44272
  } catch (cause) {
43971
44273
  closeSync(handle);
43972
44274
  handle = null;
43973
- rmSync(lockPath, { force: true });
44275
+ rmSync2(lockPath, { force: true });
43974
44276
  throw cause;
43975
44277
  }
43976
44278
  } catch {
43977
- const age = statSync3(lockPath, { throwIfNoEntry: false })?.mtimeMs;
44279
+ const age = statSync4(lockPath, { throwIfNoEntry: false })?.mtimeMs;
43978
44280
  if (age !== undefined && Date.now() - age > LOCK_STALE_MS) {
43979
44281
  try {
43980
- rmSync(lockPath, { force: true });
44282
+ rmSync2(lockPath, { force: true });
43981
44283
  } catch {}
43982
44284
  }
43983
44285
  if (!announced && Date.now() - started > 1000) {
@@ -43994,8 +44296,8 @@ function withFileLock2(lockPath, fn, options = {}) {
43994
44296
  closeSync(handle);
43995
44297
  } catch {}
43996
44298
  try {
43997
- if (readFileSync3(lockPath, "utf8") === nonce) {
43998
- rmSync(lockPath, { force: true });
44299
+ if (readFileSync4(lockPath, "utf8") === nonce) {
44300
+ rmSync2(lockPath, { force: true });
43999
44301
  }
44000
44302
  } catch {}
44001
44303
  }
@@ -44117,7 +44419,7 @@ function tightenBackup(file) {
44117
44419
  return;
44118
44420
  }
44119
44421
  try {
44120
- chmodSync2(file, 384);
44422
+ chmodSync3(file, 384);
44121
44423
  } catch {}
44122
44424
  }
44123
44425
  function quarantineDamagedRegistry(dir, onWarn) {
@@ -44145,13 +44447,13 @@ function saveProjectRegistry(registry, dir, onWarn) {
44145
44447
  };
44146
44448
  const handle = openSync2(temp, "wx", 384);
44147
44449
  try {
44148
- writeFileSync3(handle, `${JSON.stringify(sorted, null, 2)}
44450
+ writeFileSync4(handle, `${JSON.stringify(sorted, null, 2)}
44149
44451
  `, { encoding: "utf8" });
44150
44452
  fsyncSync(handle);
44151
44453
  } finally {
44152
44454
  closeSync2(handle);
44153
44455
  }
44154
- renameSync2(temp, file);
44456
+ renameSync3(temp, file);
44155
44457
  return true;
44156
44458
  } catch {
44157
44459
  try {
@@ -44231,7 +44533,7 @@ function forgetProject(projectId, dir, onWarn) {
44231
44533
  return outcome ?? "write-failed";
44232
44534
  }
44233
44535
  function isInitializedProject(projectPath) {
44234
- return statSync4(path23.join(projectPath, ".metaproject"), { throwIfNoEntry: false })?.isDirectory() === true;
44536
+ return statSync5(path23.join(projectPath, ".metaproject"), { throwIfNoEntry: false })?.isDirectory() === true;
44235
44537
  }
44236
44538
  function listProjects(dir, onWarn) {
44237
44539
  const registry = loadProjectRegistry(dir, onWarn);
@@ -45182,7 +45484,7 @@ async function choice(question, choices, defaultValue) {
45182
45484
 
45183
45485
  // src/lib/project-sandbox-policy.ts
45184
45486
  init_paths();
45185
- import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
45487
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
45186
45488
  import path29 from "path";
45187
45489
 
45188
45490
  // src/harness/process/sandbox/network-run.ts
@@ -45376,7 +45678,7 @@ function loadProjectSandboxPolicy(cwdOrRoot) {
45376
45678
  if (!existsSync7(file)) {
45377
45679
  return {};
45378
45680
  }
45379
- const raw = JSON.parse(readFileSync4(file, "utf8"));
45681
+ const raw = JSON.parse(readFileSync5(file, "utf8"));
45380
45682
  return sanitizeProjectSandboxPolicy(raw);
45381
45683
  } catch {
45382
45684
  return {};
@@ -45399,8 +45701,8 @@ function writeProjectSandboxPolicySkeletonIfMissing(cwdOrRoot) {
45399
45701
  if (existsSync7(file)) {
45400
45702
  return false;
45401
45703
  }
45402
- mkdirSync3(path29.dirname(file), { recursive: true });
45403
- writeFileSync4(file, projectSandboxPolicySkeleton(), { mode: 420 });
45704
+ mkdirSync4(path29.dirname(file), { recursive: true });
45705
+ writeFileSync5(file, projectSandboxPolicySkeleton(), { mode: 420 });
45404
45706
  return true;
45405
45707
  } catch {
45406
45708
  return false;
@@ -45408,7 +45710,7 @@ function writeProjectSandboxPolicySkeletonIfMissing(cwdOrRoot) {
45408
45710
  }
45409
45711
 
45410
45712
  // src/gdskills/catalog.ts
45411
- import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
45713
+ import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
45412
45714
  import path30 from "path";
45413
45715
  import { fileURLToPath as fileURLToPath2 } from "url";
45414
45716
  var GDSKILLS_PROFILES = [
@@ -45903,7 +46205,7 @@ function readBundledSkillRouting(category, name) {
45903
46205
  if (file === undefined) {
45904
46206
  throw new Error(`bundled skill "${category}/${name}" ships no SKILL.md under src/gdskills/bundled/skills, and BUNDLED_GDSKILLS reads its description and triggers from there. Declare it with renderedSkill() if it has no file.`);
45905
46207
  }
45906
- const { description, triggers } = parseSkillFrontmatter(readFileSync5(file, "utf8"));
46208
+ const { description, triggers } = parseSkillFrontmatter(readFileSync6(file, "utf8"));
45907
46209
  if (description === undefined || description.length === 0 || triggers === undefined || triggers.length === 0) {
45908
46210
  throw new Error(`${file}: frontmatter must declare a non-empty \`description\` and \`triggers:\` list; the skill router scores both.`);
45909
46211
  }
@@ -50624,7 +50926,7 @@ import path47 from "path";
50624
50926
  // package.json
50625
50927
  var package_default = {
50626
50928
  name: "@mrciphersmith/keryx",
50627
- version: "0.2.122",
50929
+ version: "0.2.123",
50628
50930
  description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
50629
50931
  private: false,
50630
50932
  publishConfig: {
@@ -60575,7 +60877,7 @@ function escapeRegExp6(value) {
60575
60877
  }
60576
60878
 
60577
60879
  // src/gdskills/bundled-eval.ts
60578
- import { existsSync as existsSync25, readFileSync as readFileSync7, readdirSync as readdirSync2 } from "fs";
60880
+ import { existsSync as existsSync25, readFileSync as readFileSync8, readdirSync as readdirSync2 } from "fs";
60579
60881
  import path147 from "path";
60580
60882
 
60581
60883
  // src/gdskills/model-tier.ts
@@ -61514,7 +61816,7 @@ function evaluateBundledTree(root = defaultBundledRoot()) {
61514
61816
  const skill = path147.basename(skillDir);
61515
61817
  const rel = path147.relative(skillsRoot, file).split(path147.sep).join("/");
61516
61818
  const category = path147.dirname(rel).split("/")[0] ?? "";
61517
- const text = readFileSync7(file, "utf8");
61819
+ const text = readFileSync8(file, "utf8");
61518
61820
  const add = (check, line, message) => {
61519
61821
  findings.push({ check, skill, file: rel, line, message });
61520
61822
  };
@@ -61664,7 +61966,7 @@ function evaluateBundledTree(root = defaultBundledRoot()) {
61664
61966
  }
61665
61967
  for (const file of bundledRuleFiles(root)) {
61666
61968
  const rel = path147.relative(root, file).split(path147.sep).join("/");
61667
- const text = readFileSync7(file, "utf8");
61969
+ const text = readFileSync8(file, "utf8");
61668
61970
  const add = (check, line, message) => {
61669
61971
  findings.push({ check, skill: path147.basename(file, path147.extname(file)), file: rel, line, message });
61670
61972
  };
@@ -61674,7 +61976,7 @@ function evaluateBundledTree(root = defaultBundledRoot()) {
61674
61976
  const skillDir = path147.dirname(file);
61675
61977
  const skill = path147.basename(skillDir);
61676
61978
  const rel = path147.relative(skillsRoot, file).split(path147.sep).join("/");
61677
- const text = readFileSync7(file, "utf8");
61979
+ const text = readFileSync8(file, "utf8");
61678
61980
  const add = (check, line, message) => {
61679
61981
  findings.push({ check, skill, file: rel, line, message });
61680
61982
  };
@@ -61703,7 +62005,7 @@ function evaluateBundledTree(root = defaultBundledRoot()) {
61703
62005
  const dir = path147.dirname(file);
61704
62006
  let canonicalComparable;
61705
62007
  try {
61706
- canonicalComparable = buildComparableText(readFileSync7(file, "utf8"));
62008
+ canonicalComparable = buildComparableText(readFileSync8(file, "utf8"));
61707
62009
  } catch {
61708
62010
  continue;
61709
62011
  }
@@ -61716,7 +62018,7 @@ function evaluateBundledTree(root = defaultBundledRoot()) {
61716
62018
  continue;
61717
62019
  let buildComparable;
61718
62020
  try {
61719
- buildComparable = buildComparableText(readFileSync7(buildPath, "utf8"));
62021
+ buildComparable = buildComparableText(readFileSync8(buildPath, "utf8"));
61720
62022
  } catch {
61721
62023
  continue;
61722
62024
  }
@@ -74022,11 +74324,11 @@ function wrapSeatbelt(command, profile) {
74022
74324
  }
74023
74325
 
74024
74326
  // src/harness/process/sandbox/bwrap.ts
74025
- import { statSync as statSync5 } from "fs";
74327
+ import { statSync as statSync6 } from "fs";
74026
74328
  var BWRAP_PROGRAM = "bwrap";
74027
74329
  function inspectMaskTarget(target) {
74028
74330
  try {
74029
- return statSync5(target).isDirectory() ? "dir" : "file";
74331
+ return statSync6(target).isDirectory() ? "dir" : "file";
74030
74332
  } catch {
74031
74333
  return "missing";
74032
74334
  }
@@ -76397,7 +76699,7 @@ function credentialSummary(raw) {
76397
76699
 
76398
76700
  // src/mcp-servers/store.ts
76399
76701
  init_config_dir();
76400
- import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
76702
+ import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
76401
76703
  import path170 from "path";
76402
76704
  var SERVER_NAME_PATTERN = /^[A-Za-z0-9_-]+$/;
76403
76705
  function userConfigFile(configDir) {
@@ -76446,8 +76748,8 @@ function writeDocument(file, doc, scope) {
76446
76748
  writeOwnerOnlyFileAtomic(file, body);
76447
76749
  return;
76448
76750
  }
76449
- mkdirSync6(path170.dirname(file), { recursive: true });
76450
- writeFileSync7(file, body, { encoding: "utf8" });
76751
+ mkdirSync7(path170.dirname(file), { recursive: true });
76752
+ writeFileSync8(file, body, { encoding: "utf8" });
76451
76753
  }
76452
76754
  function addServer(options) {
76453
76755
  if (!SERVER_NAME_PATTERN.test(options.name)) {
@@ -79853,7 +80155,7 @@ Reports the workspace root and one enabled/disabled line per module. Use
79853
80155
  }
79854
80156
 
79855
80157
  // src/commands/harness.ts
79856
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync8 } from "fs";
80158
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync9 } from "fs";
79857
80159
  import path182 from "path";
79858
80160
  import { randomUUID as randomUUID20 } from "crypto";
79859
80161
 
@@ -81766,7 +82068,7 @@ async function harnessCommand(args, deps) {
81766
82068
  const result = await runOffline(input, config, runDeps);
81767
82069
  structured = toStructured(result);
81768
82070
  if (record !== undefined && record.length > 0) {
81769
- writeFileSync8(record, `${JSON.stringify(toRunRecord(result, {
82071
+ writeFileSync9(record, `${JSON.stringify(toRunRecord(result, {
81770
82072
  runId: result.output.runId,
81771
82073
  status: result.output.status,
81772
82074
  recordedAt: result.output.startedAt
@@ -81812,7 +82114,7 @@ function parseReplayArgs(args) {
81812
82114
  function readJsonFile2(file) {
81813
82115
  let text;
81814
82116
  try {
81815
- text = readFileSync8(file, "utf8");
82117
+ text = readFileSync9(file, "utf8");
81816
82118
  } catch (error) {
81817
82119
  return { ok: false, reason: error instanceof Error ? error.message : String(error) };
81818
82120
  }
@@ -81867,7 +82169,7 @@ function harnessReplay(args, deps) {
81867
82169
  return;
81868
82170
  }
81869
82171
  if (parsed.writeFixture !== undefined && parsed.writeFixture.length > 0) {
81870
- writeFileSync8(parsed.writeFixture, `${JSON.stringify(fixture.value, null, 2)}
82172
+ writeFileSync9(parsed.writeFixture, `${JSON.stringify(fixture.value, null, 2)}
81871
82173
  `, {
81872
82174
  encoding: "utf8",
81873
82175
  mode: 384
@@ -82179,7 +82481,7 @@ function readSpecArg(args) {
82179
82481
  if (specPath === undefined) {
82180
82482
  throw new Error("keryx harness: a --spec <path> argument is required when no spec is injected.");
82181
82483
  }
82182
- return JSON.parse(readFileSync8(specPath, "utf8"));
82484
+ return JSON.parse(readFileSync9(specPath, "utf8"));
82183
82485
  }
82184
82486
  var DEFAULT_CHILD_RESULT = `STATUS: DONE
82185
82487
  Extension completed within its granted capabilities.`;
@@ -82286,7 +82588,7 @@ init_providers();
82286
82588
  init_make_provider();
82287
82589
  init_orient();
82288
82590
  init_metaproject_adapter();
82289
- import { randomUUID as randomUUID29 } from "crypto";
82591
+ import { randomUUID as randomUUID30 } from "crypto";
82290
82592
  import * as readline2 from "readline";
82291
82593
 
82292
82594
  // src/commands/agent-approval-context.ts
@@ -88542,7 +88844,7 @@ async function runGoalCommand(params) {
88542
88844
  const wasOpened = slateSession.opened;
88543
88845
  await ensureSlateOpened(slateSession, mintAttemptId, { provider: deps.providerId, model: deps.modelId });
88544
88846
  if (!wasOpened && slateSession.opened) {
88545
- const freshSlate = await readSlate(slateSession.dir);
88847
+ const freshSlate = await readSlateSession(slateSession);
88546
88848
  if (freshSlate !== undefined) {
88547
88849
  history.push({ role: "user", content: renderAnchorsBlock(freshSlate.anchors), provenance: "project" });
88548
88850
  io.onHistoryChange?.("tool");
@@ -88550,17 +88852,17 @@ async function runGoalCommand(params) {
88550
88852
  }
88551
88853
  if (parsed.workspaceId !== undefined) {
88552
88854
  const workspaceId = parsed.workspaceId;
88553
- await writeSlate(slateSession.dir, (prev) => {
88855
+ await writeSlateSession(slateSession, (prev) => {
88554
88856
  const base = prev ?? { anchors: { root: "", touched: [] }, course: {}, seeds: [] };
88555
88857
  return { ...base, workspaceId };
88556
88858
  });
88557
88859
  }
88558
88860
  if (parsed.auto !== undefined) {
88559
- const forCourse = await readSlate(slateSession.dir);
88861
+ const forCourse = await readSlateSession(slateSession);
88560
88862
  let flowRefForBinding = forCourse?.course.flowRef;
88561
88863
  if (forCourse !== undefined && forCourse.course.flowRef === undefined) {
88562
88864
  const flowId = await autoProvisionFlow(cwd, parsed.text);
88563
- await writeSlate(slateSession.dir, (prev) => {
88865
+ await writeSlateSession(slateSession, (prev) => {
88564
88866
  if (!prev)
88565
88867
  throw new Error(`SLATE-27 bind: no open slate in ${slateSession.dir}`);
88566
88868
  return { ...prev, course: { ...prev.course, flowRef: flowId } };
@@ -88583,7 +88885,7 @@ async function runGoalCommand(params) {
88583
88885
  delete slateSession.autoGoalRounds;
88584
88886
  let roundsLeft = roundsCap;
88585
88887
  let round = 1;
88586
- while (roundsLeft > 0 && slateSession.opened) {
88888
+ while (roundsLeft > 0 && slateSession.opened && !isSlateSessionDetached(slateSession)) {
88587
88889
  roundsLeft -= 1;
88588
88890
  round += 1;
88589
88891
  const continuationText = await buildContinuationMessage(cwd, slateSession, round, roundsCap);
@@ -88596,8 +88898,18 @@ async function runGoalCommand(params) {
88596
88898
  break;
88597
88899
  }
88598
88900
  }
88901
+ if (isSlateSessionDetached(slateSession)) {
88902
+ systemLine(io, `/goal --auto: stopped \u2014 another shell took this session over.
88903
+ `);
88904
+ return;
88905
+ }
88599
88906
  const wasOpenBeforeVerifier = slateSession.opened;
88600
88907
  const verdict = await runGoalVerifier(deps, parsed.text, cwd, slateSession, history, io, mintAttemptId);
88908
+ if (isSlateSessionDetached(slateSession)) {
88909
+ systemLine(io, `/goal --auto: stopped \u2014 another shell took this session over.
88910
+ `);
88911
+ return;
88912
+ }
88601
88913
  if (verdict === undefined) {
88602
88914
  systemLine(io, `/goal --auto: verifier unavailable \u2014 outcome not independently checked.
88603
88915
  `);
@@ -88612,12 +88924,12 @@ async function runGoalCommand(params) {
88612
88924
  if (!wasOpenBeforeVerifier) {
88613
88925
  try {
88614
88926
  await ensureSlateOpened(slateSession, mintAttemptId, { provider: deps.providerId, model: deps.modelId });
88615
- const reopened = await readSlate(slateSession.dir);
88927
+ const reopened = await readSlateSession(slateSession);
88616
88928
  if (reopened !== undefined) {
88617
88929
  history.push({ role: "user", content: renderAnchorsBlock(reopened.anchors), provenance: "project" });
88618
88930
  io.onHistoryChange?.("tool");
88619
88931
  }
88620
- await writeSlate(slateSession.dir, (prev) => {
88932
+ await writeSlateSession(slateSession, (prev) => {
88621
88933
  if (!prev)
88622
88934
  throw new Error(`SLATE-27 verifier-reopen: no open slate in ${slateSession.dir}`);
88623
88935
  return {
@@ -88647,7 +88959,7 @@ async function runGoalCommand(params) {
88647
88959
 
88648
88960
  // src/tui/tui-shell.ts
88649
88961
  init_metaproject_adapter();
88650
- import { spawnSync as spawnSync2 } from "child_process";
88962
+ import { spawnSync as spawnSync3 } from "child_process";
88651
88963
 
88652
88964
  // src/tui/transcript-blocks.ts
88653
88965
  var EVICTED_BLOCK_TEXT = "(output no longer retained)";
@@ -94649,6 +94961,463 @@ function createForegroundAgentIoFacade(owner, token, io) {
94649
94961
  };
94650
94962
  }
94651
94963
 
94964
+ // src/session/lease.ts
94965
+ init_fs();
94966
+ init_paths();
94967
+ init_store3();
94968
+ import { randomUUID as randomUUID29 } from "crypto";
94969
+ import { hostname as hostname2 } from "os";
94970
+ import path194 from "path";
94971
+ var SESSION_LEASE_STALE_MS = 15000;
94972
+ var SESSION_LEASE_HEARTBEAT_MS = 5000;
94973
+ var instanceId;
94974
+ function processInstanceId() {
94975
+ instanceId ??= randomUUID29();
94976
+ return instanceId;
94977
+ }
94978
+ var MIN_ENV_STALE_MS = 250;
94979
+ var MIN_ENV_HEARTBEAT_MS = 50;
94980
+ function envInt(name) {
94981
+ const raw = process.env[name];
94982
+ return raw !== undefined && /^\d+$/.test(raw) ? Number(raw) : undefined;
94983
+ }
94984
+ function leaseTimingEnvEnabled() {
94985
+ return process.env.KERYX_TEST_LEASE_TIMING === "1";
94986
+ }
94987
+ function resolveSessionLeaseTiming(timing = {}) {
94988
+ const fromEnv = leaseTimingEnvEnabled();
94989
+ const envStale = fromEnv ? envInt("KERYX_SESSION_LEASE_STALE_MS") : undefined;
94990
+ const staleMs = timing.staleMs ?? (envStale !== undefined && envStale >= MIN_ENV_STALE_MS ? envStale : SESSION_LEASE_STALE_MS);
94991
+ const maxHeartbeat = Math.floor(staleMs / 3);
94992
+ const envHeartbeat = fromEnv ? envInt("KERYX_SESSION_LEASE_HEARTBEAT_MS") : undefined;
94993
+ const heartbeatMs = timing.heartbeatMs ?? (envHeartbeat !== undefined && envHeartbeat >= MIN_ENV_HEARTBEAT_MS && envHeartbeat <= maxHeartbeat ? envHeartbeat : Math.max(1, Math.min(SESSION_LEASE_HEARTBEAT_MS, maxHeartbeat)));
94994
+ return { ...timing, staleMs, heartbeatMs };
94995
+ }
94996
+ var resolveTiming = resolveSessionLeaseTiming;
94997
+ function isMine(holder) {
94998
+ return holder !== undefined && holder.instanceId === processInstanceId() && holder.pid === process.pid;
94999
+ }
95000
+ function leaseOptions(timing) {
95001
+ return {
95002
+ staleMs: timing.staleMs,
95003
+ ...timing.now !== undefined ? { now: timing.now } : {},
95004
+ ...timing.isAlive !== undefined ? { isAlive: timing.isAlive } : {},
95005
+ ...timing.host !== undefined ? { host: timing.host } : {}
95006
+ };
95007
+ }
95008
+ function sessionLeasePath(cwd, sessionId, dataDir) {
95009
+ return path194.join(sessionDir(resolveProjectRoot2(cwd), sessionId, dataDir), "active.lease");
95010
+ }
95011
+ function sessionLeaseState(cwd, sessionId, dataDir, timing) {
95012
+ const resolved = resolveTiming(timing);
95013
+ const seen = inspectLeaseSync(sessionLeasePath(cwd, sessionId, dataDir), leaseOptions(resolved));
95014
+ if (seen.state === "free" || seen.state === "gone")
95015
+ return { state: "free" };
95016
+ if (isMine(seen.holder))
95017
+ return { state: "mine", holder: seen.holder };
95018
+ return { state: seen.state, ...seen.holder !== undefined ? { holder: seen.holder } : {} };
95019
+ }
95020
+ function latestUnleasedSession(cwd, dataDir, timing) {
95021
+ let skipped;
95022
+ for (const summary of listSessions(cwd, dataDir)) {
95023
+ const { state, holder } = sessionLeaseState(cwd, summary.id, dataDir, timing);
95024
+ if (state === "free" || state === "mine") {
95025
+ return { summary, ...skipped !== undefined ? { skipped } : {} };
95026
+ }
95027
+ skipped ??= { summary, holder, state };
95028
+ }
95029
+ return { summary: undefined, ...skipped !== undefined ? { skipped } : {} };
95030
+ }
95031
+ function describeLeaseHolder(holder) {
95032
+ if (holder === undefined)
95033
+ return "another instance (still starting)";
95034
+ if (typeof holder.name === "string" && holder.name.length > 0)
95035
+ return `@${holder.name} (pid ${holder.pid})`;
95036
+ return `instance ${String(holder.instanceId).slice(0, 8)} (pid ${holder.pid})`;
95037
+ }
95038
+
95039
+ class SessionLeasedError extends Error {
95040
+ summary;
95041
+ holder;
95042
+ state;
95043
+ constructor(summary, holder, state, opts = {}) {
95044
+ const where = `session ${shortSessionId(summary.id)} is open in ${describeLeaseHolder(holder)}`;
95045
+ const hint = opts.takeOverRefused === true ? "; the holder is live, so --take-over is refused; use --fork" : state === "stale" ? "; use --fork or --take-over" : "; use --fork";
95046
+ super(`${where}${hint}`);
95047
+ this.name = "SessionLeasedError";
95048
+ this.summary = summary;
95049
+ this.holder = holder;
95050
+ this.state = state;
95051
+ }
95052
+ }
95053
+ var heldByThisProcess = new Map;
95054
+ function wrapHandle(sessionId, lockPath, inner, heartbeatMs) {
95055
+ let released = false;
95056
+ let lost = false;
95057
+ let lostTo;
95058
+ const lostListeners = new Set;
95059
+ let timer;
95060
+ const onExit = () => {
95061
+ handle.release();
95062
+ };
95063
+ const stopHeartbeat = () => {
95064
+ if (timer !== undefined)
95065
+ clearInterval(timer);
95066
+ timer = undefined;
95067
+ };
95068
+ const markLost = () => {
95069
+ if (lost || released)
95070
+ return;
95071
+ lost = true;
95072
+ lostTo = readLeaseOwnerSync(lockPath);
95073
+ stopHeartbeat();
95074
+ if (heldByThisProcess.get(lockPath) === handle)
95075
+ heldByThisProcess.delete(lockPath);
95076
+ const listeners = [...lostListeners];
95077
+ lostListeners.clear();
95078
+ for (const cb of listeners) {
95079
+ try {
95080
+ cb();
95081
+ } catch {}
95082
+ }
95083
+ };
95084
+ const handle = {
95085
+ sessionId,
95086
+ lockPath,
95087
+ get owner() {
95088
+ return inner.owner;
95089
+ },
95090
+ get released() {
95091
+ return released;
95092
+ },
95093
+ get heartbeatTimer() {
95094
+ return timer;
95095
+ },
95096
+ get lost() {
95097
+ return lost;
95098
+ },
95099
+ get lostTo() {
95100
+ return lostTo;
95101
+ },
95102
+ refresh(patch) {
95103
+ if (released || lost)
95104
+ return false;
95105
+ if (inner.refresh(patch))
95106
+ return true;
95107
+ if (inner.ownership() === "lost")
95108
+ markLost();
95109
+ return false;
95110
+ },
95111
+ checkLost() {
95112
+ if (!lost && !released && inner.ownership() === "lost")
95113
+ markLost();
95114
+ return lost;
95115
+ },
95116
+ onLost(cb) {
95117
+ if (lost) {
95118
+ cb();
95119
+ return () => {};
95120
+ }
95121
+ lostListeners.add(cb);
95122
+ return () => {
95123
+ lostListeners.delete(cb);
95124
+ };
95125
+ },
95126
+ release() {
95127
+ if (released)
95128
+ return;
95129
+ released = true;
95130
+ stopHeartbeat();
95131
+ lostListeners.clear();
95132
+ process.off("exit", onExit);
95133
+ if (heldByThisProcess.get(lockPath) === handle)
95134
+ heldByThisProcess.delete(lockPath);
95135
+ if (!lost)
95136
+ inner.release();
95137
+ }
95138
+ };
95139
+ timer = setInterval(() => {
95140
+ handle.refresh();
95141
+ }, heartbeatMs);
95142
+ timer.unref?.();
95143
+ process.on("exit", onExit);
95144
+ heldByThisProcess.set(lockPath, handle);
95145
+ return handle;
95146
+ }
95147
+ function mintOwner(now) {
95148
+ const at = new Date(now()).toISOString();
95149
+ return {
95150
+ schemaVersion: 1,
95151
+ token: randomUUID29(),
95152
+ pid: process.pid,
95153
+ host: hostname2(),
95154
+ instanceId: processInstanceId(),
95155
+ name: null,
95156
+ acquiredAt: at,
95157
+ heartbeatAt: at
95158
+ };
95159
+ }
95160
+ function leaseSession(cwd, summary, dataDir, timing, takeOver) {
95161
+ const lockPath = sessionLeasePath(cwd, summary.id, dataDir);
95162
+ const existing = heldByThisProcess.get(lockPath);
95163
+ if (existing !== undefined && !existing.released && !existing.lost)
95164
+ return { handle: existing, minted: false };
95165
+ const owner = mintOwner(timing.now ?? Date.now);
95166
+ const opts = {
95167
+ ...leaseOptions(timing),
95168
+ reclaimIf: (holder) => isMine(holder)
95169
+ };
95170
+ const result = takeOver ? reclaimStaleLeaseSync(lockPath, owner, opts) : acquireLeaseSync(lockPath, owner, opts);
95171
+ if (!result.ok) {
95172
+ throw new SessionLeasedError(summary, result.holder, result.state, {
95173
+ takeOverRefused: takeOver && result.state === "live"
95174
+ });
95175
+ }
95176
+ return { handle: wrapHandle(summary.id, lockPath, result.handle, timing.heartbeatMs), minted: true };
95177
+ }
95178
+ function describeLeaseLoss(handle) {
95179
+ const short = shortSessionId(handle.sessionId);
95180
+ const by = handle.lostTo !== undefined ? describeLeaseHolder(handle.lostTo) : "another instance";
95181
+ return `session ${short} was taken over by ${by}; this shell no longer saves it. ` + `Use /new, or restart with -r ${short} --fork to keep your turns.
95182
+ `;
95183
+ }
95184
+ var LOST_LEASE_COMPACT_REFUSAL = `Not compacted: another shell took this session over, and this shell no longer saves it. Use /new.
95185
+ `;
95186
+ function watchLeaseLoss(notify) {
95187
+ let current;
95188
+ let unsubscribe;
95189
+ return {
95190
+ track(next) {
95191
+ if (next === current)
95192
+ return;
95193
+ unsubscribe?.();
95194
+ unsubscribe = undefined;
95195
+ current = next;
95196
+ if (next !== undefined) {
95197
+ unsubscribe = next.onLost(() => {
95198
+ notify(describeLeaseLoss(next), next);
95199
+ });
95200
+ }
95201
+ },
95202
+ canPersist() {
95203
+ return current === undefined || !current.checkLost();
95204
+ }
95205
+ };
95206
+ }
95207
+ function whilePersisting(value, canPersist) {
95208
+ return value !== undefined && !canPersist() ? undefined : value;
95209
+ }
95210
+ function gateBoxByLease(box, canPersist) {
95211
+ let value = box.current;
95212
+ Object.defineProperty(box, "current", {
95213
+ configurable: true,
95214
+ enumerable: true,
95215
+ get: () => whilePersisting(value, canPersist),
95216
+ set: (next) => {
95217
+ value = next;
95218
+ }
95219
+ });
95220
+ }
95221
+ function releaseSessionLease(handle) {
95222
+ handle?.release();
95223
+ }
95224
+ function openLeasedSession(opts) {
95225
+ const { takeOver, fork, staleMs, heartbeatMs, now, isAlive, host, resumeId: rawResumeId, continueLast, ...base } = opts;
95226
+ const timing = resolveTiming({
95227
+ ...staleMs !== undefined ? { staleMs } : {},
95228
+ ...heartbeatMs !== undefined ? { heartbeatMs } : {},
95229
+ ...now !== undefined ? { now } : {},
95230
+ ...isAlive !== undefined ? { isAlive } : {},
95231
+ ...host !== undefined ? { host } : {}
95232
+ });
95233
+ const { cwd, dataDir } = base;
95234
+ const resumeId = rawResumeId !== undefined && rawResumeId.length > 0 ? rawResumeId : undefined;
95235
+ const openWith = (target, lease) => {
95236
+ try {
95237
+ const opened = openSession({ ...base, ...target !== undefined ? { resumeId: target } : {} });
95238
+ return { ...opened, lease: lease?.handle ?? leaseSession(cwd, opened.handle.summary, dataDir, timing, false).handle };
95239
+ } catch (error) {
95240
+ if (lease?.minted === true)
95241
+ releaseSessionLease(lease.handle);
95242
+ throw error;
95243
+ }
95244
+ };
95245
+ if (fork === true) {
95246
+ if (resumeId === undefined) {
95247
+ throw new Error("--fork needs a session id (-r <id>).");
95248
+ }
95249
+ const forked = forkSession({
95250
+ cwd,
95251
+ sourceIdOrPrefix: resumeId,
95252
+ ...dataDir !== undefined ? { dataDir } : {}
95253
+ });
95254
+ const summary = forked.handle.summary;
95255
+ return openWith(summary.id, leaseSession(cwd, summary, dataDir, timing, false));
95256
+ }
95257
+ if (resumeId !== undefined) {
95258
+ const found = findSession(cwd, resumeId, dataDir);
95259
+ if (found === undefined) {
95260
+ return openWith(resumeId);
95261
+ }
95262
+ return openWith(found.id, leaseSession(cwd, found, dataDir, timing, takeOver === true));
95263
+ }
95264
+ let skipped;
95265
+ if (continueLast === true) {
95266
+ for (let attempt = 0;attempt < 3; attempt += 1) {
95267
+ const pick = latestUnleasedSession(cwd, dataDir, timing);
95268
+ skipped ??= pick.skipped;
95269
+ if (pick.summary === undefined)
95270
+ break;
95271
+ let lease;
95272
+ try {
95273
+ lease = leaseSession(cwd, pick.summary, dataDir, timing, false);
95274
+ } catch (error) {
95275
+ if (!(error instanceof SessionLeasedError))
95276
+ throw error;
95277
+ skipped ??= { summary: error.summary, holder: error.holder, state: error.state };
95278
+ continue;
95279
+ }
95280
+ return { ...openWith(pick.summary.id, lease), ...skipped !== undefined ? { skipped } : {} };
95281
+ }
95282
+ }
95283
+ return { ...openWith(undefined), ...skipped !== undefined ? { skipped } : {} };
95284
+ }
95285
+ function switchLeasedSession(current, openTarget) {
95286
+ const next = openTarget();
95287
+ if (current !== undefined && current !== next.lease) {
95288
+ releaseSessionLease(current);
95289
+ }
95290
+ return next;
95291
+ }
95292
+
95293
+ // src/session/lease-choice.ts
95294
+ init_store3();
95295
+ function leasedChoiceRows(error) {
95296
+ return [
95297
+ { choice: "fork", label: "fork (default): continue in a copy of the session" },
95298
+ { choice: "view", label: "view (read-only), then start a new session" },
95299
+ { choice: "cancel", label: "cancel" },
95300
+ ...error.state === "stale" ? [{ choice: "take-over", label: "take over (the holder is stale)" }] : []
95301
+ ];
95302
+ }
95303
+ function describeLeasedSession(error) {
95304
+ const stale = error.state === "stale" ? " (stale)" : "";
95305
+ return `Session ${shortSessionId(error.summary.id)} \xB7 ${error.summary.title} is open in ${describeLeaseHolder(error.holder)}${stale}.`;
95306
+ }
95307
+ function describeSkippedSession(skipped) {
95308
+ const stale = skipped.state === "stale" ? " (stale)" : "";
95309
+ return `Skipped session ${shortSessionId(skipped.summary.id)} \xB7 ${skipped.summary.title}: open in ${describeLeaseHolder(skipped.holder)}${stale}
95310
+ `;
95311
+ }
95312
+
95313
+ // src/tui/tui-session-lease.ts
95314
+ function leaseMarker(state) {
95315
+ if (state === "live")
95316
+ return "\u25CF live";
95317
+ if (state === "stale")
95318
+ return "\u25CC stale";
95319
+ return "";
95320
+ }
95321
+ function leaseStateLookup(cwd, dataDir) {
95322
+ return (sessionId) => {
95323
+ try {
95324
+ return sessionLeaseState(cwd, sessionId, dataDir).state;
95325
+ } catch {
95326
+ return "free";
95327
+ }
95328
+ };
95329
+ }
95330
+ function withLeaseMarker(label, state) {
95331
+ const marker = leaseMarker(state);
95332
+ return marker.length > 0 ? `${label} ${marker}` : label;
95333
+ }
95334
+ function leasedChoiceRequest(error) {
95335
+ const descriptions = {
95336
+ fork: "Continue in a copy of the session; the other shell keeps the original",
95337
+ view: "Show the session read-only, then start a new session",
95338
+ cancel: "Exit without opening a session",
95339
+ "take-over": "Reclaim the lease: the holder has stopped refreshing it"
95340
+ };
95341
+ return {
95342
+ title: describeLeasedSession(error),
95343
+ subtitle: "Esc = cancel",
95344
+ cancelId: "cancel",
95345
+ options: leasedChoiceRows(error).map((row) => ({
95346
+ id: row.choice,
95347
+ label: row.label,
95348
+ description: descriptions[row.choice],
95349
+ ...row.choice === "fork" ? { recommended: true } : {}
95350
+ }))
95351
+ };
95352
+ }
95353
+ function toLeasedChoice(error, id) {
95354
+ const offered = leasedChoiceRows(error).find((row) => row.choice === id);
95355
+ return offered?.choice ?? "cancel";
95356
+ }
95357
+ async function resolveLeasedStartup(first, deps) {
95358
+ let error = first;
95359
+ for (;; ) {
95360
+ const choice = await deps.choose(error);
95361
+ if (choice === "cancel") {
95362
+ return { kind: "cancelled" };
95363
+ }
95364
+ if (choice === "view") {
95365
+ try {
95366
+ deps.showReadOnly(deps.exportSession(error.summary.id));
95367
+ } catch (cause) {
95368
+ deps.notice(`Could not render the session: ${cause instanceof Error ? cause.message : String(cause)}`);
95369
+ }
95370
+ return { kind: "opened", opened: deps.open({}) };
95371
+ }
95372
+ try {
95373
+ const target = choice === "fork" ? { resumeId: error.summary.id, fork: true } : { resumeId: error.summary.id, takeOver: true };
95374
+ return { kind: "opened", opened: deps.open(target) };
95375
+ } catch (cause) {
95376
+ if (!(cause instanceof SessionLeasedError)) {
95377
+ throw cause;
95378
+ }
95379
+ deps.notice(cause.message);
95380
+ error = cause;
95381
+ }
95382
+ }
95383
+ }
95384
+ function createTuiLeaseHolder() {
95385
+ let current;
95386
+ let lossListener;
95387
+ const watch = watchLeaseLoss((message) => lossListener?.(message));
95388
+ const set = (next) => {
95389
+ current = next;
95390
+ watch.track(next);
95391
+ };
95392
+ return {
95393
+ get current() {
95394
+ return current;
95395
+ },
95396
+ hold(next) {
95397
+ if (current !== undefined && current !== next) {
95398
+ releaseSessionLease(current);
95399
+ }
95400
+ set(next);
95401
+ },
95402
+ switchTo(openTarget) {
95403
+ const next = switchLeasedSession(current, openTarget);
95404
+ set(next.lease);
95405
+ return next;
95406
+ },
95407
+ release() {
95408
+ const held = current;
95409
+ set(undefined);
95410
+ releaseSessionLease(held);
95411
+ },
95412
+ canPersist() {
95413
+ return watch.canPersist();
95414
+ },
95415
+ onLost(listener) {
95416
+ lossListener = listener;
95417
+ }
95418
+ };
95419
+ }
95420
+
94652
95421
  // src/tui/herdr-report.ts
94653
95422
  init_debug_log();
94654
95423
  import net from "net";
@@ -95027,7 +95796,7 @@ function presentComposerChoice(otui, r, dock, request) {
95027
95796
  // src/lib/version-check.ts
95028
95797
  init_config_dir();
95029
95798
  init_fs();
95030
- import path194 from "path";
95799
+ import path195 from "path";
95031
95800
  var REGISTRY_URL = "https://registry.npmjs.org/@mrciphersmith%2Fkeryx/latest";
95032
95801
  var FIXED_INSTALL_COMMAND = "npm install -g @mrciphersmith/keryx@latest";
95033
95802
  var RESPONSE_BODY_LIMIT_BYTES = 64 * 1024;
@@ -95229,7 +95998,7 @@ async function checkVersion(options) {
95229
95998
  const now = options.now ?? Date.now;
95230
95999
  const timestamp = now();
95231
96000
  const configDir = ensureKeryxConfigDir(options.cacheDir);
95232
- const cacheFile = path194.join(configDir, "version-check.json");
96001
+ const cacheFile = path195.join(configDir, "version-check.json");
95233
96002
  const cache = parseCache(cacheFile);
95234
96003
  if (cache?.latestVersion !== undefined && cache.successAt !== undefined && timestamp - cache.successAt >= 0 && timestamp - cache.successAt < SUCCESS_CACHE_TTL_MS) {
95235
96004
  return resultFor(options.currentVersion, current, cache.latestVersion, "cache");
@@ -95302,9 +96071,39 @@ init_debug_log();
95302
96071
 
95303
96072
  // src/tui/renderer-debug.ts
95304
96073
  init_debug_log();
96074
+ import { spawnSync as spawnSync2 } from "child_process";
96075
+ import { readFileSync as readFileSync10, readlinkSync as readlinkSync2 } from "fs";
95305
96076
 
95306
96077
  // src/tui/stdin-guard.ts
95307
96078
  init_debug_log();
96079
+ import { openSync as openSync3, readlinkSync } from "fs";
96080
+ import tty from "tty";
96081
+ var resolveStdin = (source) => typeof source === "function" ? source() : source;
96082
+ function stdinIsDead(stdin) {
96083
+ return stdin.destroyed === true || stdin.readableEnded === true;
96084
+ }
96085
+ function openTerminalInput() {
96086
+ let target = "/dev/tty";
96087
+ if (process.platform === "linux") {
96088
+ try {
96089
+ const link = readlinkSync("/proc/self/fd/0");
96090
+ if (link.startsWith("/dev/pts/") || link.startsWith("/dev/tty")) {
96091
+ target = link;
96092
+ }
96093
+ } catch {}
96094
+ }
96095
+ try {
96096
+ const fd = openSync3(target, "r");
96097
+ const stream = new tty.ReadStream(fd);
96098
+ stream.setRawMode(false);
96099
+ stream.setRawMode(true);
96100
+ debugEvent("stdin.reopened", { path: target, fd });
96101
+ return stream;
96102
+ } catch (error) {
96103
+ debugEvent("stdin.reopen-failed", { path: target, error: error instanceof Error ? error.message : String(error) });
96104
+ return;
96105
+ }
96106
+ }
95308
96107
  var rendererListener;
95309
96108
  function stdinSnapshot(stdin) {
95310
96109
  const safe = (read) => {
@@ -95409,35 +96208,37 @@ function instrumentStdin(stdin) {
95409
96208
  stdin.off?.(event, handler);
95410
96209
  };
95411
96210
  }
95412
- function startStdinHeartbeat(stdin, opts = {}) {
96211
+ function startStdinHeartbeat(source, opts = {}) {
95413
96212
  const intervalMs = opts.intervalMs ?? 1000;
95414
96213
  const cooldownMs = opts.cooldownMs ?? 1e4;
95415
96214
  let lastRecovery = 0;
95416
96215
  const timer = setInterval(() => {
96216
+ const stdin = resolveStdin(source);
95417
96217
  const state = stdinSnapshot(stdin);
95418
96218
  const stalled = stdinLooksStalled(state);
95419
96219
  debugEvent("heartbeat", { stdin: state, stalled, ...opts.extra?.() ?? {} });
95420
96220
  if (stalled && (opts.shouldRecover?.() ?? true) && Date.now() - lastRecovery > cooldownMs) {
95421
96221
  lastRecovery = Date.now();
95422
- const actions = recoverStdin(stdin, { cycle: true });
95423
- debugEvent("stdin.stall.recovered-in-process", { before: state, actions, after: stdinSnapshot(stdin) });
96222
+ const actions = opts.recover?.() ?? recoverStdin(stdin, { cycle: true });
96223
+ debugEvent("stdin.stall.recovered-in-process", { before: state, actions, after: stdinSnapshot(resolveStdin(source)) });
95424
96224
  }
95425
96225
  }, intervalMs);
95426
96226
  timer.unref?.();
95427
96227
  return () => clearInterval(timer);
95428
96228
  }
95429
- function installInputRecoverySignal(stdin, onRecovered, shouldRecover = () => true) {
96229
+ function installInputRecoverySignal(source, onRecovered, shouldRecover = () => true, recover) {
95430
96230
  if (process.platform === "win32") {
95431
96231
  return () => {};
95432
96232
  }
95433
96233
  const handler = () => {
96234
+ const stdin = resolveStdin(source);
95434
96235
  if (!shouldRecover()) {
95435
96236
  debugEvent("signal.SIGUSR2.skipped", { reason: "renderer suspended or destroyed", stdin: stdinSnapshot(stdin) });
95436
96237
  return;
95437
96238
  }
95438
96239
  const before = stdinSnapshot(stdin);
95439
- const actions = recoverStdin(stdin, { cycle: true });
95440
- const after = stdinSnapshot(stdin);
96240
+ const actions = recover?.() ?? recoverStdin(stdin, { cycle: true });
96241
+ const after = stdinSnapshot(resolveStdin(source));
95441
96242
  debugEvent("signal.SIGUSR2.recover", { before, actions, after, debug: isDebugEnabled() });
95442
96243
  try {
95443
96244
  onRecovered?.(actions);
@@ -95450,15 +96251,110 @@ function installInputRecoverySignal(stdin, onRecovered, shouldRecover = () => tr
95450
96251
  }
95451
96252
 
95452
96253
  // src/tui/renderer-debug.ts
95453
- function attachRendererGuards(r, stdin = process.stdin) {
96254
+ var MAX_REOPENS_PER_MINUTE = 5;
96255
+ function eofDiagnostics() {
96256
+ if (process.platform !== "linux") {
96257
+ return {};
96258
+ }
96259
+ let tty;
96260
+ try {
96261
+ tty = readlinkSync2("/proc/self/fd/0");
96262
+ } catch {
96263
+ tty = undefined;
96264
+ }
96265
+ let stat;
96266
+ try {
96267
+ stat = readFileSync10("/proc/self/stat", "utf8").trim();
96268
+ } catch {
96269
+ stat = "";
96270
+ }
96271
+ const fields = stat.replace(/^.*\)\s+/, "").split(" ");
96272
+ const stty = tty !== undefined ? spawnSync2("stty", ["-a", "-F", tty], { encoding: "utf8", timeout: 1500 }).stdout?.trim() : undefined;
96273
+ return { tty, pgrp: fields[2], session: fields[3], tpgid: fields[5], stty };
96274
+ }
96275
+ function attachRendererGuards(r, initial = process.stdin, deps = {}) {
95454
96276
  const internals = r;
95455
- registerRendererInputListener(internals.stdinListener);
96277
+ const listener = internals.stdinListener;
96278
+ registerRendererInputListener(listener);
95456
96279
  const shouldRecover = () => !r.isDestroyed && internals.controlState !== "explicit_suspended";
95457
96280
  const teardown = [];
95458
- teardown.push(installInputRecoverySignal(stdin, () => {
96281
+ let current = initial;
96282
+ let unwatch = () => {};
96283
+ let undoInstrument = () => {};
96284
+ const reopenTimes = [];
96285
+ const reopen = (reason) => {
96286
+ const now = Date.now();
96287
+ while (reopenTimes.length > 0 && now - (reopenTimes[0] ?? 0) > 60000)
96288
+ reopenTimes.shift();
96289
+ if (reopenTimes.length >= MAX_REOPENS_PER_MINUTE) {
96290
+ debugEvent("stdin.reopen-gave-up", { reason, reopensLastMinute: reopenTimes.length });
96291
+ return ["reopen skipped: rate limit"];
96292
+ }
96293
+ reopenTimes.push(now);
96294
+ const next = (deps.openInput ?? openTerminalInput)();
96295
+ if (next === undefined) {
96296
+ return ["reopen failed"];
96297
+ }
96298
+ const previous = current;
96299
+ unwatch();
96300
+ undoInstrument();
96301
+ undoInstrument = () => {};
96302
+ if (listener !== undefined) {
96303
+ try {
96304
+ previous.off?.("data", listener);
96305
+ } catch {}
96306
+ next.on("data", listener);
96307
+ }
96308
+ current = next;
96309
+ internals.stdin = next;
96310
+ watch(next);
96311
+ if (isDebugEnabled()) {
96312
+ undoInstrument = instrumentStdin(next);
96313
+ }
96314
+ next.resume();
96315
+ debugEvent("stdin.replaced", { reason, state: stdinSnapshot(next) });
96316
+ r.requestRender();
96317
+ return [`reopened terminal input (${reason})`];
96318
+ };
96319
+ function watch(stream) {
96320
+ const onDead = (event) => () => {
96321
+ if (current !== stream) {
96322
+ return;
96323
+ }
96324
+ if (isDebugEnabled()) {
96325
+ debugEvent(`stdin.dead.${event}`, { state: stdinSnapshot(stream), ...eofDiagnostics() });
96326
+ }
96327
+ if (!shouldRecover()) {
96328
+ return;
96329
+ }
96330
+ setTimeout(() => {
96331
+ if (current === stream && shouldRecover()) {
96332
+ reopen(event);
96333
+ }
96334
+ }, 0);
96335
+ };
96336
+ const onEnd = onDead("end");
96337
+ const onClose = onDead("close");
96338
+ stream.on("end", onEnd);
96339
+ stream.on("close", onClose);
96340
+ unwatch = () => {
96341
+ stream.off?.("end", onEnd);
96342
+ stream.off?.("close", onClose);
96343
+ };
96344
+ }
96345
+ watch(current);
96346
+ const recover = () => stdinIsDead(current) ? reopen("recover") : recoverStdin(current, { cycle: true });
96347
+ teardown.push(installInputRecoverySignal(() => current, () => {
95459
96348
  r.requestRender();
95460
- }, shouldRecover));
95461
- teardown.push(() => registerRendererInputListener(undefined));
96349
+ }, shouldRecover, recover));
96350
+ teardown.push(() => {
96351
+ unwatch();
96352
+ undoInstrument();
96353
+ registerRendererInputListener(undefined);
96354
+ if (current !== initial) {
96355
+ current.destroy?.();
96356
+ }
96357
+ });
95462
96358
  if (!isDebugEnabled()) {
95463
96359
  return () => {
95464
96360
  for (const fn of teardown.splice(0))
@@ -95471,9 +96367,10 @@ function attachRendererGuards(r, stdin = process.stdin) {
95471
96367
  height: r.height,
95472
96368
  hasStdinListener: internals.stdinListener !== undefined
95473
96369
  });
95474
- teardown.push(instrumentStdin(stdin));
95475
- teardown.push(startStdinHeartbeat(stdin, {
96370
+ undoInstrument = instrumentStdin(current);
96371
+ teardown.push(startStdinHeartbeat(() => current, {
95476
96372
  shouldRecover,
96373
+ recover,
95477
96374
  extra: () => ({
95478
96375
  controlState: internals.controlState,
95479
96376
  focused: internals.currentFocusedRenderable?.id
@@ -98425,7 +99322,7 @@ function parseGitHubRemote(remote) {
98425
99322
  }
98426
99323
  function runGitText(args, cwd) {
98427
99324
  try {
98428
- const proc = spawnSync2("git", args, {
99325
+ const proc = spawnSync3("git", args, {
98429
99326
  cwd,
98430
99327
  encoding: "utf8",
98431
99328
  stdio: ["ignore", "pipe", "pipe"],
@@ -98442,7 +99339,7 @@ function runGitText(args, cwd) {
98442
99339
  }
98443
99340
  function runGhJson(repo, branch, cwd) {
98444
99341
  try {
98445
- const proc = spawnSync2("gh", ["pr", "view", branch, "--repo", repo, "--json", "url"], {
99342
+ const proc = spawnSync3("gh", ["pr", "view", branch, "--repo", repo, "--json", "url"], {
98446
99343
  cwd,
98447
99344
  encoding: "utf8",
98448
99345
  stdio: ["ignore", "pipe", "pipe"],
@@ -98925,8 +99822,8 @@ async function applyRuntimeSwitchToSlate(params) {
98925
99822
  if (params.slateSession === undefined || !params.slateSession.opened) {
98926
99823
  return false;
98927
99824
  }
98928
- const result = await recordSlateTouch(params.slateSession.dir, [], { runtime: params.runtime });
98929
- if (!result.changed) {
99825
+ const result = await recordSlateSessionTouch(params.slateSession, [], { runtime: params.runtime });
99826
+ if (result === undefined || !result.changed) {
98930
99827
  return false;
98931
99828
  }
98932
99829
  params.history.push({
@@ -99896,21 +100793,40 @@ function formatSessionDate(iso) {
99896
100793
  const short = iso.trim().replace("T", " ");
99897
100794
  return short.length >= 16 ? short.slice(0, 16) : short;
99898
100795
  }
99899
- function pickSessionInTui(otui, rOrChrome, sessions) {
99900
- const chrome = isModalChrome(rOrChrome) ? rOrChrome : undefined;
99901
- const r = chrome !== undefined ? chrome.renderer : rOrChrome;
99902
- const all = sessions.map((s) => {
100796
+ function sessionPickerOptions(sessions, leaseState = () => "free") {
100797
+ return sessions.map((s) => {
99903
100798
  const created = formatSessionDate(s.createdAt);
99904
100799
  const updated = formatSessionDate(s.updatedAt);
99905
100800
  const short = shortSessionId(s.id);
99906
100801
  const title = s.title.length > 52 ? `${s.title.slice(0, 49)}\u2026` : s.title;
100802
+ const label = withLeaseMarker(`${short} \xB7 ${title}`, leaseState(s.id));
99907
100803
  return {
99908
100804
  value: s.id,
99909
- label: `${short} \xB7 ${title}`,
100805
+ label,
99910
100806
  description: `${s.projectPath} \xB7 created ${created} \xB7 updated ${updated}`,
99911
- search: `${s.id} ${short} ${s.projectPath} ${s.title} ${created} ${updated}`.toLowerCase()
100807
+ search: `${s.id} ${label} ${s.projectPath} ${s.title} ${created} ${updated}`.toLowerCase()
99912
100808
  };
99913
100809
  });
100810
+ }
100811
+ function startupSessionChoices(rows, leaseState = () => "free") {
100812
+ return [
100813
+ {
100814
+ id: "__new__",
100815
+ label: "New session",
100816
+ description: "Start fresh (old sessions stay on disk)",
100817
+ recommended: true
100818
+ },
100819
+ ...rows.map((s) => ({
100820
+ id: s.id,
100821
+ label: withLeaseMarker(s.title.length > 40 ? `${s.title.slice(0, 37)}\u2026` : s.title, leaseState(s.id)),
100822
+ description: `${shortSessionId(s.id)} \xB7 ctx ${s.messageCount} \xB7 ${s.updatedAt.slice(0, 16).replace("T", " ")}`
100823
+ }))
100824
+ ];
100825
+ }
100826
+ function pickSessionInTui(otui, rOrChrome, sessions, leaseState) {
100827
+ const chrome = isModalChrome(rOrChrome) ? rOrChrome : undefined;
100828
+ const r = chrome !== undefined ? chrome.renderer : rOrChrome;
100829
+ const all = sessionPickerOptions(sessions, leaseState);
99914
100830
  const listSpec = {
99915
100831
  idPrefix: "sp",
99916
100832
  items: all,
@@ -100017,11 +100933,13 @@ async function launchTuiAgentShell(opts) {
100017
100933
  let liveJobs;
100018
100934
  let detachExternal;
100019
100935
  const foregroundOperation = createForegroundOperationOwner();
100936
+ const sessionLease = createTuiLeaseHolder();
100020
100937
  try {
100021
100938
  const r = renderer = await createShellRenderer(otui, {
100022
100939
  onDestroy: () => {
100023
100940
  foregroundOperation.cancel("renderer destroyed");
100024
100941
  foregroundOperation.dispose();
100942
+ sessionLease.release();
100025
100943
  mountedChrome?.destroy();
100026
100944
  setAskUserHost(undefined);
100027
100945
  setSubagentFleetListener(undefined);
@@ -100054,7 +100972,8 @@ async function launchTuiAgentShell(opts) {
100054
100972
  saveShellConfig(sel.baseUrl === undefined ? { provider: sel.provider, model: sel.model } : { provider: sel.provider, model: sel.model, baseUrl: sel.baseUrl });
100055
100973
  let currentSel = sel;
100056
100974
  const suggestionGate = new NextStepSuggestionGate;
100057
- let deps = await opts.makeAgentDeps(sel, () => slateSession);
100975
+ const liveSlateSession = () => whilePersisting(slateSession, () => sessionLease.canPersist());
100976
+ let deps = await opts.makeAgentDeps(sel, liveSlateSession);
100058
100977
  liveDeps = deps;
100059
100978
  let reasoningOverride;
100060
100979
  let thinkDisplayMode = resolveThinkDisplayMode(loadShellConfig().thinkDisplay);
@@ -100127,7 +101046,7 @@ async function launchTuiAgentShell(opts) {
100127
101046
  }));
100128
101047
  };
100129
101048
  const refreshWorkspaceSidebar = async () => {
100130
- const dir = slateSession?.dir;
101049
+ const dir = liveSlateSession()?.dir;
100131
101050
  const workspaceId = dir !== undefined ? (await readSlate(dir).catch(() => {
100132
101051
  return;
100133
101052
  }))?.workspaceId : undefined;
@@ -100753,6 +101672,9 @@ async function launchTuiAgentShell(opts) {
100753
101672
  slateSession = freshSlateSessionRef(liveSession.dir, sessionCwd);
100754
101673
  };
100755
101674
  const onContextCompaction = (r) => {
101675
+ if (!sessionLease.canPersist()) {
101676
+ return;
101677
+ }
100756
101678
  const persisted = persistCompacted(liveSession, r.context, archive, {
100757
101679
  provider: currentSel.provider,
100758
101680
  model: currentSel.model
@@ -100784,7 +101706,7 @@ async function launchTuiAgentShell(opts) {
100784
101706
  return;
100785
101707
  }
100786
101708
  chrome.hideMenu();
100787
- const pickId = await chrome.withOverlay(() => pickSessionInTui(otui, chrome, rows));
101709
+ const pickId = await chrome.withOverlay(() => pickSessionInTui(otui, chrome, rows, leaseState));
100788
101710
  input.focus();
100789
101711
  if (pickId === undefined) {
100790
101712
  return;
@@ -100797,98 +101719,142 @@ async function launchTuiAgentShell(opts) {
100797
101719
  }
100798
101720
  return found;
100799
101721
  };
101722
+ const leasedOpen = (target = {}) => sessionLease.switchTo(() => openLeasedSession({
101723
+ cwd: sessionCwd,
101724
+ ...target,
101725
+ provider: currentSel.provider,
101726
+ model: currentSel.model
101727
+ }));
101728
+ const leaseState = leaseStateLookup(sessionCwd);
101729
+ const showOpenedNotes = (opened) => {
101730
+ if (opened.skipped !== undefined) {
101731
+ transcript.add(new otui.TextRenderable(r, {
101732
+ id: `sessskip${uid++}`,
101733
+ content: otui.t`${otui.yellow(describeSkippedSession(opened.skipped).replace(/\n+$/, ""))}`,
101734
+ marginTop: 1
101735
+ }));
101736
+ }
101737
+ if (opened.archiveDegraded !== undefined) {
101738
+ transcript.add(new otui.TextRenderable(r, {
101739
+ id: `sessdeg${uid++}`,
101740
+ content: otui.t`${otui.yellow(`archive unavailable \u2014 resumed from the active context (${opened.archiveDegraded})`)}`,
101741
+ marginTop: 1
101742
+ }));
101743
+ }
101744
+ if (opened.resumed) {
101745
+ transcript.add(new otui.TextRenderable(r, {
101746
+ id: `sess${uid++}`,
101747
+ content: otui.t`${otui.dim(`session ${shortSessionId(liveSession.summary.id)} \xB7 ${liveSession.summary.title} \xB7 ctx ${history.length} \xB7 archive ${archive.length}`)}`,
101748
+ marginTop: 1
101749
+ }));
101750
+ for (const m of history.filter((x) => x.role === "user").slice(-5)) {
101751
+ const t = m.content.length > 100 ? `${m.content.slice(0, 97)}\u2026` : m.content;
101752
+ transcript.add(new otui.TextRenderable(r, {
101753
+ id: `sessu${uid++}`,
101754
+ content: otui.t`${otui.dim(` \u276F ${t}`)}`
101755
+ }));
101756
+ }
101757
+ }
101758
+ };
101759
+ let startupCancelled = false;
101760
+ let viewedReadOnly = false;
100800
101761
  try {
100801
101762
  if (opts.session?.pickOnStart === true && opts.session.resumeId === undefined) {
100802
101763
  const rows = listSessions(sessionCwd).slice(0, 12);
100803
101764
  if (rows.length === 0) {
100804
- applyOpened(openSession({
100805
- cwd: sessionCwd,
100806
- provider: currentSel.provider,
100807
- model: currentSel.model
100808
- }));
101765
+ applyOpened(leasedOpen());
100809
101766
  } else {
100810
101767
  chrome.hideMenu();
100811
101768
  const pickId = await showComposerChoice(otui, r, chrome.dock, {
100812
101769
  title: "Resume session (this project)",
100813
101770
  subtitle: "Esc = new session",
100814
101771
  cancelId: "__new__",
100815
- options: [
100816
- {
100817
- id: "__new__",
100818
- label: "New session",
100819
- description: "Start fresh (old sessions stay on disk)",
100820
- recommended: true
100821
- },
100822
- ...rows.map((s) => ({
100823
- id: s.id,
100824
- label: s.title.length > 40 ? `${s.title.slice(0, 37)}\u2026` : s.title,
100825
- description: `${shortSessionId(s.id)} \xB7 ctx ${s.messageCount} \xB7 ${s.updatedAt.slice(0, 16).replace("T", " ")}`
100826
- }))
100827
- ]
101772
+ options: startupSessionChoices(rows, leaseState)
100828
101773
  });
100829
101774
  input.focus();
100830
101775
  if (pickId === "__new__") {
100831
- applyOpened(openSession({
100832
- cwd: sessionCwd,
100833
- provider: currentSel.provider,
100834
- model: currentSel.model
100835
- }));
101776
+ applyOpened(leasedOpen());
100836
101777
  } else {
100837
- applyOpened(openSession({
100838
- cwd: sessionCwd,
100839
- resumeId: pickId,
100840
- provider: currentSel.provider,
100841
- model: currentSel.model
100842
- }));
101778
+ const opened = leasedOpen({ resumeId: pickId });
101779
+ applyOpened(opened);
101780
+ showOpenedNotes(opened);
100843
101781
  }
100844
101782
  }
100845
101783
  } else {
100846
- const opened = openSession({
100847
- cwd: sessionCwd,
101784
+ const opened = leasedOpen({
100848
101785
  ...opts.session?.continueLast === true ? { continueLast: true } : {},
100849
101786
  ...opts.session?.resumeId !== undefined ? { resumeId: opts.session.resumeId } : {},
100850
- provider: currentSel.provider,
100851
- model: currentSel.model
101787
+ ...opts.session?.fork === true ? { fork: true } : {},
101788
+ ...opts.session?.takeOver === true ? { takeOver: true } : {}
100852
101789
  });
100853
101790
  applyOpened(opened);
100854
- if (opened.archiveDegraded !== undefined) {
100855
- transcript.add(new otui.TextRenderable(r, {
100856
- id: `sessdeg${uid++}`,
100857
- content: otui.t`${otui.yellow(`archive unavailable \u2014 resumed from the active context (${opened.archiveDegraded})`)}`,
100858
- marginTop: 1
100859
- }));
100860
- }
100861
- if (opened.resumed) {
100862
- transcript.add(new otui.TextRenderable(r, {
100863
- id: `sess${uid++}`,
100864
- content: otui.t`${otui.dim(`session ${shortSessionId(liveSession.summary.id)} \xB7 ${liveSession.summary.title} \xB7 ctx ${history.length} \xB7 archive ${archive.length}`)}`,
100865
- marginTop: 1
100866
- }));
100867
- for (const m of history.filter((x) => x.role === "user").slice(-5)) {
100868
- const t = m.content.length > 100 ? `${m.content.slice(0, 97)}\u2026` : m.content;
100869
- transcript.add(new otui.TextRenderable(r, {
100870
- id: `sessu${uid++}`,
100871
- content: otui.t`${otui.dim(` \u276F ${t}`)}`
100872
- }));
101791
+ showOpenedNotes(opened);
101792
+ }
101793
+ } catch (cause) {
101794
+ let failure = cause;
101795
+ if (cause instanceof SessionLeasedError) {
101796
+ try {
101797
+ const outcome = await resolveLeasedStartup(cause, {
101798
+ choose: async (error) => {
101799
+ chrome.hideMenu();
101800
+ const id = await showComposerChoice(otui, r, chrome.dock, leasedChoiceRequest(error));
101801
+ input.focus();
101802
+ return toLeasedChoice(error, id);
101803
+ },
101804
+ open: (target) => leasedOpen(target),
101805
+ exportSession: (sessionId) => exportSessionMarkdown(sessionCwd, sessionId),
101806
+ showReadOnly: (markdown) => {
101807
+ viewedReadOnly = true;
101808
+ transcript.add(new otui.TextRenderable(r, {
101809
+ id: `sessview${uid++}`,
101810
+ content: otui.t`${otui.dim(`${markdown.replace(/\n+$/, "")}
101811
+
101812
+ \u2014 read-only view; a new session starts below \u2014`)}`,
101813
+ marginTop: 1
101814
+ }));
101815
+ },
101816
+ notice: (text) => {
101817
+ transcript.add(new otui.TextRenderable(r, {
101818
+ id: `sesslease${uid++}`,
101819
+ content: otui.t`${otui.yellow(text)}`,
101820
+ marginTop: 1
101821
+ }));
101822
+ }
101823
+ });
101824
+ if (outcome.kind === "cancelled") {
101825
+ startupCancelled = true;
101826
+ } else {
101827
+ applyOpened(outcome.opened);
101828
+ showOpenedNotes(outcome.opened);
100873
101829
  }
101830
+ failure = undefined;
101831
+ } catch (leaseCause) {
101832
+ failure = leaseCause;
100874
101833
  }
100875
101834
  }
100876
- } catch (cause) {
100877
- transcript.add(new otui.TextRenderable(r, {
100878
- id: `sesserr${uid++}`,
100879
- content: otui.t`${otui.red(cause instanceof Error ? cause.message : String(cause))}`,
100880
- marginTop: 1
100881
- }));
100882
- applyOpened(openSession({
100883
- cwd: sessionCwd,
100884
- provider: currentSel.provider,
100885
- model: currentSel.model
100886
- }));
101835
+ if (failure !== undefined) {
101836
+ transcript.add(new otui.TextRenderable(r, {
101837
+ id: `sesserr${uid++}`,
101838
+ content: otui.t`${otui.red(failure instanceof Error ? failure.message : String(failure))}`,
101839
+ marginTop: 1
101840
+ }));
101841
+ applyOpened(leasedOpen());
101842
+ }
101843
+ }
101844
+ if (startupCancelled) {
101845
+ r.off("theme_mode", onThemeMode);
101846
+ r.destroy();
101847
+ await done;
101848
+ return true;
100887
101849
  }
100888
101850
  bindSlateToLiveSession();
100889
101851
  if (history.length === 0) {
100890
101852
  removeSplash = mountEmptyTranscriptSplash(otui, r, transcript);
100891
101853
  }
101854
+ if (viewedReadOnly) {
101855
+ removeSplash?.();
101856
+ removeSplash = undefined;
101857
+ }
100892
101858
  refreshWorkspaceSidebar();
100893
101859
  refreshReviewSidebar();
100894
101860
  const paintSessionHeader = () => {
@@ -100898,7 +101864,19 @@ async function launchTuiAgentShell(opts) {
100898
101864
  const cx = liveSession.summary.compactCount > 0 ? ` \xB7 c\xD7${liveSession.summary.compactCount}` : "";
100899
101865
  chrome.setTitle(`keryx \xB7 ${title} \xB7 ${sid}${cx} \xB7 ${label}`);
100900
101866
  };
101867
+ sessionLease.onLost((message) => {
101868
+ detachSlateSession(slateSession);
101869
+ slateSession = undefined;
101870
+ if (sessionPersistTimer !== undefined) {
101871
+ clearTimeout(sessionPersistTimer);
101872
+ sessionPersistTimer = undefined;
101873
+ }
101874
+ io.onSystem?.(message);
101875
+ });
100901
101876
  const saveSession = () => {
101877
+ if (!sessionLease.canPersist()) {
101878
+ return;
101879
+ }
100902
101880
  liveSession = persistHistory(liveSession, history, {
100903
101881
  archive,
100904
101882
  provider: currentSel.provider,
@@ -100951,12 +101929,17 @@ async function launchTuiAgentShell(opts) {
100951
101929
  usage.resetUsage();
100952
101930
  };
100953
101931
  const startNewSession = (note) => {
101932
+ let opened;
101933
+ try {
101934
+ opened = leasedOpen();
101935
+ } catch (cause) {
101936
+ io.onSystem?.(`Could not start a new session: ${cause instanceof Error ? cause.message : String(cause)}
101937
+ Staying in the current session.
101938
+ `);
101939
+ return false;
101940
+ }
100954
101941
  resetSessionSurface();
100955
- liveSession = createSession({
100956
- cwd: sessionCwd,
100957
- provider: currentSel.provider,
100958
- model: currentSel.model
100959
- });
101942
+ liveSession = opened.handle;
100960
101943
  history = [];
100961
101944
  archive = [];
100962
101945
  nextArchiveIndex = 0;
@@ -100965,6 +101948,7 @@ async function launchTuiAgentShell(opts) {
100965
101948
  io.onSystem?.(`${note}
100966
101949
  `);
100967
101950
  }
101951
+ return true;
100968
101952
  };
100969
101953
  const resumeSessionInteractive = async () => {
100970
101954
  const found = await pickRecentSession();
@@ -100973,15 +101957,12 @@ async function launchTuiAgentShell(opts) {
100973
101957
  }
100974
101958
  let opened;
100975
101959
  try {
100976
- opened = openSession({
100977
- cwd: sessionCwd,
100978
- resumeId: found.id,
100979
- provider: currentSel.provider,
100980
- model: currentSel.model
100981
- });
101960
+ opened = leasedOpen({ resumeId: found.id });
100982
101961
  } catch (cause) {
101962
+ const hint = cause instanceof SessionLeasedError ? `To continue it in this shell, start one with: keryx shell -r ${shortSessionId(found.id)} --fork
101963
+ ` : "";
100983
101964
  io.onSystem?.(`Could not resume ${shortSessionId(found.id)}: ${cause instanceof Error ? cause.message : String(cause)}
100984
- Staying in the current session.
101965
+ ` + hint + `Staying in the current session.
100985
101966
  `);
100986
101967
  return;
100987
101968
  }
@@ -101049,7 +102030,7 @@ Staying in the current session.
101049
102030
  };
101050
102031
  const showWorkspace = () => {
101051
102032
  (async () => {
101052
- const dir = slateSession?.dir;
102033
+ const dir = liveSlateSession()?.dir;
101053
102034
  const workspaceId = dir !== undefined ? (await readSlate(dir).catch(() => {
101054
102035
  return;
101055
102036
  }))?.workspaceId : undefined;
@@ -101268,7 +102249,7 @@ Staying in the current session.
101268
102249
  };
101269
102250
  const switchTo = async (ns) => {
101270
102251
  currentSel = ns;
101271
- deps = { ...await opts.makeAgentDeps(ns, () => slateSession), onContextCompaction };
102252
+ deps = { ...await opts.makeAgentDeps(ns, liveSlateSession), onContextCompaction };
101272
102253
  liveDeps = deps;
101273
102254
  saveShellConfig(ns.baseUrl === undefined ? { provider: ns.provider, model: ns.model } : { provider: ns.provider, model: ns.model, baseUrl: ns.baseUrl });
101274
102255
  updateModelLabels();
@@ -101556,7 +102537,7 @@ Staying in the current session.
101556
102537
  });
101557
102538
  let answer = "";
101558
102539
  try {
101559
- const base = await opts.makeAgentDeps(currentSel, () => slateSession);
102540
+ const base = await opts.makeAgentDeps(currentSel, liveSlateSession);
101560
102541
  const tools = base.tools.filter((t) => t.definition.risk === "read" && !SIDE_WORKER_DENIED_TOOL_NAMES.has(t.definition.name));
101561
102542
  const sideDeps = {
101562
102543
  ...base,
@@ -101673,6 +102654,7 @@ Staying in the current session.
101673
102654
  await closeSlateSession(slateSession, mintTimestampAttemptId);
101674
102655
  await deps.sweepBackgroundJobs?.();
101675
102656
  jobs.removeAll();
102657
+ sessionLease.release();
101676
102658
  r.off("theme_mode", onThemeMode);
101677
102659
  r.destroy();
101678
102660
  })();
@@ -101861,6 +102843,7 @@ Staying in the current session.
101861
102843
  await closeSlateSession(slateSession, mintTimestampAttemptId);
101862
102844
  await deps.sweepBackgroundJobs?.();
101863
102845
  jobs.removeAll();
102846
+ sessionLease.release();
101864
102847
  r.off("theme_mode", onThemeMode);
101865
102848
  r.destroy();
101866
102849
  })();
@@ -101885,7 +102868,9 @@ Staying in the current session.
101885
102868
  if (command.name === "/clear" || command.name === "/new") {
101886
102869
  (async () => {
101887
102870
  await closeSlateSession(slateSession, mintTimestampAttemptId);
101888
- startNewSession();
102871
+ if (!startNewSession()) {
102872
+ return;
102873
+ }
101889
102874
  bindSlateToLiveSession();
101890
102875
  refreshWorkspaceSidebar();
101891
102876
  refreshReviewSidebar();
@@ -101916,6 +102901,10 @@ Staying in the current session.
101916
102901
  return;
101917
102902
  }
101918
102903
  if (command.name === "/compact") {
102904
+ if (!sessionLease.canPersist()) {
102905
+ io.onSystem?.(LOST_LEASE_COMPACT_REFUSAL);
102906
+ return;
102907
+ }
101919
102908
  const focus = line.trim().split(/\s+/).slice(1).join(" ").trim();
101920
102909
  const packed = compactSession(liveSession, history, archive, {
101921
102910
  keepLastUserTurns: 3,
@@ -102450,6 +103439,7 @@ ${tail}`,
102450
103439
  } catch {
102451
103440
  return false;
102452
103441
  } finally {
103442
+ sessionLease.release();
102453
103443
  await herdr.release();
102454
103444
  try {
102455
103445
  renderer?.destroy();
@@ -102850,6 +103840,7 @@ init_patch_risk();
102850
103840
  init_permission_mode();
102851
103841
  init_agent();
102852
103842
  init_slate_lifecycle();
103843
+ init_slate_lifecycle();
102853
103844
  function reportMcpProblems(runtime) {
102854
103845
  for (const problem of runtime.problems()) {
102855
103846
  console.error(`keryx mcp: ${problem.file} ${problem.message}`);
@@ -102896,6 +103887,132 @@ function readlineAgentHelpText() {
102896
103887
  ` + renderCommandHelp("agent", READLINE_AGENT_COMMANDS) + `Sessions are per-project: keryx shell -c | -r [id] | keryx sessions list
102897
103888
  `;
102898
103889
  }
103890
+ function bareResumeTarget(cwd, emit) {
103891
+ const pick = latestUnleasedSession(cwd);
103892
+ if (pick.skipped !== undefined) {
103893
+ emit(describeSkippedSession(pick.skipped));
103894
+ }
103895
+ return pick.summary?.id;
103896
+ }
103897
+ function leasedOpenOptions(session, cwd, provider, model) {
103898
+ return {
103899
+ cwd,
103900
+ ...session?.continueLast === true ? { continueLast: true } : {},
103901
+ ...session?.resumeId !== undefined ? { resumeId: session.resumeId } : {},
103902
+ ...session?.fork === true ? { fork: true } : {},
103903
+ ...session?.takeOver === true ? { takeOver: true } : {},
103904
+ provider,
103905
+ model
103906
+ };
103907
+ }
103908
+ function openFreshLeasedSession(open, cwd, provider, model) {
103909
+ try {
103910
+ const opened = open({ cwd, provider, model });
103911
+ return { handle: opened.handle, lease: opened.lease };
103912
+ } catch {
103913
+ return;
103914
+ }
103915
+ }
103916
+ async function resolveLeasedChoice(io, error) {
103917
+ const rows = leasedChoiceRows(error);
103918
+ io.write(`${describeLeasedSession(error)}
103919
+ ` + rows.map((row, index) => ` ${index + 1}) ${row.label}
103920
+ `).join(""));
103921
+ for (;; ) {
103922
+ io.write(`Choose [1-${rows.length}, default 1]: `);
103923
+ const line = await io.readLine();
103924
+ if (line === undefined) {
103925
+ io.write(`
103926
+ `);
103927
+ return "cancel";
103928
+ }
103929
+ const answer = line.trim().toLowerCase();
103930
+ if (answer.length === 0) {
103931
+ return "fork";
103932
+ }
103933
+ const byNumber = /^\d+$/.test(answer) ? rows[Number(answer) - 1] : undefined;
103934
+ const byName = rows.find((row) => row.choice === answer || row.choice.replace("-", " ") === answer || row.choice.replace("-", "") === answer);
103935
+ const picked = byNumber ?? byName;
103936
+ if (picked !== undefined) {
103937
+ return picked.choice;
103938
+ }
103939
+ io.write(`Not one of the choices: ${line.trim()}
103940
+ `);
103941
+ }
103942
+ }
103943
+ async function runWithLeaseChoice(session, run, runtime) {
103944
+ let current = session;
103945
+ for (;; ) {
103946
+ try {
103947
+ await run(current);
103948
+ return;
103949
+ } catch (error) {
103950
+ if (!(error instanceof SessionLeasedError)) {
103951
+ throw error;
103952
+ }
103953
+ if (!runtime.isTty) {
103954
+ runtime.writeError(`${error.message}
103955
+ `);
103956
+ return 1;
103957
+ }
103958
+ const choice = await resolveLeasedChoice(runtime.io, error);
103959
+ const base = {
103960
+ cwd: current.cwd,
103961
+ ...current.enabled !== undefined ? { enabled: current.enabled } : {},
103962
+ ...current.leaseBox !== undefined ? { leaseBox: current.leaseBox } : {},
103963
+ ...current.openLeased !== undefined ? { openLeased: current.openLeased } : {}
103964
+ };
103965
+ if (choice === "cancel") {
103966
+ return 0;
103967
+ }
103968
+ if (choice === "view") {
103969
+ try {
103970
+ const render = runtime.exportSession ?? ((cwd, id) => exportSessionMarkdown(cwd, id));
103971
+ runtime.io.write(`${render(current.cwd, error.summary.id)}
103972
+ `);
103973
+ } catch (cause) {
103974
+ runtime.io.write(`Could not render the session: ${cause instanceof Error ? cause.message : String(cause)}
103975
+ `);
103976
+ }
103977
+ current = base;
103978
+ } else if (choice === "fork") {
103979
+ current = { ...base, resumeId: error.summary.id, fork: true };
103980
+ } else {
103981
+ current = { ...base, resumeId: error.summary.id, takeOver: true };
103982
+ }
103983
+ }
103984
+ }
103985
+ }
103986
+ async function runLeasedChatShell(io, deps, run = runShell, notes = []) {
103987
+ const system = (text) => {
103988
+ if (io.onSystem !== undefined) {
103989
+ io.onSystem(text);
103990
+ } else {
103991
+ io.write(text);
103992
+ }
103993
+ };
103994
+ for (const note of notes) {
103995
+ system(note);
103996
+ }
103997
+ if (deps.session === undefined) {
103998
+ await run(io, deps);
103999
+ return;
104000
+ }
104001
+ let iterator;
104002
+ const runtime = {
104003
+ isTty: true,
104004
+ io: {
104005
+ write: system,
104006
+ readLine: async () => {
104007
+ iterator ??= io.lines[Symbol.asyncIterator]();
104008
+ const next = await iterator.next();
104009
+ return next.done === true ? undefined : next.value;
104010
+ }
104011
+ },
104012
+ writeError: system
104013
+ };
104014
+ return runWithLeaseChoice(deps.session, (session) => run(io, { ...deps, session }), runtime);
104015
+ }
102899
104016
  async function runShell(io, deps) {
102900
104017
  let providerName = deps.initial.provider;
102901
104018
  let modelName = deps.initial.model;
@@ -102914,17 +104031,22 @@ async function runShell(io, deps) {
102914
104031
  let live;
102915
104032
  let history = [];
102916
104033
  let archive = [];
104034
+ let lease;
104035
+ const leaseWatch = watchLeaseLoss((message) => system(message));
104036
+ const holdLease = (next) => {
104037
+ lease = next;
104038
+ leaseWatch.track(next);
104039
+ if (deps.session?.leaseBox !== undefined)
104040
+ deps.session.leaseBox.current = next;
104041
+ };
104042
+ const openLeased = deps.session?.openLeased ?? openLeasedSession;
102917
104043
  if (sessionsOn) {
102918
104044
  try {
102919
- const resumeId = deps.session?.resumeId;
102920
- if (resumeId === undefined && deps.session?.continueLast !== true) {}
102921
- const opened = openSession({
102922
- cwd: sessionCwd,
102923
- ...deps.session?.continueLast === true ? { continueLast: true } : {},
102924
- ...resumeId !== undefined ? { resumeId } : {},
102925
- provider: providerName,
102926
- model: modelName
102927
- });
104045
+ const opened = openLeased(leasedOpenOptions(deps.session, sessionCwd, providerName, modelName));
104046
+ holdLease(opened.lease);
104047
+ if (opened.skipped !== undefined) {
104048
+ system(describeSkippedSession(opened.skipped));
104049
+ }
102928
104050
  live = opened.handle;
102929
104051
  history = opened.history;
102930
104052
  archive = opened.archive.length > 0 ? [...opened.archive] : [...opened.history];
@@ -102937,7 +104059,12 @@ async function runShell(io, deps) {
102937
104059
  `);
102938
104060
  }
102939
104061
  } catch (cause) {
102940
- live = createSession({ cwd: sessionCwd, provider: providerName, model: modelName });
104062
+ if (cause instanceof SessionLeasedError) {
104063
+ throw cause;
104064
+ }
104065
+ const fresh = openFreshLeasedSession(openLeased, sessionCwd, providerName, modelName);
104066
+ holdLease(fresh?.lease);
104067
+ live = fresh?.handle ?? createSession({ cwd: sessionCwd, provider: providerName, model: modelName });
102941
104068
  history = [];
102942
104069
  archive = [];
102943
104070
  system(`${cause instanceof Error ? cause.message : String(cause)}
@@ -102945,8 +104072,12 @@ Starting a new session.
102945
104072
  `);
102946
104073
  }
102947
104074
  }
104075
+ const releaseLease = () => {
104076
+ releaseSessionLease(lease);
104077
+ holdLease(undefined);
104078
+ };
102948
104079
  const save = () => {
102949
- if (live === undefined) {
104080
+ if (live === undefined || !leaseWatch.canPersist()) {
102950
104081
  return;
102951
104082
  }
102952
104083
  try {
@@ -102973,6 +104104,7 @@ Starting a new session.
102973
104104
  const command = parts[0] ?? "";
102974
104105
  const argument = parts.slice(1).join(" ");
102975
104106
  if (command === "/exit" || command === "/quit") {
104107
+ releaseLease();
102976
104108
  return;
102977
104109
  }
102978
104110
  if (command === "/help") {
@@ -103032,7 +104164,16 @@ ${formatThemeList(getThemeId())}`);
103032
104164
  }
103033
104165
  if (command === "/clear" || command === "/new") {
103034
104166
  if (sessionsOn) {
103035
- live = createSession({ cwd: sessionCwd, provider: providerName, model: modelName });
104167
+ try {
104168
+ const next = switchLeasedSession(lease, () => openLeased({ cwd: sessionCwd, provider: providerName, model: modelName }));
104169
+ holdLease(next.lease);
104170
+ live = next.handle;
104171
+ } catch (cause) {
104172
+ system(`${cause instanceof Error ? cause.message : String(cause)}
104173
+ Kept the current session.
104174
+ `);
104175
+ continue;
104176
+ }
103036
104177
  history = [];
103037
104178
  archive = [];
103038
104179
  system(`New session ${shortSessionId(live.summary.id)} (previous kept on disk)
@@ -103048,6 +104189,10 @@ ${formatThemeList(getThemeId())}`);
103048
104189
  `);
103049
104190
  continue;
103050
104191
  }
104192
+ if (!leaseWatch.canPersist()) {
104193
+ system(LOST_LEASE_COMPACT_REFUSAL);
104194
+ continue;
104195
+ }
103051
104196
  const focus = argument.trim();
103052
104197
  const packed = compactSession(live, history, archive, {
103053
104198
  keepLastUserTurns: 3,
@@ -103183,6 +104328,7 @@ ${formatThemeList(getThemeId())}`);
103183
104328
 
103184
104329
  `);
103185
104330
  }
104331
+ releaseLease();
103186
104332
  }
103187
104333
  function realMakeProvider(write) {
103188
104334
  return (name, model, baseUrl) => {
@@ -103799,7 +104945,7 @@ ${text.trim()}`;
103799
104945
  deps = {
103800
104946
  ...deps,
103801
104947
  onContextCompaction: (r) => {
103802
- if (live === undefined) {
104948
+ if (live === undefined || !leaseWatch.canPersist()) {
103803
104949
  return;
103804
104950
  }
103805
104951
  const persisted = persistCompacted(live, r.context, archive, {
@@ -103812,17 +104958,44 @@ ${text.trim()}`;
103812
104958
  `);
103813
104959
  }
103814
104960
  };
104961
+ let lease;
104962
+ const leaseWatch = watchLeaseLoss((message) => {
104963
+ detachSlateSession(slateSession);
104964
+ slateSession = undefined;
104965
+ slateSessionBox.current = undefined;
104966
+ agentIo.onSystem?.(message);
104967
+ });
104968
+ gateBoxByLease(slateSessionBox, () => leaseWatch.canPersist());
104969
+ const holdLease = (next) => {
104970
+ lease = next;
104971
+ leaseWatch.track(next);
104972
+ if (sessionOpts?.leaseBox !== undefined)
104973
+ sessionOpts.leaseBox.current = next;
104974
+ };
104975
+ const openLeased = sessionOpts?.openLeased ?? openLeasedSession;
104976
+ const switchNewSession = () => {
104977
+ try {
104978
+ const next = switchLeasedSession(lease, () => openLeased({ cwd: sessionCwd, provider: deps.providerId, model: deps.modelId }));
104979
+ holdLease(next.lease);
104980
+ return next.handle;
104981
+ } catch (cause) {
104982
+ agentIo.onSystem?.(`${cause instanceof Error ? cause.message : String(cause)}
104983
+ Kept the current session.
104984
+ `);
104985
+ return false;
104986
+ }
104987
+ };
104988
+ const releaseLease = () => {
104989
+ releaseSessionLease(lease);
104990
+ holdLease(undefined);
104991
+ };
103815
104992
  if (sessionsOn) {
103816
104993
  try {
103817
- const resumeId = sessionOpts?.resumeId;
103818
- if (resumeId === undefined && sessionOpts?.continueLast !== true && sessionOpts !== undefined) {}
103819
- const opened = openSession({
103820
- cwd: sessionCwd,
103821
- ...sessionOpts?.continueLast === true ? { continueLast: true } : {},
103822
- ...resumeId !== undefined ? { resumeId } : {},
103823
- provider: deps.providerId,
103824
- model: deps.modelId
103825
- });
104994
+ const opened = openLeased(leasedOpenOptions(sessionOpts, sessionCwd, deps.providerId, deps.modelId));
104995
+ holdLease(opened.lease);
104996
+ if (opened.skipped !== undefined) {
104997
+ agentIo.onSystem?.(describeSkippedSession(opened.skipped));
104998
+ }
103826
104999
  live = opened.handle;
103827
105000
  history = opened.history;
103828
105001
  archive = opened.archive.length > 0 ? [...opened.archive] : [...opened.history];
@@ -103835,7 +105008,12 @@ ${text.trim()}`;
103835
105008
  `);
103836
105009
  }
103837
105010
  } catch (cause) {
103838
- live = createSession({
105011
+ if (cause instanceof SessionLeasedError) {
105012
+ throw cause;
105013
+ }
105014
+ const fresh = openFreshLeasedSession(openLeased, sessionCwd, deps.providerId, deps.modelId);
105015
+ holdLease(fresh?.lease);
105016
+ live = fresh?.handle ?? createSession({
103839
105017
  cwd: sessionCwd,
103840
105018
  provider: deps.providerId,
103841
105019
  model: deps.modelId
@@ -103851,7 +105029,7 @@ New session ${shortSessionId(live.summary.id)}.
103851
105029
  slateSession = live !== undefined ? { dir: live.dir, cwd: sessionCwd, opened: false } : undefined;
103852
105030
  slateSessionBox.current = slateSession;
103853
105031
  const save = () => {
103854
- if (live === undefined) {
105032
+ if (live === undefined || !leaseWatch.canPersist()) {
103855
105033
  return;
103856
105034
  }
103857
105035
  try {
@@ -103927,6 +105105,7 @@ ${GUTTER}${turnSeparator()}
103927
105105
  if (line === undefined) {
103928
105106
  await closeSlateSession(slateSession, mintTimestampAttemptId);
103929
105107
  await deps.sweepBackgroundJobs?.();
105108
+ releaseLease();
103930
105109
  return;
103931
105110
  }
103932
105111
  rich.safeBoundary?.();
@@ -103937,6 +105116,7 @@ ${GUTTER}${turnSeparator()}
103937
105116
  if (command === "/exit" || command === "/quit") {
103938
105117
  await closeSlateSession(slateSession, mintTimestampAttemptId);
103939
105118
  await deps.sweepBackgroundJobs?.();
105119
+ releaseLease();
103940
105120
  return;
103941
105121
  }
103942
105122
  if (command === "/help") {
@@ -103981,13 +105161,14 @@ ${GUTTER}${turnSeparator()}
103981
105161
  `);
103982
105162
  }
103983
105163
  } else if (command === "/new" || command === "/clear") {
105164
+ const nextLive = sessionsOn ? switchNewSession() : undefined;
105165
+ if (nextLive === false) {
105166
+ rich.printPrompt();
105167
+ continue;
105168
+ }
103984
105169
  await closeSlateSession(slateSession, mintTimestampAttemptId);
103985
- if (sessionsOn) {
103986
- live = createSession({
103987
- cwd: sessionCwd,
103988
- provider: deps.providerId,
103989
- model: deps.modelId
103990
- });
105170
+ if (nextLive !== undefined) {
105171
+ live = nextLive;
103991
105172
  history = [];
103992
105173
  archive = [];
103993
105174
  nextArchiveIndex = 0;
@@ -104005,6 +105186,8 @@ ${GUTTER}${turnSeparator()}
104005
105186
  if (live === undefined) {
104006
105187
  agentIo.onSystem?.(`No persistent session.
104007
105188
  `);
105189
+ } else if (!leaseWatch.canPersist()) {
105190
+ agentIo.onSystem?.(LOST_LEASE_COMPACT_REFUSAL);
104008
105191
  } else {
104009
105192
  const packed = compactSession(live, history, archive, {
104010
105193
  keepLastUserTurns: 3,
@@ -104291,6 +105474,14 @@ async function resolveTuiStartup(opts) {
104291
105474
  appliedKeys
104292
105475
  };
104293
105476
  }
105477
+
105478
+ class ShellFlagError extends Error {
105479
+ exitCode = 2;
105480
+ constructor(message) {
105481
+ super(`${message}. See keryx shell --help.`);
105482
+ this.name = "ShellFlagError";
105483
+ }
105484
+ }
104294
105485
  function parseShellCliFlags(args) {
104295
105486
  if (args.includes("--help") || args.includes("-h")) {
104296
105487
  return { help: true, wantTui: true };
@@ -104313,6 +105504,8 @@ function parseShellCliFlags(args) {
104313
105504
  let continueLast;
104314
105505
  let resumeId;
104315
105506
  let resumePick;
105507
+ let fork;
105508
+ let takeOver;
104316
105509
  let permissionModeFlag;
104317
105510
  let denyTools;
104318
105511
  let printPromptArg;
@@ -104349,6 +105542,10 @@ function parseShellCliFlags(args) {
104349
105542
  } else {
104350
105543
  resumePick = true;
104351
105544
  }
105545
+ } else if (arg === "--fork") {
105546
+ fork = true;
105547
+ } else if (arg === "--take-over") {
105548
+ takeOver = true;
104352
105549
  } else if (arg === "--permission-mode") {
104353
105550
  const next = valueAfter(i++);
104354
105551
  if (!isPermissionMode(next))
@@ -104384,6 +105581,18 @@ function parseShellCliFlags(args) {
104384
105581
  if (continueLast && (resumeId !== undefined || resumePick)) {
104385
105582
  invalid("--continue and --resume cannot be combined");
104386
105583
  }
105584
+ const leaseFlag = fork === true ? "--fork" : takeOver === true ? "--take-over" : undefined;
105585
+ if (leaseFlag !== undefined) {
105586
+ if (continueLast === true) {
105587
+ throw new ShellFlagError(`${leaseFlag} cannot be combined with --continue; use -r <id> ${leaseFlag}`);
105588
+ }
105589
+ if (fork === true && takeOver === true) {
105590
+ throw new ShellFlagError("--fork and --take-over cannot be combined; choose one");
105591
+ }
105592
+ if (resumeId === undefined) {
105593
+ throw new ShellFlagError(`${leaseFlag} needs an explicit session id; use -r <id> ${leaseFlag}`);
105594
+ }
105595
+ }
104387
105596
  if (printPromptArg !== undefined && modeFlag === false) {
104388
105597
  invalid("--print is agent-mode only and cannot be combined with --chat");
104389
105598
  }
@@ -104396,6 +105605,8 @@ function parseShellCliFlags(args) {
104396
105605
  ...continueLast === true ? { continueLast: true } : {},
104397
105606
  ...resumeId !== undefined ? { resumeId } : {},
104398
105607
  ...resumePick === true ? { resumePick: true } : {},
105608
+ ...fork === true ? { fork: true } : {},
105609
+ ...takeOver === true ? { takeOver: true } : {},
104399
105610
  ...permissionModeFlag !== undefined ? { permissionModeFlag } : {},
104400
105611
  ...denyTools !== undefined ? { denyTools } : {},
104401
105612
  ...printPromptArg !== undefined ? { printPrompt: printPromptArg } : {},
@@ -104429,8 +105640,10 @@ async function shellCommand(args, runtime = {}) {
104429
105640
  --base-url <url> Override the provider endpoint
104430
105641
  --agent | --chat Choose agent (default) or chat mode
104431
105642
  --tui | --no-tui Prefer TUI (default) or readline; last flag wins
104432
- --continue, -c Continue the latest project session
105643
+ --continue, -c Continue the most recent session no other shell has open
104433
105644
  --resume, -r [id-or-title] Resume a session, or open the session picker
105645
+ --fork With -r <id>: continue in a fork of that session
105646
+ --take-over With -r <id>: take the session from a stale holder
104434
105647
  --permission-mode <mode> Agent permissions: ask, trust or auto
104435
105648
  --deny-tools <a,b> Withhold these tools from the session entirely
104436
105649
  --ask | --trust | --auto Permission shortcuts; last flag wins
@@ -104441,7 +105654,10 @@ async function shellCommand(args, runtime = {}) {
104441
105654
  that re-arms terminal input if it stops; see debug/latest.txt
104442
105655
 
104443
105656
  Session continuation and resume are mutually exclusive. Permission flags
104444
- are ignored in chat mode. Without a TTY, resume without an ID uses the latest session.
105657
+ are ignored in chat mode. Without a TTY, resume without an ID uses the latest
105658
+ session no other shell has open. A session open in another shell is refused;
105659
+ --fork and --take-over (stale holders only) need an explicit -r <id> and
105660
+ cannot be combined with each other or with --continue.
104445
105661
 
104446
105662
  A shell that stops reacting to keys can be recovered from another terminal
104447
105663
  with: kill -USR2 <keryx pid>
@@ -104481,6 +105697,10 @@ Example: keryx shell --provider ollama --model llama3.1:latest`);
104481
105697
  const modelArg = flags.modelArg;
104482
105698
  let baseUrl = flags.baseUrl;
104483
105699
  let modeFlag = flags.modeFlag;
105700
+ const leaseFlagOpts = {
105701
+ ...flags.fork === true ? { fork: true } : {},
105702
+ ...flags.takeOver === true ? { takeOver: true } : {}
105703
+ };
104484
105704
  {
104485
105705
  await Promise.resolve().then(() => init_login());
104486
105706
  const oauthFetch = (input, init) => globalThis.fetch(input, init);
@@ -104580,7 +105800,7 @@ Example: keryx shell --provider ollama --model llama3.1:latest`);
104580
105800
  sessionOverride: reasoningSessionOverride,
104581
105801
  globalEffort: loadShellConfig(runtime.cacheDir).reasoningEffort
104582
105802
  }),
104583
- idSeq: () => randomUUID29(),
105803
+ idSeq: () => randomUUID30(),
104584
105804
  askUser: invokeAskUserHost,
104585
105805
  sweepBackgroundJobs: () => jobRegistry.sweepAll(),
104586
105806
  jobRegistry,
@@ -104611,29 +105831,40 @@ Example: keryx shell --provider ollama --model llama3.1:latest`);
104611
105831
  });
104612
105832
  const tuiInitial = startup.initial;
104613
105833
  const tuiDetected = startup.detected;
105834
+ const chatLeaseBox = { current: undefined };
104614
105835
  try {
104615
105836
  if (surface === "tui-chat") {
104616
105837
  const chatFactory = realMakeProvider(() => {});
104617
105838
  let chatResumeId = flags.resumeId;
105839
+ const chatNotes = [];
104618
105840
  if (flags.resumePick === true && chatResumeId === undefined) {
104619
- chatResumeId = latestSession(cwd)?.id;
105841
+ chatResumeId = bareResumeTarget(cwd, (text) => {
105842
+ chatNotes.push(text);
105843
+ });
104620
105844
  }
104621
105845
  if (await (runtime.launchChat ?? launchTuiChatShell)({
104622
105846
  detected: tuiDetected,
104623
105847
  redetect,
104624
105848
  ...tuiInitial !== undefined ? { initial: tuiInitial } : {},
104625
- runShell,
105849
+ runShell: async (chatIo, chatDeps) => {
105850
+ const code = await runLeasedChatShell(chatIo, chatDeps, runShell, chatNotes.splice(0));
105851
+ if (code !== undefined) {
105852
+ process.exitCode = code;
105853
+ }
105854
+ },
104626
105855
  makeShellDeps: (sel) => {
104627
105856
  const resolvedModelParams = resolveProviderModelParamsByName(sel.provider, loadShellConfig(runtime.cacheDir), runtime.cacheDir);
104628
105857
  return {
104629
105858
  makeProvider: chatFactory,
104630
105859
  clock: () => new Date().toISOString(),
104631
- idSeq: () => randomUUID29(),
105860
+ idSeq: () => randomUUID30(),
104632
105861
  initial: Object.keys(resolvedModelParams).length > 0 ? { ...sel, modelParams: resolvedModelParams } : sel,
104633
105862
  session: {
104634
105863
  cwd,
104635
105864
  ...flags.continueLast === true ? { continueLast: true } : {},
104636
- ...chatResumeId !== undefined ? { resumeId: chatResumeId } : {}
105865
+ ...chatResumeId !== undefined ? { resumeId: chatResumeId } : {},
105866
+ ...leaseFlagOpts,
105867
+ leaseBox: chatLeaseBox
104637
105868
  }
104638
105869
  };
104639
105870
  },
@@ -104653,7 +105884,8 @@ Example: keryx shell --provider ollama --model llama3.1:latest`);
104653
105884
  cwd,
104654
105885
  ...flags.continueLast === true ? { continueLast: true } : {},
104655
105886
  ...flags.resumeId !== undefined ? { resumeId: flags.resumeId } : {},
104656
- ...flags.resumePick === true ? { pickOnStart: true } : {}
105887
+ ...flags.resumePick === true ? { pickOnStart: true } : {},
105888
+ ...leaseFlagOpts
104657
105889
  },
104658
105890
  ...flags.permissionModeFlag !== undefined ? { initialPermissionMode: flags.permissionModeFlag } : {},
104659
105891
  versionCheck
@@ -104661,6 +105893,8 @@ Example: keryx shell --provider ollama --model llama3.1:latest`);
104661
105893
  return;
104662
105894
  }
104663
105895
  } finally {
105896
+ releaseSessionLease(chatLeaseBox.current);
105897
+ chatLeaseBox.current = undefined;
104664
105898
  if (mcpRuntime !== undefined) {
104665
105899
  await mcpRuntime.close();
104666
105900
  reportMcpProblems(mcpRuntime);
@@ -104669,7 +105903,10 @@ Example: keryx shell --provider ollama --model llama3.1:latest`);
104669
105903
  }
104670
105904
  const rl = readline2.createInterface({ input: process.stdin });
104671
105905
  let readlineMcp;
105906
+ const leaseBox = { current: undefined };
104672
105907
  const closeAndExit = (code) => () => {
105908
+ releaseSessionLease(leaseBox.current);
105909
+ leaseBox.current = undefined;
104673
105910
  (async () => {
104674
105911
  try {
104675
105912
  await readlineMcp?.close();
@@ -104688,6 +105925,27 @@ Example: keryx shell --provider ollama --model llama3.1:latest`);
104688
105925
  }
104689
105926
  };
104690
105927
  const { io, emitSystem, printHeader, printPrompt, destroy } = createRichIo(sharedLines, versionCheck);
105928
+ const leaseChoiceRuntime = {
105929
+ isTty: process.stdin.isTTY === true && oneShotPrompt === undefined,
105930
+ io: {
105931
+ write: (text) => {
105932
+ io.write(text);
105933
+ },
105934
+ readLine: async () => {
105935
+ const next = await lineIterator.next();
105936
+ return next.done === true ? undefined : next.value;
105937
+ }
105938
+ },
105939
+ writeError: (text) => {
105940
+ process.stderr.write(text);
105941
+ }
105942
+ };
105943
+ const bareResumeId = () => bareResumeTarget(process.cwd(), emitSystem);
105944
+ const finishLeased = (code) => {
105945
+ if (code !== undefined) {
105946
+ process.exitCode = code;
105947
+ }
105948
+ };
104691
105949
  let provider;
104692
105950
  let model;
104693
105951
  try {
@@ -104727,7 +105985,7 @@ Example: keryx shell --provider ollama --model llama3.1:latest`);
104727
105985
  const deps = {
104728
105986
  makeProvider: baseFactory,
104729
105987
  clock: () => new Date().toISOString(),
104730
- idSeq: () => randomUUID29(),
105988
+ idSeq: () => randomUUID30(),
104731
105989
  initial: {
104732
105990
  provider,
104733
105991
  model,
@@ -104799,7 +106057,7 @@ Example: keryx shell --provider ollama --model llama3.1:latest`);
104799
106057
  sessionOverride: reasoningSessionOverride,
104800
106058
  globalEffort: loadShellConfig(runtime.cacheDir).reasoningEffort
104801
106059
  }),
104802
- idSeq: () => randomUUID29(),
106060
+ idSeq: () => randomUUID30(),
104803
106061
  askUser: invokeAskUserHost,
104804
106062
  sweepBackgroundJobs: () => jobRegistry.sweepAll(),
104805
106063
  ...resetSubagentBudget !== undefined ? { resetSubagentBudget } : {},
@@ -104815,34 +106073,38 @@ Example: keryx shell --provider ollama --model llama3.1:latest`);
104815
106073
  };
104816
106074
  let resumeId = flags.resumeId;
104817
106075
  if (flags.resumePick === true && resumeId === undefined) {
104818
- resumeId = latestSession(process.cwd())?.id;
106076
+ resumeId = bareResumeId();
104819
106077
  }
104820
106078
  const events = flags.eventsFile === undefined ? undefined : createFileEventSink(flags.eventsFile, (message) => {
104821
106079
  emitSystem(`${message}
104822
106080
  `);
104823
106081
  }, flags.eventsMaxField);
106082
+ const runRepl = async (session) => {
106083
+ await runAgentRepl(sharedLines, { printPrompt, safeBoundary: io.onSafeBoundary }, agentDeps, metaprojectPort, session, flags.permissionModeFlag, slateSessionBox, events, runtime.cacheDir);
106084
+ };
104824
106085
  try {
104825
- await runAgentRepl(sharedLines, { printPrompt, safeBoundary: io.onSafeBoundary }, agentDeps, metaprojectPort, {
106086
+ finishLeased(await runWithLeaseChoice({
104826
106087
  cwd: process.cwd(),
104827
106088
  ...flags.continueLast === true ? { continueLast: true } : {},
104828
- ...resumeId !== undefined ? { resumeId } : {}
104829
- }, flags.permissionModeFlag, slateSessionBox, events, runtime.cacheDir);
106089
+ ...resumeId !== undefined ? { resumeId } : {},
106090
+ ...leaseFlagOpts,
106091
+ leaseBox
106092
+ }, runRepl, leaseChoiceRuntime));
104830
106093
  } finally {
104831
106094
  await mcpRuntime.close();
104832
106095
  }
104833
106096
  } else {
104834
106097
  let resumeId = flags.resumeId;
104835
106098
  if (flags.resumePick === true && resumeId === undefined) {
104836
- resumeId = latestSession(process.cwd())?.id;
106099
+ resumeId = bareResumeId();
104837
106100
  }
104838
- await runShell(io, {
104839
- ...deps,
104840
- session: {
104841
- cwd: process.cwd(),
104842
- ...flags.continueLast === true ? { continueLast: true } : {},
104843
- ...resumeId !== undefined ? { resumeId } : {}
104844
- }
104845
- });
106101
+ finishLeased(await runWithLeaseChoice({
106102
+ cwd: process.cwd(),
106103
+ ...flags.continueLast === true ? { continueLast: true } : {},
106104
+ ...resumeId !== undefined ? { resumeId } : {},
106105
+ ...leaseFlagOpts,
106106
+ leaseBox
106107
+ }, (session) => runShell(io, { ...deps, session }), leaseChoiceRuntime));
104846
106108
  }
104847
106109
  } finally {
104848
106110
  try {
@@ -104863,12 +106125,13 @@ async function sessionsCommand(args) {
104863
106125
  const cwd = process.cwd();
104864
106126
  if (sub === "list") {
104865
106127
  const asJson = args.includes("--json");
104866
- const rows = listSessions(cwd);
106128
+ const listed = listSessions(cwd).map((s) => ({ summary: s, lease: leaseColumn(cwd, s.id) }));
104867
106129
  if (asJson) {
106130
+ const rows = listed.map(({ summary, lease }) => ({ ...summary, live: lease === "?" ? null : lease }));
104868
106131
  console.log(JSON.stringify({ schemaVersion: 1, project: resolveProjectRoot2(cwd), sessions: rows }, null, 2));
104869
106132
  return;
104870
106133
  }
104871
- if (rows.length === 0) {
106134
+ if (listed.length === 0) {
104872
106135
  console.log(`No sessions for project ${resolveProjectRoot2(cwd)}`);
104873
106136
  console.log(`(store: ${projectSessionsDir(cwd)})`);
104874
106137
  return;
@@ -104876,10 +106139,10 @@ async function sessionsCommand(args) {
104876
106139
  console.log(`Project: ${resolveProjectRoot2(cwd)}`);
104877
106140
  console.log(`Store: ${projectSessionsDir(cwd)}`);
104878
106141
  console.log("");
104879
- console.log(pad("ID", 10) + pad("UPDATED", 22) + pad("MSGS", 6) + pad("MODEL", 24) + "TITLE");
104880
- for (const s of rows) {
106142
+ console.log(pad("ID", 10) + pad("UPDATED", 22) + pad("MSGS", 6) + pad("LIVE", 7) + pad("MODEL", 24) + "TITLE");
106143
+ for (const { summary: s, lease } of listed) {
104881
106144
  const model = s.provider !== undefined && s.model !== undefined ? `${s.provider}/${s.model}` : s.model ?? "-";
104882
- console.log(pad(shortSessionId(s.id), 10) + pad(s.updatedAt.slice(0, 19).replace("T", " "), 22) + pad(String(s.messageCount), 6) + pad(clip8(model, 22), 24) + (s.parentSessionId !== undefined ? `\u21B3 ${s.title}` : s.title));
106145
+ console.log(pad(shortSessionId(s.id), 10) + pad(s.updatedAt.slice(0, 19).replace("T", " "), 22) + pad(String(s.messageCount), 6) + pad(lease ?? "", 7) + pad(clip8(model, 22), 24) + (s.parentSessionId !== undefined ? `\u21B3 ${s.title}` : s.title));
104883
106146
  }
104884
106147
  console.log("");
104885
106148
  console.log("Resume: keryx shell -r <id> Continue last: keryx shell -c");
@@ -104966,6 +106229,19 @@ async function sessionsCommand(args) {
104966
106229
  printHelp17();
104967
106230
  process.exitCode = 1;
104968
106231
  }
106232
+ function leaseColumn(cwd, sessionId) {
106233
+ let state;
106234
+ try {
106235
+ state = sessionLeaseState(cwd, sessionId).state;
106236
+ } catch {
106237
+ return "?";
106238
+ }
106239
+ if (state === "live" || state === "mine")
106240
+ return "live";
106241
+ if (state === "stale")
106242
+ return "stale";
106243
+ return null;
106244
+ }
104969
106245
  function pad(s, n) {
104970
106246
  return s.length >= n ? `${s.slice(0, n - 1)} ` : s + " ".repeat(n - s.length);
104971
106247
  }
@@ -104983,9 +106259,16 @@ Usage:
104983
106259
  keryx sessions export <id> Export transcript as Markdown
104984
106260
  keryx sessions path Print the on-disk sessions directory
104985
106261
 
106262
+ List columns: ID, UPDATED, MSGS, LIVE, MODEL, TITLE. LIVE is "live" when a
106263
+ shell has the session open, "stale" when its holder stopped heartbeating, and
106264
+ blank when no shell holds it. In --json every row carries "live": "live" |
106265
+ "stale" | null.
106266
+
104986
106267
  Shell:
104987
- keryx shell -c Continue last session in this project
106268
+ keryx shell -c Continue the last session no other shell has open
104988
106269
  keryx shell -r [id] Resume session (id / short id / title)
106270
+ keryx shell -r <id> --fork Fork a session another shell has open
106271
+ keryx shell -r <id> --take-over Take over a session whose holder is stale
104989
106272
  `);
104990
106273
  }
104991
106274
 
@@ -104993,7 +106276,7 @@ Shell:
104993
106276
  init_fs();
104994
106277
  import { readFile as readFile99 } from "fs/promises";
104995
106278
  import { stdin } from "process";
104996
- import path196 from "path";
106279
+ import path197 from "path";
104997
106280
  var MODULES = [
104998
106281
  { name: "gdgraph", flag: "--no-gdgraph", desc: "code graph, symbols, affected context", defaultEnabled: true },
104999
106282
  { name: "gdctx", flag: "--no-gdctx", desc: "token-aware command/read output", defaultEnabled: true },
@@ -105039,8 +106322,8 @@ async function modulesCommand(args = []) {
105039
106322
  return;
105040
106323
  }
105041
106324
  const wantsJson = args.includes("--json") && (sub === undefined || sub === "status" || sub === "list" || sub === "--json");
105042
- const metaprojectRoot = path196.join(process.cwd(), ".metaproject");
105043
- const manifestPath = path196.join(metaprojectRoot, "metaproject.json");
106325
+ const metaprojectRoot = path197.join(process.cwd(), ".metaproject");
106326
+ const manifestPath = path197.join(metaprojectRoot, "metaproject.json");
105044
106327
  if (!await pathExists(manifestPath)) {
105045
106328
  if (wantsJson) {
105046
106329
  console.log(JSON.stringify({ schemaVersion: 1, error: "not-initialized", modules: [] }, null, 2));
@@ -105154,26 +106437,26 @@ function printHelp18() {
105154
106437
  }
105155
106438
 
105156
106439
  // src/commands/serve.ts
105157
- import { randomUUID as randomUUID32 } from "crypto";
106440
+ import { randomUUID as randomUUID33 } from "crypto";
105158
106441
  init_serve_config();
105159
106442
 
105160
106443
  // src/lib/serve-credential.ts
105161
106444
  init_config_dir();
105162
- import { createHash as createHash44, randomBytes as randomBytes5, randomUUID as randomUUID30 } from "crypto";
106445
+ import { createHash as createHash44, randomBytes as randomBytes5, randomUUID as randomUUID31 } from "crypto";
105163
106446
  import {
105164
- chmodSync as chmodSync4,
106447
+ chmodSync as chmodSync5,
105165
106448
  closeSync as closeSync4,
105166
106449
  existsSync as existsSync34,
105167
106450
  fsyncSync as fsyncSync2,
105168
- openSync as openSync4,
105169
- renameSync as renameSync4,
105170
- statSync as statSync7,
106451
+ openSync as openSync5,
106452
+ renameSync as renameSync5,
106453
+ statSync as statSync8,
105171
106454
  unlinkSync as unlinkSync3,
105172
- writeFileSync as writeFileSync9
106455
+ writeFileSync as writeFileSync10
105173
106456
  } from "fs";
105174
- import path197 from "path";
106457
+ import path198 from "path";
105175
106458
  function serveCredentialPath(dir) {
105176
- return path197.join(keryxConfigDir(dir), "serve-credentials.json");
106459
+ return path198.join(keryxConfigDir(dir), "serve-credentials.json");
105177
106460
  }
105178
106461
  function constantTimeEqual(a, b) {
105179
106462
  const width = Math.max(a.length, b.length);
@@ -105202,7 +106485,7 @@ function isGroupOrOtherAccessible(file) {
105202
106485
  if (process.platform === "win32") {
105203
106486
  return false;
105204
106487
  }
105205
- const mode = statSync7(file, { throwIfNoEntry: false })?.mode;
106488
+ const mode = statSync8(file, { throwIfNoEntry: false })?.mode;
105206
106489
  return mode !== undefined && (mode & 63) !== 0;
105207
106490
  }
105208
106491
  function readServeCredential(dir) {
@@ -105243,18 +106526,18 @@ function readServeCredential(dir) {
105243
106526
  }
105244
106527
  function writeStore(store, dir) {
105245
106528
  const file = serveCredentialPath(dir);
105246
- const temp = `${file}.${randomUUID30()}.tmp`;
106529
+ const temp = `${file}.${randomUUID31()}.tmp`;
105247
106530
  try {
105248
106531
  ensureKeryxConfigDir(dir);
105249
- const handle = openSync4(temp, "wx", 384);
106532
+ const handle = openSync5(temp, "wx", 384);
105250
106533
  try {
105251
- writeFileSync9(handle, `${JSON.stringify(store, null, 2)}
106534
+ writeFileSync10(handle, `${JSON.stringify(store, null, 2)}
105252
106535
  `, { encoding: "utf8" });
105253
106536
  fsyncSync2(handle);
105254
106537
  } finally {
105255
106538
  closeSync4(handle);
105256
106539
  }
105257
- renameSync4(temp, file);
106540
+ renameSync5(temp, file);
105258
106541
  tighten2(file, 384);
105259
106542
  return true;
105260
106543
  } catch {
@@ -105269,7 +106552,7 @@ function tighten2(target, mode) {
105269
106552
  return;
105270
106553
  }
105271
106554
  try {
105272
- chmodSync4(target, mode);
106555
+ chmodSync5(target, mode);
105273
106556
  } catch {}
105274
106557
  }
105275
106558
  function withServeCredentialLock(dir, fn, onWaiting) {
@@ -105283,7 +106566,7 @@ function mintRecord(now) {
105283
106566
  const salt = randomBytes5(32).toString("hex");
105284
106567
  return {
105285
106568
  token,
105286
- record: { id: randomUUID30(), algorithm: "sha256", salt, hash: hashToken(salt, token), createdAt: now }
106569
+ record: { id: randomUUID31(), algorithm: "sha256", salt, hash: hashToken(salt, token), createdAt: now }
105287
106570
  };
105288
106571
  }
105289
106572
  function issueServeToken(dir, now = () => new Date().toISOString(), onWaiting) {
@@ -105406,23 +106689,23 @@ class AuthFailureThrottle {
105406
106689
  // src/lib/serve-turn-store.ts
105407
106690
  init_config_dir();
105408
106691
  import { createHash as createHash45 } from "crypto";
105409
- import { existsSync as existsSync35, readdirSync as readdirSync4, rmSync as rmSync2 } from "fs";
105410
- import path198 from "path";
106692
+ import { existsSync as existsSync35, readdirSync as readdirSync4, rmSync as rmSync3 } from "fs";
106693
+ import path199 from "path";
105411
106694
  var MAX_TURN_EVENTS = 1e4;
105412
106695
  function turnsRoot(dir) {
105413
- return path198.join(keryxConfigDir(dir), "turns");
106696
+ return path199.join(keryxConfigDir(dir), "turns");
105414
106697
  }
105415
106698
  function turnDir(turnId, dir) {
105416
- return path198.join(turnsRoot(dir), turnId);
106699
+ return path199.join(turnsRoot(dir), turnId);
105417
106700
  }
105418
106701
  function keyPath(project, idempotencyKey, dir) {
105419
106702
  const projectBytes = Buffer.byteLength(project, "utf8");
105420
106703
  const digest = createHash45("sha256").update(`${projectBytes}:${project}\x00${idempotencyKey}`, "utf8").digest("hex");
105421
- return path198.join(turnsRoot(dir), "keys", `${digest}.json`);
106704
+ return path199.join(turnsRoot(dir), "keys", `${digest}.json`);
105422
106705
  }
105423
106706
  function legacyKeyPath(idempotencyKey, dir) {
105424
106707
  const digest = createHash45("sha256").update(idempotencyKey, "utf8").digest("hex");
105425
- return path198.join(turnsRoot(dir), "keys", `${digest}.json`);
106708
+ return path199.join(turnsRoot(dir), "keys", `${digest}.json`);
105426
106709
  }
105427
106710
  function adoptLegacyClaim(project, idempotencyKey, dir) {
105428
106711
  const legacy = legacyKeyPath(idempotencyKey, dir);
@@ -105443,7 +106726,7 @@ function adoptLegacyClaim(project, idempotencyKey, dir) {
105443
106726
  } catch {}
105444
106727
  const record = turnId === undefined ? undefined : readTurnRecord(turnId, dir);
105445
106728
  if (turnId === undefined || record === undefined || !record.ok) {
105446
- rmSync2(legacy, { force: true });
106729
+ rmSync3(legacy, { force: true });
105447
106730
  return null;
105448
106731
  }
105449
106732
  if (record.value.project !== project) {
@@ -105451,7 +106734,7 @@ function adoptLegacyClaim(project, idempotencyKey, dir) {
105451
106734
  }
105452
106735
  writeOwnerOnlyFile(scoped, `${JSON.stringify({ turnId }, null, 2)}
105453
106736
  `);
105454
- rmSync2(legacy, { force: true });
106737
+ rmSync3(legacy, { force: true });
105455
106738
  return turnId;
105456
106739
  }
105457
106740
  function isServerFault(reason) {
@@ -105486,7 +106769,7 @@ function ensureTurnDir(turnId, dir) {
105486
106769
  }
105487
106770
  function createTurnRecord(record, dir) {
105488
106771
  ensureTurnDir(record.turnId, dir);
105489
- writeOwnerOnlyFile(path198.join(turnDir(record.turnId, dir), "turn.json"), `${JSON.stringify(record, null, 2)}
106772
+ writeOwnerOnlyFile(path199.join(turnDir(record.turnId, dir), "turn.json"), `${JSON.stringify(record, null, 2)}
105490
106773
  `);
105491
106774
  }
105492
106775
  function appendTurnEvent(event, dir, opts) {
@@ -105495,12 +106778,12 @@ function appendTurnEvent(event, dir, opts) {
105495
106778
  }
105496
106779
  const line = JSON.stringify(event);
105497
106780
  try {
105498
- appendOwnerOnlyLine(path198.join(turnDir(event.turnId, dir), "events.jsonl"), line);
106781
+ appendOwnerOnlyLine(path199.join(turnDir(event.turnId, dir), "events.jsonl"), line);
105499
106782
  } catch (error) {
105500
106783
  if (error?.code !== "ENOENT") {
105501
106784
  throw error;
105502
106785
  }
105503
- appendOwnerOnlyLine(path198.join(ensureTurnDir(event.turnId, dir), "events.jsonl"), line);
106786
+ appendOwnerOnlyLine(path199.join(ensureTurnDir(event.turnId, dir), "events.jsonl"), line);
105504
106787
  }
105505
106788
  return true;
105506
106789
  }
@@ -105508,7 +106791,7 @@ function readTurnEvents(turnId, after = -1, dir) {
105508
106791
  if (!isTurnId(turnId)) {
105509
106792
  return { ok: false, reason: "not-a-turn-id" };
105510
106793
  }
105511
- const read = readTurnFile(path198.join(turnDir(turnId, dir), "events.jsonl"));
106794
+ const read = readTurnFile(path199.join(turnDir(turnId, dir), "events.jsonl"));
105512
106795
  if (!read.ok) {
105513
106796
  if (isDefiniteAbsence2(read.reason)) {
105514
106797
  return { ok: true, value: [] };
@@ -105536,7 +106819,7 @@ function readTurnRecord(turnId, dir) {
105536
106819
  if (!isTurnId(turnId)) {
105537
106820
  return { ok: false, reason: "not-a-turn-id" };
105538
106821
  }
105539
- const read = readTurnFile(path198.join(turnDir(turnId, dir), "turn.json"));
106822
+ const read = readTurnFile(path199.join(turnDir(turnId, dir), "turn.json"));
105540
106823
  if (!read.ok) {
105541
106824
  return { ok: false, reason: read.reason };
105542
106825
  }
@@ -105555,7 +106838,7 @@ function finishTurn(turnId, result, dir) {
105555
106838
  if (!record.ok) {
105556
106839
  return false;
105557
106840
  }
105558
- writeOwnerOnlyFile(path198.join(turnDir(turnId, dir), "turn.json"), `${JSON.stringify({ ...record.value, result }, null, 2)}
106841
+ writeOwnerOnlyFile(path199.join(turnDir(turnId, dir), "turn.json"), `${JSON.stringify({ ...record.value, result }, null, 2)}
105559
106842
  `);
105560
106843
  return true;
105561
106844
  }
@@ -105595,13 +106878,13 @@ function releaseIdempotencyKey(project, idempotencyKey, turnId, dir) {
105595
106878
  } catch {
105596
106879
  return false;
105597
106880
  }
105598
- rmSync2(file, { force: true });
106881
+ rmSync3(file, { force: true });
105599
106882
  return true;
105600
106883
  }
105601
106884
 
105602
106885
  // src/lib/serve-turn.ts
105603
- import { randomUUID as randomUUID31 } from "crypto";
105604
- import path199 from "path";
106886
+ import { randomUUID as randomUUID32 } from "crypto";
106887
+ import path200 from "path";
105605
106888
  init_service();
105606
106889
  var REMOTE_ORIGIN = "remote:http";
105607
106890
  var MAX_PROMPT_CHARS = 32000;
@@ -105670,9 +106953,9 @@ function isUuid(value) {
105670
106953
  return typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(value);
105671
106954
  }
105672
106955
  function resolveProject(declared, dir) {
105673
- const wanted = path199.resolve(declared);
106956
+ const wanted = path200.resolve(declared);
105674
106957
  for (const entry of listProjects(dir, () => {})) {
105675
- if (path199.resolve(entry.path) === wanted) {
106958
+ if (path200.resolve(entry.path) === wanted) {
105676
106959
  return { ok: true, project: entry.path };
105677
106960
  }
105678
106961
  }
@@ -105719,7 +107002,7 @@ async function redactOut(security, text) {
105719
107002
  async function runRemoteTurn(input) {
105720
107003
  const scanRoot = input.scanRoot;
105721
107004
  const security = createSecurityService(scanRoot);
105722
- const newId = input.newId ?? (() => randomUUID31());
107005
+ const newId = input.newId ?? (() => randomUUID32());
105723
107006
  const clock = input.clock ?? (() => new Date().toISOString());
105724
107007
  const turnId = input.turnId ?? newId();
105725
107008
  const sessionId = input.request.sessionId ?? newId();
@@ -105862,7 +107145,7 @@ function outcomeOf(status, gate, unresolvedBlockerIds) {
105862
107145
  }
105863
107146
  function createSubmitTurn(deps) {
105864
107147
  return async (request, project) => {
105865
- const turnId = (deps.newId ?? (() => randomUUID31()))();
107148
+ const turnId = (deps.newId ?? (() => randomUUID32()))();
105866
107149
  const scanned = await scanPrompt(deps.dir, request.prompt);
105867
107150
  if (scanned.rejected) {
105868
107151
  return { kind: "rejected" };
@@ -106550,7 +107833,7 @@ function runConfig(args) {
106550
107833
  return;
106551
107834
  }
106552
107835
  const credential = readServeCredential();
106553
- const credentialId = credential.status === "ok" ? credential.record.id : randomUUID32();
107836
+ const credentialId = credential.status === "ok" ? credential.record.id : randomUUID33();
106554
107837
  const config = defaultServeConfig(credentialId, {
106555
107838
  address: parsed.parsed.values.get("--bind") ?? DEFAULT_SERVE_BIND_ADDRESS,
106556
107839
  port: port ?? DEFAULT_SERVE_PORT,
@@ -106708,7 +107991,7 @@ function printHelp19() {
106708
107991
  import { spawn as spawn7 } from "child_process";
106709
107992
  import { chmod as chmod4, mkdir as mkdir63, readFile as readFile100, readdir as readdir37, writeFile as writeFile54 } from "fs/promises";
106710
107993
  import { access as access4, constants as constants3, existsSync as existsSync36 } from "fs";
106711
- import path200 from "path";
107994
+ import path201 from "path";
106712
107995
  import { fileURLToPath as fileURLToPath8 } from "url";
106713
107996
  init_config2();
106714
107997
  init_config();
@@ -106725,8 +108008,8 @@ async function updateCommand(args = []) {
106725
108008
  return;
106726
108009
  }
106727
108010
  const projectRoot = process.cwd();
106728
- const metaprojectRoot = path200.join(projectRoot, ".metaproject");
106729
- banner("keryx update", `Refreshing the .metaproject workspace in ${path200.basename(projectRoot)}/`);
108011
+ const metaprojectRoot = path201.join(projectRoot, ".metaproject");
108012
+ banner("keryx update", `Refreshing the .metaproject workspace in ${path201.basename(projectRoot)}/`);
106730
108013
  if (!await pathExists(metaprojectRoot)) {
106731
108014
  console.log(` ${style.red(symbols.cross)} Metaproject is not initialized.`);
106732
108015
  console.log(` ${style.cyan(symbols.arrow)} Run ${style.cyan("keryx init")} first.`);
@@ -106799,7 +108082,7 @@ async function updateCommand(args = []) {
106799
108082
  nextSteps(steps);
106800
108083
  }
106801
108084
  async function previewServiceFiles(projectRoot, options) {
106802
- const metaprojectRoot = path200.join(projectRoot, ".metaproject");
108085
+ const metaprojectRoot = path201.join(projectRoot, ".metaproject");
106803
108086
  const manifestState = await readManifest5(metaprojectRoot);
106804
108087
  const manifest = manifestState.manifest;
106805
108088
  const enableTasks = moduleEnabled2(manifest, "tasks") || !options.noTasks;
@@ -106828,12 +108111,12 @@ async function previewServiceFiles(projectRoot, options) {
106828
108111
  });
106829
108112
  }
106830
108113
  async function refreshServiceFiles(projectRoot, options) {
106831
- const metaprojectRoot = path200.join(projectRoot, ".metaproject");
108114
+ const metaprojectRoot = path201.join(projectRoot, ".metaproject");
106832
108115
  const manifestState = await readManifest5(metaprojectRoot);
106833
108116
  const manifest = manifestState.manifest;
106834
108117
  const recoveredManifest = !manifestState.exists || !manifestState.valid;
106835
108118
  if (manifestState.migrated) {
106836
- await writeFile54(path200.join(metaprojectRoot, "metaproject.json"), `${JSON.stringify(manifest, null, 2)}
108119
+ await writeFile54(path201.join(metaprojectRoot, "metaproject.json"), `${JSON.stringify(manifest, null, 2)}
106837
108120
  `, "utf8");
106838
108121
  }
106839
108122
  const enableGdgraph = moduleEnabled2(manifest, "gdgraph");
@@ -106870,9 +108153,9 @@ async function refreshServiceFiles(projectRoot, options) {
106870
108153
  enableSecurity,
106871
108154
  enableSac
106872
108155
  });
106873
- await writeTextIfChanged4(path200.join(metaprojectRoot, "core", "README.md"), renderMetaprojectCoreReadme());
106874
- await writeTextIfChanged4(path200.join(metaprojectRoot, "hooks", "README.md"), renderHooksReadme());
106875
- await writeTextIfChanged4(path200.join(metaprojectRoot, "skills", "project-rules", "README.md"), renderProjectRulesSkillReadme({ sources: ruleSources }));
108156
+ await writeTextIfChanged4(path201.join(metaprojectRoot, "core", "README.md"), renderMetaprojectCoreReadme());
108157
+ await writeTextIfChanged4(path201.join(metaprojectRoot, "hooks", "README.md"), renderHooksReadme());
108158
+ await writeTextIfChanged4(path201.join(metaprojectRoot, "skills", "project-rules", "README.md"), renderProjectRulesSkillReadme({ sources: ruleSources }));
106876
108159
  await writeRoutingEntrypointPair(metaprojectRoot, {
106877
108160
  enableGdgraph,
106878
108161
  enableGdctx,
@@ -106895,7 +108178,7 @@ async function refreshServiceFiles(projectRoot, options) {
106895
108178
  }
106896
108179
  }
106897
108180
  });
106898
- await writeTextIfChanged4(path200.join(metaprojectRoot, "keryx-dashboard.html"), renderMetaprojectDashboardHtml({
108181
+ await writeTextIfChanged4(path201.join(metaprojectRoot, "keryx-dashboard.html"), renderMetaprojectDashboardHtml({
106899
108182
  enableGdgraph,
106900
108183
  enableGdctx,
106901
108184
  enableGdwiki,
@@ -106907,7 +108190,7 @@ async function refreshServiceFiles(projectRoot, options) {
106907
108190
  enableSecurity,
106908
108191
  data: dashboardData
106909
108192
  }));
106910
- await writeTextIfMissing4(path200.join(metaprojectRoot, "README.md"), renderMetaprojectReadme({
108193
+ await writeTextIfMissing4(path201.join(metaprojectRoot, "README.md"), renderMetaprojectReadme({
106911
108194
  enableGdgraph,
106912
108195
  enableGdctx,
106913
108196
  enableGdwiki,
@@ -106920,31 +108203,31 @@ async function refreshServiceFiles(projectRoot, options) {
106920
108203
  }));
106921
108204
  if (enableGdgraph) {
106922
108205
  await installGdgraphCoreScripts2(metaprojectRoot);
106923
- await writeTextIfChanged4(path200.join(metaprojectRoot, "modules", "gdgraph.md"), renderGdgraphManifest());
106924
- await writeTextIfChanged4(path200.join(metaprojectRoot, "core", "gdgraph", "README.md"), renderGdgraphCoreReadme());
106925
- await writeTextIfChanged4(path200.join(metaprojectRoot, "skills", "gdgraph", "SKILL.md"), renderGdgraphSkillReadme());
108206
+ await writeTextIfChanged4(path201.join(metaprojectRoot, "modules", "gdgraph.md"), renderGdgraphManifest());
108207
+ await writeTextIfChanged4(path201.join(metaprojectRoot, "core", "gdgraph", "README.md"), renderGdgraphCoreReadme());
108208
+ await writeTextIfChanged4(path201.join(metaprojectRoot, "skills", "gdgraph", "SKILL.md"), renderGdgraphSkillReadme());
106926
108209
  await seedAssetsLock(metaprojectRoot);
106927
108210
  if (manifest.modules?.gdgraph?.hooks?.gitPostCommit) {
106928
108211
  await installManagedHook2(projectRoot, "post-commit", "gdgraph-post-commit", renderGdgraphPostCommitHook());
106929
108212
  }
106930
108213
  }
106931
108214
  if (enableGdctx) {
106932
- await writeTextIfMissing4(path200.join(metaprojectRoot, "gdctx.config.json"), renderGdctxConfig());
106933
- await writeTextIfChanged4(path200.join(metaprojectRoot, "modules", "gdctx.md"), renderGdctxManifest());
106934
- await writeTextIfChanged4(path200.join(metaprojectRoot, "core", "gdctx", "README.md"), renderGdctxCoreReadme());
106935
- await writeTextIfChanged4(path200.join(metaprojectRoot, "skills", "gdctx", "SKILL.md"), renderGdctxSkillReadme());
108215
+ await writeTextIfMissing4(path201.join(metaprojectRoot, "gdctx.config.json"), renderGdctxConfig());
108216
+ await writeTextIfChanged4(path201.join(metaprojectRoot, "modules", "gdctx.md"), renderGdctxManifest());
108217
+ await writeTextIfChanged4(path201.join(metaprojectRoot, "core", "gdctx", "README.md"), renderGdctxCoreReadme());
108218
+ await writeTextIfChanged4(path201.join(metaprojectRoot, "skills", "gdctx", "SKILL.md"), renderGdctxSkillReadme());
106936
108219
  }
106937
108220
  if (enableGdwiki) {
106938
- await writeTextIfMissing4(path200.join(metaprojectRoot, "wiki", "templates", "page.md"), renderWikiPageTemplate());
106939
- await writeTextIfChanged4(path200.join(metaprojectRoot, "modules", "gdwiki.md"), renderGdwikiManifest());
106940
- await writeTextIfChanged4(path200.join(metaprojectRoot, "skills", "gdwiki", "SKILL.md"), renderGdwikiSkillReadme());
108221
+ await writeTextIfMissing4(path201.join(metaprojectRoot, "wiki", "templates", "page.md"), renderWikiPageTemplate());
108222
+ await writeTextIfChanged4(path201.join(metaprojectRoot, "modules", "gdwiki.md"), renderGdwikiManifest());
108223
+ await writeTextIfChanged4(path201.join(metaprojectRoot, "skills", "gdwiki", "SKILL.md"), renderGdwikiSkillReadme());
106941
108224
  if (manifest.modules?.gdgraph?.hooks?.gitPostCommit) {
106942
108225
  await installManagedHook2(projectRoot, "post-commit", "gdwiki-post-commit", renderGdwikiPostCommitHook());
106943
108226
  }
106944
108227
  }
106945
108228
  if (enableSac) {
106946
- await writeTextIfMissing4(path200.join(metaprojectRoot, "modules", "sac.md"), renderSacManifest());
106947
- await writeTextIfChanged4(path200.join(metaprojectRoot, "skills", "sac", "SKILL.md"), renderSacSkillReadme());
108229
+ await writeTextIfMissing4(path201.join(metaprojectRoot, "modules", "sac.md"), renderSacManifest());
108230
+ await writeTextIfChanged4(path201.join(metaprojectRoot, "skills", "sac", "SKILL.md"), renderSacSkillReadme());
106948
108231
  }
106949
108232
  let gdskillsWarnings = [];
106950
108233
  let gdskillsNotices = [];
@@ -106957,25 +108240,25 @@ async function refreshServiceFiles(projectRoot, options) {
106957
108240
  }
106958
108241
  }
106959
108242
  if (enableHealth) {
106960
- await writeTextIfMissing4(path200.join(metaprojectRoot, "health.config.json"), renderHealthConfig());
106961
- await writeTextIfChanged4(path200.join(metaprojectRoot, "modules", "health.md"), renderHealthManifest());
106962
- await writeTextIfChanged4(path200.join(metaprojectRoot, "core", "health", "README.md"), renderHealthCoreReadme());
106963
- await writeTextIfChanged4(path200.join(metaprojectRoot, "skills", "health", "SKILL.md"), renderHealthSkillReadme());
108243
+ await writeTextIfMissing4(path201.join(metaprojectRoot, "health.config.json"), renderHealthConfig());
108244
+ await writeTextIfChanged4(path201.join(metaprojectRoot, "modules", "health.md"), renderHealthManifest());
108245
+ await writeTextIfChanged4(path201.join(metaprojectRoot, "core", "health", "README.md"), renderHealthCoreReadme());
108246
+ await writeTextIfChanged4(path201.join(metaprojectRoot, "skills", "health", "SKILL.md"), renderHealthSkillReadme());
106964
108247
  if (manifest.modules?.health?.hooks?.gitPostCommit) {
106965
108248
  await installManagedHook2(projectRoot, "post-commit", "health-post-commit", renderHealthPostCommitHook());
106966
108249
  }
106967
108250
  }
106968
108251
  if (enableTesting) {
106969
- await writeTextIfMissing4(path200.join(metaprojectRoot, "testing.config.json"), renderTestingConfig({
108252
+ await writeTextIfMissing4(path201.join(metaprojectRoot, "testing.config.json"), renderTestingConfig({
106970
108253
  postCommitRefresh: Boolean(manifest.modules?.testing?.hooks?.gitPostCommit),
106971
108254
  prePushGate: Boolean(manifest.modules?.testing?.hooks?.prePush)
106972
108255
  }));
106973
- await writeTextIfChanged4(path200.join(metaprojectRoot, "modules", "testing.md"), renderTestingManifest());
106974
- await writeTextIfChanged4(path200.join(metaprojectRoot, "core", "testing", "README.md"), renderTestingCoreReadme());
106975
- await writeTextIfChanged4(path200.join(metaprojectRoot, "skills", "testing", "SKILL.md"), renderTestingSkillReadme());
108256
+ await writeTextIfChanged4(path201.join(metaprojectRoot, "modules", "testing.md"), renderTestingManifest());
108257
+ await writeTextIfChanged4(path201.join(metaprojectRoot, "core", "testing", "README.md"), renderTestingCoreReadme());
108258
+ await writeTextIfChanged4(path201.join(metaprojectRoot, "skills", "testing", "SKILL.md"), renderTestingSkillReadme());
106976
108259
  if (enableGdwiki) {
106977
- await writeTextIfMissing4(path200.join(metaprojectRoot, "wiki", "testing", "README.md"), renderTestingWikiReadme());
106978
- await writeTextIfMissing4(path200.join(metaprojectRoot, "wiki", "testing", "conventions.md"), renderTestingWikiConventions());
108260
+ await writeTextIfMissing4(path201.join(metaprojectRoot, "wiki", "testing", "README.md"), renderTestingWikiReadme());
108261
+ await writeTextIfMissing4(path201.join(metaprojectRoot, "wiki", "testing", "conventions.md"), renderTestingWikiConventions());
106979
108262
  }
106980
108263
  if (manifest.modules?.testing?.hooks?.gitPostCommit) {
106981
108264
  await installManagedHook2(projectRoot, "post-commit", "testing-post-commit", renderTestingPostCommitHook());
@@ -106988,24 +108271,24 @@ async function refreshServiceFiles(projectRoot, options) {
106988
108271
  await installManagedHook2(projectRoot, "post-commit", "metaproject-dashboard-post-commit", renderMetaprojectDashboardPostCommitHook());
106989
108272
  }
106990
108273
  if (enableMemory) {
106991
- await writeTextIfMissing4(path200.join(metaprojectRoot, "memory.config.json"), renderMemoryConfig());
106992
- await writeTextIfMissing4(path200.join(metaprojectRoot, "memory", "templates", "entry.md"), renderMemoryEntryTemplate());
106993
- await writeTextIfChanged4(path200.join(metaprojectRoot, "modules", "memory.md"), renderMemoryManifest());
106994
- await writeTextIfChanged4(path200.join(metaprojectRoot, "core", "memory", "README.md"), renderMemoryCoreReadme());
106995
- await writeTextIfChanged4(path200.join(metaprojectRoot, "skills", "memory", "SKILL.md"), renderMemorySkillReadme());
108274
+ await writeTextIfMissing4(path201.join(metaprojectRoot, "memory.config.json"), renderMemoryConfig());
108275
+ await writeTextIfMissing4(path201.join(metaprojectRoot, "memory", "templates", "entry.md"), renderMemoryEntryTemplate());
108276
+ await writeTextIfChanged4(path201.join(metaprojectRoot, "modules", "memory.md"), renderMemoryManifest());
108277
+ await writeTextIfChanged4(path201.join(metaprojectRoot, "core", "memory", "README.md"), renderMemoryCoreReadme());
108278
+ await writeTextIfChanged4(path201.join(metaprojectRoot, "skills", "memory", "SKILL.md"), renderMemorySkillReadme());
106996
108279
  }
106997
108280
  if (enableTasks) {
106998
- await writeTextIfChanged4(path200.join(metaprojectRoot, "flows", "README.md"), renderFlowsReadme());
106999
- await writeTextIfChanged4(path200.join(metaprojectRoot, "modules", "tasks.md"), renderTasksManifest());
107000
- await writeTextIfChanged4(path200.join(metaprojectRoot, "skills", "flow", "SKILL.md"), renderFlowSkillRouter());
107001
- await writeTextIfChanged4(path200.join(metaprojectRoot, "skills", "flow", "init.md"), renderFlowInitSkill());
107002
- await writeTextIfChanged4(path200.join(metaprojectRoot, "skills", "flow", "manage.md"), renderFlowManageSkill());
107003
- await writeTextIfChanged4(path200.join(metaprojectRoot, "skills", "flow", "complete.md"), renderFlowCompleteSkill());
108281
+ await writeTextIfChanged4(path201.join(metaprojectRoot, "flows", "README.md"), renderFlowsReadme());
108282
+ await writeTextIfChanged4(path201.join(metaprojectRoot, "modules", "tasks.md"), renderTasksManifest());
108283
+ await writeTextIfChanged4(path201.join(metaprojectRoot, "skills", "flow", "SKILL.md"), renderFlowSkillRouter());
108284
+ await writeTextIfChanged4(path201.join(metaprojectRoot, "skills", "flow", "init.md"), renderFlowInitSkill());
108285
+ await writeTextIfChanged4(path201.join(metaprojectRoot, "skills", "flow", "manage.md"), renderFlowManageSkill());
108286
+ await writeTextIfChanged4(path201.join(metaprojectRoot, "skills", "flow", "complete.md"), renderFlowCompleteSkill());
107004
108287
  }
107005
108288
  if (enableSecurity) {
107006
- await writeTextIfMissing4(path200.join(metaprojectRoot, "security.config.json"), renderSecurityConfig());
107007
- await writeTextIfChanged4(path200.join(metaprojectRoot, "modules", "security.md"), renderSecurityManifest());
107008
- await writeTextIfChanged4(path200.join(metaprojectRoot, "core", "security", "README.md"), renderSecurityCoreReadme());
108289
+ await writeTextIfMissing4(path201.join(metaprojectRoot, "security.config.json"), renderSecurityConfig());
108290
+ await writeTextIfChanged4(path201.join(metaprojectRoot, "modules", "security.md"), renderSecurityManifest());
108291
+ await writeTextIfChanged4(path201.join(metaprojectRoot, "core", "security", "README.md"), renderSecurityCoreReadme());
107009
108292
  if (manifest.modules?.security?.hooks?.prePush) {
107010
108293
  await installManagedHook2(projectRoot, "pre-push", "security-pre-push", renderSecurityPrePushHook());
107011
108294
  }
@@ -107058,13 +108341,13 @@ async function refreshServiceFiles(projectRoot, options) {
107058
108341
  };
107059
108342
  }
107060
108343
  async function buildDashboard(projectRoot = process.cwd()) {
107061
- const metaprojectRoot = path200.join(projectRoot, ".metaproject");
108344
+ const metaprojectRoot = path201.join(projectRoot, ".metaproject");
107062
108345
  if (!await pathExists(metaprojectRoot)) {
107063
108346
  throw new Error("Metaproject is not initialized. Run: keryx init");
107064
108347
  }
107065
108348
  const manifest = (await readManifest5(metaprojectRoot)).manifest;
107066
108349
  const data = await collectDashboardData(metaprojectRoot);
107067
- const dashboardPath = path200.join(metaprojectRoot, "keryx-dashboard.html");
108350
+ const dashboardPath = path201.join(metaprojectRoot, "keryx-dashboard.html");
107068
108351
  await writeTextIfChanged4(dashboardPath, renderMetaprojectDashboardHtml({
107069
108352
  enableGdgraph: moduleEnabled2(manifest, "gdgraph"),
107070
108353
  enableGdctx: moduleEnabled2(manifest, "gdctx"),
@@ -107084,7 +108367,7 @@ async function shouldInstallDashboardPostCommitHook(projectRoot, manifest) {
107084
108367
  if (Object.values(modules).some((module) => Boolean(module.hooks?.gitPostCommit))) {
107085
108368
  return true;
107086
108369
  }
107087
- const hookPath = path200.join(projectRoot, ".git", "hooks", "post-commit");
108370
+ const hookPath = path201.join(projectRoot, ".git", "hooks", "post-commit");
107088
108371
  if (!await pathExists(hookPath)) {
107089
108372
  return false;
107090
108373
  }
@@ -107104,11 +108387,11 @@ async function collectDashboardData(metaprojectRoot) {
107104
108387
  if (testing) {
107105
108388
  data.testing = testing;
107106
108389
  }
107107
- const wiki = await collectMarkdownPages(path200.join(metaprojectRoot, "wiki"), "wiki");
108390
+ const wiki = await collectMarkdownPages(path201.join(metaprojectRoot, "wiki"), "wiki");
107108
108391
  if (wiki.length > 0) {
107109
108392
  data.wiki = { pages: wiki };
107110
108393
  }
107111
- const memory = await collectMarkdownPages(path200.join(metaprojectRoot, "memory"), "memory");
108394
+ const memory = await collectMarkdownPages(path201.join(metaprojectRoot, "memory"), "memory");
107112
108395
  if (memory.length > 0) {
107113
108396
  data.memory = { entries: memory };
107114
108397
  }
@@ -107123,7 +108406,7 @@ async function collectDashboardData(metaprojectRoot) {
107123
108406
  return data;
107124
108407
  }
107125
108408
  async function collectTasksDashboardData(metaprojectRoot) {
107126
- const flowsRoot = path200.join(metaprojectRoot, "flows");
108409
+ const flowsRoot = path201.join(metaprojectRoot, "flows");
107127
108410
  if (!await pathExists(flowsRoot)) {
107128
108411
  return null;
107129
108412
  }
@@ -107135,7 +108418,7 @@ async function collectTasksDashboardData(metaprojectRoot) {
107135
108418
  }
107136
108419
  const flows = [];
107137
108420
  for (const dir of dirEntries) {
107138
- const flowPath = path200.join(flowsRoot, dir, "flow.json");
108421
+ const flowPath = path201.join(flowsRoot, dir, "flow.json");
107139
108422
  if (!await pathExists(flowPath)) {
107140
108423
  continue;
107141
108424
  }
@@ -107143,7 +108426,7 @@ async function collectTasksDashboardData(metaprojectRoot) {
107143
108426
  const flow = JSON.parse(await readFile100(flowPath, "utf8"));
107144
108427
  const tasks = Array.isArray(flow.tasks) ? flow.tasks : [];
107145
108428
  let acTotal = 0;
107146
- const acPath = path200.join(flowsRoot, dir, "acceptance-criteria.md");
108429
+ const acPath = path201.join(flowsRoot, dir, "acceptance-criteria.md");
107147
108430
  if (await pathExists(acPath)) {
107148
108431
  const acContent = await readFile100(acPath, "utf8");
107149
108432
  acTotal = (acContent.match(/^- AC\d+:/gm) ?? []).length;
@@ -107197,7 +108480,7 @@ async function collectDashboardDocs(metaprojectRoot, wiki, memory) {
107197
108480
  "data/testing/context.md"
107198
108481
  ];
107199
108482
  for (const href of staticHrefs) {
107200
- const filePath = path200.join(metaprojectRoot, ...href.split("/"));
108483
+ const filePath = path201.join(metaprojectRoot, ...href.split("/"));
107201
108484
  if (!await pathExists(filePath)) {
107202
108485
  continue;
107203
108486
  }
@@ -107214,7 +108497,7 @@ async function collectDashboardDocs(metaprojectRoot, wiki, memory) {
107214
108497
  return docs;
107215
108498
  }
107216
108499
  async function collectHealthDashboardData(metaprojectRoot) {
107217
- const reportPath = path200.join(metaprojectRoot, "data", "health", "artifacts", "latest.json");
108500
+ const reportPath = path201.join(metaprojectRoot, "data", "health", "artifacts", "latest.json");
107218
108501
  if (!await pathExists(reportPath)) {
107219
108502
  return;
107220
108503
  }
@@ -107323,8 +108606,8 @@ function metricToScope(metric) {
107323
108606
  };
107324
108607
  }
107325
108608
  async function collectGraphDashboardData(metaprojectRoot) {
107326
- const nodesPath = path200.join(metaprojectRoot, "data", "gdgraph", "storage", "nodes.jsonl");
107327
- const edgesPath = path200.join(metaprojectRoot, "data", "gdgraph", "storage", "edges.jsonl");
108609
+ const nodesPath = path201.join(metaprojectRoot, "data", "gdgraph", "storage", "nodes.jsonl");
108610
+ const edgesPath = path201.join(metaprojectRoot, "data", "gdgraph", "storage", "edges.jsonl");
107328
108611
  if (!await pathExists(nodesPath) || !await pathExists(edgesPath)) {
107329
108612
  return;
107330
108613
  }
@@ -107375,8 +108658,8 @@ async function collectGraphDashboardData(metaprojectRoot) {
107375
108658
  };
107376
108659
  }
107377
108660
  async function collectTestingDashboardData(metaprojectRoot) {
107378
- const reportPath = path200.join(metaprojectRoot, "data", "testing", "artifacts", "latest.json");
107379
- const contextPath = path200.join(metaprojectRoot, "data", "testing", "context.md");
108661
+ const reportPath = path201.join(metaprojectRoot, "data", "testing", "artifacts", "latest.json");
108662
+ const contextPath = path201.join(metaprojectRoot, "data", "testing", "context.md");
107380
108663
  if (await pathExists(reportPath)) {
107381
108664
  const report = JSON.parse(await readFile100(reportPath, "utf8"));
107382
108665
  const totalTests = numberOrUndefined(report.total);
@@ -107405,7 +108688,7 @@ async function collectMarkdownPages(root, hrefPrefix) {
107405
108688
  const files = await listMarkdownFiles(root);
107406
108689
  const pages = [];
107407
108690
  for (const filePath of files.slice(0, 40)) {
107408
- const relativePath = path200.relative(root, filePath).split(path200.sep).join("/");
108691
+ const relativePath = path201.relative(root, filePath).split(path201.sep).join("/");
107409
108692
  if (relativePath === "index.md" || relativePath.startsWith("templates/")) {
107410
108693
  continue;
107411
108694
  }
@@ -107426,7 +108709,7 @@ async function listMarkdownFiles(root) {
107426
108709
  const entries = await readdir37(root, { withFileTypes: true });
107427
108710
  const files = [];
107428
108711
  for (const entry of entries) {
107429
- const fullPath = path200.join(root, entry.name);
108712
+ const fullPath = path201.join(root, entry.name);
107430
108713
  if (entry.isDirectory()) {
107431
108714
  files.push(...await listMarkdownFiles(fullPath));
107432
108715
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
@@ -107474,7 +108757,7 @@ async function writeRecoveredManifest(metaprojectRoot, modules) {
107474
108757
  const manifest = {
107475
108758
  schemaVersion: 1,
107476
108759
  standardVersion: STANDARD_VERSION,
107477
- name: `${path200.basename(path200.dirname(metaprojectRoot))}-metaproject`,
108760
+ name: `${path201.basename(path201.dirname(metaprojectRoot))}-metaproject`,
107478
108761
  createdBy: "keryx",
107479
108762
  profiles: computeProfiles(enabledModuleKeys),
107480
108763
  paths: {
@@ -107564,11 +108847,11 @@ async function writeRecoveredManifest(metaprojectRoot, modules) {
107564
108847
  metaproject: ".metaproject/index.md"
107565
108848
  }
107566
108849
  };
107567
- await writeFile54(path200.join(metaprojectRoot, "metaproject.json"), `${JSON.stringify(manifest, null, 2)}
108850
+ await writeFile54(path201.join(metaprojectRoot, "metaproject.json"), `${JSON.stringify(manifest, null, 2)}
107568
108851
  `, "utf8");
107569
108852
  }
107570
108853
  async function enableTasksInManifest(metaprojectRoot) {
107571
- const manifestPath = path200.join(metaprojectRoot, "metaproject.json");
108854
+ const manifestPath = path201.join(metaprojectRoot, "metaproject.json");
107572
108855
  if (!await pathExists(manifestPath)) {
107573
108856
  return;
107574
108857
  }
@@ -107591,7 +108874,7 @@ async function enableTasksInManifest(metaprojectRoot) {
107591
108874
  `, "utf8");
107592
108875
  }
107593
108876
  async function updateManifestAgentEntrypoints(metaprojectRoot, ruleSources) {
107594
- const manifestPath = path200.join(metaprojectRoot, "metaproject.json");
108877
+ const manifestPath = path201.join(metaprojectRoot, "metaproject.json");
107595
108878
  if (!await pathExists(manifestPath)) {
107596
108879
  return;
107597
108880
  }
@@ -107627,72 +108910,72 @@ async function updateRuntime(projectRoot) {
107627
108910
  }
107628
108911
  }
107629
108912
  async function findRuntimeRoot(projectRoot) {
107630
- const projectRuntime = path200.join(projectRoot, ".metaproject", "runtime", "keryx");
107631
- if (await pathExists(path200.join(projectRuntime, ".git"))) {
108913
+ const projectRuntime = path201.join(projectRoot, ".metaproject", "runtime", "keryx");
108914
+ if (await pathExists(path201.join(projectRuntime, ".git"))) {
107632
108915
  return projectRuntime;
107633
108916
  }
107634
108917
  const home = process.env.HOME;
107635
108918
  if (!home) {
107636
108919
  return null;
107637
108920
  }
107638
- const globalRuntime = path200.join(home, ".keryx", "keryx");
107639
- if (await pathExists(path200.join(globalRuntime, ".git"))) {
108921
+ const globalRuntime = path201.join(home, ".keryx", "keryx");
108922
+ if (await pathExists(path201.join(globalRuntime, ".git"))) {
107640
108923
  return globalRuntime;
107641
108924
  }
107642
108925
  return null;
107643
108926
  }
107644
108927
  async function createServiceDirs(metaprojectRoot, modules) {
107645
108928
  const dirs = [
107646
- path200.join(metaprojectRoot, "core"),
107647
- path200.join(metaprojectRoot, "hooks", "post-update.d"),
107648
- path200.join(metaprojectRoot, "modules"),
107649
- path200.join(metaprojectRoot, "rules"),
107650
- path200.join(metaprojectRoot, "skills", "project-rules"),
108929
+ path201.join(metaprojectRoot, "core"),
108930
+ path201.join(metaprojectRoot, "hooks", "post-update.d"),
108931
+ path201.join(metaprojectRoot, "modules"),
108932
+ path201.join(metaprojectRoot, "rules"),
108933
+ path201.join(metaprojectRoot, "skills", "project-rules"),
107651
108934
  ...modules.enableGdgraph ? [
107652
- path200.join(metaprojectRoot, "core", "gdgraph"),
107653
- path200.join(metaprojectRoot, "skills", "gdgraph")
108935
+ path201.join(metaprojectRoot, "core", "gdgraph"),
108936
+ path201.join(metaprojectRoot, "skills", "gdgraph")
107654
108937
  ] : [],
107655
108938
  ...modules.enableGdctx ? [
107656
- path200.join(metaprojectRoot, "core", "gdctx"),
107657
- path200.join(metaprojectRoot, "skills", "gdctx")
108939
+ path201.join(metaprojectRoot, "core", "gdctx"),
108940
+ path201.join(metaprojectRoot, "skills", "gdctx")
107658
108941
  ] : [],
107659
108942
  ...modules.enableGdwiki ? [
107660
- path200.join(metaprojectRoot, "skills", "gdwiki"),
107661
- path200.join(metaprojectRoot, "wiki", "templates")
108943
+ path201.join(metaprojectRoot, "skills", "gdwiki"),
108944
+ path201.join(metaprojectRoot, "wiki", "templates")
107662
108945
  ] : [],
107663
108946
  ...modules.enableHealth ? [
107664
- path200.join(metaprojectRoot, "core", "health"),
107665
- path200.join(metaprojectRoot, "skills", "health")
108947
+ path201.join(metaprojectRoot, "core", "health"),
108948
+ path201.join(metaprojectRoot, "skills", "health")
107666
108949
  ] : [],
107667
108950
  ...modules.enableTesting ? [
107668
- path200.join(metaprojectRoot, "core", "testing"),
107669
- path200.join(metaprojectRoot, "skills", "testing")
108951
+ path201.join(metaprojectRoot, "core", "testing"),
108952
+ path201.join(metaprojectRoot, "skills", "testing")
107670
108953
  ] : [],
107671
108954
  ...modules.enableMemory ? [
107672
- path200.join(metaprojectRoot, "core", "memory"),
107673
- path200.join(metaprojectRoot, "skills", "memory"),
107674
- path200.join(metaprojectRoot, "memory", "templates")
108955
+ path201.join(metaprojectRoot, "core", "memory"),
108956
+ path201.join(metaprojectRoot, "skills", "memory"),
108957
+ path201.join(metaprojectRoot, "memory", "templates")
107675
108958
  ] : [],
107676
108959
  ...modules.enableTasks ? [
107677
- path200.join(metaprojectRoot, "flows"),
107678
- path200.join(metaprojectRoot, "skills", "flow")
108960
+ path201.join(metaprojectRoot, "flows"),
108961
+ path201.join(metaprojectRoot, "skills", "flow")
107679
108962
  ] : [],
107680
108963
  ...modules.enableSecurity ? [
107681
- path200.join(metaprojectRoot, "core", "security")
108964
+ path201.join(metaprojectRoot, "core", "security")
107682
108965
  ] : [],
107683
108966
  ...modules.enableSac ? [
107684
- path200.join(metaprojectRoot, "skills", "sac")
108967
+ path201.join(metaprojectRoot, "skills", "sac")
107685
108968
  ] : []
107686
108969
  ];
107687
108970
  await Promise.all(dirs.map((dir) => mkdir63(dir, { recursive: true })));
107688
108971
  }
107689
108972
  async function installGdgraphCoreScripts2(metaprojectRoot) {
107690
- const gdgraphCoreRoot = path200.join(metaprojectRoot, "core", "gdgraph");
108973
+ const gdgraphCoreRoot = path201.join(metaprojectRoot, "core", "gdgraph");
107691
108974
  await mkdir63(gdgraphCoreRoot, { recursive: true });
107692
108975
  for (const file of GDGRAPH_CORE_SOURCES) {
107693
- await copyFileIfChanged2(runtimeSourcePath2(`../gdgraph/${file}`), path200.join(gdgraphCoreRoot, file));
108976
+ await copyFileIfChanged2(runtimeSourcePath2(`../gdgraph/${file}`), path201.join(gdgraphCoreRoot, file));
107694
108977
  }
107695
- await writeTextIfChanged4(path200.join(gdgraphCoreRoot, "cli.ts"), renderGdgraphCoreCli());
108978
+ await writeTextIfChanged4(path201.join(gdgraphCoreRoot, "cli.ts"), renderGdgraphCoreCli());
107696
108979
  }
107697
108980
  async function installManagedHook2(projectRoot, hookName, blockId, content) {
107698
108981
  const hooksRoot = await resolveGitHooksRoot(projectRoot);
@@ -107700,7 +108983,7 @@ async function installManagedHook2(projectRoot, hookName, blockId, content) {
107700
108983
  return;
107701
108984
  }
107702
108985
  await mkdir63(hooksRoot, { recursive: true });
107703
- const hookPath = path200.join(hooksRoot, hookName);
108986
+ const hookPath = path201.join(hooksRoot, hookName);
107704
108987
  const blockStart = `# keryx:${blockId}:begin`;
107705
108988
  const blockEnd = `# keryx:${blockId}:end`;
107706
108989
  const managedBlock = `${blockStart}
@@ -107721,7 +109004,7 @@ async function removeManagedHook2(projectRoot, hookName, blockId) {
107721
109004
  if (!hooksRoot) {
107722
109005
  return;
107723
109006
  }
107724
- const hookPath = path200.join(hooksRoot, hookName);
109007
+ const hookPath = path201.join(hooksRoot, hookName);
107725
109008
  if (!await pathExists(hookPath)) {
107726
109009
  return;
107727
109010
  }
@@ -107743,7 +109026,7 @@ async function prePushHasSecurityBlock2(projectRoot) {
107743
109026
  if (!hooksRoot) {
107744
109027
  return false;
107745
109028
  }
107746
- const hookPath = path200.join(hooksRoot, "pre-push");
109029
+ const hookPath = path201.join(hooksRoot, "pre-push");
107747
109030
  if (!await pathExists(hookPath)) {
107748
109031
  return false;
107749
109032
  }
@@ -107758,7 +109041,7 @@ async function agentSettingsHasSecuritySentinel2(projectRoot) {
107758
109041
  return (await readFile100(file, "utf8")).includes(AGENT_HOOKS_SENTINEL);
107759
109042
  }
107760
109043
  async function readManifest5(metaprojectRoot) {
107761
- const manifestPath = path200.join(metaprojectRoot, "metaproject.json");
109044
+ const manifestPath = path201.join(metaprojectRoot, "metaproject.json");
107762
109045
  if (!await pathExists(manifestPath)) {
107763
109046
  return {
107764
109047
  exists: false,
@@ -107827,7 +109110,7 @@ async function inferManifestFromExistingMetaproject(metaprojectRoot) {
107827
109110
  }
107828
109111
  async function anyPathExists(root, candidates) {
107829
109112
  for (const candidate of candidates) {
107830
- if (await pathExists(path200.join(root, candidate))) {
109113
+ if (await pathExists(path201.join(root, candidate))) {
107831
109114
  return true;
107832
109115
  }
107833
109116
  }
@@ -107850,13 +109133,13 @@ function parseUpdateArgs(args) {
107850
109133
  };
107851
109134
  }
107852
109135
  async function runPostUpdateHooks(projectRoot) {
107853
- const hooksDir = path200.join(projectRoot, ".metaproject", "hooks", "post-update.d");
109136
+ const hooksDir = path201.join(projectRoot, ".metaproject", "hooks", "post-update.d");
107854
109137
  if (!await pathExists(hooksDir)) {
107855
109138
  return;
107856
109139
  }
107857
109140
  const entries = (await readdir37(hooksDir)).sort();
107858
109141
  for (const entry of entries) {
107859
- const hookPath = path200.join(hooksDir, entry);
109142
+ const hookPath = path201.join(hooksDir, entry);
107860
109143
  try {
107861
109144
  await accessExecutable(hookPath);
107862
109145
  } catch {
@@ -107897,14 +109180,14 @@ async function writeTextIfChanged4(filePath, content) {
107897
109180
  if (await pathExists(filePath) && await readFile100(filePath, "utf8") === content) {
107898
109181
  return;
107899
109182
  }
107900
- await mkdir63(path200.dirname(filePath), { recursive: true });
109183
+ await mkdir63(path201.dirname(filePath), { recursive: true });
107901
109184
  await writeFile54(filePath, content, "utf8");
107902
109185
  }
107903
109186
  async function writeTextIfMissing4(filePath, content) {
107904
109187
  if (await pathExists(filePath)) {
107905
109188
  return;
107906
109189
  }
107907
- await mkdir63(path200.dirname(filePath), { recursive: true });
109190
+ await mkdir63(path201.dirname(filePath), { recursive: true });
107908
109191
  await writeFile54(filePath, content, "utf8");
107909
109192
  }
107910
109193
  async function copyFileIfChanged2(from, to) {
@@ -107912,7 +109195,7 @@ async function copyFileIfChanged2(from, to) {
107912
109195
  if (await pathExists(to) && await readFile100(to, "utf8") === next) {
107913
109196
  return;
107914
109197
  }
107915
- await mkdir63(path200.dirname(to), { recursive: true });
109198
+ await mkdir63(path201.dirname(to), { recursive: true });
107916
109199
  await writeFile54(to, next, "utf8");
107917
109200
  }
107918
109201
  function runtimeSourcePath2(relativePath) {
@@ -107921,7 +109204,7 @@ function runtimeSourcePath2(relativePath) {
107921
109204
  return directPath;
107922
109205
  }
107923
109206
  if (relativePath.startsWith("../")) {
107924
- const packagedSourcePath = path200.join(path200.dirname(fileURLToPath8(import.meta.url)), "..", "src", relativePath.slice(3));
109207
+ const packagedSourcePath = path201.join(path201.dirname(fileURLToPath8(import.meta.url)), "..", "src", relativePath.slice(3));
107925
109208
  if (existsSync36(packagedSourcePath)) {
107926
109209
  return packagedSourcePath;
107927
109210
  }
@@ -107956,7 +109239,7 @@ function printHelp20() {
107956
109239
 
107957
109240
  // src/commands/dashboard.ts
107958
109241
  import { spawn as spawn8 } from "child_process";
107959
- import path201 from "path";
109242
+ import path202 from "path";
107960
109243
  init_args();
107961
109244
  async function dashboardCommand(args = []) {
107962
109245
  const options = parseOptions(args);
@@ -107967,7 +109250,7 @@ async function dashboardCommand(args = []) {
107967
109250
  }
107968
109251
  if (subcommand === "build") {
107969
109252
  const result = await buildDashboard();
107970
- const rel = path201.relative(process.cwd(), result.path);
109253
+ const rel = path202.relative(process.cwd(), result.path);
107971
109254
  console.log(` ${style.green(symbols.ok)} Dashboard built ${style.cyan(symbols.arrow)} ${style.cyan(rel)}`);
107972
109255
  note(`Open it: keryx dashboard open`);
107973
109256
  return;
@@ -107975,7 +109258,7 @@ async function dashboardCommand(args = []) {
107975
109258
  if (subcommand === "open") {
107976
109259
  const result = await buildDashboard();
107977
109260
  await openFile(result.path);
107978
- const rel = path201.relative(process.cwd(), result.path);
109261
+ const rel = path202.relative(process.cwd(), result.path);
107979
109262
  console.log(` ${style.green(symbols.ok)} Opened ${style.cyan(rel)}`);
107980
109263
  return;
107981
109264
  }
@@ -108019,12 +109302,12 @@ function printHelp21() {
108019
109302
  }
108020
109303
 
108021
109304
  // src/commands/agents.ts
108022
- import { readFileSync as readFileSync10 } from "fs";
109305
+ import { readFileSync as readFileSync12 } from "fs";
108023
109306
 
108024
109307
  // src/agents/bootstrap.ts
108025
109308
  import { mkdir as mkdir64, readFile as readFile101, writeFile as writeFile55 } from "fs/promises";
108026
109309
  import { homedir as homedir10 } from "os";
108027
- import path202 from "path";
109310
+ import path203 from "path";
108028
109311
  init_fs();
108029
109312
  var AGENT_BOOTSTRAP_START = "<!-- keryx:global-bootstrap -->";
108030
109313
  var AGENT_BOOTSTRAP_END = "<!-- /keryx:global-bootstrap -->";
@@ -108034,35 +109317,35 @@ var AGENT_BOOTSTRAP_RUNTIMES = [
108034
109317
  aliases: ["claude-code"],
108035
109318
  label: "Claude Code",
108036
109319
  fileName: "CLAUDE.md",
108037
- filePath: (homeRoot) => path202.join(homeRoot, ".claude", "CLAUDE.md")
109320
+ filePath: (homeRoot) => path203.join(homeRoot, ".claude", "CLAUDE.md")
108038
109321
  },
108039
109322
  {
108040
109323
  id: "opencode",
108041
109324
  aliases: ["open-code"],
108042
109325
  label: "OpenCode",
108043
109326
  fileName: "AGENTS.md",
108044
- filePath: (homeRoot) => path202.join(homeRoot, ".config", "opencode", "AGENTS.md")
109327
+ filePath: (homeRoot) => path203.join(homeRoot, ".config", "opencode", "AGENTS.md")
108045
109328
  },
108046
109329
  {
108047
109330
  id: "zcode",
108048
109331
  aliases: ["zed", "zed-code"],
108049
109332
  label: "ZCode",
108050
109333
  fileName: "AGENTS.md",
108051
- filePath: (homeRoot) => path202.join(homeRoot, ".zcode", "AGENTS.md")
109334
+ filePath: (homeRoot) => path203.join(homeRoot, ".zcode", "AGENTS.md")
108052
109335
  },
108053
109336
  {
108054
109337
  id: "codex",
108055
109338
  aliases: [],
108056
109339
  label: "Codex",
108057
109340
  fileName: "AGENTS.md",
108058
- filePath: (homeRoot) => path202.join(homeRoot, ".codex", "AGENTS.md")
109341
+ filePath: (homeRoot) => path203.join(homeRoot, ".codex", "AGENTS.md")
108059
109342
  },
108060
109343
  {
108061
109344
  id: "antigravity",
108062
109345
  aliases: ["antigravuty", "antigravity-code"],
108063
109346
  label: "Antigravity",
108064
109347
  fileName: "AGENTS.md",
108065
- filePath: (homeRoot) => path202.join(homeRoot, ".config", "antigravity", "AGENTS.md")
109348
+ filePath: (homeRoot) => path203.join(homeRoot, ".config", "antigravity", "AGENTS.md")
108066
109349
  }
108067
109350
  ];
108068
109351
  function agentBootstrapRuntimeIds() {
@@ -108110,7 +109393,7 @@ async function installAgentBootstrap(runtime, options = {}) {
108110
109393
  const dryRun = options.dryRun === true;
108111
109394
  const wrote = next !== current;
108112
109395
  if (wrote && !dryRun) {
108113
- await mkdir64(path202.dirname(filePath), { recursive: true });
109396
+ await mkdir64(path203.dirname(filePath), { recursive: true });
108114
109397
  await writeFile55(filePath, next, "utf8");
108115
109398
  }
108116
109399
  const status = dryRun ? statusFromContent(runtime, filePath, exists, next) : await agentBootstrapStatus(runtime, homeRoot);
@@ -108520,7 +109803,7 @@ function monitorCommand(args) {
108520
109803
  }
108521
109804
  let events;
108522
109805
  try {
108523
- events = parseAgentEvents(readFileSync10(source, "utf8"));
109806
+ events = parseAgentEvents(readFileSync12(source, "utf8"));
108524
109807
  } catch (error) {
108525
109808
  console.error(`Failed to read/parse agent-event source "${source}": ${error.message}`);
108526
109809
  process.exitCode = 1;
@@ -108647,7 +109930,7 @@ function printBootstrapHelp() {
108647
109930
  // src/commands/metrics.ts
108648
109931
  init_args();
108649
109932
  import { readFile as readFile102 } from "fs/promises";
108650
- import path204 from "path";
109933
+ import path205 from "path";
108651
109934
 
108652
109935
  // src/metrics/benchmark.ts
108653
109936
  var RELIABILITIES2 = new Set(["exact", "estimated", "unknown"]);
@@ -109464,7 +110747,7 @@ function buildContainmentManifest(inputs, options = {}) {
109464
110747
 
109465
110748
  // src/metrics/oracle-runner.ts
109466
110749
  import { mkdir as mkdir65, writeFile as writeFile56 } from "fs/promises";
109467
- import path203 from "path";
110750
+ import path204 from "path";
109468
110751
 
109469
110752
  // src/metrics/ir.ts
109470
110753
  function toIdSet(ids) {
@@ -109910,9 +111193,9 @@ function buildEvidenceBundle(input, options = {}) {
109910
111193
  async function persistEvidenceBundle(outDir, bundle, ladder = "metastore") {
109911
111194
  const safeTarget = bundle.target.replace(/[^A-Za-z0-9._/-]/g, "_");
109912
111195
  const safeCase = bundle.caseId.replace(/[^A-Za-z0-9._-]/g, "_");
109913
- const dir = path203.join(outDir, "bench", ladder, safeTarget, safeCase, bundle.variant, String(bundle.seed));
111196
+ const dir = path204.join(outDir, "bench", ladder, safeTarget, safeCase, bundle.variant, String(bundle.seed));
109914
111197
  await mkdir65(dir, { recursive: true });
109915
- const write = (name, value) => writeFile56(path203.join(dir, name), `${JSON.stringify(value, null, 2)}
111198
+ const write = (name, value) => writeFile56(path204.join(dir, name), `${JSON.stringify(value, null, 2)}
109916
111199
  `, "utf8");
109917
111200
  await Promise.all([
109918
111201
  write("inputs.json", bundle.inputs),
@@ -109952,7 +111235,7 @@ async function metricsCommand(args = [], projectRoot = process.cwd()) {
109952
111235
  console.log("# metrics status");
109953
111236
  console.log("");
109954
111237
  console.log(`root: ${root}`);
109955
- console.log(`enabled: ${await Bun.file(path204.join(projectRoot, ".metaproject", "metaproject.json")).exists() ? "yes" : "no"}`);
111238
+ console.log(`enabled: ${await Bun.file(path205.join(projectRoot, ".metaproject", "metaproject.json")).exists() ? "yes" : "no"}`);
109956
111239
  const latest = await readLatestPointer(root);
109957
111240
  console.log(`latest: ${latest.status}`);
109958
111241
  return;
@@ -109964,7 +111247,7 @@ async function metricsCommand(args = [], projectRoot = process.cwd()) {
109964
111247
  process.exitCode = 1;
109965
111248
  return;
109966
111249
  }
109967
- const record = JSON.parse(await readFile102(path204.resolve(projectRoot, file), "utf8"));
111250
+ const record = JSON.parse(await readFile102(path205.resolve(projectRoot, file), "utf8"));
109968
111251
  const result = validateRunRecord(record);
109969
111252
  console.log(result.valid ? "valid: yes" : "valid: no");
109970
111253
  for (const error of result.errors)
@@ -109989,7 +111272,7 @@ async function metricsCommand(args = [], projectRoot = process.cwd()) {
109989
111272
  process.exitCode = 1;
109990
111273
  return;
109991
111274
  }
109992
- const file = path204.join(metricsRoot(projectRoot), "runs", `${runId}.json`);
111275
+ const file = path205.join(metricsRoot(projectRoot), "runs", `${runId}.json`);
109993
111276
  if (!await Bun.file(file).exists()) {
109994
111277
  console.error(`Run not found: ${runId}`);
109995
111278
  process.exitCode = 1;
@@ -110006,8 +111289,8 @@ async function metricsCommand(args = [], projectRoot = process.cwd()) {
110006
111289
  process.exitCode = 1;
110007
111290
  return;
110008
111291
  }
110009
- const a = JSON.parse(await readFile102(path204.join(metricsRoot(projectRoot), "runs", `${runA}.json`), "utf8"));
110010
- const b = JSON.parse(await readFile102(path204.join(metricsRoot(projectRoot), "runs", `${runB}.json`), "utf8"));
111292
+ const a = JSON.parse(await readFile102(path205.join(metricsRoot(projectRoot), "runs", `${runA}.json`), "utf8"));
111293
+ const b = JSON.parse(await readFile102(path205.join(metricsRoot(projectRoot), "runs", `${runB}.json`), "utf8"));
110011
111294
  const comparison = compareExecutionRuns(a, b);
110012
111295
  console.log(stableJson(comparison));
110013
111296
  return;
@@ -110041,8 +111324,8 @@ async function metricsCommand(args = [], projectRoot = process.cwd()) {
110041
111324
  return;
110042
111325
  }
110043
111326
  const template = createPairedBenchmarkTemplate(taskIds);
110044
- await Bun.write(path204.resolve(projectRoot, out), stableJson(template));
110045
- console.log(`manifest: ${path204.relative(projectRoot, path204.resolve(projectRoot, out))}`);
111327
+ await Bun.write(path205.resolve(projectRoot, out), stableJson(template));
111328
+ console.log(`manifest: ${path205.relative(projectRoot, path205.resolve(projectRoot, out))}`);
110046
111329
  return;
110047
111330
  }
110048
111331
  if (subcommand === "benchmark" && args[1] === "run") {
@@ -110056,7 +111339,7 @@ async function metricsCommand(args = [], projectRoot = process.cwd()) {
110056
111339
  process.exitCode = 1;
110057
111340
  return;
110058
111341
  }
110059
- const raw = JSON.parse(await readFile102(path204.resolve(projectRoot, file), "utf8"));
111342
+ const raw = JSON.parse(await readFile102(path205.resolve(projectRoot, file), "utf8"));
110060
111343
  const input = Array.isArray(raw) ? raw : raw.runs ?? [];
110061
111344
  const result = validatePairedBenchmark(input);
110062
111345
  console.log(stableJson(result));
@@ -110068,7 +111351,7 @@ async function metricsCommand(args = [], projectRoot = process.cwd()) {
110068
111351
  process.exitCode = 1;
110069
111352
  }
110070
111353
  async function loadAffectedSets(projectRoot, file) {
110071
- const raw = JSON.parse(await readFile102(path204.resolve(projectRoot, file), "utf8"));
111354
+ const raw = JSON.parse(await readFile102(path205.resolve(projectRoot, file), "utf8"));
110072
111355
  const map = new Map;
110073
111356
  for (const entry of raw.targets ?? []) {
110074
111357
  if (typeof entry.target === "string")
@@ -110139,7 +111422,7 @@ async function runHarnessLayer(projectRoot, args, ladder) {
110139
111422
  let tasks;
110140
111423
  let model;
110141
111424
  try {
110142
- const raw = JSON.parse(await readFile102(path204.resolve(projectRoot, resultsPath), "utf8"));
111425
+ const raw = JSON.parse(await readFile102(path205.resolve(projectRoot, resultsPath), "utf8"));
110143
111426
  tasks = raw.tasks ?? [];
110144
111427
  model = raw.model;
110145
111428
  } catch (error) {
@@ -110170,7 +111453,7 @@ async function runSafetyCompletionHonestyLayer(projectRoot, args, ladder) {
110170
111453
  let cases;
110171
111454
  let model;
110172
111455
  try {
110173
- const raw = JSON.parse(await readFile102(path204.resolve(projectRoot, resultsPath), "utf8"));
111456
+ const raw = JSON.parse(await readFile102(path205.resolve(projectRoot, resultsPath), "utf8"));
110174
111457
  cases = raw.cases ?? [];
110175
111458
  model = raw.model;
110176
111459
  } catch (error) {
@@ -110195,7 +111478,7 @@ async function runSafetyFalsePremiseLayer(projectRoot, args, ladder) {
110195
111478
  let cases;
110196
111479
  let model;
110197
111480
  try {
110198
- const raw = JSON.parse(await readFile102(path204.resolve(projectRoot, resultsPath), "utf8"));
111481
+ const raw = JSON.parse(await readFile102(path205.resolve(projectRoot, resultsPath), "utf8"));
110199
111482
  cases = raw.cases ?? [];
110200
111483
  model = raw.model;
110201
111484
  } catch (error) {
@@ -110220,7 +111503,7 @@ async function runSafetyContainmentLayer(projectRoot, args, ladder, caseClass) {
110220
111503
  let cases;
110221
111504
  let model;
110222
111505
  try {
110223
- const raw = JSON.parse(await readFile102(path204.resolve(projectRoot, resultsPath), "utf8"));
111506
+ const raw = JSON.parse(await readFile102(path205.resolve(projectRoot, resultsPath), "utf8"));
110224
111507
  cases = raw.cases ?? [];
110225
111508
  model = raw.model;
110226
111509
  } catch (error) {
@@ -110282,12 +111565,12 @@ async function runGdgraphLayer(projectRoot, args, ladder) {
110282
111565
  }
110283
111566
  const manifests = buildOracleManifestsByGold(inputs, { ladder });
110284
111567
  if (outDir) {
110285
- const resolvedOut = path204.resolve(projectRoot, outDir);
111568
+ const resolvedOut = path205.resolve(projectRoot, outDir);
110286
111569
  for (const input of inputs) {
110287
111570
  for (const named of input.golds) {
110288
111571
  const bundle = buildEvidenceBundle({ target: input.target, system: input.system, gold: named.gold }, { ladder, goldReference: goldPathFor(named.kind), timestamp: new Date().toISOString() });
110289
- const dir = await persistEvidenceBundle(path204.join(resolvedOut, named.kind), bundle, ladder);
110290
- console.error(`bundle[${named.kind}]: ${path204.relative(projectRoot, dir)}`);
111572
+ const dir = await persistEvidenceBundle(path205.join(resolvedOut, named.kind), bundle, ladder);
111573
+ console.error(`bundle[${named.kind}]: ${path205.relative(projectRoot, dir)}`);
110291
111574
  }
110292
111575
  }
110293
111576
  }
@@ -110311,7 +111594,7 @@ async function runGdgraphLayer(projectRoot, args, ladder) {
110311
111594
  return allValid;
110312
111595
  }
110313
111596
  async function loadCoverageMap2(projectRoot, file) {
110314
- const raw = JSON.parse(await readFile102(path204.resolve(projectRoot, file), "utf8"));
111597
+ const raw = JSON.parse(await readFile102(path205.resolve(projectRoot, file), "utf8"));
110315
111598
  return raw.coverageMap ?? {};
110316
111599
  }
110317
111600
  async function runTestingLayer(projectRoot, args, ladder) {
@@ -110348,7 +111631,7 @@ async function runTestingLayer(projectRoot, args, ladder) {
110348
111631
  return result.valid;
110349
111632
  }
110350
111633
  async function loadMemoryGoldK(projectRoot, file) {
110351
- const raw = JSON.parse(await readFile102(path204.resolve(projectRoot, file), "utf8"));
111634
+ const raw = JSON.parse(await readFile102(path205.resolve(projectRoot, file), "utf8"));
110352
111635
  return typeof raw.k === "number" && raw.k > 0 ? raw.k : 3;
110353
111636
  }
110354
111637
  async function runMemoryLayer(projectRoot, args, ladder) {
@@ -110385,11 +111668,11 @@ async function runMemoryLayer(projectRoot, args, ladder) {
110385
111668
  return result.valid;
110386
111669
  }
110387
111670
  async function loadWikiGoldK(projectRoot, file) {
110388
- const raw = JSON.parse(await readFile102(path204.resolve(projectRoot, file), "utf8"));
111671
+ const raw = JSON.parse(await readFile102(path205.resolve(projectRoot, file), "utf8"));
110389
111672
  return typeof raw.k === "number" && raw.k > 0 ? raw.k : 5;
110390
111673
  }
110391
111674
  async function loadWikiGroundedness(projectRoot, file) {
110392
- const raw = JSON.parse(await readFile102(path204.resolve(projectRoot, file), "utf8"));
111675
+ const raw = JSON.parse(await readFile102(path205.resolve(projectRoot, file), "utf8"));
110393
111676
  const map = new Map;
110394
111677
  for (const entry of raw.targets ?? []) {
110395
111678
  if (typeof entry.target !== "string" || !Array.isArray(entry.scores) || entry.scores.length !== 3)
@@ -110445,7 +111728,7 @@ async function runWikiLayer(projectRoot, args, ladder) {
110445
111728
  return result.valid;
110446
111729
  }
110447
111730
  async function loadGdctxFacts(projectRoot, file) {
110448
- const raw = JSON.parse(await readFile102(path204.resolve(projectRoot, file), "utf8"));
111731
+ const raw = JSON.parse(await readFile102(path205.resolve(projectRoot, file), "utf8"));
110449
111732
  const inputs = [];
110450
111733
  for (const entry of raw.inputs ?? []) {
110451
111734
  if (typeof entry.input === "string") {
@@ -110483,7 +111766,7 @@ async function collect(projectRoot, args) {
110483
111766
  process.exitCode = 1;
110484
111767
  return;
110485
111768
  }
110486
- const raw = JSON.parse(await readFile102(path204.resolve(projectRoot, eventFile), "utf8"));
111769
+ const raw = JSON.parse(await readFile102(path205.resolve(projectRoot, eventFile), "utf8"));
110487
111770
  const events = Array.isArray(raw) ? raw : raw.events;
110488
111771
  const startedAt = optionValue(args, "--started-at") ?? events[0]?.timestamp_utc ?? new Date().toISOString();
110489
111772
  const finishedAt = optionValue(args, "--finished-at") ?? events.at(-1)?.timestamp_utc ?? startedAt;
@@ -110499,11 +111782,11 @@ async function collect(projectRoot, args) {
110499
111782
  parentRunId: optionValue(args, "--parent-run-id") ?? null
110500
111783
  });
110501
111784
  const result = await writeRunArtifacts(metricsRoot(projectRoot), record, { cwd: projectRoot });
110502
- console.log(`json: ${path204.relative(projectRoot, result.jsonPath)}`);
110503
- console.log(`markdown: ${path204.relative(projectRoot, result.markdownPath)}`);
111785
+ console.log(`json: ${path205.relative(projectRoot, result.jsonPath)}`);
111786
+ console.log(`markdown: ${path205.relative(projectRoot, result.markdownPath)}`);
110504
111787
  }
110505
111788
  function metricsRoot(projectRoot) {
110506
- return path204.join(projectRoot, ".metaproject", "data", "metrics");
111789
+ return path205.join(projectRoot, ".metaproject", "data", "metrics");
110507
111790
  }
110508
111791
  function printMetricsHelp() {
110509
111792
  console.log(`keryx metrics
@@ -110555,7 +111838,7 @@ async function versionCommand(args, deps = {}) {
110555
111838
  // src/commands/workspace.ts
110556
111839
  init_args();
110557
111840
  init_workspace_service();
110558
- import { randomUUID as randomUUID33 } from "crypto";
111841
+ import { randomUUID as randomUUID34 } from "crypto";
110559
111842
  import { writeFile as writeFile57 } from "fs/promises";
110560
111843
 
110561
111844
  // src/sac/fwk-explain.ts
@@ -110634,7 +111917,7 @@ async function workspaceCommand(args) {
110634
111917
  const component = optionValue(args, "--component");
110635
111918
  if (!title)
110636
111919
  throw new Error("Usage: keryx workspace create --title <title> [--component <workspace-relative-ref>]");
110637
- const workspace = await service6().create({ request: undefined, requestCorrelationId: randomUUID33(), id: newWorkspaceId(), title, ...component ? { component: { kind: "component", uri: component } } : {} });
111920
+ const workspace = await service6().create({ request: undefined, requestCorrelationId: randomUUID34(), id: newWorkspaceId(), title, ...component ? { component: { kind: "component", uri: component } } : {} });
110638
111921
  console.log(JSON.stringify(workspace, null, 2));
110639
111922
  return;
110640
111923
  }
@@ -110664,7 +111947,7 @@ async function workspaceCommand(args) {
110664
111947
  const revision = optionValue(args, "--revision");
110665
111948
  if (!workspaceId || !kind || !uri)
110666
111949
  throw new Error("Usage: keryx workspace add-resource <workspace-id> --kind <kind> --uri <workspace-relative-ref> [--revision <revision>]");
110667
- console.log(JSON.stringify(await service6().addResource({ request: undefined, requestCorrelationId: randomUUID33(), workspaceId, resource: { kind, uri, ...revision ? { revision } : {} } }), null, 2));
111950
+ console.log(JSON.stringify(await service6().addResource({ request: undefined, requestCorrelationId: randomUUID34(), workspaceId, resource: { kind, uri, ...revision ? { revision } : {} } }), null, 2));
110668
111951
  return;
110669
111952
  }
110670
111953
  if (subcommand === "archive") {
@@ -110672,7 +111955,7 @@ async function workspaceCommand(args) {
110672
111955
  const workspaceId = args[1];
110673
111956
  if (!workspaceId)
110674
111957
  throw new Error("Usage: keryx workspace archive <workspace-id>");
110675
- console.log(JSON.stringify(await service6().archive({ request: undefined, requestCorrelationId: randomUUID33(), workspaceId }), null, 2));
111958
+ console.log(JSON.stringify(await service6().archive({ request: undefined, requestCorrelationId: randomUUID34(), workspaceId }), null, 2));
110676
111959
  return;
110677
111960
  }
110678
111961
  if (subcommand === "remove-resource") {
@@ -110681,7 +111964,7 @@ async function workspaceCommand(args) {
110681
111964
  const uri = optionValue(args, "--uri");
110682
111965
  if (!workspaceId || !uri)
110683
111966
  throw new Error("Usage: keryx workspace remove-resource <workspace-id> --uri <workspace-relative-ref>");
110684
- console.log(JSON.stringify(await service6().removeResource({ request: undefined, requestCorrelationId: randomUUID33(), workspaceId, uri }), null, 2));
111967
+ console.log(JSON.stringify(await service6().removeResource({ request: undefined, requestCorrelationId: randomUUID34(), workspaceId, uri }), null, 2));
110685
111968
  return;
110686
111969
  }
110687
111970
  if (subcommand === "rename") {
@@ -110690,7 +111973,7 @@ async function workspaceCommand(args) {
110690
111973
  const title = optionValue(args, "--title");
110691
111974
  if (!workspaceId || !title)
110692
111975
  throw new Error("Usage: keryx workspace rename <workspace-id> --title <title>");
110693
- console.log(JSON.stringify(await service6().rename({ request: undefined, requestCorrelationId: randomUUID33(), workspaceId, title }), null, 2));
111976
+ console.log(JSON.stringify(await service6().rename({ request: undefined, requestCorrelationId: randomUUID34(), workspaceId, title }), null, 2));
110694
111977
  return;
110695
111978
  }
110696
111979
  if (subcommand === "overview") {
@@ -110702,7 +111985,7 @@ async function workspaceCommand(args) {
110702
111985
  const maxTokens = Number(optionValue(args, "--max-tokens") ?? "4096");
110703
111986
  if (!Number.isInteger(maxItems) || !Number.isInteger(maxTokens) || maxItems < 0 || maxTokens < 0)
110704
111987
  throw new Error("--max-items and --max-tokens must be non-negative integers");
110705
- const result = await createLocalFwkReadService(process.cwd()).overview({ workspaceId, request: undefined, requestCorrelationId: randomUUID33(), budget: { maxItems, maxTokens } });
111988
+ const result = await createLocalFwkReadService(process.cwd()).overview({ workspaceId, request: undefined, requestCorrelationId: randomUUID34(), budget: { maxItems, maxTokens } });
110706
111989
  const normalized = normalizeFwkResult(result);
110707
111990
  console.log(JSON.stringify(normalized, null, 2));
110708
111991
  if (args.includes("--explain"))
@@ -110719,7 +112002,7 @@ async function workspaceCommand(args) {
110719
112002
  const maxTokens = Number(optionValue(args, "--max-tokens") ?? "4096");
110720
112003
  if (!Number.isInteger(maxItems) || !Number.isInteger(maxTokens) || maxItems < 0 || maxTokens < 0)
110721
112004
  throw new Error("--max-items and --max-tokens must be non-negative integers");
110722
- const result = await createLocalFwkReadService(process.cwd()).read({ workspaceId, itemId, request: undefined, requestCorrelationId: randomUUID33(), budget: { maxItems, maxTokens } });
112005
+ const result = await createLocalFwkReadService(process.cwd()).read({ workspaceId, itemId, request: undefined, requestCorrelationId: randomUUID34(), budget: { maxItems, maxTokens } });
110723
112006
  const normalized = normalizeFwkResult(result);
110724
112007
  console.log(JSON.stringify(normalized, null, 2));
110725
112008
  if (args.includes("--explain"))
@@ -110741,14 +112024,14 @@ async function workspaceCommand(args) {
110741
112024
  const session = findSession(cwd, sessionRef);
110742
112025
  if (!session)
110743
112026
  throw new Error(`no session matching "${sessionRef}" in this project \u2014 use \`keryx sessions list\``);
110744
- const proposalId = `proposal-${randomUUID33().replace(/-/g, "").slice(0, 16)}`;
112027
+ const proposalId = `proposal-${randomUUID34().replace(/-/g, "").slice(0, 16)}`;
110745
112028
  const notePath = proposalNotePath(cwd, workspaceId, proposalId);
110746
112029
  const materializedNote = note === undefined ? undefined : prepareOutputForPersistence(await guardOutput({ cwd, content: note, target: "task", source: "generated", path: notePath }), note);
110747
112030
  if (materializedNote !== undefined && !materializedNote.allowed)
110748
112031
  throw new Error(materializedNote.reason);
110749
112032
  const persistedNote = materializedNote?.allowed ? materializedNote.content : undefined;
110750
112033
  const { service, wrapUpAuthority, authorizationServer } = createHarnessProposalLifecycleService(cwd, { workspaceId, ...persistedNote ? { note: persistedNote } : {} });
110751
- const requestCorrelationId = randomUUID33();
112034
+ const requestCorrelationId = randomUUID34();
110752
112035
  const actor = await authorizationServer.actorContextFor(undefined, requestCorrelationId);
110753
112036
  if (!actor)
110754
112037
  throw new Error("trusted ActorContext is required");
@@ -110788,13 +112071,13 @@ async function workspaceCommand(args) {
110788
112071
  const proposalId = args[2];
110789
112072
  const decision = optionValue(args, "--decision");
110790
112073
  const reason = optionValue(args, "--reason");
110791
- const idempotencyKey = optionValue(args, "--idempotency-key") ?? randomUUID33();
112074
+ const idempotencyKey = optionValue(args, "--idempotency-key") ?? randomUUID34();
110792
112075
  const confirmToken = optionValue(args, "--confirm-token");
110793
112076
  if (!workspaceId || !proposalId || !decision)
110794
112077
  throw new Error("Usage: keryx workspace review <workspace-id> <proposal-id> --decision <accepted|rejected|dismissed> [--reason <reason>] [--idempotency-key <key>] [--confirm-token <token>]");
110795
112078
  if (decision === "accepted" && !confirmToken)
110796
112079
  throw new Error("--decision accepted requires --confirm-token \u2014 run `keryx workspace confirm-review " + workspaceId + " " + proposalId + "` first");
110797
- const result = await createHarnessProposalLifecycleService(process.cwd(), { workspaceId }).service.review({ request: undefined, requestCorrelationId: randomUUID33(), workspaceId, proposalId, decision, idempotencyKey, interactive: true, ...reason ? { reason } : {}, ...confirmToken ? { confirmToken } : {} });
112080
+ const result = await createHarnessProposalLifecycleService(process.cwd(), { workspaceId }).service.review({ request: undefined, requestCorrelationId: randomUUID34(), workspaceId, proposalId, decision, idempotencyKey, interactive: true, ...reason ? { reason } : {}, ...confirmToken ? { confirmToken } : {} });
110798
112081
  console.log(JSON.stringify(normalizeProposalLifecycleResult(result), null, 2));
110799
112082
  return;
110800
112083
  }
@@ -110819,7 +112102,7 @@ async function workspaceCommand(args) {
110819
112102
  const artifactRef = optionValue(args, "--artifact");
110820
112103
  if (!workspaceId || !to || !artifactRef)
110821
112104
  throw new Error("Usage: keryx workspace handoff <workspace-id> --to <subject> --artifact <ref>");
110822
- const activity = await createLocalCollaborationService(process.cwd()).record({ request: undefined, requestCorrelationId: randomUUID33(), workspaceId, activity: { kind: "handoff-recorded", handoff: { to, artifactRef } } });
112105
+ const activity = await createLocalCollaborationService(process.cwd()).record({ request: undefined, requestCorrelationId: randomUUID34(), workspaceId, activity: { kind: "handoff-recorded", handoff: { to, artifactRef } } });
110823
112106
  console.log(JSON.stringify(activity, null, 2));
110824
112107
  return;
110825
112108
  }
@@ -110828,7 +112111,7 @@ async function workspaceCommand(args) {
110828
112111
  const workspaceId = args[1];
110829
112112
  if (!workspaceId)
110830
112113
  throw new Error("Usage: keryx workspace collaboration <workspace-id>");
110831
- console.log(JSON.stringify(await createLocalCollaborationService(process.cwd()).overview({ workspaceId, request: undefined, requestCorrelationId: randomUUID33() }), null, 2));
112114
+ console.log(JSON.stringify(await createLocalCollaborationService(process.cwd()).overview({ workspaceId, request: undefined, requestCorrelationId: randomUUID34() }), null, 2));
110832
112115
  return;
110833
112116
  }
110834
112117
  if (subcommand === "policy-readiness") {
@@ -110855,7 +112138,7 @@ async function workspaceCommand(args) {
110855
112138
  const workspaceId = args[1];
110856
112139
  const cwd = process.cwd();
110857
112140
  const authorizationServer = localWorkspaceAuthorizationServer();
110858
- const actor = await authorizationServer.actorContextFor(undefined, randomUUID33());
112141
+ const actor = await authorizationServer.actorContextFor(undefined, randomUUID34());
110859
112142
  if (!actor)
110860
112143
  throw new Error("trusted ActorContext is required");
110861
112144
  if (workspaceId) {
@@ -111594,13 +112877,17 @@ Commands:
111594
112877
  forgetting Read the deletion trail \u2014 was this removed, or did it never exist?
111595
112878
  `);
111596
112879
  }
112880
+ function exitCodeForError(error) {
112881
+ return error instanceof ShellFlagError ? error.exitCode : 1;
112882
+ }
111597
112883
  if (import.meta.main) {
111598
112884
  main().catch((error) => {
111599
112885
  console.error(error instanceof Error ? error.message : String(error));
111600
- process.exitCode = 1;
112886
+ process.exitCode = exitCodeForError(error);
111601
112887
  });
111602
112888
  }
111603
112889
  export {
111604
112890
  CLI_ROUTES,
112891
+ exitCodeForError,
111605
112892
  main
111606
112893
  };