sessionpipe-core 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # @sessionpipe/core
1
+ # sessionpipe-core
2
2
 
3
3
  sessionpipe core: protocol types, harness adapters, privacy filter and redaction rulesets, outbox and sinks. Zero runtime dependencies.
4
4
 
package/dist/index.js CHANGED
@@ -1,7 +1,16 @@
1
- // sessionpipe-core 0.1.0 · Apache-2.0 · https://sessionpipe.org
1
+ // sessionpipe-core 0.1.1 · Apache-2.0 · https://sessionpipe.org
2
2
 
3
3
  // 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";
4
+ import {
5
+ existsSync as existsSync2,
6
+ mkdirSync as mkdirSync2,
7
+ readdirSync as readdirSync2,
8
+ readFileSync as readFileSync2,
9
+ realpathSync,
10
+ statSync as statSync2,
11
+ unlinkSync as unlinkSync2,
12
+ writeFileSync as writeFileSync2
13
+ } from "node:fs";
5
14
  import path4 from "node:path";
6
15
 
7
16
  // src/paths.ts
@@ -228,16 +237,42 @@ function readAntigravity(lines, from) {
228
237
  }
229
238
 
230
239
  // src/adapters/claude-shaped.ts
231
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
240
+ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
232
241
  import path3 from "node:path";
