sessionpipe-core 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,21 @@
1
- // sessionpipe-core 0.1.0 · Apache-2.0 · https://sessionpipe.org
1
+ // sessionpipe-core 0.2.0 · Apache-2.0 · https://sessionpipe.org
2
+ var __defProp = Object.defineProperty;
3
+ var __export = (target, all) => {
4
+ for (var name in all)
5
+ __defProp(target, name, { get: all[name], enumerable: true });
6
+ };
2
7
 
3
8
  // src/adapters/antigravity.ts
4
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, realpathSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "node:fs";
9
+ import {
10
+ existsSync as existsSync2,
11
+ mkdirSync as mkdirSync2,
12
+ readdirSync as readdirSync2,
13
+ readFileSync as readFileSync2,
14
+ realpathSync,
15
+ statSync as statSync2,
16
+ unlinkSync as unlinkSync2,
17
+ writeFileSync as writeFileSync2
18
+ } from "node:fs";
5
19
  import path4 from "node:path";
6
20
 
7
21
  // src/paths.ts
@@ -228,28 +242,67 @@ function readAntigravity(lines, from) {
228
242
  }
229
243
 
230
244
  // src/adapters/claude-shaped.ts
231
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
245
+ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
232
246
  import path3 from "node:path";
233
247
  var OURS = /sessionpipe[^"\n]*[\\/]hook\.js"?\s+[a-z][a-z-]*\s+[A-Za-z]+\s*$/;
234
248
  var isOurs = (h) => Array.isArray(h?.hooks) && h.hooks.some((x) => typeof x.command === "string" && OURS.test(x.command));
235
- function readJson(file) {
249
+ function readJsonFile(file) {
250
+ let raw;
236
251
  try {
237
- return JSON.parse(readFileSync(file, "utf8"));
252
+ raw = readFileSync(file, "utf8");
238
253
  } catch {
239
- return {};
254
+ return { ok: true, existed: false, value: {}, raw: "", indent: " ", eol: "\n", finalNewline: true };
240
255
  }
256
+ let value;
257
+ try {
258
+ value = JSON.parse(raw);
259
+ } catch {
260
+ const hint = /^\s*\/\/|\/\*/m.test(raw) ? "not plain JSON (it has comments)" : "not plain JSON";
261
+ return { ok: false, existed: true, reason: hint };
262
+ }
263
+ if (!value || typeof value !== "object" || Array.isArray(value))
264
+ return { ok: false, existed: true, reason: "not a JSON object" };
265
+ const eol = raw.includes("\r\n") ? "\r\n" : "\n";
266
+ const m = /^( +|\t+)\S/m.exec(raw);
267
+ const indent = m?.[1] ?? " ";
268
+ return {
269
+ ok: true,
270
+ existed: true,
271
+ value,
272
+ raw,
273
+ indent,
274
+ eol,
275
+ finalNewline: /\r?\n$/.test(raw)
276
+ };
277
+ }
278
+ function formatJson(value, f) {
279
+ const text = JSON.stringify(value, null, f.indent).replace(/\n/g, f.eol);
280
+ return f.finalNewline ? text + f.eol : text;
241
281
  }
242
282
  function entryFor(event, harness, cmd, o) {
243
283
  const matcher = o.matcherFor(event);
244
284
  return {
245
285
  ...matcher !== void 0 ? { matcher } : {},
246
- hooks: [{ type: "command", command: cmd([harness, event]).command, timeout: o.timeout }]
286
+ hooks: [{ type: "command", command: cmd([harness, event]).command, timeout: o.timeoutFor?.(event) ?? o.timeout }]
247
287
  };
248
288
  }
249
289
  function installClaudeShaped(file, harness, events, cmd, o) {
250
- const settings2 = readJson(file);
290
+ const f = readJsonFile(file);
291
+ if (!f.ok) return { file, changed: false, skipped: true, note: `${f.reason}; not touched` };
292
+ const settings2 = f.value;
251
293
  const hooks = settings2.hooks && typeof settings2.hooks === "object" ? settings2.hooks : {};
252
294
  let changed = false;
295
+ const want = new Set(events);
296
+ for (const ev of Object.keys(hooks)) {
297
+ if (want.has(ev)) continue;
298
+ const list = Array.isArray(hooks[ev]) ? hooks[ev] : [];
299
+ const kept = list.filter((h) => !isOurs(h));
300
+ if (kept.length !== list.length) {
301
+ changed = true;
302
+ if (kept.length) hooks[ev] = kept;
303
+ else delete hooks[ev];
304
+ }
305
+ }
253
306
  for (const ev of events) {
254
307
  const list = Array.isArray(hooks[ev]) ? hooks[ev] : [];
255
308
  const entry2 = entryFor(ev, harness, cmd, o);
@@ -268,19 +321,19 @@ function installClaudeShaped(file, harness, events, cmd, o) {
268
321
  if (changed) {
269
322
  settings2.hooks = hooks;
270
323
  mkdirSync(path3.dirname(file), { recursive: true });
271
- writeFileSync(file, `${JSON.stringify(settings2, null, 2)}
272
- `);
324
+ writeFileSync(file, formatJson(settings2, f));
273
325
  }
274
- return { file, changed };
326
+ return { file, changed, created: !f.existed && changed };
275
327
  }
276
- function uninstallClaudeShaped(file, events) {
328
+ function uninstallClaudeShaped(file, events, opts = {}) {
277
329
  if (!existsSync(file)) return { file, changed: false };
278
- const raw = readFileSync(file, "utf8");
279
- const settings2 = readJson(file);
330
+ const f = readJsonFile(file);
331
+ if (!f.ok) return { file, changed: false, skipped: true, note: `${f.reason}; not touched` };
332
+ const settings2 = f.value;
280
333
  const hooks = settings2.hooks;
281
334
  if (!hooks || typeof hooks !== "object") return { file, changed: false };
282
335
  let changed = false;
283
- for (const ev of events) {
336
+ for (const ev of [...events, ...Object.keys(hooks)]) {
284
337
  const list = Array.isArray(hooks[ev]) ? hooks[ev] : [];
285
338
  const kept = list.filter((h) => !isOurs(h));
286
339
  if (kept.length !== list.length) {
@@ -291,13 +344,17 @@ function uninstallClaudeShaped(file, events) {
291
344
  }
292
345
  if (!changed) return { file, changed: false };
293
346
  if (!Object.keys(hooks).length) delete settings2.hooks;
294
- const next = `${JSON.stringify(settings2, null, 2)}
295
- `;
296
- if (next !== raw) writeFileSync(file, next);
347
+ if (opts.created && !Object.keys(settings2).length) {
348
+ unlinkSync(file);
349
+ return { file, changed: true, note: "removed (sessionpipe created it)" };
350
+ }
351
+ const next = formatJson(settings2, f);
352
+ if (next !== f.raw) writeFileSync(file, next);
297
353
  return { file, changed: true };
298
354
  }
299
355
  function installedClaudeShaped(file, harness, events, cmd, o) {
300
- const hooks = readJson(file).hooks ?? {};
356
+ const f = readJsonFile(file);
357
+ const hooks = f.ok ? f.value.hooks ?? {} : {};
301
358
  let present = 0;
302
359
  let current = 0;
303
360
  for (const ev of events) {
@@ -306,6 +363,9 @@ function installedClaudeShaped(file, harness, events, cmd, o) {
306
363
  if (mine.length) present++;
307
364
  if (mine.length === 1 && JSON.stringify(mine[0]) === JSON.stringify(entryFor(ev, harness, cmd, o))) current++;
308
365
  }
366
+ for (const ev of Object.keys(hooks))
367
+ if (!events.includes(ev) && (Array.isArray(hooks[ev]) ? hooks[ev] : []).some((h) => isOurs(h)))
368
+ return "stale";
309
369
  if (!present) return "missing";
310
370
  return current === events.length ? "current" : "stale";
311
371
  }
@@ -320,10 +380,13 @@ var projectsDir = (env) => path4.join(gemini(env), "config", "projects");
320
380
  var dataDirs = (env) => ["antigravity", "antigravity-cli", "antigravity-ide"].map((d) => path4.join(gemini(env), d));
321
381
  var ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
322
382
  var validTool = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
323
- function entry(cmd) {
383
+ function entry(cmd, lean) {
324
384
  const handler = (e) => ({ type: "command", command: cmd([NAME, e]).command, timeout: 5 });
325
385
  const out = { enabled: true };
326
- for (const e of AG_EVENTS) out[e] = /Tool/.test(e) ? [{ matcher: "*", hooks: [handler(e)] }] : [handler(e)];
386
+ for (const e of AG_EVENTS) {
387
+ if (lean && e === "PreToolUse") continue;
388
+ out[e] = /Tool/.test(e) ? [{ matcher: "*", hooks: [handler(e)] }] : [handler(e)];
389
+ }
327
390
  return out;
328
391
  }
329
392
  var antigravity = {
@@ -331,34 +394,49 @@ var antigravity = {
331
394
  events: AG_EVENTS,
332
395
  detect: (env = process.env) => existsSync2(path4.join(gemini(env), "config")) || dataDirs(env).some((d) => existsSync2(d)),
333
396
  configFiles: (env = process.env) => [hooksFile(env)],
334
- install(cmd, env = process.env) {
397
+ install(cmd, env = process.env, opts = {}) {
335
398
  const file = hooksFile(env);
336
- const hooks = readJson(file);
337
- const e = entry(cmd);
399
+ const f = readJsonFile(file);
400
+ if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
401
+ const hooks = f.value;
402
+ const e = entry(cmd, opts.lean);
338
403
  if (JSON.stringify(hooks[HOOK_NAME]) === JSON.stringify(e)) return [{ file, changed: false }];
339
404
  hooks[HOOK_NAME] = e;
340
405
  mkdirSync2(path4.dirname(file), { recursive: true });
341
- writeFileSync2(file, `${JSON.stringify(hooks, null, 2)}
342
- `);
406
+ writeFileSync2(file, formatJson(hooks, f));
343
407
  return [
344
- { file, changed: true, note: "hooks.json is read when a conversation starts; open ones report after a restart" }
408
+ {
409
+ file,
410
+ changed: true,
411
+ created: !f.existed,
412
+ note: "hooks.json is read when a conversation starts; open ones report after a restart"
413
+ }
345
414
  ];
346
415
  },
347
- uninstall(env = process.env) {
416
+ uninstall(env = process.env, opts = {}) {
348
417
  const file = hooksFile(env);
349
418
  if (!existsSync2(file)) return [{ file, changed: false }];
350
- const hooks = readJson(file);
419
+ const f = readJsonFile(file);
420
+ if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
421
+ const hooks = f.value;
351
422
  if (!(HOOK_NAME in hooks)) return [{ file, changed: false }];
352
423
  delete hooks[HOOK_NAME];
353
- writeFileSync2(file, `${JSON.stringify(hooks, null, 2)}
354
- `);
424
+ if (opts.created?.includes(file) && !Object.keys(hooks).length) {
425
+ unlinkSync2(file);
426
+ return [{ file, changed: true, note: "removed (sessionpipe created it)" }];
427
+ }
428
+ writeFileSync2(file, formatJson(hooks, f));
355
429
  return [{ file, changed: true }];
356
430
  },
357
- installed(cmd, env = process.env) {
431
+ installed(cmd, env = process.env, opts = {}) {
358
432
  const file = hooksFile(env);
359
- const have = readJson(file)[HOOK_NAME];
433
+ const f = readJsonFile(file);
434
+ const have = f.ok ? f.value[HOOK_NAME] : void 0;
360
435
  return [
361
- { file, state: !have ? "missing" : JSON.stringify(have) === JSON.stringify(entry(cmd)) ? "current" : "stale" }
436
+ {
437
+ file,
438
+ state: !have ? "missing" : JSON.stringify(have) === JSON.stringify(entry(cmd, opts.lean)) ? "current" : "stale"
439
+ }
362
440
  ];
363
441
  },
364
442
  fromHook(input) {
@@ -624,9 +702,18 @@ var CLAUDE_EVENTS = [
624
702
  "SubagentStop",
625
703
  "PreCompact",
626
704
  "PostCompact",
627
- "SessionEnd"
705
+ "SessionEnd",
706
+ "StopFailure"
628
707
  ];
629
- var OPTS = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
708
+ var LEAN_SKIP = /* @__PURE__ */ new Set(["PreToolUse", "PostToolUseFailure"]);
709
+ var eventsFor = (lean) => lean ? CLAUDE_EVENTS.filter((e) => !LEAN_SKIP.has(e)) : [...CLAUDE_EVENTS];
710
+ var OPTS = {
711
+ matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0,
712
+ timeout: 5,
713
+ // The hook waits up to 125 s for a verified control answer, and only when control
714
+ // is paired on this machine; everywhere else it exits in milliseconds as before.
715
+ timeoutFor: (e) => e === "PermissionRequest" ? 130 : void 0
716
+ };
630
717
  var DESKTOP_ID = /^local_[A-Za-z0-9-]{1,64}$/;
631
718
  function claudeDirs(env = process.env, home = HOME) {
632
719
  const out = [];
@@ -657,11 +744,13 @@ var claudeCode = {
657
744
  events: CLAUDE_EVENTS,
658
745
  detect: (env = process.env) => claudeDirs(env).some((d) => existsSync3(d)),
659
746
  configFiles: (env = process.env) => claudeDirs(env).map(settingsIn),
660
- install: (cmd, env = process.env) => claudeDirs(env).map((d) => installClaudeShaped(settingsIn(d), NAME2, CLAUDE_EVENTS, cmd, OPTS)),
661
- uninstall: (env = process.env) => claudeDirs(env).map((d) => uninstallClaudeShaped(settingsIn(d), CLAUDE_EVENTS)),
662
- installed: (cmd, env = process.env) => claudeDirs(env).map((d) => ({
747
+ install: (cmd, env = process.env, opts = {}) => claudeDirs(env).map((d) => installClaudeShaped(settingsIn(d), NAME2, eventsFor(opts.lean), cmd, OPTS)),
748
+ uninstall: (env = process.env, opts = {}) => claudeDirs(env).map(
749
+ (d) => uninstallClaudeShaped(settingsIn(d), CLAUDE_EVENTS, { created: opts.created?.includes(settingsIn(d)) })
750
+ ),
751
+ installed: (cmd, env = process.env, opts = {}) => claudeDirs(env).map((d) => ({
663
752
  file: settingsIn(d),
664
- state: installedClaudeShaped(settingsIn(d), NAME2, CLAUDE_EVENTS, cmd, OPTS)
753
+ state: installedClaudeShaped(settingsIn(d), NAME2, eventsFor(opts.lean), cmd, OPTS)
665
754
  })),
666
755
  fromHook(input) {
667
756
  const event = input.argv[1] ?? "";
@@ -752,6 +841,15 @@ var claudeCode = {
752
841
  case "Stop":
753
842
  events.push(ev("turn.ended", { turn_id, reason: "stop" }));
754
843
  break;
844
+ case "StopFailure":
845
+ events.push(
846
+ ev("turn.ended", {
847
+ turn_id,
848
+ reason: "error",
849
+ error: typeof s.error === "string" ? s.error.slice(0, 200) : void 0
850
+ })
851
+ );
852
+ break;
755
853
  case "SubagentStart":
756
854
  events.push(
757
855
  ev("subagent.started", {
@@ -1263,6 +1361,7 @@ var CODEX_EVENTS = [
1263
1361
  "SessionEnd"
1264
1362
  ];
1265
1363
  var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
1364
+ var eventsFor2 = (lean) => lean ? CODEX_EVENTS.filter((e) => e !== "PreToolUse") : [...CODEX_EVENTS];
1266
1365
  var codexHome = (env) => env.CODEX_HOME || path6.join(HOME, ".codex");
1267
1366
  var hooksFile2 = (env) => path6.join(codexHome(env), "hooks.json");
1268
1367
  var configFile2 = (env) => path6.join(codexHome(env), "config.toml");
@@ -1311,7 +1410,8 @@ function installNotify(cmd, env) {
1311
1410
  return {
1312
1411
  file,
1313
1412
  changed: false,
1314
- note: `left alone: notify runs ${n.raw.slice(0, 80)} (hooks.json carries the events; the notify fallback is off)`
1413
+ skipped: true,
1414
+ note: `notify belongs to ${(n.argv?.[0] ?? n.raw).replace(/^.*\//, "").slice(0, 40)}; left alone. Codex will report nothing until you approve the hooks: start \`codex\` and accept the hook review`
1315
1415
  };
1316
1416
  }
1317
1417
  write(withNotify(text, line));
@@ -1368,14 +1468,21 @@ var codex = {
1368
1468
  events: CODEX_EVENTS,
1369
1469
  detect: (env = process.env) => existsSync4(codexHome(env)),
1370
1470
  configFiles: (env = process.env) => [hooksFile2(env), configFile2(env)],
1371
- install: (cmd, env = process.env) => [
1372
- installClaudeShaped(hooksFile2(env), NAME3, CODEX_EVENTS, cmd, OPTS2),
1471
+ install: (cmd, env = process.env, opts = {}) => [
1472
+ installClaudeShaped(hooksFile2(env), NAME3, eventsFor2(opts.lean), cmd, OPTS2),
1373
1473
  installNotify(cmd, env)
1374
1474
  ],
1375
- uninstall: (env = process.env) => [uninstallClaudeShaped(hooksFile2(env), CODEX_EVENTS), uninstallNotify(env)],
1376
- installed: (cmd, env = process.env) => {
1475
+ uninstall: (env = process.env, opts = {}) => [
1476
+ uninstallClaudeShaped(hooksFile2(env), CODEX_EVENTS, { created: opts.created?.includes(hooksFile2(env)) }),
1477
+ uninstallNotify(env)
1478
+ ],
1479
+ installed: (cmd, env = process.env, opts = {}) => {
1377
1480
  const out = [
1378
- { file: hooksFile2(env), state: installedClaudeShaped(hooksFile2(env), NAME3, CODEX_EVENTS, cmd, OPTS2) }
1481
+ {
1482
+ file: hooksFile2(env),
1483
+ state: installedClaudeShaped(hooksFile2(env), NAME3, eventsFor2(opts.lean), cmd, OPTS2),
1484
+ note: "Codex runs non-managed hooks only after the review in `codex` (/hooks); until then nothing is reported unless the notify fallback is ours"
1485
+ }
1379
1486
  ];
1380
1487
  let text = "";
1381
1488
  try {
@@ -1563,6 +1670,7 @@ function codexFacts(file) {
1563
1670
  }
1564
1671
 
1565
1672
  // src/adapters/gemini-cli.ts
1673
+ import { execFileSync as execFileSync3 } from "node:child_process";
1566
1674
  import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
1567
1675
  import path7 from "node:path";
1568
1676
  var NAME4 = "gemini-cli";
@@ -1580,17 +1688,61 @@ var OPTS3 = { matcherFor: (e) => /Tool/.test(e) ? ".*" : void 0, timeout: 5e3 };
1580
1688
  var geminiDir = (env) => env.GEMINI_CLI_HOME || path7.join(HOME, ".gemini");
1581
1689
  var settings = (env) => path7.join(geminiDir(env), "settings.json");
1582
1690
  var validTool4 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
1691
+ var eventsFor3 = (lean) => lean ? GEMINI_EVENTS.filter((e) => e !== "BeforeTool") : [...GEMINI_EVENTS];
1692
+ function geminiHooksActive(settingsFile) {
1693
+ let tools;
1694
+ try {
1695
+ tools = JSON.parse(readFileSync5(settingsFile, "utf8")).tools;
1696
+ } catch {
1697
+ }
1698
+ if (tools?.enableHooks === true) return { active: true };
1699
+ if (tools?.enableHooks === false)
1700
+ return { active: false, note: "inactive: tools.enableHooks is false in settings.json" };
1701
+ let version = "";
1702
+ try {
1703
+ version = execFileSync3("gemini", ["--version"], {
1704
+ encoding: "utf8",
1705
+ timeout: 4e3,
1706
+ stdio: ["ignore", "pipe", "ignore"]
1707
+ }).trim();
1708
+ } catch {
1709
+ return {
1710
+ active: true,
1711
+ note: "could not run `gemini --version`; hooks need tools.enableHooks on versions before 0.46"
1712
+ };
1713
+ }
1714
+ const m = /(\d+)\.(\d+)/.exec(version);
1715
+ if (m && (Number(m[1]) > 0 || Number(m[2]) >= 46)) return { active: true };
1716
+ return {
1717
+ active: false,
1718
+ note: `inactive: Gemini CLI ${version || "?"} runs hooks only with "tools": {"enableHooks": true} in settings.json`
1719
+ };
1720
+ }
1583
1721
  var geminiCli = {
1584
1722
  name: NAME4,
1585
1723
  events: GEMINI_EVENTS,
1586
1724
  // ~/.gemini also belongs to Antigravity; Gemini CLI is here when its settings or its chat logs exist.
1587
1725
  detect: (env = process.env) => existsSync5(settings(env)) || existsSync5(path7.join(geminiDir(env), "tmp")),
1588
1726
  configFiles: (env = process.env) => [settings(env)],
1589
- install: (cmd, env = process.env) => [installClaudeShaped(settings(env), NAME4, GEMINI_EVENTS, cmd, OPTS3)],
1590
- uninstall: (env = process.env) => [uninstallClaudeShaped(settings(env), GEMINI_EVENTS)],
1591
- installed: (cmd, env = process.env) => [
1592
- { file: settings(env), state: installedClaudeShaped(settings(env), NAME4, GEMINI_EVENTS, cmd, OPTS3) }
1727
+ install: (cmd, env = process.env, opts = {}) => {
1728
+ const r = installClaudeShaped(settings(env), NAME4, eventsFor3(opts.lean), cmd, OPTS3);
1729
+ const a = geminiHooksActive(settings(env));
1730
+ return [a.note && !r.skipped ? { ...r, note: [r.note, a.note].filter(Boolean).join("; ") } : r];
1731
+ },
1732
+ uninstall: (env = process.env, opts = {}) => [
1733
+ uninstallClaudeShaped(settings(env), GEMINI_EVENTS, { created: opts.created?.includes(settings(env)) })
1593
1734
  ],
1735
+ installed: (cmd, env = process.env, opts = {}) => {
1736
+ const state = installedClaudeShaped(settings(env), NAME4, eventsFor3(opts.lean), cmd, OPTS3);
1737
+ const a = geminiHooksActive(settings(env));
1738
+ return [
1739
+ {
1740
+ file: settings(env),
1741
+ state: state === "current" && !a.active ? "inactive" : state,
1742
+ ...a.note ? { note: a.note } : {}
1743
+ }
1744
+ ];
1745
+ },
1594
1746
  fromHook(input) {
1595
1747
  const event = input.argv[1] ?? "";
1596
1748
  let s = {};
@@ -1702,17 +1854,366 @@ function prune4(o) {
1702
1854
  return o;
1703
1855
  }
1704
1856
 
1857
+ // src/adapters/claude-code-control.ts
1858
+ var claude_code_control_exports = {};
1859
+ __export(claude_code_control_exports, {
1860
+ JOB_TIMEOUT_MS: () => JOB_TIMEOUT_MS,
1861
+ claudeEnv: () => claudeEnv,
1862
+ detectCaps: () => detectCaps,
1863
+ findClaude: () => findClaude,
1864
+ findTranscript: () => findTranscript,
1865
+ isUuid: () => isUuid,
1866
+ liveProcess: () => liveProcess,
1867
+ modeFlags: () => modeFlags,
1868
+ parseCaps: () => parseCaps,
1869
+ parseResult: () => parseResult,
1870
+ recentlyWritten: () => recentlyWritten,
1871
+ runClaude: () => runClaude,
1872
+ sessionFolder: () => sessionFolder
1873
+ });
1874
+ import { execFileSync as execFileSync4, spawn } from "node:child_process";
1875
+ import { accessSync, closeSync as closeSync2, constants, openSync as openSync2, readdirSync as readdirSync5, readFileSync as readFileSync6, readSync as readSync2, statSync as statSync4 } from "node:fs";
1876
+ import os2 from "node:os";
1877
+ import path8 from "node:path";
1878
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1879
+ var JOB_TIMEOUT_MS = 30 * 6e4;
1880
+ var OUT_CAP = 1024 * 1024;
1881
+ var LIVE_WRITE_MS = 9e4;
1882
+ function isExe(p) {
1883
+ try {
1884
+ if (!statSync4(p).isFile()) return false;
1885
+ accessSync(p, constants.X_OK);
1886
+ return true;
1887
+ } catch {
1888
+ return false;
1889
+ }
1890
+ }
1891
+ var cmpVersion = (a, b) => {
1892
+ const pa = a.split(".").map(Number);
1893
+ const pb = b.split(".").map(Number);
1894
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++)
1895
+ if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) - (pb[i] ?? 0);
1896
+ return 0;
1897
+ };
1898
+ function findClaude(env = process.env, home = os2.homedir()) {
1899
+ if (env.SESSIONPIPE_CLAUDE && isExe(env.SESSIONPIPE_CLAUDE)) return env.SESSIONPIPE_CLAUDE;
1900
+ const names = process.platform === "win32" ? ["claude.exe", "claude.cmd"] : ["claude"];
1901
+ for (const d of String(env.PATH || "").split(path8.delimiter)) {
1902
+ if (!d || !path8.isAbsolute(d)) continue;
1903
+ for (const n of names) if (isExe(path8.join(d, n))) return path8.join(d, n);
1904
+ }
1905
+ for (const p of [
1906
+ path8.join(home, ".claude", "local", "claude"),
1907
+ path8.join(home, ".local", "bin", "claude"),
1908
+ "/opt/homebrew/bin/claude",
1909
+ "/usr/local/bin/claude"
1910
+ ])
1911
+ if (isExe(p)) return p;
1912
+ const app = path8.join(home, "Library", "Application Support", "Claude", "claude-code");
1913
+ let versions = [];
1914
+ try {
1915
+ versions = readdirSync5(app).filter((n) => /^\d+(\.\d+)*$/.test(n)).sort(cmpVersion).reverse();
1916
+ } catch {
1917
+ }
1918
+ for (const v of versions) {
1919
+ const p = path8.join(app, v, "claude.app", "Contents", "MacOS", "claude");
1920
+ if (isExe(p)) return p;
1921
+ }
1922
+ return null;
1923
+ }
1924
+ function optionBlock(help, flag) {
1925
+ const lines = help.split("\n");
1926
+ const re = new RegExp(`^\\s{0,4}(?:-\\w, )?${flag.replace(/-/g, "\\-")}(?![\\w-])`);
1927
+ const start = lines.findIndex((l) => re.test(l));
1928
+ if (start < 0) return null;
1929
+ const out = [lines[start]];
1930
+ for (let i = start + 1; i < lines.length && lines[i]?.trim() && !/^\s{0,4}-/.test(lines[i]); i++)
1931
+ out.push(lines[i]);
1932
+ return out.join(" ");
1933
+ }
1934
+ function parseCaps(help) {
1935
+ const pm = optionBlock(help, "--permission-mode");
1936
+ const pp = optionBlock(help, "--permission-prompts");
1937
+ return {
1938
+ hasMode: !!pm,
1939
+ modes: pm ? [...pm.matchAll(/"([A-Za-z]+)"/g)].map((m) => m[1]) : [],
1940
+ promptsNone: !!pp && /"none"/.test(pp),
1941
+ fork: !!optionBlock(help, "--fork-session")
1942
+ };
1943
+ }
1944
+ function modeFlags(mode, caps) {
1945
+ if (mode === "auto") {
1946
+ if (!caps.modes.includes("auto"))
1947
+ return { error: "this machine is in auto mode, but its Claude Code has no auto permission mode" };
1948
+ return { args: ["--permission-mode", "auto"] };
1949
+ }
1950
+ if (caps.modes.includes("dontAsk")) return { args: ["--permission-mode", "dontAsk"] };
1951
+ const args = [];
1952
+ if (caps.hasMode)
1953
+ args.push(
1954
+ "--permission-mode",
1955
+ caps.modes.includes("manual") && !caps.modes.includes("default") ? "manual" : "default"
1956
+ );
1957
+ if (caps.promptsNone) args.push("--permission-prompts", "none");
1958
+ return { args };
1959
+ }
1960
+ function claudeEnv(configDir, env = process.env) {
1961
+ const e = { ...env };
1962
+ for (const k of [
1963
+ "CLAUDECODE",
1964
+ "CLAUDE_CODE_SESSION_ID",
1965
+ "CLAUDE_CODE_HOST_SESSION_ID",
1966
+ "CLAUDE_CODE_ENTRYPOINT",
1967
+ "CLAUDE_CODE_SESSION_ATTENDED",
1968
+ "CLAUDE_CODE_SSE_PORT"
1969
+ ])
1970
+ delete e[k];
1971
+ e.SESSIONPIPE_CONTROL_JOB = "1";
1972
+ if (configDir !== void 0) {
1973
+ if (path8.resolve(configDir) === path8.resolve(os2.homedir(), ".claude")) delete e.CLAUDE_CONFIG_DIR;
1974
+ else e.CLAUDE_CONFIG_DIR = configDir;
1975
+ }
1976
+ return e;
1977
+ }
1978
+ function detectCaps(bin) {
1979
+ let help = "";
1980
+ try {
1981
+ help = execFileSync4(bin, ["--help"], {
1982
+ encoding: "utf8",
1983
+ timeout: 3e4,
1984
+ env: claudeEnv(),
1985
+ stdio: ["ignore", "pipe", "pipe"],
1986
+ maxBuffer: 4 * 1024 * 1024
1987
+ });
1988
+ } catch (e) {
1989
+ help = String(e?.stdout || "");
1990
+ }
1991
+ return parseCaps(help);
1992
+ }
1993
+ function findTranscript(session, dirs) {
1994
+ if (!UUID_RE.test(session)) return null;
1995
+ for (const dir of dirs) {
1996
+ let projects = [];
1997
+ try {
1998
+ projects = readdirSync5(path8.join(dir, "projects"), { withFileTypes: true });
1999
+ } catch {
2000
+ continue;
2001
+ }
2002
+ for (const p of projects) {
2003
+ if (!p.isDirectory()) continue;
2004
+ const file = path8.join(dir, "projects", p.name, `${session}.jsonl`);
2005
+ try {
2006
+ if (statSync4(file).isFile()) return { file, configDir: dir, project: p.name };
2007
+ } catch {
2008
+ }
2009
+ }
2010
+ }
2011
+ return null;
2012
+ }
2013
+ var projectName = (cwd) => cwd.replace(/[^a-zA-Z0-9]/g, "-");
2014
+ function sessionFolder(t, cap2 = 8 * 1024 * 1024) {
2015
+ let first = null;
2016
+ let count = 0;
2017
+ let fd;
2018
+ const look = (line) => {
2019
+ if (line.indexOf('"cwd"') < 0) return null;
2020
+ let j;
2021
+ try {
2022
+ j = JSON.parse(line.toString("utf8"));
2023
+ } catch {
2024
+ return null;
2025
+ }
2026
+ if (!j || typeof j.cwd !== "string" || !path8.isAbsolute(j.cwd)) return null;
2027
+ if (projectName(j.cwd) === t.project) return j.cwd;
2028
+ first ??= j.cwd;
2029
+ count++;
2030
+ return null;
2031
+ };
2032
+ try {
2033
+ fd = openSync2(t.file, "r");
2034
+ const buf = Buffer.alloc(256 * 1024);
2035
+ let pos = 0;
2036
+ let rest = Buffer.alloc(0);
2037
+ let skipping = false;
2038
+ while (pos < cap2 && count < 50) {
2039
+ const n = readSync2(fd, buf, 0, buf.length, pos);
2040
+ if (!n) {
2041
+ if (!skipping && rest.length) {
2042
+ const hit = look(rest);
2043
+ if (hit) return hit;
2044
+ }
2045
+ break;
2046
+ }
2047
+ pos += n;
2048
+ let chunk = rest.length ? Buffer.concat([rest, buf.subarray(0, n)]) : buf.subarray(0, n);
2049
+ let nl = chunk.indexOf(10);
2050
+ while (nl >= 0) {
2051
+ const line = chunk.subarray(0, nl);
2052
+ chunk = chunk.subarray(nl + 1);
2053
+ if (skipping) skipping = false;
2054
+ else {
2055
+ const hit = look(line);
2056
+ if (hit) return hit;
2057
+ }
2058
+ nl = chunk.indexOf(10);
2059
+ }
2060
+ if (chunk.length > 2 * 1024 * 1024) {
2061
+ skipping = true;
2062
+ rest = Buffer.alloc(0);
2063
+ } else rest = skipping ? Buffer.alloc(0) : Buffer.from(chunk);
2064
+ }
2065
+ } catch {
2066
+ } finally {
2067
+ if (fd !== void 0)
2068
+ try {
2069
+ closeSync2(fd);
2070
+ } catch {
2071
+ }
2072
+ }
2073
+ return first;
2074
+ }
2075
+ function pidAlive(pid) {
2076
+ if (!Number.isSafeInteger(pid) || pid <= 1) return false;
2077
+ try {
2078
+ process.kill(pid, 0);
2079
+ return true;
2080
+ } catch (e) {
2081
+ return e.code === "EPERM";
2082
+ }
2083
+ }
2084
+ function liveProcess(session, configDir) {
2085
+ let names = [];
2086
+ try {
2087
+ names = readdirSync5(path8.join(configDir, "sessions")).filter((n) => /^\d+\.json$/.test(n));
2088
+ } catch {
2089
+ return false;
2090
+ }
2091
+ for (const n of names) {
2092
+ try {
2093
+ const j = JSON.parse(readFileSync6(path8.join(configDir, "sessions", n), "utf8"));
2094
+ if (String(j.sessionId) !== session) continue;
2095
+ if (pidAlive(Number(j.pid ?? n.slice(0, -5)))) return true;
2096
+ } catch {
2097
+ }
2098
+ }
2099
+ return false;
2100
+ }
2101
+ function recentlyWritten(file, ourLastEnd, now = Date.now()) {
2102
+ let m;
2103
+ try {
2104
+ m = statSync4(file).mtimeMs;
2105
+ } catch {
2106
+ return false;
2107
+ }
2108
+ if (now - m > LIVE_WRITE_MS) return false;
2109
+ return !(ourLastEnd && m <= ourLastEnd + 2e3);
2110
+ }
2111
+ function runClaude(bin, args, o) {
2112
+ return new Promise((resolve) => {
2113
+ let out = [];
2114
+ let outLen = 0;
2115
+ let tooBig = false;
2116
+ let errTail = "";
2117
+ let timedOut = false;
2118
+ let settled = false;
2119
+ let child;
2120
+ try {
2121
+ child = spawn(bin, args, {
2122
+ cwd: o.cwd,
2123
+ env: o.env,
2124
+ stdio: ["ignore", "pipe", "pipe"],
2125
+ detached: process.platform !== "win32",
2126
+ windowsHide: true
2127
+ });
2128
+ } catch (e) {
2129
+ resolve({ error: e.message });
2130
+ return;
2131
+ }
2132
+ const kill = (sig) => {
2133
+ try {
2134
+ if (process.platform !== "win32" && child.pid) process.kill(-child.pid, sig);
2135
+ else child.kill(sig);
2136
+ } catch {
2137
+ try {
2138
+ child.kill(sig);
2139
+ } catch {
2140
+ }
2141
+ }
2142
+ };
2143
+ child.stdout?.on("data", (b) => {
2144
+ if (tooBig) return;
2145
+ if (outLen + b.length > OUT_CAP) {
2146
+ tooBig = true;
2147
+ out = [];
2148
+ return;
2149
+ }
2150
+ out.push(b);
2151
+ outLen += b.length;
2152
+ });
2153
+ child.stderr?.on("data", (b) => {
2154
+ errTail = (errTail + b.toString("utf8")).slice(-4e3);
2155
+ });
2156
+ const timer = setTimeout(() => {
2157
+ timedOut = true;
2158
+ kill("SIGTERM");
2159
+ setTimeout(() => kill("SIGKILL"), 1e4).unref();
2160
+ }, o.timeoutMs ?? JOB_TIMEOUT_MS);
2161
+ const result = (code, signal) => ({
2162
+ code,
2163
+ signal,
2164
+ stdout: tooBig ? null : Buffer.concat(out).toString("utf8"),
2165
+ tooBig,
2166
+ stderr: errTail,
2167
+ timedOut
2168
+ });
2169
+ const finish = (r) => {
2170
+ if (settled) return;
2171
+ settled = true;
2172
+ clearTimeout(timer);
2173
+ resolve(r);
2174
+ };
2175
+ child.on("error", (e) => finish({ error: e.message }));
2176
+ child.on("exit", (code, signal) => setTimeout(() => finish(result(code, signal)), 2e3).unref());
2177
+ child.on("close", (code, signal) => finish(result(code, signal)));
2178
+ });
2179
+ }
2180
+ function parseResult(stdout) {
2181
+ const s = String(stdout || "").trim();
2182
+ if (!s) return null;
2183
+ const pick = (j) => {
2184
+ if (Array.isArray(j)) {
2185
+ for (let i = j.length - 1; i >= 0; i--) if (j[i]?.type === "result") return j[i];
2186
+ return null;
2187
+ }
2188
+ return j && typeof j === "object" ? j : null;
2189
+ };
2190
+ try {
2191
+ return pick(JSON.parse(s));
2192
+ } catch {
2193
+ }
2194
+ const lines = s.split("\n");
2195
+ for (let i = lines.length - 1; i >= 0; i--) {
2196
+ try {
2197
+ const j = pick(JSON.parse(lines[i]));
2198
+ if (j && (j.type === "result" || "result" in j)) return j;
2199
+ } catch {
2200
+ }
2201
+ }
2202
+ return null;
2203
+ }
2204
+ var isUuid = (s) => typeof s === "string" && UUID_RE.test(s);
2205
+
1705
2206
  // src/adapters/index.ts
1706
2207
  var ADAPTERS = [claudeCode, codex, geminiCli, antigravity];
1707
2208
  var adapterByName = (name) => ADAPTERS.find((a) => a.name === name);
1708
2209
 
1709
2210
  // src/config.ts
1710
- import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
1711
- import path8 from "node:path";
2211
+ import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
2212
+ import path9 from "node:path";
1712
2213
  var DEFAULT_CONFIG = { sinks: [], harnesses: {}, update_check: true, keep_days: 30 };
1713
2214
  function readConfig(file = configFile()) {
1714
2215
  try {
1715
- const j = JSON.parse(readFileSync6(file, "utf8"));
2216
+ const j = JSON.parse(readFileSync7(file, "utf8"));
1716
2217
  return {
1717
2218
  ...DEFAULT_CONFIG,
1718
2219
  ...j,
@@ -1724,7 +2225,7 @@ function readConfig(file = configFile()) {
1724
2225
  }
1725
2226
  }
1726
2227
  function writeConfig(c, file = configFile()) {
1727
- mkdirSync4(path8.dirname(file), { recursive: true, mode: 448 });
2228
+ mkdirSync4(path9.dirname(file), { recursive: true, mode: 448 });
1728
2229
  writeFileSync4(file, `${JSON.stringify(c, null, 2)}
1729
2230
  `, { mode: 384 });
1730
2231
  if (process.platform !== "win32" && existsSync6(file)) {
@@ -1793,32 +2294,32 @@ function makeEvent(a, ctx) {
1793
2294
  // src/outbox.ts
1794
2295
  import {
1795
2296
  appendFileSync,
1796
- closeSync as closeSync2,
2297
+ closeSync as closeSync3,
1797
2298
  existsSync as existsSync7,
1798
2299
  mkdirSync as mkdirSync5,
1799
- openSync as openSync2,
1800
- readdirSync as readdirSync5,
1801
- readFileSync as readFileSync7,
1802
- readSync as readSync2,
2300
+ openSync as openSync3,
2301
+ readdirSync as readdirSync6,
2302
+ readFileSync as readFileSync8,
2303
+ readSync as readSync3,
1803
2304
  renameSync,
1804
- statSync as statSync4,
1805
- unlinkSync,
2305
+ statSync as statSync5,
2306
+ unlinkSync as unlinkSync3,
1806
2307
  writeFileSync as writeFileSync5
1807
2308
  } from "node:fs";
1808
- import path9 from "node:path";
2309
+ import path10 from "node:path";
1809
2310
  var Outbox = class {
1810
2311
  constructor(root) {
1811
2312
  this.root = root;
1812
2313
  }
1813
2314
  root;
1814
2315
  dir(harness) {
1815
- return path9.join(this.root, "outbox", safeId(harness));
2316
+ return path10.join(this.root, "outbox", safeId(harness));
1816
2317
  }
1817
2318
  file(ref) {
1818
- return path9.join(this.dir(ref.harness), `${safeId(ref.session)}.jsonl`);
2319
+ return path10.join(this.dir(ref.harness), `${safeId(ref.session)}.jsonl`);
1819
2320
  }
1820
2321
  seqFile(ref) {
1821
- return path9.join(this.dir(ref.harness), `${safeId(ref.session)}.seq`);
2322
+ return path10.join(this.dir(ref.harness), `${safeId(ref.session)}.seq`);
1822
2323
  }
1823
2324
  /** The next seq for a session, persisted (survives a hibernating machine and a
1824
2325
  * restart; an in-memory counter would restart at 0 and collide). */
@@ -1826,18 +2327,18 @@ var Outbox = class {
1826
2327
  const f = this.seqFile(ref);
1827
2328
  let n = 0;
1828
2329
  try {
1829
- n = Number(readFileSync7(f, "utf8")) || 0;
2330
+ n = Number(readFileSync8(f, "utf8")) || 0;
1830
2331
  } catch {
1831
2332
  n = this.lastSeqInFile(ref) + 1;
1832
2333
  }
1833
- mkdirSync5(path9.dirname(f), { recursive: true });
1834
- writeFileSync5(f, String(n + 1));
2334
+ mkdirSync5(path10.dirname(f), { recursive: true, mode: 448 });
2335
+ writeFileSync5(f, String(n + 1), { mode: 384 });
1835
2336
  return n;
1836
2337
  }
1837
2338
  lastSeqInFile(ref) {
1838
2339
  const f = this.file(ref);
1839
2340
  if (!existsSync7(f)) return -1;
1840
- const lines = readFileSync7(f, "utf8").trim().split("\n");
2341
+ const lines = readFileSync8(f, "utf8").trim().split("\n");
1841
2342
  for (let i = lines.length - 1; i >= 0; i--) {
1842
2343
  try {
1843
2344
  const j = JSON.parse(lines[i] ?? "");
@@ -1849,26 +2350,26 @@ var Outbox = class {
1849
2350
  }
1850
2351
  append(ref, events) {
1851
2352
  if (!events.length) return;
1852
- mkdirSync5(this.dir(ref.harness), { recursive: true });
2353
+ mkdirSync5(this.dir(ref.harness), { recursive: true, mode: 448 });
1853
2354
  appendFileSync(this.file(ref), `${events.map((e) => JSON.stringify(e)).join("\n")}
1854
- `);
2355
+ `, { mode: 384 });
1855
2356
  }
1856
2357
  /** Every session file, newest first. */
1857
2358
  sessions() {
1858
2359
  const out = [];
1859
- const base = path9.join(this.root, "outbox");
2360
+ const base = path10.join(this.root, "outbox");
1860
2361
  if (!existsSync7(base)) return [];
1861
- for (const h of readdirSync5(base)) {
1862
- const d = path9.join(base, h);
2362
+ for (const h of readdirSync6(base)) {
2363
+ const d = path10.join(base, h);
1863
2364
  let names = [];
1864
2365
  try {
1865
- names = readdirSync5(d).filter((n) => n.endsWith(".jsonl"));
2366
+ names = readdirSync6(d).filter((n) => n.endsWith(".jsonl"));
1866
2367
  } catch {
1867
2368
  continue;
1868
2369
  }
1869
2370
  for (const n of names) {
1870
2371
  try {
1871
- out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime: statSync4(path9.join(d, n)).mtimeMs });
2372
+ out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime: statSync5(path10.join(d, n)).mtimeMs });
1872
2373
  } catch {
1873
2374
  }
1874
2375
  }
@@ -1880,12 +2381,12 @@ var Outbox = class {
1880
2381
  read(ref, from, maxEvents = 500) {
1881
2382
  const f = this.file(ref);
1882
2383
  if (!existsSync7(f)) return { events: [], offset: from };
1883
- const size = statSync4(f).size;
2384
+ const size = statSync5(f).size;
1884
2385
  if (from >= size) return { events: [], offset: from };
1885
- const fd = openSync2(f, "r");
2386
+ const fd = openSync3(f, "r");
1886
2387
  try {
1887
2388
  const buf = Buffer.alloc(Math.min(size - from, 8 * 1024 * 1024));
1888
- const n = readSync2(fd, buf, 0, buf.length, from);
2389
+ const n = readSync3(fd, buf, 0, buf.length, from);
1889
2390
  const text = buf.subarray(0, n).toString("utf8");
1890
2391
  const events = [];
1891
2392
  let offset = from;
@@ -1904,7 +2405,7 @@ var Outbox = class {
1904
2405
  }
1905
2406
  return { events, offset };
1906
2407
  } finally {
1907
- closeSync2(fd);
2408
+ closeSync3(fd);
1908
2409
  }
1909
2410
  }
1910
2411
  /** Files older than `days` are pruned; the cursor files with them. */
@@ -1914,10 +2415,10 @@ var Outbox = class {
1914
2415
  for (const ref of this.sessions()) {
1915
2416
  const f = this.file(ref);
1916
2417
  try {
1917
- if (statSync4(f).mtimeMs < cutoff) {
1918
- unlinkSync(f);
2418
+ if (statSync5(f).mtimeMs < cutoff) {
2419
+ unlinkSync3(f);
1919
2420
  try {
1920
- unlinkSync(this.seqFile(ref));
2421
+ unlinkSync3(this.seqFile(ref));
1921
2422
  } catch {
1922
2423
  }
1923
2424
  n++;
@@ -1930,7 +2431,7 @@ var Outbox = class {
1930
2431
  delete(ref) {
1931
2432
  for (const f of [this.file(ref), this.seqFile(ref)]) {
1932
2433
  try {
1933
- unlinkSync(f);
2434
+ unlinkSync3(f);
1934
2435
  } catch {
1935
2436
  }
1936
2437
  }
@@ -1944,20 +2445,20 @@ var Cursors = class {
1944
2445
  root;
1945
2446
  sink;
1946
2447
  file() {
1947
- return path9.join(this.root, "cursors", `${safeId(this.sink)}.json`);
2448
+ return path10.join(this.root, "cursors", `${safeId(this.sink)}.json`);
1948
2449
  }
1949
2450
  read() {
1950
2451
  try {
1951
- return JSON.parse(readFileSync7(this.file(), "utf8"));
2452
+ return JSON.parse(readFileSync8(this.file(), "utf8"));
1952
2453
  } catch {
1953
2454
  return {};
1954
2455
  }
1955
2456
  }
1956
2457
  write(c) {
1957
2458
  const f = this.file();
1958
- mkdirSync5(path9.dirname(f), { recursive: true });
2459
+ mkdirSync5(path10.dirname(f), { recursive: true, mode: 448 });
1959
2460
  const tmp = `${f}.${process.pid}.tmp`;
1960
- writeFileSync5(tmp, JSON.stringify(c));
2461
+ writeFileSync5(tmp, JSON.stringify(c), { mode: 384 });
1961
2462
  renameSync(tmp, f);
1962
2463
  }
1963
2464
  key(ref) {
@@ -1965,11 +2466,11 @@ var Cursors = class {
1965
2466
  }
1966
2467
  };
1967
2468
  function takeLock(root, ref, staleMs = 5 * 6e4) {
1968
- const dir = path9.join(root, "locks");
1969
- mkdirSync5(dir, { recursive: true });
1970
- const f = path9.join(dir, `${safeId(ref.harness)}-${safeId(ref.session)}.lock`);
2469
+ const dir = path10.join(root, "locks");
2470
+ mkdirSync5(dir, { recursive: true, mode: 448 });
2471
+ const f = path10.join(dir, `${safeId(ref.harness)}-${safeId(ref.session)}.lock`);
1971
2472
  try {
1972
- if (Date.now() - statSync4(f).mtimeMs > staleMs) unlinkSync(f);
2473
+ if (Date.now() - statSync5(f).mtimeMs > staleMs) unlinkSync3(f);
1973
2474
  else return null;
1974
2475
  } catch {
1975
2476
  }
@@ -1980,7 +2481,7 @@ function takeLock(root, ref, staleMs = 5 * 6e4) {
1980
2481
  }
1981
2482
  return () => {
1982
2483
  try {
1983
- unlinkSync(f);
2484
+ unlinkSync3(f);
1984
2485
  } catch {
1985
2486
  }
1986
2487
  };
@@ -2116,7 +2617,7 @@ function redactAssignments(text) {
2116
2617
  function isSecretName(name) {
2117
2618
  if (/^[A-Z0-9_]+_KEY$/.test(name)) return true;
2118
2619
  const flat = name.toLowerCase().replace(/[_-]/g, "");
2119
- return /(?:pass(?:word|wd|phrase|code)?|pwd|secret|secretkey|secretaccesskey|clientsecret|apikey|accesskey|accesstoken|authtoken|refreshtoken|privatekey|token|credential|creds)s?$/.test(
2620
+ return /(?:pass(?:word|wd|phrase|code)|pwd|secret|secretkey|secretaccesskey|clientsecret|apikey|accesskey|accesstoken|authtoken|refreshtoken|privatekey|token|credential|creds)s?$/.test(
2120
2621
  flat
2121
2622
  );
2122
2623
  }
@@ -2208,7 +2709,7 @@ var DATA_ALLOW = {
2208
2709
  "session.backfill": { 0: ["started_at", "last_at", "turns"] },
2209
2710
  "session.forgotten": { 0: [] },
2210
2711
  "turn.started": { 0: ["turn_id"], 1: ["prompt_chars"] },
2211
- "turn.ended": { 0: ["turn_id", "reason", "ms"] },
2712
+ "turn.ended": { 0: ["turn_id", "reason", "ms"], 1: ["error"] },
2212
2713
  "turn.transcript": { 2: ["turn", "user", "assistant", "at"] },
2213
2714
  "tool.started": { 1: ["tool", "call_id", "turn_id", "agent_id"], 3: ["input"] },
2214
2715
  "tool.ended": { 1: ["tool", "call_id", "turn_id", "agent_id", "ms", "ok", "error"], 3: ["input", "output"] },
@@ -2286,7 +2787,7 @@ function filterEvent(event, tier, pii, opts = {}) {
2286
2787
 
2287
2788
  // src/sinks/file.ts
2288
2789
  import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync6 } from "node:fs";
2289
- import path10 from "node:path";
2790
+ import path11 from "node:path";
2290
2791
  var FileSink = class {
2291
2792
  constructor(name, file, tier, pii) {
2292
2793
  this.name = name;
@@ -2300,7 +2801,7 @@ var FileSink = class {
2300
2801
  pii;
2301
2802
  async send(events) {
2302
2803
  try {
2303
- mkdirSync6(path10.dirname(this.file), { recursive: true });
2804
+ mkdirSync6(path11.dirname(this.file), { recursive: true });
2304
2805
  appendFileSync2(this.file, `${events.map((e) => JSON.stringify(e)).join("\n")}
2305
2806
  `);
2306
2807
  return { ok: true, acknowledged: events.length, accepted: events.length, duplicates: 0, rejected: [] };
@@ -2347,6 +2848,8 @@ export {
2347
2848
  adapterByName,
2348
2849
  antigravity,
2349
2850
  claudeCode,
2851
+ claude_code_control_exports as claudeControl,
2852
+ claudeDirs,
2350
2853
  codex,
2351
2854
  configFile,
2352
2855
  filterEvent,