sessionpipe-conformance 0.11.0 → 0.13.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,7 @@
1
- // sessionpipe-conformance 0.11.0 · Apache-2.0 · https://sessionpipe.org
1
+ // sessionpipe-conformance 0.13.0 · Apache-2.0 · https://sessionpipe.org
2
2
 
3
3
  // src/index.ts
4
- import { readFileSync as readFileSync6 } from "node:fs";
4
+ import { readFileSync as readFileSync7 } from "node:fs";
5
5
 
6
6
  // ../core/src/paths.ts
7
7
  import os from "node:os";
@@ -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 });
@@ -596,13 +634,7 @@ var claudeCode = {
596
634
  );
597
635
  break;
598
636
  case "Stop":
599
- events.push(
600
- ev("turn.ended", {
601
- turn_id,
602
- reason: "stop",
603
- background: Array.isArray(s.background_tasks) ? s.background_tasks.length : void 0
604
- })
605
- );
637
+ events.push(ev("turn.ended", { turn_id, reason: "stop", background: backgroundCount(s.background_tasks) }));
606
638
  break;
607
639
  case "StopFailure":
608
640
  events.push(
@@ -625,7 +657,8 @@ var claudeCode = {
625
657
  events.push(
626
658
  ev("subagent.ended", {
627
659
  agent_id: String(s.agent_id ?? ""),
628
- agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
660
+ agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0,
661
+ background: backgroundCount(s.background_tasks, String(s.agent_id ?? ""))
629
662
  })
630
663
  );
631
664
  break;
@@ -700,6 +733,15 @@ var claudeCode = {
700
733
  return rows2;
701
734
  }
702
735
  };
736
+ var WAITER_RE = /(?:^|[\s"'`/\\])sessionpipe(?:\.\w+)?["']?\s+wait(?:\s|$)/;
737
+ function backgroundCount(tasks, ending) {
738
+ if (!Array.isArray(tasks)) return void 0;
739
+ return tasks.filter((t) => {
740
+ const task = t;
741
+ if (ending !== void 0 && task?.id === ending) return false;
742
+ return !(typeof task?.command === "string" && WAITER_RE.test(task.command));
743
+ }).length;
744
+ }
703
745
  var LINE_REFRESH_S = 60;
704
746
  function statusLine(file, want, cmd) {
705
747
  const f = readJsonFile(file);
@@ -1128,8 +1170,36 @@ async function claudeAiTitle(bridge, token) {
1128
1170
  }
1129
1171
 
1130
1172
  // ../core/src/adapters/headless.ts
1131
- import { accessSync, constants, statSync as statSync3 } from "node:fs";
1173
+ import { accessSync, constants, readFileSync as readFileSync3, statSync as statSync3 } from "node:fs";
1132
1174
  import path4 from "node:path";
1175
+
1176
+ // ../core/src/launched-by.ts
1177
+ var LAUNCHED_BY_ENV = "SESSIONPIPE_LAUNCHED_BY";
1178
+ var DELEGATED_BY_ENV = "SESSIONPIPE_ASKED_BY";
1179
+ var SESSION_REF = /^[a-z][a-z0-9-]{0,39}:[A-Za-z0-9._:-]{1,128}$/;
1180
+ function refIn(env, name) {
1181
+ const v = env[name]?.trim();
1182
+ return v && SESSION_REF.test(v) ? v : void 0;
1183
+ }
1184
+ function launchedBy(env) {
1185
+ return refIn(env, LAUNCHED_BY_ENV);
1186
+ }
1187
+ function delegatedBy(env) {
1188
+ return refIn(env, DELEGATED_BY_ENV);
1189
+ }
1190
+ function readHook(adapter, input) {
1191
+ const r = adapter.fromHook(input);
1192
+ if (r && !r.session.parent_id) {
1193
+ const self = `${adapter.name}:${r.session.id}`;
1194
+ const launcher = launchedBy(input.env);
1195
+ if (launcher && launcher !== self) r.session.launched_by = launcher;
1196
+ const asker = delegatedBy(input.env);
1197
+ if (asker && asker !== self) r.session.delegated_by = asker;
1198
+ }
1199
+ return r;
1200
+ }
1201
+
1202
+ // ../core/src/adapters/headless.ts
1133
1203
  var JOB_TIMEOUT_MS = 30 * 6e4;
1134
1204
  var OUT_CAP = 1024 * 1024;
1135
1205
  function isExe(p) {
@@ -1150,13 +1220,38 @@ function findBinary(o) {
1150
1220
  for (const d of o.dirs) for (const n of o.names) if (isExe(path4.join(d, n))) return path4.join(d, n);
1151
1221
  return null;
1152
1222
  }
1223
+ function shimTarget(shim) {
1224
+ let text = "";
1225
+ try {
1226
+ text = readFileSync3(shim, "utf8");
1227
+ } catch {
1228
+ return null;
1229
+ }
1230
+ const m = /"%(?:~dp0|dp0%)\\([^"]+)"\s+%\*/.exec(text);
1231
+ if (!m) return null;
1232
+ const target = path4.resolve(path4.dirname(shim), m[1].split("\\").join(path4.sep));
1233
+ try {
1234
+ return statSync3(target).isFile() ? target : null;
1235
+ } catch {
1236
+ return null;
1237
+ }
1238
+ }
1239
+ function spawnable(bin, args) {
1240
+ if (!/\.(cmd|bat)$/i.test(bin)) return { file: bin, args };
1241
+ const target = shimTarget(bin);
1242
+ return target ? { file: process.execPath, args: [target, ...args] } : null;
1243
+ }
1244
+ function probe(bin, args) {
1245
+ const s = spawnable(bin, args);
1246
+ return s ? { ...s, shell: false } : { file: [`"${bin}"`, ...args].join(" "), args: [], shell: true };
1247
+ }
1153
1248
 
1154
1249
  // ../core/src/adapters/antigravity.ts
1155
1250
  import {
1156
1251
  existsSync as existsSync3,
1157
1252
  mkdirSync as mkdirSync2,
1158
1253
  readdirSync as readdirSync3,
1159
- readFileSync as readFileSync3,
1254
+ readFileSync as readFileSync4,
1160
1255
  realpathSync,
1161
1256
  statSync as statSync4,
1162
1257
  unlinkSync as unlinkSync2,
@@ -1285,7 +1380,7 @@ var antigravity = {
1285
1380
  readTranscript(file, fromLine) {
1286
1381
  let text = "";
1287
1382
  try {
1288
- text = readFileSync3(file, "utf8");
1383
+ text = readFileSync4(file, "utf8");
1289
1384
  } catch {
1290
1385
  return { turns: [], line: fromLine };
1291
1386
  }
@@ -1364,7 +1459,7 @@ function agProjects(dir) {
1364
1459
  if (!ID_RE.test(id)) continue;
1365
1460
  let j;
1366
1461
  try {
1367
- j = JSON.parse(readFileSync3(path5.join(dir, n), "utf8"));
1462
+ j = JSON.parse(readFileSync4(path5.join(dir, n), "utf8"));
1368
1463
  } catch {
1369
1464
  continue;
1370
1465
  }
@@ -1401,7 +1496,7 @@ function antigravityRemoteUrl(transcript, opts) {
1401
1496
  for (const d of dirs) {
1402
1497
  try {
1403
1498
  const m = /installation_uuid:\s*"([0-9a-f-]{36})"/i.exec(
1404
- readFileSync3(path5.join(d, "antigravity_state.pbtxt"), "utf8")
1499
+ readFileSync4(path5.join(d, "antigravity_state.pbtxt"), "utf8")
1405
1500
  );
1406
1501
  if (!m) continue;
1407
1502
  const base = `https://antigravity.google.com/r/${m[1]}-v2`;
@@ -1440,7 +1535,7 @@ function antigravityFacts(file, opts) {
1440
1535
  }
1441
1536
 
1442
1537
  // ../core/src/adapters/codex.ts
1443
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
1538
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
1444
1539
  import path6 from "node:path";
1445
1540
 
1446
1541
  // ../core/src/adapters/codex-config.ts
@@ -1597,7 +1692,7 @@ function installNotify(cmd, env) {
1597
1692
  const file = configFile(env);
1598
1693
  let text = "";
1599
1694
  try {
1600
- text = readFileSync4(file, "utf8");
1695
+ text = readFileSync5(file, "utf8");
1601
1696
  } catch {
1602
1697
  }
1603
1698
  const argv = cmd([NAME3, "notify"]).argv;
@@ -1643,7 +1738,7 @@ function uninstallNotify(env) {
1643
1738
  const file = configFile(env);
1644
1739
  let text;
1645
1740
  try {
1646
- text = readFileSync4(file, "utf8");
1741
+ text = readFileSync5(file, "utf8");
1647
1742
  } catch {
1648
1743
  return { file, changed: false };
1649
1744
  }
@@ -1715,7 +1810,7 @@ var codex = {
1715
1810
  ];
1716
1811
  let text = "";
1717
1812
  try {
1718
- text = readFileSync4(configFile(env), "utf8");
1813
+ text = readFileSync5(configFile(env), "utf8");
1719
1814
  } catch {
1720
1815
  }
1721
1816
  const n = readNotify(text);
@@ -1842,7 +1937,7 @@ var codex = {
1842
1937
  readTranscript(file, fromLine) {
1843
1938
  let text = "";
1844
1939
  try {
1845
- text = readFileSync4(file, "utf8");
1940
+ text = readFileSync5(file, "utf8");
1846
1941
  } catch {
1847
1942
  return { turns: [], line: fromLine };
1848
1943
  }
@@ -2363,7 +2458,7 @@ function devinAccount(env = process.env) {
2363
2458
 
2364
2459
  // ../core/src/adapters/gemini-cli.ts
2365
2460
  import { execFileSync as execFileSync4 } from "node:child_process";
2366
- import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
2461
+ import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
2367
2462
  import path8 from "node:path";
2368
2463
  var NAME5 = "gemini-cli";
2369
2464
  var GEMINI_EVENTS = [
@@ -2385,14 +2480,14 @@ function geminiVersion(platform = process.platform, env = process.env) {
2385
2480
  const win = platform === "win32";
2386
2481
  const bin = win ? findBinary({ names: ["gemini.exe", "gemini.cmd"], dirs: [path8.dirname(process.execPath)], env }) : "gemini";
2387
2482
  if (!bin) throw new Error("no gemini on PATH");
2388
- const o = {
2483
+ const c = probe(bin, ["--version"]);
2484
+ return execFileSync4(c.file, c.args, {
2389
2485
  encoding: "utf8",
2390
2486
  timeout: 4e3,
2391
2487
  stdio: ["ignore", "pipe", "ignore"],
2392
- env
2393
- };
2394
- const out = /\.cmd$/i.test(bin) ? execFileSync4(`"${bin}" --version`, { ...o, shell: true }) : execFileSync4(bin, ["--version"], o);
2395
- return out.trim();
2488
+ env,
2489
+ shell: c.shell
2490
+ }).trim();
2396
2491
  }
2397
2492
  function geminiHooksActive(settingsFile) {
2398
2493
  const tools = readJson(settingsFile).tools;
@@ -2517,7 +2612,7 @@ var geminiCli = {
2517
2612
  readTranscript(file, fromLine) {
2518
2613
  let text = "";
2519
2614
  try {
2520
- text = readFileSync5(file, "utf8");
2615
+ text = readFileSync6(file, "utf8");
2521
2616
  } catch {
2522
2617
  return { turns: [], line: fromLine };
2523
2618
  }
@@ -2556,22 +2651,6 @@ function prune5(o) {
2556
2651
  var ADAPTERS = [claudeCode, codex, geminiCli, antigravity, devin];
2557
2652
  var adapterByName = (name) => ADAPTERS.find((a) => a.name === name);
2558
2653
 
2559
- // ../core/src/launched-by.ts
2560
- var LAUNCHED_BY_ENV = "SESSIONPIPE_LAUNCHED_BY";
2561
- var LAUNCHED_BY = /^[a-z][a-z0-9-]{0,39}:[A-Za-z0-9._:-]{1,128}$/;
2562
- function launchedBy(env) {
2563
- const v = env[LAUNCHED_BY_ENV]?.trim();
2564
- return v && LAUNCHED_BY.test(v) ? v : void 0;
2565
- }
2566
- function readHook(adapter, input) {
2567
- const r = adapter.fromHook(input);
2568
- if (r && !r.session.parent_id) {
2569
- const launcher = launchedBy(input.env);
2570
- if (launcher && launcher !== `${adapter.name}:${r.session.id}`) r.session.launched_by = launcher;
2571
- }
2572
- return r;
2573
- }
2574
-
2575
2654
  // src/index.ts
2576
2655
  function matches(expect, got, pathStr = "") {
2577
2656
  if (expect === "*") return null;
@@ -2596,7 +2675,7 @@ function matches(expect, got, pathStr = "") {
2596
2675
  async function runAdapterFixtures(files) {
2597
2676
  const results = [];
2598
2677
  for (const file of files) {
2599
- const fx = JSON.parse(readFileSync6(file, "utf8"));
2678
+ const fx = JSON.parse(readFileSync7(file, "utf8"));
2600
2679
  const adapter = adapterByName(fx.harness);
2601
2680
  if (!adapter) {
2602
2681
  results.push({ file, ok: false, why: `no adapter ${fx.harness}` });