sessionpipe-conformance 0.12.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.12.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
 
@@ -162,7 +162,14 @@ ${m.text}`;
162
162
  continue;
163
163
  }
164
164
  if (cur) turns.push(cur);
165
- cur = { user: m.text, assistant: "", at: m.at, startLine: m.line, endLine: m.line };
165
+ cur = {
166
+ user: m.text,
167
+ assistant: "",
168
+ at: m.at,
169
+ startLine: m.line,
170
+ endLine: m.line,
171
+ ...m.injected ? { injected: true } : {}
172
+ };
166
173
  } else {
167
174
  if (!cur) cur = { user: "", assistant: "", at: m.at, startLine: m.line, endLine: m.line };
168
175
  cur.assistant += (cur.assistant ? "\n\n" : "") + m.text;
@@ -172,10 +179,17 @@ ${m.text}`;
172
179
  if (cur?.assistant) turns.push(cur);
173
180
  const line = cur && !cur.assistant ? cur.startLine : endLine;
174
181
  const out = {
175
- turns: turns.map((t) => ({ user: t.user, assistant: t.assistant, at: t.at, endLine: t.endLine })),
182
+ turns: turns.map((t) => ({
183
+ user: t.user,
184
+ assistant: t.assistant,
185
+ at: t.at,
186
+ endLine: t.endLine,
187
+ ...t.injected ? { injected: true } : {}
188
+ })),
176
189
  line
177
190
  };
178
- if (cur && !cur.assistant && cur.user) out.open = { user: cur.user, at: cur.at };
191
+ if (cur && !cur.assistant && cur.user)
192
+ out.open = { user: cur.user, at: cur.at, ...cur.injected ? { injected: true } : {} };
179
193
  return out;
180
194
  }
181
195
  var isInjected = (text) => /^<[a-z_-]+(?:\s[^>]*)?>/i.test(text) && /<\/[a-z_-]+>\s*$/i.test(text) || /^<!--\s/.test(text) || /^<command-/.test(text);
@@ -198,27 +212,51 @@ var textParts = (c, kinds) => {
198
212
  (b) => b && typeof b === "object" && kinds.includes(b.type ?? "") && typeof b.text === "string"
199
213
  ).map((b) => b.text).join("\n");
200
214
  };
215
+ var SPOKEN = /* @__PURE__ */ new Set(["human", "peer"]);
216
+ var PEER_INTRO = /sent a message[^\n]*:$/;
217
+ var PEER_OUTRO = "\n\nThis came from another ";
218
+ var ENVELOPE = /^<cross-session-message\b[^>]*>\n([\s\S]*)\n<\/cross-session-message>$/;
219
+ function peerAsk(origin, text) {
220
+ const body = origin?.body;
221
+ let t = typeof body === "string" && body.trim() ? body : text;
222
+ if (t !== body) {
223
+ const nl = t.indexOf("\n");
224
+ if (nl > 0 && PEER_INTRO.test(t.slice(0, nl))) t = t.slice(nl + 1);
225
+ const outro = t.indexOf(PEER_OUTRO);
226
+ if (outro > 0) t = t.slice(0, outro);
227
+ }
228
+ return (ENVELOPE.exec(t.trim())?.[1] ?? t).trim();
229
+ }
201
230
  var queuedPrompt = (a) => {
202
231
  if (!a || typeof a !== "object") return "";
203
232
  const q = a;
204
233
  if (q.type !== "queued_command" || (q.commandMode ?? "prompt") !== "prompt") return "";
205
- if (q.origin?.kind && q.origin.kind !== "human") return "";
206
- return textParts(q.prompt, ["text"]).trim();
234
+ const kind = q.origin?.kind ?? "human";
235
+ if (!SPOKEN.has(kind)) return "";
236
+ const text = textParts(q.prompt, ["text"]).trim();
237
+ return kind === "peer" ? peerAsk(q.origin, text) : text;
207
238
  };
