sessionpipe-conformance 0.6.1 → 0.9.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/README.md +1 -1
- package/dist/cli.d.ts +6 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +655 -162
- package/dist/cli.js.map +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +588 -109
- package/dist/index.js.map +4 -4
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// sessionpipe-conformance 0.
|
|
1
|
+
// sessionpipe-conformance 0.9.0 · Apache-2.0 · https://sessionpipe.org
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { readFileSync as readFileSync6 } from "node:fs";
|
|
@@ -18,7 +18,7 @@ import path3 from "node:path";
|
|
|
18
18
|
|
|
19
19
|
// ../core/src/paths.ts
|
|
20
20
|
import os from "node:os";
|
|
21
|
-
var
|
|
21
|
+
var homeDir = () => os.homedir();
|
|
22
22
|
|
|
23
23
|
// ../core/src/readers/files.ts
|
|
24
24
|
import { closeSync, fstatSync, openSync, readdirSync, readSync, statSync } from "node:fs";
|
|
@@ -110,6 +110,19 @@ function recentFiles(root, depth, sinceMs, match) {
|
|
|
110
110
|
return out;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
// ../core/src/adapters/codex-rollout.ts
|
|
114
|
+
function codexSubagent(meta) {
|
|
115
|
+
if (meta?.type !== "session_meta") return false;
|
|
116
|
+
const p = meta.payload;
|
|
117
|
+
return !!p?.source?.subagent;
|
|
118
|
+
}
|
|
119
|
+
function codexReplayBoundary(meta) {
|
|
120
|
+
if (meta?.type !== "session_meta") return void 0;
|
|
121
|
+
const p = meta.payload;
|
|
122
|
+
if (!p || !p.forked_from_id && !p.source?.subagent) return void 0;
|
|
123
|
+
return (record) => typeof p.id === "string" && !!p.id && record?.type === "event_msg" && record.payload?.thread_id === p.id;
|
|
124
|
+
}
|
|
125
|
+
|
|
113
126
|
// ../core/src/readers/transcripts.ts
|
|
114
127
|
function pairTurns(msgs, endLine) {
|
|
115
128
|
const turns = [];
|
|
@@ -132,10 +145,14 @@ ${m.text}`;
|
|
|
132
145
|
}
|
|
133
146
|
if (cur?.assistant) turns.push(cur);
|
|
134
147
|
const line = cur && !cur.assistant ? cur.startLine : endLine;
|
|
135
|
-
|
|
148
|
+
const out = {
|
|
149
|
+
turns: turns.map((t) => ({ user: t.user, assistant: t.assistant, at: t.at, endLine: t.endLine })),
|
|
150
|
+
line
|
|
151
|
+
};
|
|
152
|
+
if (cur && !cur.assistant && cur.user) out.open = { user: cur.user, at: cur.at };
|
|
153
|
+
return out;
|
|
136
154
|
}
|
|
137
|
-
var isInjected = (text) => /^<[a-z_-]
|
|
138
|
-
var endOf = (lines) => lines[lines.length - 1] === "" ? lines.length - 1 : lines.length;
|
|
155
|
+
var isInjected = (text) => /^<[a-z_-]+(?:\s[^>]*)?>/i.test(text) && /<\/[a-z_-]+>\s*$/i.test(text) || /^<!--\s/.test(text) || /^<command-/.test(text);
|
|
139
156
|
var parse = (raw) => {
|
|
140
157
|
try {
|
|
141
158
|
return JSON.parse(raw);
|
|
@@ -143,6 +160,11 @@ var parse = (raw) => {
|
|
|
143
160
|
return null;
|
|
144
161
|
}
|
|
145
162
|
};
|
|
163
|
+
var endOf = (lines) => {
|
|
164
|
+
const last = lines[lines.length - 1];
|
|
165
|
+
if (last === void 0) return 0;
|
|
166
|
+
return last === "" || parse(last) === null ? lines.length - 1 : lines.length;
|
|
167
|
+
};
|
|
146
168
|
var textParts = (c, kinds) => {
|
|
147
169
|
if (typeof c === "string") return c;
|
|
148
170
|
if (!Array.isArray(c)) return "";
|
|
@@ -150,13 +172,21 @@ var textParts = (c, kinds) => {
|
|
|
150
172
|
(b) => b && typeof b === "object" && kinds.includes(b.type ?? "") && typeof b.text === "string"
|
|
151
173
|
).map((b) => b.text).join("\n");
|
|
152
174
|
};
|
|
175
|
+
var queuedPrompt = (a) => {
|
|
176
|
+
if (!a || typeof a !== "object") return "";
|
|
177
|
+
const q = a;
|
|
178
|
+
if (q.type !== "queued_command" || (q.commandMode ?? "prompt") !== "prompt") return "";
|
|
179
|
+
if (q.origin?.kind && q.origin.kind !== "human") return "";
|
|
180
|
+
return textParts(q.prompt, ["text"]).trim();
|
|
181
|
+
};
|
|
153
182
|
function readClaude(lines, from) {
|
|
154
183
|
const msgs = [];
|
|
155
184
|
for (let i = from; i < lines.length; i++) {
|
|
156
185
|
const j = lines[i] ? parse(lines[i]) : null;
|
|
157
186
|
if (!j || j.isSidechain) continue;
|
|
158
187
|
const origin = j.origin;
|
|
159
|
-
if (j.type === "user" && (j.isMeta || origin?.kind && origin.kind !== "human"))
|
|
188
|
+
if (j.type === "user" && (j.isMeta || j.isCompactSummary || j.repeatsCompactSummary || origin?.kind && origin.kind !== "human"))
|
|
189
|
+
continue;
|
|
160
190
|
const at = Date.parse(String(j.timestamp ?? "")) || Date.now();
|
|
161
191
|
const c = j.message?.content;
|
|
162
192
|
if (j.type === "user") {
|
|
@@ -166,13 +196,22 @@ function readClaude(lines, from) {
|
|
|
166
196
|
} else if (j.type === "assistant" && Array.isArray(c)) {
|
|
167
197
|
const text = textParts(c, ["text"]).trim();
|
|
168
198
|
if (text) msgs.push({ role: "assistant", text, at, line: i });
|
|
199
|
+
} else if (j.type === "attachment") {
|
|
200
|
+
const text = queuedPrompt(j.attachment);
|
|
201
|
+
if (text && !isInjected(text)) msgs.push({ role: "user", text, at, line: i });
|
|
169
202
|
}
|
|
170
203
|
}
|
|
171
204
|
return pairTurns(msgs, endOf(lines));
|
|
172
205
|
}
|
|
206
|
+
function codexInjected(text) {
|
|
207
|
+
return /^<[a-z_-]+>/i.test(text) || /^# AGENTS\.md instructions\b/i.test(text);
|
|
208
|
+
}
|
|
173
209
|
function readCodex(lines, from) {
|
|
174
210
|
const msgs = [];
|
|
175
|
-
|
|
211
|
+
const boundary = codexReplayBoundary(parse(lines.find((line) => line.trim()) ?? ""));
|
|
212
|
+
const start = boundary ? lines.findIndex((line) => boundary(parse(line))) : 0;
|
|
213
|
+
if (start < 0) return pairTurns(msgs, endOf(lines));
|
|
214
|
+
for (let i = Math.max(from, start); i < lines.length; i++) {
|
|
176
215
|
const j = lines[i] ? parse(lines[i]) : null;
|
|
177
216
|
if (!j || j.type !== "response_item") continue;
|
|
178
217
|
const p = j.payload;
|
|
@@ -181,7 +220,7 @@ function readCodex(lines, from) {
|
|
|
181
220
|
const text = textParts(p.content, ["input_text", "output_text"]).trim();
|
|
182
221
|
if (!text) continue;
|
|
183
222
|
if (p.role === "user") {
|
|
184
|
-
if (
|
|
223
|
+
if (codexInjected(text)) continue;
|
|
185
224
|
msgs.push({ role: "user", text, at, line: i });
|
|
186
225
|
} else if (p.role === "assistant") msgs.push({ role: "assistant", text, at, line: i });
|
|
187
226
|
}
|
|
@@ -210,7 +249,7 @@ function readAntigravity(lines, from) {
|
|
|
210
249
|
const at = Date.parse(String(j.created_at ?? j.timestamp ?? "")) || Date.now();
|
|
211
250
|
if (j.type === "USER_INPUT") {
|
|
212
251
|
const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
|
|
213
|
-
const text = (m ? m[1]
|
|
252
|
+
const text = (m ? m[1] : j.content).trim();
|
|
214
253
|
if (text) msgs.push({ role: "user", text, at, line: i });
|
|
215
254
|
} else if (j.type === "PLANNER_RESPONSE") {
|
|
216
255
|
const text = j.content.trim();
|
|
@@ -258,6 +297,10 @@ function formatJson(value, f) {
|
|
|
258
297
|
const text = JSON.stringify(value, null, f.indent).replace(/\n/g, f.eol);
|
|
259
298
|
return f.finalNewline ? text + f.eol : text;
|
|
260
299
|
}
|
|
300
|
+
function readJson(file) {
|
|
301
|
+
const f = readJsonFile(file);
|
|
302
|
+
return f.ok ? f.value : {};
|
|
303
|
+
}
|
|
261
304
|
function entryFor(event, harness, cmd, o) {
|
|
262
305
|
const matcher = o.matcherFor(event);
|
|
263
306
|
return {
|
|
@@ -327,8 +370,7 @@ function uninstallClaudeShaped(file, events, opts = {}) {
|
|
|
327
370
|
unlinkSync(file);
|
|
328
371
|
return { file, changed: true, note: "removed (sessionpipe created it)" };
|
|
329
372
|
}
|
|
330
|
-
|
|
331
|
-
if (next !== f.raw) writeFileSync(file, next);
|
|
373
|
+
writeFileSync(file, formatJson(settings2, f));
|
|
332
374
|
return { file, changed: true };
|
|
333
375
|
}
|
|
334
376
|
function installedClaudeShaped(file, harness, events, cmd, o) {
|
|
@@ -355,7 +397,7 @@ var HOOK_NAME = "sessionpipe";
|
|
|
355
397
|
var AG_EVENTS = ["PreInvocation", "PostInvocation", "PostToolUse", "Stop"];
|
|
356
398
|
var AG_STALE_PRETOOL = '{"decision":"ask","reason":"sessionpipe: an outdated PreToolUse hook is still loaded. Restart this conversation (or run sessionpipe install) to drop it."}\n';
|
|
357
399
|
var antigravityAnswer = (event) => event === "PreToolUse" ? AG_STALE_PRETOOL : "{}\n";
|
|
358
|
-
var gemini = (env) => env.GEMINI_CLI_HOME || path3.join(
|
|
400
|
+
var gemini = (env) => env.GEMINI_CLI_HOME || path3.join(homeDir(), ".gemini");
|
|
359
401
|
var hooksFile = (env) => path3.join(gemini(env), "config", "hooks.json");
|
|
360
402
|
var projectsDir = (env) => path3.join(gemini(env), "config", "projects");
|
|
361
403
|
var dataDirs = (env) => ["antigravity", "antigravity-cli", "antigravity-ide"].map((d) => path3.join(gemini(env), d));
|
|
@@ -421,7 +463,8 @@ var antigravity = {
|
|
|
421
463
|
const event = input.argv[1] ?? "";
|
|
422
464
|
let s = {};
|
|
423
465
|
try {
|
|
424
|
-
|
|
466
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
467
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
425
468
|
} catch {
|
|
426
469
|
}
|
|
427
470
|
const id = s.conversationId ?? s.session_id ?? s.sessionId;
|
|
@@ -477,19 +520,16 @@ var antigravity = {
|
|
|
477
520
|
}
|
|
478
521
|
return readAntigravity(text.split("\n"), fromLine);
|
|
479
522
|
},
|
|
480
|
-
facts(session, transcript, hints
|
|
523
|
+
facts(session, transcript, hints) {
|
|
481
524
|
if (!transcript) return {};
|
|
482
525
|
return antigravityFacts(transcript, {
|
|
483
526
|
conversation: session.id,
|
|
484
527
|
workspaces: hints.workspaces ?? (session.cwd ? [session.cwd] : []),
|
|
485
|
-
state,
|
|
486
528
|
env: process.env
|
|
487
529
|
});
|
|
488
530
|
},
|
|
489
531
|
backfill(sinceMs, env = process.env) {
|
|
490
|
-
const
|
|
491
|
-
const noState = { get: () => ({}), save: () => {
|
|
492
|
-
} };
|
|
532
|
+
const rows2 = [];
|
|
493
533
|
for (const data of dataDirs(env)) {
|
|
494
534
|
let ids = [];
|
|
495
535
|
try {
|
|
@@ -503,13 +543,13 @@ var antigravity = {
|
|
|
503
543
|
} catch {
|
|
504
544
|
continue;
|
|
505
545
|
}
|
|
506
|
-
const facts = antigravityFacts(f, { conversation: id, workspaces: [],
|
|
546
|
+
const facts = antigravityFacts(f, { conversation: id, workspaces: [], env });
|
|
507
547
|
if (!facts.started_at || !facts.last_at) continue;
|
|
508
548
|
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
509
|
-
|
|
549
|
+
rows2.push({ session: { id, ...rest }, started_at, last_at });
|
|
510
550
|
}
|
|
511
551
|
}
|
|
512
|
-
return
|
|
552
|
+
return rows2;
|
|
513
553
|
}
|
|
514
554
|
};
|
|
515
555
|
function prune(o) {
|
|
@@ -559,7 +599,7 @@ function agProjects(dir) {
|
|
|
559
599
|
}
|
|
560
600
|
const folders = [];
|
|
561
601
|
for (const r of j?.projectResources?.resources ?? []) {
|
|
562
|
-
const f = agPath(r?.folderUri);
|
|
602
|
+
const f = agPath(r?.folderUri ?? r?.gitFolder?.folderUri);
|
|
563
603
|
if (f) folders.push(...agVariants(f));
|
|
564
604
|
}
|
|
565
605
|
if (folders.length) out.push({ id, folders });
|
|
@@ -613,7 +653,7 @@ function antigravityFacts(file, opts) {
|
|
|
613
653
|
for (const j of H) {
|
|
614
654
|
if (j.type !== "USER_INPUT" || typeof j.content !== "string") continue;
|
|
615
655
|
const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
|
|
616
|
-
const text = (m ? m[1]
|
|
656
|
+
const text = (m ? m[1] : j.content).trim();
|
|
617
657
|
const line = (text.split("\n").find((l) => l.trim()) ?? "").trim();
|
|
618
658
|
if (!line || /^<[a-z_-]+>/i.test(line)) continue;
|
|
619
659
|
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
@@ -661,10 +701,9 @@ function gitFacts(cwd) {
|
|
|
661
701
|
return out;
|
|
662
702
|
}
|
|
663
703
|
function stripCredentials(url) {
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
return u.replace(/^https?:\/\//, "").replace(/\.git$/, "");
|
|
704
|
+
const ssh = /^(?:ssh:\/\/)?git@([^:/]+)[:/](.+?)(?:\.git)?$/.exec(url);
|
|
705
|
+
if (ssh) return `${ssh[1]}/${ssh[2]}`;
|
|
706
|
+
return url.replace(/\/\/[^/@]*@/, "//").replace(/^https?:\/\//, "").replace(/\.git$/, "");
|
|
668
707
|
}
|
|
669
708
|
|
|
670
709
|
// ../core/src/adapters/claude-code.ts
|
|
@@ -695,7 +734,7 @@ var OPTS = {
|
|
|
695
734
|
timeoutFor: (e) => e === "PermissionRequest" ? 130 : void 0
|
|
696
735
|
};
|
|
697
736
|
var DESKTOP_ID = /^local_[A-Za-z0-9-]{1,64}$/;
|
|
698
|
-
function claudeDirs(env = process.env, home = env.HOME ||
|
|
737
|
+
function claudeDirs(env = process.env, home = env.HOME || homeDir()) {
|
|
699
738
|
const out = [];
|
|
700
739
|
const add = (d) => {
|
|
701
740
|
if (!d) return;
|
|
@@ -736,11 +775,13 @@ var claudeCode = {
|
|
|
736
775
|
const event = input.argv[1] ?? "";
|
|
737
776
|
let s = {};
|
|
738
777
|
try {
|
|
739
|
-
|
|
778
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
779
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
740
780
|
} catch {
|
|
741
781
|
}
|
|
742
782
|
const id = s.session_id ?? s.sessionId;
|
|
743
783
|
if (!id) return null;
|
|
784
|
+
if (notClaudeCode(String(id), s)) return null;
|
|
744
785
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
745
786
|
if (typeof s.model === "string") session.model = s.model;
|
|
746
787
|
if (typeof s.agent_id === "string" && event.startsWith("Subagent") === false && s.agent_id)
|
|
@@ -859,6 +900,7 @@ var claudeCode = {
|
|
|
859
900
|
return null;
|
|
860
901
|
}
|
|
861
902
|
const hints = {};
|
|
903
|
+
if (event === "UserPromptSubmit" && typeof s.prompt === "string") hints.prompt = s.prompt;
|
|
862
904
|
const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
|
|
863
905
|
if (host && DESKTOP_ID.test(host)) hints.desktop_id = host;
|
|
864
906
|
if (event !== "PreToolUse" && event !== "PostToolUse") hints.claude_pid = claudePid(input.env);
|
|
@@ -884,7 +926,7 @@ var claudeCode = {
|
|
|
884
926
|
return out;
|
|
885
927
|
},
|
|
886
928
|
backfill(sinceMs, env = process.env) {
|
|
887
|
-
const
|
|
929
|
+
const rows2 = [];
|
|
888
930
|
const files = [];
|
|
889
931
|
for (const d of claudeDirs(env))
|
|
890
932
|
files.push(...recentFiles(path4.join(d, "projects"), 1, sinceMs, (n) => /^[0-9a-f-]{36}\.jsonl$/.test(n)));
|
|
@@ -896,19 +938,23 @@ var claudeCode = {
|
|
|
896
938
|
if (!facts.started_at || !facts.last_at) continue;
|
|
897
939
|
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
898
940
|
const cwd = rest.cwd;
|
|
899
|
-
|
|
941
|
+
rows2.push({
|
|
900
942
|
session: { id, ...rest, ...gitFacts(cwd), ...rest.branch ? { branch: rest.branch } : {} },
|
|
901
943
|
started_at,
|
|
902
944
|
last_at
|
|
903
945
|
});
|
|
904
946
|
}
|
|
905
|
-
return
|
|
947
|
+
return rows2;
|
|
906
948
|
}
|
|
907
949
|
};
|
|
908
950
|
function prune2(o) {
|
|
909
951
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
910
952
|
return o;
|
|
911
953
|
}
|
|
954
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
955
|
+
function notClaudeCode(id, s) {
|
|
956
|
+
return typeof s.transcript_path !== "string" && !UUID.test(id);
|
|
957
|
+
}
|
|
912
958
|
function pickSource(v) {
|
|
913
959
|
return typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
|
|
914
960
|
}
|
|
@@ -1014,23 +1060,27 @@ function parentOf() {
|
|
|
1014
1060
|
}
|
|
1015
1061
|
return (pid) => table.get(pid) || 0;
|
|
1016
1062
|
}
|
|
1017
|
-
var
|
|
1018
|
-
function
|
|
1019
|
-
const hit = accountCache.get(dir);
|
|
1020
|
-
if (hit !== void 0) return hit;
|
|
1063
|
+
var EMAIL_RE = /^[^@\s]{1,64}@[^@\s]{1,189}$/;
|
|
1064
|
+
function claudeLogin(dir, home = homeDir()) {
|
|
1021
1065
|
const files = [path4.join(dir, ".claude.json")];
|
|
1022
|
-
if (path4.resolve(dir) === path4.join(
|
|
1023
|
-
let acct = null;
|
|
1066
|
+
if (path4.resolve(dir) === path4.join(home, ".claude")) files.unshift(path4.join(home, ".claude.json"));
|
|
1024
1067
|
for (const f of files) {
|
|
1025
1068
|
try {
|
|
1026
1069
|
const a = JSON.parse(readFileSync3(f, "utf8")).oauthAccount;
|
|
1027
|
-
if (a?.accountUuid) {
|
|
1028
|
-
|
|
1029
|
-
|
|
1070
|
+
if (typeof a?.accountUuid === "string" && a.accountUuid) {
|
|
1071
|
+
const email = typeof a.emailAddress === "string" && EMAIL_RE.test(a.emailAddress) ? a.emailAddress : void 0;
|
|
1072
|
+
return { id: a.accountUuid.slice(0, 128), ...email ? { email } : {} };
|
|
1030
1073
|
}
|
|
1031
1074
|
} catch {
|
|
1032
1075
|
}
|
|
1033
1076
|
}
|
|
1077
|
+
return null;
|
|
1078
|
+
}
|
|
1079
|
+
var accountCache = /* @__PURE__ */ new Map();
|
|
1080
|
+
function claudeAccount(dir) {
|
|
1081
|
+
const hit = accountCache.get(dir);
|
|
1082
|
+
if (hit !== void 0) return hit;
|
|
1083
|
+
const acct = claudeLogin(dir)?.id ?? null;
|
|
1034
1084
|
accountCache.set(dir, acct);
|
|
1035
1085
|
return acct;
|
|
1036
1086
|
}
|
|
@@ -1250,18 +1300,13 @@ function parseArgv(raw) {
|
|
|
1250
1300
|
function readNotify(text) {
|
|
1251
1301
|
const { head, tail, at } = split(text);
|
|
1252
1302
|
const m = NOTIFY_LINE.exec(head);
|
|
1253
|
-
if (m)
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
index: m.index,
|
|
1258
|
-
length: m[0].length,
|
|
1259
|
-
misplaced: false,
|
|
1260
|
-
other: !OURS2.test(m[1] ?? "")
|
|
1261
|
-
};
|
|
1303
|
+
if (m) {
|
|
1304
|
+
const raw = m[1];
|
|
1305
|
+
return { raw, argv: parseArgv(raw), index: m.index, length: m[0].length, misplaced: false, other: !OURS2.test(raw) };
|
|
1306
|
+
}
|
|
1262
1307
|
const k = NOTIFY_KEY.exec(head);
|
|
1263
1308
|
if (k) {
|
|
1264
|
-
const rest = head.slice(k.index).split("\n")[0]
|
|
1309
|
+
const rest = head.slice(k.index).split("\n")[0];
|
|
1265
1310
|
return {
|
|
1266
1311
|
raw: rest.replace(NOTIFY_KEY, "").trim(),
|
|
1267
1312
|
argv: null,
|
|
@@ -1272,15 +1317,11 @@ function readNotify(text) {
|
|
|
1272
1317
|
};
|
|
1273
1318
|
}
|
|
1274
1319
|
const t = NOTIFY_LINE.exec(tail);
|
|
1275
|
-
if (t
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
argv: parseArgv(t[
|
|
1279
|
-
|
|
1280
|
-
length: t[0].length,
|
|
1281
|
-
misplaced: true,
|
|
1282
|
-
other: false
|
|
1283
|
-
};
|
|
1320
|
+
if (t) {
|
|
1321
|
+
const raw = t[1];
|
|
1322
|
+
if (OURS2.test(raw))
|
|
1323
|
+
return { raw, argv: parseArgv(raw), index: at + t.index, length: t[0].length, misplaced: true, other: false };
|
|
1324
|
+
}
|
|
1284
1325
|
return null;
|
|
1285
1326
|
}
|
|
1286
1327
|
function dropLine(text, index, length) {
|
|
@@ -1299,9 +1340,7 @@ function withNotify(text, line) {
|
|
|
1299
1340
|
if (n?.misplaced) return insertTopLevel(dropLine(text, n.index, n.length), line);
|
|
1300
1341
|
return insertTopLevel(text, line);
|
|
1301
1342
|
}
|
|
1302
|
-
function replaceNotify(text, line) {
|
|
1303
|
-
const n = readNotify(text);
|
|
1304
|
-
if (!n || n.misplaced || !n.length) return null;
|
|
1343
|
+
function replaceNotify(text, n, line) {
|
|
1305
1344
|
return text.slice(0, n.index) + line + text.slice(n.index + n.length);
|
|
1306
1345
|
}
|
|
1307
1346
|
function withoutNotify(text) {
|
|
@@ -1342,7 +1381,7 @@ var CODEX_EVENTS = [
|
|
|
1342
1381
|
];
|
|
1343
1382
|
var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
1344
1383
|
var eventsFor2 = (lean) => lean ? CODEX_EVENTS.filter((e) => e !== "PreToolUse") : [...CODEX_EVENTS];
|
|
1345
|
-
var codexHome = (env) => env.CODEX_HOME || path5.join(
|
|
1384
|
+
var codexHome = (env) => env.CODEX_HOME || path5.join(homeDir(), ".codex");
|
|
1346
1385
|
var hooksFile2 = (env) => path5.join(codexHome(env), "hooks.json");
|
|
1347
1386
|
var configFile = (env) => path5.join(codexHome(env), "config.toml");
|
|
1348
1387
|
var sessionsDir = (env) => path5.join(codexHome(env), "sessions");
|
|
@@ -1380,11 +1419,8 @@ function installNotify(cmd, env) {
|
|
|
1380
1419
|
return { file, changed: false, note: "notify current (after Codex Computer Use)" };
|
|
1381
1420
|
}
|
|
1382
1421
|
if (!prev || OURS2.test(JSON.stringify(prev))) {
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
write(next);
|
|
1386
|
-
return { file, changed: true, note: "chained after Codex Computer Use (--previous-notify)" };
|
|
1387
|
-
}
|
|
1422
|
+
write(replaceNotify(text, n, `notify = ${withPreviousRaw(n.raw, argv)}`));
|
|
1423
|
+
return { file, changed: true, note: "chained after Codex Computer Use (--previous-notify)" };
|
|
1388
1424
|
}
|
|
1389
1425
|
}
|
|
1390
1426
|
return {
|
|
@@ -1413,13 +1449,13 @@ function uninstallNotify(env) {
|
|
|
1413
1449
|
}
|
|
1414
1450
|
const prev = previousNotify(n.argv);
|
|
1415
1451
|
if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev))) {
|
|
1416
|
-
|
|
1417
|
-
if (next) writeFileSync3(file, next);
|
|
1452
|
+
writeFileSync3(file, replaceNotify(text, n, `notify = ${stripPreviousRaw(n.raw)}`));
|
|
1418
1453
|
return { file, changed: true };
|
|
1419
1454
|
}
|
|
1420
1455
|
return { file, changed: false };
|
|
1421
1456
|
}
|
|
1422
|
-
function findRollout(threadId, env) {
|
|
1457
|
+
function findRollout(threadId, env = process.env) {
|
|
1458
|
+
if (!/^[A-Za-z0-9-]{8,128}$/.test(threadId)) return null;
|
|
1423
1459
|
let best = null;
|
|
1424
1460
|
const walk = (dir, depth) => {
|
|
1425
1461
|
let names = [];
|
|
@@ -1430,7 +1466,7 @@ function findRollout(threadId, env) {
|
|
|
1430
1466
|
}
|
|
1431
1467
|
for (const n of names.sort().reverse()) {
|
|
1432
1468
|
const p = path5.join(dir, n);
|
|
1433
|
-
if (n.endsWith(
|
|
1469
|
+
if (n.endsWith(`-${threadId}.jsonl`)) {
|
|
1434
1470
|
best = p;
|
|
1435
1471
|
return;
|
|
1436
1472
|
}
|
|
@@ -1443,6 +1479,13 @@ function findRollout(threadId, env) {
|
|
|
1443
1479
|
walk(sessionsDir(env), 0);
|
|
1444
1480
|
return best;
|
|
1445
1481
|
}
|
|
1482
|
+
function rolloutMeta(file) {
|
|
1483
|
+
return scanLines(file, (line) => parseLines([line])[0] ?? {});
|
|
1484
|
+
}
|
|
1485
|
+
function ofAnotherThread(file, id) {
|
|
1486
|
+
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);
|
|
1487
|
+
return !!m?.[1] && m[1].toLowerCase() !== id.toLowerCase();
|
|
1488
|
+
}
|
|
1446
1489
|
var codex = {
|
|
1447
1490
|
name: NAME3,
|
|
1448
1491
|
events: CODEX_EVENTS,
|
|
@@ -1485,11 +1528,14 @@ var codex = {
|
|
|
1485
1528
|
if (event === "notify") {
|
|
1486
1529
|
const raw = input.argv.find((a) => a.startsWith("{")) ?? input.stdin;
|
|
1487
1530
|
try {
|
|
1488
|
-
|
|
1531
|
+
const j = raw ? JSON.parse(raw) : {};
|
|
1532
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1489
1533
|
} catch {
|
|
1490
1534
|
}
|
|
1491
1535
|
const id2 = s["thread-id"] ?? s.thread_id ?? s["session-id"] ?? s.session_id;
|
|
1492
1536
|
if (!id2) return null;
|
|
1537
|
+
const file = findRollout(String(id2), input.env);
|
|
1538
|
+
if (!file || codexSubagent(rolloutMeta(file))) return null;
|
|
1493
1539
|
const r2 = {
|
|
1494
1540
|
session: { id: String(id2), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd },
|
|
1495
1541
|
events: [
|
|
@@ -1499,18 +1545,19 @@ var codex = {
|
|
|
1499
1545
|
harnessEvent: "notify"
|
|
1500
1546
|
}
|
|
1501
1547
|
],
|
|
1502
|
-
hints: {}
|
|
1548
|
+
hints: {},
|
|
1549
|
+
transcript: file
|
|
1503
1550
|
};
|
|
1504
|
-
const file = findRollout(String(id2), input.env);
|
|
1505
|
-
if (file) r2.transcript = file;
|
|
1506
1551
|
return r2;
|
|
1507
1552
|
}
|
|
1508
1553
|
try {
|
|
1509
|
-
|
|
1554
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
1555
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1510
1556
|
} catch {
|
|
1511
1557
|
}
|
|
1512
1558
|
const id = s.session_id;
|
|
1513
1559
|
if (!id) return null;
|
|
1560
|
+
if (s.transcript_path === null) return null;
|
|
1514
1561
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
1515
1562
|
if (typeof s.model === "string") session.model = s.model;
|
|
1516
1563
|
const turn_id = typeof s.turn_id === "string" ? s.turn_id : void 0;
|
|
@@ -1582,7 +1629,8 @@ var codex = {
|
|
|
1582
1629
|
return null;
|
|
1583
1630
|
}
|
|
1584
1631
|
const r = { session, events, hints: {} };
|
|
1585
|
-
if (typeof s.transcript_path === "string"
|
|
1632
|
+
if (typeof s.transcript_path === "string" && !ofAnotherThread(s.transcript_path, String(id)))
|
|
1633
|
+
r.transcript = s.transcript_path;
|
|
1586
1634
|
return r;
|
|
1587
1635
|
},
|
|
1588
1636
|
readTranscript(file, fromLine) {
|
|
@@ -1600,16 +1648,17 @@ var codex = {
|
|
|
1600
1648
|
return out;
|
|
1601
1649
|
},
|
|
1602
1650
|
backfill(sinceMs, env = process.env) {
|
|
1603
|
-
const
|
|
1651
|
+
const rows2 = [];
|
|
1604
1652
|
for (const f of recentFiles(sessionsDir(env), 3, sinceMs, (n) => /^rollout-.*\.jsonl$/.test(n))) {
|
|
1605
1653
|
const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(path5.basename(f));
|
|
1606
1654
|
if (!m?.[1]) continue;
|
|
1655
|
+
if (codexSubagent(rolloutMeta(f))) continue;
|
|
1607
1656
|
const facts = codexFacts(f);
|
|
1608
1657
|
if (!facts.started_at || !facts.last_at) continue;
|
|
1609
1658
|
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
1610
|
-
|
|
1659
|
+
rows2.push({ session: { id: m[1], ...rest }, started_at, last_at });
|
|
1611
1660
|
}
|
|
1612
|
-
return
|
|
1661
|
+
return rows2;
|
|
1613
1662
|
}
|
|
1614
1663
|
};
|
|
1615
1664
|
function prune3(o) {
|
|
@@ -1618,10 +1667,11 @@ function prune3(o) {
|
|
|
1618
1667
|
}
|
|
1619
1668
|
function codexFacts(file) {
|
|
1620
1669
|
const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
|
|
1621
|
-
|
|
1622
|
-
|
|
1670
|
+
let H = parseLines(head);
|
|
1671
|
+
let T = parseLines(tail);
|
|
1623
1672
|
const out = {};
|
|
1624
1673
|
const meta = H.find((j) => j.type === "session_meta" && j.payload);
|
|
1674
|
+
const boundary = codexReplayBoundary(meta);
|
|
1625
1675
|
if (meta?.payload) {
|
|
1626
1676
|
const p = meta.payload;
|
|
1627
1677
|
if (p.cwd) out.cwd = p.cwd;
|
|
@@ -1629,19 +1679,26 @@ function codexFacts(file) {
|
|
|
1629
1679
|
if (p.git?.branch) out.branch = p.git.branch;
|
|
1630
1680
|
if (p.git?.repository_url) out.repo = String(p.git.repository_url).replace(/\/\/[^/@]*@/, "//");
|
|
1631
1681
|
}
|
|
1682
|
+
if (boundary) {
|
|
1683
|
+
const h = H.findIndex(boundary);
|
|
1684
|
+
const t = T.findIndex(boundary);
|
|
1685
|
+
H = h < 0 ? [] : H.slice(h);
|
|
1686
|
+
const found = h >= 0 || t >= 0 || scanLines(file, (line) => boundary(parseLines([line])[0] ?? null) ? true : null, Number.POSITIVE_INFINITY);
|
|
1687
|
+
T = t >= 0 ? T.slice(t) : found ? T : [];
|
|
1688
|
+
}
|
|
1632
1689
|
const ctx = H.concat(T).reverse().find((j) => j.type === "turn_context" && j.payload?.model);
|
|
1633
1690
|
if (ctx?.payload?.model) out.model = ctx.payload.model;
|
|
1634
1691
|
for (const j of H) {
|
|
1635
1692
|
const p = j.payload;
|
|
1636
1693
|
if (j.type !== "response_item" || !p || p.type !== "message" || p.role !== "user") continue;
|
|
1637
1694
|
const text = (p.content ?? []).filter((b) => b && b.type === "input_text" && typeof b.text === "string").map((b) => b.text).join("\n").trim();
|
|
1638
|
-
if (!text ||
|
|
1639
|
-
const line = text.split("\n")
|
|
1695
|
+
if (!text || codexInjected(text)) continue;
|
|
1696
|
+
const line = text.split("\n")[0];
|
|
1640
1697
|
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
1641
1698
|
out.title_source = "first-ask";
|
|
1642
1699
|
break;
|
|
1643
1700
|
}
|
|
1644
|
-
const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
|
|
1701
|
+
const times = (boundary && meta ? [meta, ...H, ...T] : H.concat(T)).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
|
|
1645
1702
|
if (times.length) {
|
|
1646
1703
|
out.started_at = Math.min(...times);
|
|
1647
1704
|
out.last_at = Math.max(...times);
|
|
@@ -1649,11 +1706,432 @@ function codexFacts(file) {
|
|
|
1649
1706
|
return out;
|
|
1650
1707
|
}
|
|
1651
1708
|
|
|
1652
|
-
// ../core/src/adapters/
|
|
1653
|
-
import {
|
|
1654
|
-
import { existsSync as
|
|
1709
|
+
// ../core/src/adapters/devin.ts
|
|
1710
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
1711
|
+
import { existsSync as existsSync6, readlinkSync } from "node:fs";
|
|
1655
1712
|
import path6 from "node:path";
|
|
1656
|
-
|
|
1713
|
+
|
|
1714
|
+
// ../core/src/readers/sqlite.ts
|
|
1715
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
1716
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
1717
|
+
import { createRequire } from "node:module";
|
|
1718
|
+
function quiet(fn) {
|
|
1719
|
+
const original = process.emitWarning;
|
|
1720
|
+
try {
|
|
1721
|
+
process.emitWarning = ((warning, ...rest) => {
|
|
1722
|
+
const kind = typeof rest[0] === "string" ? rest[0] : rest[0]?.type;
|
|
1723
|
+
if (kind === "ExperimentalWarning" && /sqlite/i.test(String(warning))) return;
|
|
1724
|
+
original.call(process, warning, ...rest);
|
|
1725
|
+
});
|
|
1726
|
+
return fn();
|
|
1727
|
+
} finally {
|
|
1728
|
+
process.emitWarning = original;
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
var ctorCache;
|
|
1732
|
+
function dbCtor() {
|
|
1733
|
+
if (ctorCache !== void 0) return ctorCache;
|
|
1734
|
+
ctorCache = null;
|
|
1735
|
+
try {
|
|
1736
|
+
const req = createRequire(import.meta.url);
|
|
1737
|
+
const m = quiet(() => req("node:sqlite"));
|
|
1738
|
+
if (typeof m?.DatabaseSync === "function") ctorCache = m.DatabaseSync;
|
|
1739
|
+
} catch {
|
|
1740
|
+
}
|
|
1741
|
+
return ctorCache;
|
|
1742
|
+
}
|
|
1743
|
+
function plain(row) {
|
|
1744
|
+
if (!row || typeof row !== "object") return null;
|
|
1745
|
+
const out = {};
|
|
1746
|
+
for (const [k, v] of Object.entries(row)) {
|
|
1747
|
+
if (typeof v === "bigint") out[k] = Number(v);
|
|
1748
|
+
else if (v instanceof Uint8Array) out[k] = null;
|
|
1749
|
+
else out[k] = v;
|
|
1750
|
+
}
|
|
1751
|
+
return out;
|
|
1752
|
+
}
|
|
1753
|
+
var rows = (v) => {
|
|
1754
|
+
if (!Array.isArray(v)) return null;
|
|
1755
|
+
const out = [];
|
|
1756
|
+
for (const r of v) {
|
|
1757
|
+
const p = plain(r);
|
|
1758
|
+
if (p) out.push(p);
|
|
1759
|
+
}
|
|
1760
|
+
return out;
|
|
1761
|
+
};
|
|
1762
|
+
function viaNode(file, sql, params, o) {
|
|
1763
|
+
const C = dbCtor();
|
|
1764
|
+
if (!C) return null;
|
|
1765
|
+
let db;
|
|
1766
|
+
try {
|
|
1767
|
+
db = quiet(() => new C(file, { readOnly: true, timeout: o.timeoutMs }));
|
|
1768
|
+
} catch {
|
|
1769
|
+
return null;
|
|
1770
|
+
}
|
|
1771
|
+
try {
|
|
1772
|
+
try {
|
|
1773
|
+
db.exec(`PRAGMA busy_timeout = ${Math.round(o.timeoutMs)}`);
|
|
1774
|
+
} catch {
|
|
1775
|
+
}
|
|
1776
|
+
return quiet(() => rows(db.prepare(sql).all(...params)));
|
|
1777
|
+
} catch {
|
|
1778
|
+
return null;
|
|
1779
|
+
} finally {
|
|
1780
|
+
try {
|
|
1781
|
+
db.close();
|
|
1782
|
+
} catch {
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
var CHILD = `const { DatabaseSync } = require("node:sqlite");
|
|
1787
|
+
const [, file, sql, params, ms] = process.argv;
|
|
1788
|
+
const db = new DatabaseSync(file, { readOnly: true, timeout: Number(ms) });
|
|
1789
|
+
try { db.exec("PRAGMA busy_timeout = " + Number(ms)); } catch {}
|
|
1790
|
+
const out = db.prepare(sql).all(...JSON.parse(params));
|
|
1791
|
+
db.close();
|
|
1792
|
+
process.stdout.write(JSON.stringify(out));`;
|
|
1793
|
+
function viaChild(file, sql, params, o) {
|
|
1794
|
+
try {
|
|
1795
|
+
const out = execFileSync3(
|
|
1796
|
+
process.execPath,
|
|
1797
|
+
["--experimental-sqlite", "--no-warnings", "-e", CHILD, file, sql, JSON.stringify(params), String(o.timeoutMs)],
|
|
1798
|
+
{
|
|
1799
|
+
encoding: "utf8",
|
|
1800
|
+
timeout: o.timeoutMs + 3e3,
|
|
1801
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1802
|
+
maxBuffer: o.maxBuffer,
|
|
1803
|
+
windowsHide: true
|
|
1804
|
+
}
|
|
1805
|
+
);
|
|
1806
|
+
return rows(JSON.parse(out || "null"));
|
|
1807
|
+
} catch {
|
|
1808
|
+
return null;
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
var SAFE = /^[A-Za-z0-9 ._:@/+-]{0,256}$/;
|
|
1812
|
+
function inlineParams(sql, params) {
|
|
1813
|
+
if (sql.includes("'")) return null;
|
|
1814
|
+
const holes = sql.split("?").length - 1;
|
|
1815
|
+
if (holes !== params.length) return null;
|
|
1816
|
+
let i = 0;
|
|
1817
|
+
let bad = false;
|
|
1818
|
+
const out = sql.replace(/\?/g, () => {
|
|
1819
|
+
const p = params[i++];
|
|
1820
|
+
if (typeof p === "number") {
|
|
1821
|
+
if (!Number.isFinite(p)) {
|
|
1822
|
+
bad = true;
|
|
1823
|
+
return "NULL";
|
|
1824
|
+
}
|
|
1825
|
+
return String(p);
|
|
1826
|
+
}
|
|
1827
|
+
if (typeof p !== "string" || !SAFE.test(p)) {
|
|
1828
|
+
bad = true;
|
|
1829
|
+
return "NULL";
|
|
1830
|
+
}
|
|
1831
|
+
return `'${p}'`;
|
|
1832
|
+
});
|
|
1833
|
+
return bad ? null : out;
|
|
1834
|
+
}
|
|
1835
|
+
function viaCli(file, sql, params, o) {
|
|
1836
|
+
const text = inlineParams(sql, params);
|
|
1837
|
+
if (!text) return null;
|
|
1838
|
+
try {
|
|
1839
|
+
const out = execFileSync3("sqlite3", ["-readonly", "-json", file, text], {
|
|
1840
|
+
encoding: "utf8",
|
|
1841
|
+
timeout: o.timeoutMs + 3e3,
|
|
1842
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1843
|
+
maxBuffer: o.maxBuffer,
|
|
1844
|
+
windowsHide: true
|
|
1845
|
+
}).trim();
|
|
1846
|
+
if (!out) return [];
|
|
1847
|
+
return rows(JSON.parse(out));
|
|
1848
|
+
} catch {
|
|
1849
|
+
return null;
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
function querySqlite(file, sql, params = [], opts = {}) {
|
|
1853
|
+
const o = {
|
|
1854
|
+
timeoutMs: opts.timeoutMs ?? 2e3,
|
|
1855
|
+
engine: opts.engine ?? "auto",
|
|
1856
|
+
maxBuffer: opts.maxBuffer ?? 32 * 1024 * 1024
|
|
1857
|
+
};
|
|
1858
|
+
if (!file) return null;
|
|
1859
|
+
try {
|
|
1860
|
+
if (!existsSync5(file)) return null;
|
|
1861
|
+
} catch {
|
|
1862
|
+
return null;
|
|
1863
|
+
}
|
|
1864
|
+
const ways = o.engine === "auto" ? [viaNode, viaChild, viaCli] : o.engine === "node" ? [viaNode] : o.engine === "child" ? [viaChild] : [viaCli];
|
|
1865
|
+
for (const way of ways) {
|
|
1866
|
+
const r = way(file, sql, params, o);
|
|
1867
|
+
if (r) return r;
|
|
1868
|
+
}
|
|
1869
|
+
return null;
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
// ../core/src/adapters/devin.ts
|
|
1873
|
+
var NAME4 = "devin";
|
|
1874
|
+
var DEVIN_EVENTS = [
|
|
1875
|
+
"SessionStart",
|
|
1876
|
+
"UserPromptSubmit",
|
|
1877
|
+
"PreToolUse",
|
|
1878
|
+
"PostToolUse",
|
|
1879
|
+
"PermissionRequest",
|
|
1880
|
+
"Stop",
|
|
1881
|
+
"PostCompaction",
|
|
1882
|
+
"SessionEnd"
|
|
1883
|
+
];
|
|
1884
|
+
var OPTS3 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
1885
|
+
var eventsFor3 = (lean) => lean ? DEVIN_EVENTS.filter((e) => e !== "PreToolUse") : [...DEVIN_EVENTS];
|
|
1886
|
+
var INSTALL_NOTE = "Devin drops a whole hooks file on one event name it doesn't know; `/hooks` in `devin` lists what loaded";
|
|
1887
|
+
var CLAUDE_NOTE = "Devin also reads ~/.claude/settings.json hooks (read_config_from.claude); a Claude-only event name there makes it ignore that file whole";
|
|
1888
|
+
var deadFileNote = (bad) => `Devin loads NO hooks from this file: "${bad}" is not one of its event names and it rejects the file whole \u2014 remove that key (\`/hooks\` in \`devin\` lists what loaded)`;
|
|
1889
|
+
function foreignEvent(file) {
|
|
1890
|
+
const hooks = readJson(file).hooks;
|
|
1891
|
+
if (!hooks || typeof hooks !== "object" || Array.isArray(hooks)) return void 0;
|
|
1892
|
+
const ok = DEVIN_EVENTS;
|
|
1893
|
+
return Object.keys(hooks).find((k) => !ok.includes(k));
|
|
1894
|
+
}
|
|
1895
|
+
var homeOf = (env) => env.HOME || homeDir();
|
|
1896
|
+
var appData = (env) => env.APPDATA || path6.join(homeOf(env), "AppData", "Roaming");
|
|
1897
|
+
var devinConfigDir = (env = process.env) => process.platform === "win32" ? path6.join(appData(env), "devin") : path6.join(env.XDG_CONFIG_HOME || path6.join(homeOf(env), ".config"), "devin");
|
|
1898
|
+
var configFile2 = (env) => path6.join(devinConfigDir(env), "config.json");
|
|
1899
|
+
var devinDataDir = (env = process.env) => process.platform === "win32" ? path6.join(appData(env), "devin", "cli") : path6.join(env.XDG_DATA_HOME || path6.join(homeOf(env), ".local", "share"), "devin", "cli");
|
|
1900
|
+
var sessionsDb = (env) => path6.join(devinDataDir(env), "sessions.db");
|
|
1901
|
+
var SESSION_ID = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
1902
|
+
var validTool4 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
1903
|
+
var endReason3 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
|
|
1904
|
+
var pickSource2 = (v) => typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
|
|
1905
|
+
function errText2(v) {
|
|
1906
|
+
if (typeof v === "string" && v) return v.slice(0, 2e3);
|
|
1907
|
+
if (v && typeof v === "object") {
|
|
1908
|
+
const e = v;
|
|
1909
|
+
if (typeof e.message === "string" && e.message) return e.message.slice(0, 2e3);
|
|
1910
|
+
if (typeof e.error === "string" && e.error) return e.error.slice(0, 2e3);
|
|
1911
|
+
}
|
|
1912
|
+
return void 0;
|
|
1913
|
+
}
|
|
1914
|
+
var cut = (s, n) => s.length > n ? `${s.slice(0, n).replace(/\s+\S*$/, "")}\u2026` : s;
|
|
1915
|
+
var devin = {
|
|
1916
|
+
name: NAME4,
|
|
1917
|
+
events: DEVIN_EVENTS,
|
|
1918
|
+
detect: (env = process.env) => existsSync6(devinConfigDir(env)) || existsSync6(devinDataDir(env)),
|
|
1919
|
+
configFiles: (env = process.env) => [configFile2(env)],
|
|
1920
|
+
install: (cmd, env = process.env, opts = {}) => {
|
|
1921
|
+
const file = configFile2(env);
|
|
1922
|
+
const r = installClaudeShaped(file, NAME4, eventsFor3(opts.lean), cmd, OPTS3);
|
|
1923
|
+
if (r.skipped) return [r];
|
|
1924
|
+
const bad = foreignEvent(file);
|
|
1925
|
+
return [{ ...r, note: bad ? deadFileNote(bad) : INSTALL_NOTE }];
|
|
1926
|
+
},
|
|
1927
|
+
uninstall: (env = process.env, opts = {}) => [
|
|
1928
|
+
uninstallClaudeShaped(configFile2(env), DEVIN_EVENTS, { created: opts.created?.includes(configFile2(env)) })
|
|
1929
|
+
],
|
|
1930
|
+
installed: (cmd, env = process.env, opts = {}) => {
|
|
1931
|
+
const file = configFile2(env);
|
|
1932
|
+
const state = installedClaudeShaped(file, NAME4, eventsFor3(opts.lean), cmd, OPTS3);
|
|
1933
|
+
const bad = state === "missing" ? void 0 : foreignEvent(file);
|
|
1934
|
+
return [{ file, state: bad ? "inactive" : state, note: bad ? deadFileNote(bad) : CLAUDE_NOTE }];
|
|
1935
|
+
},
|
|
1936
|
+
fromHook(input) {
|
|
1937
|
+
const event = input.argv[1] ?? "";
|
|
1938
|
+
let s = {};
|
|
1939
|
+
try {
|
|
1940
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
1941
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1942
|
+
} catch {
|
|
1943
|
+
}
|
|
1944
|
+
const id = typeof s.session_id === "string" ? s.session_id : "";
|
|
1945
|
+
if (!id) return null;
|
|
1946
|
+
const session = { id, cwd: input.env.DEVIN_PROJECT_DIR ?? input.cwd };
|
|
1947
|
+
const turn_id = typeof s.prompt_id === "string" ? s.prompt_id : void 0;
|
|
1948
|
+
const tool = validTool4(s.tool_name) ? s.tool_name : void 0;
|
|
1949
|
+
const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
|
|
1950
|
+
const response = s.tool_response && typeof s.tool_response === "object" && !Array.isArray(s.tool_response) ? s.tool_response : void 0;
|
|
1951
|
+
const permissionId = () => `perm-${createHash2("sha256").update(`${turn_id ?? ""}|${tool ?? ""}|${JSON.stringify(s.tool_input ?? null)}`).digest("hex").slice(0, 16)}`;
|
|
1952
|
+
const ev = (type, data) => ({ type, data: prune4(data), harnessEvent: event });
|
|
1953
|
+
const events = [];
|
|
1954
|
+
switch (event) {
|
|
1955
|
+
case "SessionStart":
|
|
1956
|
+
events.push(ev("session.started", { source: pickSource2(s.source) }));
|
|
1957
|
+
break;
|
|
1958
|
+
case "UserPromptSubmit":
|
|
1959
|
+
events.push(
|
|
1960
|
+
ev("turn.started", {
|
|
1961
|
+
turn_id,
|
|
1962
|
+
prompt_chars: typeof s.prompt === "string" ? [...s.prompt].length : void 0
|
|
1963
|
+
})
|
|
1964
|
+
);
|
|
1965
|
+
break;
|
|
1966
|
+
case "PreToolUse":
|
|
1967
|
+
if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, input: s.tool_input }));
|
|
1968
|
+
break;
|
|
1969
|
+
case "PostToolUse":
|
|
1970
|
+
if (tool)
|
|
1971
|
+
events.push(
|
|
1972
|
+
ev("tool.ended", {
|
|
1973
|
+
tool,
|
|
1974
|
+
call_id,
|
|
1975
|
+
turn_id,
|
|
1976
|
+
// `ok` is required on tool.ended (PROTOCOL.md §events), so a response
|
|
1977
|
+
// that carries no success flag at all reads as "nothing said it failed".
|
|
1978
|
+
ok: response?.success !== false,
|
|
1979
|
+
error: errText2(response?.error),
|
|
1980
|
+
input: s.tool_input,
|
|
1981
|
+
output: response ? response.output : s.tool_response
|
|
1982
|
+
})
|
|
1983
|
+
);
|
|
1984
|
+
break;
|
|
1985
|
+
case "PermissionRequest":
|
|
1986
|
+
events.push(ev("attention.needed", { attention_id: call_id ?? permissionId(), kind: "permission", tool }));
|
|
1987
|
+
break;
|
|
1988
|
+
case "Stop":
|
|
1989
|
+
events.push(ev("turn.ended", { turn_id, reason: "stop" }));
|
|
1990
|
+
break;
|
|
1991
|
+
case "PostCompaction":
|
|
1992
|
+
events.push(ev("context.compacted", { trigger: "auto" }));
|
|
1993
|
+
break;
|
|
1994
|
+
case "SessionEnd":
|
|
1995
|
+
events.push(ev("session.ended", { reason: endReason3(s.reason) }));
|
|
1996
|
+
break;
|
|
1997
|
+
default:
|
|
1998
|
+
return null;
|
|
1999
|
+
}
|
|
2000
|
+
return {
|
|
2001
|
+
session,
|
|
2002
|
+
events,
|
|
2003
|
+
hints: {},
|
|
2004
|
+
transcript: transcriptRef(sessionsDb(input.env), id)
|
|
2005
|
+
};
|
|
2006
|
+
},
|
|
2007
|
+
readTranscript(file, fromLine) {
|
|
2008
|
+
const ref = splitRef(file);
|
|
2009
|
+
if (!ref) return { turns: [], line: fromLine };
|
|
2010
|
+
return readDevinChain(ref.db, ref.session, fromLine);
|
|
2011
|
+
},
|
|
2012
|
+
facts(session, transcript, _hints, _state, env = process.env) {
|
|
2013
|
+
const out = {};
|
|
2014
|
+
const version = devinVersion(env);
|
|
2015
|
+
if (version) out.harness_version = version;
|
|
2016
|
+
const account = devinAccount(env);
|
|
2017
|
+
if (account) out.account_id = account;
|
|
2018
|
+
const ref = splitRef(transcript) ?? { db: sessionsDb(env), session: session.id };
|
|
2019
|
+
if (!SESSION_ID.test(ref.session)) return out;
|
|
2020
|
+
const rows2 = querySqlite(ref.db, SESSION_SQL, [ref.session]);
|
|
2021
|
+
const row = rows2?.[0];
|
|
2022
|
+
if (!row) return out;
|
|
2023
|
+
Object.assign(out, sessionFacts(row));
|
|
2024
|
+
return out;
|
|
2025
|
+
},
|
|
2026
|
+
backfill(sinceMs, env = process.env) {
|
|
2027
|
+
const rows2 = querySqlite(sessionsDb(env), BACKFILL_SQL, ["text", Math.floor(sinceMs / 1e3)]);
|
|
2028
|
+
if (!rows2) return [];
|
|
2029
|
+
const out = [];
|
|
2030
|
+
for (const row of rows2) {
|
|
2031
|
+
const id = String(row.id);
|
|
2032
|
+
if (!SESSION_ID.test(id)) continue;
|
|
2033
|
+
const { started_at, last_at, ...rest } = sessionFacts(row);
|
|
2034
|
+
if (!started_at || !last_at) continue;
|
|
2035
|
+
out.push({ session: { id, ...rest }, started_at, last_at });
|
|
2036
|
+
}
|
|
2037
|
+
return out;
|
|
2038
|
+
}
|
|
2039
|
+
};
|
|
2040
|
+
function prune4(o) {
|
|
2041
|
+
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
2042
|
+
return o;
|
|
2043
|
+
}
|
|
2044
|
+
var transcriptRef = (db, session) => `${db}#${session}`;
|
|
2045
|
+
function splitRef(ref) {
|
|
2046
|
+
if (!ref) return null;
|
|
2047
|
+
const at = ref.lastIndexOf("#");
|
|
2048
|
+
if (at <= 0) return null;
|
|
2049
|
+
const db = ref.slice(0, at);
|
|
2050
|
+
const session = ref.slice(at + 1);
|
|
2051
|
+
return db && SESSION_ID.test(session) ? { db, session } : null;
|
|
2052
|
+
}
|
|
2053
|
+
var CHAIN_LIMIT = 4e3;
|
|
2054
|
+
var CHAIN_SQL = `WITH RECURSIVE chain(node_id, parent_node_id, chat_message, created_at) AS (
|
|
2055
|
+
SELECT node_id, parent_node_id, chat_message, created_at FROM message_nodes
|
|
2056
|
+
WHERE session_id = ? AND node_id = (SELECT main_chain_id FROM sessions WHERE id = ?)
|
|
2057
|
+
UNION ALL
|
|
2058
|
+
SELECT m.node_id, m.parent_node_id, m.chat_message, m.created_at FROM message_nodes m
|
|
2059
|
+
JOIN chain c ON m.node_id = c.parent_node_id AND m.session_id = ? AND m.node_id >= ?
|
|
2060
|
+
)
|
|
2061
|
+
SELECT node_id, chat_message, created_at FROM chain ORDER BY node_id DESC LIMIT ${CHAIN_LIMIT}`;
|
|
2062
|
+
var SESSION_FIELDS = `s.model, s.working_directory, s.created_at, s.last_activity_at,
|
|
2063
|
+
(SELECT p.content FROM prompt_history p WHERE p.session_id = s.id AND p.is_shell = 0
|
|
2064
|
+
ORDER BY p.timestamp ASC, p.id ASC LIMIT 1) AS first_ask`;
|
|
2065
|
+
var SESSION_SQL = `SELECT ${SESSION_FIELDS} FROM sessions s WHERE s.id = ? LIMIT 1`;
|
|
2066
|
+
var BACKFILL_SQL = `SELECT s.id, ${SESSION_FIELDS} FROM sessions s
|
|
2067
|
+
WHERE s.hidden = 0 AND typeof(s.id) = ? AND s.last_activity_at >= ?
|
|
2068
|
+
ORDER BY s.last_activity_at DESC LIMIT 500`;
|
|
2069
|
+
function sessionFacts(row) {
|
|
2070
|
+
const out = {};
|
|
2071
|
+
if (typeof row.model === "string" && row.model) out.model = row.model;
|
|
2072
|
+
if (typeof row.working_directory === "string" && path6.isAbsolute(row.working_directory))
|
|
2073
|
+
out.cwd = row.working_directory;
|
|
2074
|
+
const started = Number(row.created_at);
|
|
2075
|
+
const last = Number(row.last_activity_at);
|
|
2076
|
+
if (Number.isFinite(started) && started > 0) out.started_at = Math.round(started * 1e3);
|
|
2077
|
+
if (Number.isFinite(last) && last > 0) out.last_at = Math.round(last * 1e3);
|
|
2078
|
+
if (typeof row.first_ask === "string") {
|
|
2079
|
+
const line = row.first_ask.split("\n").find((l) => l.trim()) ?? "";
|
|
2080
|
+
if (line.trim()) {
|
|
2081
|
+
out.title = cut(line.trim(), 80);
|
|
2082
|
+
out.title_source = "first-ask";
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
return out;
|
|
2086
|
+
}
|
|
2087
|
+
function readDevinChain(db, session, fromLine) {
|
|
2088
|
+
if (!SESSION_ID.test(session)) return { turns: [], line: fromLine };
|
|
2089
|
+
const floor = Number.isFinite(fromLine) && fromLine > 0 ? Math.floor(fromLine) : 0;
|
|
2090
|
+
const rows2 = querySqlite(db, CHAIN_SQL, [session, session, session, floor]);
|
|
2091
|
+
if (!rows2?.length) return { turns: [], line: fromLine };
|
|
2092
|
+
const msgs = [];
|
|
2093
|
+
let end = fromLine;
|
|
2094
|
+
for (const row of rows2) {
|
|
2095
|
+
const node = Number(row.node_id);
|
|
2096
|
+
if (!Number.isFinite(node)) continue;
|
|
2097
|
+
if (node + 1 > end) end = node + 1;
|
|
2098
|
+
if (node < fromLine) continue;
|
|
2099
|
+
let j;
|
|
2100
|
+
try {
|
|
2101
|
+
j = JSON.parse(String(row.chat_message ?? ""));
|
|
2102
|
+
} catch {
|
|
2103
|
+
continue;
|
|
2104
|
+
}
|
|
2105
|
+
const text = typeof j.content === "string" ? j.content.trim() : "";
|
|
2106
|
+
if (!text) continue;
|
|
2107
|
+
const secs = Number(row.created_at);
|
|
2108
|
+
const at = Date.parse(String(j.metadata?.created_at ?? "")) || (Number.isFinite(secs) && secs > 0 ? Math.round(secs * 1e3) : 0) || Date.now();
|
|
2109
|
+
if (j.role === "user" && j.metadata?.is_user_input === true) msgs.push({ role: "user", text, at, line: node });
|
|
2110
|
+
else if (j.role === "assistant") msgs.push({ role: "assistant", text, at, line: node });
|
|
2111
|
+
}
|
|
2112
|
+
msgs.sort((a, b) => a.line - b.line);
|
|
2113
|
+
return pairTurns(msgs, end);
|
|
2114
|
+
}
|
|
2115
|
+
function devinVersion(env = process.env) {
|
|
2116
|
+
try {
|
|
2117
|
+
const target = readlinkSync(path6.join(devinDataDir(env), "_versions", "current"));
|
|
2118
|
+
const name = path6.basename(target);
|
|
2119
|
+
return /^[0-9][0-9A-Za-z.+-]{0,63}$/.test(name) ? name : void 0;
|
|
2120
|
+
} catch {
|
|
2121
|
+
return void 0;
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
function devinAccount(env = process.env) {
|
|
2125
|
+
const d = readJson(configFile2(env)).devin;
|
|
2126
|
+
const id = d && typeof d === "object" ? d.org_id : void 0;
|
|
2127
|
+
return typeof id === "string" && /^[A-Za-z0-9_.:-]{1,128}$/.test(id) ? id : void 0;
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
// ../core/src/adapters/gemini-cli.ts
|
|
2131
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
2132
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
|
|
2133
|
+
import path7 from "node:path";
|
|
2134
|
+
var NAME5 = "gemini-cli";
|
|
1657
2135
|
var GEMINI_EVENTS = [
|
|
1658
2136
|
"SessionStart",
|
|
1659
2137
|
"BeforeAgent",
|
|
@@ -1664,11 +2142,11 @@ var GEMINI_EVENTS = [
|
|
|
1664
2142
|
"PreCompress",
|
|
1665
2143
|
"SessionEnd"
|
|
1666
2144
|
];
|
|
1667
|
-
var
|
|
1668
|
-
var geminiDir = (env) => env.GEMINI_CLI_HOME ||
|
|
1669
|
-
var settings = (env) =>
|
|
1670
|
-
var
|
|
1671
|
-
var
|
|
2145
|
+
var OPTS4 = { matcherFor: (e) => /Tool/.test(e) ? ".*" : void 0, timeout: 5e3 };
|
|
2146
|
+
var geminiDir = (env) => env.GEMINI_CLI_HOME || path7.join(homeDir(), ".gemini");
|
|
2147
|
+
var settings = (env) => path7.join(geminiDir(env), "settings.json");
|
|
2148
|
+
var validTool5 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
2149
|
+
var eventsFor4 = (lean) => lean ? GEMINI_EVENTS.filter((e) => e !== "BeforeTool") : [...GEMINI_EVENTS];
|
|
1672
2150
|
function geminiHooksActive(settingsFile) {
|
|
1673
2151
|
let tools;
|
|
1674
2152
|
try {
|
|
@@ -1680,7 +2158,7 @@ function geminiHooksActive(settingsFile) {
|
|
|
1680
2158
|
return { active: false, note: "inactive: tools.enableHooks is false in settings.json" };
|
|
1681
2159
|
let version = "";
|
|
1682
2160
|
try {
|
|
1683
|
-
version =
|
|
2161
|
+
version = execFileSync4("gemini", ["--version"], {
|
|
1684
2162
|
encoding: "utf8",
|
|
1685
2163
|
timeout: 4e3,
|
|
1686
2164
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -1699,13 +2177,13 @@ function geminiHooksActive(settingsFile) {
|
|
|
1699
2177
|
};
|
|
1700
2178
|
}
|
|
1701
2179
|
var geminiCli = {
|
|
1702
|
-
name:
|
|
2180
|
+
name: NAME5,
|
|
1703
2181
|
events: GEMINI_EVENTS,
|
|
1704
2182
|
// ~/.gemini also belongs to Antigravity; Gemini CLI is here when its settings or its chat logs exist.
|
|
1705
|
-
detect: (env = process.env) =>
|
|
2183
|
+
detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(path7.join(geminiDir(env), "tmp")),
|
|
1706
2184
|
configFiles: (env = process.env) => [settings(env)],
|
|
1707
2185
|
install: (cmd, env = process.env, opts = {}) => {
|
|
1708
|
-
const r = installClaudeShaped(settings(env),
|
|
2186
|
+
const r = installClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
|
|
1709
2187
|
const a = geminiHooksActive(settings(env));
|
|
1710
2188
|
return [a.note && !r.skipped ? { ...r, note: [r.note, a.note].filter(Boolean).join("; ") } : r];
|
|
1711
2189
|
},
|
|
@@ -1713,7 +2191,7 @@ var geminiCli = {
|
|
|
1713
2191
|
uninstallClaudeShaped(settings(env), GEMINI_EVENTS, { created: opts.created?.includes(settings(env)) })
|
|
1714
2192
|
],
|
|
1715
2193
|
installed: (cmd, env = process.env, opts = {}) => {
|
|
1716
|
-
const state = installedClaudeShaped(settings(env),
|
|
2194
|
+
const state = installedClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
|
|
1717
2195
|
const a = geminiHooksActive(settings(env));
|
|
1718
2196
|
return [
|
|
1719
2197
|
{
|
|
@@ -1727,14 +2205,15 @@ var geminiCli = {
|
|
|
1727
2205
|
const event = input.argv[1] ?? "";
|
|
1728
2206
|
let s = {};
|
|
1729
2207
|
try {
|
|
1730
|
-
|
|
2208
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
2209
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1731
2210
|
} catch {
|
|
1732
2211
|
}
|
|
1733
2212
|
const id = s.session_id;
|
|
1734
2213
|
if (!id) return null;
|
|
1735
2214
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
1736
|
-
const tool =
|
|
1737
|
-
const ev = (type, data) => ({ type, data:
|
|
2215
|
+
const tool = validTool5(s.tool_name) ? s.tool_name : void 0;
|
|
2216
|
+
const ev = (type, data) => ({ type, data: prune5(data), harnessEvent: event });
|
|
1738
2217
|
const events = [];
|
|
1739
2218
|
switch (event) {
|
|
1740
2219
|
case "SessionStart":
|
|
@@ -1815,7 +2294,7 @@ var geminiCli = {
|
|
|
1815
2294
|
const raw = first ? first.content ?? first.message ?? first.text : void 0;
|
|
1816
2295
|
const text = typeof raw === "string" ? raw.trim() : "";
|
|
1817
2296
|
if (text) {
|
|
1818
|
-
const line = text.split("\n")
|
|
2297
|
+
const line = text.split("\n")[0];
|
|
1819
2298
|
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
1820
2299
|
out.title_source = "first-ask";
|
|
1821
2300
|
}
|
|
@@ -1829,13 +2308,13 @@ var geminiCli = {
|
|
|
1829
2308
|
backfill: () => []
|
|
1830
2309
|
// chat logs are keyed by a project hash, not a session id we can name; the first hook of a resumed session reports it
|
|
1831
2310
|
};
|
|
1832
|
-
function
|
|
2311
|
+
function prune5(o) {
|
|
1833
2312
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
1834
2313
|
return o;
|
|
1835
2314
|
}
|
|
1836
2315
|
|
|
1837
2316
|
// ../core/src/adapters/index.ts
|
|
1838
|
-
var ADAPTERS = [claudeCode, codex, geminiCli, antigravity];
|
|
2317
|
+
var ADAPTERS = [claudeCode, codex, geminiCli, antigravity, devin];
|
|
1839
2318
|
var adapterByName = (name) => ADAPTERS.find((a) => a.name === name);
|
|
1840
2319
|
|
|
1841
2320
|
// src/index.ts
|
|
@@ -1875,7 +2354,7 @@ async function runAdapterFixtures(files) {
|
|
|
1875
2354
|
cwd: fx.input.cwd ?? "/"
|
|
1876
2355
|
});
|
|
1877
2356
|
if (!r) {
|
|
1878
|
-
results.push({ file, ok: false, why: "fromHook returned null" });
|
|
2357
|
+
results.push(fx.expect.length ? { file, ok: false, why: "fromHook returned null" } : { file, ok: true });
|
|
1879
2358
|
continue;
|
|
1880
2359
|
}
|
|
1881
2360
|
let why = matches(
|
|
@@ -1886,7 +2365,7 @@ async function runAdapterFixtures(files) {
|
|
|
1886
2365
|
if (!why)
|
|
1887
2366
|
for (const [i, e] of fx.expect.entries()) {
|
|
1888
2367
|
const got = r.events[i];
|
|
1889
|
-
why = matches(e.harness.event, got
|
|
2368
|
+
why = matches(e.harness.event, got.harnessEvent, `[${i}].harness.event`) ?? matches(e.data, got.data, `[${i}].data`) ?? matches({ ...e.session, seq: "*" }, r.session, `[${i}].session`);
|
|
1890
2369
|
if (why) break;
|
|
1891
2370
|
}
|
|
1892
2371
|
results.push(why ? { file, ok: false, why } : { file, ok: true });
|