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/cli.js +133 -54
- package/dist/cli.js.map +4 -4
- package/dist/index.js +133 -54
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// sessionpipe-conformance 0.
|
|
2
|
+
// sessionpipe-conformance 0.13.0 · Apache-2.0 · https://sessionpipe.org
|
|
3
3
|
|
|
4
4
|
// src/cli.ts
|
|
5
5
|
import { readdirSync as readdirSync5, realpathSync as realpathSync2, statSync as statSync5 } from "node:fs";
|
|
@@ -7,7 +7,7 @@ import path9 from "node:path";
|
|
|
7
7
|
import { pathToFileURL } from "node:url";
|
|
8
8
|
|
|
9
9
|
// src/index.ts
|
|
10
|
-
import { readFileSync as
|
|
10
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
11
11
|
|
|
12
12
|
// ../core/src/paths.ts
|
|
13
13
|
import os from "node:os";
|
|
@@ -168,7 +168,14 @@ ${m.text}`;
|
|
|
168
168
|
continue;
|
|
169
169
|
}
|
|
170
170
|
if (cur) turns.push(cur);
|
|
171
|
-
cur = {
|
|
171
|
+
cur = {
|
|
172
|
+
user: m.text,
|
|
173
|
+
assistant: "",
|
|
174
|
+
at: m.at,
|
|
175
|
+
startLine: m.line,
|
|
176
|
+
endLine: m.line,
|
|
177
|
+
...m.injected ? { injected: true } : {}
|
|
178
|
+
};
|
|
172
179
|
} else {
|
|
173
180
|
if (!cur) cur = { user: "", assistant: "", at: m.at, startLine: m.line, endLine: m.line };
|
|
174
181
|
cur.assistant += (cur.assistant ? "\n\n" : "") + m.text;
|
|
@@ -178,10 +185,17 @@ ${m.text}`;
|
|
|
178
185
|
if (cur?.assistant) turns.push(cur);
|
|
179
186
|
const line = cur && !cur.assistant ? cur.startLine : endLine;
|
|
180
187
|
const out = {
|
|
181
|
-
turns: turns.map((t) => ({
|
|
188
|
+
turns: turns.map((t) => ({
|
|
189
|
+
user: t.user,
|
|
190
|
+
assistant: t.assistant,
|
|
191
|
+
at: t.at,
|
|
192
|
+
endLine: t.endLine,
|
|
193
|
+
...t.injected ? { injected: true } : {}
|
|
194
|
+
})),
|
|
182
195
|
line
|
|
183
196
|
};
|
|
184
|
-
if (cur && !cur.assistant && cur.user)
|
|
197
|
+
if (cur && !cur.assistant && cur.user)
|
|
198
|
+
out.open = { user: cur.user, at: cur.at, ...cur.injected ? { injected: true } : {} };
|
|
185
199
|
return out;
|
|
186
200
|
}
|
|
187
201
|
var isInjected = (text) => /^<[a-z_-]+(?:\s[^>]*)?>/i.test(text) && /<\/[a-z_-]+>\s*$/i.test(text) || /^<!--\s/.test(text) || /^<command-/.test(text);
|
|
@@ -204,27 +218,51 @@ var textParts = (c, kinds) => {
|
|
|
204
218
|
(b) => b && typeof b === "object" && kinds.includes(b.type ?? "") && typeof b.text === "string"
|
|
205
219
|
).map((b) => b.text).join("\n");
|
|
206
220
|
};
|
|
221
|
+
var SPOKEN = /* @__PURE__ */ new Set(["human", "peer"]);
|
|
222
|
+
var PEER_INTRO = /sent a message[^\n]*:$/;
|
|
223
|
+
var PEER_OUTRO = "\n\nThis came from another ";
|
|
224
|
+
var ENVELOPE = /^<cross-session-message\b[^>]*>\n([\s\S]*)\n<\/cross-session-message>$/;
|
|
225
|
+
function peerAsk(origin, text) {
|
|
226
|
+
const body = origin?.body;
|
|
227
|
+
let t = typeof body === "string" && body.trim() ? body : text;
|
|
228
|
+
if (t !== body) {
|
|
229
|
+
const nl = t.indexOf("\n");
|
|
230
|
+
if (nl > 0 && PEER_INTRO.test(t.slice(0, nl))) t = t.slice(nl + 1);
|
|
231
|
+
const outro = t.indexOf(PEER_OUTRO);
|
|
232
|
+
if (outro > 0) t = t.slice(0, outro);
|
|
233
|
+
}
|
|
234
|
+
return (ENVELOPE.exec(t.trim())?.[1] ?? t).trim();
|
|
235
|
+
}
|
|
207
236
|
var queuedPrompt = (a) => {
|
|
208
237
|
if (!a || typeof a !== "object") return "";
|
|
209
238
|
const q = a;
|
|
210
239
|
if (q.type !== "queued_command" || (q.commandMode ?? "prompt") !== "prompt") return "";
|
|
211
|
-
|
|
212
|
-
|
|
240
|
+
const kind = q.origin?.kind ?? "human";
|
|
241
|
+
if (!SPOKEN.has(kind)) return "";
|
|
242
|
+
const text = textParts(q.prompt, ["text"]).trim();
|
|
243
|
+
return kind === "peer" ? peerAsk(q.origin, text) : text;
|
|
213
244
|
};
|
|
245
|
+
var channelText = (text) => /^<channel\b[^>]*>([\s\S]*)<\/channel>$/.exec(text)?.[1]?.trim() ?? "";
|
|
214
246
|
function readClaude(lines, from) {
|
|
215
247
|
const msgs = [];
|
|
216
248
|
for (let i = from; i < lines.length; i++) {
|
|
217
249
|
const j = lines[i] ? parse(lines[i]) : null;
|
|
218
250
|
if (!j || j.isSidechain) continue;
|
|
219
251
|
const origin = j.origin;
|
|
220
|
-
|
|
221
|
-
|
|
252
|
+
const channel = j.type === "user" && origin?.kind === "channel";
|
|
253
|
+
const peer = origin?.kind === "peer";
|
|
254
|
+
if (j.type === "user" && (j.isCompactSummary || j.repeatsCompactSummary)) continue;
|
|
255
|
+
if (j.type === "user" && !channel && !peer && (j.isMeta || origin?.kind && origin.kind !== "human")) continue;
|
|
222
256
|
const at = Date.parse(String(j.timestamp ?? "")) || Date.now();
|
|
223
257
|
const c = j.message?.content;
|
|
224
|
-
if (
|
|
225
|
-
const text = textParts(c, ["text"]).trim();
|
|
258
|
+
if (channel) {
|
|
259
|
+
const text = channelText(textParts(c, ["text"]).trim());
|
|
260
|
+
if (text) msgs.push({ role: "user", text, at, line: i, injected: true });
|
|
261
|
+
} else if (j.type === "user") {
|
|
262
|
+
const raw = textParts(c, ["text"]).trim();
|
|
263
|
+
const text = peer ? peerAsk(origin, raw) : raw;
|
|
226
264
|
if (!text || isInjected(text)) continue;
|
|
227
|
-
msgs.push({ role: "user", text, at, line: i });
|
|
265
|
+
msgs.push({ role: "user", text, at, line: i, ...peer ? { injected: true } : {} });
|
|
228
266
|
} else if (j.type === "assistant" && Array.isArray(c)) {
|
|
229
267
|
const text = textParts(c, ["text"]).trim();
|
|
230
268
|
if (text) msgs.push({ role: "assistant", text, at, line: i });
|
|
@@ -602,13 +640,7 @@ var claudeCode = {
|
|
|
602
640
|
);
|
|
603
641
|
break;
|
|
604
642
|
case "Stop":
|
|
605
|
-
events.push(
|
|
606
|
-
ev("turn.ended", {
|
|
607
|
-
turn_id,
|
|
608
|
-
reason: "stop",
|
|
609
|
-
background: Array.isArray(s.background_tasks) ? s.background_tasks.length : void 0
|
|
610
|
-
})
|
|
611
|
-
);
|
|
643
|
+
events.push(ev("turn.ended", { turn_id, reason: "stop", background: backgroundCount(s.background_tasks) }));
|
|
612
644
|
break;
|
|
613
645
|
case "StopFailure":
|
|
614
646
|
events.push(
|
|
@@ -631,7 +663,8 @@ var claudeCode = {
|
|
|
631
663
|
events.push(
|
|
632
664
|
ev("subagent.ended", {
|
|
633
665
|
agent_id: String(s.agent_id ?? ""),
|
|
634
|
-
agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
|
|
666
|
+
agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0,
|
|
667
|
+
background: backgroundCount(s.background_tasks, String(s.agent_id ?? ""))
|
|
635
668
|
})
|
|
636
669
|
);
|
|
637
670
|
break;
|
|
@@ -706,6 +739,15 @@ var claudeCode = {
|
|
|
706
739
|
return rows2;
|
|
707
740
|
}
|
|
708
741
|
};
|
|
742
|
+
var WAITER_RE = /(?:^|[\s"'`/\\])sessionpipe(?:\.\w+)?["']?\s+wait(?:\s|$)/;
|
|
743
|
+
function backgroundCount(tasks, ending) {
|
|
744
|
+
if (!Array.isArray(tasks)) return void 0;
|
|
745
|
+
return tasks.filter((t) => {
|
|
746
|
+
const task = t;
|
|
747
|
+
if (ending !== void 0 && task?.id === ending) return false;
|
|
748
|
+
return !(typeof task?.command === "string" && WAITER_RE.test(task.command));
|
|
749
|
+
}).length;
|
|
750
|
+
}
|
|
709
751
|
var LINE_REFRESH_S = 60;
|
|
710
752
|
function statusLine(file, want, cmd) {
|
|
711
753
|
const f = readJsonFile(file);
|
|
@@ -1134,8 +1176,36 @@ async function claudeAiTitle(bridge, token) {
|
|
|
1134
1176
|
}
|
|
1135
1177
|
|
|
1136
1178
|
// ../core/src/adapters/headless.ts
|
|
1137
|
-
import { accessSync, constants, statSync as statSync3 } from "node:fs";
|
|
1179
|
+
import { accessSync, constants, readFileSync as readFileSync3, statSync as statSync3 } from "node:fs";
|
|
1138
1180
|
import path4 from "node:path";
|
|
1181
|
+
|
|
1182
|
+
// ../core/src/launched-by.ts
|
|
1183
|
+
var LAUNCHED_BY_ENV = "SESSIONPIPE_LAUNCHED_BY";
|
|
1184
|
+
var DELEGATED_BY_ENV = "SESSIONPIPE_ASKED_BY";
|
|
1185
|
+
var SESSION_REF = /^[a-z][a-z0-9-]{0,39}:[A-Za-z0-9._:-]{1,128}$/;
|
|
1186
|
+
function refIn(env, name) {
|
|
1187
|
+
const v = env[name]?.trim();
|
|
1188
|
+
return v && SESSION_REF.test(v) ? v : void 0;
|
|
1189
|
+
}
|
|
1190
|
+
function launchedBy(env) {
|
|
1191
|
+
return refIn(env, LAUNCHED_BY_ENV);
|
|
1192
|
+
}
|
|
1193
|
+
function delegatedBy(env) {
|
|
1194
|
+
return refIn(env, DELEGATED_BY_ENV);
|
|
1195
|
+
}
|
|
1196
|
+
function readHook(adapter, input) {
|
|
1197
|
+
const r = adapter.fromHook(input);
|
|
1198
|
+
if (r && !r.session.parent_id) {
|
|
1199
|
+
const self = `${adapter.name}:${r.session.id}`;
|
|
1200
|
+
const launcher = launchedBy(input.env);
|
|
1201
|
+
if (launcher && launcher !== self) r.session.launched_by = launcher;
|
|
1202
|
+
const asker = delegatedBy(input.env);
|
|
1203
|
+
if (asker && asker !== self) r.session.delegated_by = asker;
|
|
1204
|
+
}
|
|
1205
|
+
return r;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
// ../core/src/adapters/headless.ts
|
|
1139
1209
|
var JOB_TIMEOUT_MS = 30 * 6e4;
|
|
1140
1210
|
var OUT_CAP = 1024 * 1024;
|
|
1141
1211
|
function isExe(p) {
|
|
@@ -1156,13 +1226,38 @@ function findBinary(o) {
|
|
|
1156
1226
|
for (const d of o.dirs) for (const n of o.names) if (isExe(path4.join(d, n))) return path4.join(d, n);
|
|
1157
1227
|
return null;
|
|
1158
1228
|
}
|
|
1229
|
+
function shimTarget(shim) {
|
|
1230
|
+
let text = "";
|
|
1231
|
+
try {
|
|
1232
|
+
text = readFileSync3(shim, "utf8");
|
|
1233
|
+
} catch {
|
|
1234
|
+
return null;
|
|
1235
|
+
}
|
|
1236
|
+
const m = /"%(?:~dp0|dp0%)\\([^"]+)"\s+%\*/.exec(text);
|
|
1237
|
+
if (!m) return null;
|
|
1238
|
+
const target = path4.resolve(path4.dirname(shim), m[1].split("\\").join(path4.sep));
|
|
1239
|
+
try {
|
|
1240
|
+
return statSync3(target).isFile() ? target : null;
|
|
1241
|
+
} catch {
|
|
1242
|
+
return null;
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
function spawnable(bin, args) {
|
|
1246
|
+
if (!/\.(cmd|bat)$/i.test(bin)) return { file: bin, args };
|
|
1247
|
+
const target = shimTarget(bin);
|
|
1248
|
+
return target ? { file: process.execPath, args: [target, ...args] } : null;
|
|
1249
|
+
}
|
|
1250
|
+
function probe(bin, args) {
|
|
1251
|
+
const s = spawnable(bin, args);
|
|
1252
|
+
return s ? { ...s, shell: false } : { file: [`"${bin}"`, ...args].join(" "), args: [], shell: true };
|
|
1253
|
+
}
|
|
1159
1254
|
|
|
1160
1255
|
// ../core/src/adapters/antigravity.ts
|
|
1161
1256
|
import {
|
|
1162
1257
|
existsSync as existsSync3,
|
|
1163
1258
|
mkdirSync as mkdirSync2,
|
|
1164
1259
|
readdirSync as readdirSync3,
|
|
1165
|
-
readFileSync as
|
|
1260
|
+
readFileSync as readFileSync4,
|
|
1166
1261
|
realpathSync,
|
|
1167
1262
|
statSync as statSync4,
|
|
1168
1263
|
unlinkSync as unlinkSync2,
|
|
@@ -1291,7 +1386,7 @@ var antigravity = {
|
|
|
1291
1386
|
readTranscript(file, fromLine) {
|
|
1292
1387
|
let text = "";
|
|
1293
1388
|
try {
|
|
1294
|
-
text =
|
|
1389
|
+
text = readFileSync4(file, "utf8");
|
|
1295
1390
|
} catch {
|
|
1296
1391
|
return { turns: [], line: fromLine };
|
|
1297
1392
|
}
|
|
@@ -1370,7 +1465,7 @@ function agProjects(dir) {
|
|
|
1370
1465
|
if (!ID_RE.test(id)) continue;
|
|
1371
1466
|
let j;
|
|
1372
1467
|
try {
|
|
1373
|
-
j = JSON.parse(
|
|
1468
|
+
j = JSON.parse(readFileSync4(path5.join(dir, n), "utf8"));
|
|
1374
1469
|
} catch {
|
|
1375
1470
|
continue;
|
|
1376
1471
|
}
|
|
@@ -1407,7 +1502,7 @@ function antigravityRemoteUrl(transcript, opts) {
|
|
|
1407
1502
|
for (const d of dirs) {
|
|
1408
1503
|
try {
|
|
1409
1504
|
const m = /installation_uuid:\s*"([0-9a-f-]{36})"/i.exec(
|
|
1410
|
-
|
|
1505
|
+
readFileSync4(path5.join(d, "antigravity_state.pbtxt"), "utf8")
|
|
1411
1506
|
);
|
|
1412
1507
|
if (!m) continue;
|
|
1413
1508
|
const base = `https://antigravity.google.com/r/${m[1]}-v2`;
|
|
@@ -1446,7 +1541,7 @@ function antigravityFacts(file, opts) {
|
|
|
1446
1541
|
}
|
|
1447
1542
|
|
|
1448
1543
|
// ../core/src/adapters/codex.ts
|
|
1449
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as
|
|
1544
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
1450
1545
|
import path6 from "node:path";
|
|
1451
1546
|
|
|
1452
1547
|
// ../core/src/adapters/codex-config.ts
|
|
@@ -1603,7 +1698,7 @@ function installNotify(cmd, env) {
|
|
|
1603
1698
|
const file = configFile(env);
|
|
1604
1699
|
let text = "";
|
|
1605
1700
|
try {
|
|
1606
|
-
text =
|
|
1701
|
+
text = readFileSync5(file, "utf8");
|
|
1607
1702
|
} catch {
|
|
1608
1703
|
}
|
|
1609
1704
|
const argv = cmd([NAME3, "notify"]).argv;
|
|
@@ -1649,7 +1744,7 @@ function uninstallNotify(env) {
|
|
|
1649
1744
|
const file = configFile(env);
|
|
1650
1745
|
let text;
|
|
1651
1746
|
try {
|
|
1652
|
-
text =
|
|
1747
|
+
text = readFileSync5(file, "utf8");
|
|
1653
1748
|
} catch {
|
|
1654
1749
|
return { file, changed: false };
|
|
1655
1750
|
}
|
|
@@ -1721,7 +1816,7 @@ var codex = {
|
|
|
1721
1816
|
];
|
|
1722
1817
|
let text = "";
|
|
1723
1818
|
try {
|
|
1724
|
-
text =
|
|
1819
|
+
text = readFileSync5(configFile(env), "utf8");
|
|
1725
1820
|
} catch {
|
|
1726
1821
|
}
|
|
1727
1822
|
const n = readNotify(text);
|
|
@@ -1848,7 +1943,7 @@ var codex = {
|
|
|
1848
1943
|
readTranscript(file, fromLine) {
|
|
1849
1944
|
let text = "";
|
|
1850
1945
|
try {
|
|
1851
|
-
text =
|
|
1946
|
+
text = readFileSync5(file, "utf8");
|
|
1852
1947
|
} catch {
|
|
1853
1948
|
return { turns: [], line: fromLine };
|
|
1854
1949
|
}
|
|
@@ -2369,7 +2464,7 @@ function devinAccount(env = process.env) {
|
|
|
2369
2464
|
|
|
2370
2465
|
// ../core/src/adapters/gemini-cli.ts
|
|
2371
2466
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
2372
|
-
import { existsSync as existsSync7, readFileSync as
|
|
2467
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
|
|
2373
2468
|
import path8 from "node:path";
|
|
2374
2469
|
var NAME5 = "gemini-cli";
|
|
2375
2470
|
var GEMINI_EVENTS = [
|
|
@@ -2391,14 +2486,14 @@ function geminiVersion(platform = process.platform, env = process.env) {
|
|
|
2391
2486
|
const win = platform === "win32";
|
|
2392
2487
|
const bin = win ? findBinary({ names: ["gemini.exe", "gemini.cmd"], dirs: [path8.dirname(process.execPath)], env }) : "gemini";
|
|
2393
2488
|
if (!bin) throw new Error("no gemini on PATH");
|
|
2394
|
-
const
|
|
2489
|
+
const c = probe(bin, ["--version"]);
|
|
2490
|
+
return execFileSync4(c.file, c.args, {
|
|
2395
2491
|
encoding: "utf8",
|
|
2396
2492
|
timeout: 4e3,
|
|
2397
2493
|
stdio: ["ignore", "pipe", "ignore"],
|
|
2398
|
-
env
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
return out.trim();
|
|
2494
|
+
env,
|
|
2495
|
+
shell: c.shell
|
|
2496
|
+
}).trim();
|
|
2402
2497
|
}
|
|
2403
2498
|
function geminiHooksActive(settingsFile) {
|
|
2404
2499
|
const tools = readJson(settingsFile).tools;
|
|
@@ -2523,7 +2618,7 @@ var geminiCli = {
|
|
|
2523
2618
|
readTranscript(file, fromLine) {
|
|
2524
2619
|
let text = "";
|
|
2525
2620
|
try {
|
|
2526
|
-
text =
|
|
2621
|
+
text = readFileSync6(file, "utf8");
|
|
2527
2622
|
} catch {
|
|
2528
2623
|
return { turns: [], line: fromLine };
|
|
2529
2624
|
}
|
|
@@ -2562,22 +2657,6 @@ function prune5(o) {
|
|
|
2562
2657
|
var ADAPTERS = [claudeCode, codex, geminiCli, antigravity, devin];
|
|
2563
2658
|
var adapterByName = (name) => ADAPTERS.find((a) => a.name === name);
|
|
2564
2659
|
|
|
2565
|
-
// ../core/src/launched-by.ts
|
|
2566
|
-
var LAUNCHED_BY_ENV = "SESSIONPIPE_LAUNCHED_BY";
|
|
2567
|
-
var LAUNCHED_BY = /^[a-z][a-z0-9-]{0,39}:[A-Za-z0-9._:-]{1,128}$/;
|
|
2568
|
-
function launchedBy(env) {
|
|
2569
|
-
const v = env[LAUNCHED_BY_ENV]?.trim();
|
|
2570
|
-
return v && LAUNCHED_BY.test(v) ? v : void 0;
|
|
2571
|
-
}
|
|
2572
|
-
function readHook(adapter, input) {
|
|
2573
|
-
const r = adapter.fromHook(input);
|
|
2574
|
-
if (r && !r.session.parent_id) {
|
|
2575
|
-
const launcher = launchedBy(input.env);
|
|
2576
|
-
if (launcher && launcher !== `${adapter.name}:${r.session.id}`) r.session.launched_by = launcher;
|
|
2577
|
-
}
|
|
2578
|
-
return r;
|
|
2579
|
-
}
|
|
2580
|
-
|
|
2581
2660
|
// src/index.ts
|
|
2582
2661
|
function matches(expect, got, pathStr = "") {
|
|
2583
2662
|
if (expect === "*") return null;
|
|
@@ -2602,7 +2681,7 @@ function matches(expect, got, pathStr = "") {
|
|
|
2602
2681
|
async function runAdapterFixtures(files) {
|
|
2603
2682
|
const results = [];
|
|
2604
2683
|
for (const file of files) {
|
|
2605
|
-
const fx = JSON.parse(
|
|
2684
|
+
const fx = JSON.parse(readFileSync7(file, "utf8"));
|
|
2606
2685
|
const adapter = adapterByName(fx.harness);
|
|
2607
2686
|
if (!adapter) {
|
|
2608
2687
|
results.push({ file, ok: false, why: `no adapter ${fx.harness}` });
|