sessionpipe-conformance 0.13.0 → 0.13.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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,OAAO,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAuBD,oGAAoG;AACpG,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,MAAM,EAAE,GACd,OAAO,CAAC;IAAE,OAAO,EAAE,aAAa,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CA2CtE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,OAAO,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAuBD,oGAAoG;AACpG,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,MAAM,EAAE,GACd,OAAO,CAAC;IAAE,OAAO,EAAE,aAAa,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAwDtE"}
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- // sessionpipe-conformance 0.13.0 · Apache-2.0 · https://sessionpipe.org
1
+ // sessionpipe-conformance 0.13.1 · Apache-2.0 · https://sessionpipe.org
2
2
 
3
3
  // src/index.ts
4
4
  import { readFileSync as readFileSync7 } from "node:fs";
@@ -10,7 +10,7 @@ var homeDir = () => os.homedir();
10
10
  // ../core/src/adapters/claude-code.ts
11
11
  import { execFileSync as execFileSync2 } from "node:child_process";
12
12
  import { createHash } from "node:crypto";
13
- import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync2, writeFileSync as writeFileSync2 } from "node:fs";
13
+ import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
14
14
  import { userInfo } from "node:os";
15
15
  import path3 from "node:path";
16
16
 
@@ -368,6 +368,41 @@ function readJson(file) {
368
368
  const f = readJsonFile(file);
369
369
  return f.ok ? f.value : {};
370
370
  }
371
+ function refused(file, e, what, existed) {
372
+ const { code, syscall } = e;
373
+ const why = `can't ${what} it (${code ?? String(e)})`;
374
+ if (what === "remove" || syscall === "open" || syscall === "mkdir")
375
+ return { file, changed: false, skipped: true, note: `${why}; not touched` };
376
+ return {
377
+ file,
378
+ changed: true,
379
+ skipped: true,
380
+ ...existed ? {} : { created: true },
381
+ note: `${why} part way; check the file before the harness reads it`
382
+ };
383
+ }
384
+ function writeBack(file, text, existed = true) {
385
+ try {
386
+ mkdirSync(path2.dirname(file), { recursive: true });
387
+ writeFileSync(file, text);
388
+ return null;
389
+ } catch (e) {
390
+ return refused(file, e, "write", existed);
391
+ }
392
+ }
393
+ function removeEmptied(file, empty) {
394
+ try {
395
+ unlinkSync(file);
396
+ return { file, changed: true, note: "removed (sessionpipe created it)" };
397
+ } catch (e) {
398
+ const bad = refused(file, e, "remove", true);
399
+ return writeBack(file, empty) ?? {
400
+ file,
401
+ changed: true,
402
+ note: `emptied; ${bad.note.replace(/; not touched$/, "")}, so the empty file stays`
403
+ };
404
+ }
405
+ }
371
406
  function entryFor(event, harness, cmd, o) {
372
407
  const matcher = o.matcherFor(event);
373
408
  return {
@@ -409,8 +444,8 @@ function installClaudeShaped(file, harness, events, cmd, o) {
409
444
  }
410
445
  if (changed) {
411
446
  settings2.hooks = hooks;
412
- mkdirSync(path2.dirname(file), { recursive: true });
413
- writeFileSync(file, formatJson(settings2, f));
447
+ const bad = writeBack(file, formatJson(settings2, f), f.existed);
448
+ if (bad) return bad;
414
449
  }
415
450
  return { file, changed, created: !f.existed && changed };
416
451
  }
@@ -433,12 +468,8 @@ function uninstallClaudeShaped(file, events, opts = {}) {
433
468
  }
434
469
  if (!changed) return { file, changed: false };
435
470
  if (!Object.keys(hooks).length) delete settings2.hooks;
436
- if (opts.created && !Object.keys(settings2).length) {
437
- unlinkSync(file);
438
- return { file, changed: true, note: "removed (sessionpipe created it)" };
439
- }
440
- writeFileSync(file, formatJson(settings2, f));
441
- return { file, changed: true };
471
+ if (opts.created && !Object.keys(settings2).length) return removeEmptied(file, formatJson(settings2, f));
472
+ return writeBack(file, formatJson(settings2, f)) ?? { file, changed: true };
442
473
  }