239
+ var channelText = (text) => /^<channel\b[^>]*>([\s\S]*)<\/channel>$/.exec(text)?.[1]?.trim() ?? "";
208
240
  function readClaude(lines, from) {
209
241
  const msgs = [];
210
242
  for (let i = from; i < lines.length; i++) {
211
243
  const j = lines[i] ? parse(lines[i]) : null;
212
244
  if (!j || j.isSidechain) continue;
213
245
  const origin = j.origin;
214
- if (j.type === "user" && (j.isMeta || j.isCompactSummary || j.repeatsCompactSummary || origin?.kind && origin.kind !== "human"))
215
- continue;
246
+ const channel = j.type === "user" && origin?.kind === "channel";
247
+ const peer = origin?.kind === "peer";
248
+ if (j.type === "user" && (j.isCompactSummary || j.repeatsCompactSummary)) continue;
249
+ if (j.type === "user" && !channel && !peer && (j.isMeta || origin?.kind && origin.kind !== "human")) continue;
216
250
  const at = Date.parse(String(j.timestamp ?? "")) || Date.now();
217
251
  const c = j.message?.content;
218
- if (j.type === "user") {
219
- const text = textParts(c, ["text"]).trim();
252
+ if (channel) {
253
+ const text = channelText(textParts(c, ["text"]).trim());
254
+ if (text) msgs.push({ role: "user", text, at, line: i, injected: true });
255
+ } else if (j.type === "user") {
256
+ const raw = textParts(c, ["text"]).trim();
257
+ const text = peer ? peerAsk(origin, raw) : raw;
220
258
  if (!text || isInjected(text)) continue;
221
- msgs.push({ role: "user", text, at, line: i });
259
+ msgs.push({ role: "user", text, at, line: i, ...peer ? { injected: true } : {} });
222
260
  } else if (j.type === "assistant" && Array.isArray(c)) {
223
261
  const text = textParts(c, ["text"]).trim();
224
262
  if (text) msgs.push({ role: "assistant", text, at, line: i });
@@ -330,6 +368,41 @@ function readJson(file) {
330
368
  const f = readJsonFile(file);
331
369
  return f.ok ? f.value : {};
332
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
+ }
333
406
  function entryFor(event, harness, cmd, o) {
334
407
  const matcher = o.matcherFor(event);
335
408
  return {
@@ -371,8 +444,8 @@ function installClaudeShaped(file, harness, events, cmd, o) {
371
444
  }
372
445
  if (changed) {
373
446
  settings2.hooks = hooks;
374
- mkdirSync(path2.dirname(file), { recursive: true });
375
- writeFileSync(file, formatJson(settings2, f));
447
+ const bad = writeBack(file, formatJson(settings2, f), f.existed);
448
+ if (bad) return bad;
376
449
  }
377
450
  return { file, changed, created: !f.existed && changed };
378
451
  }
@@ -395,12 +468,8 @@ function uninstallClaudeShaped(file, events, opts = {}) {
395
468
  }
396
469
  if (!changed) return { file, changed: false };
397
470
  if (!Object.keys(hooks).length) delete settings2.hooks;
398
- if (opts.created && !Object.keys(settings2).length) {
399
- unlinkSync(file);
400
- return { file, changed: true, note: "removed (sessionpipe created it)" };
401
- }
402
- writeFileSync(file, formatJson(settings2, f));
403
- 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 };
404
473
  }
405
474
  function installedClaudeShaped(file, harness, events, cmd, o) {
406
475
  const f = readJsonFile(file);
@@ -479,7 +548,7 @@ var claudeCode = {
479
548
  configFiles: (env = process.env) => claudeDirs(env).map(settingsIn),
480
549
  install: (cmd, env = process.env, opts = {}) => claudeDirs(env).flatMap((d) => {
481
550
  const hooks = installClaudeShaped(settingsIn(d), NAME, eventsFor(opts.lean), cmd, OPTS);
482
- 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);
483
552
  return line ? [hooks, line] : [hooks];
484
553
  }),
485
554
  uninstall: (env = process.env, opts = {}) => claudeDirs(env).map((d) => {
@@ -487,7 +556,9 @@ var claudeCode = {
487
556
  const hooks = uninstallClaudeShaped(settingsIn(d), CLAUDE_EVENTS, {
488
557
  created: opts.created?.includes(settingsIn(d))
489
558
  });
490
- 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;
491
562
  }),
492
563
  installed: (cmd, env = process.env, opts = {}) => claudeDirs(env).map((d) => ({
493
564
  file: settingsIn(d),
@@ -582,7 +653,8 @@ var claudeCode = {
582
653
  attention_id: call_id ?? permissionId(),
583
654
  kind: "permission",
584
655
  tool,
585
- message: tool ? `${tool}${describe(s.tool_input)}` : void 0
656
+ message: tool ? `${tool}${describe(s.tool_input)}` : void 0,
657
+ agent_id
586
658
  })
587
659
  );
588
660
  break;
@@ -591,7 +663,8 @@ var claudeCode = {
591
663
  ev("attention.needed", {
592
664
  attention_id: `notif-${Date.now()}`,
593
665
  kind: notifKind(s.notification_type),
594
- 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
595
668
  })
596
669
  );
597
670
  break;
@@ -716,8 +789,11 @@ function statusLine(file, want, cmd) {
716
789
  if (want ? ours && cur?.command === command && cur?.refreshInterval === LINE_REFRESH_S : !ours) return null;
717
790
  if (want) f.value.statusLine = { type: "command", command, refreshInterval: LINE_REFRESH_S };
718
791
  else delete f.value.statusLine;
719
- writeFileSync2(file, formatJson(f.value, f));
720
- 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
+ };
721
797
  }
722
798
  var epochIso = (t) => typeof t === "number" && t > 0 && t < 1e11 ? new Date(t * 1e3).toISOString() : void 0;
723
799
  var WINDOW_MINUTES = { five_hour: 300, seven_day: 10080 };
@@ -1134,6 +1210,34 @@ async function claudeAiTitle(bridge, token) {
1134
1210
  // ../core/src/adapters/headless.ts
1135
1211
  import { accessSync, constants, readFileSync as readFileSync3, statSync as statSync3 } from "node:fs";
1136
1212
  import path4 from "node:path";
1213
+
1214
+ // ../core/src/launched-by.ts
1215
+ var LAUNCHED_BY_ENV = "SESSIONPIPE_LAUNCHED_BY";
1216
+ var DELEGATED_BY_ENV = "SESSIONPIPE_ASKED_BY";
1217
+ var SESSION_REF = /^[a-z][a-z0-9-]{0,39}:[A-Za-z0-9._:-]{1,128}$/;
1218
+ function refIn(env, name) {
1219
+ const v = env[name]?.trim();
1220
+ return v && SESSION_REF.test(v) ? v : void 0;
1221
+ }
1222
+ function launchedBy(env) {
1223
+ return refIn(env, LAUNCHED_BY_ENV);
1224
+ }
1225
+ function delegatedBy(env) {
1226
+ return refIn(env, DELEGATED_BY_ENV);
1227
+ }
1228
+ function readHook(adapter, input) {
1229
+ const r = adapter.fromHook(input);
1230
+ if (r && !r.session.parent_id) {
1231
+ const self = `${adapter.name}:${r.session.id}`;
1232
+ const launcher = launchedBy(input.env);
1233
+ if (launcher && launcher !== self) r.session.launched_by = launcher;
1234
+ const asker = delegatedBy(input.env);
1235
+ if (asker && asker !== self) r.session.delegated_by = asker;
1236
+ }
1237
+ return r;
1238
+ }
1239
+
1240
+ // ../core/src/adapters/headless.ts
1137
1241
  var JOB_TIMEOUT_MS = 30 * 6e4;
1138
1242
  var OUT_CAP = 1024 * 1024;
1139
1243
  function isExe(p) {
@@ -1181,16 +1285,7 @@ function probe(bin, args) {
1181
1285
  }
1182
1286
 
1183
1287
  // ../core/src/adapters/antigravity.ts
1184
- import {
1185
- existsSync as existsSync3,
1186
- mkdirSync as mkdirSync2,
1187
- readdirSync as readdirSync3,
1188
- readFileSync as readFileSync4,
1189
- realpathSync,
1190
- statSync as statSync4,
1191
- unlinkSync as unlinkSync2,
1192
- writeFileSync as writeFileSync3
1193
- } from "node:fs";
1288
+ import { existsSync as existsSync3, readdirSync as readdirSync3, readFileSync as readFileSync4, realpathSync, statSync as statSync4 } from "node:fs";
1194
1289
  import path5 from "node:path";
1195
1290
  var NAME2 = "antigravity";
1196
1291
  var HOOK_NAME = "sessionpipe";
@@ -1222,10 +1317,8 @@ var antigravity = {
1222
1317
  const e = entry(cmd);
1223
1318
  if (JSON.stringify(hooks[HOOK_NAME]) === JSON.stringify(e)) return [{ file, changed: false }];
1224
1319
  hooks[HOOK_NAME] = e;
1225
- mkdirSync2(path5.dirname(file), { recursive: true });
1226
- writeFileSync3(file, formatJson(hooks, f));
1227
1320
  return [
1228
- {
1321
+ writeBack(file, formatJson(hooks, f), f.existed) ?? {
1229
1322
  file,
1230
1323
  changed: true,
1231
1324
  created: !f.existed,
@@ -1241,12 +1334,8 @@ var antigravity = {
1241
1334
  const hooks = f.value;
1242
1335
  if (!(HOOK_NAME in hooks)) return [{ file, changed: false }];
1243
1336
  delete hooks[HOOK_NAME];
1244
- if (opts.created?.includes(file) && !Object.keys(hooks).length) {
1245
- unlinkSync2(file);
1246
- return [{ file, changed: true, note: "removed (sessionpipe created it)" }];
1247
- }
1248
- writeFileSync3(file, formatJson(hooks, f));
1249
- 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 }];
1250
1339
  },
1251
1340
  installed(cmd, env = process.env) {
1252
1341
  const file = hooksFile(env);
@@ -1469,7 +1558,7 @@ function antigravityFacts(file, opts) {
1469
1558
  }
1470
1559
 
1471
1560
  // ../core/src/adapters/codex.ts
1472
- 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";
1473
1562
  import path6 from "node:path";
1474
1563
 
1475
1564
  // ../core/src/adapters/codex-config.ts
@@ -1622,29 +1711,30 @@ var configFile = (env) => path6.join(codexHome(env), "config.toml");
1622
1711
  var sessionsDir = (env) => path6.join(codexHome(env), "sessions");
1623
1712
  var validTool3 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
1624
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";
1625
1715
  function installNotify(cmd, env) {
1626
1716
  const file = configFile(env);
1627
1717
  let text = "";
1718
+ let existed = true;
1628
1719
  try {
1629
1720
  text = readFileSync5(file, "utf8");
1630
- } catch {
1721
+ } catch (e) {
1722
+ existed = e.code !== "ENOENT";
1631
1723
  }
1632
1724
  const argv = cmd([NAME3, "notify"]).argv;
1633
1725
  const line = `notify = ${JSON.stringify(argv)}`;
1634
1726
  const n = readNotify(text);
1635
- const write = (next) => {
1636
- mkdirSync3(path6.dirname(file), { recursive: true });
1637
- 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 };
1638
1730
  };
1639
1731
  if (n && !n.other) {
1640
1732
  if (!n.misplaced && JSON.stringify(n.argv) === JSON.stringify(argv))
1641
1733
  return { file, changed: false, note: "notify current" };
1642
- write(withNotify(text, line));
1643
- return {
1644
- file,
1645
- changed: true,
1646
- note: n.misplaced ? "notify moved to the top level (it sat inside a [table], where Codex never reads it)" : "notify updated"
1647
- };
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
+ );
1648
1738
  }
1649
1739
  if (n?.other) {
1650
1740
  const prev = previousNotify(n.argv);
@@ -1653,20 +1743,20 @@ function installNotify(cmd, env) {
1653
1743
  if (JSON.stringify(prev) === JSON.stringify(argv))
1654
1744
  return { file, changed: false, note: "notify current (after Codex Computer Use)" };
1655
1745
  }
1656
- if (!prev || OURS2.test(JSON.stringify(prev))) {
1657
- write(replaceNotify(text, n, `notify = ${withPreviousRaw(n.raw, argv)}`));
1658
- return { file, changed: true, note: "chained after Codex Computer Use (--previous-notify)" };
1659
- }
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
+ );
1660
1751
  }
1661
1752
  return {
1662
1753
  file,
1663
1754
  changed: false,
1664
1755
  skipped: true,
1665
- 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}`
1666
1757
  };
1667
1758
  }
1668
- write(withNotify(text, line));
1669
- return { file, changed: true, note: "notify set at the top level" };
1759
+ return write(withNotify(text, line), "notify set at the top level");
1670
1760
  }
1671
1761
  function uninstallNotify(env) {
1672
1762
  const file = configFile(env);
@@ -1678,15 +1768,13 @@ function uninstallNotify(env) {
1678
1768
  }
1679
1769
  const n = readNotify(text);
1680
1770
  if (!n) return { file, changed: false };
1681
- if (!n.other) {
1682
- writeFileSync4(file, withoutNotify(text));
1683
- return { file, changed: true };
1684
- }
1771
+ if (!n.other) return writeBack(file, withoutNotify(text)) ?? { file, changed: true };
1685
1772
  const prev = previousNotify(n.argv);
1686
- if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev))) {
1687
- writeFileSync4(file, replaceNotify(text, n, `notify = ${stripPreviousRaw(n.raw)}`));
1688
- return { file, changed: true };
1689
- }
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
+ };
1690
1778
  return { file, changed: false };
1691
1779
  }
1692
1780
  function findRollout(threadId, env = process.env) {
@@ -1718,8 +1806,12 @@ function rolloutMeta(file) {
1718
1806
  return scanLines(file, (line) => parseLines([line])[0] ?? {});
1719
1807
  }
1720
1808
  function ofAnotherThread(file, id) {
1809
+ const other = threadOf(file);
1810
+ return !!other && other !== id.toLowerCase();
1811
+ }
1812
+ function threadOf(file) {
1721
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);
1722
- return !!m?.[1] && m[1].toLowerCase() !== id.toLowerCase();
1814
+ return m?.[1] ? m[1].toLowerCase() : null;
1723
1815
  }
1724
1816
  var codex = {
1725
1817
  name: NAME3,
@@ -1798,6 +1890,8 @@ var codex = {
1798
1890
  const turn_id = typeof s.turn_id === "string" ? s.turn_id : void 0;
1799
1891
  const tool = validTool3(s.tool_name) ? s.tool_name : void 0;
1800
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;
1801
1895
  const ev = (type, data) => ({ type, data: prune3(data), harnessEvent: event });
1802
1896
  const events = [];
1803
1897
  switch (event) {
@@ -1817,17 +1911,30 @@ var codex = {
1817
1911
  );
1818
1912
  break;
1819
1913
  case "PreToolUse":
1820
- 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 }));
1821
1915
  break;
1822
1916
  case "PostToolUse":
1823
1917
  if (tool)
1824
1918
  events.push(
1825
- 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
+ })
1826
1928
  );
1827
1929
  break;
1828
1930
  case "PermissionRequest":
1829
1931
  events.push(
1830
- 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
+ })
1831
1938
  );
1832
1939
  break;
1833
1940
  case "Stop":
@@ -2317,7 +2424,7 @@ var CHAIN_LIMIT = 4e3;
2317
2424
  var CHAIN_SQL = `WITH RECURSIVE chain(node_id, parent_node_id, chat_message, created_at) AS (
2318
2425
  SELECT node_id, parent_node_id, chat_message, created_at FROM message_nodes
2319
2426
  WHERE session_id = ? AND node_id = (SELECT main_chain_id FROM sessions WHERE id = ?)
2320
- UNION ALL
2427
+ UNION
2321
2428
  SELECT m.node_id, m.parent_node_id, m.chat_message, m.created_at FROM message_nodes m
2322
2429
  JOIN chain c ON m.node_id = c.parent_node_id AND m.session_id = ? AND m.node_id >= ?
2323
2430
  )
@@ -2585,22 +2692,6 @@ function prune5(o) {
2585
2692
  var ADAPTERS = [claudeCode, codex, geminiCli, antigravity, devin];
2586
2693
  var adapterByName = (name) => ADAPTERS.find((a) => a.name === name);
2587
2694
 
2588
- // ../core/src/launched-by.ts
2589
- var LAUNCHED_BY_ENV = "SESSIONPIPE_LAUNCHED_BY";
2590
- var LAUNCHED_BY = /^[a-z][a-z0-9-]{0,39}:[A-Za-z0-9._:-]{1,128}$/;
2591
- function launchedBy(env) {
2592
- const v = env[LAUNCHED_BY_ENV]?.trim();
2593
- return v && LAUNCHED_BY.test(v) ? v : void 0;
2594
- }
2595
- function readHook(adapter, input) {
2596
- const r = adapter.fromHook(input);
2597
- if (r && !r.session.parent_id) {
2598
- const launcher = launchedBy(input.env);
2599
- if (launcher && launcher !== `${adapter.name}:${r.session.id}`) r.session.launched_by = launcher;
2600
- }
2601
- return r;
2602
- }
2603
-
2604
2695
  // src/index.ts
2605
2696
  function matches(expect, got, pathStr = "") {
2606
2697
  if (expect === "*") return null;
@@ -2625,7 +2716,18 @@ function matches(expect, got, pathStr = "") {
2625
2716
  async function runAdapterFixtures(files) {
2626
2717
  const results = [];
2627
2718
  for (const file of files) {
2628
- 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
+ }
2629
2731
  const adapter = adapterByName(fx.harness);
2630
2732
  if (!adapter) {
2631
2733
  results.push({ file, ok: false, why: `no adapter ${fx.harness}` });