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.
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +211 -96
- package/dist/cli.js.map +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +195 -93
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAeA,gFAAgF;AAChF,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE;IAAE,MAAM,EAAE;QAAE,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAA;CAAY,GAAG,OAAO,CAAC,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAeA,gFAAgF;AAChF,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE;IAAE,MAAM,EAAE;QAAE,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAA;CAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CA2BnH"}
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// sessionpipe-conformance 0.
|
|
2
|
+
// sessionpipe-conformance 0.13.1 · 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";
|
|
@@ -16,7 +16,7 @@ var homeDir = () => os.homedir();
|
|
|
16
16
|
// ../core/src/adapters/claude-code.ts
|
|
17
17
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
18
18
|
import { createHash } from "node:crypto";
|
|
19
|
-
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync2
|
|
19
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
|
|
20
20
|
import { userInfo } from "node:os";
|
|
21
21
|
import path3 from "node:path";
|
|
22
22
|
|
|
@@ -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 });
|
|
@@ -336,6 +374,41 @@ function readJson(file) {
|
|
|
336
374
|
const f = readJsonFile(file);
|
|
337
375
|
return f.ok ? f.value : {};
|
|
338
376
|
}
|
|
377
|
+
function refused(file, e, what, existed) {
|
|
378
|
+
const { code, syscall } = e;
|
|
379
|
+
const why = `can't ${what} it (${code ?? String(e)})`;
|
|
380
|
+
if (what === "remove" || syscall === "open" || syscall === "mkdir")
|
|
381
|
+
return { file, changed: false, skipped: true, note: `${why}; not touched` };
|
|
382
|
+
return {
|
|
383
|
+
file,
|
|
384
|
+
changed: true,
|
|
385
|
+
skipped: true,
|
|
386
|
+
...existed ? {} : { created: true },
|
|
387
|
+
note: `${why} part way; check the file before the harness reads it`
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
function writeBack(file, text, existed = true) {
|
|
391
|
+
try {
|
|
392
|
+
mkdirSync(path2.dirname(file), { recursive: true });
|
|
393
|
+
writeFileSync(file, text);
|
|
394
|
+
return null;
|
|
395
|
+
} catch (e) {
|
|
396
|
+
return refused(file, e, "write", existed);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
function removeEmptied(file, empty) {
|
|
400
|
+
try {
|
|
401
|
+
unlinkSync(file);
|
|
402
|
+
return { file, changed: true, note: "removed (sessionpipe created it)" };
|
|
403
|
+
} catch (e) {
|
|
404
|
+
const bad = refused(file, e, "remove", true);
|
|
405
|
+
return writeBack(file, empty) ?? {
|
|
406
|
+
file,
|
|
407
|
+
changed: true,
|
|
408
|
+
note: `emptied; ${bad.note.replace(/; not touched$/, "")}, so the empty file stays`
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
}
|
|
339
412
|
function entryFor(event, harness, cmd, o) {
|
|
340
413
|
const matcher = o.matcherFor(event);
|
|
341
414
|
return {
|
|
@@ -377,8 +450,8 @@ function installClaudeShaped(file, harness, events, cmd, o) {
|
|
|
377
450
|
}
|
|
378
451
|
if (changed) {
|
|
379
452
|
settings2.hooks = hooks;
|
|
380
|
-
|
|
381
|
-
|
|
453
|
+
const bad = writeBack(file, formatJson(settings2, f), f.existed);
|
|
454
|
+
if (bad) return bad;
|
|
382
455
|
}
|
|
383
456
|
return { file, changed, created: !f.existed && changed };
|
|
384
457
|
}
|
|
@@ -401,12 +474,8 @@ function uninstallClaudeShaped(file, events, opts = {}) {
|
|
|
401
474
|
}
|
|
402
475
|
if (!changed) return { file, changed: false };
|
|
403
476
|
if (!Object.keys(hooks).length) delete settings2.hooks;
|
|
404
|
-
if (opts.created && !Object.keys(settings2).length)
|
|
405
|
-
|
|
406
|
-
return { file, changed: true, note: "removed (sessionpipe created it)" };
|
|
407
|
-
}
|
|
408
|
-
writeFileSync(file, formatJson(settings2, f));
|
|
409
|
-
return { file, changed: true };
|
|
477
|
+
if (opts.created && !Object.keys(settings2).length) return removeEmptied(file, formatJson(settings2, f));
|
|
478
|
+
return writeBack(file, formatJson(settings2, f)) ?? { file, changed: true };
|
|
410
479
|
}
|
|
411
480
|
function installedClaudeShaped(file, harness, events, cmd, o) {
|
|
412
481
|
const f = readJsonFile(file);
|
|
@@ -485,7 +554,7 @@ var claudeCode = {
|
|
|
485
554
|
configFiles: (env = process.env) => claudeDirs(env).map(settingsIn),
|
|
486
555
|
install: (cmd, env = process.env, opts = {}) => claudeDirs(env).flatMap((d) => {
|
|
487
556
|
const hooks = installClaudeShaped(settingsIn(d), NAME, eventsFor(opts.lean), cmd, OPTS);
|
|
488
|
-
const line = opts.statusLine === void 0 ? null : statusLine(settingsIn(d), opts.statusLine, cmd);
|
|
557
|
+
const line = opts.statusLine === void 0 || hooks.skipped ? null : statusLine(settingsIn(d), opts.statusLine, cmd);
|
|
489
558
|
return line ? [hooks, line] : [hooks];
|
|
490
559
|
}),
|
|
491
560
|
uninstall: (env = process.env, opts = {}) => claudeDirs(env).map((d) => {
|
|
@@ -493,7 +562,9 @@ var claudeCode = {
|
|
|
493
562
|
const hooks = uninstallClaudeShaped(settingsIn(d), CLAUDE_EVENTS, {
|
|
494
563
|
created: opts.created?.includes(settingsIn(d))
|
|
495
564
|
});
|
|
496
|
-
|
|
565
|
+
if (line?.changed && !line.skipped && hooks.skipped)
|
|
566
|
+
return { ...hooks, note: `status line removed, hooks not: ${hooks.note?.replace(/; not touched$/, "")}` };
|
|
567
|
+
return line && !hooks.changed && !hooks.skipped ? line : hooks;
|
|
497
568
|
}),
|
|
498
569
|
installed: (cmd, env = process.env, opts = {}) => claudeDirs(env).map((d) => ({
|
|
499
570
|
file: settingsIn(d),
|
|
@@ -588,7 +659,8 @@ var claudeCode = {
|
|
|
588
659
|
attention_id: call_id ?? permissionId(),
|
|
589
660
|
kind: "permission",
|
|
590
661
|
tool,
|
|
591
|
-
message: tool ? `${tool}${describe(s.tool_input)}` : void 0
|
|
662
|
+
message: tool ? `${tool}${describe(s.tool_input)}` : void 0,
|
|
663
|
+
agent_id
|
|
592
664
|
})
|
|
593
665
|
);
|
|
594
666
|
break;
|
|
@@ -597,7 +669,8 @@ var claudeCode = {
|
|
|
597
669
|
ev("attention.needed", {
|
|
598
670
|
attention_id: `notif-${Date.now()}`,
|
|
599
671
|
kind: notifKind(s.notification_type),
|
|
600
|
-
message: typeof s.message === "string" ? s.message.slice(0, 200) : void 0
|
|
672
|
+
message: typeof s.message === "string" ? s.message.slice(0, 200) : void 0,
|
|
673
|
+
agent_id
|
|
601
674
|
})
|
|
602
675
|
);
|
|
603
676
|
break;
|
|
@@ -722,8 +795,11 @@ function statusLine(file, want, cmd) {
|
|
|
722
795
|
if (want ? ours && cur?.command === command && cur?.refreshInterval === LINE_REFRESH_S : !ours) return null;
|
|
723
796
|
if (want) f.value.statusLine = { type: "command", command, refreshInterval: LINE_REFRESH_S };
|
|
724
797
|
else delete f.value.statusLine;
|
|
725
|
-
|
|
726
|
-
|
|
798
|
+
return writeBack(file, formatJson(f.value, f), f.existed) ?? {
|
|
799
|
+
file,
|
|
800
|
+
changed: true,
|
|
801
|
+
note: want ? "status line written (account limits)" : "status line removed"
|
|
802
|
+
};
|
|
727
803
|
}
|
|
728
804
|
var epochIso = (t) => typeof t === "number" && t > 0 && t < 1e11 ? new Date(t * 1e3).toISOString() : void 0;
|
|
729
805
|
var WINDOW_MINUTES = { five_hour: 300, seven_day: 10080 };
|
|
@@ -1140,6 +1216,34 @@ async function claudeAiTitle(bridge, token) {
|
|
|
1140
1216
|
// ../core/src/adapters/headless.ts
|
|
1141
1217
|
import { accessSync, constants, readFileSync as readFileSync3, statSync as statSync3 } from "node:fs";
|
|
1142
1218
|
import path4 from "node:path";
|
|
1219
|
+
|
|
1220
|
+
// ../core/src/launched-by.ts
|
|
1221
|
+
var LAUNCHED_BY_ENV = "SESSIONPIPE_LAUNCHED_BY";
|
|
1222
|
+
var DELEGATED_BY_ENV = "SESSIONPIPE_ASKED_BY";
|
|
1223
|
+
var SESSION_REF = /^[a-z][a-z0-9-]{0,39}:[A-Za-z0-9._:-]{1,128}$/;
|
|
1224
|
+
function refIn(env, name) {
|
|
1225
|
+
const v = env[name]?.trim();
|
|
1226
|
+
return v && SESSION_REF.test(v) ? v : void 0;
|
|
1227
|
+
}
|
|
1228
|
+
function launchedBy(env) {
|
|
1229
|
+
return refIn(env, LAUNCHED_BY_ENV);
|
|
1230
|
+
}
|
|
1231
|
+
function delegatedBy(env) {
|
|
1232
|
+
return refIn(env, DELEGATED_BY_ENV);
|
|
1233
|
+
}
|
|
1234
|
+
function readHook(adapter, input) {
|
|
1235
|
+
const r = adapter.fromHook(input);
|
|
1236
|
+
if (r && !r.session.parent_id) {
|
|
1237
|
+
const self = `${adapter.name}:${r.session.id}`;
|
|
1238
|
+
const launcher = launchedBy(input.env);
|
|
1239
|
+
if (launcher && launcher !== self) r.session.launched_by = launcher;
|
|
1240
|
+
const asker = delegatedBy(input.env);
|
|
1241
|
+
if (asker && asker !== self) r.session.delegated_by = asker;
|
|
1242
|
+
}
|
|
1243
|
+
return r;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
// ../core/src/adapters/headless.ts
|
|
1143
1247
|
var JOB_TIMEOUT_MS = 30 * 6e4;
|
|
1144
1248
|
var OUT_CAP = 1024 * 1024;
|
|
1145
1249
|
function isExe(p) {
|
|
@@ -1187,16 +1291,7 @@ function probe(bin, args) {
|
|
|
1187
1291
|
}
|
|
1188
1292
|
|
|
1189
1293
|
// ../core/src/adapters/antigravity.ts
|
|
1190
|
-
import {
|
|
1191
|
-
existsSync as existsSync3,
|
|
1192
|
-
mkdirSync as mkdirSync2,
|
|
1193
|
-
readdirSync as readdirSync3,
|
|
1194
|
-
readFileSync as readFileSync4,
|
|
1195
|
-
realpathSync,
|
|
1196
|
-
statSync as statSync4,
|
|
1197
|
-
unlinkSync as unlinkSync2,
|
|
1198
|
-
writeFileSync as writeFileSync3
|
|
1199
|
-
} from "node:fs";
|
|
1294
|
+
import { existsSync as existsSync3, readdirSync as readdirSync3, readFileSync as readFileSync4, realpathSync, statSync as statSync4 } from "node:fs";
|
|
1200
1295
|
import path5 from "node:path";
|
|
1201
1296
|
var NAME2 = "antigravity";
|
|
1202
1297
|
var HOOK_NAME = "sessionpipe";
|
|
@@ -1228,10 +1323,8 @@ var antigravity = {
|
|
|
1228
1323
|
const e = entry(cmd);
|
|
1229
1324
|
if (JSON.stringify(hooks[HOOK_NAME]) === JSON.stringify(e)) return [{ file, changed: false }];
|
|
1230
1325
|
hooks[HOOK_NAME] = e;
|
|
1231
|
-
mkdirSync2(path5.dirname(file), { recursive: true });
|
|
1232
|
-
writeFileSync3(file, formatJson(hooks, f));
|
|
1233
1326
|
return [
|
|
1234
|
-
{
|
|
1327
|
+
writeBack(file, formatJson(hooks, f), f.existed) ?? {
|
|
1235
1328
|
file,
|
|
1236
1329
|
changed: true,
|
|
1237
1330
|
created: !f.existed,
|
|
@@ -1247,12 +1340,8 @@ var antigravity = {
|
|
|
1247
1340
|
const hooks = f.value;
|
|
1248
1341
|
if (!(HOOK_NAME in hooks)) return [{ file, changed: false }];
|
|
1249
1342
|
delete hooks[HOOK_NAME];
|
|
1250
|
-
if (opts.created?.includes(file) && !Object.keys(hooks).length)
|
|
1251
|
-
|
|
1252
|
-
return [{ file, changed: true, note: "removed (sessionpipe created it)" }];
|
|
1253
|
-
}
|
|
1254
|
-
writeFileSync3(file, formatJson(hooks, f));
|
|
1255
|
-
return [{ file, changed: true }];
|
|
1343
|
+
if (opts.created?.includes(file) && !Object.keys(hooks).length) return [removeEmptied(file, formatJson(hooks, f))];
|
|
1344
|
+
return [writeBack(file, formatJson(hooks, f)) ?? { file, changed: true }];
|
|
1256
1345
|
},
|
|
1257
1346
|
installed(cmd, env = process.env) {
|
|
1258
1347
|
const file = hooksFile(env);
|
|
@@ -1475,7 +1564,7 @@ function antigravityFacts(file, opts) {
|
|
|
1475
1564
|
}
|
|
1476
1565
|
|
|
1477
1566
|
// ../core/src/adapters/codex.ts
|
|
1478
|
-
import { existsSync as existsSync4,
|
|
1567
|
+
import { existsSync as existsSync4, readdirSync as readdirSync4, readFileSync as readFileSync5 } from "node:fs";
|
|
1479
1568
|
import path6 from "node:path";
|
|
1480
1569
|
|
|
1481
1570
|
// ../core/src/adapters/codex-config.ts
|
|
@@ -1628,29 +1717,30 @@ var configFile = (env) => path6.join(codexHome(env), "config.toml");
|
|
|
1628
1717
|
var sessionsDir = (env) => path6.join(codexHome(env), "sessions");
|
|
1629
1718
|
var validTool3 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
1630
1719
|
var endReason2 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
|
|
1720
|
+
var UNTIL_APPROVED = "Codex will report nothing until you approve the hooks: start `codex` and accept the hook review";
|
|
1631
1721
|
function installNotify(cmd, env) {
|
|
1632
1722
|
const file = configFile(env);
|
|
1633
1723
|
let text = "";
|
|
1724
|
+
let existed = true;
|
|
1634
1725
|
try {
|
|
1635
1726
|
text = readFileSync5(file, "utf8");
|
|
1636
|
-
} catch {
|
|
1727
|
+
} catch (e) {
|
|
1728
|
+
existed = e.code !== "ENOENT";
|
|
1637
1729
|
}
|
|
1638
1730
|
const argv = cmd([NAME3, "notify"]).argv;
|
|
1639
1731
|
const line = `notify = ${JSON.stringify(argv)}`;
|
|
1640
1732
|
const n = readNotify(text);
|
|
1641
|
-
const write = (next) => {
|
|
1642
|
-
|
|
1643
|
-
|
|
1733
|
+
const write = (next, note) => {
|
|
1734
|
+
const bad = writeBack(file, next, existed);
|
|
1735
|
+
return bad ? { ...bad, note: `${bad.note}. ${UNTIL_APPROVED}` } : { file, changed: true, note };
|
|
1644
1736
|
};
|
|
1645
1737
|
if (n && !n.other) {
|
|
1646
1738
|
if (!n.misplaced && JSON.stringify(n.argv) === JSON.stringify(argv))
|
|
1647
1739
|
return { file, changed: false, note: "notify current" };
|
|
1648
|
-
write(
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
note: n.misplaced ? "notify moved to the top level (it sat inside a [table], where Codex never reads it)" : "notify updated"
|
|
1653
|
-
};
|
|
1740
|
+
return write(
|
|
1741
|
+
withNotify(text, line),
|
|
1742
|
+
n.misplaced ? "notify moved to the top level (it sat inside a [table], where Codex never reads it)" : "notify updated"
|
|
1743
|
+
);
|
|
1654
1744
|
}
|
|
1655
1745
|
if (n?.other) {
|
|
1656
1746
|
const prev = previousNotify(n.argv);
|
|
@@ -1659,20 +1749,20 @@ function installNotify(cmd, env) {
|
|
|
1659
1749
|
if (JSON.stringify(prev) === JSON.stringify(argv))
|
|
1660
1750
|
return { file, changed: false, note: "notify current (after Codex Computer Use)" };
|
|
1661
1751
|
}
|
|
1662
|
-
if (!prev || OURS2.test(JSON.stringify(prev)))
|
|
1663
|
-
write(
|
|
1664
|
-
|
|
1665
|
-
|
|
1752
|
+
if (!prev || OURS2.test(JSON.stringify(prev)))
|
|
1753
|
+
return write(
|
|
1754
|
+
replaceNotify(text, n, `notify = ${withPreviousRaw(n.raw, argv)}`),
|
|
1755
|
+
"chained after Codex Computer Use (--previous-notify)"
|
|
1756
|
+
);
|
|
1666
1757
|
}
|
|
1667
1758
|
return {
|
|
1668
1759
|
file,
|
|
1669
1760
|
changed: false,
|
|
1670
1761
|
skipped: true,
|
|
1671
|
-
note: `notify belongs to ${(n.argv?.[0] ?? n.raw).replace(/^.*\//, "").slice(0, 40)}; left alone.
|
|
1762
|
+
note: `notify belongs to ${(n.argv?.[0] ?? n.raw).replace(/^.*\//, "").slice(0, 40)}; left alone. ${UNTIL_APPROVED}`
|
|
1672
1763
|
};
|
|
1673
1764
|
}
|
|
1674
|
-
write(withNotify(text, line));
|
|
1675
|
-
return { file, changed: true, note: "notify set at the top level" };
|
|
1765
|
+
return write(withNotify(text, line), "notify set at the top level");
|
|
1676
1766
|
}
|
|
1677
1767
|
function uninstallNotify(env) {
|
|
1678
1768
|
const file = configFile(env);
|
|
@@ -1684,15 +1774,13 @@ function uninstallNotify(env) {
|
|
|
1684
1774
|
}
|
|
1685
1775
|
const n = readNotify(text);
|
|
1686
1776
|
if (!n) return { file, changed: false };
|
|
1687
|
-
if (!n.other) {
|
|
1688
|
-
writeFileSync4(file, withoutNotify(text));
|
|
1689
|
-
return { file, changed: true };
|
|
1690
|
-
}
|
|
1777
|
+
if (!n.other) return writeBack(file, withoutNotify(text)) ?? { file, changed: true };
|
|
1691
1778
|
const prev = previousNotify(n.argv);
|
|
1692
|
-
if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev)))
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1779
|
+
if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev)))
|
|
1780
|
+
return writeBack(file, replaceNotify(text, n, `notify = ${stripPreviousRaw(n.raw)}`)) ?? {
|
|
1781
|
+
file,
|
|
1782
|
+
changed: true
|
|
1783
|
+
};
|
|
1696
1784
|
return { file, changed: false };
|
|
1697
1785
|
}
|
|
1698
1786
|
function findRollout(threadId, env = process.env) {
|
|
@@ -1724,8 +1812,12 @@ function rolloutMeta(file) {
|
|
|
1724
1812
|
return scanLines(file, (line) => parseLines([line])[0] ?? {});
|
|
1725
1813
|
}
|
|
1726
1814
|
function ofAnotherThread(file, id) {
|
|
1815
|
+
const other = threadOf(file);
|
|
1816
|
+
return !!other && other !== id.toLowerCase();
|
|
1817
|
+
}
|
|
1818
|
+
function threadOf(file) {
|
|
1727
1819
|
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);
|
|
1728
|
-
return
|
|
1820
|
+
return m?.[1] ? m[1].toLowerCase() : null;
|
|
1729
1821
|
}
|
|
1730
1822
|
var codex = {
|
|
1731
1823
|
name: NAME3,
|
|
@@ -1804,6 +1896,8 @@ var codex = {
|
|
|
1804
1896
|
const turn_id = typeof s.turn_id === "string" ? s.turn_id : void 0;
|
|
1805
1897
|
const tool = validTool3(s.tool_name) ? s.tool_name : void 0;
|
|
1806
1898
|
const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
|
|
1899
|
+
const thread = typeof s.transcript_path === "string" ? threadOf(s.transcript_path) : null;
|
|
1900
|
+
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;
|
|
1807
1901
|
const ev = (type, data) => ({ type, data: prune3(data), harnessEvent: event });
|
|
1808
1902
|
const events = [];
|
|
1809
1903
|
switch (event) {
|
|
@@ -1823,17 +1917,30 @@ var codex = {
|
|
|
1823
1917
|
);
|
|
1824
1918
|
break;
|
|
1825
1919
|
case "PreToolUse":
|
|
1826
|
-
if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, input: s.tool_input }));
|
|
1920
|
+
if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, agent_id, input: s.tool_input }));
|
|
1827
1921
|
break;
|
|
1828
1922
|
case "PostToolUse":
|
|
1829
1923
|
if (tool)
|
|
1830
1924
|
events.push(
|
|
1831
|
-
ev("tool.ended", {
|
|
1925
|
+
ev("tool.ended", {
|
|
1926
|
+
tool,
|
|
1927
|
+
call_id,
|
|
1928
|
+
turn_id,
|
|
1929
|
+
agent_id,
|
|
1930
|
+
ok: true,
|
|
1931
|
+
input: s.tool_input,
|
|
1932
|
+
output: s.tool_response
|
|
1933
|
+
})
|
|
1832
1934
|
);
|
|
1833
1935
|
break;
|
|
1834
1936
|
case "PermissionRequest":
|
|
1835
1937
|
events.push(
|
|
1836
|
-
ev("attention.needed", {
|
|
1938
|
+
ev("attention.needed", {
|
|
1939
|
+
attention_id: call_id ?? `perm-${Date.now()}`,
|
|
1940
|
+
kind: "permission",
|
|
1941
|
+
tool,
|
|
1942
|
+
agent_id
|
|
1943
|
+
})
|
|
1837
1944
|
);
|
|
1838
1945
|
break;
|
|
1839
1946
|
case "Stop":
|
|
@@ -2323,7 +2430,7 @@ var CHAIN_LIMIT = 4e3;
|
|
|
2323
2430
|
var CHAIN_SQL = `WITH RECURSIVE chain(node_id, parent_node_id, chat_message, created_at) AS (
|
|
2324
2431
|
SELECT node_id, parent_node_id, chat_message, created_at FROM message_nodes
|
|
2325
2432
|
WHERE session_id = ? AND node_id = (SELECT main_chain_id FROM sessions WHERE id = ?)
|
|
2326
|
-
UNION
|
|
2433
|
+
UNION
|
|
2327
2434
|
SELECT m.node_id, m.parent_node_id, m.chat_message, m.created_at FROM message_nodes m
|
|
2328
2435
|
JOIN chain c ON m.node_id = c.parent_node_id AND m.session_id = ? AND m.node_id >= ?
|
|
2329
2436
|
)
|
|
@@ -2591,22 +2698,6 @@ function prune5(o) {
|
|
|
2591
2698
|
var ADAPTERS = [claudeCode, codex, geminiCli, antigravity, devin];
|
|
2592
2699
|
var adapterByName = (name) => ADAPTERS.find((a) => a.name === name);
|
|
2593
2700
|
|
|
2594
|
-
// ../core/src/launched-by.ts
|
|
2595
|
-
var LAUNCHED_BY_ENV = "SESSIONPIPE_LAUNCHED_BY";
|
|
2596
|
-
var LAUNCHED_BY = /^[a-z][a-z0-9-]{0,39}:[A-Za-z0-9._:-]{1,128}$/;
|
|
2597
|
-
function launchedBy(env) {
|
|
2598
|
-
const v = env[LAUNCHED_BY_ENV]?.trim();
|
|
2599
|
-
return v && LAUNCHED_BY.test(v) ? v : void 0;
|
|
2600
|
-
}
|
|
2601
|
-
function readHook(adapter, input) {
|
|
2602
|
-
const r = adapter.fromHook(input);
|
|
2603
|
-
if (r && !r.session.parent_id) {
|
|
2604
|
-
const launcher = launchedBy(input.env);
|
|
2605
|
-
if (launcher && launcher !== `${adapter.name}:${r.session.id}`) r.session.launched_by = launcher;
|
|
2606
|
-
}
|
|
2607
|
-
return r;
|
|
2608
|
-
}
|
|
2609
|
-
|
|
2610
2701
|
// src/index.ts
|
|
2611
2702
|
function matches(expect, got, pathStr = "") {
|
|
2612
2703
|
if (expect === "*") return null;
|
|
@@ -2631,7 +2722,18 @@ function matches(expect, got, pathStr = "") {
|
|
|
2631
2722
|
async function runAdapterFixtures(files) {
|
|
2632
2723
|
const results = [];
|
|
2633
2724
|
for (const file of files) {
|
|
2634
|
-
|
|
2725
|
+
let fx;
|
|
2726
|
+
try {
|
|
2727
|
+
fx = JSON.parse(readFileSync7(file, "utf8"));
|
|
2728
|
+
} catch (e) {
|
|
2729
|
+
const code = e.code;
|
|
2730
|
+
results.push({ file, ok: false, why: code ? `not readable: ${code}` : `not JSON: ${e.message}` });
|
|
2731
|
+
continue;
|
|
2732
|
+
}
|
|
2733
|
+
if (typeof fx?.harness !== "string" || !fx.input || !Array.isArray(fx.expect)) {
|
|
2734
|
+
results.push({ file, ok: false, why: "not an adapter fixture: it needs harness, input and expect" });
|
|
2735
|
+
continue;
|
|
2736
|
+
}
|
|
2635
2737
|
const adapter = adapterByName(fx.harness);
|
|
2636
2738
|
if (!adapter) {
|
|
2637
2739
|
results.push({ file, ok: false, why: `no adapter ${fx.harness}` });
|
|
@@ -2673,14 +2775,27 @@ var walk = (d) => readdirSync5(d).flatMap((n) => {
|
|
|
2673
2775
|
async function main(argv, io = process) {
|
|
2674
2776
|
const target = argv[0];
|
|
2675
2777
|
if (!target) {
|
|
2676
|
-
io.stdout.write("usage: sessionpipe-conformance <conformance/harness dir | https://receiver>\n");
|
|
2778
|
+
io.stdout.write("usage: sessionpipe-conformance <conformance/harness dir | fixture file | https://receiver>\n");
|
|
2677
2779
|
return 2;
|
|
2678
2780
|
}
|
|
2679
|
-
if (/^https
|
|
2781
|
+
if (/^https?:\/\//i.test(target)) {
|
|
2680
2782
|
io.stdout.write("receiver scenarios land in M3\n");
|
|
2681
2783
|
return 2;
|
|
2682
2784
|
}
|
|
2683
|
-
|
|
2785
|
+
let files;
|
|
2786
|
+
try {
|
|
2787
|
+
files = statSync5(target).isDirectory() ? walk(target) : [target];
|
|
2788
|
+
} catch (e) {
|
|
2789
|
+
io.stdout.write(`sessionpipe-conformance: ${e.message}
|
|
2790
|
+
`);
|
|
2791
|
+
return 2;
|
|
2792
|
+
}
|
|
2793
|
+
if (!files.length) {
|
|
2794
|
+
io.stdout.write(`sessionpipe-conformance: no .json fixtures under ${target}
|
|
2795
|
+
`);
|
|
2796
|
+
return 2;
|
|
2797
|
+
}
|
|
2798
|
+
const r = await runAdapterFixtures(files);
|
|
2684
2799
|
io.stdout.write(`${r.table}
|
|
2685
2800
|
${r.results.length - r.failed}/${r.results.length} passed
|
|
2686
2801
|
`);
|