sessionpipe-conformance 0.7.0 → 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 +647 -158
- package/dist/cli.js.map +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +580 -105
- 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,6 +196,9 @@ 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));
|
|
@@ -175,7 +208,10 @@ function codexInjected(text) {
|
|
|
175
208
|
}
|
|
176
209
|
function readCodex(lines, from) {
|
|
177
210
|
const msgs = [];
|
|
178
|
-
|
|
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++) {
|
|
179
215
|
const j = lines[i] ? parse(lines[i]) : null;
|
|
180
216
|
if (!j || j.type !== "response_item") continue;
|
|
181
217
|
const p = j.payload;
|
|
@@ -213,7 +249,7 @@ function readAntigravity(lines, from) {
|
|
|
213
249
|
const at = Date.parse(String(j.created_at ?? j.timestamp ?? "")) || Date.now();
|
|
214
250
|
if (j.type === "USER_INPUT") {
|
|
215
251
|
const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
|
|
216
|
-
const text = (m ? m[1]
|
|
252
|
+
const text = (m ? m[1] : j.content).trim();
|
|
217
253
|
if (text) msgs.push({ role: "user", text, at, line: i });
|
|
218
254
|
} else if (j.type === "PLANNER_RESPONSE") {
|
|
219
255
|
const text = j.content.trim();
|
|
@@ -261,6 +297,10 @@ function formatJson(value, f) {
|
|
|
261
297
|
const text = JSON.stringify(value, null, f.indent).replace(/\n/g, f.eol);
|
|
262
298
|
return f.finalNewline ? text + f.eol : text;
|
|
263
299
|
}
|
|
300
|
+
function readJson(file) {
|
|
301
|
+
const f = readJsonFile(file);
|
|
302
|
+
return f.ok ? f.value : {};
|
|
303
|
+
}
|
|
264
304
|
function entryFor(event, harness, cmd, o) {
|
|
265
305
|
const matcher = o.matcherFor(event);
|
|
266
306
|
return {
|
|
@@ -330,8 +370,7 @@ function uninstallClaudeShaped(file, events, opts = {}) {
|
|
|
330
370
|
unlinkSync(file);
|
|
331
371
|
return { file, changed: true, note: "removed (sessionpipe created it)" };
|
|
332
372
|
}
|
|
333
|
-
|
|
334
|
-
if (next !== f.raw) writeFileSync(file, next);
|
|
373
|
+
writeFileSync(file, formatJson(settings2, f));
|
|
335
374
|
return { file, changed: true };
|
|
336
375
|
}
|
|
337
376
|
function installedClaudeShaped(file, harness, events, cmd, o) {
|
|
@@ -358,7 +397,7 @@ var HOOK_NAME = "sessionpipe";
|
|
|
358
397
|
var AG_EVENTS = ["PreInvocation", "PostInvocation", "PostToolUse", "Stop"];
|
|
359
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';
|
|
360
399
|
var antigravityAnswer = (event) => event === "PreToolUse" ? AG_STALE_PRETOOL : "{}\n";
|
|
361
|
-
var gemini = (env) => env.GEMINI_CLI_HOME || path3.join(
|
|
400
|
+
var gemini = (env) => env.GEMINI_CLI_HOME || path3.join(homeDir(), ".gemini");
|
|
362
401
|
var hooksFile = (env) => path3.join(gemini(env), "config", "hooks.json");
|
|
363
402
|
var projectsDir = (env) => path3.join(gemini(env), "config", "projects");
|
|
364
403
|
var dataDirs = (env) => ["antigravity", "antigravity-cli", "antigravity-ide"].map((d) => path3.join(gemini(env), d));
|
|
@@ -424,7 +463,8 @@ var antigravity = {
|
|
|
424
463
|
const event = input.argv[1] ?? "";
|
|
425
464
|
let s = {};
|
|
426
465
|
try {
|
|
427
|
-
|
|
466
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
467
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
428
468
|
} catch {
|
|
429
469
|
}
|
|
430
470
|
const id = s.conversationId ?? s.session_id ?? s.sessionId;
|
|
@@ -480,19 +520,16 @@ var antigravity = {
|
|
|
480
520
|
}
|
|
481
521
|
return readAntigravity(text.split("\n"), fromLine);
|
|
482
522
|
},
|
|
483
|
-
facts(session, transcript, hints
|
|
523
|
+
facts(session, transcript, hints) {
|
|
484
524
|
if (!transcript) return {};
|
|
485
525
|
return antigravityFacts(transcript, {
|
|
486
526
|
conversation: session.id,
|
|
487
527
|
workspaces: hints.workspaces ?? (session.cwd ? [session.cwd] : []),
|
|
488
|
-
state,
|
|
489
528
|
env: process.env
|
|
490
529
|
});
|
|
491
530
|
},
|
|
492
531
|
backfill(sinceMs, env = process.env) {
|
|
493
|
-
const
|
|
494
|
-
const noState = { get: () => ({}), save: () => {
|
|
495
|
-
} };
|
|
532
|
+
const rows2 = [];
|
|
496
533
|
for (const data of dataDirs(env)) {
|
|
497
534
|
let ids = [];
|
|
498
535
|
try {
|
|
@@ -506,13 +543,13 @@ var antigravity = {
|
|
|
506
543
|
} catch {
|
|
507
544
|
continue;
|
|
508
545
|
}
|
|
509
|
-
const facts = antigravityFacts(f, { conversation: id, workspaces: [],
|
|
546
|
+
const facts = antigravityFacts(f, { conversation: id, workspaces: [], env });
|
|
510
547
|
if (!facts.started_at || !facts.last_at) continue;
|
|
511
548
|
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
512
|
-
|
|
549
|
+
rows2.push({ session: { id, ...rest }, started_at, last_at });
|
|
513
550
|
}
|
|
514
551
|
}
|
|
515
|
-
return
|
|
552
|
+
return rows2;
|
|
516
553
|
}
|
|
517
554
|
};
|
|
518
555
|
function prune(o) {
|
|
@@ -562,7 +599,7 @@ function agProjects(dir) {
|
|
|
562
599
|
}
|
|
563
600
|
const folders = [];
|
|
564
601
|
for (const r of j?.projectResources?.resources ?? []) {
|
|
565
|
-
const f = agPath(r?.folderUri);
|
|
602
|
+
const f = agPath(r?.folderUri ?? r?.gitFolder?.folderUri);
|
|
566
603
|
if (f) folders.push(...agVariants(f));
|
|
567
604
|
}
|
|
568
605
|
if (folders.length) out.push({ id, folders });
|
|
@@ -616,7 +653,7 @@ function antigravityFacts(file, opts) {
|
|
|
616
653
|
for (const j of H) {
|
|
617
654
|
if (j.type !== "USER_INPUT" || typeof j.content !== "string") continue;
|
|
618
655
|
const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
|
|
619
|
-
const text = (m ? m[1]
|
|
656
|
+
const text = (m ? m[1] : j.content).trim();
|
|
620
657
|
const line = (text.split("\n").find((l) => l.trim()) ?? "").trim();
|
|
621
658
|
if (!line || /^<[a-z_-]+>/i.test(line)) continue;
|
|
622
659
|
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
@@ -664,10 +701,9 @@ function gitFacts(cwd) {
|
|
|
664
701
|
return out;
|
|
665
702
|
}
|
|
666
703
|
function stripCredentials(url) {
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
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$/, "");
|
|
671
707
|
}
|
|
672
708
|
|
|
673
709
|
// ../core/src/adapters/claude-code.ts
|
|
@@ -698,7 +734,7 @@ var OPTS = {
|
|
|
698
734
|
timeoutFor: (e) => e === "PermissionRequest" ? 130 : void 0
|
|
699
735
|
};
|
|
700
736
|
var DESKTOP_ID = /^local_[A-Za-z0-9-]{1,64}$/;
|
|
701
|
-
function claudeDirs(env = process.env, home = env.HOME ||
|
|
737
|
+
function claudeDirs(env = process.env, home = env.HOME || homeDir()) {
|
|
702
738
|
const out = [];
|
|
703
739
|
const add = (d) => {
|
|
704
740
|
if (!d) return;
|
|
@@ -739,11 +775,13 @@ var claudeCode = {
|
|
|
739
775
|
const event = input.argv[1] ?? "";
|
|
740
776
|
let s = {};
|
|
741
777
|
try {
|
|
742
|
-
|
|
778
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
779
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
743
780
|
} catch {
|
|
744
781
|
}
|
|
745
782
|
const id = s.session_id ?? s.sessionId;
|
|
746
783
|
if (!id) return null;
|
|
784
|
+
if (notClaudeCode(String(id), s)) return null;
|
|
747
785
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
748
786
|
if (typeof s.model === "string") session.model = s.model;
|
|
749
787
|
if (typeof s.agent_id === "string" && event.startsWith("Subagent") === false && s.agent_id)
|
|
@@ -862,6 +900,7 @@ var claudeCode = {
|
|
|
862
900
|
return null;
|
|
863
901
|
}
|
|
864
902
|
const hints = {};
|
|
903
|
+
if (event === "UserPromptSubmit" && typeof s.prompt === "string") hints.prompt = s.prompt;
|
|
865
904
|
const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
|
|
866
905
|
if (host && DESKTOP_ID.test(host)) hints.desktop_id = host;
|
|
867
906
|
if (event !== "PreToolUse" && event !== "PostToolUse") hints.claude_pid = claudePid(input.env);
|
|
@@ -887,7 +926,7 @@ var claudeCode = {
|
|
|
887
926
|
return out;
|
|
888
927
|
},
|
|
889
928
|
backfill(sinceMs, env = process.env) {
|
|
890
|
-
const
|
|
929
|
+
const rows2 = [];
|
|
891
930
|
const files = [];
|
|
892
931
|
for (const d of claudeDirs(env))
|
|
893
932
|
files.push(...recentFiles(path4.join(d, "projects"), 1, sinceMs, (n) => /^[0-9a-f-]{36}\.jsonl$/.test(n)));
|
|
@@ -899,19 +938,23 @@ var claudeCode = {
|
|
|
899
938
|
if (!facts.started_at || !facts.last_at) continue;
|
|
900
939
|
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
901
940
|
const cwd = rest.cwd;
|
|
902
|
-
|
|
941
|
+
rows2.push({
|
|
903
942
|
session: { id, ...rest, ...gitFacts(cwd), ...rest.branch ? { branch: rest.branch } : {} },
|
|
904
943
|
started_at,
|
|
905
944
|
last_at
|
|
906
945
|
});
|
|
907
946
|
}
|
|
908
|
-
return
|
|
947
|
+
return rows2;
|
|
909
948
|
}
|
|
910
949
|
};
|
|
911
950
|
function prune2(o) {
|
|
912
951
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
913
952
|
return o;
|
|
914
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
|
+
}
|
|
915
958
|
function pickSource(v) {
|
|
916
959
|
return typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
|
|
917
960
|
}
|
|
@@ -1017,23 +1060,27 @@ function parentOf() {
|
|
|
1017
1060
|
}
|
|
1018
1061
|
return (pid) => table.get(pid) || 0;
|
|
1019
1062
|
}
|
|
1020
|
-
var
|
|
1021
|
-
function
|
|
1022
|
-
const hit = accountCache.get(dir);
|
|
1023
|
-
if (hit !== void 0) return hit;
|
|
1063
|
+
var EMAIL_RE = /^[^@\s]{1,64}@[^@\s]{1,189}$/;
|
|
1064
|
+
function claudeLogin(dir, home = homeDir()) {
|
|
1024
1065
|
const files = [path4.join(dir, ".claude.json")];
|
|
1025
|
-
if (path4.resolve(dir) === path4.join(
|
|
1026
|
-
let acct = null;
|
|
1066
|
+
if (path4.resolve(dir) === path4.join(home, ".claude")) files.unshift(path4.join(home, ".claude.json"));
|
|
1027
1067
|
for (const f of files) {
|
|
1028
1068
|
try {
|
|
1029
1069
|
const a = JSON.parse(readFileSync3(f, "utf8")).oauthAccount;
|
|
1030
|
-
if (a?.accountUuid) {
|
|
1031
|
-
|
|
1032
|
-
|
|
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 } : {} };
|
|
1033
1073
|
}
|
|
1034
1074
|
} catch {
|
|
1035
1075
|
}
|
|
1036
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;
|
|
1037
1084
|
accountCache.set(dir, acct);
|
|
1038
1085
|
return acct;
|
|
1039
1086
|
}
|
|
@@ -1253,18 +1300,13 @@ function parseArgv(raw) {
|
|
|
1253
1300
|
function readNotify(text) {
|
|
1254
1301
|
const { head, tail, at } = split(text);
|
|
1255
1302
|
const m = NOTIFY_LINE.exec(head);
|
|
1256
|
-
if (m)
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
index: m.index,
|
|
1261
|
-
length: m[0].length,
|
|
1262
|
-
misplaced: false,
|
|
1263
|
-
other: !OURS2.test(m[1] ?? "")
|
|
1264
|
-
};
|
|
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
|
+
}
|
|
1265
1307
|
const k = NOTIFY_KEY.exec(head);
|
|
1266
1308
|
if (k) {
|
|
1267
|
-
const rest = head.slice(k.index).split("\n")[0]
|
|
1309
|
+
const rest = head.slice(k.index).split("\n")[0];
|
|
1268
1310
|
return {
|
|
1269
1311
|
raw: rest.replace(NOTIFY_KEY, "").trim(),
|
|
1270
1312
|
argv: null,
|
|
@@ -1275,15 +1317,11 @@ function readNotify(text) {
|
|
|
1275
1317
|
};
|
|
1276
1318
|
}
|
|
1277
1319
|
const t = NOTIFY_LINE.exec(tail);
|
|
1278
|
-
if (t
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
argv: parseArgv(t[
|
|
1282
|
-
|
|
1283
|
-
length: t[0].length,
|
|
1284
|
-
misplaced: true,
|
|
1285
|
-
other: false
|
|
1286
|
-
};
|
|
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
|
+
}
|
|
1287
1325
|
return null;
|
|
1288
1326
|
}
|
|
1289
1327
|
function dropLine(text, index, length) {
|
|
@@ -1302,9 +1340,7 @@ function withNotify(text, line) {
|
|
|
1302
1340
|
if (n?.misplaced) return insertTopLevel(dropLine(text, n.index, n.length), line);
|
|
1303
1341
|
return insertTopLevel(text, line);
|
|
1304
1342
|
}
|
|
1305
|
-
function replaceNotify(text, line) {
|
|
1306
|
-
const n = readNotify(text);
|
|
1307
|
-
if (!n || n.misplaced || !n.length) return null;
|
|
1343
|
+
function replaceNotify(text, n, line) {
|
|
1308
1344
|
return text.slice(0, n.index) + line + text.slice(n.index + n.length);
|
|
1309
1345
|
}
|
|
1310
1346
|
function withoutNotify(text) {
|
|
@@ -1345,7 +1381,7 @@ var CODEX_EVENTS = [
|
|
|
1345
1381
|
];
|
|
1346
1382
|
var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
1347
1383
|
var eventsFor2 = (lean) => lean ? CODEX_EVENTS.filter((e) => e !== "PreToolUse") : [...CODEX_EVENTS];
|
|
1348
|
-
var codexHome = (env) => env.CODEX_HOME || path5.join(
|
|
1384
|
+
var codexHome = (env) => env.CODEX_HOME || path5.join(homeDir(), ".codex");
|
|
1349
1385
|
var hooksFile2 = (env) => path5.join(codexHome(env), "hooks.json");
|
|
1350
1386
|
var configFile = (env) => path5.join(codexHome(env), "config.toml");
|
|
1351
1387
|
var sessionsDir = (env) => path5.join(codexHome(env), "sessions");
|
|
@@ -1383,11 +1419,8 @@ function installNotify(cmd, env) {
|
|
|
1383
1419
|
return { file, changed: false, note: "notify current (after Codex Computer Use)" };
|
|
1384
1420
|
}
|
|
1385
1421
|
if (!prev || OURS2.test(JSON.stringify(prev))) {
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
write(next);
|
|
1389
|
-
return { file, changed: true, note: "chained after Codex Computer Use (--previous-notify)" };
|
|
1390
|
-
}
|
|
1422
|
+
write(replaceNotify(text, n, `notify = ${withPreviousRaw(n.raw, argv)}`));
|
|
1423
|
+
return { file, changed: true, note: "chained after Codex Computer Use (--previous-notify)" };
|
|
1391
1424
|
}
|
|
1392
1425
|
}
|
|
1393
1426
|
return {
|
|
@@ -1416,8 +1449,7 @@ function uninstallNotify(env) {
|
|
|
1416
1449
|
}
|
|
1417
1450
|
const prev = previousNotify(n.argv);
|
|
1418
1451
|
if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev))) {
|
|
1419
|
-
|
|
1420
|
-
if (next) writeFileSync3(file, next);
|
|
1452
|
+
writeFileSync3(file, replaceNotify(text, n, `notify = ${stripPreviousRaw(n.raw)}`));
|
|
1421
1453
|
return { file, changed: true };
|
|
1422
1454
|
}
|
|
1423
1455
|
return { file, changed: false };
|
|
@@ -1447,6 +1479,13 @@ function findRollout(threadId, env = process.env) {
|
|
|
1447
1479
|
walk(sessionsDir(env), 0);
|
|
1448
1480
|
return best;
|
|
1449
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
|
+
}
|
|
1450
1489
|
var codex = {
|
|
1451
1490
|
name: NAME3,
|
|
1452
1491
|
events: CODEX_EVENTS,
|
|
@@ -1489,11 +1528,14 @@ var codex = {
|
|
|
1489
1528
|
if (event === "notify") {
|
|
1490
1529
|
const raw = input.argv.find((a) => a.startsWith("{")) ?? input.stdin;
|
|
1491
1530
|
try {
|
|
1492
|
-
|
|
1531
|
+
const j = raw ? JSON.parse(raw) : {};
|
|
1532
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1493
1533
|
} catch {
|
|
1494
1534
|
}
|
|
1495
1535
|
const id2 = s["thread-id"] ?? s.thread_id ?? s["session-id"] ?? s.session_id;
|
|
1496
1536
|
if (!id2) return null;
|
|
1537
|
+
const file = findRollout(String(id2), input.env);
|
|
1538
|
+
if (!file || codexSubagent(rolloutMeta(file))) return null;
|
|
1497
1539
|
const r2 = {
|
|
1498
1540
|
session: { id: String(id2), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd },
|
|
1499
1541
|
events: [
|
|
@@ -1503,18 +1545,19 @@ var codex = {
|
|
|
1503
1545
|
harnessEvent: "notify"
|
|
1504
1546
|
}
|
|
1505
1547
|
],
|
|
1506
|
-
hints: {}
|
|
1548
|
+
hints: {},
|
|
1549
|
+
transcript: file
|
|
1507
1550
|
};
|
|
1508
|
-
const file = findRollout(String(id2), input.env);
|
|
1509
|
-
if (file) r2.transcript = file;
|
|
1510
1551
|
return r2;
|
|
1511
1552
|
}
|
|
1512
1553
|
try {
|
|
1513
|
-
|
|
1554
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
1555
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1514
1556
|
} catch {
|
|
1515
1557
|
}
|
|
1516
1558
|
const id = s.session_id;
|
|
1517
1559
|
if (!id) return null;
|
|
1560
|
+
if (s.transcript_path === null) return null;
|
|
1518
1561
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
1519
1562
|
if (typeof s.model === "string") session.model = s.model;
|
|
1520
1563
|
const turn_id = typeof s.turn_id === "string" ? s.turn_id : void 0;
|
|
@@ -1586,7 +1629,8 @@ var codex = {
|
|
|
1586
1629
|
return null;
|
|
1587
1630
|
}
|
|
1588
1631
|
const r = { session, events, hints: {} };
|
|
1589
|
-
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;
|
|
1590
1634
|
return r;
|
|
1591
1635
|
},
|
|
1592
1636
|
readTranscript(file, fromLine) {
|
|
@@ -1604,16 +1648,17 @@ var codex = {
|
|
|
1604
1648
|
return out;
|
|
1605
1649
|
},
|
|
1606
1650
|
backfill(sinceMs, env = process.env) {
|
|
1607
|
-
const
|
|
1651
|
+
const rows2 = [];
|
|
1608
1652
|
for (const f of recentFiles(sessionsDir(env), 3, sinceMs, (n) => /^rollout-.*\.jsonl$/.test(n))) {
|
|
1609
1653
|
const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(path5.basename(f));
|
|
1610
1654
|
if (!m?.[1]) continue;
|
|
1655
|
+
if (codexSubagent(rolloutMeta(f))) continue;
|
|
1611
1656
|
const facts = codexFacts(f);
|
|
1612
1657
|
if (!facts.started_at || !facts.last_at) continue;
|
|
1613
1658
|
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
1614
|
-
|
|
1659
|
+
rows2.push({ session: { id: m[1], ...rest }, started_at, last_at });
|
|
1615
1660
|
}
|
|
1616
|
-
return
|
|
1661
|
+
return rows2;
|
|
1617
1662
|
}
|
|
1618
1663
|
};
|
|
1619
1664
|
function prune3(o) {
|
|
@@ -1622,10 +1667,11 @@ function prune3(o) {
|
|
|
1622
1667
|
}
|
|
1623
1668
|
function codexFacts(file) {
|
|
1624
1669
|
const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
|
|
1625
|
-
|
|
1626
|
-
|
|
1670
|
+
let H = parseLines(head);
|
|
1671
|
+
let T = parseLines(tail);
|
|
1627
1672
|
const out = {};
|
|
1628
1673
|
const meta = H.find((j) => j.type === "session_meta" && j.payload);
|
|
1674
|
+
const boundary = codexReplayBoundary(meta);
|
|
1629
1675
|
if (meta?.payload) {
|
|
1630
1676
|
const p = meta.payload;
|
|
1631
1677
|
if (p.cwd) out.cwd = p.cwd;
|
|
@@ -1633,6 +1679,13 @@ function codexFacts(file) {
|
|
|
1633
1679
|
if (p.git?.branch) out.branch = p.git.branch;
|
|
1634
1680
|
if (p.git?.repository_url) out.repo = String(p.git.repository_url).replace(/\/\/[^/@]*@/, "//");
|
|
1635
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
|
+
}
|
|
1636
1689
|
const ctx = H.concat(T).reverse().find((j) => j.type === "turn_context" && j.payload?.model);
|
|
1637
1690
|
if (ctx?.payload?.model) out.model = ctx.payload.model;
|
|
1638
1691
|
for (const j of H) {
|
|
@@ -1640,12 +1693,12 @@ function codexFacts(file) {
|
|
|
1640
1693
|
if (j.type !== "response_item" || !p || p.type !== "message" || p.role !== "user") continue;
|
|
1641
1694
|
const text = (p.content ?? []).filter((b) => b && b.type === "input_text" && typeof b.text === "string").map((b) => b.text).join("\n").trim();
|
|
1642
1695
|
if (!text || codexInjected(text)) continue;
|
|
1643
|
-
const line = text.split("\n")
|
|
1696
|
+
const line = text.split("\n")[0];
|
|
1644
1697
|
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
1645
1698
|
out.title_source = "first-ask";
|
|
1646
1699
|
break;
|
|
1647
1700
|
}
|
|
1648
|
-
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);
|
|
1649
1702
|
if (times.length) {
|
|
1650
1703
|
out.started_at = Math.min(...times);
|
|
1651
1704
|
out.last_at = Math.max(...times);
|
|
@@ -1653,11 +1706,432 @@ function codexFacts(file) {
|
|
|
1653
1706
|
return out;
|
|
1654
1707
|
}
|
|
1655
1708
|
|
|
1656
|
-
// ../core/src/adapters/
|
|
1657
|
-
import {
|
|
1658
|
-
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";
|
|
1659
1712
|
import path6 from "node:path";
|
|
1660
|
-
|
|
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";
|
|
1661
2135
|
var GEMINI_EVENTS = [
|
|
1662
2136
|
"SessionStart",
|
|
1663
2137
|
"BeforeAgent",
|
|
@@ -1668,11 +2142,11 @@ var GEMINI_EVENTS = [
|
|
|
1668
2142
|
"PreCompress",
|
|
1669
2143
|
"SessionEnd"
|
|
1670
2144
|
];
|
|
1671
|
-
var
|
|
1672
|
-
var geminiDir = (env) => env.GEMINI_CLI_HOME ||
|
|
1673
|
-
var settings = (env) =>
|
|
1674
|
-
var
|
|
1675
|
-
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];
|
|
1676
2150
|
function geminiHooksActive(settingsFile) {
|
|
1677
2151
|
let tools;
|
|
1678
2152
|
try {
|
|
@@ -1684,7 +2158,7 @@ function geminiHooksActive(settingsFile) {
|
|
|
1684
2158
|
return { active: false, note: "inactive: tools.enableHooks is false in settings.json" };
|
|
1685
2159
|
let version = "";
|
|
1686
2160
|
try {
|
|
1687
|
-
version =
|
|
2161
|
+
version = execFileSync4("gemini", ["--version"], {
|
|
1688
2162
|
encoding: "utf8",
|
|
1689
2163
|
timeout: 4e3,
|
|
1690
2164
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -1703,13 +2177,13 @@ function geminiHooksActive(settingsFile) {
|
|
|
1703
2177
|
};
|
|
1704
2178
|
}
|
|
1705
2179
|
var geminiCli = {
|
|
1706
|
-
name:
|
|
2180
|
+
name: NAME5,
|
|
1707
2181
|
events: GEMINI_EVENTS,
|
|
1708
2182
|
// ~/.gemini also belongs to Antigravity; Gemini CLI is here when its settings or its chat logs exist.
|
|
1709
|
-
detect: (env = process.env) =>
|
|
2183
|
+
detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(path7.join(geminiDir(env), "tmp")),
|
|
1710
2184
|
configFiles: (env = process.env) => [settings(env)],
|
|
1711
2185
|
install: (cmd, env = process.env, opts = {}) => {
|
|
1712
|
-
const r = installClaudeShaped(settings(env),
|
|
2186
|
+
const r = installClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
|
|
1713
2187
|
const a = geminiHooksActive(settings(env));
|
|
1714
2188
|
return [a.note && !r.skipped ? { ...r, note: [r.note, a.note].filter(Boolean).join("; ") } : r];
|
|
1715
2189
|
},
|
|
@@ -1717,7 +2191,7 @@ var geminiCli = {
|
|
|
1717
2191
|
uninstallClaudeShaped(settings(env), GEMINI_EVENTS, { created: opts.created?.includes(settings(env)) })
|
|
1718
2192
|
],
|
|
1719
2193
|
installed: (cmd, env = process.env, opts = {}) => {
|
|
1720
|
-
const state = installedClaudeShaped(settings(env),
|
|
2194
|
+
const state = installedClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
|
|
1721
2195
|
const a = geminiHooksActive(settings(env));
|
|
1722
2196
|
return [
|
|
1723
2197
|
{
|
|
@@ -1731,14 +2205,15 @@ var geminiCli = {
|
|
|
1731
2205
|
const event = input.argv[1] ?? "";
|
|
1732
2206
|
let s = {};
|
|
1733
2207
|
try {
|
|
1734
|
-
|
|
2208
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
2209
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1735
2210
|
} catch {
|
|
1736
2211
|
}
|
|
1737
2212
|
const id = s.session_id;
|
|
1738
2213
|
if (!id) return null;
|
|
1739
2214
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
1740
|
-
const tool =
|
|
1741
|
-
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 });
|
|
1742
2217
|
const events = [];
|
|
1743
2218
|
switch (event) {
|
|
1744
2219
|
case "SessionStart":
|
|
@@ -1819,7 +2294,7 @@ var geminiCli = {
|
|
|
1819
2294
|
const raw = first ? first.content ?? first.message ?? first.text : void 0;
|
|
1820
2295
|
const text = typeof raw === "string" ? raw.trim() : "";
|
|
1821
2296
|
if (text) {
|
|
1822
|
-
const line = text.split("\n")
|
|
2297
|
+
const line = text.split("\n")[0];
|
|
1823
2298
|
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
1824
2299
|
out.title_source = "first-ask";
|
|
1825
2300
|
}
|
|
@@ -1833,13 +2308,13 @@ var geminiCli = {
|
|
|
1833
2308
|
backfill: () => []
|
|
1834
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
|
|
1835
2310
|
};
|
|
1836
|
-
function
|
|
2311
|
+
function prune5(o) {
|
|
1837
2312
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
1838
2313
|
return o;
|
|
1839
2314
|
}
|
|
1840
2315
|
|
|
1841
2316
|
// ../core/src/adapters/index.ts
|
|
1842
|
-
var ADAPTERS = [claudeCode, codex, geminiCli, antigravity];
|
|
2317
|
+
var ADAPTERS = [claudeCode, codex, geminiCli, antigravity, devin];
|
|
1843
2318
|
var adapterByName = (name) => ADAPTERS.find((a) => a.name === name);
|
|
1844
2319
|
|
|
1845
2320
|
// src/index.ts
|
|
@@ -1879,7 +2354,7 @@ async function runAdapterFixtures(files) {
|
|
|
1879
2354
|
cwd: fx.input.cwd ?? "/"
|
|
1880
2355
|
});
|
|
1881
2356
|
if (!r) {
|
|
1882
|
-
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 });
|
|
1883
2358
|
continue;
|
|
1884
2359
|
}
|
|
1885
2360
|
let why = matches(
|
|
@@ -1890,7 +2365,7 @@ async function runAdapterFixtures(files) {
|
|
|
1890
2365
|
if (!why)
|
|
1891
2366
|
for (const [i, e] of fx.expect.entries()) {
|
|
1892
2367
|
const got = r.events[i];
|
|
1893
|
-
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`);
|
|
1894
2369
|
if (why) break;
|
|
1895
2370
|
}
|
|
1896
2371
|
results.push(why ? { file, ok: false, why } : { file, ok: true });
|