@mrciphersmith/keryx 0.3.64 → 0.3.67

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 (3) hide show
  1. package/README.md +10 -2
  2. package/dist/cli.js +1237 -410
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -20107,7 +20107,7 @@ var package_default;
20107
20107
  var init_package = __esm(() => {
20108
20108
  package_default = {
20109
20109
  name: "@mrciphersmith/keryx",
20110
- version: "0.3.64",
20110
+ version: "0.3.67",
20111
20111
  description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
20112
20112
  private: false,
20113
20113
  publishConfig: {
@@ -88435,6 +88435,58 @@ var MAX_TEXT_LENGTH = 300;
88435
88435
  function emptyTally() {
88436
88436
  return { answered: 0, matched: 0 };
88437
88437
  }
88438
+ function median(values) {
88439
+ if (values.length === 0)
88440
+ return null;
88441
+ const sorted = [...values].sort((a, b) => a - b);
88442
+ const mid = Math.floor(sorted.length / 2);
88443
+ const low = sorted[mid - 1] ?? 0;
88444
+ const high = sorted[mid] ?? 0;
88445
+ return sorted.length % 2 === 1 ? high : Math.round((low + high) / 2);
88446
+ }
88447
+ function buildBackfilled(history, firstAnswer, reasons) {
88448
+ const out = {
88449
+ total: history.length,
88450
+ answered: 0,
88451
+ withRecommendation: 0,
88452
+ withoutRecommendation: 0,
88453
+ matchTally: emptyTally(),
88454
+ matchShare: null,
88455
+ deviations: []
88456
+ };
88457
+ for (const open of history) {
88458
+ if (open.recommendation === null)
88459
+ out.withoutRecommendation += 1;
88460
+ else
88461
+ out.withRecommendation += 1;
88462
+ const first = firstAnswer(open.id);
88463
+ if (first === undefined)
88464
+ continue;
88465
+ out.answered += 1;
88466
+ if (open.recommendation === null)
88467
+ continue;
88468
+ out.matchTally.answered += 1;
88469
+ if (first.choice === open.recommendation.optionId) {
88470
+ out.matchTally.matched += 1;
88471
+ continue;
88472
+ }
88473
+ const reason = reasons.get(open.id)?.reason;
88474
+ out.deviations.push({
88475
+ id: open.id,
88476
+ flow: open.flow,
88477
+ stage: open.stage,
88478
+ mode: open.mode,
88479
+ question: open.question,
88480
+ recommended: open.recommendation.optionId,
88481
+ chose: first.choice,
88482
+ changed: false,
88483
+ ...open.source !== undefined ? { source: open.source } : {},
88484
+ ...reason !== undefined ? { reason } : {}
88485
+ });
88486
+ }
88487
+ out.matchShare = out.matchTally.answered === 0 ? null : out.matchTally.matched / out.matchTally.answered;
88488
+ return out;
88489
+ }
88438
88490
  function buildReport2(records, skipped = 0) {
88439
88491
  const opens = [];
88440
88492
  const answers = new Map;
@@ -88454,8 +88506,11 @@ function buildReport2(records, skipped = 0) {
88454
88506
  reasons.set(record.id, record);
88455
88507
  }
88456
88508
  }
88509
+ const live = opens.filter((open) => open.backfilled !== true);
88510
+ const history = opens.filter((open) => open.backfilled === true);
88511
+ const firstAnswer = (id) => [...answers.get(id) ?? []].sort((a, b) => a.seq - b.seq)[0];
88457
88512
  const report = {
88458
- total: opens.length,
88513
+ total: live.length,
88459
88514
  answered: 0,
88460
88515
  unanswered: 0,
88461
88516
  withoutRecommendation: 0,
@@ -88463,9 +88518,11 @@ function buildReport2(records, skipped = 0) {
88463
88518
  byMode: { ordinary: emptyTally(), blind: emptyTally() },
88464
88519
  byStage: [],
88465
88520
  deviations: [],
88466
- irreversible: opens.filter((open) => open.irreversible).length,
88467
- blindRefused: opens.filter((open) => open.blindRefused === true).length,
88468
- inferredFlow: opens.filter((open) => open.flow !== null && open.flowSource === "inferred").length,
88521
+ irreversible: live.filter((open) => open.irreversible).length,
88522
+ blindRefused: live.filter((open) => open.blindRefused === true).length,
88523
+ inferredFlow: live.filter((open) => open.flow !== null && open.flowSource === "inferred").length,
88524
+ timing: { answered: 0, medianMs: null },
88525
+ backfilled: buildBackfilled(history, firstAnswer, reasons),
88469
88526
  skipped
88470
88527
  };
88471
88528
  const stages = new Map;
@@ -88474,7 +88531,8 @@ function buildReport2(records, skipped = 0) {
88474
88531
  stages.set(name, found);
88475
88532
  return found;
88476
88533
  };
88477
- for (const open of opens) {
88534
+ const times = [];
88535
+ for (const open of live) {
88478
88536
  if (open.blindRefused === true)
88479
88537
  stageOf(open.stage).blindRefused += 1;
88480
88538
  const list = (answers.get(open.id) ?? []).sort((a, b) => a.seq - b.seq);
@@ -88484,6 +88542,7 @@ function buildReport2(records, skipped = 0) {
88484
88542
  continue;
88485
88543
  }
88486
88544
  report.answered += 1;
88545
+ times.push(first.timeToAnswerMs);
88487
88546
  if (list.length > 1)
88488
88547
  report.changed += 1;
88489
88548
  if (open.recommendation === null) {
@@ -88513,6 +88572,7 @@ function buildReport2(records, skipped = 0) {
88513
88572
  });
88514
88573
  }
88515
88574
  }
88575
+ report.timing = { answered: times.length, medianMs: median(times) };
88516
88576
  report.byStage = [...stages.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([stage, tallies]) => ({ stage, ...tallies }));
88517
88577
  return report;
88518
88578
  }
@@ -88521,14 +88581,45 @@ function share(tally) {
88521
88581
  return "n/a";
88522
88582
  return `${Math.round(tally.matched / tally.answered * 100)}% (${tally.matched}/${tally.answered})`;
88523
88583
  }
88584
+ function backfilledShare(report) {
88585
+ if (report.matchTally.answered === 0)
88586
+ return "n/a";
88587
+ return `${Math.round(report.matchTally.matched / report.matchTally.answered * 100)}% (${report.matchTally.matched}/${report.matchTally.answered})`;
88588
+ }
88589
+ function renderBackfilled(report) {
88590
+ const lines = [
88591
+ "\u0414\u043E (\u0438\u0441\u0442\u043E\u0440\u0438\u0447\u0435\u0441\u043A\u043E\u0435, \u0434\u043E\u0437\u0430\u043F\u043E\u043B\u043D\u0435\u043D\u043E \u0437\u0430\u0434\u043D\u0438\u043C \u0447\u0438\u0441\u043B\u043E\u043C):",
88592
+ ` \u0432\u0441\u0435\u0433\u043E: ${report.total}, \u0441 \u0440\u0435\u043A\u043E\u043C\u0435\u043D\u0434\u0430\u0446\u0438\u0435\u0439: ${report.withRecommendation}, \u0441\u043E\u0432\u043F\u0430\u0434\u0435\u043D\u0438\u0435: ${backfilledShare(report)}`,
88593
+ ` \u043E\u0442\u043A\u043B\u043E\u043D\u0435\u043D\u0438\u0439: ${report.deviations.length}`
88594
+ ];
88595
+ for (const dev of report.deviations) {
88596
+ const where = [dev.source === undefined ? undefined : oneLine2(dev.source), dev.flow === null ? "project" : `flow ${oneLine2(dev.flow)}`, oneLine2(dev.stage)].filter((part) => part !== undefined);
88597
+ lines.push(` ${oneLine2(dev.id)} [${where.join(", ")}]`);
88598
+ lines.push(` ${oneLine2(dev.question)}`);
88599
+ lines.push(` \u0440\u0435\u043A\u043E\u043C\u0435\u043D\u0434\u043E\u0432\u0430\u043D\u043E ${oneLine2(dev.recommended)}, \u0432\u044B\u0431\u0440\u0430\u043D\u043E ${oneLine2(dev.chose)}`);
88600
+ lines.push(` \u043F\u0440\u0438\u0447\u0438\u043D\u0430: ${dev.reason === undefined ? "(\u043D\u0435 \u0443\u043A\u0430\u0437\u0430\u043D\u0430)" : oneLine2(dev.reason)}`);
88601
+ }
88602
+ return lines;
88603
+ }
88604
+ function seconds(ms) {
88605
+ return `${(ms / 1000).toFixed(1)}s`;
88606
+ }
88524
88607
  function renderReport(report) {
88525
88608
  const skippedLine = report.skipped > 0 ? `Skipped ${report.skipped} unreadable journal record${report.skipped === 1 ? "" : "s"}.` : undefined;
88526
- if (report.total === 0)
88609
+ if (report.total === 0 && report.backfilled.total === 0)
88527
88610
  return skippedLine === undefined ? "No decisions recorded yet." : `No decisions recorded yet.
88528
88611
  ${skippedLine}`;
88612
+ if (report.total === 0) {
88613
+ const lines = ["No live decisions recorded yet.", "", ...renderBackfilled(report.backfilled)];
88614
+ if (skippedLine !== undefined)
88615
+ lines.push("", skippedLine);
88616
+ return lines.join(`
88617
+ `);
88618
+ }
88529
88619
  const lines = [
88530
88620
  `Decisions: ${report.total} recorded, ${report.answered} answered, ${report.unanswered} not answered yet`,
88531
88621
  `Changed after the reveal: ${report.changed}. Without a recommendation: ${report.withoutRecommendation}.`,
88622
+ ...report.timing.medianMs === null ? [] : [`Median time to answer: ${seconds(report.timing.medianMs)} (${report.timing.answered} answered).`],
88532
88623
  "",
88533
88624
  "Match with the recommendation, by mode (first answer):",
88534
88625
  ` ordinary ${share(report.byMode.ordinary)}`,
@@ -88555,15 +88646,27 @@ ${skippedLine}`;
88555
88646
  if (report.inferredFlow > 0) {
88556
88647
  lines.push("", `Flow attribution inferred (the one flow in progress, not named by KERYX_FLOW or the branch): ${report.inferredFlow} decision${report.inferredFlow === 1 ? "" : "s"}.`);
88557
88648
  }
88649
+ if (report.backfilled.total > 0)
88650
+ lines.push("", ...renderBackfilled(report.backfilled));
88558
88651
  if (skippedLine !== undefined)
88559
88652
  lines.push("", skippedLine);
88560
88653
  return lines.join(`
88561
88654
  `);
88562
88655
  }
88656
+ function percent(tally) {
88657
+ if (tally.answered === 0)
88658
+ return "\u043D\u0435\u0442 \u0434\u0430\u043D\u043D\u044B\u0445";
88659
+ return `${Math.round(tally.matched / tally.answered * 100)}% (${tally.matched}/${tally.answered})`;
88660
+ }
88661
+ function renderReportLine(report) {
88662
+ const all = report.total + report.backfilled.total;
88663
+ return `\u0416\u0443\u0440\u043D\u0430\u043B \u0440\u0435\u0448\u0435\u043D\u0438\u0439: \u0432\u0441\u0435\u0433\u043E ${all} (\u0434\u043E: ${report.backfilled.total}, \u043F\u043E\u0441\u043B\u0435: ${report.total}). ` + `\u0421\u043E\u0432\u043F\u0430\u0434\u0435\u043D\u0438\u0435 \u0441 \u0440\u0435\u043A\u043E\u043C\u0435\u043D\u0434\u0430\u0446\u0438\u0435\u0439: \u0432\u0438\u0434\u0438\u043C\u0430\u044F ${percent(report.byMode.ordinary)}, \u0441\u043A\u0440\u044B\u0442\u0430\u044F ${percent(report.byMode.blind)}; ` + `\u0434\u043E: ${percent(report.backfilled.matchTally)}.`;
88664
+ }
88563
88665
  var init_report4 = () => {};
88564
88666
 
88565
88667
  // src/decisions/store.ts
88566
- import { appendFile as appendFile11, mkdir as mkdir52, readFile as readFile102 } from "fs/promises";
88668
+ import { randomUUID as randomUUID23 } from "crypto";
88669
+ import { appendFile as appendFile11, mkdir as mkdir52, open as open9, readFile as readFile102, rename as rename10, rm as rm13, stat as stat24 } from "fs/promises";
88567
88670
  import path211 from "path";
88568
88671
  async function journalRoot(cwd) {
88569
88672
  const cached = rootCache.get(cwd);
@@ -88594,6 +88697,102 @@ async function appendRecord(cwd, record) {
88594
88697
  await appendFile11(journalFile(root), `${JSON.stringify(record)}
88595
88698
  `, { encoding: "utf8", mode: 384 });
88596
88699
  }
88700
+ async function endsWithoutNewline2(file) {
88701
+ try {
88702
+ const text = await readFile102(file, "utf8");
88703
+ return text.length > 0 && !text.endsWith(`
88704
+ `);
88705
+ } catch {
88706
+ return false;
88707
+ }
88708
+ }
88709
+ async function appendRecords(cwd, records) {
88710
+ if (records.length === 0)
88711
+ return;
88712
+ const root = await journalRoot(cwd);
88713
+ await mkdir52(decisionsDir(root), { recursive: true });
88714
+ const file = journalFile(root);
88715
+ const lead = await endsWithoutNewline2(file) ? `
88716
+ ` : "";
88717
+ await appendFile11(file, lead + records.map((record) => `${JSON.stringify(record)}
88718
+ `).join(""), { encoding: "utf8", mode: 384 });
88719
+ }
88720
+ function journalLockFile(root) {
88721
+ return `${journalFile(root)}.lock`;
88722
+ }
88723
+ function pidAlive(pid) {
88724
+ try {
88725
+ process.kill(pid, 0);
88726
+ return true;
88727
+ } catch (error) {
88728
+ return error.code === "EPERM";
88729
+ }
88730
+ }
88731
+ async function lockIsStale(file, staleMs) {
88732
+ let mtimeMs;
88733
+ try {
88734
+ mtimeMs = (await stat24(file)).mtimeMs;
88735
+ } catch {
88736
+ return false;
88737
+ }
88738
+ if (Date.now() - mtimeMs > staleMs)
88739
+ return true;
88740
+ try {
88741
+ const pid = JSON.parse(await readFile102(file, "utf8")).pid;
88742
+ return typeof pid === "number" && !pidAlive(pid);
88743
+ } catch {
88744
+ return false;
88745
+ }
88746
+ }
88747
+ async function ownsLock2(file, token) {
88748
+ try {
88749
+ return JSON.parse(await readFile102(file, "utf8")).token === token;
88750
+ } catch {
88751
+ return false;
88752
+ }
88753
+ }
88754
+ async function withJournalLock(cwd, fn, options = {}) {
88755
+ const staleMs = options.staleMs ?? JOURNAL_LOCK_STALE_MS;
88756
+ const timeoutMs = options.timeoutMs ?? JOURNAL_LOCK_TIMEOUT_MS;
88757
+ const pollMs = options.pollMs ?? 25;
88758
+ const root = await journalRoot(cwd);
88759
+ await mkdir52(decisionsDir(root), { recursive: true });
88760
+ const file = journalLockFile(root);
88761
+ const token = randomUUID23();
88762
+ const deadline = Date.now() + timeoutMs;
88763
+ for (;; ) {
88764
+ try {
88765
+ const handle = await open9(file, "wx", 384);
88766
+ try {
88767
+ await handle.writeFile(JSON.stringify({ pid: process.pid, at: new Date().toISOString(), token }));
88768
+ } finally {
88769
+ await handle.close();
88770
+ }
88771
+ break;
88772
+ } catch (error) {
88773
+ if (error.code !== "EEXIST")
88774
+ throw error;
88775
+ }
88776
+ if (await lockIsStale(file, staleMs)) {
88777
+ const aside = `${file}.stale-${process.pid}-${Date.now()}`;
88778
+ try {
88779
+ await rename10(file, aside);
88780
+ await rm13(aside, { force: true });
88781
+ } catch {}
88782
+ continue;
88783
+ }
88784
+ if (Date.now() >= deadline) {
88785
+ throw new Error(`the decisions journal is locked by another run (${file}); try again, or delete that file if no import is running`);
88786
+ }
88787
+ await new Promise((resolve) => setTimeout(resolve, pollMs));
88788
+ }
88789
+ try {
88790
+ return await fn();
88791
+ } finally {
88792
+ if (await ownsLock2(file, token))
88793
+ await rm13(file, { force: true });
88794
+ }
88795
+ }
88597
88796
  function isOption(value) {
88598
88797
  if (value === null || typeof value !== "object")
88599
88798
  return false;
@@ -88669,7 +88868,7 @@ async function readJournal(cwd) {
88669
88868
  async function readRecords(cwd) {
88670
88869
  return (await readJournal(cwd)).records;
88671
88870
  }
88672
- var rootCache, isString2 = (value) => typeof value === "string";
88871
+ var rootCache, JOURNAL_LOCK_STALE_MS = 60000, JOURNAL_LOCK_TIMEOUT_MS = 15000, isString2 = (value) => typeof value === "string";
88673
88872
  var init_store6 = __esm(() => {
88674
88873
  init_git_worktrees();
88675
88874
  rootCache = new Map;
@@ -88935,6 +89134,8 @@ async function answerDecision(input) {
88935
89134
  const open = records.find((r) => r.kind === "open" && r.id === input.id);
88936
89135
  if (open === undefined)
88937
89136
  throw new Error(`no open decision with id ${input.id}`);
89137
+ if (open.backfilled === true)
89138
+ throw new Error(`decision ${open.id} is a backfilled historical record; its answer is not changed`);
88938
89139
  const other = input.other === true;
88939
89140
  const choice = other ? oneLine2(input.choice) : input.choice.trim();
88940
89141
  if (choice.length === 0)
@@ -88979,8 +89180,11 @@ async function answerDecision(input) {
88979
89180
  }
88980
89181
  async function recordReason(cwd, id, text, now = () => new Date, options = {}) {
88981
89182
  const records = await readRecords(cwd);
88982
- if (!records.some((r) => r.kind === "open" && r.id === id))
89183
+ const open = records.find((r) => r.kind === "open" && r.id === id);
89184
+ if (open === undefined)
88983
89185
  throw new Error(`no open decision with id ${id}`);
89186
+ if (open.backfilled === true)
89187
+ throw new Error(`decision ${id} is a backfilled historical record; its reason is not changed`);
88984
89188
  if (options.replace !== true && records.some((r) => r.kind === "reason" && r.id === id))
88985
89189
  return false;
88986
89190
  const reason = text === undefined ? "" : oneLine2(text);
@@ -89116,6 +89320,244 @@ var init_ask2 = __esm(() => {
89116
89320
  init_journal2();
89117
89321
  });
89118
89322
 
89323
+ // src/decisions/import.ts
89324
+ function parseEntry3(raw) {
89325
+ if (!isObject(raw))
89326
+ return "a decision must be a JSON object";
89327
+ const id = raw["id"];
89328
+ if (!isString3(id) || id.trim().length === 0)
89329
+ return "needs a non-empty string id";
89330
+ const at = raw["at"];
89331
+ if (!isString3(at) || !Number.isFinite(Date.parse(at)))
89332
+ return `${id}: "at" must be an ISO date`;
89333
+ const flow = raw["flow"] === undefined ? null : raw["flow"];
89334
+ if (flow !== null && !isString3(flow))
89335
+ return `${id}: "flow" must be a string or null`;
89336
+ const stageRaw = raw["stage"];
89337
+ if (stageRaw !== undefined && !isString3(stageRaw))
89338
+ return `${id}: "stage" must be a string`;
89339
+ const question = raw["question"];
89340
+ if (!isString3(question) || oneLine2(question).length === 0)
89341
+ return `${id}: needs a question`;
89342
+ const optionsRaw = raw["options"];
89343
+ if (!Array.isArray(optionsRaw) || optionsRaw.length < 2)
89344
+ return `${id}: needs at least two options`;
89345
+ const options = [];
89346
+ const ids = new Set;
89347
+ for (const item of optionsRaw) {
89348
+ if (!isObject(item) || !isString3(item["id"]) || item["id"].trim().length === 0)
89349
+ return `${id}: every option needs an id`;
89350
+ const optionId = item["id"];
89351
+ if (ids.has(optionId))
89352
+ return `${id}: duplicate option id: ${optionId}`;
89353
+ ids.add(optionId);
89354
+ const label = isString3(item["label"]) && item["label"].length > 0 ? item["label"] : optionId;
89355
+ options.push({ id: optionId, label, ...isString3(item["description"]) && item["description"].length > 0 ? { description: item["description"] } : {} });
89356
+ }
89357
+ let recommendation = null;
89358
+ const recRaw = raw["recommendation"];
89359
+ if (recRaw !== undefined && recRaw !== null) {
89360
+ if (!isObject(recRaw) || !isString3(recRaw["optionId"]))
89361
+ return `${id}: "recommendation" needs an optionId`;
89362
+ if (!ids.has(recRaw["optionId"]))
89363
+ return `${id}: the recommended option "${recRaw["optionId"]}" is not one of the options`;
89364
+ recommendation = { optionId: recRaw["optionId"], reason: isString3(recRaw["reason"]) ? oneLine2(recRaw["reason"]) : "" };
89365
+ }
89366
+ let answer = null;
89367
+ const answerRaw = raw["answer"];
89368
+ if (answerRaw !== undefined && answerRaw !== null) {
89369
+ if (!isObject(answerRaw) || !isString3(answerRaw["choice"]) || answerRaw["choice"].trim().length === 0)
89370
+ return `${id}: "answer" needs a choice`;
89371
+ const other = answerRaw["other"] === true;
89372
+ if (!other && !ids.has(answerRaw["choice"].trim())) {
89373
+ return `${id}: the answer "${answerRaw["choice"]}" is not one of the options (use "other": true for the human's own words)`;
89374
+ }
89375
+ answer = { choice: other ? oneLine2(answerRaw["choice"]) : answerRaw["choice"].trim(), other };
89376
+ }
89377
+ const sourceRaw = raw["source"];
89378
+ if (sourceRaw !== undefined && !isString3(sourceRaw))
89379
+ return `${id}: "source" must be a string`;
89380
+ const reasonRaw = raw["reason"];
89381
+ if (reasonRaw !== undefined && reasonRaw !== null && !isString3(reasonRaw))
89382
+ return `${id}: "reason" must be a string`;
89383
+ const reason = isString3(reasonRaw) ? oneLine2(reasonRaw) : "";
89384
+ const source = isString3(sourceRaw) ? oneLine2(sourceRaw) : "";
89385
+ return {
89386
+ id,
89387
+ at,
89388
+ flow,
89389
+ stage: isString3(stageRaw) && stageRaw.trim().length > 0 ? oneLine2(stageRaw) : DEFAULT_STAGE,
89390
+ question: oneLine2(question),
89391
+ options,
89392
+ recommendation,
89393
+ ...source.length > 0 ? { source } : {},
89394
+ answer,
89395
+ ...reason.length > 0 ? { reason } : {}
89396
+ };
89397
+ }
89398
+ function parseBackfill(text) {
89399
+ const entries = [];
89400
+ const errors = [];
89401
+ text.split(`
89402
+ `).forEach((line, index) => {
89403
+ if (line.trim().length === 0)
89404
+ return;
89405
+ let parsed;
89406
+ try {
89407
+ parsed = JSON.parse(line);
89408
+ } catch {
89409
+ errors.push({ line: index + 1, message: "not valid JSON" });
89410
+ return;
89411
+ }
89412
+ const entry = parseEntry3(parsed);
89413
+ if (typeof entry === "string")
89414
+ errors.push({ line: index + 1, message: entry });
89415
+ else
89416
+ entries.push(entry);
89417
+ });
89418
+ return { entries, errors };
89419
+ }
89420
+ function answerRecordOf(entry, at) {
89421
+ if (entry.answer === null)
89422
+ return;
89423
+ return {
89424
+ kind: "answer",
89425
+ id: entry.id,
89426
+ at,
89427
+ seq: 1,
89428
+ choice: entry.answer.choice,
89429
+ timeToAnswerMs: 0,
89430
+ changed: false,
89431
+ ...entry.answer.other ? { other: true } : {}
89432
+ };
89433
+ }
89434
+ function reasonRecordOf(entry, at) {
89435
+ return entry.reason === undefined ? undefined : { kind: "reason", id: entry.id, at, reason: entry.reason };
89436
+ }
89437
+ function backfillRecords(entry) {
89438
+ const open = {
89439
+ kind: "open",
89440
+ id: entry.id,
89441
+ at: entry.at,
89442
+ flow: entry.flow,
89443
+ stage: entry.stage,
89444
+ question: entry.question,
89445
+ options: entry.options,
89446
+ recommendation: entry.recommendation,
89447
+ mode: "ordinary",
89448
+ order: entry.options.map((option) => option.id),
89449
+ showMark: entry.recommendation !== null,
89450
+ irreversible: false,
89451
+ backfilled: true,
89452
+ ...entry.source !== undefined ? { source: entry.source } : {}
89453
+ };
89454
+ const records = [open];
89455
+ const answer = answerRecordOf(entry, entry.at);
89456
+ if (answer !== undefined)
89457
+ records.push(answer);
89458
+ const reason = reasonRecordOf(entry, entry.at);
89459
+ if (reason !== undefined)
89460
+ records.push(reason);
89461
+ return records;
89462
+ }
89463
+ async function importBackfill(cwd, text, options = {}) {
89464
+ if (options.dryRun === true)
89465
+ return importUnlocked(cwd, text, options);
89466
+ return withJournalLock(cwd, () => importUnlocked(cwd, text, options));
89467
+ }
89468
+ async function importUnlocked(cwd, text, options) {
89469
+ const { entries, errors } = parseBackfill(text);
89470
+ const records = await readRecords(cwd);
89471
+ const known = new Map;
89472
+ const answered = new Set;
89473
+ const reasoned = new Set;
89474
+ for (const record of records) {
89475
+ if (record.kind === "open")
89476
+ known.set(record.id, record);
89477
+ else if (record.kind === "answer")
89478
+ answered.add(record.id);
89479
+ else
89480
+ reasoned.add(record.id);
89481
+ }
89482
+ const result = {
89483
+ dryRun: options.dryRun === true,
89484
+ imported: 0,
89485
+ skipped: 0,
89486
+ withRecommendation: 0,
89487
+ answered: 0,
89488
+ deviations: 0,
89489
+ skippedIds: [],
89490
+ repaired: 0,
89491
+ repairedIds: [],
89492
+ malformed: errors
89493
+ };
89494
+ const toWrite = [];
89495
+ for (const entry of entries) {
89496
+ const existing = known.get(entry.id);
89497
+ if (existing !== undefined) {
89498
+ const answer = entry.answer;
89499
+ const repairable = existing.backfilled === true && answer !== null && !answered.has(entry.id) && (answer.other || existing.options.some((option) => option.id === answer.choice));
89500
+ if (!repairable || answer === null) {
89501
+ result.skipped += 1;
89502
+ result.skippedIds.push(entry.id);
89503
+ continue;
89504
+ }
89505
+ const missing = [answerRecordOf(entry, existing.at), reasoned.has(entry.id) ? undefined : reasonRecordOf(entry, existing.at)].filter((record) => record !== undefined);
89506
+ toWrite.push(...missing);
89507
+ answered.add(entry.id);
89508
+ result.repaired += 1;
89509
+ result.repairedIds.push(entry.id);
89510
+ result.answered += 1;
89511
+ if (existing.recommendation !== null && answer.choice !== existing.recommendation.optionId)
89512
+ result.deviations += 1;
89513
+ continue;
89514
+ }
89515
+ known.set(entry.id, backfillRecords(entry)[0]);
89516
+ answered.add(entry.id);
89517
+ result.imported += 1;
89518
+ if (entry.recommendation !== null)
89519
+ result.withRecommendation += 1;
89520
+ if (entry.answer !== null) {
89521
+ result.answered += 1;
89522
+ if (entry.recommendation !== null && entry.answer.choice !== entry.recommendation.optionId)
89523
+ result.deviations += 1;
89524
+ }
89525
+ toWrite.push(...backfillRecords(entry));
89526
+ }
89527
+ if (!result.dryRun)
89528
+ await appendRecords(cwd, toWrite);
89529
+ return result;
89530
+ }
89531
+ function renderImportResult(result) {
89532
+ const repaired = result.repaired > 0 ? `, repaired: ${result.repaired}` : "";
89533
+ const malformed = result.malformed.length > 0 ? `, malformed: ${result.malformed.length}` : "";
89534
+ const lines = [
89535
+ `${result.dryRun ? "Dry run, nothing written. " : ""}Imported: ${result.imported}, skipped: ${result.skipped}, with recommendation: ${result.withRecommendation}, answered: ${result.answered}, deviations: ${result.deviations}${repaired}${malformed}`
89536
+ ];
89537
+ if (result.skippedIds.length > 0) {
89538
+ const shown = result.skippedIds.slice(0, 20).map((id) => oneLine2(id, 80));
89539
+ lines.push(`Skipped (already in the journal): ${shown.join(", ")}${result.skippedIds.length > shown.length ? `, ... and ${result.skippedIds.length - shown.length} more` : ""}`);
89540
+ }
89541
+ if (result.repairedIds.length > 0) {
89542
+ const shown = result.repairedIds.slice(0, 20).map((id) => oneLine2(id, 80));
89543
+ lines.push(`Repaired (the answer was missing from the journal and was added): ${shown.join(", ")}${result.repairedIds.length > shown.length ? `, ... and ${result.repairedIds.length - shown.length} more` : ""}`);
89544
+ }
89545
+ if (result.malformed.length > 0) {
89546
+ lines.push("Malformed (skipped):");
89547
+ for (const error of result.malformed.slice(0, 20))
89548
+ lines.push(` line ${error.line}: ${oneLine2(error.message, 200)}`);
89549
+ if (result.malformed.length > 20)
89550
+ lines.push(` ... and ${result.malformed.length - 20} more`);
89551
+ }
89552
+ return lines.join(`
89553
+ `);
89554
+ }
89555
+ var isString3 = (value) => typeof value === "string", isObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
89556
+ var init_import = __esm(() => {
89557
+ init_journal2();
89558
+ init_store6();
89559
+ });
89560
+
89119
89561
  // src/decisions/context.ts
89120
89562
  async function currentBranch(cwd) {
89121
89563
  try {
@@ -89203,7 +89645,7 @@ async function latestAnsweredDecision(cwd, flow) {
89203
89645
  const answered = new Set(records.filter((r) => r.kind === "answer").map((r) => r.id));
89204
89646
  for (let i = records.length - 1;i >= 0; i -= 1) {
89205
89647
  const record = records[i];
89206
- if (record !== undefined && record.kind === "open" && answered.has(record.id) && (flow === undefined || record.flow === flow))
89648
+ if (record !== undefined && record.kind === "open" && record.backfilled !== true && answered.has(record.id) && (flow === undefined || record.flow === flow))
89207
89649
  return record.id;
89208
89650
  }
89209
89651
  return;
@@ -89261,6 +89703,8 @@ function resolveOptionId(open, typed) {
89261
89703
  }
89262
89704
  async function changeAnswer(input) {
89263
89705
  const open = await resolveOpen(input.cwd, input.id, input.lastId, input.flow, input.session, "keryx decisions answer <id> --choice <option>");
89706
+ if (open.backfilled === true)
89707
+ throw new Error(`decision ${open.id} is a backfilled historical record; its answer is not changed`);
89264
89708
  const choice = resolveOptionId(open, input.choice);
89265
89709
  const answers = (await readRecords(input.cwd)).filter((r) => r.kind === "answer" && r.id === open.id);
89266
89710
  const first = [...answers].sort((x, y) => x.seq - y.seq)[0];
@@ -89280,9 +89724,15 @@ async function loadReport(cwd) {
89280
89724
  async function reportText(cwd) {
89281
89725
  return renderReport(await loadReport(cwd));
89282
89726
  }
89727
+ async function reportLine(cwd) {
89728
+ return renderReportLine(await loadReport(cwd));
89729
+ }
89283
89730
  async function decisionCount(cwd) {
89284
89731
  return (await loadReport(cwd)).total;
89285
89732
  }
89733
+ async function backfilledCount(cwd) {
89734
+ return (await loadReport(cwd)).backfilled.total;
89735
+ }
89286
89736
  var init_service13 = __esm(() => {
89287
89737
  init_report4();
89288
89738
  init_store6();
@@ -89290,13 +89740,14 @@ var init_service13 = __esm(() => {
89290
89740
  init_blind();
89291
89741
  init_journal2();
89292
89742
  init_report4();
89743
+ init_import();
89293
89744
  init_store6();
89294
89745
  init_context2();
89295
89746
  init_followup();
89296
89747
  });
89297
89748
 
89298
89749
  // src/tui/ask-user-bridge.ts
89299
- import { randomUUID as randomUUID23 } from "crypto";
89750
+ import { randomUUID as randomUUID24 } from "crypto";
89300
89751
  function setAskUserHost(fn) {
89301
89752
  host = fn;
89302
89753
  }
@@ -89331,7 +89782,7 @@ function journaledAskUser(cwd) {
89331
89782
  var host, notice, lastDecisionId, sessionId;
89332
89783
  var init_ask_user_bridge = __esm(() => {
89333
89784
  init_service13();
89334
- sessionId = `s-${randomUUID23()}`;
89785
+ sessionId = `s-${randomUUID24()}`;
89335
89786
  });
89336
89787
 
89337
89788
  // src/harness/tool/metaproject-operations.ts
@@ -90872,7 +91323,7 @@ var init_project_tools = __esm(() => {
90872
91323
  });
90873
91324
 
90874
91325
  // src/commands/interactive-agent-tools.ts
90875
- import { randomUUID as randomUUID24 } from "crypto";
91326
+ import { randomUUID as randomUUID25 } from "crypto";
90876
91327
  function denyInteractiveTools(tools, denied) {
90877
91328
  if (denied.length === 0)
90878
91329
  return [...tools];
@@ -90890,7 +91341,7 @@ function buildInteractiveAgentTools(input) {
90890
91341
  const getSessionDir = input.getSessionDir ?? (() => {
90891
91342
  return;
90892
91343
  });
90893
- const idSeq = input.idSeq ?? (() => randomUUID24());
91344
+ const idSeq = input.idSeq ?? (() => randomUUID25());
90894
91345
  const clock = input.clock ?? (() => new Date().toISOString());
90895
91346
  const jobRegistry = input.jobRegistry;
90896
91347
  const project = buildProjectTools(input.cwd, input.metaprojectPort);
@@ -93393,9 +93844,9 @@ var init_registry3 = __esm(() => {
93393
93844
  });
93394
93845
 
93395
93846
  // src/harness/tool/builtin/spawn-subagent-tool.ts
93396
- import { createHash as createHash60, randomUUID as randomUUID25 } from "crypto";
93847
+ import { createHash as createHash60, randomUUID as randomUUID26 } from "crypto";
93397
93848
  import { execFile as execFile7 } from "child_process";
93398
- import { lstat as lstat14, mkdtemp as mkdtemp2, readFile as readFile104, realpath as realpath17, rm as rm13 } from "fs/promises";
93849
+ import { lstat as lstat14, mkdtemp as mkdtemp2, readFile as readFile104, realpath as realpath17, rm as rm14 } from "fs/promises";
93399
93850
  import { tmpdir as tmpdir6 } from "os";
93400
93851
  import path216 from "path";
93401
93852
  import { promisify as promisify5 } from "util";
@@ -93585,7 +94036,7 @@ async function verifyWorktreeCandidate(candidate, commonDir, worktreesDir) {
93585
94036
  }
93586
94037
  }
93587
94038
  function createSpawnSubagentTool(deps) {
93588
- const idSeq = deps.idSeq ?? (() => randomUUID25());
94039
+ const idSeq = deps.idSeq ?? (() => randomUUID26());
93589
94040
  const clock = deps.clock ?? (() => new Date().toISOString());
93590
94041
  const parentRunId = deps.parentRunId ?? idSeq();
93591
94042
  const parentSessionId = deps.parentSessionId ?? idSeq();
@@ -93946,7 +94397,7 @@ function createSpawnSubagentTool(deps) {
93946
94397
  provider = deps.makeProvider(runModel.provider, runModel.model, parent.baseUrl);
93947
94398
  } catch (cause) {
93948
94399
  if (ephemeralDir !== undefined) {
93949
- await rm13(ephemeralDir, { recursive: true, force: true }).catch(() => {});
94400
+ await rm14(ephemeralDir, { recursive: true, force: true }).catch(() => {});
93950
94401
  }
93951
94402
  await fireSubagentStop("Error").catch(() => {});
93952
94403
  ledger.release(spawned.reservation.reservationId, {
@@ -94072,7 +94523,7 @@ function createSpawnSubagentTool(deps) {
94072
94523
  }
94073
94524
  }
94074
94525
  } finally {
94075
- await rm13(dirToClean, { recursive: true, force: true }).catch(() => {});
94526
+ await rm14(dirToClean, { recursive: true, force: true }).catch(() => {});
94076
94527
  }
94077
94528
  });
94078
94529
  } catch (lockCause) {
@@ -94082,7 +94533,7 @@ function createSpawnSubagentTool(deps) {
94082
94533
  id: workerId,
94083
94534
  entry: { kind: "system", text: `slate cleanup lock failed (ignored): ${lockMsg}` }
94084
94535
  });
94085
- await rm13(dirToClean, { recursive: true, force: true }).catch(() => {});
94536
+ await rm14(dirToClean, { recursive: true, force: true }).catch(() => {});
94086
94537
  }
94087
94538
  };
94088
94539
  try {
@@ -95887,7 +96338,7 @@ var init_dispatch2 = __esm(() => {
95887
96338
 
95888
96339
  // src/harness/external/acp-fs.ts
95889
96340
  import { constants as constants2, realpathSync as realpathSync15, statSync as statSync12 } from "fs";
95890
- import { mkdir as mkdir53, open as open9, readFile as readFile105 } from "fs/promises";
96341
+ import { mkdir as mkdir53, open as open10, readFile as readFile105 } from "fs/promises";
95891
96342
  import path219 from "path";
95892
96343
  function refusedMethodError(method, reason) {
95893
96344
  return new AcpError(JSON_RPC_ERROR_CODES.methodNotFound, `${method} refused: ${reason}`, { reason });
@@ -95956,7 +96407,7 @@ async function writeTextFileInWorktree(worktree, target, content, requested, hoo
95956
96407
  const finalTarget = path219.join(parentReal, path219.basename(target));
95957
96408
  let handle;
95958
96409
  try {
95959
- handle = await open9(finalTarget, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_TRUNC | constants2.O_NOFOLLOW, 420);
96410
+ handle = await open10(finalTarget, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_TRUNC | constants2.O_NOFOLLOW, 420);
95960
96411
  } catch (error) {
95961
96412
  const code = error.code;
95962
96413
  if (code === "ELOOP" || code === "EMLINK") {
@@ -96121,7 +96572,7 @@ function describeOutcome(outcome) {
96121
96572
  return outcome.reason;
96122
96573
  return "an unexpected result";
96123
96574
  }
96124
- function isObject(value) {
96575
+ function isObject2(value) {
96125
96576
  return typeof value === "object" && value !== null && !Array.isArray(value);
96126
96577
  }
96127
96578
  function short(value, max = 500) {
@@ -96131,7 +96582,7 @@ function short(value, max = 500) {
96131
96582
  function readOptions(raw) {
96132
96583
  if (!Array.isArray(raw))
96133
96584
  return [];
96134
- return raw.filter((option) => isObject(option) && typeof option["optionId"] === "string" && typeof option["kind"] === "string");
96585
+ return raw.filter((option) => isObject2(option) && typeof option["optionId"] === "string" && typeof option["kind"] === "string");
96135
96586
  }
96136
96587
  async function superviseAcpRun(input, deps) {
96137
96588
  const clientCapabilities = acpClientCapabilities(input.write);
@@ -96203,7 +96654,7 @@ async function superviseAcpRun(input, deps) {
96203
96654
  let promptRequestId;
96204
96655
  let boundSessionId;
96205
96656
  const guardSession = (method, params) => {
96206
- const claimed = isObject(params) ? params["sessionId"] : undefined;
96657
+ const claimed = isObject2(params) ? params["sessionId"] : undefined;
96207
96658
  let reason;
96208
96659
  if (phase !== "prompting") {
96209
96660
  reason = phase === "handshake" ? "no prompt is in progress yet; keryx answers only during this run's turn" : "this run's turn has ended; keryx no longer answers for it";
@@ -96234,9 +96685,9 @@ async function superviseAcpRun(input, deps) {
96234
96685
  });
96235
96686
  dispatcher.onRequest(ACP_CLIENT_METHODS.sessionRequestPermission, async (params, context) => {
96236
96687
  guardSession(ACP_CLIENT_METHODS.sessionRequestPermission, params);
96237
- const p = isObject(params) ? params : {};
96688
+ const p = isObject2(params) ? params : {};
96238
96689
  const rawToolCall = p["toolCall"];
96239
- const toolCall = isObject(rawToolCall) && typeof rawToolCall["toolCallId"] === "string" ? rawToolCall : { toolCallId: "unidentified" };
96690
+ const toolCall = isObject2(rawToolCall) && typeof rawToolCall["toolCallId"] === "string" ? rawToolCall : { toolCallId: "unidentified" };
96240
96691
  const { answer, decision } = await answerAcpPermission({ requestId: context.id, toolCall, options: readOptions(p["options"]) }, permissionContext);
96241
96692
  recordDecision(decision);
96242
96693
  return { outcome: answer.outcome };
@@ -96244,7 +96695,7 @@ async function superviseAcpRun(input, deps) {
96244
96695
  dispatcher.onRequest(ACP_CLIENT_METHODS.fsReadTextFile, async (params) => {
96245
96696
  const method = ACP_CLIENT_METHODS.fsReadTextFile;
96246
96697
  guardSession(method, params);
96247
- const p = isObject(params) ? params : {};
96698
+ const p = isObject2(params) ? params : {};
96248
96699
  try {
96249
96700
  const { content } = await readTextFileInWorktree(input.cwd, p);
96250
96701
  recordFs({ method, path: String(p["path"]), outcome: "served", bytes: Buffer.byteLength(content, "utf8") });
@@ -96257,7 +96708,7 @@ async function superviseAcpRun(input, deps) {
96257
96708
  dispatcher.onRequest(ACP_CLIENT_METHODS.fsWriteTextFile, async (params, context) => {
96258
96709
  const method = ACP_CLIENT_METHODS.fsWriteTextFile;
96259
96710
  guardSession(method, params);
96260
- const p = isObject(params) ? params : {};
96711
+ const p = isObject2(params) ? params : {};
96261
96712
  const requested = typeof p["path"] === "string" ? p["path"] : String(p["path"]);
96262
96713
  if (!input.write) {
96263
96714
  const reason = "keryx did not advertise fs.writeTextFile (a read-only run)";
@@ -96294,7 +96745,7 @@ async function superviseAcpRun(input, deps) {
96294
96745
  });
96295
96746
  for (const [method, reason] of ACP_REFUSED_CLIENT_METHODS) {
96296
96747
  dispatcher.onRequest(method, (params) => {
96297
- const p = isObject(params) ? params : {};
96748
+ const p = isObject2(params) ? params : {};
96298
96749
  recordFs({
96299
96750
  method,
96300
96751
  ...typeof p["command"] === "string" ? { path: p["command"] } : {},
@@ -96308,7 +96759,7 @@ async function superviseAcpRun(input, deps) {
96308
96759
  return;
96309
96760
  });
96310
96761
  dispatcher.onNotification(ACP_CLIENT_METHODS.sessionUpdate, (params) => {
96311
- if (!isObject(params) || !isObject(params["update"]))
96762
+ if (!isObject2(params) || !isObject2(params["update"]))
96312
96763
  return;
96313
96764
  if (boundSessionId === undefined || params["sessionId"] !== boundSessionId) {
96314
96765
  stderr.push(`[keryx] ignored a session/update for session ${JSON.stringify(params["sessionId"] ?? null)}`);
@@ -96368,7 +96819,7 @@ async function superviseAcpRun(input, deps) {
96368
96819
  usage = { used: update.used, size: update.size };
96369
96820
  }
96370
96821
  const reported = update.cost;
96371
- if (isObject(reported) && typeof reported["amount"] === "number" && typeof reported["currency"] === "string") {
96822
+ if (isObject2(reported) && typeof reported["amount"] === "number" && typeof reported["currency"] === "string") {
96372
96823
  cost = { amount: reported["amount"], currency: reported["currency"] };
96373
96824
  }
96374
96825
  emit(cost !== undefined && cost.currency === "USD" ? { kind: "usage", costUnits: cost.amount } : { kind: "usage" });
@@ -96385,7 +96836,7 @@ async function superviseAcpRun(input, deps) {
96385
96836
  if (phase === "prompting" && promptRequestId !== undefined && line.includes(promptRequestId)) {
96386
96837
  try {
96387
96838
  const value = JSON.parse(line);
96388
- if (isObject(value) && !("method" in value) && value["id"] === promptRequestId)
96839
+ if (isObject2(value) && !("method" in value) && value["id"] === promptRequestId)
96389
96840
  phase = "ended";
96390
96841
  } catch {}
96391
96842
  }
@@ -96465,16 +96916,16 @@ async function superviseAcpRun(input, deps) {
96465
96916
  failure = `the agent did not answer initialize within ${input.timeoutMs}ms`;
96466
96917
  return await finish();
96467
96918
  }
96468
- if (init.kind !== "result" || !isObject(init.result)) {
96919
+ if (init.kind !== "result" || !isObject2(init.result)) {
96469
96920
  failure = `the agent did not answer initialize: ${describeOutcome(init)}`;
96470
96921
  return await finish();
96471
96922
  }
96472
96923
  const initResult = init.result;
96473
96924
  const version = initResult["protocolVersion"];
96474
96925
  protocolVersion = typeof version === "number" ? version : undefined;
96475
- if (isObject(initResult["agentInfo"]))
96926
+ if (isObject2(initResult["agentInfo"]))
96476
96927
  agentInfo = initResult["agentInfo"];
96477
- if (isObject(initResult["agentCapabilities"]))
96928
+ if (isObject2(initResult["agentCapabilities"]))
96478
96929
  agentCapabilities = initResult["agentCapabilities"];
96479
96930
  if (typeof version !== "number" || !ACP_SUPPORTED_PROTOCOL_VERSIONS.includes(version)) {
96480
96931
  failure = `unsupported ACP protocol version: the agent answered initialize with protocolVersion ${JSON.stringify(version)}; ` + `keryx speaks ${ACP_SUPPORTED_PROTOCOL_VERSIONS.join(", ")}`;
@@ -96486,7 +96937,7 @@ async function superviseAcpRun(input, deps) {
96486
96937
  failure = `the agent did not answer session/new within ${input.timeoutMs}ms`;
96487
96938
  return await finish();
96488
96939
  }
96489
- if (created.kind !== "result" || !isObject(created.result) || typeof created.result["sessionId"] !== "string") {
96940
+ if (created.kind !== "result" || !isObject2(created.result) || typeof created.result["sessionId"] !== "string") {
96490
96941
  failure = `the agent refused session/new: ${created.kind === "result" ? "no sessionId in the answer" : describeOutcome(created)}`;
96491
96942
  return await finish();
96492
96943
  }
@@ -96526,12 +96977,12 @@ ${resource.text}` });
96526
96977
  const grace = afterMs(graceMs);
96527
96978
  const late = await Promise.race([turn, grace.promise]);
96528
96979
  grace.cancel();
96529
- if (late !== TIMEOUT && late.kind === "result" && isObject(late.result) && typeof late.result["stopReason"] === "string") {
96980
+ if (late !== TIMEOUT && late.kind === "result" && isObject2(late.result) && typeof late.result["stopReason"] === "string") {
96530
96981
  stopReason = late.result["stopReason"];
96531
96982
  }
96532
96983
  return await finish();
96533
96984
  }
96534
- if (answered.kind !== "result" || !isObject(answered.result)) {
96985
+ if (answered.kind !== "result" || !isObject2(answered.result)) {
96535
96986
  failure = `session/prompt did not complete: ${describeOutcome(answered)}`;
96536
96987
  return await finish();
96537
96988
  }
@@ -96610,7 +97061,7 @@ var init_acp_client = __esm(() => {
96610
97061
  // src/harness/external/acp-run.ts
96611
97062
  import { execFile as execFile9 } from "child_process";
96612
97063
  import { existsSync as existsSync38, readFileSync as readFileSync20, writeFileSync as writeFileSync4 } from "fs";
96613
- import { mkdtemp as mkdtemp3, rm as rm14 } from "fs/promises";
97064
+ import { mkdtemp as mkdtemp3, rm as rm15 } from "fs/promises";
96614
97065
  import { tmpdir as tmpdir7 } from "os";
96615
97066
  import path220 from "path";
96616
97067
  import { promisify as promisify7 } from "util";
@@ -96687,7 +97138,7 @@ async function captureWorktreePatch(worktreePath) {
96687
97138
  } catch {
96688
97139
  return;
96689
97140
  } finally {
96690
- await rm14(dir, { recursive: true, force: true }).catch(() => {
97141
+ await rm15(dir, { recursive: true, force: true }).catch(() => {
96691
97142
  return;
96692
97143
  });
96693
97144
  }
@@ -98048,7 +98499,7 @@ var init_codec2 = __esm(() => {
98048
98499
  });
98049
98500
 
98050
98501
  // src/harness/external/runtime.ts
98051
- import { mkdtemp as mkdtemp4, rm as rm15, writeFile as writeFile34 } from "fs/promises";
98502
+ import { mkdtemp as mkdtemp4, rm as rm16, writeFile as writeFile34 } from "fs/promises";
98052
98503
  import { tmpdir as tmpdir8 } from "os";
98053
98504
  import path221 from "path";
98054
98505
  function refuse2(status, output) {
@@ -98065,7 +98516,7 @@ async function prepareResultSchema() {
98065
98516
  return { ok: true, schema, schemaText, schemaPath, schemaDir };
98066
98517
  } catch (error) {
98067
98518
  if (schemaDir !== undefined) {
98068
- await rm15(schemaDir, { recursive: true, force: true }).catch(() => {
98519
+ await rm16(schemaDir, { recursive: true, force: true }).catch(() => {
98069
98520
  return;
98070
98521
  });
98071
98522
  }
@@ -98172,7 +98623,7 @@ async function runExternalChild(input, deps) {
98172
98623
  try {
98173
98624
  await writeFile34(resultSchemaPathForCodec, JSON.stringify(toStrictOutputSchema(bundledResultSchema)), "utf8");
98174
98625
  } catch (error) {
98175
- await rm15(schemaDir, { recursive: true, force: true }).catch(() => {
98626
+ await rm16(schemaDir, { recursive: true, force: true }).catch(() => {
98176
98627
  return;
98177
98628
  });
98178
98629
  const message = error instanceof Error ? error.message : String(error);
@@ -98267,7 +98718,7 @@ async function runExternalChild(input, deps) {
98267
98718
  });
98268
98719
  }
98269
98720
  } finally {
98270
- await rm15(schemaDir, { recursive: true, force: true }).catch(() => {
98721
+ await rm16(schemaDir, { recursive: true, force: true }).catch(() => {
98271
98722
  return;
98272
98723
  });
98273
98724
  }
@@ -98434,7 +98885,7 @@ var init_runtime2 = __esm(() => {
98434
98885
  });
98435
98886
 
98436
98887
  // src/harness/run-external-factory.ts
98437
- import { randomUUID as randomUUID26 } from "crypto";
98888
+ import { randomUUID as randomUUID27 } from "crypto";
98438
98889
  import { execFile as execFile10 } from "child_process";
98439
98890
  import { mkdir as mkdir54 } from "fs/promises";
98440
98891
  import { tmpdir as tmpdir9 } from "os";
@@ -98495,7 +98946,7 @@ async function createRunExternal(options) {
98495
98946
  return;
98496
98947
  }
98497
98948
  const config = gate.config;
98498
- const idSeq = options.idSeq ?? (() => randomUUID26());
98949
+ const idSeq = options.idSeq ?? (() => randomUUID27());
98499
98950
  const worktreesDir = options.worktreesDir ?? path222.join(tmpdir9(), "keryx-external-worktrees");
98500
98951
  const spawn = options.spawn ?? createBunSpawnPort();
98501
98952
  const worktree = options.worktree ?? ensuringParentDir(createGitWorktreePort({ repoRoot: options.cwd, worktreesDir }), worktreesDir);
@@ -99434,7 +99885,7 @@ var init_client2 = __esm(() => {
99434
99885
 
99435
99886
  // src/lib/file-lock.ts
99436
99887
  import { closeSync as closeSync3, mkdirSync as mkdirSync7, openSync as openSync3, readFileSync as readFileSync21, rmSync as rmSync3, statSync as statSync13, writeFileSync as writeFileSync6 } from "fs";
99437
- import { randomUUID as randomUUID27 } from "crypto";
99888
+ import { randomUUID as randomUUID28 } from "crypto";
99438
99889
  import path225 from "path";
99439
99890
  function sleepSync(ms) {
99440
99891
  const bunSleep = globalThis.Bun?.sleepSync;
@@ -99451,7 +99902,7 @@ function withFileLock2(lockPath, fn, options = {}) {
99451
99902
  } catch {
99452
99903
  return null;
99453
99904
  }
99454
- const nonce = randomUUID27();
99905
+ const nonce = randomUUID28();
99455
99906
  const started = Date.now();
99456
99907
  const deadline = started + LOCK_TIMEOUT_MS;
99457
99908
  let handle = null;
@@ -101709,7 +102160,7 @@ import {
101709
102160
  writeFileSync as writeFileSync7
101710
102161
  } from "fs";
101711
102162
  import path232 from "path";
101712
- import { randomUUID as randomUUID28 } from "crypto";
102163
+ import { randomUUID as randomUUID29 } from "crypto";
101713
102164
  function projectRegistryPath(dir) {
101714
102165
  return path232.join(keryxConfigDir(dir), "projects.json");
101715
102166
  }
@@ -101824,7 +102275,7 @@ function quarantineDamagedRegistry(dir, onWarn) {
101824
102275
  }
101825
102276
  function saveProjectRegistry(registry, dir, onWarn) {
101826
102277
  const file = projectRegistryPath(dir);
101827
- const temp = `${file}.${randomUUID28()}.tmp`;
102278
+ const temp = `${file}.${randomUUID29()}.tmp`;
101828
102279
  try {
101829
102280
  ensureKeryxConfigDir(dir);
101830
102281
  const sorted = {
@@ -101880,7 +102331,7 @@ function registerProject(projectPath, options = {}) {
101880
102331
  return { ok: true, entry: existing, created: false };
101881
102332
  }
101882
102333
  const entry = {
101883
- projectId: randomUUID28(),
102334
+ projectId: randomUUID29(),
101884
102335
  path: absolute,
101885
102336
  displayName: options.displayName ?? path232.basename(absolute),
101886
102337
  state: "active",
@@ -109019,7 +109470,7 @@ function formatHygieneTags(hygiene) {
109019
109470
  }
109020
109471
 
109021
109472
  // src/sac/lifecycle-flag.ts
109022
- import { randomUUID as randomUUID29 } from "crypto";
109473
+ import { randomUUID as randomUUID30 } from "crypto";
109023
109474
  function normalize3(raw) {
109024
109475
  return raw.replace(/^\.\//, "");
109025
109476
  }
@@ -109038,7 +109489,7 @@ async function computeLifecycleFlags(cwd, now = () => new Date) {
109038
109489
  authorizationServer: localWorkspaceAuthorizationServer(),
109039
109490
  strictGuard: { mode: "strict", availability: "available", decision: "pass", policyRevision: "local-offline-v1" }
109040
109491
  });
109041
- const workspaces = await workspaceService.list({ request: undefined, requestCorrelationId: randomUUID29(), includeArchived: true });
109492
+ const workspaces = await workspaceService.list({ request: undefined, requestCorrelationId: randomUUID30(), includeArchived: true });
109042
109493
  for (const workspace of workspaces) {
109043
109494
  const component = workspace.resources.find((r) => r.kind === "component")?.uri;
109044
109495
  if (component !== undefined && !isStillPresent(component, valid)) {
@@ -109067,8 +109518,8 @@ var init_lifecycle_flag = __esm(() => {
109067
109518
  });
109068
109519
 
109069
109520
  // src/sac/catch-up.ts
109070
- import { randomUUID as randomUUID30 } from "crypto";
109071
- import { readdir as readdir36, stat as stat24 } from "fs/promises";
109521
+ import { randomUUID as randomUUID31 } from "crypto";
109522
+ import { readdir as readdir36, stat as stat25 } from "fs/promises";
109072
109523
  import path243 from "path";
109073
109524
  async function buildCatchUp(input) {
109074
109525
  const [proposals, sessionCategories, lifecycleFlagsAll, unreviewedPathsAll] = await Promise.all([
@@ -109083,7 +109534,7 @@ async function buildCatchUp(input) {
109083
109534
  }
109084
109535
  async function collectProposals(cwd, workspaceId) {
109085
109536
  const authorizationServer = localWorkspaceAuthorizationServer();
109086
- const actor = await authorizationServer.actorContextFor(undefined, randomUUID30());
109537
+ const actor = await authorizationServer.actorContextFor(undefined, randomUUID31());
109087
109538
  if (!actor)
109088
109539
  throw new Error("trusted ActorContext is required for catch-up");
109089
109540
  const proposalService = createLocalProposalLifecycleService(cwd);
@@ -109286,7 +109737,7 @@ async function collectReceiptTargets(cwd, owner) {
109286
109737
  async function attributeToSession(absolutePath, sessionsNewestFirst) {
109287
109738
  let mtimeMs;
109288
109739
  try {
109289
- mtimeMs = (await stat24(absolutePath)).mtimeMs;
109740
+ mtimeMs = (await stat25(absolutePath)).mtimeMs;
109290
109741
  } catch {
109291
109742
  return;
109292
109743
  }
@@ -109572,7 +110023,7 @@ var init_session = __esm(() => {
109572
110023
  });
109573
110024
 
109574
110025
  // src/tui/inspector-sources.ts
109575
- import { randomUUID as randomUUID31 } from "crypto";
110026
+ import { randomUUID as randomUUID32 } from "crypto";
109576
110027
  function workspaceFromManifest(manifest) {
109577
110028
  return {
109578
110029
  id: manifest.id,
@@ -109651,7 +110102,7 @@ async function loadInspectorWorkspaces(cwd) {
109651
110102
  authorizationServer: localWorkspaceAuthorizationServer(),
109652
110103
  strictGuard: localSacGuard()
109653
110104
  });
109654
- const listed = await service.list({ request: undefined, requestCorrelationId: randomUUID31() });
110105
+ const listed = await service.list({ request: undefined, requestCorrelationId: randomUUID32() });
109655
110106
  return listed.map(workspaceFromManifest);
109656
110107
  } catch {
109657
110108
  return [];
@@ -109664,7 +110115,7 @@ async function loadInspectorWorkspace(cwd, workspaceId) {
109664
110115
  authorizationServer: localWorkspaceAuthorizationServer(),
109665
110116
  strictGuard: localSacGuard()
109666
110117
  });
109667
- const manifest = await service.show({ request: undefined, requestCorrelationId: randomUUID31(), workspaceId });
110118
+ const manifest = await service.show({ request: undefined, requestCorrelationId: randomUUID32(), workspaceId });
109668
110119
  return workspaceFromManifest(manifest);
109669
110120
  } catch {
109670
110121
  return;
@@ -111983,7 +112434,7 @@ var init_repair = __esm(() => {
111983
112434
  });
111984
112435
 
111985
112436
  // src/review/managed.ts
111986
- import { mkdir as mkdir55, readFile as readFile117, readdir as readdir38, realpath as realpath18, rm as rm16, stat as stat25 } from "fs/promises";
112437
+ import { mkdir as mkdir55, readFile as readFile117, readdir as readdir38, realpath as realpath18, rm as rm17, stat as stat26 } from "fs/promises";
111987
112438
  import path246 from "path";
111988
112439
  function reviewsRoot(cwd) {
111989
112440
  return path246.join(cwd, ".metaproject", "reviews");
@@ -112210,7 +112661,7 @@ async function persistScreenRadius(packageDir, resolved) {
112210
112661
  `);
112211
112662
  }
112212
112663
  if (resolved.source === "handoff" && resolved.file !== undefined) {
112213
- await rm16(resolved.file, { force: true });
112664
+ await rm17(resolved.file, { force: true });
112214
112665
  }
112215
112666
  }
112216
112667
  function concurrencyPlan(input, coverage) {
@@ -112489,7 +112940,7 @@ function readFromWorkingTree(input) {
112489
112940
  return null;
112490
112941
  }
112491
112942
  try {
112492
- const info = await stat25(real);
112943
+ const info = await stat26(real);
112493
112944
  if (!info.isFile() || info.size > MAX_LOCATE_FILE_BYTES) {
112494
112945
  return null;
112495
112946
  }
@@ -114338,6 +114789,9 @@ function channelsRoutePath(route) {
114338
114789
  function isSessionId(value) {
114339
114790
  return typeof value === "string" && SESSION_ID_PATTERN.test(value);
114340
114791
  }
114792
+ function isApprovalId(value) {
114793
+ return typeof value === "string" && APPROVAL_ID_PATTERN.test(value);
114794
+ }
114341
114795
  function approvalCallbackData(approvalId, decision) {
114342
114796
  return `ap:${approvalId}:${decision}`;
114343
114797
  }
@@ -114410,7 +114864,7 @@ class SseParser {
114410
114864
  return frames;
114411
114865
  }
114412
114866
  }
114413
- var REMOTE_API_PREFIX = "/v1/remote", REMOTE_SCHEMA_VERSION = "1.0.0", REMOTE_ROUTE_METHODS, CHANNELS_ROUTE_METHODS, MAX_REMOTE_BODY_BYTES, MAX_PROJECT_CHARS = 256, MAX_APPROVAL_PROMPT_CHARS = 3000, MAX_KEYBOARD_ROWS = 8, MAX_KEYBOARD_BUTTONS_PER_ROW = 8, MAX_BUTTON_TEXT_CHARS = 64, MAX_CALLBACK_DATA_BYTES = 64, DEFAULT_APPROVAL_TIMEOUT_MS, MAX_APPROVAL_TIMEOUT_MS, MIN_APPROVAL_TIMEOUT_MS = 100, DEFAULT_KEEPALIVE_MS = 15000, DEFAULT_ACK_TIMEOUT_MS = 30000, SESSION_ID_PATTERN, APPROVAL_CALLBACK_PATTERN, RESERVED_CALLBACK_PREFIX = "ap:", CHOICE_CALLBACK_PATTERN, RESERVED_CHOICE_PREFIX = "pk:", MAX_PROMPT_BUTTONS = 24, MESSAGE_STATES, SSE_KEEPALIVE_FRAME = `: keepalive
114867
+ var REMOTE_API_PREFIX = "/v1/remote", REMOTE_SCHEMA_VERSION = "1.0.0", REMOTE_ROUTE_METHODS, CHANNELS_ROUTE_METHODS, MAX_REMOTE_BODY_BYTES, MAX_PROJECT_CHARS = 256, MAX_APPROVAL_PROMPT_CHARS = 3000, MAX_KEYBOARD_ROWS = 8, MAX_KEYBOARD_BUTTONS_PER_ROW = 8, MAX_BUTTON_TEXT_CHARS = 64, MAX_CALLBACK_DATA_BYTES = 64, DEFAULT_APPROVAL_TIMEOUT_MS, MAX_APPROVAL_TIMEOUT_MS, MIN_APPROVAL_TIMEOUT_MS = 100, DEFAULT_APPROVAL_ACK_MS = 5000, DEFAULT_KEEPALIVE_MS = 15000, DEFAULT_ACK_TIMEOUT_MS = 30000, SESSION_ID_PATTERN, APPROVAL_ID_PATTERN, APPROVAL_CALLBACK_PATTERN, RESERVED_CALLBACK_PREFIX = "ap:", CHOICE_CALLBACK_PATTERN, RESERVED_CHOICE_PREFIX = "pk:", MAX_PROMPT_BUTTONS = 24, MESSAGE_STATES, SSE_KEEPALIVE_FRAME = `: keepalive
114414
114868
 
114415
114869
  `;
114416
114870
  var init_protocol2 = __esm(() => {
@@ -114423,6 +114877,7 @@ var init_protocol2 = __esm(() => {
114423
114877
  prompt: "POST",
114424
114878
  state: "POST",
114425
114879
  ack: "POST",
114880
+ "approval-ack": "POST",
114426
114881
  stream: "GET"
114427
114882
  };
114428
114883
  CHANNELS_ROUTE_METHODS = {
@@ -114438,6 +114893,7 @@ var init_protocol2 = __esm(() => {
114438
114893
  DEFAULT_APPROVAL_TIMEOUT_MS = 5 * 60000;
114439
114894
  MAX_APPROVAL_TIMEOUT_MS = 60 * 60000;
114440
114895
  SESSION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
114896
+ APPROVAL_ID_PATTERN = /^ap[0-9a-f]{12}$/;
114441
114897
  APPROVAL_CALLBACK_PATTERN = /^ap:(ap[0-9a-f]{12}):(allow|deny)$/;
114442
114898
  CHOICE_CALLBACK_PATTERN = /^pk:(pk[0-9a-f]{12}):(\d{1,3})$/;
114443
114899
  MESSAGE_STATES = ["reading", "working", "done", "failed"];
@@ -114473,7 +114929,7 @@ var init_endpoint = __esm(() => {
114473
114929
  });
114474
114930
 
114475
114931
  // src/lib/serve-credential.ts
114476
- import { createHash as createHash63, randomBytes as randomBytes11, randomUUID as randomUUID32 } from "crypto";
114932
+ import { createHash as createHash63, randomBytes as randomBytes11, randomUUID as randomUUID33 } from "crypto";
114477
114933
  import {
114478
114934
  chmodSync as chmodSync5,
114479
114935
  closeSync as closeSync5,
@@ -114557,7 +115013,7 @@ function readServeCredential(dir) {
114557
115013
  }
114558
115014
  function writeStore(store, dir) {
114559
115015
  const file = serveCredentialPath(dir);
114560
- const temp = `${file}.${randomUUID32()}.tmp`;
115016
+ const temp = `${file}.${randomUUID33()}.tmp`;
114561
115017
  try {
114562
115018
  ensureKeryxConfigDir(dir);
114563
115019
  const handle = openSync5(temp, "wx", 384);
@@ -114597,7 +115053,7 @@ function mintRecord(now) {
114597
115053
  const salt = randomBytes11(32).toString("hex");
114598
115054
  return {
114599
115055
  token,
114600
- record: { id: randomUUID32(), algorithm: "sha256", salt, hash: hashToken(salt, token), createdAt: now }
115056
+ record: { id: randomUUID33(), algorithm: "sha256", salt, hash: hashToken(salt, token), createdAt: now }
114601
115057
  };
114602
115058
  }
114603
115059
  function issueServeToken(dir, now = () => new Date().toISOString(), onWaiting) {
@@ -114802,6 +115258,10 @@ class RemoteClient {
114802
115258
  inboundChain = Promise.resolve();
114803
115259
  waiters = new Map;
114804
115260
  earlyDecisions = new Map;
115261
+ seenApprovals = new Map;
115262
+ earlyDecisionMs;
115263
+ unackedApprovals = new Map;
115264
+ lingerTimers = new Set;
114805
115265
  choiceWaiters = new Map;
114806
115266
  earlyChoices = new Map;
114807
115267
  firstResult;
@@ -114818,10 +115278,20 @@ class RemoteClient {
114818
115278
  this.sleep = options.sleep ?? abortableSleep2;
114819
115279
  this.requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
114820
115280
  this.isAlive = options.isAlive ?? ownProcessIsAlive;
115281
+ this.earlyDecisionMs = options.earlyDecisionMs ?? DEFAULT_EARLY_DECISION_MS;
114821
115282
  }
114822
115283
  get connected() {
114823
115284
  return this.streamOpen && !this.stopped;
114824
115285
  }
115286
+ get unconfirmedApprovals() {
115287
+ const cutoff = Date.now() - UNCONFIRMED_APPROVAL_LINGER_MS;
115288
+ for (const [id, at] of [...this.unackedApprovals.entries()]) {
115289
+ if (at < cutoff) {
115290
+ this.unackedApprovals.delete(id);
115291
+ }
115292
+ }
115293
+ return this.unackedApprovals.size;
115294
+ }
114825
115295
  start() {
114826
115296
  if (this.started) {
114827
115297
  return Promise.resolve({ ok: false, code: "already-started", message: "this client was already started", retrying: false });
@@ -114872,6 +115342,10 @@ class RemoteClient {
114872
115342
  }
114873
115343
  this.lifetime.abort();
114874
115344
  this.streamAbort?.abort();
115345
+ for (const timer of this.lingerTimers) {
115346
+ clearTimeout(timer);
115347
+ }
115348
+ this.lingerTimers.clear();
114875
115349
  this.failApprovals();
114876
115350
  this.firstResult?.({ ok: false, code: "stopped", message: "the client was stopped before it connected", retrying: false });
114877
115351
  this.firstResult = undefined;
@@ -114905,8 +115379,10 @@ class RemoteClient {
114905
115379
  }
114906
115380
  const early = this.earlyDecisions.get(approvalId);
114907
115381
  if (early !== undefined) {
115382
+ clearTimeout(early.timer);
114908
115383
  this.earlyDecisions.delete(approvalId);
114909
- return early;
115384
+ this.settleApproval(approvalId, early.decision, true);
115385
+ return early.decision;
114910
115386
  }
114911
115387
  if (!this.connected) {
114912
115388
  return "deny";
@@ -115148,8 +115624,7 @@ class RemoteClient {
115148
115624
  return;
115149
115625
  });
115150
115626
  } else if (event === "approval") {
115151
- const approval = parsed;
115152
- this.resolveApproval(approval.approvalId, approval.decision);
115627
+ this.handleApprovalFrame(parsed);
115153
115628
  } else if (event === "choice") {
115154
115629
  const choice = parsed;
115155
115630
  this.resolveChoice(choice.promptId, choice.index);
@@ -115194,16 +115669,115 @@ class RemoteClient {
115194
115669
  await this.post("ack", { sessionId: this.options.sessionId, updateId });
115195
115670
  } catch {}
115196
115671
  }
115672
+ handleApprovalFrame(approval) {
115673
+ const approvalId = approval?.approvalId;
115674
+ if (!isApprovalId(approvalId) || approval.decision !== "allow" && approval.decision !== "deny") {
115675
+ return;
115676
+ }
115677
+ const decision = approval.decision;
115678
+ const seen = this.seenApprovals.get(approvalId);
115679
+ if (seen === "pending") {
115680
+ return;
115681
+ }
115682
+ this.countUnconfirmed(approvalId);
115683
+ if (seen !== undefined) {
115684
+ this.ackApproval(approvalId, seen === "applied");
115685
+ return;
115686
+ }
115687
+ if (this.waiters.has(approvalId)) {
115688
+ this.settleApproval(approvalId, decision, true, () => this.resolveApproval(approvalId, decision));
115689
+ return;
115690
+ }
115691
+ this.rememberSeen(approvalId, "pending");
115692
+ this.parkEarlyDecision(approvalId, decision);
115693
+ }
115694
+ rememberSeen(approvalId, outcome) {
115695
+ this.seenApprovals.delete(approvalId);
115696
+ this.seenApprovals.set(approvalId, outcome);
115697
+ if (this.seenApprovals.size > MAX_SEEN_APPROVALS) {
115698
+ const oldest = this.seenApprovals.keys().next().value;
115699
+ if (oldest !== undefined) {
115700
+ this.seenApprovals.delete(oldest);
115701
+ }
115702
+ }
115703
+ }
115704
+ countUnconfirmed(approvalId) {
115705
+ this.unackedApprovals.delete(approvalId);
115706
+ this.unackedApprovals.set(approvalId, Date.now());
115707
+ this.notifyUnconfirmed();
115708
+ while (this.unackedApprovals.size > MAX_UNCONFIRMED_APPROVALS) {
115709
+ const oldest = this.unackedApprovals.keys().next().value;
115710
+ if (oldest === undefined) {
115711
+ break;
115712
+ }
115713
+ this.unackedApprovals.delete(oldest);
115714
+ }
115715
+ }
115716
+ settleApproval(approvalId, decision, applied, apply) {
115717
+ this.rememberSeen(approvalId, applied ? "applied" : "not-applied");
115718
+ try {
115719
+ this.options.onApprovalFrame?.({ approvalId, decision, applied });
115720
+ } catch {}
115721
+ apply?.();
115722
+ this.ackApproval(approvalId, applied);
115723
+ }
115724
+ parkEarlyDecision(approvalId, decision) {
115725
+ while (this.earlyDecisions.size >= MAX_EARLY_DECISIONS) {
115726
+ const oldest = this.earlyDecisions.keys().next().value;
115727
+ if (oldest === undefined) {
115728
+ break;
115729
+ }
115730
+ this.dropEarlyDecision(oldest);
115731
+ }
115732
+ const timer = setTimeout(() => this.dropEarlyDecision(approvalId), this.earlyDecisionMs);
115733
+ timer.unref?.();
115734
+ this.earlyDecisions.set(approvalId, { decision, timer });
115735
+ }
115736
+ dropEarlyDecision(approvalId) {
115737
+ const early = this.earlyDecisions.get(approvalId);
115738
+ if (early === undefined) {
115739
+ return;
115740
+ }
115741
+ clearTimeout(early.timer);
115742
+ this.earlyDecisions.delete(approvalId);
115743
+ if (!this.stopped) {
115744
+ this.settleApproval(approvalId, early.decision, false);
115745
+ }
115746
+ }
115747
+ async ackApproval(approvalId, applied) {
115748
+ let confirmed = false;
115749
+ try {
115750
+ const response = await this.post("approval-ack", { sessionId: this.options.sessionId, approvalId, applied });
115751
+ confirmed = response.ok;
115752
+ } catch {}
115753
+ if (this.stopped) {
115754
+ return;
115755
+ }
115756
+ if (confirmed) {
115757
+ this.unackedApprovals.delete(approvalId);
115758
+ this.notifyUnconfirmed();
115759
+ return;
115760
+ }
115761
+ this.lingerTimers.add(this.scheduleUnref(() => {
115762
+ this.notifyUnconfirmed();
115763
+ }, UNCONFIRMED_APPROVAL_LINGER_MS + 50));
115764
+ }
115765
+ notifyUnconfirmed() {
115766
+ try {
115767
+ this.options.onUnconfirmedChange?.();
115768
+ } catch {}
115769
+ }
115770
+ scheduleUnref(fn, ms) {
115771
+ const timer = setTimeout(() => {
115772
+ this.lingerTimers.delete(timer);
115773
+ fn();
115774
+ }, ms);
115775
+ timer.unref?.();
115776
+ return timer;
115777
+ }
115197
115778
  resolveApproval(approvalId, decision) {
115198
115779
  const waiter = this.waiters.get(approvalId);
115199
115780
  if (waiter === undefined) {
115200
- if (this.earlyDecisions.size >= MAX_EARLY_DECISIONS) {
115201
- const oldest = this.earlyDecisions.keys().next().value;
115202
- if (oldest !== undefined) {
115203
- this.earlyDecisions.delete(oldest);
115204
- }
115205
- }
115206
- this.earlyDecisions.set(approvalId, decision);
115207
115781
  return;
115208
115782
  }
115209
115783
  clearTimeout(waiter.timer);
@@ -115240,11 +115814,13 @@ class RemoteClient {
115240
115814
  this.waiters.delete(id);
115241
115815
  waiter.resolve("deny");
115242
115816
  }
115243
- this.earlyDecisions.clear();
115817
+ for (const id of [...this.earlyDecisions.keys()]) {
115818
+ this.dropEarlyDecision(id);
115819
+ }
115244
115820
  this.failChoices();
115245
115821
  }
115246
115822
  }
115247
- var UNVERIFIED_SERVE_MESSAGE = "the program answering on keryx serve's port did not prove it is the keryx serve this shell trusts; its answer was ignored. Restart `keryx serve` (an older serve cannot prove itself; update keryx on both sides)", FatalClientError, TransientClientError, DEFAULT_BACKOFF_INITIAL_MS = 250, DEFAULT_BACKOFF_MAX_MS = 15000, DEFAULT_REQUEST_TIMEOUT_MS = 1e4, MAX_EARLY_DECISIONS = 32, MAX_COMPLETED_IDS = 256;
115823
+ var UNVERIFIED_SERVE_MESSAGE = "the program answering on keryx serve's port did not prove it is the keryx serve this shell trusts; its answer was ignored. Restart `keryx serve` (an older serve cannot prove itself; update keryx on both sides)", FatalClientError, TransientClientError, DEFAULT_BACKOFF_INITIAL_MS = 250, DEFAULT_BACKOFF_MAX_MS = 15000, DEFAULT_REQUEST_TIMEOUT_MS = 1e4, MAX_EARLY_DECISIONS = 32, DEFAULT_EARLY_DECISION_MS = 2000, MAX_SEEN_APPROVALS = 256, UNCONFIRMED_APPROVAL_LINGER_MS = 60000, MAX_UNCONFIRMED_APPROVALS = 64, MAX_COMPLETED_IDS = 256;
115248
115824
  var init_client3 = __esm(() => {
115249
115825
  init_serve_config();
115250
115826
  init_lease();
@@ -115924,10 +116500,12 @@ class RemoteBridge {
115924
116500
  }
115925
116501
  const online = this.clientConnected && client.connected;
115926
116502
  const heartbeat = client.lastHeartbeatAt;
116503
+ const unconfirmed = client.unconfirmedApprovals ?? 0;
115927
116504
  return {
115928
116505
  state: online ? "on" : "offline",
115929
116506
  ...client.name !== undefined ? { name: client.name } : {},
115930
116507
  ...heartbeat !== undefined ? { heartbeatAgeMs: Math.max(0, this.now() - heartbeat) } : {},
116508
+ ...unconfirmed > 0 ? { unconfirmedApprovals: unconfirmed } : {},
115931
116509
  events: [...this.events]
115932
116510
  };
115933
116511
  }
@@ -115954,7 +116532,9 @@ class RemoteBridge {
115954
116532
  ...name !== undefined ? { name } : {},
115955
116533
  ...this.options.dir !== undefined ? { dir: this.options.dir } : {},
115956
116534
  onLine: (text, meta) => this.accept(text, meta),
115957
- onStatus: (status) => this.onStatus(status)
116535
+ onStatus: (status) => this.onStatus(status),
116536
+ onApprovalFrame: (frame) => this.host.notice(frame.applied ? `\u25C7 approval ${frame.approvalId} ${frame.decision === "allow" ? "allowed" : "denied"} from Telegram` : `\u25C7 approval ${frame.approvalId}: ${frame.decision === "allow" ? "an allow" : "a deny"} from Telegram arrived but nothing here was waiting for it; not applied`),
116537
+ onUnconfirmedChange: () => this.host.onChange?.()
115958
116538
  };
115959
116539
  const client = this.options.makeClient !== undefined ? this.options.makeClient(clientOptions) : new RemoteClient(clientOptions);
115960
116540
  this.client = client;
@@ -116193,7 +116773,7 @@ function fileExists(file) {
116193
116773
  const read = readConfigFile(file);
116194
116774
  return read.ok || read.reason !== "absent";
116195
116775
  }
116196
- function isObject2(value) {
116776
+ function isObject3(value) {
116197
116777
  return typeof value === "object" && value !== null && !Array.isArray(value);
116198
116778
  }
116199
116779
  function plural2(count, one, many) {
@@ -116220,7 +116800,7 @@ class ChannelsClient {
116220
116800
  status() {
116221
116801
  return this.call("channels-status", (body) => {
116222
116802
  const telegram = body.telegram;
116223
- return typeof body.machine === "string" && isObject2(telegram) && typeof telegram.state === "string" && typeof telegram.sessions === "number";
116803
+ return typeof body.machine === "string" && isObject3(telegram) && typeof telegram.state === "string" && typeof telegram.sessions === "number";
116224
116804
  });
116225
116805
  }
116226
116806
  async startPairing(token) {
@@ -116389,12 +116969,12 @@ class ChannelsClient {
116389
116969
  parsed = undefined;
116390
116970
  }
116391
116971
  if (!response.ok) {
116392
- const error = isObject2(parsed) && isObject2(parsed.error) ? parsed.error : undefined;
116972
+ const error = isObject3(parsed) && isObject3(parsed.error) ? parsed.error : undefined;
116393
116973
  const code = typeof error?.code === "string" ? error.code : `http-${response.status}`;
116394
116974
  const message = typeof error?.message === "string" ? error.message : `keryx serve refused the request (HTTP ${response.status})`;
116395
116975
  return { ok: false, code, reason: message };
116396
116976
  }
116397
- if (!isObject2(parsed) || !guard(parsed)) {
116977
+ if (!isObject3(parsed) || !guard(parsed)) {
116398
116978
  return { ok: false, code: "unexpected-response", reason: "keryx serve sent an answer this shell does not understand; update keryx on both sides" };
116399
116979
  }
116400
116980
  return { ok: true, value: parsed };
@@ -116414,7 +116994,7 @@ class ChannelsClient {
116414
116994
  }
116415
116995
  function refusedConnection(error) {
116416
116996
  const seen = new Set;
116417
- for (let current = error;isObject2(current) || current instanceof Error; ) {
116997
+ for (let current = error;isObject3(current) || current instanceof Error; ) {
116418
116998
  if (seen.has(current))
116419
116999
  break;
116420
117000
  seen.add(current);
@@ -117583,6 +118163,10 @@ function formatRemoteStatusLines(status) {
117583
118163
  lines.push(`Last heartbeat: ${status.heartbeatAgeMs === undefined ? "none yet" : `${formatAge(status.heartbeatAgeMs)} ago`}`);
117584
118164
  if (status.state === "offline")
117585
118165
  lines.push("Serve is not reachable. The shell keeps retrying; nothing is lost from Telegram.");
118166
+ if (status.unconfirmedApprovals !== undefined && status.unconfirmedApprovals > 0) {
118167
+ const n = status.unconfirmedApprovals;
118168
+ lines.push(`Approvals not confirmed: ${n}. The topic shows ${n === 1 ? "it" : "them"} as "not confirmed" until serve takes the shell's ack.`);
118169
+ }
117586
118170
  lines.push(`Turn it off with ${REMOTE_CONTROL_COMMAND} off, or press o here. Rename: ${REMOTE_CONTROL_COMMAND} off, then ${REMOTE_CONTROL_COMMAND} <name>.`);
117587
118171
  return lines;
117588
118172
  }
@@ -117703,7 +118287,11 @@ function projectRemoteRow(status, width) {
117703
118287
  return { text: fit3("off", width), role: "muted", action: "open" };
117704
118288
  if (status.state === "offline")
117705
118289
  return { text: fit3("offline", width), role: "attention", action: "open" };
117706
- return { text: fit3(status.name ?? "connecting", width), role: "ok", action: "open" };
118290
+ const topic = status.name ?? "connecting";
118291
+ const unconfirmed = status.unconfirmedApprovals ?? 0;
118292
+ if (unconfirmed > 0)
118293
+ return { text: fit3(`${topic} \xB7 ${unconfirmed} unconfirmed`, width), role: "attention", action: "open" };
118294
+ return { text: fit3(topic, width), role: "ok", action: "open" };
117707
118295
  }
117708
118296
  function mountRemotePanel(otui, renderer, parent, options) {
117709
118297
  const core = otui;
@@ -120921,10 +121509,10 @@ var init_serve_turn_store = __esm(() => {
120921
121509
  });
120922
121510
 
120923
121511
  // src/lib/serve-approvals-store.ts
120924
- import { randomUUID as randomUUID33 } from "crypto";
121512
+ import { randomUUID as randomUUID34 } from "crypto";
120925
121513
  import { existsSync as existsSync45, readdirSync as readdirSync10 } from "fs";
120926
121514
  import path261 from "path";
120927
- function isApprovalId(value) {
121515
+ function isApprovalId2(value) {
120928
121516
  return ID_PATTERN2.test(value);
120929
121517
  }
120930
121518
  function root(dir) {
@@ -120950,7 +121538,7 @@ function appendEvidence(event, dir) {
120950
121538
  appendOwnerOnlyLine(ledgerFile(dir), JSON.stringify(event));
120951
121539
  }
120952
121540
  function createApproval(input, dir, now = new Date) {
120953
- if (!isApprovalId(input.approvalId)) {
121541
+ if (!isApprovalId2(input.approvalId)) {
120954
121542
  throw new Error("approvalId is not an approval id");
120955
121543
  }
120956
121544
  ensureKeryxSubdir(["approvals"], dir);
@@ -121017,7 +121605,7 @@ function parseResolution(text) {
121017
121605
  }
121018
121606
  }
121019
121607
  function readRaw(id, dir) {
121020
- if (!isApprovalId(id)) {
121608
+ if (!isApprovalId2(id)) {
121021
121609
  return { ok: false, reason: "not-an-approval-id" };
121022
121610
  }
121023
121611
  const read = readConfigFile(requestFile(id, dir));
@@ -121121,7 +121709,7 @@ function approvalIds(dir) {
121121
121709
  if (!existsSync45(base)) {
121122
121710
  return [];
121123
121711
  }
121124
- return readdirSync10(base).filter((name) => name.endsWith(".json") && !name.endsWith(".resolution.json")).map((name) => name.slice(0, -".json".length)).filter(isApprovalId);
121712
+ return readdirSync10(base).filter((name) => name.endsWith(".json") && !name.endsWith(".resolution.json")).map((name) => name.slice(0, -".json".length)).filter(isApprovalId2);
121125
121713
  }
121126
121714
  function listApprovals(dir, options = {}) {
121127
121715
  const views = [];
@@ -121197,7 +121785,7 @@ function toPublicApproval(view) {
121197
121785
  };
121198
121786
  }
121199
121787
  function newApprovalId() {
121200
- return randomUUID33();
121788
+ return randomUUID34();
121201
121789
  }
121202
121790
  var APPROVAL_SCHEMA_VERSION = "1.0.0", SUMMARY_MAX = 500, SCOPE_MAX = 300, CONSEQUENCE_MAX = 300, ID_PATTERN2;
121203
121791
  var init_serve_approvals_store = __esm(() => {
@@ -121718,10 +122306,12 @@ async function openDecisions(otui, chrome, options) {
121718
122306
  const text = await reportText(cwd).catch(() => UNREADABLE);
121719
122307
  return presentDecisions((hostOtui, hostChrome, input) => openModal(hostOtui, hostChrome, input), otui, chrome, { ...rest, text });
121720
122308
  }
121721
- function projectDecisionsPanel(count, width) {
121722
- if (count <= 0)
122309
+ function projectDecisionsPanel(count, width, backfilled = 0) {
122310
+ if (count <= 0 && backfilled <= 0)
121723
122311
  return { visible: false, text: "" };
121724
- const label = `${count} decision${count === 1 ? "" : "s"}`;
122312
+ const live = count > 0 ? `${count} decision${count === 1 ? "" : "s"}` : "";
122313
+ const before = backfilled > 0 ? `${count > 0 ? " + " : ""}${backfilled} before` : "";
122314
+ const label = `${live}${before}`;
121725
122315
  const candidates = [`${label} \xB7 /decisions`, label];
121726
122316
  const text = candidates.find((candidate) => candidate.length <= width) ?? label.slice(0, Math.max(1, width));
121727
122317
  return { visible: true, text };
@@ -121744,6 +122334,7 @@ function mountDecisionsSidebar(options) {
121744
122334
  const { chrome } = options;
121745
122335
  const r = chrome.renderer;
121746
122336
  const count = options.count ?? (() => decisionCount(options.cwd));
122337
+ const backfilledTotal = options.backfilled ?? (() => backfilledCount(options.cwd));
121747
122338
  const box = new core.BoxRenderable(r, { id: "sb-decisions", flexDirection: "column", flexShrink: 0 });
121748
122339
  options.parent.add(box);
121749
122340
  let modal;
@@ -121789,7 +122380,13 @@ function mountDecisionsSidebar(options) {
121789
122380
  } catch {
121790
122381
  n = 0;
121791
122382
  }
121792
- projected = projectDecisionsPanel(n, options.width);
122383
+ let before;
122384
+ try {
122385
+ before = await backfilledTotal();
122386
+ } catch {
122387
+ before = 0;
122388
+ }
122389
+ projected = projectDecisionsPanel(n, options.width, before);
121793
122390
  paint();
121794
122391
  };
121795
122392
  const unsubscribeTheme = onThemeChange(guardedThemeRepaint("decisions-row", () => paint(true), () => disposed || isRenderableGone(box)));
@@ -121837,8 +122434,8 @@ var init_decisions_surface = __esm(() => {
121837
122434
 
121838
122435
  // src/harness/external/write-run.ts
121839
122436
  import { execFile as execFile11 } from "child_process";
121840
- import { createHash as createHash66, randomUUID as randomUUID34 } from "crypto";
121841
- import { mkdtemp as mkdtemp5, readlink, realpath as realpath19, rm as rm17 } from "fs/promises";
122437
+ import { createHash as createHash66, randomUUID as randomUUID35 } from "crypto";
122438
+ import { mkdtemp as mkdtemp5, readlink, realpath as realpath19, rm as rm18 } from "fs/promises";
121842
122439
  import { tmpdir as tmpdir11 } from "os";
121843
122440
  import path262 from "path";
121844
122441
  import { promisify as promisify9 } from "util";
@@ -121957,7 +122554,7 @@ async function captureWriteDiff(input) {
121957
122554
  refusedPaths
121958
122555
  };
121959
122556
  } finally {
121960
- await rm17(indexDir, { recursive: true, force: true }).catch(() => {
122557
+ await rm18(indexDir, { recursive: true, force: true }).catch(() => {
121961
122558
  return;
121962
122559
  });
121963
122560
  }
@@ -122031,7 +122628,7 @@ async function runExternalWriteChild(input, deps) {
122031
122628
  });
122032
122629
  }
122033
122630
  if (ownedDir !== undefined)
122034
- await rm17(ownedDir, { recursive: true, force: true }).catch(() => {
122631
+ await rm18(ownedDir, { recursive: true, force: true }).catch(() => {
122035
122632
  return;
122036
122633
  });
122037
122634
  }
@@ -122056,7 +122653,7 @@ async function runExternalWriteChild(input, deps) {
122056
122653
  }
122057
122654
  }
122058
122655
  function persistWriteRun(args) {
122059
- const runId = randomUUID34();
122656
+ const runId = randomUUID35();
122060
122657
  const title = `External write ${args.agentId}: ${args.runStatus}`;
122061
122658
  const handle = createSession({
122062
122659
  cwd: args.projectRoot,
@@ -122172,7 +122769,7 @@ var init_write_run = __esm(() => {
122172
122769
  import { execFile as execFile12 } from "child_process";
122173
122770
  import { createHash as createHash67 } from "crypto";
122174
122771
  import { existsSync as existsSync46, rmSync as rmSync5 } from "fs";
122175
- import { mkdtemp as mkdtemp6, rm as rm18 } from "fs/promises";
122772
+ import { mkdtemp as mkdtemp6, rm as rm19 } from "fs/promises";
122176
122773
  import { tmpdir as tmpdir12 } from "os";
122177
122774
  import path263 from "path";
122178
122775
  function errorMessage3(error) {
@@ -122292,11 +122889,11 @@ async function landWriteRun(input, deps = {}) {
122292
122889
  return;
122293
122890
  });
122294
122891
  if (ownedDir !== undefined)
122295
- await rm18(ownedDir, { recursive: true, force: true }).catch(() => {
122892
+ await rm19(ownedDir, { recursive: true, force: true }).catch(() => {
122296
122893
  return;
122297
122894
  });
122298
122895
  else
122299
- await rm18(path263.join(worktreesDir, worktreeId), { recursive: true, force: true }).catch(() => {
122896
+ await rm19(path263.join(worktreesDir, worktreeId), { recursive: true, force: true }).catch(() => {
122300
122897
  return;
122301
122898
  });
122302
122899
  await git(["worktree", "prune"], repo).catch(() => {
@@ -126285,7 +126882,7 @@ function recordTaskCost(store, record) {
126285
126882
  const next = existing.length >= MAX_SAMPLES_PER_KEY ? [...existing.slice(existing.length - MAX_SAMPLES_PER_KEY + 1), record] : [...existing, record];
126286
126883
  return { ...store, [key]: next };
126287
126884
  }
126288
- function median(values) {
126885
+ function median2(values) {
126289
126886
  if (values.length === 0)
126290
126887
  return 0;
126291
126888
  const sorted = [...values].sort((a, b) => a - b);
@@ -126294,9 +126891,9 @@ function median(values) {
126294
126891
  }
126295
126892
  function statsForKey(records, providerId, modelId, category) {
126296
126893
  const n = records.length;
126297
- const medianTokens = median(records.map((r) => r.totalTokens));
126894
+ const medianTokens = median2(records.map((r) => r.totalTokens));
126298
126895
  const knownCosts = records.map((r) => r.costUsd).filter((c) => c !== undefined);
126299
- const medianCostUsd = knownCosts.length > 0 ? median(knownCosts) : undefined;
126896
+ const medianCostUsd = knownCosts.length > 0 ? median2(knownCosts) : undefined;
126300
126897
  const successRate = records.filter((r) => r.success).length / n;
126301
126898
  return { providerId, modelId, category, n, medianTokens, ...medianCostUsd !== undefined ? { medianCostUsd } : {}, successRate };
126302
126899
  }
@@ -128677,7 +129274,7 @@ var init_conform_report = __esm(() => {
128677
129274
  });
128678
129275
 
128679
129276
  // src/tui/conform-source.ts
128680
- import { readFile as readFile127, readdir as readdir41, stat as stat26 } from "fs/promises";
129277
+ import { readFile as readFile127, readdir as readdir41, stat as stat27 } from "fs/promises";
128681
129278
  import path270 from "path";
128682
129279
  async function readRecentConformDocs(cwd) {
128683
129280
  try {
@@ -128720,7 +129317,7 @@ async function mostRecentReviewPackage(cwd) {
128720
129317
  continue;
128721
129318
  const dir = path270.join(reviewsDir, reviewEntry.name);
128722
129319
  try {
128723
- const info = await stat26(path270.join(dir, "report.md"));
129320
+ const info = await stat27(path270.join(dir, "report.md"));
128724
129321
  if (best === undefined || info.mtimeMs > best.mtimeMs)
128725
129322
  best = { dir, mtimeMs: info.mtimeMs };
128726
129323
  } catch {}
@@ -129687,7 +130284,7 @@ var init_jev_rules = __esm(() => {
129687
130284
  });
129688
130285
 
129689
130286
  // src/commands/review-jev-rules.ts
129690
- import { readFile as readFile130, readdir as readdir42, stat as stat27 } from "fs/promises";
130287
+ import { readFile as readFile130, readdir as readdir42, stat as stat28 } from "fs/promises";
129691
130288
  import { basename as basename3, dirname as dirname2, isAbsolute as isAbsolute3, join as join5, relative as relative3 } from "path";
129692
130289
  function rejectUnknownJevRulesFlags(args) {
129693
130290
  const unknown = args.filter((arg) => arg.startsWith("--") && !JEV_RULES_FLAGS.includes(arg.split("=")[0]));
@@ -129800,7 +130397,7 @@ async function discoverRuleSources(cwd, explicitPaths) {
129800
130397
  const explicitDirSources = [];
129801
130398
  for (const raw of explicitPaths) {
129802
130399
  const resolved = isAbsolute3(raw) ? raw : join5(cwd, raw);
129803
- const stats = await stat27(resolved).catch(() => {
130400
+ const stats = await stat28(resolved).catch(() => {
129804
130401
  return;
129805
130402
  });
129806
130403
  if (stats === undefined) {
@@ -131677,7 +132274,7 @@ var init_jev_triage = __esm(() => {
131677
132274
  });
131678
132275
 
131679
132276
  // src/commands/review-jev-triage.ts
131680
- import { readFile as readFile136, stat as stat28 } from "fs/promises";
132277
+ import { readFile as readFile136, stat as stat29 } from "fs/promises";
131681
132278
  import path276 from "path";
131682
132279
  function rejectUnknownJevTriageFlags(args) {
131683
132280
  const unknown = args.filter((arg) => arg.startsWith("--") && !JEV_TRIAGE_FLAGS.includes(arg.split("=")[0]));
@@ -131712,7 +132309,7 @@ function isMaxTokensExceededError2(error) {
131712
132309
  async function loadReportRaw(reportPath) {
131713
132310
  let filePath = reportPath;
131714
132311
  try {
131715
- const stats = await stat28(reportPath);
132312
+ const stats = await stat29(reportPath);
131716
132313
  if (stats.isDirectory())
131717
132314
  filePath = path276.join(reportPath, "findings.json");
131718
132315
  } catch {}
@@ -131857,7 +132454,7 @@ var init_review_jev_triage = __esm(() => {
131857
132454
  });
131858
132455
 
131859
132456
  // src/tui/jev-triage-command.ts
131860
- import { readdir as readdir43, readFile as readFile137, stat as stat29 } from "fs/promises";
132457
+ import { readdir as readdir43, readFile as readFile137, stat as stat30 } from "fs/promises";
131861
132458
  import path277 from "path";
131862
132459
  function isJevTriageCommand(name) {
131863
132460
  return name === JEV_TRIAGE_COMMAND;
@@ -131886,7 +132483,7 @@ async function resolveLatestReviewPackage(cwd) {
131886
132483
  const packageDir = path277.join(reviewsDir, reviewName);
131887
132484
  const findingsPath = path277.join(packageDir, "findings.json");
131888
132485
  try {
131889
- const stats = await stat29(findingsPath);
132486
+ const stats = await stat30(findingsPath);
131890
132487
  candidates.push({ dir: packageDir, mtimeMs: stats.mtimeMs });
131891
132488
  } catch {
131892
132489
  continue;
@@ -139215,7 +139812,7 @@ function maxOverFiles(files, values) {
139215
139812
  }
139216
139813
 
139217
139814
  // src/wiki/deep-enrich.ts
139218
- import { randomUUID as randomUUID35, createHash as createHash68 } from "crypto";
139815
+ import { randomUUID as randomUUID36, createHash as createHash68 } from "crypto";
139219
139816
  function errorMessage4(cause) {
139220
139817
  return cause instanceof Error ? cause.message : String(cause);
139221
139818
  }
@@ -139251,7 +139848,7 @@ function buildDeepUserPrompt(page, original, extra) {
139251
139848
  `);
139252
139849
  }
139253
139850
  async function enrichPageDeep(input) {
139254
- const idSeq = input.idSeq ?? (() => randomUUID35());
139851
+ const idSeq = input.idSeq ?? (() => randomUUID36());
139255
139852
  const clock = input.clock ?? (() => new Date().toISOString());
139256
139853
  const toolCalls = [];
139257
139854
  if (input.signal?.aborted) {
@@ -141490,6 +142087,98 @@ function drainIdleMainQueue(items, options) {
141490
142087
  return true;
141491
142088
  }
141492
142089
 
142090
+ // src/tui/remote-queue-wiring.ts
142091
+ function createRemoteQueueWiring(deps) {
142092
+ let pendingEdit;
142093
+ return {
142094
+ removeMainQueue(index) {
142095
+ const queue = deps.getQueue();
142096
+ if (index < 0 || index >= queue.length)
142097
+ return;
142098
+ const removed = queue[index];
142099
+ if (removed?.source === TG_SOURCE) {
142100
+ deps.getBridge()?.queuedLineRemoved(removed.question);
142101
+ }
142102
+ deps.setQueue(removeMainQueueItem(queue, index));
142103
+ deps.paint();
142104
+ },
142105
+ editMainQueue(index) {
142106
+ const edited = editMainQueueItem(deps.getQueue(), index);
142107
+ if (edited === undefined)
142108
+ return;
142109
+ deps.setQueue(edited.rest);
142110
+ pendingEdit = pendingQueueEditFor(edited.removed, index);
142111
+ deps.composer.setText(edited.text);
142112
+ deps.composer.focus();
142113
+ deps.paint();
142114
+ },
142115
+ requeuePendingEdit(line, displayLine) {
142116
+ if (pendingEdit === undefined)
142117
+ return false;
142118
+ const edit = pendingEdit;
142119
+ pendingEdit = undefined;
142120
+ deps.setQueue(reinsertEditedMainQueueItem(deps.getQueue(), edit, line, displayLine));
142121
+ deps.paint();
142122
+ return true;
142123
+ },
142124
+ hasPendingEdit: () => pendingEdit !== undefined,
142125
+ stopRemoteForSessionSwitch() {
142126
+ const bridge = deps.getBridge();
142127
+ if (bridge?.keepingTopic === true) {
142128
+ bridge.sessionLeaving();
142129
+ return;
142130
+ }
142131
+ if (bridge?.active !== true)
142132
+ return;
142133
+ const closing = bridge.disable();
142134
+ deps.say(`\u25C7 remote control is turning off: the session changed, and its Telegram topic is being deleted. Turn it on again with /remote-control <name>.
142135
+ `);
142136
+ closing.then(() => deps.say(`\u25C7 remote control off: the topic is deleted.
142137
+ `));
142138
+ },
142139
+ dropQueuedTelegramLines() {
142140
+ const { kept, dropped } = dropQueuedBySource(deps.getQueue(), TG_SOURCE);
142141
+ if (dropped.length === 0)
142142
+ return [];
142143
+ deps.setQueue(kept);
142144
+ deps.paint();
142145
+ return dropped.map((item) => item.question);
142146
+ },
142147
+ enqueueTelegramLine(text) {
142148
+ const queue = deps.getQueue();
142149
+ queue.push({ id: deps.nextId(), question: text, displayQuestion: deps.summarize(text), source: TG_SOURCE });
142150
+ deps.paint();
142151
+ deps.say(`\u25C7 a line from Telegram is queued as q${queue.length}.
142152
+ `);
142153
+ },
142154
+ runTelegramLine(text) {
142155
+ deps.runLine(text, "operator", TG_SOURCE);
142156
+ },
142157
+ runQueued(item) {
142158
+ deps.runLine(item.question, "operator", item.source);
142159
+ },
142160
+ turnStarted(source) {
142161
+ deps.getBridge()?.turnStarted(source);
142162
+ },
142163
+ assistantText(text) {
142164
+ deps.getBridge()?.assistantText(text);
142165
+ },
142166
+ wrapOnToolCall(base) {
142167
+ return (...args) => {
142168
+ deps.getBridge()?.toolCall();
142169
+ base?.(...args);
142170
+ };
142171
+ },
142172
+ turnSettled(outcome) {
142173
+ const bridge = deps.getBridge();
142174
+ return bridge === undefined ? Promise.resolve() : bridge.turnSettled({ failed: outcome.failed || outcome.aborted });
142175
+ }
142176
+ };
142177
+ }
142178
+ var init_remote_queue_wiring = __esm(() => {
142179
+ init_shell_bridge();
142180
+ });
142181
+
141493
142182
  // src/tui/worker-fleet.ts
141494
142183
  function clip4(s, max) {
141495
142184
  if (s.length <= max) {
@@ -144740,7 +145429,7 @@ var init_reflect = __esm(() => {
144740
145429
  });
144741
145430
 
144742
145431
  // src/commands/memory.ts
144743
- import { randomUUID as randomUUID36 } from "crypto";
145432
+ import { randomUUID as randomUUID37 } from "crypto";
144744
145433
  import { readdir as readdir46 } from "fs/promises";
144745
145434
  import { join as join9 } from "path";
144746
145435
  function getService2() {
@@ -144928,7 +145617,7 @@ async function runSearch(args) {
144928
145617
  }
144929
145618
  if (args.includes("--json")) {
144930
145619
  const projection = renderMemorySearchReport({
144931
- runId: randomUUID36(),
145620
+ runId: randomUUID37(),
144932
145621
  generatedAt: new Date,
144933
145622
  search: result,
144934
145623
  filters
@@ -145932,8 +146621,8 @@ var init_proxy = __esm(() => {
145932
146621
 
145933
146622
  // src/commands/trigger-agent-task.ts
145934
146623
  import { execFile as execFile13 } from "child_process";
145935
- import { randomUUID as randomUUID37 } from "crypto";
145936
- import { mkdtemp as mkdtemp7, mkdir as mkdir61, readdir as readdir47, rm as rm19, writeFile as writeFile38 } from "fs/promises";
146624
+ import { randomUUID as randomUUID38 } from "crypto";
146625
+ import { mkdtemp as mkdtemp7, mkdir as mkdir61, readdir as readdir47, rm as rm20, writeFile as writeFile38 } from "fs/promises";
145937
146626
  import { homedir as homedir12, tmpdir as tmpdir13 } from "os";
145938
146627
  import path290 from "path";
145939
146628
  function scrubGrantedOutput(text, env, secrets = []) {
@@ -146055,12 +146744,12 @@ function lastAssistantText(history) {
146055
146744
  async function pruneReports(dir, keep) {
146056
146745
  const names = (await readdir47(dir).catch(() => [])).filter((n) => n.endsWith(".md")).sort();
146057
146746
  for (const old of names.slice(0, Math.max(0, names.length - keep))) {
146058
- await rm19(path290.join(dir, old), { force: true }).catch(() => {});
146747
+ await rm20(path290.join(dir, old), { force: true }).catch(() => {});
146059
146748
  }
146060
146749
  }
146061
146750
  async function runAgentTaskDispatch(projectRoot, entry, action, deps = {}) {
146062
146751
  const now = deps.now ?? (() => new Date);
146063
- const runId = deps.runId?.() ?? `sch-${now().toISOString().replace(/[-:.]/g, "").slice(0, 15)}-${randomUUID37().slice(0, 8)}`;
146752
+ const runId = deps.runId?.() ?? `sch-${now().toISOString().replace(/[-:.]/g, "").slice(0, 15)}-${randomUUID38().slice(0, 8)}`;
146064
146753
  await ensureLocksDir(projectRoot);
146065
146754
  try {
146066
146755
  return await withFileLock(dispatchLockPath(projectRoot, `schedule-${entry.name}`), () => runLocked(projectRoot, entry, action, deps, runId, now), {
@@ -146100,7 +146789,7 @@ async function runLocked(projectRoot, entry, action, deps, runId, now) {
146100
146789
  await mkdir61(scratchHome, { recursive: true });
146101
146790
  await mkdir61(grantedCwd, { recursive: true, mode: 448 });
146102
146791
  const cleanup = async () => {
146103
- await rm19(scratch, { recursive: true, force: true }).catch(() => {});
146792
+ await rm20(scratch, { recursive: true, force: true }).catch(() => {});
146104
146793
  };
146105
146794
  let closeProxy = async () => {};
146106
146795
  const networkDecisions = [];
@@ -146223,7 +146912,7 @@ async function runLocked(projectRoot, entry, action, deps, runId, now) {
146223
146912
  modelId: dispatch.model,
146224
146913
  toolNames: tools.map((t) => t.definition.name)
146225
146914
  }),
146226
- idSeq: () => randomUUID37(),
146915
+ idSeq: () => randomUUID38(),
146227
146916
  unattended: true,
146228
146917
  hardDeny: unattendedRefusal,
146229
146918
  ...shellHooks !== undefined ? { hooks: shellHooks } : {}
@@ -146738,7 +147427,7 @@ var init_digest_limits = __esm(() => {
146738
147427
  });
146739
147428
 
146740
147429
  // src/scheduler/digest-summary.ts
146741
- import { randomUUID as randomUUID38 } from "crypto";
147430
+ import { randomUUID as randomUUID39 } from "crypto";
146742
147431
  var SYSTEM, MAX_OUTPUT_TOKENS = 400, defaultSummarize = async (input) => {
146743
147432
  const { action, entry } = input;
146744
147433
  const dispatch = action.dispatch;
@@ -146768,7 +147457,7 @@ var SYSTEM, MAX_OUTPUT_TOKENS = 400, defaultSummarize = async (input) => {
146768
147457
  local.abort();
146769
147458
  });
146770
147459
  const provider = guardUsage(built, () => meter.markUsageMissing());
146771
- const requestId = `digest-${randomUUID38().slice(0, 8)}`;
147460
+ const requestId = `digest-${randomUUID39().slice(0, 8)}`;
146772
147461
  const request = {
146773
147462
  providerId: dispatch.provider,
146774
147463
  modelId: dispatch.model,
@@ -146817,8 +147506,8 @@ var init_digest_summary = __esm(() => {
146817
147506
  });
146818
147507
 
146819
147508
  // src/scheduler/digest-run.ts
146820
- import { randomUUID as randomUUID39 } from "crypto";
146821
- import { mkdir as mkdir62, mkdtemp as mkdtemp8, rm as rm20, writeFile as writeFile39 } from "fs/promises";
147509
+ import { randomUUID as randomUUID40 } from "crypto";
147510
+ import { mkdir as mkdir62, mkdtemp as mkdtemp8, rm as rm21, writeFile as writeFile39 } from "fs/promises";
146822
147511
  import path292 from "path";
146823
147512
  function capForDelivery(text, reportPath) {
146824
147513
  if (text.length <= DELIVERY_TEXT_MAX)
@@ -146838,7 +147527,7 @@ async function announceRefusal(projectRoot, name, topic, runId, reason, result,
146838
147527
  }
146839
147528
  async function runDigestDispatch(projectRoot, entry, action, deps = {}) {
146840
147529
  const now = deps.now ?? (() => new Date);
146841
- const runId = deps.runId?.() ?? `dig-${now().toISOString().replace(/[-:.]/g, "").slice(0, 15)}-${randomUUID39().slice(0, 8)}`;
147530
+ const runId = deps.runId?.() ?? `dig-${now().toISOString().replace(/[-:.]/g, "").slice(0, 15)}-${randomUUID40().slice(0, 8)}`;
146842
147531
  await ensureLocksDir(projectRoot);
146843
147532
  try {
146844
147533
  return await withFileLock(dispatchLockPath(projectRoot, `schedule-${entry.name}`), () => runLocked2(projectRoot, entry, action, deps, runId, now), {
@@ -146950,7 +147639,7 @@ async function runLocked2(projectRoot, entry, action, deps, runId, now) {
146950
147639
  crashed = error instanceof Error ? error.message : String(error);
146951
147640
  } finally {
146952
147641
  limits.dispose();
146953
- await rm20(scratch, { recursive: true, force: true }).catch(() => {});
147642
+ await rm21(scratch, { recursive: true, force: true }).catch(() => {});
146954
147643
  }
146955
147644
  const tools = DIGEST_TOOL_IDS.filter((id) => action.grants.tools.includes(id));
146956
147645
  const attempted = tools.length * action.grants.repos.length;
@@ -151303,17 +151992,31 @@ async function launchTuiAgentShell(opts) {
151303
151992
  guardCollector.onToolResult(name, toolResult);
151304
151993
  baseOnToolResultForGuard?.(name, toolResult);
151305
151994
  };
151995
+ const remoteQueue = createRemoteQueueWiring({
151996
+ getBridge: () => remoteBridge,
151997
+ getQueue: () => mainQueue,
151998
+ setQueue: (next) => {
151999
+ mainQueue = next;
152000
+ },
152001
+ nextId: () => `mq${mainQueueSeq++}`,
152002
+ summarize: summarizeSubmittedLine,
152003
+ paint: () => paintMainQueue(),
152004
+ composer: {
152005
+ setText: (text) => {
152006
+ input.value = text;
152007
+ },
152008
+ focus: () => input.focus()
152009
+ },
152010
+ say: (text) => io.onSystem?.(text),
152011
+ runLine: (line, origin, source) => runLine(line, origin, source)
152012
+ });
151306
152013
  const baseOnAssistantTextForGuard = io.onAssistantText?.bind(io);
151307
152014
  io.onAssistantText = (text) => {
151308
152015
  guardCollector.onAssistantText(text);
151309
- remoteBridge?.assistantText(text);
152016
+ remoteQueue.assistantText(text);
151310
152017
  baseOnAssistantTextForGuard?.(text);
151311
152018
  };
151312
- const baseOnToolCallForRemote = io.onToolCall?.bind(io);
151313
- io.onToolCall = (name, toolInput) => {
151314
- remoteBridge?.toolCall();
151315
- baseOnToolCallForRemote?.(name, toolInput);
151316
- };
152019
+ io.onToolCall = remoteQueue.wrapOnToolCall(io.onToolCall?.bind(io));
151317
152020
  const refreshGuardSidebar = () => {
151318
152021
  clearTranscriptChildren(sbGuard);
151319
152022
  if (!guardEnabled)
@@ -151959,19 +152662,7 @@ async function launchTuiAgentShell(opts) {
151959
152662
  deps = { ...deps, onContextCompaction };
151960
152663
  liveDeps = deps;
151961
152664
  let splash = undefined;
151962
- const stopRemoteForSessionSwitch = () => {
151963
- if (remoteBridge?.keepingTopic === true) {
151964
- remoteBridge.sessionLeaving();
151965
- return;
151966
- }
151967
- if (remoteBridge?.active !== true)
151968
- return;
151969
- const closing = remoteBridge.disable();
151970
- io.onSystem?.(`\u25C7 remote control is turning off: the session changed, and its Telegram topic is being deleted. Turn it on again with /remote-control <name>.
151971
- `);
151972
- closing.then(() => io.onSystem?.(`\u25C7 remote control off: the topic is deleted.
151973
- `));
151974
- };
152665
+ const stopRemoteForSessionSwitch = () => remoteQueue.stopRemoteForSessionSwitch();
151975
152666
  const applyOpened = (opened, previewHistory) => {
151976
152667
  if (opened.history.length > 0) {
151977
152668
  splash?.removeIfShown();
@@ -153107,7 +153798,6 @@ ${formatThemeList(getThemeId())}`);
153107
153798
  const sideQueue = [];
153108
153799
  let mainQueue = [];
153109
153800
  let mainQueueSeq = 0;
153110
- let pendingQueueEdit;
153111
153801
  const forceHandoff = createForegroundForceHandoff();
153112
153802
  let sideWorkerRunning = false;
153113
153803
  let sideClearTimeout;
@@ -153215,26 +153905,8 @@ ${formatThemeList(getThemeId())}`);
153215
153905
  fleet.remove("agent:queue");
153216
153906
  }
153217
153907
  };
153218
- const removeMainQueue = (index) => {
153219
- if (index < 0 || index >= mainQueue.length)
153220
- return;
153221
- const removedItem = mainQueue[index];
153222
- if (removedItem?.source === TG_SOURCE) {
153223
- remoteBridge?.queuedLineRemoved(removedItem.question);
153224
- }
153225
- mainQueue = removeMainQueueItem(mainQueue, index);
153226
- paintMainQueue();
153227
- };
153228
- const editMainQueue = (index) => {
153229
- const edited = editMainQueueItem(mainQueue, index);
153230
- if (edited === undefined)
153231
- return;
153232
- mainQueue = edited.rest;
153233
- pendingQueueEdit = pendingQueueEditFor(edited.removed, index);
153234
- input.value = edited.text;
153235
- input.focus();
153236
- paintMainQueue();
153237
- };
153908
+ const removeMainQueue = (index) => remoteQueue.removeMainQueue(index);
153909
+ const editMainQueue = (index) => remoteQueue.editMainQueue(index);
153238
153910
  const forceMainQueue = (index) => {
153239
153911
  const item = mainQueue[index];
153240
153912
  if (item === undefined)
@@ -153249,7 +153921,7 @@ ${formatThemeList(getThemeId())}`);
153249
153921
  forceForegroundQueueItem(foregroundOperation, forceHandoff, item, {
153250
153922
  announce: () => io.onSystem?.(`\u25C7 main turn interrupted \u2014 q${index + 1} will run next.
153251
153923
  `),
153252
- run: (next) => runLine(next.question, "operator", next.source)
153924
+ run: (next) => remoteQueue.runQueued(next)
153253
153925
  });
153254
153926
  };
153255
153927
  const handleQueueNavKey = (key) => {
@@ -153658,11 +154330,7 @@ ${formatThemeList(getThemeId())}`);
153658
154330
  case "not-a-command":
153659
154331
  break;
153660
154332
  }
153661
- if (pendingQueueEdit !== undefined) {
153662
- const edit = pendingQueueEdit;
153663
- pendingQueueEdit = undefined;
153664
- mainQueue = reinsertEditedMainQueueItem(mainQueue, edit, line, displayLine);
153665
- paintMainQueue();
154333
+ if (remoteQueue.requeuePendingEdit(line, displayLine)) {
153666
154334
  return;
153667
154335
  }
153668
154336
  if (leaseView()?.held() === true) {
@@ -153703,7 +154371,7 @@ ${formatThemeList(getThemeId())}`);
153703
154371
  takeForced: () => forceHandoff.takeNext(),
153704
154372
  dispatch: (next) => {
153705
154373
  paintMainQueue();
153706
- runLine(next.question, "operator", next.source);
154374
+ remoteQueue.runQueued(next);
153707
154375
  }
153708
154376
  });
153709
154377
  } else {
@@ -154479,7 +155147,7 @@ ${formatThemeList(getThemeId())}`);
154479
155147
  const next = forceHandoff.takeNext() ?? mainQueue.shift();
154480
155148
  paintMainQueue();
154481
155149
  if (next !== undefined)
154482
- runLine(next.question, "operator", next.source);
155150
+ remoteQueue.runQueued(next);
154483
155151
  }
154484
155152
  }
154485
155153
  });
@@ -154491,7 +155159,7 @@ ${formatThemeList(getThemeId())}`);
154491
155159
  sessions.clear();
154492
155160
  deps.resetSubagentBudget?.();
154493
155161
  const operation = foregroundOperation.begin();
154494
- remoteBridge?.turnStarted(source);
155162
+ remoteQueue.turnStarted(source);
154495
155163
  blockIo.resetReasoningLiveState();
154496
155164
  setMainAgent("running", "waiting");
154497
155165
  startBusy("waiting for model");
@@ -154586,7 +155254,7 @@ ${formatThemeList(getThemeId())}`);
154586
155254
  ...slateSession !== undefined ? { slateSession, pruneArchive: true } : {}
154587
155255
  }).finally(() => {
154588
155256
  foregroundOperation.settle(operation);
154589
- remoteBridge?.turnSettled({ failed: turnFailed || turnSignal.aborted });
155257
+ remoteQueue.turnSettled({ failed: turnFailed, aborted: turnSignal.aborted });
154590
155258
  if (foregroundOperation.isDisposed)
154591
155259
  return;
154592
155260
  refreshRewindSidebar();
@@ -154639,7 +155307,7 @@ ${formatThemeList(getThemeId())}`);
154639
155307
  const next = forceHandoff.takeNext() ?? mainQueue.shift();
154640
155308
  paintMainQueue();
154641
155309
  if (next !== undefined) {
154642
- runLine(next.question, "operator", next.source);
155310
+ remoteQueue.runQueued(next);
154643
155311
  } else {
154644
155312
  busWakeController?.onSettle();
154645
155313
  }
@@ -154683,7 +155351,7 @@ ${formatThemeList(getThemeId())}`);
154683
155351
  const drained = forceHandoff.takeNext() ?? mainQueue.shift();
154684
155352
  paintMainQueue();
154685
155353
  if (drained !== undefined)
154686
- runLine(drained.question, "operator", drained.source);
155354
+ remoteQueue.runQueued(drained);
154687
155355
  }
154688
155356
  });
154689
155357
  const captureShellOutput = async (run) => {
@@ -154795,14 +155463,8 @@ Turn: ${chrome.isBusy() ? "running" : "idle"} \xB7 queue: ${mainQueue.length} wa
154795
155463
  sessionId: () => liveSession.summary.id,
154796
155464
  project: () => sessionCwd,
154797
155465
  isBusy: () => chrome.isBusy() || foregroundOperation.isActive || forceHandoff.isAwaitingSettlement || mainQueue.length > 0 || leaseView()?.held() === true,
154798
- runLine: (text) => runLine(text, "operator", TG_SOURCE),
154799
- enqueue: (text) => {
154800
- const id = `mq${mainQueueSeq++}`;
154801
- mainQueue.push({ id, question: text, displayQuestion: summarizeSubmittedLine(text), source: TG_SOURCE });
154802
- paintMainQueue();
154803
- io.onSystem?.(`\u25C7 a line from Telegram is queued as q${mainQueue.length}.
154804
- `);
154805
- },
155466
+ runLine: (text) => remoteQueue.runTelegramLine(text),
155467
+ enqueue: (text) => remoteQueue.enqueueTelegramLine(text),
154806
155468
  notice: (text) => io.onSystem?.(`${text}
154807
155469
  `),
154808
155470
  cancelTurn: () => foregroundOperation.cancel("remote run time limit"),
@@ -154820,14 +155482,7 @@ Turn: ${chrome.isBusy() ? "running" : "idle"} \xB7 queue: ${mainQueue.length} wa
154820
155482
  liveSession = recordRemoteOff(liveSession);
154821
155483
  } catch {}
154822
155484
  },
154823
- dropQueuedTelegramLines: () => {
154824
- const { kept, dropped } = dropQueuedBySource(mainQueue, TG_SOURCE);
154825
- if (dropped.length === 0)
154826
- return [];
154827
- mainQueue = kept;
154828
- paintMainQueue();
154829
- return dropped.map((item) => item.question);
154830
- },
155485
+ dropQueuedTelegramLines: () => remoteQueue.dropQueuedTelegramLines(),
154831
155486
  runCommand: runRemoteCommand,
154832
155487
  busyRefusal: (line) => {
154833
155488
  const command = findAgentCommand(line, "agent");
@@ -155054,6 +155709,7 @@ var init_tui_shell = __esm(() => {
155054
155709
  init_composer_choice();
155055
155710
  init_filter_list();
155056
155711
  init_shell_chrome();
155712
+ init_remote_queue_wiring();
155057
155713
  init_queue_nav();
155058
155714
  init_subagent_inspector();
155059
155715
  init_subagent_session();
@@ -155829,7 +156485,7 @@ var init_debug_watcher = __esm(() => {
155829
156485
  });
155830
156486
 
155831
156487
  // src/commands/shell.ts
155832
- import { randomUUID as randomUUID40 } from "crypto";
156488
+ import { randomUUID as randomUUID41 } from "crypto";
155833
156489
  import { join as joinPath2 } from "path";
155834
156490
  import * as readline2 from "readline";
155835
156491
  function reportMcpProblems(runtime) {
@@ -158188,14 +158844,14 @@ Example: keryx shell --provider ollama --model llama3.1:latest`);
158188
158844
  `);
158189
158845
  }
158190
158846
  const surface = chooseShellSurface(flags, runtime.isTty ?? process.stdout.isTTY === true);
158191
- const shellHookSessionId = randomUUID40();
158847
+ const shellHookSessionId = randomUUID41();
158192
158848
  let shellHooks;
158193
158849
  const getShellHooks = () => {
158194
158850
  if (shellHooks === undefined) {
158195
158851
  shellHooks = buildShellHookRuntime({
158196
158852
  projectRoot: resolveProjectRoot2(process.cwd()),
158197
158853
  sessionId: shellHookSessionId,
158198
- runId: randomUUID40(),
158854
+ runId: randomUUID41(),
158199
158855
  interactive: true,
158200
158856
  profileId: "monitored-trusted-local",
158201
158857
  ...runtime.cacheDir === undefined ? {} : { configDir: runtime.cacheDir }
@@ -158300,7 +158956,7 @@ Example: keryx shell --provider ollama --model llama3.1:latest`);
158300
158956
  sessionOverride: reasoningSessionOverride,
158301
158957
  globalEffort: loadShellConfig(runtime.cacheDir).reasoningEffort
158302
158958
  }),
158303
- idSeq: () => randomUUID40(),
158959
+ idSeq: () => randomUUID41(),
158304
158960
  askUser: invokeAskUserHost,
158305
158961
  sweepBackgroundJobs: () => jobRegistry.sweepAll(),
158306
158962
  jobRegistry,
@@ -158371,7 +159027,7 @@ Example: keryx shell --provider ollama --model llama3.1:latest`);
158371
159027
  return {
158372
159028
  makeProvider: chatFactory,
158373
159029
  clock: () => new Date().toISOString(),
158374
- idSeq: () => randomUUID40(),
159030
+ idSeq: () => randomUUID41(),
158375
159031
  initial: Object.keys(resolvedModelParams).length > 0 ? { ...sel, modelParams: resolvedModelParams } : sel,
158376
159032
  session: {
158377
159033
  cwd,
@@ -158499,7 +159155,7 @@ Example: keryx shell --provider ollama --model llama3.1:latest`);
158499
159155
  const deps = {
158500
159156
  makeProvider: baseFactory,
158501
159157
  clock: () => new Date().toISOString(),
158502
- idSeq: () => randomUUID40(),
159158
+ idSeq: () => randomUUID41(),
158503
159159
  initial: {
158504
159160
  provider,
158505
159161
  model,
@@ -158580,7 +159236,7 @@ Example: keryx shell --provider ollama --model llama3.1:latest`);
158580
159236
  sessionOverride: reasoningSessionOverride,
158581
159237
  globalEffort: loadShellConfig(runtime.cacheDir).reasoningEffort
158582
159238
  }),
158583
- idSeq: () => randomUUID40(),
159239
+ idSeq: () => randomUUID41(),
158584
159240
  askUser: invokeAskUserHost,
158585
159241
  mcpRuntime: () => mcpRuntime,
158586
159242
  sweepBackgroundJobs: () => jobRegistry.sweepAll(),
@@ -163090,8 +163746,8 @@ var init_sweep = __esm(() => {
163090
163746
  });
163091
163747
 
163092
163748
  // src/retention/auto-sweep.ts
163093
- import { mkdir as mkdir63, readFile as readFile158, rm as rm21, stat as stat30, writeFile as writeFile40 } from "fs/promises";
163094
- import { randomUUID as randomUUID41 } from "crypto";
163749
+ import { mkdir as mkdir63, readFile as readFile158, rm as rm22, stat as stat31, writeFile as writeFile40 } from "fs/promises";
163750
+ import { randomUUID as randomUUID42 } from "crypto";
163095
163751
  import path309 from "path";
163096
163752
  function autoSweepStampPath(cwd) {
163097
163753
  return path309.join(cwd, ".metaproject", "data", "retention", "last-auto-sweep.json");
@@ -163123,7 +163779,7 @@ async function readAutoSweepStamp(cwd) {
163123
163779
  }
163124
163780
  async function claimIsLive(cwd, staleMs) {
163125
163781
  const claimPath = autoSweepClaimPath(cwd);
163126
- const held = await stat30(claimPath).catch(() => null);
163782
+ const held = await stat31(claimPath).catch(() => null);
163127
163783
  if (held === null || Date.now() - held.mtimeMs > staleMs) {
163128
163784
  return false;
163129
163785
  }
@@ -163140,7 +163796,7 @@ async function claimIsLive(cwd, staleMs) {
163140
163796
  }
163141
163797
  }
163142
163798
  async function acquireClaim(claimPath, staleMs) {
163143
- const nonce = `${process.pid}:${randomUUID41()}`;
163799
+ const nonce = `${process.pid}:${randomUUID42()}`;
163144
163800
  for (let attempt = 0;attempt < 2; attempt += 1) {
163145
163801
  try {
163146
163802
  await writeFile40(claimPath, nonce, { flag: "wx", mode: 384 });
@@ -163148,7 +163804,7 @@ async function acquireClaim(claimPath, staleMs) {
163148
163804
  release: async () => {
163149
163805
  try {
163150
163806
  if (await readFile158(claimPath, "utf8") === nonce) {
163151
- await rm21(claimPath, { force: true });
163807
+ await rm22(claimPath, { force: true });
163152
163808
  }
163153
163809
  } catch {}
163154
163810
  }
@@ -163158,9 +163814,9 @@ async function acquireClaim(claimPath, staleMs) {
163158
163814
  if (code !== "EEXIST") {
163159
163815
  return null;
163160
163816
  }
163161
- const held = await stat30(claimPath).catch(() => null);
163817
+ const held = await stat31(claimPath).catch(() => null);
163162
163818
  if (held !== null && Date.now() - held.mtimeMs > staleMs) {
163163
- await rm21(claimPath, { force: true }).catch(() => {
163819
+ await rm22(claimPath, { force: true }).catch(() => {
163164
163820
  return;
163165
163821
  });
163166
163822
  continue;
@@ -163229,7 +163885,7 @@ var init_auto_sweep = __esm(() => {
163229
163885
 
163230
163886
  // src/ctx/artifact-id.ts
163231
163887
  import { randomBytes as randomBytes13 } from "crypto";
163232
- import { mkdir as mkdir64, readFile as readFile159, rm as rm22, writeFile as writeFile41 } from "fs/promises";
163888
+ import { mkdir as mkdir64, readFile as readFile159, rm as rm23, writeFile as writeFile41 } from "fs/promises";
163233
163889
  import path310 from "path";
163234
163890
  function pinnedClock() {
163235
163891
  const pin = process.env.KERYX_CTX_CLOCK_PIN_MS;
@@ -163278,7 +163934,7 @@ async function reserveArtifact(options) {
163278
163934
  continue;
163279
163935
  }
163280
163936
  if (!await claim(summaryPath)) {
163281
- await rm22(rawPath, { force: true });
163937
+ await rm23(rawPath, { force: true });
163282
163938
  continue;
163283
163939
  }
163284
163940
  return { id, rawPath, summaryPath };
@@ -164754,7 +165410,7 @@ function readLiteral(source, start, quote) {
164754
165410
  }
164755
165411
 
164756
165412
  // src/wiki/freshness/queue.ts
164757
- import { readFile as readFile161, rename as rename10, stat as stat31, unlink as unlink3, writeFile as writeFile43 } from "fs/promises";
165413
+ import { readFile as readFile161, rename as rename11, stat as stat32, unlink as unlink3, writeFile as writeFile43 } from "fs/promises";
164758
165414
  import path312 from "path";
164759
165415
  function queuePath(cwd) {
164760
165416
  return path312.join(cwd, ".metaproject", "data", "wiki", QUEUE_FILE);
@@ -164776,7 +165432,7 @@ async function drainQueue(cwd) {
164776
165432
  if (line.trim().length === 0) {
164777
165433
  continue;
164778
165434
  }
164779
- const entry = parseEntry3(line);
165435
+ const entry = parseEntry4(line);
164780
165436
  if (entry === null) {
164781
165437
  corruptLines += 1;
164782
165438
  continue;
@@ -164798,7 +165454,7 @@ async function clearQueue(cwd) {
164798
165454
  }
164799
165455
  }
164800
165456
  }
164801
- function parseEntry3(line) {
165457
+ function parseEntry4(line) {
164802
165458
  try {
164803
165459
  const value = JSON.parse(line);
164804
165460
  if (value.schemaVersion !== 1 || typeof value.rev !== "string" || !/^[0-9a-f]{40}$/.test(value.rev) || typeof value.recordedAt !== "string" || !Array.isArray(value.paths)) {
@@ -169551,7 +170207,7 @@ var init_sync2 = __esm(() => {
169551
170207
  });
169552
170208
 
169553
170209
  // src/gdskills/rule-references.ts
169554
- import { stat as stat32 } from "fs/promises";
170210
+ import { stat as stat33 } from "fs/promises";
169555
170211
  import path328 from "path";
169556
170212
  function ruleReferences(content) {
169557
170213
  const refs = new Set;
@@ -169568,7 +170224,7 @@ function withKeryxDirSpelling(ref) {
169568
170224
  }
169569
170225
  async function isRegularFile(absolute) {
169570
170226
  try {
169571
- return (await stat32(absolute)).isFile();
170227
+ return (await stat33(absolute)).isFile();
169572
170228
  } catch {
169573
170229
  return false;
169574
170230
  }
@@ -169789,7 +170445,7 @@ var init_project_reviewers = __esm(() => {
169789
170445
  });
169790
170446
 
169791
170447
  // src/gdskills/import-skills.ts
169792
- import { readdir as readdir54, readFile as readFile170, stat as stat33 } from "fs/promises";
170448
+ import { readdir as readdir54, readFile as readFile170, stat as stat34 } from "fs/promises";
169793
170449
  import { homedir as homedir14 } from "os";
169794
170450
  import path330 from "path";
169795
170451
  function emptyOnlyError(label) {
@@ -169830,7 +170486,7 @@ async function importProjectSkills(options) {
169830
170486
  addFlagWarnings(imported, sources, existingFlags);
169831
170487
  const rules = [];
169832
170488
  for (const decision of ruleDecisions) {
169833
- rules.push("row" in decision ? decision.row : options.dryRun ? unwrittenRow(decision.placement, [decision.dryRunReason, ...gateNotes(decision.gate, true)].filter(isString3).join(" \u2014 ") || undefined, decision.gate.security) : await writePlacedRule(options, decision.placement, decision.gate));
170489
+ rules.push("row" in decision ? decision.row : options.dryRun ? unwrittenRow(decision.placement, [decision.dryRunReason, ...gateNotes(decision.gate, true)].filter(isString4).join(" \u2014 ") || undefined, decision.gate.security) : await writePlacedRule(options, decision.placement, decision.gate));
169834
170490
  }
169835
170491
  return {
169836
170492
  from: options.from,
@@ -170150,7 +170806,7 @@ async function resolveImportSources(options) {
170150
170806
  if (!await pathExists(resolved)) {
170151
170807
  throw new Error(`keryx skills import: --from ${options.from} does not exist (resolved to ${resolved})`);
170152
170808
  }
170153
- const stats = await stat33(resolved);
170809
+ const stats = await stat34(resolved);
170154
170810
  if (stats.isFile()) {
170155
170811
  return [await sourceFromFile(options, resolved)];
170156
170812
  }
@@ -170351,7 +171007,7 @@ function gateRowParts(gate, dryRun) {
170351
171007
  return security;
170352
171008
  }
170353
171009
  function gateNotes(gate, dryRun) {
170354
- return [gateRowParts(gate, dryRun).reason].filter(isString3);
171010
+ return [gateRowParts(gate, dryRun).reason].filter(isString4);
170355
171011
  }
170356
171012
  function plannedSkillText(content, header) {
170357
171013
  const version = parseSkillFrontmatter(content).metadataVersion ?? "0.1.0";
@@ -170967,7 +171623,7 @@ Examples:
170967
171623
  var BUNDLED_NAMES, PATHS_NONE_WARNING = 'paths: none \u2014 dispatched on every round. Declare `metadata.paths: "<glob>, <glob>"` in its frontmatter to gate it on the diff.', DEFAULT_FETCHER = async (url) => {
170968
171624
  const response = await fetch(url);
170969
171625
  return { ok: response.ok, status: response.status, text: await response.text() };
170970
- }, DEPRECATED_REASON = "deprecated", KNOWN_CATEGORIES, isString3 = (value) => value !== undefined, REFUSED_NOTE = "A refused file was not written.", ALLOW_FLAGGED_HINT = "A prompt-injection refusal can be overridden after you read the file: run the command again with --allow-flagged.", IMPORT_HEADER_LABELS, IMPORT_HEADER_LINE, BYTE_ORDER_MARK = 65279, ALLOW_FLAGGED_FLAG = "--allow-flagged", SECURITY_GATE_HELP = `Security gate: a SKILL.md or rule is written as the gate returns it, so a secret is
171626
+ }, DEPRECATED_REASON = "deprecated", KNOWN_CATEGORIES, isString4 = (value) => value !== undefined, REFUSED_NOTE = "A refused file was not written.", ALLOW_FLAGGED_HINT = "A prompt-injection refusal can be overridden after you read the file: run the command again with --allow-flagged.", IMPORT_HEADER_LABELS, IMPORT_HEADER_LINE, BYTE_ORDER_MARK = 65279, ALLOW_FLAGGED_FLAG = "--allow-flagged", SECURITY_GATE_HELP = `Security gate: a SKILL.md or rule is written as the gate returns it, so a secret is
170971
171627
  masked and the row says it was redacted, and any other finding is reported on the
170972
171628
  row. Text with a prompt-injection finding is refused in every security mode: the
170973
171629
  row is \`refused\` (\`would-refuse\` on a dry run) with the gate's own summary,
@@ -181155,12 +181811,29 @@ var init_command_registry = __esm(() => {
181155
181811
  {
181156
181812
  module: "decisions",
181157
181813
  command: "decisions report",
181158
- summary: "Match share between the human's choice and the agent's recommendation, by mode (ordinary, blind) and by stage, " + "with every deviation and its reason. Deterministic, no model.",
181814
+ summary: "Match share between the human's choice and the agent's recommendation, by mode (ordinary, blind) and by stage, " + "with every deviation and its reason. Backfilled (imported, historical) decisions are reported in a block of their own and never counted in the live shares or the time to answer. Deterministic, no model.",
181159
181815
  intent: ["decisions report", "\u043E\u0442\u0447\u0451\u0442 \u043F\u043E \u0440\u0435\u043A\u043E\u043C\u0435\u043D\u0434\u0430\u0446\u0438\u044F\u043C", "how often does the human follow the recommendation", "blind mode results"],
181160
- args: [{ name: "json", type: "bool", required: false, desc: "print the report as JSON" }],
181816
+ args: [
181817
+ { name: "json", type: "bool", required: false, desc: "print the report as JSON (with a `backfilled` section)" },
181818
+ { name: "line", type: "bool", required: false, desc: "print ONE line in Russian for the daily topic message: the total and the match share of the visible, hidden and historical decisions" }
181819
+ ],
181161
181820
  json: true,
181162
181821
  read: true
181163
181822
  },
181823
+ {
181824
+ module: "decisions",
181825
+ command: "decisions import",
181826
+ summary: 'Import historical decisions from a JSON-lines file as backfilled records: the "before" arm of a comparison with the live journal. ' + "Each is marked backfilled, is never blind, keeps the time it was asked, and is reported apart from the live ones. " + "A line that is not a decision is skipped, named with its line number and counted as malformed, and the rest is imported; an id already in the journal is skipped, so a second import changes nothing (except that a backfilled decision missing its answer gets just that answer).",
181827
+ intent: ["decisions import", "import historical decisions", "backfill the recommendation journal", "\u0438\u043C\u043F\u043E\u0440\u0442 \u0438\u0441\u0442\u043E\u0440\u0438\u0447\u0435\u0441\u043A\u0438\u0445 \u0440\u0435\u0448\u0435\u043D\u0438\u0439", "\u0434\u043E\u0437\u0430\u043F\u043E\u043B\u043D\u0438\u0442\u044C \u0436\u0443\u0440\u043D\u0430\u043B \u0440\u0435\u0448\u0435\u043D\u0438\u0439"],
181828
+ args: [
181829
+ { name: "<file>", type: "string", required: true, desc: "a JSON-lines file, one decision per line: id, at, flow, stage, question, options, recommendation, source, answer, reason" },
181830
+ { name: "dry-run", type: "bool", required: false, desc: "check and count without writing" },
181831
+ { name: "json", type: "bool", required: false, desc: 'print the counts as JSON (a failure is {"error": "..."})' }
181832
+ ],
181833
+ json: true,
181834
+ read: false,
181835
+ sideEffects: ["appends open, answer and reason records to .metaproject/data/decisions/journal.jsonl (nothing with --dry-run)"]
181836
+ },
181164
181837
  {
181165
181838
  module: "product",
181166
181839
  command: "product index",
@@ -182014,7 +182687,7 @@ function isPassGate(gate) {
182014
182687
 
182015
182688
  // src/commands/security-audit-harness.ts
182016
182689
  import path347 from "path";
182017
- import { stat as stat34 } from "fs/promises";
182690
+ import { stat as stat35 } from "fs/promises";
182018
182691
  function isSeverity(value) {
182019
182692
  return value !== undefined && SEVERITIES3.includes(value);
182020
182693
  }
@@ -182059,7 +182732,7 @@ async function handleAuditHarness(cwd, args = []) {
182059
182732
  }
182060
182733
  async function handleRun(cwd, args) {
182061
182734
  const root = resolveRoot(cwd, args);
182062
- const rootStat = await stat34(root).catch(() => {
182735
+ const rootStat = await stat35(root).catch(() => {
182063
182736
  return;
182064
182737
  });
182065
182738
  if (!rootStat || !rootStat.isDirectory()) {
@@ -182146,7 +182819,7 @@ async function handleApply(cwd, args) {
182146
182819
  process.exitCode = 1;
182147
182820
  return;
182148
182821
  }
182149
- const rootStat = await stat34(root).catch(() => {
182822
+ const rootStat = await stat35(root).catch(() => {
182150
182823
  return;
182151
182824
  });
182152
182825
  if (!rootStat || !rootStat.isDirectory()) {
@@ -184193,7 +184866,7 @@ var init_metaproject_tools2 = __esm(() => {
184193
184866
  // src/mcp/tools.ts
184194
184867
  import { readFile as readFile185, realpath as realpath22, writeFile as writeFile49 } from "fs/promises";
184195
184868
  import path351 from "path";
184196
- import { randomUUID as randomUUID42 } from "crypto";
184869
+ import { randomUUID as randomUUID43 } from "crypto";
184197
184870
  function sacWorkspaceService(cwd) {
184198
184871
  return new WorkspaceService({
184199
184872
  workspaceRoot: cwd,
@@ -184279,7 +184952,7 @@ function buildToolRegistry() {
184279
184952
  { name: "sac.collaboration", module: "sac", description: "Read the collaboration references and activity for one Shared Agent Context workspace \u2014 who is participating and what has moved recently, redacted to what this actor may see. Local stdio only: over HTTP it returns `sac_transport_denied` without reading anything, because HTTP has no verified principal and must not inherit the local OS actor. Returns references and activity, not the items themselves \u2014 use sac.overview to enumerate items and sac.read to fetch one. Discover the workspaceId with sac.workspaceList.", inputSchema: OBJECT_SCHEMA({ workspaceId: { type: "string" } }, ["workspaceId"]), mutating: false, async invoke(cwd, params, context) {
184280
184953
  if (context?.transport === "http")
184281
184954
  return { code: "sac_transport_denied" };
184282
- return normalizeCollaborationResult(await createLocalCollaborationService(cwd).overview({ workspaceId: stringParam2(params, "workspaceId") ?? "", request: undefined, requestCorrelationId: randomUUID42() }));
184955
+ return normalizeCollaborationResult(await createLocalCollaborationService(cwd).overview({ workspaceId: stringParam2(params, "workspaceId") ?? "", request: undefined, requestCorrelationId: randomUUID43() }));
184283
184956
  } },
184284
184957
  {
184285
184958
  name: "sac.overview",
@@ -184293,7 +184966,7 @@ function buildToolRegistry() {
184293
184966
  const workspaceId = stringParam2(params, "workspaceId") ?? "";
184294
184967
  const maxItems = typeof params.maxItems === "number" ? params.maxItems : 32;
184295
184968
  const maxTokens = typeof params.maxTokens === "number" ? params.maxTokens : 4096;
184296
- return normalizeFwkResult(await createLocalFwkReadService(cwd).overview({ workspaceId, request: undefined, requestCorrelationId: randomUUID42(), budget: { maxItems, maxTokens } }));
184969
+ return normalizeFwkResult(await createLocalFwkReadService(cwd).overview({ workspaceId, request: undefined, requestCorrelationId: randomUUID43(), budget: { maxItems, maxTokens } }));
184297
184970
  }
184298
184971
  },
184299
184972
  {
@@ -184309,7 +184982,7 @@ function buildToolRegistry() {
184309
184982
  const itemId = stringParam2(params, "itemId") ?? "";
184310
184983
  const maxItems = typeof params.maxItems === "number" ? params.maxItems : 1;
184311
184984
  const maxTokens = typeof params.maxTokens === "number" ? params.maxTokens : 4096;
184312
- return normalizeFwkResult(await createLocalFwkReadService(cwd).read({ workspaceId, itemId, request: undefined, requestCorrelationId: randomUUID42(), budget: { maxItems, maxTokens } }));
184985
+ return normalizeFwkResult(await createLocalFwkReadService(cwd).read({ workspaceId, itemId, request: undefined, requestCorrelationId: randomUUID43(), budget: { maxItems, maxTokens } }));
184313
184986
  }
184314
184987
  },
184315
184988
  {
@@ -184330,12 +185003,12 @@ function buildToolRegistry() {
184330
185003
  if (!session)
184331
185004
  throw new Error(`no session matching "${sessionRef}" in this project`);
184332
185005
  const { service, wrapUpAuthority, authorizationServer } = createHarnessProposalLifecycleService(cwd, { workspaceId, ...note ? { note } : {} });
184333
- const requestCorrelationId = randomUUID42();
185006
+ const requestCorrelationId = randomUUID43();
184334
185007
  const actor = await authorizationServer.actorContextFor(undefined, requestCorrelationId);
184335
185008
  if (!actor)
184336
185009
  throw new Error("trusted ActorContext is required");
184337
185010
  const wrapUp = await wrapUpAuthority.issue({ actor, source: "session", sourceRef: sessionEvidenceRef(workspaceId, session.id) });
184338
- const proposal = await service.create({ request: undefined, requestCorrelationId, workspaceId, id: `proposal-${randomUUID42().replace(/-/g, "").slice(0, 16)}`, proposalRevision, kind, wrapUp });
185011
+ const proposal = await service.create({ request: undefined, requestCorrelationId, workspaceId, id: `proposal-${randomUUID43().replace(/-/g, "").slice(0, 16)}`, proposalRevision, kind, wrapUp });
184339
185012
  if (note)
184340
185013
  await writeFile49(proposalNotePath(cwd, workspaceId, proposal.id), note, "utf8");
184341
185014
  return normalizeProposalLifecycleResult(proposal);
@@ -184354,9 +185027,9 @@ function buildToolRegistry() {
184354
185027
  const proposalId = stringParam2(params, "proposalId") ?? "";
184355
185028
  const decision = stringParam2(params, "decision");
184356
185029
  const reason = stringParam2(params, "reason");
184357
- const idempotencyKey = stringParam2(params, "idempotencyKey") ?? randomUUID42();
185030
+ const idempotencyKey = stringParam2(params, "idempotencyKey") ?? randomUUID43();
184358
185031
  const confirmToken = stringParam2(params, "confirmToken");
184359
- const result = await createHarnessProposalLifecycleService(cwd, { workspaceId }).service.review({ request: undefined, requestCorrelationId: randomUUID42(), workspaceId, proposalId, decision, idempotencyKey, interactive: true, ...reason ? { reason } : {}, ...confirmToken ? { confirmToken } : {} });
185032
+ const result = await createHarnessProposalLifecycleService(cwd, { workspaceId }).service.review({ request: undefined, requestCorrelationId: randomUUID43(), workspaceId, proposalId, decision, idempotencyKey, interactive: true, ...reason ? { reason } : {}, ...confirmToken ? { confirmToken } : {} });
184360
185033
  return normalizeProposalLifecycleResult(result);
184361
185034
  }
184362
185035
  },
@@ -184404,7 +185077,7 @@ function buildToolRegistry() {
184404
185077
  throw new Error("sac.workspaceCreate requires a non-empty 'title'");
184405
185078
  const rawComponent = stringParam2(params, "component");
184406
185079
  const component = rawComponent === undefined ? undefined : requireWorkspaceReference(rawComponent);
184407
- const workspace = await sacWorkspaceService(cwd).create({ request: undefined, requestCorrelationId: randomUUID42(), id: newWorkspaceId(), title, ...component ? { component: { kind: "component", uri: component } } : {} });
185080
+ const workspace = await sacWorkspaceService(cwd).create({ request: undefined, requestCorrelationId: randomUUID43(), id: newWorkspaceId(), title, ...component ? { component: { kind: "component", uri: component } } : {} });
184408
185081
  return workspace;
184409
185082
  }
184410
185083
  },
@@ -184465,7 +185138,7 @@ function buildToolRegistry() {
184465
185138
  const text = redactSensitiveText(rawText);
184466
185139
  const ts = new Date().toISOString();
184467
185140
  const seed = {
184468
- id: `seed-${randomUUID42()}`,
185141
+ id: `seed-${randomUUID43()}`,
184469
185142
  text,
184470
185143
  ts,
184471
185144
  ...kind ? { kind } : {},
@@ -184911,7 +185584,7 @@ var init_tools3 = __esm(() => {
184911
185584
 
184912
185585
  // src/mcp/resources.ts
184913
185586
  import path352 from "path";
184914
- import { lstat as lstat19, readdir as readdir63, realpath as realpath23, stat as stat35 } from "fs/promises";
185587
+ import { lstat as lstat19, readdir as readdir63, realpath as realpath23, stat as stat36 } from "fs/promises";
184915
185588
  function mimeForPath(filePath) {
184916
185589
  if (filePath.endsWith(".json") || filePath.endsWith(".jsonl")) {
184917
185590
  return "application/json";
@@ -184954,7 +185627,7 @@ async function walkFiles(root, ownerRoot = root, ancestors = new Set) {
184954
185627
  }
184955
185628
  for (const entry of entries) {
184956
185629
  const full = path352.join(root, entry.name);
184957
- const info = await stat35(full).catch(() => null);
185630
+ const info = await stat36(full).catch(() => null);
184958
185631
  if (!info || !isPathInside(ownerReal, await realpath23(full).catch(() => "")))
184959
185632
  continue;
184960
185633
  if (info.isDirectory()) {
@@ -185075,7 +185748,7 @@ async function readResource(cwd, roots, uri, harnessIdentity = null) {
185075
185748
  throw new Error(`Resource not found: ${uri}`);
185076
185749
  }
185077
185750
  }
185078
- const info = await stat35(resolved.absolute).catch(() => null);
185751
+ const info = await stat36(resolved.absolute).catch(() => null);
185079
185752
  if (!info || !info.isFile()) {
185080
185753
  throw new Error(`Resource not found: ${uri}`);
185081
185754
  }
@@ -186593,7 +187266,7 @@ var init_service18 = __esm(() => {
186593
187266
 
186594
187267
  // src/commands/stack.ts
186595
187268
  import path356 from "path";
186596
- import { stat as stat36 } from "fs/promises";
187269
+ import { stat as stat37 } from "fs/promises";
186597
187270
  async function stackCommand(args, cwd = process.cwd()) {
186598
187271
  const sub = args[0];
186599
187272
  if (sub === undefined || sub === "--help" || sub === "-h") {
@@ -186625,7 +187298,7 @@ async function handleDetect(args, cwd) {
186625
187298
  const write = !args.includes("--no-write");
186626
187299
  let rootStat;
186627
187300
  try {
186628
- rootStat = await stat36(root);
187301
+ rootStat = await stat37(root);
186629
187302
  } catch {
186630
187303
  console.error(`--cwd directory does not exist: ${root}`);
186631
187304
  process.exitCode = 1;
@@ -188125,7 +188798,7 @@ function checkApproval(input) {
188125
188798
  // src/commands/harness.ts
188126
188799
  import { readFileSync as readFileSync28, writeFileSync as writeFileSync10 } from "fs";
188127
188800
  import path357 from "path";
188128
- import { randomUUID as randomUUID43 } from "crypto";
188801
+ import { randomUUID as randomUUID44 } from "crypto";
188129
188802
  import { realpathSync as realpathSync18 } from "fs";
188130
188803
  import { homedir as homedir15, tmpdir as tmpdir14 } from "os";
188131
188804
  function canonicalPath2(p) {
@@ -188152,7 +188825,7 @@ function resolveRuntime(deps) {
188152
188825
  const env = deps?.env ?? process.env;
188153
188826
  const clock = deps?.clock ?? (() => new Date().toISOString());
188154
188827
  let idCounter = 0;
188155
- const idSeq = deps?.idSeq ?? (() => `${randomUUID43()}-${idCounter++}`);
188828
+ const idSeq = deps?.idSeq ?? (() => `${randomUUID44()}-${idCounter++}`);
188156
188829
  return { env, clock, idSeq };
188157
188830
  }
188158
188831
  function shellAllowProfile() {
@@ -188256,7 +188929,7 @@ async function harnessCommand(args, deps) {
188256
188929
  const env = deps?.env ?? process.env;
188257
188930
  const clock = deps?.clock ?? (() => new Date().toISOString());
188258
188931
  let idCounter = 0;
188259
- const idSeq = deps?.idSeq ?? (() => `${randomUUID43()}-${idCounter++}`);
188932
+ const idSeq = deps?.idSeq ?? (() => `${randomUUID44()}-${idCounter++}`);
188260
188933
  const fetchImpl = deps?.fetch ?? globalThis.fetch;
188261
188934
  if (provider === "anthropic") {
188262
188935
  const apiKey = env.ANTHROPIC_API_KEY;
@@ -188397,7 +189070,7 @@ function harnessReplay(args, deps) {
188397
189070
  }
188398
189071
  const clock = deps?.clock ?? (() => new Date().toISOString());
188399
189072
  let idCounter = 0;
188400
- const idSeq = deps?.idSeq ?? (() => `${randomUUID43()}-${idCounter++}`);
189073
+ const idSeq = deps?.idSeq ?? (() => `${randomUUID44()}-${idCounter++}`);
188401
189074
  const fixture = (() => {
188402
189075
  if (parsed.fixture === undefined || parsed.fixture.length === 0) {
188403
189076
  return { ok: true, value: buildReplayFixture(run, { idSeq }), built: true };
@@ -189112,7 +189785,7 @@ var init_permission = __esm(() => {
189112
189785
  });
189113
189786
 
189114
189787
  // src/acp/agent-io.ts
189115
- import { randomUUID as randomUUID44 } from "crypto";
189788
+ import { randomUUID as randomUUID45 } from "crypto";
189116
189789
  function toolKindFor(name) {
189117
189790
  return TOOL_KIND_BY_NAME[name] ?? "other";
189118
189791
  }
@@ -189127,7 +189800,7 @@ function createAcpAgentIo(sessionId, send, askPermission) {
189127
189800
  const pendingByName = new Map;
189128
189801
  const emit = (update) => send(sessionId, update);
189129
189802
  const openToolCall = (name, input, claimed) => {
189130
- const toolCallId = randomUUID44();
189803
+ const toolCallId = randomUUID45();
189131
189804
  const queue = pendingByName.get(name) ?? [];
189132
189805
  queue.push({ toolCallId, input, claimed });
189133
189806
  pendingByName.set(name, queue);
@@ -189214,7 +189887,7 @@ function createAcpAgentIo(sessionId, send, askPermission) {
189214
189887
  const toolCallId = pending?.toolCallId;
189215
189888
  emit({
189216
189889
  sessionUpdate: "tool_call_update",
189217
- toolCallId: toolCallId ?? randomUUID44(),
189890
+ toolCallId: toolCallId ?? randomUUID45(),
189218
189891
  status: result.isError ? "failed" : "completed",
189219
189892
  content: [{ type: "content", content: textBlock(result.output) }],
189220
189893
  rawOutput: result.output
@@ -189813,7 +190486,7 @@ var init_session_mcp = __esm(() => {
189813
190486
  });
189814
190487
 
189815
190488
  // src/acp/server.ts
189816
- import { randomUUID as randomUUID45 } from "crypto";
190489
+ import { randomUUID as randomUUID46 } from "crypto";
189817
190490
  function finishReasonToStopReason(finishReason) {
189818
190491
  switch (finishReason) {
189819
190492
  case "budget":
@@ -189833,7 +190506,7 @@ function refuse5(error) {
189833
190506
  async function runAcpServer(options) {
189834
190507
  let initialized = false;
189835
190508
  let clientCapabilities;
189836
- const idSeq = options.idSeq ?? (() => randomUUID45());
190509
+ const idSeq = options.idSeq ?? (() => randomUUID46());
189837
190510
  const registry = new AcpSessionRegistry({
189838
190511
  providerId: options.providerId,
189839
190512
  modelId: options.modelId,
@@ -190476,7 +191149,7 @@ Switch with /model <model>; it applies from the next turn.`;
190476
191149
  }
190477
191150
  continue;
190478
191151
  }
190479
- const toolCallId = message.toolCallId ?? randomUUID45();
191152
+ const toolCallId = message.toolCallId ?? randomUUID46();
190480
191153
  sendUpdate(sessionId, {
190481
191154
  sessionUpdate: "tool_call_update",
190482
191155
  toolCallId,
@@ -190715,7 +191388,7 @@ var MODEL_PLACEHOLDER = "{{model}}";
190715
191388
  var init_fixture_provider = () => {};
190716
191389
 
190717
191390
  // src/commands/acp.ts
190718
- import { randomUUID as randomUUID46 } from "crypto";
191391
+ import { randomUUID as randomUUID47 } from "crypto";
190719
191392
  function parseAcpArgs(args) {
190720
191393
  const parsed = {};
190721
191394
  for (let i = 0;i < args.length; i += 1) {
@@ -190969,7 +191642,7 @@ async function acpCommand(args) {
190969
191642
  } : { providerUnavailable: resolution.message, providerId: "", modelId: "" },
190970
191643
  agentInfo: { name: "keryx", version: package_default.version },
190971
191644
  ...parsed.dataDir !== undefined ? { dataDir: parsed.dataDir } : {},
190972
- idSeq: () => randomUUID46()
191645
+ idSeq: () => randomUUID47()
190973
191646
  });
190974
191647
  } finally {
190975
191648
  process.off("SIGTERM", onSignal);
@@ -191036,7 +191709,7 @@ var init_acp = __esm(() => {
191036
191709
  });
191037
191710
 
191038
191711
  // src/bus/prune.ts
191039
- import { randomUUID as randomUUID47 } from "crypto";
191712
+ import { randomUUID as randomUUID48 } from "crypto";
191040
191713
  async function expiryAlreadyLogged(root, leaseId) {
191041
191714
  const { events } = await readEvents(root, await cursorAtStart(root));
191042
191715
  return events.some((event) => event.kind === "lease-expired" && event.refs?.leaseId === leaseId);
@@ -191070,7 +191743,7 @@ async function pruneBus(root, options = {}) {
191070
191743
  const file = leasePath(root, lease.leaseId);
191071
191744
  try {
191072
191745
  await appendEvent(root, {
191073
- from: { instanceId: randomUUID47(), name: "system", origin: "system" },
191746
+ from: { instanceId: randomUUID48(), name: "system", origin: "system" },
191074
191747
  to: ["*"],
191075
191748
  toLabel: "@all",
191076
191749
  kind: "lease-expired",
@@ -191123,7 +191796,7 @@ var init_prune3 = __esm(() => {
191123
191796
  });
191124
191797
 
191125
191798
  // src/commands/bus.ts
191126
- import { randomUUID as randomUUID48 } from "crypto";
191799
+ import { randomUUID as randomUUID49 } from "crypto";
191127
191800
  async function busCommand(args) {
191128
191801
  const code = await runBusCommand(args);
191129
191802
  if (code !== 0)
@@ -191276,7 +191949,7 @@ ${PAUSE_USAGE3}`);
191276
191949
  if (toLabel === undefined || reason === undefined || reason.length === 0) {
191277
191950
  throw new BusRefusal("invalid-event", PAUSE_USAGE3);
191278
191951
  }
191279
- const holder = { instanceId: randomUUID48(), name: "cli", origin: "cli" };
191952
+ const holder = { instanceId: randomUUID49(), name: "cli", origin: "cli" };
191280
191953
  const lease = await createPauseLease(busRoot, {
191281
191954
  holder,
191282
191955
  toLabel,
@@ -191298,7 +191971,7 @@ async function resume(args, busRoot, ctx) {
191298
191971
  if (leaseId === undefined) {
191299
191972
  throw new BusRefusal("invalid-event", "usage: keryx bus resume <leaseId> [--json]");
191300
191973
  }
191301
- await resumePauseLease(busRoot, { leaseId, by: { instanceId: randomUUID48(), name: "cli", origin: "cli" }, now: ctx.now });
191974
+ await resumePauseLease(busRoot, { leaseId, by: { instanceId: randomUUID49(), name: "cli", origin: "cli" }, now: ctx.now });
191302
191975
  if (args.includes("--json")) {
191303
191976
  ctx.out(JSON.stringify({ schemaVersion: 1, leaseId }, null, 2));
191304
191977
  } else {
@@ -191602,7 +192275,7 @@ var init_modules = __esm(() => {
191602
192275
  });
191603
192276
 
191604
192277
  // src/lib/serve-approvals-broker.ts
191605
- import { randomUUID as randomUUID49 } from "crypto";
192278
+ import { randomUUID as randomUUID50 } from "crypto";
191606
192279
  function describeAsk(ask) {
191607
192280
  const floors = ask.floors.length > 0 ? ` Needs a fresh answer every time (${ask.floors.join(", ")}).` : "";
191608
192281
  return {
@@ -191658,7 +192331,7 @@ function createApprovalBroker(options) {
191658
192331
  sessionId: ask.sessionId,
191659
192332
  ...text,
191660
192333
  expiresAt: new Date(createdAt.getTime() + options.expirySeconds * 1000),
191661
- correlationId: randomUUID49(),
192334
+ correlationId: randomUUID50(),
191662
192335
  callFingerprint: ask.callFingerprint,
191663
192336
  floors: ask.floors
191664
192337
  }, options.dir, createdAt);
@@ -191800,7 +192473,7 @@ var init_serve_throttle = __esm(() => {
191800
192473
  });
191801
192474
 
191802
192475
  // src/lib/serve-turn.ts
191803
- import { randomUUID as randomUUID50 } from "crypto";
192476
+ import { randomUUID as randomUUID51 } from "crypto";
191804
192477
  import path359 from "path";
191805
192478
  function validateTurnRequest(body) {
191806
192479
  const problem = (message) => ({
@@ -191975,7 +192648,7 @@ async function redactOut(security, text) {
191975
192648
  async function runRemoteTurn(input) {
191976
192649
  const scanRoot = input.scanRoot;
191977
192650
  const security = createSecurityService(scanRoot);
191978
- const newId = input.newId ?? (() => randomUUID50());
192651
+ const newId = input.newId ?? (() => randomUUID51());
191979
192652
  const clock = input.clock ?? (() => new Date().toISOString());
191980
192653
  const turnId = input.turnId ?? newId();
191981
192654
  const sessionId = input.request.sessionId ?? newId();
@@ -192158,7 +192831,7 @@ function outcomeOf(status, gate, unresolvedBlockerIds) {
192158
192831
  }
192159
192832
  function createSubmitTurn(deps) {
192160
192833
  return async (request, project) => {
192161
- const turnId = (deps.newId ?? (() => randomUUID50()))();
192834
+ const turnId = (deps.newId ?? (() => randomUUID51()))();
192162
192835
  const scanned = await scanPrompt(deps.dir, request.prompt);
192163
192836
  if (scanned.rejected) {
192164
192837
  return { kind: "rejected" };
@@ -192173,7 +192846,7 @@ function createSubmitTurn(deps) {
192173
192846
  }
192174
192847
  let effected = false;
192175
192848
  const detached = deps.approvals !== undefined && (deps.toolRegistry?.list().length ?? 0) > 0;
192176
- const sessionId = request.sessionId ?? (deps.newId ?? (() => randomUUID50()))();
192849
+ const sessionId = request.sessionId ?? (deps.newId ?? (() => randomUUID51()))();
192177
192850
  let onStarted = () => {
192178
192851
  return;
192179
192852
  };
@@ -192434,7 +193107,7 @@ function listApprovalsRoute(request, ctx) {
192434
193107
  });
192435
193108
  }
192436
193109
  async function answerApprovalRoute(request, approvalId, ctx) {
192437
- if (!isApprovalId(approvalId)) {
193110
+ if (!isApprovalId2(approvalId)) {
192438
193111
  return approvalNotFound();
192439
193112
  }
192440
193113
  const declared = Number(request.headers.get("content-length") ?? "0");
@@ -192701,6 +193374,7 @@ var init_serve_server = __esm(() => {
192701
193374
  ["prompt", "POST"],
192702
193375
  ["state", "POST"],
192703
193376
  ["ack", "POST"],
193377
+ ["approval-ack", "POST"],
192704
193378
  ["stream", "GET"],
192705
193379
  ["channels-status", "GET"],
192706
193380
  ["channels-pair", "POST"],
@@ -194059,7 +194733,7 @@ var init_durable_log = __esm(() => {
194059
194733
  import { createHash as createHash80 } from "crypto";
194060
194734
  import { unlinkSync as unlinkSync9 } from "fs";
194061
194735
  import path361 from "path";
194062
- function parseEntry4(value) {
194736
+ function parseEntry5(value) {
194063
194737
  if (typeof value !== "object" || value === null) {
194064
194738
  return;
194065
194739
  }
@@ -194095,7 +194769,7 @@ class InboundQueues {
194095
194769
  log(bindingKey) {
194096
194770
  let log = this.logs.get(bindingKey);
194097
194771
  if (log === undefined) {
194098
- log = new DurableLog({ file: this.fileFor(bindingKey), parse: parseEntry4, maxEntries: MAX_INBOUND_PER_TOPIC });
194772
+ log = new DurableLog({ file: this.fileFor(bindingKey), parse: parseEntry5, maxEntries: MAX_INBOUND_PER_TOPIC });
194099
194773
  log.load();
194100
194774
  this.logs.set(bindingKey, log);
194101
194775
  }
@@ -194736,9 +195410,9 @@ var init_rendering = __esm(() => {
194736
195410
  });
194737
195411
 
194738
195412
  // src/remote/outbound-queue.ts
194739
- import { randomUUID as randomUUID51 } from "crypto";
195413
+ import { randomUUID as randomUUID52 } from "crypto";
194740
195414
  import path363 from "path";
194741
- function parseEntry5(value) {
195415
+ function parseEntry6(value) {
194742
195416
  if (typeof value !== "object" || value === null) {
194743
195417
  return;
194744
195418
  }
@@ -194781,7 +195455,7 @@ class OutboundQueue {
194781
195455
  this.log = new DurableLog({
194782
195456
  file: path363.join(directory, OUTBOUND_FILE),
194783
195457
  maxEntries: options.maxEntries ?? OUTBOUND_MAX_ENTRIES,
194784
- parse: parseEntry5
195458
+ parse: parseEntry6
194785
195459
  });
194786
195460
  }
194787
195461
  load() {
@@ -194798,7 +195472,7 @@ class OutboundQueue {
194798
195472
  const entries = [];
194799
195473
  parts.forEach((text, index) => {
194800
195474
  const entry = {
194801
- id: randomUUID51(),
195475
+ id: randomUUID52(),
194802
195476
  chatId: message.chatId,
194803
195477
  ...message.threadId === undefined ? {} : { threadId: message.threadId },
194804
195478
  text,
@@ -196188,6 +196862,7 @@ class RemoteHttpSurface {
196188
196862
  timers;
196189
196863
  keepaliveMs;
196190
196864
  ackTimeoutMs;
196865
+ approvalAckMs;
196191
196866
  newApprovalId;
196192
196867
  newPromptId;
196193
196868
  streams = new Map;
@@ -196196,6 +196871,8 @@ class RemoteHttpSurface {
196196
196871
  bound = new Map;
196197
196872
  pendingAcks = new Map;
196198
196873
  approvals = new Map;
196874
+ awaiting = new Map;
196875
+ decided = new Map;
196199
196876
  choices = new Map;
196200
196877
  finished = new Map;
196201
196878
  constructor(options) {
@@ -196204,6 +196881,7 @@ class RemoteHttpSurface {
196204
196881
  this.timers = options.timers ?? realTimers;
196205
196882
  this.keepaliveMs = options.keepaliveMs ?? DEFAULT_KEEPALIVE_MS;
196206
196883
  this.ackTimeoutMs = options.ackTimeoutMs ?? DEFAULT_ACK_TIMEOUT_MS;
196884
+ this.approvalAckMs = options.approvalAckMs ?? DEFAULT_APPROVAL_ACK_MS;
196207
196885
  this.newApprovalId = options.approvalIds ?? (() => `ap${randomBytes15(6).toString("hex")}`);
196208
196886
  this.newPromptId = options.promptIds ?? (() => `pk${randomBytes15(6).toString("hex")}`);
196209
196887
  this.consumer = {
@@ -196270,6 +196948,8 @@ class RemoteHttpSurface {
196270
196948
  return this.messageState(hub, request);
196271
196949
  case "ack":
196272
196950
  return this.ack(hub, request);
196951
+ case "approval-ack":
196952
+ return this.approvalAck(hub, request);
196273
196953
  case "stream":
196274
196954
  return this.openStream(hub, request, io);
196275
196955
  }
@@ -196288,6 +196968,9 @@ class RemoteHttpSurface {
196288
196968
  for (const id of [...this.approvals.keys()]) {
196289
196969
  this.dropApproval(id);
196290
196970
  }
196971
+ for (const id of [...this.awaiting.keys()]) {
196972
+ this.dropAwaiting(id);
196973
+ }
196291
196974
  for (const id of [...this.choices.keys()]) {
196292
196975
  this.dropChoice(id);
196293
196976
  }
@@ -196611,6 +197294,48 @@ ${asCodeBlock(redactSensitiveText(value.prompt))}`, {
196611
197294
  this.complete(value.sessionId, value.updateId);
196612
197295
  return ok2({ acknowledged: true });
196613
197296
  }
197297
+ async approvalAck(hub, request) {
197298
+ const body = await readJsonBody(request);
197299
+ if (!body.ok) {
197300
+ return body.response;
197301
+ }
197302
+ const value = body.value;
197303
+ if (!isSessionId(value.sessionId)) {
197304
+ return invalid2("sessionId must be 1 to 64 characters of letters, digits, '-' and '_'.");
197305
+ }
197306
+ if (!isApprovalId(value.approvalId)) {
197307
+ return invalid2("approvalId must be 'ap' and 12 lowercase hex characters.");
197308
+ }
197309
+ if (!hub.hasSession(value.sessionId)) {
197310
+ return fail4(404, "unknown-session", "This session is not registered for remote control; register again.");
197311
+ }
197312
+ if (value.applied !== undefined && typeof value.applied !== "boolean") {
197313
+ return invalid2("applied must be true or false when present.");
197314
+ }
197315
+ const sessionId = value.sessionId;
197316
+ const id = value.approvalId;
197317
+ const waiting = this.awaiting.get(id);
197318
+ if (waiting !== undefined && waiting.sessionId === sessionId) {
197319
+ this.dropAwaiting(id);
197320
+ this.rememberDecided(id, sessionId);
197321
+ if (value.applied === false) {
197322
+ this.finishApproval(id, sessionId, waiting.messageId, waiting.text, {
197323
+ result: `Not applied at ${waiting.sentAt}: the shell was no longer waiting for this question, so the ${waiting.decision === "allow" ? "Allow" : "Deny"} from ${waiting.who} changed nothing.`,
197324
+ shortReply: "Not applied: the shell was no longer waiting for this question."
197325
+ });
197326
+ return ok2({ acknowledged: true });
197327
+ }
197328
+ this.finishApproval(id, sessionId, waiting.messageId, waiting.text, {
197329
+ result: waiting.decision === "allow" ? `Allowed by ${waiting.who} at ${waiting.sentAt}.` : `Denied by ${waiting.who} at ${waiting.sentAt}.`,
197330
+ shortReply: waiting.decision === "allow" ? "Approval granted." : "Approval denied."
197331
+ });
197332
+ return ok2({ acknowledged: true });
197333
+ }
197334
+ if (this.decided.get(id) === sessionId) {
197335
+ return ok2({ acknowledged: true });
197336
+ }
197337
+ return fail4(404, "unknown-approval", "No approval with that id was sent to this session.");
197338
+ }
196614
197339
  openStream(hub, request, io) {
196615
197340
  const sessionId = new URL(request.url).searchParams.get("sessionId");
196616
197341
  if (!isSessionId(sessionId)) {
@@ -196767,10 +197492,14 @@ ${asCodeBlock(redactSensitiveText(value.prompt))}`, {
196767
197492
  });
196768
197493
  return;
196769
197494
  }
196770
- const who = `user ${callback.fromId}`;
196771
- this.finishApproval(id, sessionId, messageId, entry.text, {
196772
- result: approval.decision === "allow" ? `Allowed by ${who} at ${when}.` : `Denied by ${who} at ${when}.`,
196773
- shortReply: approval.decision === "allow" ? "Approval granted." : "Approval denied."
197495
+ this.awaiting.set(id, {
197496
+ sessionId,
197497
+ decision: approval.decision,
197498
+ who: `user ${callback.fromId}`,
197499
+ sentAt: when,
197500
+ ...messageId === undefined ? {} : { messageId },
197501
+ ...entry.text === undefined ? {} : { text: entry.text },
197502
+ timer: this.timers.setTimeout(() => this.giveUpOnAck(id), this.approvalAckMs)
196774
197503
  });
196775
197504
  return;
196776
197505
  }
@@ -196778,6 +197507,9 @@ ${asCodeBlock(redactSensitiveText(value.prompt))}`, {
196778
197507
  this.expireApproval(id);
196779
197508
  return;
196780
197509
  }
197510
+ if (this.awaiting.has(id)) {
197511
+ return;
197512
+ }
196781
197513
  const done = this.finished.get(id);
196782
197514
  if (done !== undefined) {
196783
197515
  if (done.sessionId === sessionId) {
@@ -196798,7 +197530,7 @@ ${asCodeBlock(redactSensitiveText(value.prompt))}`, {
196798
197530
  }
196799
197531
  }
196800
197532
  carriesLivePrompt(messageId) {
196801
- return [...this.approvals.values(), ...this.choices.values()].some((live) => live.messageId === messageId);
197533
+ return [...this.approvals.values(), ...this.awaiting.values(), ...this.choices.values()].some((live) => live.messageId === messageId);
196802
197534
  }
196803
197535
  pressChoice(sessionId, callback, choice, stream) {
196804
197536
  const id = choice.promptId;
@@ -196881,7 +197613,7 @@ ${asCodeBlock(redactSensitiveText(value.prompt))}`, {
196881
197613
  });
196882
197614
  }
196883
197615
  onApprovalSent(approvalId, sessionId, messageId, text) {
196884
- const live = this.approvals.get(approvalId);
197616
+ const live = this.approvals.get(approvalId) ?? this.awaiting.get(approvalId);
196885
197617
  if (live !== undefined) {
196886
197618
  live.messageId = messageId;
196887
197619
  live.text = text;
@@ -196934,6 +197666,37 @@ ${asCodeBlock(redactSensitiveText(value.prompt))}`, {
196934
197666
  this.timers.clearTimeout(entry.timer);
196935
197667
  this.approvals.delete(approvalId);
196936
197668
  }
197669
+ dropAwaiting(approvalId) {
197670
+ const entry = this.awaiting.get(approvalId);
197671
+ if (entry === undefined) {
197672
+ return;
197673
+ }
197674
+ this.timers.clearTimeout(entry.timer);
197675
+ this.awaiting.delete(approvalId);
197676
+ }
197677
+ rememberDecided(approvalId, sessionId) {
197678
+ this.decided.delete(approvalId);
197679
+ this.decided.set(approvalId, sessionId);
197680
+ while (this.decided.size > MAX_DECIDED_APPROVALS) {
197681
+ const oldest = this.decided.keys().next().value;
197682
+ if (oldest === undefined) {
197683
+ break;
197684
+ }
197685
+ this.decided.delete(oldest);
197686
+ }
197687
+ }
197688
+ giveUpOnAck(approvalId) {
197689
+ const entry = this.awaiting.get(approvalId);
197690
+ if (entry === undefined) {
197691
+ return;
197692
+ }
197693
+ this.dropAwaiting(approvalId);
197694
+ this.rememberDecided(approvalId, entry.sessionId);
197695
+ this.finishApproval(approvalId, entry.sessionId, entry.messageId, entry.text, {
197696
+ result: `Sent to the shell at ${entry.sentAt}, not confirmed; the shell denies by itself if it did not receive it.`,
197697
+ shortReply: "Sent to the shell, not confirmed; it denies by itself if it did not receive it."
197698
+ });
197699
+ }
196937
197700
  expireApproval(approvalId) {
196938
197701
  const entry = this.approvals.get(approvalId);
196939
197702
  if (entry === undefined) {
@@ -196970,6 +197733,11 @@ ${asCodeBlock(redactSensitiveText(value.prompt))}`, {
196970
197733
  }
196971
197734
  }
196972
197735
  }
197736
+ for (const [id, entry] of [...this.awaiting.entries()]) {
197737
+ if (entry.sessionId === stream.sessionId && !this.closed) {
197738
+ this.giveUpOnAck(id);
197739
+ }
197740
+ }
196973
197741
  for (const [id, entry] of [...this.choices.entries()]) {
196974
197742
  if (entry.sessionId === stream.sessionId) {
196975
197743
  this.dropChoice(id);
@@ -196995,7 +197763,7 @@ function asCodeBlock(text) {
196995
197763
  ${safe}
196996
197764
  ${fence}`;
196997
197765
  }
196998
- var MAX_REPLY_CHARS = 20000, MAX_PENDING_APPROVALS_PER_SESSION = 8, MAX_PENDING_CHOICES_PER_SESSION = 8, MAX_REMEMBERED_IDS_PER_SESSION = 512, MAX_PROJECT_NAME_CHARS = 128, CONTROL_CHARS2, JSON_HEADERS2, MAX_FINISHED_PROMPTS = 128, MAX_SETTLED_TEXT_CHARS = 3600, MAX_BACKTICK_RUN = 8;
197766
+ var MAX_REPLY_CHARS = 20000, MAX_PENDING_APPROVALS_PER_SESSION = 8, MAX_PENDING_CHOICES_PER_SESSION = 8, MAX_REMEMBERED_IDS_PER_SESSION = 512, MAX_PROJECT_NAME_CHARS = 128, CONTROL_CHARS2, JSON_HEADERS2, MAX_FINISHED_PROMPTS = 128, MAX_DECIDED_APPROVALS = 256, MAX_SETTLED_TEXT_CHARS = 3600, MAX_BACKTICK_RUN = 8;
196999
197767
  var init_http_surface = __esm(() => {
197000
197768
  init_service2();
197001
197769
  init_hub();
@@ -197932,7 +198700,7 @@ var init_serve_digest = __esm(() => {
197932
198700
  });
197933
198701
 
197934
198702
  // src/commands/serve.ts
197935
- import { randomUUID as randomUUID52 } from "crypto";
198703
+ import { randomUUID as randomUUID53 } from "crypto";
197936
198704
  function parseArgs5(args, valueFlags, booleanFlags) {
197937
198705
  const values = new Map;
197938
198706
  const flags = new Set;
@@ -198258,7 +199026,7 @@ function runConfig(args) {
198258
199026
  return;
198259
199027
  }
198260
199028
  const credential = readServeCredential();
198261
- const credentialId = credential.status === "ok" ? credential.record.id : randomUUID52();
199029
+ const credentialId = credential.status === "ok" ? credential.record.id : randomUUID53();
198262
199030
  const config = defaultServeConfig(credentialId, {
198263
199031
  address: parsed.parsed.values.get("--bind") ?? DEFAULT_SERVE_BIND_ADDRESS,
198264
199032
  port: port ?? DEFAULT_SERVE_PORT,
@@ -199932,7 +200700,7 @@ ${settled.stderr}` };
199932
200700
  var PROBE_TIMEOUT_MS = 1e4;
199933
200701
 
199934
200702
  // src/commands/agents-external.ts
199935
- import { randomUUID as randomUUID53 } from "crypto";
200703
+ import { randomUUID as randomUUID54 } from "crypto";
199936
200704
  import { mkdir as mkdir73 } from "fs/promises";
199937
200705
  import { tmpdir as tmpdir15 } from "os";
199938
200706
  import path369 from "path";
@@ -200219,7 +200987,7 @@ async function runCommand4(args, deps, log) {
200219
200987
  taskTitle: task.length > 80 ? `${task.slice(0, 77)}...` : task,
200220
200988
  taskDescription: task,
200221
200989
  acceptanceCriteria: [],
200222
- worktreeId: `acp-${randomUUID53()}`,
200990
+ worktreeId: `acp-${randomUUID54()}`,
200223
200991
  maxPromptBytes: gate.config.maxPromptBytes,
200224
200992
  timeoutMs,
200225
200993
  parentEnv: env,
@@ -201865,7 +202633,7 @@ var init_metrics2 = __esm(() => {
201865
202633
  });
201866
202634
 
201867
202635
  // src/metrics/ablation-runner.ts
201868
- function median2(values) {
202636
+ function median3(values) {
201869
202637
  if (values.length === 0)
201870
202638
  return null;
201871
202639
  const sorted = [...values].sort((a, b) => a - b);
@@ -201891,7 +202659,7 @@ function toolCallDistribution(samples) {
201891
202659
  }));
201892
202660
  return {
201893
202661
  samples: runSamples,
201894
- median: median2(values),
202662
+ median: median3(values),
201895
202663
  spread: spread(values),
201896
202664
  reliability: ABLATION_RELIABILITY
201897
202665
  };
@@ -201901,7 +202669,7 @@ function tokenCost(samples, source) {
201901
202669
  if (known.length === 0)
201902
202670
  return;
201903
202671
  const note = `median of ${known.length}/${samples.length} seed samples with a reported token count`;
201904
- return { tokens: { raw: measured(median2(known), source, note) } };
202672
+ return { tokens: { raw: measured(median3(known), source, note) } };
201905
202673
  }
201906
202674
  function successRate(samples) {
201907
202675
  const successes = samples.filter((s) => s.success).length;
@@ -203228,7 +203996,7 @@ function formatFwkExplain(result) {
203228
203996
  }
203229
203997
 
203230
203998
  // src/commands/workspace.ts
203231
- import { randomUUID as randomUUID54 } from "crypto";
203999
+ import { randomUUID as randomUUID55 } from "crypto";
203232
204000
  import { writeFile as writeFile51 } from "fs/promises";
203233
204001
  function service8() {
203234
204002
  return new WorkspaceService({
@@ -203249,7 +204017,7 @@ async function workspaceCommand(args) {
203249
204017
  const component = rawComponent === undefined ? undefined : requireWorkspaceReference(rawComponent);
203250
204018
  if (!title)
203251
204019
  throw new Error("Usage: keryx workspace create --title <title> [--component <workspace-relative-ref>]");
203252
- const workspace = await service8().create({ request: undefined, requestCorrelationId: randomUUID54(), id: newWorkspaceId(), title, ...component ? { component: { kind: "component", uri: component } } : {} });
204020
+ const workspace = await service8().create({ request: undefined, requestCorrelationId: randomUUID55(), id: newWorkspaceId(), title, ...component ? { component: { kind: "component", uri: component } } : {} });
203253
204021
  console.log(JSON.stringify(workspace, null, 2));
203254
204022
  return;
203255
204023
  }
@@ -203279,7 +204047,7 @@ async function workspaceCommand(args) {
203279
204047
  const revision = optionValue(args, "--revision");
203280
204048
  if (!workspaceId || !kind || !rawUri)
203281
204049
  throw new Error("Usage: keryx workspace add-resource <workspace-id> --kind <kind> --uri <workspace-relative-ref> [--revision <revision>]");
203282
- console.log(JSON.stringify(await service8().addResource({ request: undefined, requestCorrelationId: randomUUID54(), workspaceId, resource: { kind, uri: requireWorkspaceReference(rawUri), ...revision ? { revision } : {} } }), null, 2));
204050
+ console.log(JSON.stringify(await service8().addResource({ request: undefined, requestCorrelationId: randomUUID55(), workspaceId, resource: { kind, uri: requireWorkspaceReference(rawUri), ...revision ? { revision } : {} } }), null, 2));
203283
204051
  return;
203284
204052
  }
203285
204053
  if (subcommand === "archive") {
@@ -203287,7 +204055,7 @@ async function workspaceCommand(args) {
203287
204055
  const workspaceId = args[1];
203288
204056
  if (!workspaceId)
203289
204057
  throw new Error("Usage: keryx workspace archive <workspace-id>");
203290
- console.log(JSON.stringify(await service8().archive({ request: undefined, requestCorrelationId: randomUUID54(), workspaceId }), null, 2));
204058
+ console.log(JSON.stringify(await service8().archive({ request: undefined, requestCorrelationId: randomUUID55(), workspaceId }), null, 2));
203291
204059
  return;
203292
204060
  }
203293
204061
  if (subcommand === "remove-resource") {
@@ -203296,7 +204064,7 @@ async function workspaceCommand(args) {
203296
204064
  const uri = optionValue(args, "--uri");
203297
204065
  if (!workspaceId || !uri)
203298
204066
  throw new Error("Usage: keryx workspace remove-resource <workspace-id> --uri <workspace-relative-ref>");
203299
- console.log(JSON.stringify(await service8().removeResource({ request: undefined, requestCorrelationId: randomUUID54(), workspaceId, uri }), null, 2));
204067
+ console.log(JSON.stringify(await service8().removeResource({ request: undefined, requestCorrelationId: randomUUID55(), workspaceId, uri }), null, 2));
203300
204068
  return;
203301
204069
  }
203302
204070
  if (subcommand === "rename") {
@@ -203305,7 +204073,7 @@ async function workspaceCommand(args) {
203305
204073
  const title = optionValue(args, "--title");
203306
204074
  if (!workspaceId || !title)
203307
204075
  throw new Error("Usage: keryx workspace rename <workspace-id> --title <title>");
203308
- console.log(JSON.stringify(await service8().rename({ request: undefined, requestCorrelationId: randomUUID54(), workspaceId, title }), null, 2));
204076
+ console.log(JSON.stringify(await service8().rename({ request: undefined, requestCorrelationId: randomUUID55(), workspaceId, title }), null, 2));
203309
204077
  return;
203310
204078
  }
203311
204079
  if (subcommand === "overview") {
@@ -203317,7 +204085,7 @@ async function workspaceCommand(args) {
203317
204085
  const maxTokens = Number(optionValue(args, "--max-tokens") ?? "4096");
203318
204086
  if (!Number.isInteger(maxItems) || !Number.isInteger(maxTokens) || maxItems < 0 || maxTokens < 0)
203319
204087
  throw new Error("--max-items and --max-tokens must be non-negative integers");
203320
- const result = await createLocalFwkReadService(process.cwd()).overview({ workspaceId, request: undefined, requestCorrelationId: randomUUID54(), budget: { maxItems, maxTokens } });
204088
+ const result = await createLocalFwkReadService(process.cwd()).overview({ workspaceId, request: undefined, requestCorrelationId: randomUUID55(), budget: { maxItems, maxTokens } });
203321
204089
  const normalized = normalizeFwkResult(result);
203322
204090
  console.log(JSON.stringify(normalized, null, 2));
203323
204091
  if (args.includes("--explain"))
@@ -203334,7 +204102,7 @@ async function workspaceCommand(args) {
203334
204102
  const maxTokens = Number(optionValue(args, "--max-tokens") ?? "4096");
203335
204103
  if (!Number.isInteger(maxItems) || !Number.isInteger(maxTokens) || maxItems < 0 || maxTokens < 0)
203336
204104
  throw new Error("--max-items and --max-tokens must be non-negative integers");
203337
- const result = await createLocalFwkReadService(process.cwd()).read({ workspaceId, itemId, request: undefined, requestCorrelationId: randomUUID54(), budget: { maxItems, maxTokens } });
204105
+ const result = await createLocalFwkReadService(process.cwd()).read({ workspaceId, itemId, request: undefined, requestCorrelationId: randomUUID55(), budget: { maxItems, maxTokens } });
203338
204106
  const normalized = normalizeFwkResult(result);
203339
204107
  console.log(JSON.stringify(normalized, null, 2));
203340
204108
  if (args.includes("--explain"))
@@ -203356,14 +204124,14 @@ async function workspaceCommand(args) {
203356
204124
  const session = findSession(cwd, sessionRef);
203357
204125
  if (!session)
203358
204126
  throw new Error(`no session matching "${sessionRef}" in this project \u2014 use \`keryx sessions list\``);
203359
- const proposalId = `proposal-${randomUUID54().replace(/-/g, "").slice(0, 16)}`;
204127
+ const proposalId = `proposal-${randomUUID55().replace(/-/g, "").slice(0, 16)}`;
203360
204128
  const notePath = proposalNotePath(cwd, workspaceId, proposalId);
203361
204129
  const materializedNote = note === undefined ? undefined : prepareOutputForPersistence(await guardOutput({ cwd, content: note, target: "task", source: "generated", path: notePath }), note);
203362
204130
  if (materializedNote !== undefined && !materializedNote.allowed)
203363
204131
  throw new Error(materializedNote.reason);
203364
204132
  const persistedNote = materializedNote?.allowed ? materializedNote.content : undefined;
203365
204133
  const { service, wrapUpAuthority, authorizationServer } = createHarnessProposalLifecycleService(cwd, { workspaceId, ...persistedNote ? { note: persistedNote } : {} });
203366
- const requestCorrelationId = randomUUID54();
204134
+ const requestCorrelationId = randomUUID55();
203367
204135
  const actor = await authorizationServer.actorContextFor(undefined, requestCorrelationId);
203368
204136
  if (!actor)
203369
204137
  throw new Error("trusted ActorContext is required");
@@ -203404,13 +204172,13 @@ async function workspaceCommand(args) {
203404
204172
  const proposalId = args[2];
203405
204173
  const decision = optionValue(args, "--decision");
203406
204174
  const reason = optionValue(args, "--reason");
203407
- const idempotencyKey = optionValue(args, "--idempotency-key") ?? randomUUID54();
204175
+ const idempotencyKey = optionValue(args, "--idempotency-key") ?? randomUUID55();
203408
204176
  const confirmToken = optionValue(args, "--confirm-token");
203409
204177
  if (!workspaceId || !proposalId || !decision)
203410
204178
  throw new Error("Usage: keryx workspace review <workspace-id> <proposal-id> --decision <accepted|rejected|dismissed> [--reason <reason>] [--idempotency-key <key>] [--confirm-token <token>]");
203411
204179
  if (decision === "accepted" && !confirmToken)
203412
204180
  throw new Error("--decision accepted requires --confirm-token \u2014 run `keryx workspace confirm-review " + workspaceId + " " + proposalId + "` first");
203413
- const result = await createHarnessProposalLifecycleService(process.cwd(), { workspaceId }).service.review({ request: undefined, requestCorrelationId: randomUUID54(), workspaceId, proposalId, decision, idempotencyKey, interactive: true, ...reason ? { reason } : {}, ...confirmToken ? { confirmToken } : {} });
204181
+ const result = await createHarnessProposalLifecycleService(process.cwd(), { workspaceId }).service.review({ request: undefined, requestCorrelationId: randomUUID55(), workspaceId, proposalId, decision, idempotencyKey, interactive: true, ...reason ? { reason } : {}, ...confirmToken ? { confirmToken } : {} });
203414
204182
  console.log(JSON.stringify(normalizeProposalLifecycleResult(result), null, 2));
203415
204183
  return;
203416
204184
  }
@@ -203435,7 +204203,7 @@ async function workspaceCommand(args) {
203435
204203
  const artifactRef = optionValue(args, "--artifact");
203436
204204
  if (!workspaceId || !to || !artifactRef)
203437
204205
  throw new Error("Usage: keryx workspace handoff <workspace-id> --to <subject> --artifact <ref>");
203438
- const activity = await createLocalCollaborationService(process.cwd()).record({ request: undefined, requestCorrelationId: randomUUID54(), workspaceId, activity: { kind: "handoff-recorded", handoff: { to, artifactRef } } });
204206
+ const activity = await createLocalCollaborationService(process.cwd()).record({ request: undefined, requestCorrelationId: randomUUID55(), workspaceId, activity: { kind: "handoff-recorded", handoff: { to, artifactRef } } });
203439
204207
  console.log(JSON.stringify(activity, null, 2));
203440
204208
  return;
203441
204209
  }
@@ -203444,7 +204212,7 @@ async function workspaceCommand(args) {
203444
204212
  const workspaceId = args[1];
203445
204213
  if (!workspaceId)
203446
204214
  throw new Error("Usage: keryx workspace collaboration <workspace-id>");
203447
- console.log(JSON.stringify(await createLocalCollaborationService(process.cwd()).overview({ workspaceId, request: undefined, requestCorrelationId: randomUUID54() }), null, 2));
204215
+ console.log(JSON.stringify(await createLocalCollaborationService(process.cwd()).overview({ workspaceId, request: undefined, requestCorrelationId: randomUUID55() }), null, 2));
203448
204216
  return;
203449
204217
  }
203450
204218
  if (subcommand === "policy-readiness") {
@@ -203471,7 +204239,7 @@ async function workspaceCommand(args) {
203471
204239
  const workspaceId = args[1];
203472
204240
  const cwd = process.cwd();
203473
204241
  const authorizationServer = localWorkspaceAuthorizationServer();
203474
- const actor = await authorizationServer.actorContextFor(undefined, randomUUID54());
204242
+ const actor = await authorizationServer.actorContextFor(undefined, randomUUID55());
203475
204243
  if (!actor)
203476
204244
  throw new Error("trusted ActorContext is required");
203477
204245
  if (workspaceId) {
@@ -204614,6 +205382,8 @@ var init_forgetting = __esm(() => {
204614
205382
  });
204615
205383
 
204616
205384
  // src/commands/decisions.ts
205385
+ import { readFile as readFile190 } from "fs/promises";
205386
+ import path373 from "path";
204617
205387
  function rejectUnknownFlags2(sub, args, accepted) {
204618
205388
  const unknown = args.filter((argument) => argument.startsWith("--")).map((argument) => argument.split("=")[0]).filter((name) => !accepted.includes(name));
204619
205389
  if (unknown.length > 0) {
@@ -204729,8 +205499,33 @@ async function runReason(args) {
204729
205499
  }
204730
205500
  console.log(recorded ? `decision ${id}: reason recorded` : `decision ${id}: the human was already asked once; nothing written`);
204731
205501
  }
205502
+ async function runImport(args) {
205503
+ const file = positionalId(args);
205504
+ if (file === undefined)
205505
+ throw new Error("keryx decisions import needs the file: keryx decisions import <file.jsonl> [--dry-run] [--json]");
205506
+ const rest = args.slice(1);
205507
+ rejectUnknownFlags2("import", rest, IMPORT_FLAGS2);
205508
+ let text;
205509
+ try {
205510
+ text = await readFile190(path373.resolve(process.cwd(), file), "utf8");
205511
+ } catch {
205512
+ throw new Error(`cannot read ${file}`);
205513
+ }
205514
+ const result = await importBackfill(process.cwd(), text, { dryRun: rest.includes("--dry-run") });
205515
+ if (rest.includes("--json")) {
205516
+ console.log(JSON.stringify(result, null, 2));
205517
+ return;
205518
+ }
205519
+ console.log(renderImportResult(result));
205520
+ }
204732
205521
  async function runReport3(args) {
204733
205522
  rejectUnknownFlags2("report", args, REPORT_FLAGS);
205523
+ if (args.includes("--line") && args.includes("--json"))
205524
+ throw new Error("keryx decisions report: --line and --json cannot be combined");
205525
+ if (args.includes("--line")) {
205526
+ console.log(await reportLine(process.cwd()));
205527
+ return;
205528
+ }
204734
205529
  if (args.includes("--json")) {
204735
205530
  console.log(JSON.stringify(await loadReport(process.cwd()), null, 2));
204736
205531
  return;
@@ -204752,8 +205547,14 @@ async function decisionsCommand(args = []) {
204752
205547
  return await runReason(args.slice(1));
204753
205548
  if (command === "report")
204754
205549
  return await runReport3(args.slice(1));
205550
+ if (command === "import")
205551
+ return await runImport(args.slice(1));
204755
205552
  } catch (cause) {
204756
- console.error(cause instanceof Error ? cause.message : String(cause));
205553
+ const message = cause instanceof Error ? cause.message : String(cause);
205554
+ if (command === "import" && args.includes("--json"))
205555
+ console.log(JSON.stringify({ error: message }, null, 2));
205556
+ else
205557
+ console.error(message);
204757
205558
  process.exitCode = 1;
204758
205559
  return;
204759
205560
  }
@@ -204771,7 +205572,8 @@ Usage:
204771
205572
  keryx decisions open --question "<text>" --option <id>=<label> [--option ...] [--recommend <id> --reason "<why>"] [--stage <name>] [--flow <id>] [--action <tag>] [--json]
204772
205573
  keryx decisions answer <id> --choice <id> [--other] [--reason "<why>"] [--json]
204773
205574
  keryx decisions reason <id> --text "<why>" [--json]
204774
- keryx decisions report [--json]
205575
+ keryx decisions report [--json | --line]
205576
+ keryx decisions import <file.jsonl> [--dry-run] [--json]
204775
205577
 
204776
205578
  Every agent question with options leaves one record: what was asked, what the
204777
205579
  agent recommended and why, how it was shown, what the human chose and how long
@@ -204804,6 +205606,28 @@ it took. Call \`open\` BEFORE showing the question and \`answer\` after.
204804
205606
  its reason. It also counts the questions that looked irreversible
204805
205607
  and the ones where blind was refused because of it, per stage: a
204806
205608
  high number on ordinary questions means the list over-matches.
205609
+ Backfilled decisions (see import) are reported in a separate block
205610
+ "\u0434\u043E (\u0438\u0441\u0442\u043E\u0440\u0438\u0447\u0435\u0441\u043A\u043E\u0435, \u0434\u043E\u0437\u0430\u043F\u043E\u043B\u043D\u0435\u043D\u043E \u0437\u0430\u0434\u043D\u0438\u043C \u0447\u0438\u0441\u043B\u043E\u043C)" and never counted in
205611
+ the live shares or in the time to answer. --line prints ONE line in
205612
+ Russian for the daily topic message: the total (before, after) and the
205613
+ match share of the visible, the hidden and the historical decisions.
205614
+ import loads historical decisions from a JSON-lines file, one per line:
205615
+ {"id","at","flow","stage","question","options":[{"id","label"}],
205616
+ "recommendation":{"optionId","reason"}|null,"source",
205617
+ "answer":{"choice","other"?}|null,"reason"?}
205618
+ Every one is marked backfilled (its recommendation was written down
205619
+ after the fact), is never blind, and keeps the time it was asked in
205620
+ "at"; its time to answer is unknown and never used. The file is
205621
+ read line by line: a line that is not a decision is skipped, named
205622
+ (line number and why) and counted, and the rest is imported. An id
205623
+ already in the journal is skipped and reported, so the same file can
205624
+ be imported twice; a backfilled decision whose answer is missing from
205625
+ the journal (an interrupted write) gets just that answer on the next
205626
+ import. --dry-run checks and counts without writing. It prints:
205627
+ Imported: N, skipped: S, with recommendation: R, answered: A,
205628
+ deviations: D, plus ", repaired: R" and ", malformed: M" when
205629
+ there are any. With --json a failure is {"error": "..."}. A bare /decisions change never touches a backfilled
205630
+ decision.
204807
205631
 
204808
205632
  The journal is one file per repository, .metaproject/data/decisions/journal.jsonl
204809
205633
  under the main checkout (every worktree shares it; it is git-ignored). Inside a
@@ -204813,14 +205637,15 @@ the answer also adds a line to that flow's journal.md, except for an inferred
204813
205637
  flow, which only stays in the project-wide journal. A failure here never
204814
205638
  stops the question.`);
204815
205639
  }
204816
- var OPEN_FLAGS, ANSWER_FLAGS, REASON_FLAGS, REPORT_FLAGS;
205640
+ var OPEN_FLAGS, ANSWER_FLAGS, REASON_FLAGS, REPORT_FLAGS, IMPORT_FLAGS2;
204817
205641
  var init_decisions2 = __esm(() => {
204818
205642
  init_args();
204819
205643
  init_service13();
204820
205644
  OPEN_FLAGS = ["--question", "--option", "--options-json", "--recommend", "--reason", "--stage", "--flow", "--action", "--json"];
204821
205645
  ANSWER_FLAGS = ["--choice", "--other", "--reason", "--json"];
204822
205646
  REASON_FLAGS = ["--text", "--json"];
204823
- REPORT_FLAGS = ["--json"];
205647
+ REPORT_FLAGS = ["--json", "--line"];
205648
+ IMPORT_FLAGS2 = ["--dry-run", "--json"];
204824
205649
  });
204825
205650
 
204826
205651
  // src/commands/governance.ts
@@ -205034,7 +205859,7 @@ var init_product = __esm(() => {
205034
205859
 
205035
205860
  // src/commands/hooks.ts
205036
205861
  import { existsSync as existsSync58, readFileSync as readFileSync32 } from "fs";
205037
- import path373 from "path";
205862
+ import path374 from "path";
205038
205863
  function resolveHomeDir(deps) {
205039
205864
  return resolveHooksHomeDir(process.env, deps.homeDir);
205040
205865
  }
@@ -205045,10 +205870,10 @@ function optConfigDir(deps) {
205045
205870
  return deps.configDir !== undefined ? { configDir: deps.configDir } : {};
205046
205871
  }
205047
205872
  function userHooksPath(homeDir) {
205048
- return path373.join(homeDir, ".keryx", "hooks.json");
205873
+ return path374.join(homeDir, ".keryx", "hooks.json");
205049
205874
  }
205050
205875
  function projectHooksPath(projectRoot) {
205051
- return path373.join(projectRoot, ".metaproject", "hooks.json");
205876
+ return path374.join(projectRoot, ".metaproject", "hooks.json");
205052
205877
  }
205053
205878
  function fail6(message) {
205054
205879
  console.error(message);
@@ -205166,10 +205991,10 @@ async function runList7(args, deps) {
205166
205991
  function argv0Resolves(argv0) {
205167
205992
  if (argv0 === "keryx")
205168
205993
  return true;
205169
- if (path373.isAbsolute(argv0))
205994
+ if (path374.isAbsolute(argv0))
205170
205995
  return existsSync58(argv0);
205171
205996
  const pathEnv = process.env.PATH ?? "";
205172
- return pathEnv.split(path373.delimiter).filter((dir) => dir.length > 0).some((dir) => existsSync58(path373.join(dir, argv0)));
205997
+ return pathEnv.split(path374.delimiter).filter((dir) => dir.length > 0).some((dir) => existsSync58(path374.join(dir, argv0)));
205173
205998
  }
205174
205999
  function checkArgvResolutions(registrations) {
205175
206000
  const seen = new Set;
@@ -205623,7 +206448,7 @@ async function writeDocAtomic(filePath, doc, scope, cwd, homeDir) {
205623
206448
  if (scope === "project") {
205624
206449
  await writeContained(cwd, PROJECT_HOOKS_REL, body);
205625
206450
  } else {
205626
- const userConfigDir = path373.join(homeDir, ".keryx");
206451
+ const userConfigDir = path374.join(homeDir, ".keryx");
205627
206452
  if (!existsSync58(userConfigDir)) {
205628
206453
  await mkdirContained(homeDir, ".keryx");
205629
206454
  }
@@ -205938,13 +206763,13 @@ var init_types12 = __esm(() => {
205938
206763
  });
205939
206764
 
205940
206765
  // src/bundle/paths.ts
205941
- import { lstat as lstat20, readFile as readFile190 } from "fs/promises";
205942
- import path374 from "path";
206766
+ import { lstat as lstat20, readFile as readFile191 } from "fs/promises";
206767
+ import path375 from "path";
205943
206768
  function scopeRoot(scope, ctx) {
205944
206769
  if (scope === "user") {
205945
206770
  return userStoreRoot(ctx.env ?? process.env, ctx.homeDir);
205946
206771
  }
205947
- return path374.join(ctx.projectRoot, ".metaproject");
206772
+ return path375.join(ctx.projectRoot, ".metaproject");
205948
206773
  }
205949
206774
  function isPortablePath(relPath) {
205950
206775
  return relPath.split("/").every((segment) => PORTABLE_SEGMENT_RE.test(segment));
@@ -206118,10 +206943,10 @@ async function targetFor(entry, targetScope, ctx) {
206118
206943
  return { ok: true, absolutePath: containment.absolutePath };
206119
206944
  }
206120
206945
  async function resolveContainedPathForWrite(root, relPath) {
206121
- const absolute = path374.resolve(root, relPath);
206122
- const rootResolved = path374.resolve(root);
206123
- const relative = path374.relative(rootResolved, absolute);
206124
- if (relative === "" || relative === ".." || relative.startsWith(`..${path374.sep}`) || path374.isAbsolute(relative)) {
206946
+ const absolute = path375.resolve(root, relPath);
206947
+ const rootResolved = path375.resolve(root);
206948
+ const relative = path375.relative(rootResolved, absolute);
206949
+ if (relative === "" || relative === ".." || relative.startsWith(`..${path375.sep}`) || path375.isAbsolute(relative)) {
206125
206950
  return {
206126
206951
  ok: false,
206127
206952
  refusal: { reason: BUNDLE_REFUSAL.pathEscape, path: relPath, message: `${relPath} resolves outside the scope root` }
@@ -206130,7 +206955,7 @@ async function resolveContainedPathForWrite(root, relPath) {
206130
206955
  return { ok: true, absolutePath: absolute };
206131
206956
  }
206132
206957
  async function refuseSymlinkChain(root, relPath) {
206133
- const rootResolved = path374.resolve(root);
206958
+ const rootResolved = path375.resolve(root);
206134
206959
  try {
206135
206960
  const rootStat = await lstat20(rootResolved);
206136
206961
  if (rootStat.isSymbolicLink()) {
@@ -206143,7 +206968,7 @@ async function refuseSymlinkChain(root, relPath) {
206143
206968
  const segments = relPath.split("/");
206144
206969
  let current = rootResolved;
206145
206970
  for (const segment of segments) {
206146
- current = path374.join(current, segment);
206971
+ current = path375.join(current, segment);
206147
206972
  try {
206148
206973
  const stat = await lstat20(current);
206149
206974
  if (stat.isSymbolicLink()) {
@@ -206158,7 +206983,7 @@ async function refuseSymlinkChain(root, relPath) {
206158
206983
  }
206159
206984
  async function readTargetFile(absolutePath) {
206160
206985
  try {
206161
- const bytes = await readFile190(absolutePath);
206986
+ const bytes = await readFile191(absolutePath);
206162
206987
  return { ok: true, bytes };
206163
206988
  } catch (err) {
206164
206989
  const code = err.code;
@@ -206195,8 +207020,8 @@ var init_paths5 = __esm(() => {
206195
207020
  });
206196
207021
 
206197
207022
  // src/bundle/archive.ts
206198
- import { readdir as readdir65, readFile as readFile191, lstat as lstat21 } from "fs/promises";
206199
- import path375 from "path";
207023
+ import { readdir as readdir65, readFile as readFile192, lstat as lstat21 } from "fs/promises";
207024
+ import path376 from "path";
206200
207025
  import { gzipSync, gunzipSync } from "zlib";
206201
207026
  function resolveLimits(limits) {
206202
207027
  const maxTotalBytes = limits?.maxTotalBytes ?? DEFAULT_BUNDLE_LIMITS.maxTotalBytes;
@@ -206279,11 +207104,11 @@ function buildBundleArchive(files, manifestBytes) {
206279
207104
  const dirs = new Set;
206280
207105
  const sortedPaths = [...files.keys()].sort();
206281
207106
  for (const p of sortedPaths) {
206282
- let dir = path375.dirname(p);
207107
+ let dir = path376.dirname(p);
206283
207108
  const segs = [];
206284
207109
  while (dir !== "." && dir !== "/" && dir !== "") {
206285
207110
  segs.unshift(dir);
206286
- dir = path375.dirname(dir);
207111
+ dir = path376.dirname(dir);
206287
207112
  }
206288
207113
  for (const d of segs)
206289
207114
  dirs.add(d);
@@ -206351,7 +207176,7 @@ function parseUstar(tar, limits) {
206351
207176
  const name = prefix.length > 0 ? `${prefix}/${nameRaw}` : nameRaw;
206352
207177
  const typeflag = String.fromCharCode(header[156] ?? 0);
206353
207178
  const size = readOctal(header, 124, 12);
206354
- if (path375.isAbsolute(name) || name.split("/").includes("..") || name.length === 0) {
207179
+ if (path376.isAbsolute(name) || name.split("/").includes("..") || name.length === 0) {
206355
207180
  return { ok: false, refusal: { reason: BUNDLE_REFUSAL.archiveInvalid, path: name, message: `unsafe entry name: ${name}` } };
206356
207181
  }
206357
207182
  const isDir = typeflag === "5";
@@ -206415,7 +207240,7 @@ async function openBundle(bundlePath, limits) {
206415
207240
  async function walk(dir, relPrefix) {
206416
207241
  const entries = await readdir65(dir, { withFileTypes: true });
206417
207242
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
206418
- const abs = path375.join(dir, entry.name);
207243
+ const abs = path376.join(dir, entry.name);
206419
207244
  const rel = relPrefix.length > 0 ? `${relPrefix}/${entry.name}` : entry.name;
206420
207245
  const entryStat = await lstat21(abs);
206421
207246
  if (entryStat.isSymbolicLink()) {
@@ -206434,7 +207259,7 @@ async function openBundle(bundlePath, limits) {
206434
207259
  if (entryStat.size > resolved.maxManifestBytes) {
206435
207260
  return { reason: BUNDLE_REFUSAL.archiveTooLarge, path: rel, message: `bundle.json is ${entryStat.size} bytes, over the ${resolved.maxManifestBytes}-byte manifest cap` };
206436
207261
  }
206437
- manifestBytes = await readFile191(abs);
207262
+ manifestBytes = await readFile192(abs);
206438
207263
  continue;
206439
207264
  }
206440
207265
  entryCount += 1;
@@ -206445,7 +207270,7 @@ async function openBundle(bundlePath, limits) {
206445
207270
  if (totalBytes > resolved.maxTotalBytes) {
206446
207271
  return { reason: BUNDLE_REFUSAL.archiveTooLarge, message: `bundle directory exceeds ${resolved.maxTotalBytes} bytes total` };
206447
207272
  }
206448
- const bytes = await readFile191(abs);
207273
+ const bytes = await readFile192(abs);
206449
207274
  files.set(rel, bytes);
206450
207275
  }
206451
207276
  return null;
@@ -206462,7 +207287,7 @@ async function openBundle(bundlePath, limits) {
206462
207287
  if (stat.size > resolved.maxCompressedBytes) {
206463
207288
  return { ok: false, refusal: { reason: BUNDLE_REFUSAL.archiveTooLarge, message: `${bundlePath} is ${stat.size} bytes, over the ${resolved.maxCompressedBytes}-byte compressed-size cap` } };
206464
207289
  }
206465
- const raw = await readFile191(bundlePath);
207290
+ const raw = await readFile192(bundlePath);
206466
207291
  let tar;
206467
207292
  try {
206468
207293
  tar = gunzipSync(raw, { maxOutputLength: resolved.maxDecompressedBytes });
@@ -207292,9 +208117,9 @@ var init_learned_pattern_schema = __esm(() => {
207292
208117
 
207293
208118
  // src/bundle/export.ts
207294
208119
  import { execFileSync as execFileSync3 } from "child_process";
207295
- import { mkdir as mkdir75, readdir as readdir66, readFile as readFile192, writeFile as writeFile52, lstat as lstat22 } from "fs/promises";
208120
+ import { mkdir as mkdir75, readdir as readdir66, readFile as readFile193, writeFile as writeFile52, lstat as lstat22 } from "fs/promises";
207296
208121
  import { existsSync as existsSync59 } from "fs";
207297
- import path376 from "path";
208122
+ import path377 from "path";
207298
208123
  function globToRegExp5(glob) {
207299
208124
  let out = "";
207300
208125
  for (let i = 0;i < glob.length; i += 1) {
@@ -207377,7 +208202,7 @@ async function walkFiles2(dir, relPrefix, out, skipped) {
207377
208202
  return;
207378
208203
  }
207379
208204
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
207380
- const abs = path376.join(dir, entry.name);
208205
+ const abs = path377.join(dir, entry.name);
207381
208206
  const rel = relPrefix.length > 0 ? `${relPrefix}/${entry.name}` : entry.name;
207382
208207
  const st = await lstat22(abs);
207383
208208
  if (st.isSymbolicLink()) {
@@ -207394,7 +208219,7 @@ async function walkFiles2(dir, relPrefix, out, skipped) {
207394
208219
  async function collectSkills(root, scope, skipped) {
207395
208220
  const candidates = [];
207396
208221
  for (const baseDirName of scope === "project" ? ["skills", "project-skills"] : ["skills"]) {
207397
- const base = path376.join(root, baseDirName);
208222
+ const base = path377.join(root, baseDirName);
207398
208223
  if (!existsSync59(base))
207399
208224
  continue;
207400
208225
  const files = [];
@@ -207407,7 +208232,7 @@ async function collectSkills(root, scope, skipped) {
207407
208232
  async function collectRules(root, scope, skipped) {
207408
208233
  if (scope === "user")
207409
208234
  return [];
207410
- const base = path376.join(root, "rules");
208235
+ const base = path377.join(root, "rules");
207411
208236
  if (!existsSync59(base))
207412
208237
  return [];
207413
208238
  const files = [];
@@ -207415,7 +208240,7 @@ async function collectRules(root, scope, skipped) {
207415
208240
  return files.filter((f) => /\.(md|mdc)$/.test(f.path)).map((f) => ({ bundlePath: f.path, kind: "rule", absolutePath: f.abs }));
207416
208241
  }
207417
208242
  async function collectAgents(root, skipped) {
207418
- const base = path376.join(root, "agents");
208243
+ const base = path377.join(root, "agents");
207419
208244
  if (!existsSync59(base))
207420
208245
  return [];
207421
208246
  let entries;
@@ -207426,7 +208251,7 @@ async function collectAgents(root, skipped) {
207426
208251
  }
207427
208252
  const candidates = [];
207428
208253
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
207429
- const abs = path376.join(base, entry.name);
208254
+ const abs = path377.join(base, entry.name);
207430
208255
  const st = await lstat22(abs);
207431
208256
  if (st.isSymbolicLink()) {
207432
208257
  skipped.push({ path: `agents/${entry.name}`, reason: "symlink" });
@@ -207441,7 +208266,7 @@ async function collectAgents(root, skipped) {
207441
208266
  async function collectMemory(root, skipped) {
207442
208267
  const candidates = [];
207443
208268
  for (const { folder } of MEMORY_TYPES) {
207444
- const base = path376.join(root, "memory", folder);
208269
+ const base = path377.join(root, "memory", folder);
207445
208270
  if (!existsSync59(base))
207446
208271
  continue;
207447
208272
  let entries;
@@ -207451,7 +208276,7 @@ async function collectMemory(root, skipped) {
207451
208276
  continue;
207452
208277
  }
207453
208278
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
207454
- const abs = path376.join(base, entry.name);
208279
+ const abs = path377.join(base, entry.name);
207455
208280
  const st = await lstat22(abs);
207456
208281
  if (st.isSymbolicLink()) {
207457
208282
  skipped.push({ path: `memory/${folder}/${entry.name}`, reason: "symlink" });
@@ -207465,7 +208290,7 @@ async function collectMemory(root, skipped) {
207465
208290
  return candidates;
207466
208291
  }
207467
208292
  async function collectHookConfig(root) {
207468
- const abs = path376.join(root, "hooks.json");
208293
+ const abs = path377.join(root, "hooks.json");
207469
208294
  if (!existsSync59(abs))
207470
208295
  return [];
207471
208296
  const st = await lstat22(abs);
@@ -207475,7 +208300,7 @@ async function collectHookConfig(root) {
207475
208300
  }
207476
208301
  async function collectLearnedPatterns(root, scope) {
207477
208302
  const relDir = scope === "user" ? "learning/patterns" : "data/learning/candidates";
207478
- const base = path376.join(root, ...relDir.split("/"));
208303
+ const base = path377.join(root, ...relDir.split("/"));
207479
208304
  if (!existsSync59(base))
207480
208305
  return [];
207481
208306
  let entries;
@@ -207487,7 +208312,7 @@ async function collectLearnedPatterns(root, scope) {
207487
208312
  const candidates = [];
207488
208313
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
207489
208314
  if (entry.isFile() && entry.name.endsWith(".json")) {
207490
- candidates.push({ bundlePath: `${relDir}/${entry.name}`, kind: "learned-pattern", absolutePath: path376.join(base, entry.name) });
208315
+ candidates.push({ bundlePath: `${relDir}/${entry.name}`, kind: "learned-pattern", absolutePath: path377.join(base, entry.name) });
207491
208316
  }
207492
208317
  }
207493
208318
  return candidates;
@@ -207528,10 +208353,10 @@ async function outDirIsEmpty(out) {
207528
208353
  }
207529
208354
  async function writeDirectoryBundle(out, manifestBytes, files) {
207530
208355
  await mkdir75(out, { recursive: true });
207531
- await writeFile52(path376.join(out, "bundle.json"), manifestBytes);
208356
+ await writeFile52(path377.join(out, "bundle.json"), manifestBytes);
207532
208357
  for (const [rel, bytes] of files) {
207533
- const abs = path376.join(out, ...rel.split("/"));
207534
- await mkdir75(path376.dirname(abs), { recursive: true });
208358
+ const abs = path377.join(out, ...rel.split("/"));
208359
+ await mkdir75(path377.dirname(abs), { recursive: true });
207535
208360
  await writeFile52(abs, bytes);
207536
208361
  }
207537
208362
  }
@@ -207563,7 +208388,7 @@ async function exportBundle(opts) {
207563
208388
  } else {
207564
208389
  const rawDigestEntries = [];
207565
208390
  for (const candidate of filtered) {
207566
- const bytes = await readFile192(candidate.absolutePath);
208391
+ const bytes = await readFile193(candidate.absolutePath);
207567
208392
  rawDigestEntries.push({ path: candidate.bundlePath, sha256: sha256Hex5(bytes) });
207568
208393
  }
207569
208394
  bundleId = contentDigestBundleId(opts.scope, rawDigestEntries);
@@ -207578,7 +208403,7 @@ async function exportBundle(opts) {
207578
208403
  skipped.push({ path: candidate.bundlePath, reason: `non-portable-name: ${portable.refusal.message}` });
207579
208404
  continue;
207580
208405
  }
207581
- let bytes = await readFile192(candidate.absolutePath);
208406
+ let bytes = await readFile193(candidate.absolutePath);
207582
208407
  if (candidate.kind === "hook-config") {
207583
208408
  let parsed;
207584
208409
  try {
@@ -207665,7 +208490,7 @@ async function exportBundle(opts) {
207665
208490
  const archive = buildBundleArchive(files, manifestBytes);
207666
208491
  if (!archive.ok)
207667
208492
  return { ok: false, refusals: [archive.refusal] };
207668
- await mkdir75(path376.dirname(opts.out), { recursive: true });
208493
+ await mkdir75(path377.dirname(opts.out), { recursive: true });
207669
208494
  await writeFile52(opts.out, archive.value);
207670
208495
  } else {
207671
208496
  if (existsSync59(opts.out) && !await outDirIsEmpty(opts.out)) {
@@ -207731,16 +208556,16 @@ var init_verify3 = __esm(() => {
207731
208556
  });
207732
208557
 
207733
208558
  // src/bundle/applied-state.ts
207734
- import { readFile as readFile193 } from "fs/promises";
207735
- import path377 from "path";
208559
+ import { readFile as readFile194 } from "fs/promises";
208560
+ import path378 from "path";
207736
208561
  function emptyState2() {
207737
208562
  return { schemaVersion: APPLIED_STATE_SCHEMA_VERSION, entries: {} };
207738
208563
  }
207739
208564
  function appliedStatePath(scope, ctx) {
207740
208565
  if (scope === "user") {
207741
- return path377.join(scopeRoot("user", ctx), "bundles", "applied-state.json");
208566
+ return path378.join(scopeRoot("user", ctx), "bundles", "applied-state.json");
207742
208567
  }
207743
- return path377.join(scopeRoot(scope, ctx), "data", "bundles", "applied-state.json");
208568
+ return path378.join(scopeRoot(scope, ctx), "data", "bundles", "applied-state.json");
207744
208569
  }
207745
208570
  function isPlainEntryShape(value) {
207746
208571
  if (typeof value !== "object" || value === null)
@@ -207799,7 +208624,7 @@ function migrateV1(v1) {
207799
208624
  async function readAppliedState(ledgerPath) {
207800
208625
  let raw;
207801
208626
  try {
207802
- raw = await readFile193(ledgerPath, "utf8");
208627
+ raw = await readFile194(ledgerPath, "utf8");
207803
208628
  } catch (err) {
207804
208629
  const code = err.code;
207805
208630
  if (code === "ENOENT") {
@@ -207822,14 +208647,14 @@ async function readAppliedState(ledgerPath) {
207822
208647
  return { ok: false, reason: "corrupt-ledger", message: `applied-state ledger at ${ledgerPath} has an unrecognized shape` };
207823
208648
  }
207824
208649
  function containedRootAndRel(ledgerPath) {
207825
- const marker = `${path377.sep}.metaproject${path377.sep}`;
208650
+ const marker = `${path378.sep}.metaproject${path378.sep}`;
207826
208651
  const idx = ledgerPath.indexOf(marker);
207827
208652
  if (idx >= 0) {
207828
- return { root: ledgerPath.slice(0, idx), rel: ledgerPath.slice(idx + path377.sep.length).split(path377.sep).join("/") };
208653
+ return { root: ledgerPath.slice(0, idx), rel: ledgerPath.slice(idx + path378.sep.length).split(path378.sep).join("/") };
207829
208654
  }
207830
- const bundlesDir = path377.dirname(ledgerPath);
207831
- const root = path377.dirname(bundlesDir);
207832
- const rel = path377.join(path377.basename(bundlesDir), path377.basename(ledgerPath)).split(path377.sep).join("/");
208655
+ const bundlesDir = path378.dirname(ledgerPath);
208656
+ const root = path378.dirname(bundlesDir);
208657
+ const rel = path378.join(path378.basename(bundlesDir), path378.basename(ledgerPath)).split(path378.sep).join("/");
207833
208658
  return { root, rel };
207834
208659
  }
207835
208660
  async function writeAppliedState(ledgerPath, state) {
@@ -207849,14 +208674,14 @@ var init_applied_state = __esm(() => {
207849
208674
  });
207850
208675
 
207851
208676
  // src/lib/private-dir.ts
207852
- import { lstat as lstat23, mkdir as mkdir76, open as open10, readFile as readFile194 } from "fs/promises";
207853
- import path378 from "path";
208677
+ import { lstat as lstat23, mkdir as mkdir76, open as open11, readFile as readFile195 } from "fs/promises";
208678
+ import path379 from "path";
207854
208679
  async function checkPrivateDirGitignore(dir, root) {
207855
208680
  if (root !== undefined) {
207856
- const rootResolved = path378.resolve(root);
207857
- const dirResolved = path378.resolve(dir);
207858
- const relative = path378.relative(rootResolved, dirResolved);
207859
- const relativePosix = relative.split(path378.sep).join("/");
208681
+ const rootResolved = path379.resolve(root);
208682
+ const dirResolved = path379.resolve(dir);
208683
+ const relative = path379.relative(rootResolved, dirResolved);
208684
+ const relativePosix = relative.split(path379.sep).join("/");
207860
208685
  const refusal = await refuseEscapingSymlink(rootResolved, relativePosix);
207861
208686
  if (refusal !== undefined) {
207862
208687
  return {
@@ -207886,7 +208711,7 @@ async function checkPrivateDirGitignore(dir, root) {
207886
208711
  message: `${dir} is a symlink; keryx will not manage a private directory reached through a symlink.`
207887
208712
  };
207888
208713
  }
207889
- const gitignorePath = path378.join(dir, ".gitignore");
208714
+ const gitignorePath = path379.join(dir, ".gitignore");
207890
208715
  let stats;
207891
208716
  try {
207892
208717
  stats = await lstat23(gitignorePath);
@@ -207916,7 +208741,7 @@ async function checkPrivateDirGitignore(dir, root) {
207916
208741
  }
207917
208742
  let bytes;
207918
208743
  try {
207919
- bytes = await readFile194(gitignorePath);
208744
+ bytes = await readFile195(gitignorePath);
207920
208745
  } catch (error) {
207921
208746
  return {
207922
208747
  ok: false,
@@ -207938,10 +208763,10 @@ async function ensurePrivateDirGitignore(dir, root) {
207938
208763
  if (!check.ok || check.action === "present") {
207939
208764
  return check;
207940
208765
  }
207941
- const gitignorePath = path378.join(dir, ".gitignore");
208766
+ const gitignorePath = path379.join(dir, ".gitignore");
207942
208767
  await mkdir76(dir, { recursive: true });
207943
208768
  try {
207944
- const handle = await open10(gitignorePath, "wx");
208769
+ const handle = await open11(gitignorePath, "wx");
207945
208770
  try {
207946
208771
  await handle.writeFile(PRIVATE_DIR_GITIGNORE, "utf8");
207947
208772
  } finally {
@@ -208354,9 +209179,9 @@ var init_plan2 = __esm(() => {
208354
209179
  });
208355
209180
 
208356
209181
  // src/bundle/audit.ts
208357
- import { mkdtemp as mkdtemp9, mkdir as mkdir77, rm as rm23, writeFile as writeFile53 } from "fs/promises";
209182
+ import { mkdtemp as mkdtemp9, mkdir as mkdir77, rm as rm24, writeFile as writeFile53 } from "fs/promises";
208358
209183
  import { tmpdir as tmpdir16 } from "os";
208359
- import path379 from "path";
209184
+ import path380 from "path";
208360
209185
  function entriesToStage(plan) {
208361
209186
  return plan.entries.filter((e) => e.bucket === "new" || e.bucket === "update" || e.bucket === "conflict" && e.forced);
208362
209187
  }
@@ -208366,12 +209191,12 @@ async function auditBundlePlan(plan, opts = {}) {
208366
209191
  return { ok: true, refusals: [], report: null };
208367
209192
  }
208368
209193
  const runAudit = opts.runAudit ?? runHarnessAudit;
208369
- const stagingDir = await mkdtemp9(path379.join(tmpdir16(), "keryx-bundle-audit-"));
209194
+ const stagingDir = await mkdtemp9(path380.join(tmpdir16(), "keryx-bundle-audit-"));
208370
209195
  try {
208371
209196
  try {
208372
209197
  for (const entry of toStage) {
208373
- const abs = path379.join(stagingDir, ...entry.path.split("/"));
208374
- await mkdir77(path379.dirname(abs), { recursive: true });
209198
+ const abs = path380.join(stagingDir, ...entry.path.split("/"));
209199
+ await mkdir77(path380.dirname(abs), { recursive: true });
208375
209200
  await writeFile53(abs, entry.bytes);
208376
209201
  }
208377
209202
  } catch (err) {
@@ -208396,7 +209221,7 @@ async function auditBundlePlan(plan, opts = {}) {
208396
209221
  }
208397
209222
  return { ok: refusals.length === 0, refusals, report };
208398
209223
  } finally {
208399
- await rm23(stagingDir, { recursive: true, force: true });
209224
+ await rm24(stagingDir, { recursive: true, force: true });
208400
209225
  }
208401
209226
  }
208402
209227
  var init_audit = __esm(() => {
@@ -208405,8 +209230,8 @@ var init_audit = __esm(() => {
208405
209230
  });
208406
209231
 
208407
209232
  // src/bundle/apply.ts
208408
- import { mkdir as mkdir78, rmdir as rmdir2, stat as stat37 } from "fs/promises";
208409
- import path380 from "path";
209233
+ import { mkdir as mkdir78, rmdir as rmdir2, stat as stat38 } from "fs/promises";
209234
+ import path381 from "path";
208410
209235
  function writableEntries(plan) {
208411
209236
  return plan.entries.filter((e) => e.bucket === "new" || e.bucket === "update" || e.bucket === "conflict" && e.forced);
208412
209237
  }
@@ -208477,7 +209302,7 @@ async function applyBundlePlan(plan, auditResult, opts = {}) {
208477
209302
  throw new ApplyAbort({ ...previousFile.refusal, path: entry.path });
208478
209303
  }
208479
209304
  const previousBytes = previousFile.bytes ?? null;
208480
- const createdDirs = await mkdirRecordingCreated(path380.dirname(entry.targetPath));
209305
+ const createdDirs = await mkdirRecordingCreated(path381.dirname(entry.targetPath));
208481
209306
  for (const dir of [...createdDirs].reverse())
208482
209307
  rollback.push({ kind: "dir", absolutePath: dir });
208483
209308
  rollback.push({ kind: "file", root, rel: entry.targetRelative, previousBytes });
@@ -208560,14 +209385,14 @@ async function applyBundlePlan(plan, auditResult, opts = {}) {
208560
209385
  }
208561
209386
  async function mkdirRecordingCreated(dir) {
208562
209387
  const missing = [];
208563
- let current = path380.resolve(dir);
209388
+ let current = path381.resolve(dir);
208564
209389
  for (;; ) {
208565
209390
  try {
208566
- await stat37(current);
209391
+ await stat38(current);
208567
209392
  break;
208568
209393
  } catch {
208569
209394
  missing.push(current);
208570
- const parent = path380.dirname(current);
209395
+ const parent = path381.dirname(current);
208571
209396
  if (parent === current)
208572
209397
  break;
208573
209398
  current = parent;
@@ -208582,9 +209407,9 @@ function resolveLedgerPathForEntry(entry, opts) {
208582
209407
  if (entry.targetScope === "user") {
208583
209408
  return userStorePaths(opts.env ?? process.env, opts.homeDir).appliedState;
208584
209409
  }
208585
- const metaprojectIdx = entry.targetPath.indexOf(`${path380.sep}.metaproject${path380.sep}`);
208586
- const metaprojectRoot = metaprojectIdx === -1 ? path380.join(path380.dirname(entry.targetPath), ".metaproject") : entry.targetPath.slice(0, metaprojectIdx + `${path380.sep}.metaproject`.length);
208587
- return path380.join(metaprojectRoot, "data", "bundles", "applied-state.json");
209410
+ const metaprojectIdx = entry.targetPath.indexOf(`${path381.sep}.metaproject${path381.sep}`);
209411
+ const metaprojectRoot = metaprojectIdx === -1 ? path381.join(path381.dirname(entry.targetPath), ".metaproject") : entry.targetPath.slice(0, metaprojectIdx + `${path381.sep}.metaproject`.length);
209412
+ return path381.join(metaprojectRoot, "data", "bundles", "applied-state.json");
208588
209413
  }
208589
209414
  var ApplyAbort;
208590
209415
  var init_apply4 = __esm(() => {
@@ -208660,26 +209485,26 @@ var init_inspect = __esm(() => {
208660
209485
  });
208661
209486
 
208662
209487
  // src/bundle/uninstall.ts
208663
- import path381 from "path";
209488
+ import path382 from "path";
208664
209489
  function isContained(root, candidate) {
208665
- const rel = path381.relative(root, candidate);
208666
- return rel === "" || !rel.startsWith(`..${path381.sep}`) && rel !== ".." && !path381.isAbsolute(rel);
209490
+ const rel = path382.relative(root, candidate);
209491
+ return rel === "" || !rel.startsWith(`..${path382.sep}`) && rel !== ".." && !path382.isAbsolute(rel);
208667
209492
  }
208668
209493
  async function removeNowEmptyParents(root, relFilePath) {
208669
- let relDir = path381.posix.dirname(relFilePath);
209494
+ let relDir = path382.posix.dirname(relFilePath);
208670
209495
  while (relDir !== "." && relDir !== "/" && relDir !== "") {
208671
209496
  if (PROTECTED_KIND_ROOTS.has(relDir))
208672
209497
  break;
208673
209498
  const removed = await rmdirIfEmptyContained(root, relDir).catch(() => false);
208674
209499
  if (!removed)
208675
209500
  break;
208676
- relDir = path381.posix.dirname(relDir);
209501
+ relDir = path382.posix.dirname(relDir);
208677
209502
  }
208678
209503
  }
208679
209504
  async function uninstallBundle(opts) {
208680
209505
  const ctx = { projectRoot: opts.projectRoot, env: opts.env, homeDir: opts.homeDir };
208681
209506
  const root = scopeRoot(opts.targetScope, ctx);
208682
- const rootResolved = path381.resolve(root);
209507
+ const rootResolved = path382.resolve(root);
208683
209508
  const ledgerPath = appliedStatePath(opts.targetScope, ctx);
208684
209509
  const ledgerRead = await readAppliedState(ledgerPath);
208685
209510
  if (!ledgerRead.ok) {
@@ -208719,7 +209544,7 @@ async function uninstallBundle(opts) {
208719
209544
  if (!kindCheck.ok) {
208720
209545
  return { ok: false, refusals: [{ reason: BUNDLE_REFUSAL.corruptLedger, path: relPath, message: `applied-state ledger record "${relPath}" does not match its recorded kind "${record.kind}": ${kindCheck.refusal.message}` }], removed: [], kept: [], missing: [] };
208721
209546
  }
208722
- const absolute = path381.resolve(rootResolved, normalized.path);
209547
+ const absolute = path382.resolve(rootResolved, normalized.path);
208723
209548
  if (!isContained(rootResolved, absolute)) {
208724
209549
  return { ok: false, refusals: [{ reason: BUNDLE_REFUSAL.corruptLedger, path: relPath, message: `applied-state ledger record "${relPath}" resolves outside the scope root` }], removed: [], kept: [], missing: [] };
208725
209550
  }
@@ -208798,7 +209623,7 @@ var init_service20 = __esm(() => {
208798
209623
  });
208799
209624
 
208800
209625
  // src/commands/bundle.ts
208801
- import path382 from "path";
209626
+ import path383 from "path";
208802
209627
  async function bundleCommand(args, cwd = process.cwd()) {
208803
209628
  const sub = args[0];
208804
209629
  if (sub === undefined || sub === "--help" || sub === "-h") {
@@ -208931,7 +209756,7 @@ async function handleExport(args, cwd) {
208931
209756
  const outcome = await exportBundle({
208932
209757
  projectRoot,
208933
209758
  scope: scopeArg,
208934
- out: path382.resolve(cwd, out),
209759
+ out: path383.resolve(cwd, out),
208935
209760
  include: include.length > 0 ? include : undefined,
208936
209761
  kinds: kindsRaw.length > 0 ? kindsRaw : undefined,
208937
209762
  bundleId,
@@ -208976,7 +209801,7 @@ async function handleImport(args, cwd) {
208976
209801
  }
208977
209802
  const projectRoot = resolveBundleProjectRoot(cwd);
208978
209803
  if (external) {
208979
- await handleImportExternal(path382.resolve(cwd, target), projectRoot, { dryRun, json });
209804
+ await handleImportExternal(path383.resolve(cwd, target), projectRoot, { dryRun, json });
208980
209805
  return;
208981
209806
  }
208982
209807
  const targetScopeArg = optionValue([...args], "--target-scope");
@@ -209003,7 +209828,7 @@ async function handleImport(args, cwd) {
209003
209828
  return;
209004
209829
  }
209005
209830
  }
209006
- const opened = await openBundle(path382.resolve(cwd, target));
209831
+ const opened = await openBundle(path383.resolve(cwd, target));
209007
209832
  if (!opened.ok) {
209008
209833
  printRefusals([opened.refusal], json);
209009
209834
  process.exitCode = 1;
@@ -209181,7 +210006,7 @@ async function handleInspect(args, cwd) {
209181
210006
  return;
209182
210007
  }
209183
210008
  const projectRoot = resolveBundleProjectRoot(cwd);
209184
- const result = await inspectBundle(path382.resolve(cwd, bundlePath), {
210009
+ const result = await inspectBundle(path383.resolve(cwd, bundlePath), {
209185
210010
  projectRoot,
209186
210011
  targetScope: targetScopeArg
209187
210012
  });
@@ -209225,7 +210050,7 @@ async function handleVerify(args, cwd) {
209225
210050
  failUsage("keryx bundle verify requires a <bundle> argument (or --external-imports)");
209226
210051
  return;
209227
210052
  }
209228
- const outcome = await verifyBundlePath(path382.resolve(cwd, bundlePath));
210053
+ const outcome = await verifyBundlePath(path383.resolve(cwd, bundlePath));
209229
210054
  if (!outcome.ok) {
209230
210055
  printRefusals(outcome.refusals, json);
209231
210056
  process.exitCode = 1;
@@ -209368,23 +210193,23 @@ var init_bundle = __esm(() => {
209368
210193
 
209369
210194
  // src/commands/learn.ts
209370
210195
  import { existsSync as existsSync60 } from "fs";
209371
- import { readFile as readFile195 } from "fs/promises";
210196
+ import { readFile as readFile196 } from "fs/promises";
209372
210197
  import { createInterface as createInterface4 } from "readline/promises";
209373
- import path383 from "path";
210198
+ import path384 from "path";
209374
210199
  function resolveRoot2(deps) {
209375
210200
  return resolveProjectRoot2(deps.cwd ?? process.cwd());
209376
210201
  }
209377
210202
  function resolveLearnRoot(startDir, env) {
209378
- const hasMetaproject = (dir) => existsSync60(path383.join(dir, ".metaproject"));
210203
+ const hasMetaproject = (dir) => existsSync60(path384.join(dir, ".metaproject"));
209379
210204
  const fromEnv = env.CLAUDE_PROJECT_DIR;
209380
210205
  if (typeof fromEnv === "string" && fromEnv.length > 0 && hasMetaproject(fromEnv)) {
209381
210206
  return fromEnv;
209382
210207
  }
209383
- let current = path383.resolve(startDir);
210208
+ let current = path384.resolve(startDir);
209384
210209
  for (;; ) {
209385
210210
  if (hasMetaproject(current))
209386
210211
  return current;
209387
- const parent = path383.dirname(current);
210212
+ const parent = path384.dirname(current);
209388
210213
  if (parent === current)
209389
210214
  return;
209390
210215
  current = parent;
@@ -209480,7 +210305,7 @@ async function runObserveHook(deps) {
209480
210305
  }
209481
210306
  async function countObservationLines(filePath) {
209482
210307
  try {
209483
- const raw = await readFile195(filePath, "utf8");
210308
+ const raw = await readFile196(filePath, "utf8");
209484
210309
  return raw.split(`
209485
210310
  `).filter((line) => line.length > 0).length;
209486
210311
  } catch {
@@ -210217,7 +211042,7 @@ var init_group_subcommands = __esm(() => {
210217
211042
  ["trigger", ["run", "install", "uninstall", "list", "status", "schedule", "resolve"]],
210218
211043
  ["schedule", ["add", "list", "show", "pause", "resume", "run", "remove"]],
210219
211044
  ["governance", ["report", "show"]],
210220
- ["decisions", ["open", "answer", "reason", "report"]],
211045
+ ["decisions", ["open", "answer", "reason", "report", "import"]],
210221
211046
  ["product", ["index", "open"]],
210222
211047
  ["hooks", ["list", "validate", "test", "enable", "disable", "trust", "untrust"]],
210223
211048
  ["bundle", ["export", "import", "inspect", "verify", "uninstall"]],
@@ -210521,9 +211346,11 @@ var VERSION3, CLI_ROUTES, MCP_KNOWN_SUBCOMMANDS, USAGE_BODY = `Usage:
210521
211346
  Spend, confirmations, signatures and gate outcomes,
210522
211347
  unified across flows; writes latest.md/latest.json
210523
211348
  keryx governance show [--json] Reprint the most recently written governance report
210524
- keryx decisions open|answer|reason|report Recommendation journal: record a question with options and its
211349
+ keryx decisions open|answer|reason|report [--json|--line]|import <file.jsonl> [--dry-run] [--json]
211350
+ Recommendation journal: record a question with options and its
210525
211351
  recommendation before showing it, the human's choice after;
210526
- report = match share by mode and stage, deviations (no model)
211352
+ report = match share by mode and stage, deviations (no model);
211353
+ import = earlier decisions from a JSON-lines file, kept apart
210527
211354
  keryx product index [--json] Read every flow and requirements package into a disposable intent index; reports entries with no stated intent
210528
211355
  keryx product open [--json] Intents closed in code with no recorded look back, each with its outcome criterion
210529
211356
  keryx hooks list [--json] Resolved keryx shell lifecycle hooks (built-in -> user -> project)