443
474
  function installedClaudeShaped(file, harness, events, cmd, o) {
444
475
  const f = readJsonFile(file);
@@ -517,7 +548,7 @@ var claudeCode = {
517
548
  configFiles: (env = process.env) => claudeDirs(env).map(settingsIn),
518
549
  install: (cmd, env = process.env, opts = {}) => claudeDirs(env).flatMap((d) => {
519
550
  const hooks = installClaudeShaped(settingsIn(d), NAME, eventsFor(opts.lean), cmd, OPTS);
520
- const line = opts.statusLine === void 0 ? null : statusLine(settingsIn(d), opts.statusLine, cmd);
551
+ const line = opts.statusLine === void 0 || hooks.skipped ? null : statusLine(settingsIn(d), opts.statusLine, cmd);
521
552
  return line ? [hooks, line] : [hooks];
522
553
  }),
523
554
  uninstall: (env = process.env, opts = {}) => claudeDirs(env).map((d) => {
@@ -525,7 +556,9 @@ var claudeCode = {
525
556
  const hooks = uninstallClaudeShaped(settingsIn(d), CLAUDE_EVENTS, {
526
557
  created: opts.created?.includes(settingsIn(d))
527
558
  });
528
- return line && !hooks.changed ? line : hooks;
559
+ if (line?.changed && !line.skipped && hooks.skipped)
560
+ return { ...hooks, note: `status line removed, hooks not: ${hooks.note?.replace(/; not touched$/, "")}` };
561
+ return line && !hooks.changed && !hooks.skipped ? line : hooks;
529
562
  }),
530
563
  installed: (cmd, env = process.env, opts = {}) => claudeDirs(env).map((d) => ({
531
564
  file: settingsIn(d),
@@ -620,7 +653,8 @@ var claudeCode = {
620
653
  attention_id: call_id ?? permissionId(),
621
654
  kind: "permission",
622
655
  tool,
623
- message: tool ? `${tool}${describe(s.tool_input)}` : void 0
656
+ message: tool ? `${tool}${describe(s.tool_input)}` : void 0,
657
+ agent_id
624
658
  })
625
659
  );
626
660
  break;
@@ -629,7 +663,8 @@ var claudeCode = {
629
663
  ev("attention.needed", {
630
664
  attention_id: `notif-${Date.now()}`,
631
665
  kind: notifKind(s.notification_type),
632
- message: typeof s.message === "string" ? s.message.slice(0, 200) : void 0
666
+ message: typeof s.message === "string" ? s.message.slice(0, 200) : void 0,
667
+ agent_id
633
668
  })
634
669
  );
635
670
  break;
@@ -754,8 +789,11 @@ function statusLine(file, want, cmd) {
754
789
  if (want ? ours && cur?.command === command && cur?.refreshInterval === LINE_REFRESH_S : !ours) return null;
755
790
  if (want) f.value.statusLine = { type: "command", command, refreshInterval: LINE_REFRESH_S };
756
791
  else delete f.value.statusLine;
757
- writeFileSync2(file, formatJson(f.value, f));
758
- return { file, changed: true, note: want ? "status line written (account limits)" : "status line removed" };
792
+ return writeBack(file, formatJson(f.value, f), f.existed) ?? {
793
+ file,
794
+ changed: true,
795
+ note: want ? "status line written (account limits)" : "status line removed"
796
+ };
759
797
  }
760
798
  var epochIso = (t) => typeof t === "number" && t > 0 && t < 1e11 ? new Date(t * 1e3).toISOString() : void 0;
761
799
  var WINDOW_MINUTES = { five_hour: 300, seven_day: 10080 };
@@ -1247,16 +1285,7 @@ function probe(bin, args) {
1247
1285
  }
1248
1286
 
1249
1287
  // ../core/src/adapters/antigravity.ts
1250
- import {
1251
- existsSync as existsSync3,
1252
- mkdirSync as mkdirSync2,
1253
- readdirSync as readdirSync3,
1254
- readFileSync as readFileSync4,
1255
- realpathSync,
1256
- statSync as statSync4,
1257
- unlinkSync as unlinkSync2,
1258
- writeFileSync as writeFileSync3
1259
- } from "node:fs";
1288
+ import { existsSync as existsSync3, readdirSync as readdirSync3, readFileSync as readFileSync4, realpathSync, statSync as statSync4 } from "node:fs";
1260
1289
  import path5 from "node:path";
1261
1290
  var NAME2 = "antigravity";
1262
1291
  var HOOK_NAME = "sessionpipe";
@@ -1288,10 +1317,8 @@ var antigravity = {
1288
1317
  const e = entry(cmd);
1289
1318
  if (JSON.stringify(hooks[HOOK_NAME]) === JSON.stringify(e)) return [{ file, changed: false }];
1290
1319
  hooks[HOOK_NAME] = e;
1291
- mkdirSync2(path5.dirname(file), { recursive: true });
1292
- writeFileSync3(file, formatJson(hooks, f));
1293
1320
  return [
1294
- {
1321
+ writeBack(file, formatJson(hooks, f), f.existed) ?? {
1295
1322
  file,
1296
1323
  changed: true,
1297
1324
  created: !f.existed,
@@ -1307,12 +1334,8 @@ var antigravity = {
1307
1334
  const hooks = f.value;
1308
1335
  if (!(HOOK_NAME in hooks)) return [{ file, changed: false }];
1309
1336
  delete hooks[HOOK_NAME];
1310
- if (opts.created?.includes(file) && !Object.keys(hooks).length) {
1311
- unlinkSync2(file);
1312
- return [{ file, changed: true, note: "removed (sessionpipe created it)" }];
1313
- }
1314
- writeFileSync3(file, formatJson(hooks, f));
1315
- return [{ file, changed: true }];
1337
+ if (opts.created?.includes(file) && !Object.keys(hooks).length) return [removeEmptied(file, formatJson(hooks, f))];
1338
+ return [writeBack(file, formatJson(hooks, f)) ?? { file, changed: true }];
1316
1339
  },
1317
1340
  installed(cmd, env = process.env) {
1318
1341
  const file = hooksFile(env);
@@ -1535,7 +1558,7 @@ function antigravityFacts(file, opts) {
1535
1558
  }
1536
1559
 
1537
1560
  // ../core/src/adapters/codex.ts
1538
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
1561
+ import { existsSync as existsSync4, readdirSync as readdirSync4, readFileSync as readFileSync5 } from "node:fs";
1539
1562
  import path6 from "node:path";
1540
1563
 
1541
1564
  // ../core/src/adapters/codex-config.ts
@@ -1688,29 +1711,30 @@ var configFile = (env) => path6.join(codexHome(env), "config.toml");
1688
1711
  var sessionsDir = (env) => path6.join(codexHome(env), "sessions");
1689
1712
  var validTool3 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
1690
1713
  var endReason2 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
1714
+ var UNTIL_APPROVED = "Codex will report nothing until you approve the hooks: start `codex` and accept the hook review";
1691
1715
  function installNotify(cmd, env) {
1692
1716
  const file = configFile(env);
1693
1717
  let text = "";
1718
+ let existed = true;
1694
1719
  try {
1695
1720
  text = readFileSync5(file, "utf8");
1696
- } catch {
1721
+ } catch (e) {
1722
+ existed = e.code !== "ENOENT";
1697
1723
  }
1698
1724
  const argv = cmd([NAME3, "notify"]).argv;
1699
1725
  const line = `notify = ${JSON.stringify(argv)}`;
1700
1726
  const n = readNotify(text);
1701
- const write = (next) => {
1702
- mkdirSync3(path6.dirname(file), { recursive: true });
1703
- writeFileSync4(file, next);
1727
+ const write = (next, note) => {
1728
+ const bad = writeBack(file, next, existed);
1729
+ return bad ? { ...bad, note: `${bad.note}. ${UNTIL_APPROVED}` } : { file, changed: true, note };
1704
1730
  };
1705
1731
  if (n && !n.other) {
1706
1732
  if (!n.misplaced && JSON.stringify(n.argv) === JSON.stringify(argv))
1707
1733
  return { file, changed: false, note: "notify current" };
1708
- write(withNotify(text, line));
1709
- return {
1710
- file,
1711
- changed: true,
1712
- note: n.misplaced ? "notify moved to the top level (it sat inside a [table], where Codex never reads it)" : "notify updated"
1713
- };
1734
+ return write(
1735
+ withNotify(text, line),
1736
+ n.misplaced ? "notify moved to the top level (it sat inside a [table], where Codex never reads it)" : "notify updated"
1737
+ );
1714
1738
  }
1715
1739
  if (n?.other) {
1716
1740
  const prev = previousNotify(n.argv);
@@ -1719,20 +1743,20 @@ function installNotify(cmd, env) {
1719
1743
  if (JSON.stringify(prev) === JSON.stringify(argv))
1720
1744
  return { file, changed: false, note: "notify current (after Codex Computer Use)" };
1721
1745
  }
1722
- if (!prev || OURS2.test(JSON.stringify(prev))) {
1723
- write(replaceNotify(text, n, `notify = ${withPreviousRaw(n.raw, argv)}`));
1724
- return { file, changed: true, note: "chained after Codex Computer Use (--previous-notify)" };
1725
- }
1746
+ if (!prev || OURS2.test(JSON.stringify(prev)))
1747
+ return write(
1748
+ replaceNotify(text, n, `notify = ${withPreviousRaw(n.raw, argv)}`),
1749
+ "chained after Codex Computer Use (--previous-notify)"
1750
+ );
1726
1751
  }
1727
1752
  return {
1728
1753
  file,
1729
1754
  changed: false,
1730
1755
  skipped: true,
1731
- 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`
1756
+ note: `notify belongs to ${(n.argv?.[0] ?? n.raw).replace(/^.*\//, "").slice(0, 40)}; left alone. ${UNTIL_APPROVED}`
1732
1757
  };
1733
1758
  }
1734
- write(withNotify(text, line));
1735
- return { file, changed: true, note: "notify set at the top level" };
1759
+ return write(withNotify(text, line), "notify set at the top level");
1736
1760
  }
1737
1761
  function uninstallNotify(env) {
1738
1762
  const file = configFile(env);
@@ -1744,15 +1768,13 @@ function uninstallNotify(env) {
1744
1768
  }
1745
1769
  const n = readNotify(text);
1746
1770
  if (!n) return { file, changed: false };
1747
- if (!n.other) {
1748
- writeFileSync4(file, withoutNotify(text));
1749
- return { file, changed: true };
1750
- }
1771
+ if (!n.other) return writeBack(file, withoutNotify(text)) ?? { file, changed: true };
1751
1772
  const prev = previousNotify(n.argv);
1752
- if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev))) {
1753
- writeFileSync4(file, replaceNotify(text, n, `notify = ${stripPreviousRaw(n.raw)}`));
1754
- return { file, changed: true };
1755
- }
1773
+ if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev)))
1774
+ return writeBack(file, replaceNotify(text, n, `notify = ${stripPreviousRaw(n.raw)}`)) ?? {
1775
+ file,
1776
+ changed: true
1777
+ };
1756
1778
  return { file, changed: false };
1757
1779
  }
1758
1780
  function findRollout(threadId, env = process.env) {
@@ -1784,8 +1806,12 @@ function rolloutMeta(file) {
1784
1806
  return scanLines(file, (line) => parseLines([line])[0] ?? {});
1785
1807
  }
1786
1808
  function ofAnotherThread(file, id) {
1809
+ const other = threadOf(file);
1810
+ return !!other && other !== id.toLowerCase();
1811
+ }
1812
+ function threadOf(file) {
1787
1813
  const m = /-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl(?:\.zst)?$/i.exec(file);
1788
- return !!m?.[1] && m[1].toLowerCase() !== id.toLowerCase();
1814
+ return m?.[1] ? m[1].toLowerCase() : null;
1789
1815
  }
1790
1816
  var codex = {
1791
1817
  name: NAME3,
@@ -1864,6 +1890,8 @@ var codex = {
1864
1890
  const turn_id = typeof s.turn_id === "string" ? s.turn_id : void 0;
1865
1891
  const tool = validTool3(s.tool_name) ? s.tool_name : void 0;
1866
1892
  const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
1893
+ const thread = typeof s.transcript_path === "string" ? threadOf(s.transcript_path) : null;
1894
+ const agent_id = typeof s.agent_id === "string" && s.agent_id ? s.agent_id : typeof s.subagent_id === "string" && s.subagent_id ? s.subagent_id : thread && thread !== String(id).toLowerCase() ? thread : void 0;
1867
1895
  const ev = (type, data) => ({ type, data: prune3(data), harnessEvent: event });
1868
1896
  const events = [];
1869
1897
  switch (event) {
@@ -1883,17 +1911,30 @@ var codex = {
1883
1911
  );
1884
1912
  break;
1885
1913
  case "PreToolUse":
1886
- if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, input: s.tool_input }));
1914
+ if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, agent_id, input: s.tool_input }));
1887
1915
  break;
1888
1916
  case "PostToolUse":
1889
1917
  if (tool)
1890
1918
  events.push(
1891
- ev("tool.ended", { tool, call_id, turn_id, ok: true, input: s.tool_input, output: s.tool_response })
1919
+ ev("tool.ended", {
1920
+ tool,
1921
+ call_id,
1922
+ turn_id,
1923
+ agent_id,
1924
+ ok: true,
1925
+ input: s.tool_input,
1926
+ output: s.tool_response
1927
+ })
1892
1928
  );
1893
1929
  break;
1894
1930
  case "PermissionRequest":
1895
1931
  events.push(
1896
- ev("attention.needed", { attention_id: call_id ?? `perm-${Date.now()}`, kind: "permission", tool })
1932
+ ev("attention.needed", {
1933
+ attention_id: call_id ?? `perm-${Date.now()}`,
1934
+ kind: "permission",
1935
+ tool,
1936
+ agent_id
1937
+ })
1897
1938
  );
1898
1939
  break;
1899
1940
  case "Stop":
@@ -2383,7 +2424,7 @@ var CHAIN_LIMIT = 4e3;
2383
2424
  var CHAIN_SQL = `WITH RECURSIVE chain(node_id, parent_node_id, chat_message, created_at) AS (
2384
2425
  SELECT node_id, parent_node_id, chat_message, created_at FROM message_nodes
2385
2426
  WHERE session_id = ? AND node_id = (SELECT main_chain_id FROM sessions WHERE id = ?)
2386
- UNION ALL
2427
+ UNION
2387
2428
  SELECT m.node_id, m.parent_node_id, m.chat_message, m.created_at FROM message_nodes m
2388
2429
  JOIN chain c ON m.node_id = c.parent_node_id AND m.session_id = ? AND m.node_id >= ?
2389
2430
  )
@@ -2675,7 +2716,18 @@ function matches(expect, got, pathStr = "") {
2675
2716
  async function runAdapterFixtures(files) {
2676
2717
  const results = [];
2677
2718
  for (const file of files) {
2678
- const fx = JSON.parse(readFileSync7(file, "utf8"));
2719
+ let fx;
2720
+ try {
2721
+ fx = JSON.parse(readFileSync7(file, "utf8"));
2722
+ } catch (e) {
2723
+ const code = e.code;
2724
+ results.push({ file, ok: false, why: code ? `not readable: ${code}` : `not JSON: ${e.message}` });
2725
+ continue;
2726
+ }
2727
+ if (typeof fx?.harness !== "string" || !fx.input || !Array.isArray(fx.expect)) {
2728
+ results.push({ file, ok: false, why: "not an adapter fixture: it needs harness, input and expect" });
2729
+ continue;
2730
+ }
2679
2731
  const adapter = adapterByName(fx.harness);
2680
2732
  if (!adapter) {
2681
2733
  results.push({ file, ok: false, why: `no adapter ${fx.harness}` });