233
242
  var OURS = /sessionpipe[^"\n]*[\\/]hook\.js"?\s+[a-z][a-z-]*\s+[A-Za-z]+\s*$/;
234
243
  var isOurs = (h) => Array.isArray(h?.hooks) && h.hooks.some((x) => typeof x.command === "string" && OURS.test(x.command));
235
- function readJson(file) {
244
+ function readJsonFile(file) {
245
+ let raw;
236
246
  try {
237
- return JSON.parse(readFileSync(file, "utf8"));
247
+ raw = readFileSync(file, "utf8");
238
248
  } catch {
239
- return {};
249
+ return { ok: true, existed: false, value: {}, raw: "", indent: " ", eol: "\n", finalNewline: true };
240
250
  }
251
+ let value;
252
+ try {
253
+ value = JSON.parse(raw);
254
+ } catch {
255
+ const hint = /^\s*\/\/|\/\*/m.test(raw) ? "not plain JSON (it has comments)" : "not plain JSON";
256
+ return { ok: false, existed: true, reason: hint };
257
+ }
258
+ if (!value || typeof value !== "object" || Array.isArray(value))
259
+ return { ok: false, existed: true, reason: "not a JSON object" };
260
+ const eol = raw.includes("\r\n") ? "\r\n" : "\n";
261
+ const m = /^( +|\t+)\S/m.exec(raw);
262
+ const indent = m?.[1] ?? " ";
263
+ return {
264
+ ok: true,
265
+ existed: true,
266
+ value,
267
+ raw,
268
+ indent,
269
+ eol,
270
+ finalNewline: /\r?\n$/.test(raw)
271
+ };
272
+ }
273
+ function formatJson(value, f) {
274
+ const text = JSON.stringify(value, null, f.indent).replace(/\n/g, f.eol);
275
+ return f.finalNewline ? text + f.eol : text;
241
276
  }
242
277
  function entryFor(event, harness, cmd, o) {
243
278
  const matcher = o.matcherFor(event);
@@ -247,9 +282,22 @@ function entryFor(event, harness, cmd, o) {
247
282
  };
248
283
  }
249
284
  function installClaudeShaped(file, harness, events, cmd, o) {
250
- const settings2 = readJson(file);
285
+ const f = readJsonFile(file);
286
+ if (!f.ok) return { file, changed: false, skipped: true, note: `${f.reason}; not touched` };
287
+ const settings2 = f.value;
251
288
  const hooks = settings2.hooks && typeof settings2.hooks === "object" ? settings2.hooks : {};
252
289
  let changed = false;
290
+ const want = new Set(events);
291
+ for (const ev of Object.keys(hooks)) {
292
+ if (want.has(ev)) continue;
293
+ const list = Array.isArray(hooks[ev]) ? hooks[ev] : [];
294
+ const kept = list.filter((h) => !isOurs(h));
295
+ if (kept.length !== list.length) {
296
+ changed = true;
297
+ if (kept.length) hooks[ev] = kept;
298
+ else delete hooks[ev];
299
+ }
300
+ }
253
301
  for (const ev of events) {
254
302
  const list = Array.isArray(hooks[ev]) ? hooks[ev] : [];
255
303
  const entry2 = entryFor(ev, harness, cmd, o);
@@ -268,19 +316,19 @@ function installClaudeShaped(file, harness, events, cmd, o) {
268
316
  if (changed) {
269
317
  settings2.hooks = hooks;
270
318
  mkdirSync(path3.dirname(file), { recursive: true });
271
- writeFileSync(file, `${JSON.stringify(settings2, null, 2)}
272
- `);
319
+ writeFileSync(file, formatJson(settings2, f));
273
320
  }
274
- return { file, changed };
321
+ return { file, changed, created: !f.existed && changed };
275
322
  }
276
- function uninstallClaudeShaped(file, events) {
323
+ function uninstallClaudeShaped(file, events, opts = {}) {
277
324
  if (!existsSync(file)) return { file, changed: false };
278
- const raw = readFileSync(file, "utf8");
279
- const settings2 = readJson(file);
325
+ const f = readJsonFile(file);
326
+ if (!f.ok) return { file, changed: false, skipped: true, note: `${f.reason}; not touched` };
327
+ const settings2 = f.value;
280
328
  const hooks = settings2.hooks;
281
329
  if (!hooks || typeof hooks !== "object") return { file, changed: false };
282
330
  let changed = false;
283
- for (const ev of events) {
331
+ for (const ev of [...events, ...Object.keys(hooks)]) {
284
332
  const list = Array.isArray(hooks[ev]) ? hooks[ev] : [];
285
333
  const kept = list.filter((h) => !isOurs(h));
286
334
  if (kept.length !== list.length) {
@@ -291,13 +339,17 @@ function uninstallClaudeShaped(file, events) {
291
339
  }
292
340
  if (!changed) return { file, changed: false };
293
341
  if (!Object.keys(hooks).length) delete settings2.hooks;
294
- const next = `${JSON.stringify(settings2, null, 2)}
295
- `;
296
- if (next !== raw) writeFileSync(file, next);
342
+ if (opts.created && !Object.keys(settings2).length) {
343
+ unlinkSync(file);
344
+ return { file, changed: true, note: "removed (sessionpipe created it)" };
345
+ }
346
+ const next = formatJson(settings2, f);
347
+ if (next !== f.raw) writeFileSync(file, next);
297
348
  return { file, changed: true };
298
349
  }
299
350
  function installedClaudeShaped(file, harness, events, cmd, o) {
300
- const hooks = readJson(file).hooks ?? {};
351
+ const f = readJsonFile(file);
352
+ const hooks = f.ok ? f.value.hooks ?? {} : {};
301
353
  let present = 0;
302
354
  let current = 0;
303
355
  for (const ev of events) {
@@ -306,6 +358,9 @@ function installedClaudeShaped(file, harness, events, cmd, o) {
306
358
  if (mine.length) present++;
307
359
  if (mine.length === 1 && JSON.stringify(mine[0]) === JSON.stringify(entryFor(ev, harness, cmd, o))) current++;
308
360
  }
361
+ for (const ev of Object.keys(hooks))
362
+ if (!events.includes(ev) && (Array.isArray(hooks[ev]) ? hooks[ev] : []).some((h) => isOurs(h)))
363
+ return "stale";
309
364
  if (!present) return "missing";
310
365
  return current === events.length ? "current" : "stale";
311
366
  }
@@ -320,10 +375,13 @@ var projectsDir = (env) => path4.join(gemini(env), "config", "projects");
320
375
  var dataDirs = (env) => ["antigravity", "antigravity-cli", "antigravity-ide"].map((d) => path4.join(gemini(env), d));
321
376
  var ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
322
377
  var validTool = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
323
- function entry(cmd) {
378
+ function entry(cmd, lean) {
324
379
  const handler = (e) => ({ type: "command", command: cmd([NAME, e]).command, timeout: 5 });
325
380
  const out = { enabled: true };
326
- for (const e of AG_EVENTS) out[e] = /Tool/.test(e) ? [{ matcher: "*", hooks: [handler(e)] }] : [handler(e)];
381
+ for (const e of AG_EVENTS) {
382
+ if (lean && e === "PreToolUse") continue;
383
+ out[e] = /Tool/.test(e) ? [{ matcher: "*", hooks: [handler(e)] }] : [handler(e)];
384
+ }
327
385
  return out;
328
386
  }
329
387
  var antigravity = {
@@ -331,34 +389,49 @@ var antigravity = {
331
389
  events: AG_EVENTS,
332
390
  detect: (env = process.env) => existsSync2(path4.join(gemini(env), "config")) || dataDirs(env).some((d) => existsSync2(d)),
333
391
  configFiles: (env = process.env) => [hooksFile(env)],
334
- install(cmd, env = process.env) {
392
+ install(cmd, env = process.env, opts = {}) {
335
393
  const file = hooksFile(env);
336
- const hooks = readJson(file);
337
- const e = entry(cmd);
394
+ const f = readJsonFile(file);
395
+ if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
396
+ const hooks = f.value;
397
+ const e = entry(cmd, opts.lean);
338
398
  if (JSON.stringify(hooks[HOOK_NAME]) === JSON.stringify(e)) return [{ file, changed: false }];
339
399
  hooks[HOOK_NAME] = e;
340
400
  mkdirSync2(path4.dirname(file), { recursive: true });
341
- writeFileSync2(file, `${JSON.stringify(hooks, null, 2)}
342
- `);
401
+ writeFileSync2(file, formatJson(hooks, f));
343
402
  return [
344
- { file, changed: true, note: "hooks.json is read when a conversation starts; open ones report after a restart" }
403
+ {
404
+ file,
405
+ changed: true,
406
+ created: !f.existed,
407
+ note: "hooks.json is read when a conversation starts; open ones report after a restart"
408
+ }
345
409
  ];
346
410
  },
347
- uninstall(env = process.env) {
411
+ uninstall(env = process.env, opts = {}) {
348
412
  const file = hooksFile(env);
349
413
  if (!existsSync2(file)) return [{ file, changed: false }];
350
- const hooks = readJson(file);
414
+ const f = readJsonFile(file);
415
+ if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
416
+ const hooks = f.value;
351
417
  if (!(HOOK_NAME in hooks)) return [{ file, changed: false }];
352
418
  delete hooks[HOOK_NAME];
353
- writeFileSync2(file, `${JSON.stringify(hooks, null, 2)}
354
- `);
419
+ if (opts.created?.includes(file) && !Object.keys(hooks).length) {
420
+ unlinkSync2(file);
421
+ return [{ file, changed: true, note: "removed (sessionpipe created it)" }];
422
+ }
423
+ writeFileSync2(file, formatJson(hooks, f));
355
424
  return [{ file, changed: true }];
356
425
  },
357
- installed(cmd, env = process.env) {
426
+ installed(cmd, env = process.env, opts = {}) {
358
427
  const file = hooksFile(env);
359
- const have = readJson(file)[HOOK_NAME];
428
+ const f = readJsonFile(file);
429
+ const have = f.ok ? f.value[HOOK_NAME] : void 0;
360
430
  return [
361
- { file, state: !have ? "missing" : JSON.stringify(have) === JSON.stringify(entry(cmd)) ? "current" : "stale" }
431
+ {
432
+ file,
433
+ state: !have ? "missing" : JSON.stringify(have) === JSON.stringify(entry(cmd, opts.lean)) ? "current" : "stale"
434
+ }
362
435
  ];
363
436
  },
364
437
  fromHook(input) {
@@ -624,8 +697,11 @@ var CLAUDE_EVENTS = [
624
697
  "SubagentStop",
625
698
  "PreCompact",
626
699
  "PostCompact",
627
- "SessionEnd"
700
+ "SessionEnd",
701
+ "StopFailure"
628
702
  ];
703
+ var LEAN_SKIP = /* @__PURE__ */ new Set(["PreToolUse", "PostToolUseFailure"]);
704
+ var eventsFor = (lean) => lean ? CLAUDE_EVENTS.filter((e) => !LEAN_SKIP.has(e)) : [...CLAUDE_EVENTS];
629
705
  var OPTS = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
630
706
  var DESKTOP_ID = /^local_[A-Za-z0-9-]{1,64}$/;
631
707
  function claudeDirs(env = process.env, home = HOME) {
@@ -657,11 +733,13 @@ var claudeCode = {
657
733
  events: CLAUDE_EVENTS,
658
734
  detect: (env = process.env) => claudeDirs(env).some((d) => existsSync3(d)),
659
735
  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) => ({
736
+ install: (cmd, env = process.env, opts = {}) => claudeDirs(env).map((d) => installClaudeShaped(settingsIn(d), NAME2, eventsFor(opts.lean), cmd, OPTS)),
737
+ uninstall: (env = process.env, opts = {}) => claudeDirs(env).map(
738
+ (d) => uninstallClaudeShaped(settingsIn(d), CLAUDE_EVENTS, { created: opts.created?.includes(settingsIn(d)) })
739
+ ),
740
+ installed: (cmd, env = process.env, opts = {}) => claudeDirs(env).map((d) => ({
663
741
  file: settingsIn(d),
664
- state: installedClaudeShaped(settingsIn(d), NAME2, CLAUDE_EVENTS, cmd, OPTS)
742
+ state: installedClaudeShaped(settingsIn(d), NAME2, eventsFor(opts.lean), cmd, OPTS)
665
743
  })),
666
744
  fromHook(input) {
667
745
  const event = input.argv[1] ?? "";
@@ -752,6 +830,15 @@ var claudeCode = {
752
830
  case "Stop":
753
831
  events.push(ev("turn.ended", { turn_id, reason: "stop" }));
754
832
  break;
833
+ case "StopFailure":
834
+ events.push(
835
+ ev("turn.ended", {
836
+ turn_id,
837
+ reason: "error",
838
+ error: typeof s.error === "string" ? s.error.slice(0, 200) : void 0
839
+ })
840
+ );
841
+ break;
755
842
  case "SubagentStart":
756
843
  events.push(
757
844
  ev("subagent.started", {
@@ -1263,6 +1350,7 @@ var CODEX_EVENTS = [
1263
1350
  "SessionEnd"
1264
1351
  ];
1265
1352
  var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
1353
+ var eventsFor2 = (lean) => lean ? CODEX_EVENTS.filter((e) => e !== "PreToolUse") : [...CODEX_EVENTS];
1266
1354
  var codexHome = (env) => env.CODEX_HOME || path6.join(HOME, ".codex");
1267
1355
  var hooksFile2 = (env) => path6.join(codexHome(env), "hooks.json");
1268
1356
  var configFile2 = (env) => path6.join(codexHome(env), "config.toml");
@@ -1311,7 +1399,8 @@ function installNotify(cmd, env) {
1311
1399
  return {
1312
1400
  file,
1313
1401
  changed: false,
1314
- note: `left alone: notify runs ${n.raw.slice(0, 80)} (hooks.json carries the events; the notify fallback is off)`
1402
+ skipped: true,
1403
+ 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
1404
  };
1316
1405
  }
1317
1406
  write(withNotify(text, line));
@@ -1368,14 +1457,21 @@ var codex = {
1368
1457
  events: CODEX_EVENTS,
1369
1458
  detect: (env = process.env) => existsSync4(codexHome(env)),
1370
1459
  configFiles: (env = process.env) => [hooksFile2(env), configFile2(env)],
1371
- install: (cmd, env = process.env) => [
1372
- installClaudeShaped(hooksFile2(env), NAME3, CODEX_EVENTS, cmd, OPTS2),
1460
+ install: (cmd, env = process.env, opts = {}) => [
1461
+ installClaudeShaped(hooksFile2(env), NAME3, eventsFor2(opts.lean), cmd, OPTS2),
1373
1462
  installNotify(cmd, env)
1374
1463
  ],
1375
- uninstall: (env = process.env) => [uninstallClaudeShaped(hooksFile2(env), CODEX_EVENTS), uninstallNotify(env)],
1376
- installed: (cmd, env = process.env) => {
1464
+ uninstall: (env = process.env, opts = {}) => [
1465
+ uninstallClaudeShaped(hooksFile2(env), CODEX_EVENTS, { created: opts.created?.includes(hooksFile2(env)) }),
1466
+ uninstallNotify(env)
1467
+ ],
1468
+ installed: (cmd, env = process.env, opts = {}) => {
1377
1469
  const out = [
1378
- { file: hooksFile2(env), state: installedClaudeShaped(hooksFile2(env), NAME3, CODEX_EVENTS, cmd, OPTS2) }
1470
+ {
1471
+ file: hooksFile2(env),
1472
+ state: installedClaudeShaped(hooksFile2(env), NAME3, eventsFor2(opts.lean), cmd, OPTS2),
1473
+ note: "Codex runs non-managed hooks only after the review in `codex` (/hooks); until then nothing is reported unless the notify fallback is ours"
1474
+ }
1379
1475
  ];
1380
1476
  let text = "";
1381
1477
  try {
@@ -1563,6 +1659,7 @@ function codexFacts(file) {
1563
1659
  }
1564
1660
 
1565
1661
  // src/adapters/gemini-cli.ts
1662
+ import { execFileSync as execFileSync3 } from "node:child_process";
1566
1663
  import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
1567
1664
  import path7 from "node:path";
1568
1665
  var NAME4 = "gemini-cli";
@@ -1580,17 +1677,61 @@ var OPTS3 = { matcherFor: (e) => /Tool/.test(e) ? ".*" : void 0, timeout: 5e3 };
1580
1677
  var geminiDir = (env) => env.GEMINI_CLI_HOME || path7.join(HOME, ".gemini");
1581
1678
  var settings = (env) => path7.join(geminiDir(env), "settings.json");
1582
1679
  var validTool4 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
1680
+ var eventsFor3 = (lean) => lean ? GEMINI_EVENTS.filter((e) => e !== "BeforeTool") : [...GEMINI_EVENTS];
1681
+ function geminiHooksActive(settingsFile) {
1682
+ let tools;
1683
+ try {
1684
+ tools = JSON.parse(readFileSync5(settingsFile, "utf8")).tools;
1685
+ } catch {
1686
+ }
1687
+ if (tools?.enableHooks === true) return { active: true };
1688
+ if (tools?.enableHooks === false)
1689
+ return { active: false, note: "inactive: tools.enableHooks is false in settings.json" };
1690
+ let version = "";
1691
+ try {
1692
+ version = execFileSync3("gemini", ["--version"], {
1693
+ encoding: "utf8",
1694
+ timeout: 4e3,
1695
+ stdio: ["ignore", "pipe", "ignore"]
1696
+ }).trim();
1697
+ } catch {
1698
+ return {
1699
+ active: true,
1700
+ note: "could not run `gemini --version`; hooks need tools.enableHooks on versions before 0.46"
1701
+ };
1702
+ }
1703
+ const m = /(\d+)\.(\d+)/.exec(version);
1704
+ if (m && (Number(m[1]) > 0 || Number(m[2]) >= 46)) return { active: true };
1705
+ return {
1706
+ active: false,
1707
+ note: `inactive: Gemini CLI ${version || "?"} runs hooks only with "tools": {"enableHooks": true} in settings.json`
1708
+ };
1709
+ }
1583
1710
  var geminiCli = {
1584
1711
  name: NAME4,
1585
1712
  events: GEMINI_EVENTS,
1586
1713
  // ~/.gemini also belongs to Antigravity; Gemini CLI is here when its settings or its chat logs exist.
1587
1714
  detect: (env = process.env) => existsSync5(settings(env)) || existsSync5(path7.join(geminiDir(env), "tmp")),
1588
1715
  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) }
1716
+ install: (cmd, env = process.env, opts = {}) => {
1717
+ const r = installClaudeShaped(settings(env), NAME4, eventsFor3(opts.lean), cmd, OPTS3);
1718
+ const a = geminiHooksActive(settings(env));
1719
+ return [a.note && !r.skipped ? { ...r, note: [r.note, a.note].filter(Boolean).join("; ") } : r];
1720
+ },
1721
+ uninstall: (env = process.env, opts = {}) => [
1722
+ uninstallClaudeShaped(settings(env), GEMINI_EVENTS, { created: opts.created?.includes(settings(env)) })
1593
1723
  ],
1724
+ installed: (cmd, env = process.env, opts = {}) => {
1725
+ const state = installedClaudeShaped(settings(env), NAME4, eventsFor3(opts.lean), cmd, OPTS3);
1726
+ const a = geminiHooksActive(settings(env));
1727
+ return [
1728
+ {
1729
+ file: settings(env),
1730
+ state: state === "current" && !a.active ? "inactive" : state,
1731
+ ...a.note ? { note: a.note } : {}
1732
+ }
1733
+ ];
1734
+ },
1594
1735
  fromHook(input) {
1595
1736
  const event = input.argv[1] ?? "";
1596
1737
  let s = {};
@@ -1802,7 +1943,7 @@ import {
1802
1943
  readSync as readSync2,
1803
1944
  renameSync,
1804
1945
  statSync as statSync4,
1805
- unlinkSync,
1946
+ unlinkSync as unlinkSync3,
1806
1947
  writeFileSync as writeFileSync5
1807
1948
  } from "node:fs";
1808
1949
  import path9 from "node:path";
@@ -1830,8 +1971,8 @@ var Outbox = class {
1830
1971
  } catch {
1831
1972
  n = this.lastSeqInFile(ref) + 1;
1832
1973
  }
1833
- mkdirSync5(path9.dirname(f), { recursive: true });
1834
- writeFileSync5(f, String(n + 1));
1974
+ mkdirSync5(path9.dirname(f), { recursive: true, mode: 448 });
1975
+ writeFileSync5(f, String(n + 1), { mode: 384 });
1835
1976
  return n;
1836
1977
  }
1837
1978
  lastSeqInFile(ref) {
@@ -1849,9 +1990,9 @@ var Outbox = class {
1849
1990
  }
1850
1991
  append(ref, events) {
1851
1992
  if (!events.length) return;
1852
- mkdirSync5(this.dir(ref.harness), { recursive: true });
1993
+ mkdirSync5(this.dir(ref.harness), { recursive: true, mode: 448 });
1853
1994
  appendFileSync(this.file(ref), `${events.map((e) => JSON.stringify(e)).join("\n")}
1854
- `);
1995
+ `, { mode: 384 });
1855
1996
  }
1856
1997
  /** Every session file, newest first. */
1857
1998
  sessions() {
@@ -1915,9 +2056,9 @@ var Outbox = class {
1915
2056
  const f = this.file(ref);
1916
2057
  try {
1917
2058
  if (statSync4(f).mtimeMs < cutoff) {
1918
- unlinkSync(f);
2059
+ unlinkSync3(f);
1919
2060
  try {
1920
- unlinkSync(this.seqFile(ref));
2061
+ unlinkSync3(this.seqFile(ref));
1921
2062
  } catch {
1922
2063
  }
1923
2064
  n++;
@@ -1930,7 +2071,7 @@ var Outbox = class {
1930
2071
  delete(ref) {
1931
2072
  for (const f of [this.file(ref), this.seqFile(ref)]) {
1932
2073
  try {
1933
- unlinkSync(f);
2074
+ unlinkSync3(f);
1934
2075
  } catch {
1935
2076
  }
1936
2077
  }
@@ -1955,9 +2096,9 @@ var Cursors = class {
1955
2096
  }
1956
2097
  write(c) {
1957
2098
  const f = this.file();
1958
- mkdirSync5(path9.dirname(f), { recursive: true });
2099
+ mkdirSync5(path9.dirname(f), { recursive: true, mode: 448 });
1959
2100
  const tmp = `${f}.${process.pid}.tmp`;
1960
- writeFileSync5(tmp, JSON.stringify(c));
2101
+ writeFileSync5(tmp, JSON.stringify(c), { mode: 384 });
1961
2102
  renameSync(tmp, f);
1962
2103
  }
1963
2104
  key(ref) {
@@ -1966,10 +2107,10 @@ var Cursors = class {
1966
2107
  };
1967
2108
  function takeLock(root, ref, staleMs = 5 * 6e4) {
1968
2109
  const dir = path9.join(root, "locks");
1969
- mkdirSync5(dir, { recursive: true });
2110
+ mkdirSync5(dir, { recursive: true, mode: 448 });
1970
2111
  const f = path9.join(dir, `${safeId(ref.harness)}-${safeId(ref.session)}.lock`);
1971
2112
  try {
1972
- if (Date.now() - statSync4(f).mtimeMs > staleMs) unlinkSync(f);
2113
+ if (Date.now() - statSync4(f).mtimeMs > staleMs) unlinkSync3(f);
1973
2114
  else return null;
1974
2115
  } catch {
1975
2116
  }
@@ -1980,7 +2121,7 @@ function takeLock(root, ref, staleMs = 5 * 6e4) {
1980
2121
  }
1981
2122
  return () => {
1982
2123
  try {
1983
- unlinkSync(f);
2124
+ unlinkSync3(f);
1984
2125
  } catch {
1985
2126
  }
1986
2127
  };
@@ -2116,7 +2257,7 @@ function redactAssignments(text) {
2116
2257
  function isSecretName(name) {
2117
2258
  if (/^[A-Z0-9_]+_KEY$/.test(name)) return true;
2118
2259
  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(
2260
+ return /(?:pass(?:word|wd|phrase|code)|pwd|secret|secretkey|secretaccesskey|clientsecret|apikey|accesskey|accesstoken|authtoken|refreshtoken|privatekey|token|credential|creds)s?$/.test(
2120
2261
  flat
2121
2262
  );
2122
2263
  }
@@ -2208,7 +2349,7 @@ var DATA_ALLOW = {
2208
2349
  "session.backfill": { 0: ["started_at", "last_at", "turns"] },
2209
2350
  "session.forgotten": { 0: [] },
2210
2351
  "turn.started": { 0: ["turn_id"], 1: ["prompt_chars"] },
2211
- "turn.ended": { 0: ["turn_id", "reason", "ms"] },
2352
+ "turn.ended": { 0: ["turn_id", "reason", "ms"], 1: ["error"] },
2212
2353
  "turn.transcript": { 2: ["turn", "user", "assistant", "at"] },
2213
2354
  "tool.started": { 1: ["tool", "call_id", "turn_id", "agent_id"], 3: ["input"] },
2214
2355
  "tool.ended": { 1: ["tool", "call_id", "turn_id", "agent_id", "ms", "ok", "error"], 3: ["input", "output"] },