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/cli.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// sessionpipe-conformance 0.
|
|
2
|
+
// sessionpipe-conformance 0.9.0 · Apache-2.0 · https://sessionpipe.org
|
|
3
3
|
|
|
4
4
|
// src/cli.ts
|
|
5
|
-
import { readdirSync as readdirSync5, statSync as statSync4 } from "node:fs";
|
|
6
|
-
import
|
|
5
|
+
import { readdirSync as readdirSync5, realpathSync as realpathSync2, statSync as statSync4 } from "node:fs";
|
|
6
|
+
import path8 from "node:path";
|
|
7
|
+
import { pathToFileURL } from "node:url";
|
|
7
8
|
|
|
8
9
|
// src/index.ts
|
|
9
10
|
import { readFileSync as readFileSync6 } from "node:fs";
|
|
@@ -23,7 +24,7 @@ import path3 from "node:path";
|
|
|
23
24
|
|
|
24
25
|
// ../core/src/paths.ts
|
|
25
26
|
import os from "node:os";
|
|
26
|
-
var
|
|
27
|
+
var homeDir = () => os.homedir();
|
|
27
28
|
|
|
28
29
|
// ../core/src/readers/files.ts
|
|
29
30
|
import { closeSync, fstatSync, openSync, readdirSync, readSync, statSync } from "node:fs";
|
|
@@ -72,8 +73,8 @@ function scanLines(file, visit, cap = 16 * 1024 * 1024) {
|
|
|
72
73
|
let chunk = Buffer.concat([rest, buf.subarray(0, n)]);
|
|
73
74
|
let nl = chunk.indexOf(10);
|
|
74
75
|
while (nl >= 0) {
|
|
75
|
-
const
|
|
76
|
-
if (
|
|
76
|
+
const r = visit(chunk.subarray(0, nl).toString("utf8"));
|
|
77
|
+
if (r !== null) return r;
|
|
77
78
|
chunk = chunk.subarray(nl + 1);
|
|
78
79
|
nl = chunk.indexOf(10);
|
|
79
80
|
}
|
|
@@ -115,6 +116,19 @@ function recentFiles(root, depth, sinceMs, match) {
|
|
|
115
116
|
return out;
|
|
116
117
|
}
|
|
117
118
|
|
|
119
|
+
// ../core/src/adapters/codex-rollout.ts
|
|
120
|
+
function codexSubagent(meta) {
|
|
121
|
+
if (meta?.type !== "session_meta") return false;
|
|
122
|
+
const p = meta.payload;
|
|
123
|
+
return !!p?.source?.subagent;
|
|
124
|
+
}
|
|
125
|
+
function codexReplayBoundary(meta) {
|
|
126
|
+
if (meta?.type !== "session_meta") return void 0;
|
|
127
|
+
const p = meta.payload;
|
|
128
|
+
if (!p || !p.forked_from_id && !p.source?.subagent) return void 0;
|
|
129
|
+
return (record) => typeof p.id === "string" && !!p.id && record?.type === "event_msg" && record.payload?.thread_id === p.id;
|
|
130
|
+
}
|
|
131
|
+
|
|
118
132
|
// ../core/src/readers/transcripts.ts
|
|
119
133
|
function pairTurns(msgs, endLine) {
|
|
120
134
|
const turns = [];
|
|
@@ -137,10 +151,14 @@ ${m.text}`;
|
|
|
137
151
|
}
|
|
138
152
|
if (cur?.assistant) turns.push(cur);
|
|
139
153
|
const line = cur && !cur.assistant ? cur.startLine : endLine;
|
|
140
|
-
|
|
154
|
+
const out = {
|
|
155
|
+
turns: turns.map((t) => ({ user: t.user, assistant: t.assistant, at: t.at, endLine: t.endLine })),
|
|
156
|
+
line
|
|
157
|
+
};
|
|
158
|
+
if (cur && !cur.assistant && cur.user) out.open = { user: cur.user, at: cur.at };
|
|
159
|
+
return out;
|
|
141
160
|
}
|
|
142
|
-
var isInjected = (text) => /^<[a-z_-]
|
|
143
|
-
var endOf = (lines) => lines[lines.length - 1] === "" ? lines.length - 1 : lines.length;
|
|
161
|
+
var isInjected = (text) => /^<[a-z_-]+(?:\s[^>]*)?>/i.test(text) && /<\/[a-z_-]+>\s*$/i.test(text) || /^<!--\s/.test(text) || /^<command-/.test(text);
|
|
144
162
|
var parse = (raw) => {
|
|
145
163
|
try {
|
|
146
164
|
return JSON.parse(raw);
|
|
@@ -148,6 +166,11 @@ var parse = (raw) => {
|
|
|
148
166
|
return null;
|
|
149
167
|
}
|
|
150
168
|
};
|
|
169
|
+
var endOf = (lines) => {
|
|
170
|
+
const last = lines[lines.length - 1];
|
|
171
|
+
if (last === void 0) return 0;
|
|
172
|
+
return last === "" || parse(last) === null ? lines.length - 1 : lines.length;
|
|
173
|
+
};
|
|
151
174
|
var textParts = (c, kinds) => {
|
|
152
175
|
if (typeof c === "string") return c;
|
|
153
176
|
if (!Array.isArray(c)) return "";
|
|
@@ -155,13 +178,21 @@ var textParts = (c, kinds) => {
|
|
|
155
178
|
(b) => b && typeof b === "object" && kinds.includes(b.type ?? "") && typeof b.text === "string"
|
|
156
179
|
).map((b) => b.text).join("\n");
|
|
157
180
|
};
|
|
181
|
+
var queuedPrompt = (a) => {
|
|
182
|
+
if (!a || typeof a !== "object") return "";
|
|
183
|
+
const q = a;
|
|
184
|
+
if (q.type !== "queued_command" || (q.commandMode ?? "prompt") !== "prompt") return "";
|
|
185
|
+
if (q.origin?.kind && q.origin.kind !== "human") return "";
|
|
186
|
+
return textParts(q.prompt, ["text"]).trim();
|
|
187
|
+
};
|
|
158
188
|
function readClaude(lines, from) {
|
|
159
189
|
const msgs = [];
|
|
160
190
|
for (let i = from; i < lines.length; i++) {
|
|
161
191
|
const j = lines[i] ? parse(lines[i]) : null;
|
|
162
192
|
if (!j || j.isSidechain) continue;
|
|
163
193
|
const origin = j.origin;
|
|
164
|
-
if (j.type === "user" && (j.isMeta || origin?.kind && origin.kind !== "human"))
|
|
194
|
+
if (j.type === "user" && (j.isMeta || j.isCompactSummary || j.repeatsCompactSummary || origin?.kind && origin.kind !== "human"))
|
|
195
|
+
continue;
|
|
165
196
|
const at = Date.parse(String(j.timestamp ?? "")) || Date.now();
|
|
166
197
|
const c = j.message?.content;
|
|
167
198
|
if (j.type === "user") {
|
|
@@ -171,13 +202,22 @@ function readClaude(lines, from) {
|
|
|
171
202
|
} else if (j.type === "assistant" && Array.isArray(c)) {
|
|
172
203
|
const text = textParts(c, ["text"]).trim();
|
|
173
204
|
if (text) msgs.push({ role: "assistant", text, at, line: i });
|
|
205
|
+
} else if (j.type === "attachment") {
|
|
206
|
+
const text = queuedPrompt(j.attachment);
|
|
207
|
+
if (text && !isInjected(text)) msgs.push({ role: "user", text, at, line: i });
|
|
174
208
|
}
|
|
175
209
|
}
|
|
176
210
|
return pairTurns(msgs, endOf(lines));
|
|
177
211
|
}
|
|
212
|
+
function codexInjected(text) {
|
|
213
|
+
return /^<[a-z_-]+>/i.test(text) || /^# AGENTS\.md instructions\b/i.test(text);
|
|
214
|
+
}
|
|
178
215
|
function readCodex(lines, from) {
|
|
179
216
|
const msgs = [];
|
|
180
|
-
|
|
217
|
+
const boundary = codexReplayBoundary(parse(lines.find((line) => line.trim()) ?? ""));
|
|
218
|
+
const start = boundary ? lines.findIndex((line) => boundary(parse(line))) : 0;
|
|
219
|
+
if (start < 0) return pairTurns(msgs, endOf(lines));
|
|
220
|
+
for (let i = Math.max(from, start); i < lines.length; i++) {
|
|
181
221
|
const j = lines[i] ? parse(lines[i]) : null;
|
|
182
222
|
if (!j || j.type !== "response_item") continue;
|
|
183
223
|
const p = j.payload;
|
|
@@ -186,7 +226,7 @@ function readCodex(lines, from) {
|
|
|
186
226
|
const text = textParts(p.content, ["input_text", "output_text"]).trim();
|
|
187
227
|
if (!text) continue;
|
|
188
228
|
if (p.role === "user") {
|
|
189
|
-
if (
|
|
229
|
+
if (codexInjected(text)) continue;
|
|
190
230
|
msgs.push({ role: "user", text, at, line: i });
|
|
191
231
|
} else if (p.role === "assistant") msgs.push({ role: "assistant", text, at, line: i });
|
|
192
232
|
}
|
|
@@ -215,7 +255,7 @@ function readAntigravity(lines, from) {
|
|
|
215
255
|
const at = Date.parse(String(j.created_at ?? j.timestamp ?? "")) || Date.now();
|
|
216
256
|
if (j.type === "USER_INPUT") {
|
|
217
257
|
const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
|
|
218
|
-
const text = (m ? m[1]
|
|
258
|
+
const text = (m ? m[1] : j.content).trim();
|
|
219
259
|
if (text) msgs.push({ role: "user", text, at, line: i });
|
|
220
260
|
} else if (j.type === "PLANNER_RESPONSE") {
|
|
221
261
|
const text = j.content.trim();
|
|
@@ -263,6 +303,10 @@ function formatJson(value, f) {
|
|
|
263
303
|
const text = JSON.stringify(value, null, f.indent).replace(/\n/g, f.eol);
|
|
264
304
|
return f.finalNewline ? text + f.eol : text;
|
|
265
305
|
}
|
|
306
|
+
function readJson(file) {
|
|
307
|
+
const f = readJsonFile(file);
|
|
308
|
+
return f.ok ? f.value : {};
|
|
309
|
+
}
|
|
266
310
|
function entryFor(event, harness, cmd, o) {
|
|
267
311
|
const matcher = o.matcherFor(event);
|
|
268
312
|
return {
|
|
@@ -332,8 +376,7 @@ function uninstallClaudeShaped(file, events, opts = {}) {
|
|
|
332
376
|
unlinkSync(file);
|
|
333
377
|
return { file, changed: true, note: "removed (sessionpipe created it)" };
|
|
334
378
|
}
|
|
335
|
-
|
|
336
|
-
if (next !== f.raw) writeFileSync(file, next);
|
|
379
|
+
writeFileSync(file, formatJson(settings2, f));
|
|
337
380
|
return { file, changed: true };
|
|
338
381
|
}
|
|
339
382
|
function installedClaudeShaped(file, harness, events, cmd, o) {
|
|
@@ -360,7 +403,7 @@ var HOOK_NAME = "sessionpipe";
|
|
|
360
403
|
var AG_EVENTS = ["PreInvocation", "PostInvocation", "PostToolUse", "Stop"];
|
|
361
404
|
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';
|
|
362
405
|
var antigravityAnswer = (event) => event === "PreToolUse" ? AG_STALE_PRETOOL : "{}\n";
|
|
363
|
-
var gemini = (env) => env.GEMINI_CLI_HOME || path3.join(
|
|
406
|
+
var gemini = (env) => env.GEMINI_CLI_HOME || path3.join(homeDir(), ".gemini");
|
|
364
407
|
var hooksFile = (env) => path3.join(gemini(env), "config", "hooks.json");
|
|
365
408
|
var projectsDir = (env) => path3.join(gemini(env), "config", "projects");
|
|
366
409
|
var dataDirs = (env) => ["antigravity", "antigravity-cli", "antigravity-ide"].map((d) => path3.join(gemini(env), d));
|
|
@@ -426,7 +469,8 @@ var antigravity = {
|
|
|
426
469
|
const event = input.argv[1] ?? "";
|
|
427
470
|
let s = {};
|
|
428
471
|
try {
|
|
429
|
-
|
|
472
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
473
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
430
474
|
} catch {
|
|
431
475
|
}
|
|
432
476
|
const id = s.conversationId ?? s.session_id ?? s.sessionId;
|
|
@@ -469,9 +513,9 @@ var antigravity = {
|
|
|
469
513
|
default:
|
|
470
514
|
return { session, events: [], stdout };
|
|
471
515
|
}
|
|
472
|
-
const
|
|
473
|
-
if (typeof s.transcriptPath === "string")
|
|
474
|
-
return
|
|
516
|
+
const r = { session, events, stdout, hints: { workspaces } };
|
|
517
|
+
if (typeof s.transcriptPath === "string") r.transcript = s.transcriptPath;
|
|
518
|
+
return r;
|
|
475
519
|
},
|
|
476
520
|
readTranscript(file, fromLine) {
|
|
477
521
|
let text = "";
|
|
@@ -482,19 +526,16 @@ var antigravity = {
|
|
|
482
526
|
}
|
|
483
527
|
return readAntigravity(text.split("\n"), fromLine);
|
|
484
528
|
},
|
|
485
|
-
facts(session, transcript, hints
|
|
529
|
+
facts(session, transcript, hints) {
|
|
486
530
|
if (!transcript) return {};
|
|
487
531
|
return antigravityFacts(transcript, {
|
|
488
532
|
conversation: session.id,
|
|
489
533
|
workspaces: hints.workspaces ?? (session.cwd ? [session.cwd] : []),
|
|
490
|
-
state,
|
|
491
534
|
env: process.env
|
|
492
535
|
});
|
|
493
536
|
},
|
|
494
537
|
backfill(sinceMs, env = process.env) {
|
|
495
|
-
const
|
|
496
|
-
const noState = { get: () => ({}), save: () => {
|
|
497
|
-
} };
|
|
538
|
+
const rows2 = [];
|
|
498
539
|
for (const data of dataDirs(env)) {
|
|
499
540
|
let ids = [];
|
|
500
541
|
try {
|
|
@@ -508,13 +549,13 @@ var antigravity = {
|
|
|
508
549
|
} catch {
|
|
509
550
|
continue;
|
|
510
551
|
}
|
|
511
|
-
const facts = antigravityFacts(f, { conversation: id, workspaces: [],
|
|
552
|
+
const facts = antigravityFacts(f, { conversation: id, workspaces: [], env });
|
|
512
553
|
if (!facts.started_at || !facts.last_at) continue;
|
|
513
554
|
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
514
|
-
|
|
555
|
+
rows2.push({ session: { id, ...rest }, started_at, last_at });
|
|
515
556
|
}
|
|
516
557
|
}
|
|
517
|
-
return
|
|
558
|
+
return rows2;
|
|
518
559
|
}
|
|
519
560
|
};
|
|
520
561
|
function prune(o) {
|
|
@@ -539,8 +580,8 @@ var agWorkspaces = (paths) => (Array.isArray(paths) ? paths : []).map(agPath).fi
|
|
|
539
580
|
function agVariants(p) {
|
|
540
581
|
const out = [p];
|
|
541
582
|
try {
|
|
542
|
-
const
|
|
543
|
-
if (
|
|
583
|
+
const r = realpathSync.native(p);
|
|
584
|
+
if (r !== p) out.push(r);
|
|
544
585
|
} catch {
|
|
545
586
|
}
|
|
546
587
|
return out;
|
|
@@ -563,8 +604,8 @@ function agProjects(dir) {
|
|
|
563
604
|
continue;
|
|
564
605
|
}
|
|
565
606
|
const folders = [];
|
|
566
|
-
for (const
|
|
567
|
-
const f = agPath(
|
|
607
|
+
for (const r of j?.projectResources?.resources ?? []) {
|
|
608
|
+
const f = agPath(r?.folderUri ?? r?.gitFolder?.folderUri);
|
|
568
609
|
if (f) folders.push(...agVariants(f));
|
|
569
610
|
}
|
|
570
611
|
if (folders.length) out.push({ id, folders });
|
|
@@ -618,7 +659,7 @@ function antigravityFacts(file, opts) {
|
|
|
618
659
|
for (const j of H) {
|
|
619
660
|
if (j.type !== "USER_INPUT" || typeof j.content !== "string") continue;
|
|
620
661
|
const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
|
|
621
|
-
const text = (m ? m[1]
|
|
662
|
+
const text = (m ? m[1] : j.content).trim();
|
|
622
663
|
const line = (text.split("\n").find((l) => l.trim()) ?? "").trim();
|
|
623
664
|
if (!line || /^<[a-z_-]+>/i.test(line)) continue;
|
|
624
665
|
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
@@ -666,10 +707,9 @@ function gitFacts(cwd) {
|
|
|
666
707
|
return out;
|
|
667
708
|
}
|
|
668
709
|
function stripCredentials(url) {
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
return u.replace(/^https?:\/\//, "").replace(/\.git$/, "");
|
|
710
|
+
const ssh = /^(?:ssh:\/\/)?git@([^:/]+)[:/](.+?)(?:\.git)?$/.exec(url);
|
|
711
|
+
if (ssh) return `${ssh[1]}/${ssh[2]}`;
|
|
712
|
+
return url.replace(/\/\/[^/@]*@/, "//").replace(/^https?:\/\//, "").replace(/\.git$/, "");
|
|
673
713
|
}
|
|
674
714
|
|
|
675
715
|
// ../core/src/adapters/claude-code.ts
|
|
@@ -700,12 +740,12 @@ var OPTS = {
|
|
|
700
740
|
timeoutFor: (e) => e === "PermissionRequest" ? 130 : void 0
|
|
701
741
|
};
|
|
702
742
|
var DESKTOP_ID = /^local_[A-Za-z0-9-]{1,64}$/;
|
|
703
|
-
function claudeDirs(env = process.env, home = env.HOME ||
|
|
743
|
+
function claudeDirs(env = process.env, home = env.HOME || homeDir()) {
|
|
704
744
|
const out = [];
|
|
705
745
|
const add = (d) => {
|
|
706
746
|
if (!d) return;
|
|
707
|
-
const
|
|
708
|
-
if (!out.includes(
|
|
747
|
+
const r = path4.resolve(d);
|
|
748
|
+
if (!out.includes(r)) out.push(r);
|
|
709
749
|
};
|
|
710
750
|
add(path4.join(home, ".claude"));
|
|
711
751
|
add(env.CLAUDE_CONFIG_DIR);
|
|
@@ -722,7 +762,7 @@ function claudeDirs(env = process.env, home = env.HOME || HOME) {
|
|
|
722
762
|
var settingsIn = (dir) => path4.join(dir, "settings.json");
|
|
723
763
|
var configDirOf = (transcript) => path4.dirname(path4.dirname(path4.dirname(transcript)));
|
|
724
764
|
var validTool2 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
725
|
-
var endReason = (
|
|
765
|
+
var endReason = (r) => r === "clear" || r === "logout" || r === "exit" ? r : r === "prompt_input_exit" ? "exit" : "other";
|
|
726
766
|
var notifKind = (t) => t === "permission_prompt" ? "permission" : t === "idle_prompt" ? "idle" : t === "elicitation_dialog" ? "elicitation" : "question";
|
|
727
767
|
var claudeCode = {
|
|
728
768
|
name: NAME2,
|
|
@@ -741,11 +781,13 @@ var claudeCode = {
|
|
|
741
781
|
const event = input.argv[1] ?? "";
|
|
742
782
|
let s = {};
|
|
743
783
|
try {
|
|
744
|
-
|
|
784
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
785
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
745
786
|
} catch {
|
|
746
787
|
}
|
|
747
788
|
const id = s.session_id ?? s.sessionId;
|
|
748
789
|
if (!id) return null;
|
|
790
|
+
if (notClaudeCode(String(id), s)) return null;
|
|
749
791
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
750
792
|
if (typeof s.model === "string") session.model = s.model;
|
|
751
793
|
if (typeof s.agent_id === "string" && event.startsWith("Subagent") === false && s.agent_id)
|
|
@@ -864,12 +906,13 @@ var claudeCode = {
|
|
|
864
906
|
return null;
|
|
865
907
|
}
|
|
866
908
|
const hints = {};
|
|
909
|
+
if (event === "UserPromptSubmit" && typeof s.prompt === "string") hints.prompt = s.prompt;
|
|
867
910
|
const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
|
|
868
911
|
if (host && DESKTOP_ID.test(host)) hints.desktop_id = host;
|
|
869
912
|
if (event !== "PreToolUse" && event !== "PostToolUse") hints.claude_pid = claudePid(input.env);
|
|
870
|
-
const
|
|
871
|
-
if (typeof s.transcript_path === "string")
|
|
872
|
-
return
|
|
913
|
+
const r = { session, events, hints };
|
|
914
|
+
if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
|
|
915
|
+
return r;
|
|
873
916
|
},
|
|
874
917
|
readTranscript(file, fromLine) {
|
|
875
918
|
let text = "";
|
|
@@ -889,7 +932,7 @@ var claudeCode = {
|
|
|
889
932
|
return out;
|
|
890
933
|
},
|
|
891
934
|
backfill(sinceMs, env = process.env) {
|
|
892
|
-
const
|
|
935
|
+
const rows2 = [];
|
|
893
936
|
const files = [];
|
|
894
937
|
for (const d of claudeDirs(env))
|
|
895
938
|
files.push(...recentFiles(path4.join(d, "projects"), 1, sinceMs, (n) => /^[0-9a-f-]{36}\.jsonl$/.test(n)));
|
|
@@ -901,19 +944,23 @@ var claudeCode = {
|
|
|
901
944
|
if (!facts.started_at || !facts.last_at) continue;
|
|
902
945
|
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
903
946
|
const cwd = rest.cwd;
|
|
904
|
-
|
|
947
|
+
rows2.push({
|
|
905
948
|
session: { id, ...rest, ...gitFacts(cwd), ...rest.branch ? { branch: rest.branch } : {} },
|
|
906
949
|
started_at,
|
|
907
950
|
last_at
|
|
908
951
|
});
|
|
909
952
|
}
|
|
910
|
-
return
|
|
953
|
+
return rows2;
|
|
911
954
|
}
|
|
912
955
|
};
|
|
913
956
|
function prune2(o) {
|
|
914
957
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
915
958
|
return o;
|
|
916
959
|
}
|
|
960
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
961
|
+
function notClaudeCode(id, s) {
|
|
962
|
+
return typeof s.transcript_path !== "string" && !UUID.test(id);
|
|
963
|
+
}
|
|
917
964
|
function pickSource(v) {
|
|
918
965
|
return typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
|
|
919
966
|
}
|
|
@@ -940,13 +987,13 @@ var NAME_RANK = {
|
|
|
940
987
|
};
|
|
941
988
|
function registryEntry(j) {
|
|
942
989
|
if (!j || typeof j !== "object") return null;
|
|
943
|
-
const
|
|
944
|
-
const source =
|
|
990
|
+
const r = j;
|
|
991
|
+
const source = r.nameSource ? NAME_RANK[r.nameSource] : void 0;
|
|
945
992
|
return {
|
|
946
|
-
sessionId:
|
|
947
|
-
name: source && typeof
|
|
993
|
+
sessionId: r.sessionId ? String(r.sessionId) : null,
|
|
994
|
+
name: source && typeof r.name === "string" && r.name.trim() ? r.name.trim() : null,
|
|
948
995
|
name_source: source ?? null,
|
|
949
|
-
bridge: typeof
|
|
996
|
+
bridge: typeof r.bridgeSessionId === "string" ? r.bridgeSessionId : null
|
|
950
997
|
};
|
|
951
998
|
}
|
|
952
999
|
var liveCache = /* @__PURE__ */ new Map();
|
|
@@ -1019,23 +1066,27 @@ function parentOf() {
|
|
|
1019
1066
|
}
|
|
1020
1067
|
return (pid) => table.get(pid) || 0;
|
|
1021
1068
|
}
|
|
1022
|
-
var
|
|
1023
|
-
function
|
|
1024
|
-
const hit = accountCache.get(dir);
|
|
1025
|
-
if (hit !== void 0) return hit;
|
|
1069
|
+
var EMAIL_RE = /^[^@\s]{1,64}@[^@\s]{1,189}$/;
|
|
1070
|
+
function claudeLogin(dir, home = homeDir()) {
|
|
1026
1071
|
const files = [path4.join(dir, ".claude.json")];
|
|
1027
|
-
if (path4.resolve(dir) === path4.join(
|
|
1028
|
-
let acct = null;
|
|
1072
|
+
if (path4.resolve(dir) === path4.join(home, ".claude")) files.unshift(path4.join(home, ".claude.json"));
|
|
1029
1073
|
for (const f of files) {
|
|
1030
1074
|
try {
|
|
1031
1075
|
const a = JSON.parse(readFileSync3(f, "utf8")).oauthAccount;
|
|
1032
|
-
if (a?.accountUuid) {
|
|
1033
|
-
|
|
1034
|
-
|
|
1076
|
+
if (typeof a?.accountUuid === "string" && a.accountUuid) {
|
|
1077
|
+
const email = typeof a.emailAddress === "string" && EMAIL_RE.test(a.emailAddress) ? a.emailAddress : void 0;
|
|
1078
|
+
return { id: a.accountUuid.slice(0, 128), ...email ? { email } : {} };
|
|
1035
1079
|
}
|
|
1036
1080
|
} catch {
|
|
1037
1081
|
}
|
|
1038
1082
|
}
|
|
1083
|
+
return null;
|
|
1084
|
+
}
|
|
1085
|
+
var accountCache = /* @__PURE__ */ new Map();
|
|
1086
|
+
function claudeAccount(dir) {
|
|
1087
|
+
const hit = accountCache.get(dir);
|
|
1088
|
+
if (hit !== void 0) return hit;
|
|
1089
|
+
const acct = claudeLogin(dir)?.id ?? null;
|
|
1039
1090
|
accountCache.set(dir, acct);
|
|
1040
1091
|
return acct;
|
|
1041
1092
|
}
|
|
@@ -1255,18 +1306,13 @@ function parseArgv(raw) {
|
|
|
1255
1306
|
function readNotify(text) {
|
|
1256
1307
|
const { head, tail, at } = split(text);
|
|
1257
1308
|
const m = NOTIFY_LINE.exec(head);
|
|
1258
|
-
if (m)
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
index: m.index,
|
|
1263
|
-
length: m[0].length,
|
|
1264
|
-
misplaced: false,
|
|
1265
|
-
other: !OURS2.test(m[1] ?? "")
|
|
1266
|
-
};
|
|
1309
|
+
if (m) {
|
|
1310
|
+
const raw = m[1];
|
|
1311
|
+
return { raw, argv: parseArgv(raw), index: m.index, length: m[0].length, misplaced: false, other: !OURS2.test(raw) };
|
|
1312
|
+
}
|
|
1267
1313
|
const k = NOTIFY_KEY.exec(head);
|
|
1268
1314
|
if (k) {
|
|
1269
|
-
const rest = head.slice(k.index).split("\n")[0]
|
|
1315
|
+
const rest = head.slice(k.index).split("\n")[0];
|
|
1270
1316
|
return {
|
|
1271
1317
|
raw: rest.replace(NOTIFY_KEY, "").trim(),
|
|
1272
1318
|
argv: null,
|
|
@@ -1277,15 +1323,11 @@ function readNotify(text) {
|
|
|
1277
1323
|
};
|
|
1278
1324
|
}
|
|
1279
1325
|
const t = NOTIFY_LINE.exec(tail);
|
|
1280
|
-
if (t
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
argv: parseArgv(t[
|
|
1284
|
-
|
|
1285
|
-
length: t[0].length,
|
|
1286
|
-
misplaced: true,
|
|
1287
|
-
other: false
|
|
1288
|
-
};
|
|
1326
|
+
if (t) {
|
|
1327
|
+
const raw = t[1];
|
|
1328
|
+
if (OURS2.test(raw))
|
|
1329
|
+
return { raw, argv: parseArgv(raw), index: at + t.index, length: t[0].length, misplaced: true, other: false };
|
|
1330
|
+
}
|
|
1289
1331
|
return null;
|
|
1290
1332
|
}
|
|
1291
1333
|
function dropLine(text, index, length) {
|
|
@@ -1304,9 +1346,7 @@ function withNotify(text, line) {
|
|
|
1304
1346
|
if (n?.misplaced) return insertTopLevel(dropLine(text, n.index, n.length), line);
|
|
1305
1347
|
return insertTopLevel(text, line);
|
|
1306
1348
|
}
|
|
1307
|
-
function replaceNotify(text, line) {
|
|
1308
|
-
const n = readNotify(text);
|
|
1309
|
-
if (!n || n.misplaced || !n.length) return null;
|
|
1349
|
+
function replaceNotify(text, n, line) {
|
|
1310
1350
|
return text.slice(0, n.index) + line + text.slice(n.index + n.length);
|
|
1311
1351
|
}
|
|
1312
1352
|
function withoutNotify(text) {
|
|
@@ -1347,12 +1387,12 @@ var CODEX_EVENTS = [
|
|
|
1347
1387
|
];
|
|
1348
1388
|
var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
1349
1389
|
var eventsFor2 = (lean) => lean ? CODEX_EVENTS.filter((e) => e !== "PreToolUse") : [...CODEX_EVENTS];
|
|
1350
|
-
var codexHome = (env) => env.CODEX_HOME || path5.join(
|
|
1390
|
+
var codexHome = (env) => env.CODEX_HOME || path5.join(homeDir(), ".codex");
|
|
1351
1391
|
var hooksFile2 = (env) => path5.join(codexHome(env), "hooks.json");
|
|
1352
1392
|
var configFile = (env) => path5.join(codexHome(env), "config.toml");
|
|
1353
1393
|
var sessionsDir = (env) => path5.join(codexHome(env), "sessions");
|
|
1354
1394
|
var validTool3 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
1355
|
-
var endReason2 = (
|
|
1395
|
+
var endReason2 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
|
|
1356
1396
|
function installNotify(cmd, env) {
|
|
1357
1397
|
const file = configFile(env);
|
|
1358
1398
|
let text = "";
|
|
@@ -1385,11 +1425,8 @@ function installNotify(cmd, env) {
|
|
|
1385
1425
|
return { file, changed: false, note: "notify current (after Codex Computer Use)" };
|
|
1386
1426
|
}
|
|
1387
1427
|
if (!prev || OURS2.test(JSON.stringify(prev))) {
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
write(next);
|
|
1391
|
-
return { file, changed: true, note: "chained after Codex Computer Use (--previous-notify)" };
|
|
1392
|
-
}
|
|
1428
|
+
write(replaceNotify(text, n, `notify = ${withPreviousRaw(n.raw, argv)}`));
|
|
1429
|
+
return { file, changed: true, note: "chained after Codex Computer Use (--previous-notify)" };
|
|
1393
1430
|
}
|
|
1394
1431
|
}
|
|
1395
1432
|
return {
|
|
@@ -1418,13 +1455,13 @@ function uninstallNotify(env) {
|
|
|
1418
1455
|
}
|
|
1419
1456
|
const prev = previousNotify(n.argv);
|
|
1420
1457
|
if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev))) {
|
|
1421
|
-
|
|
1422
|
-
if (next) writeFileSync3(file, next);
|
|
1458
|
+
writeFileSync3(file, replaceNotify(text, n, `notify = ${stripPreviousRaw(n.raw)}`));
|
|
1423
1459
|
return { file, changed: true };
|
|
1424
1460
|
}
|
|
1425
1461
|
return { file, changed: false };
|
|
1426
1462
|
}
|
|
1427
|
-
function findRollout(threadId, env) {
|
|
1463
|
+
function findRollout(threadId, env = process.env) {
|
|
1464
|
+
if (!/^[A-Za-z0-9-]{8,128}$/.test(threadId)) return null;
|
|
1428
1465
|
let best = null;
|
|
1429
1466
|
const walk2 = (dir, depth) => {
|
|
1430
1467
|
let names = [];
|
|
@@ -1435,7 +1472,7 @@ function findRollout(threadId, env) {
|
|
|
1435
1472
|
}
|
|
1436
1473
|
for (const n of names.sort().reverse()) {
|
|
1437
1474
|
const p = path5.join(dir, n);
|
|
1438
|
-
if (n.endsWith(
|
|
1475
|
+
if (n.endsWith(`-${threadId}.jsonl`)) {
|
|
1439
1476
|
best = p;
|
|
1440
1477
|
return;
|
|
1441
1478
|
}
|
|
@@ -1448,6 +1485,13 @@ function findRollout(threadId, env) {
|
|
|
1448
1485
|
walk2(sessionsDir(env), 0);
|
|
1449
1486
|
return best;
|
|
1450
1487
|
}
|
|
1488
|
+
function rolloutMeta(file) {
|
|
1489
|
+
return scanLines(file, (line) => parseLines([line])[0] ?? {});
|
|
1490
|
+
}
|
|
1491
|
+
function ofAnotherThread(file, id) {
|
|
1492
|
+
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);
|
|
1493
|
+
return !!m?.[1] && m[1].toLowerCase() !== id.toLowerCase();
|
|
1494
|
+
}
|
|
1451
1495
|
var codex = {
|
|
1452
1496
|
name: NAME3,
|
|
1453
1497
|
events: CODEX_EVENTS,
|
|
@@ -1490,12 +1534,15 @@ var codex = {
|
|
|
1490
1534
|
if (event === "notify") {
|
|
1491
1535
|
const raw = input.argv.find((a) => a.startsWith("{")) ?? input.stdin;
|
|
1492
1536
|
try {
|
|
1493
|
-
|
|
1537
|
+
const j = raw ? JSON.parse(raw) : {};
|
|
1538
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1494
1539
|
} catch {
|
|
1495
1540
|
}
|
|
1496
1541
|
const id2 = s["thread-id"] ?? s.thread_id ?? s["session-id"] ?? s.session_id;
|
|
1497
1542
|
if (!id2) return null;
|
|
1498
|
-
const
|
|
1543
|
+
const file = findRollout(String(id2), input.env);
|
|
1544
|
+
if (!file || codexSubagent(rolloutMeta(file))) return null;
|
|
1545
|
+
const r2 = {
|
|
1499
1546
|
session: { id: String(id2), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd },
|
|
1500
1547
|
events: [
|
|
1501
1548
|
{
|
|
@@ -1504,18 +1551,19 @@ var codex = {
|
|
|
1504
1551
|
harnessEvent: "notify"
|
|
1505
1552
|
}
|
|
1506
1553
|
],
|
|
1507
|
-
hints: {}
|
|
1554
|
+
hints: {},
|
|
1555
|
+
transcript: file
|
|
1508
1556
|
};
|
|
1509
|
-
|
|
1510
|
-
if (file) r3.transcript = file;
|
|
1511
|
-
return r3;
|
|
1557
|
+
return r2;
|
|
1512
1558
|
}
|
|
1513
1559
|
try {
|
|
1514
|
-
|
|
1560
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
1561
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1515
1562
|
} catch {
|
|
1516
1563
|
}
|
|
1517
1564
|
const id = s.session_id;
|
|
1518
1565
|
if (!id) return null;
|
|
1566
|
+
if (s.transcript_path === null) return null;
|
|
1519
1567
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
1520
1568
|
if (typeof s.model === "string") session.model = s.model;
|
|
1521
1569
|
const turn_id = typeof s.turn_id === "string" ? s.turn_id : void 0;
|
|
@@ -1586,9 +1634,10 @@ var codex = {
|
|
|
1586
1634
|
default:
|
|
1587
1635
|
return null;
|
|
1588
1636
|
}
|
|
1589
|
-
const
|
|
1590
|
-
if (typeof s.transcript_path === "string"
|
|
1591
|
-
|
|
1637
|
+
const r = { session, events, hints: {} };
|
|
1638
|
+
if (typeof s.transcript_path === "string" && !ofAnotherThread(s.transcript_path, String(id)))
|
|
1639
|
+
r.transcript = s.transcript_path;
|
|
1640
|
+
return r;
|
|
1592
1641
|
},
|
|
1593
1642
|
readTranscript(file, fromLine) {
|
|
1594
1643
|
let text = "";
|
|
@@ -1605,16 +1654,17 @@ var codex = {
|
|
|
1605
1654
|
return out;
|
|
1606
1655
|
},
|
|
1607
1656
|
backfill(sinceMs, env = process.env) {
|
|
1608
|
-
const
|
|
1657
|
+
const rows2 = [];
|
|
1609
1658
|
for (const f of recentFiles(sessionsDir(env), 3, sinceMs, (n) => /^rollout-.*\.jsonl$/.test(n))) {
|
|
1610
1659
|
const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(path5.basename(f));
|
|
1611
1660
|
if (!m?.[1]) continue;
|
|
1661
|
+
if (codexSubagent(rolloutMeta(f))) continue;
|
|
1612
1662
|
const facts = codexFacts(f);
|
|
1613
1663
|
if (!facts.started_at || !facts.last_at) continue;
|
|
1614
1664
|
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
1615
|
-
|
|
1665
|
+
rows2.push({ session: { id: m[1], ...rest }, started_at, last_at });
|
|
1616
1666
|
}
|
|
1617
|
-
return
|
|
1667
|
+
return rows2;
|
|
1618
1668
|
}
|
|
1619
1669
|
};
|
|
1620
1670
|
function prune3(o) {
|
|
@@ -1623,10 +1673,11 @@ function prune3(o) {
|
|
|
1623
1673
|
}
|
|
1624
1674
|
function codexFacts(file) {
|
|
1625
1675
|
const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
|
|
1626
|
-
|
|
1627
|
-
|
|
1676
|
+
let H = parseLines(head);
|
|
1677
|
+
let T = parseLines(tail);
|
|
1628
1678
|
const out = {};
|
|
1629
1679
|
const meta = H.find((j) => j.type === "session_meta" && j.payload);
|
|
1680
|
+
const boundary = codexReplayBoundary(meta);
|
|
1630
1681
|
if (meta?.payload) {
|
|
1631
1682
|
const p = meta.payload;
|
|
1632
1683
|
if (p.cwd) out.cwd = p.cwd;
|
|
@@ -1634,19 +1685,26 @@ function codexFacts(file) {
|
|
|
1634
1685
|
if (p.git?.branch) out.branch = p.git.branch;
|
|
1635
1686
|
if (p.git?.repository_url) out.repo = String(p.git.repository_url).replace(/\/\/[^/@]*@/, "//");
|
|
1636
1687
|
}
|
|
1688
|
+
if (boundary) {
|
|
1689
|
+
const h = H.findIndex(boundary);
|
|
1690
|
+
const t = T.findIndex(boundary);
|
|
1691
|
+
H = h < 0 ? [] : H.slice(h);
|
|
1692
|
+
const found = h >= 0 || t >= 0 || scanLines(file, (line) => boundary(parseLines([line])[0] ?? null) ? true : null, Number.POSITIVE_INFINITY);
|
|
1693
|
+
T = t >= 0 ? T.slice(t) : found ? T : [];
|
|
1694
|
+
}
|
|
1637
1695
|
const ctx = H.concat(T).reverse().find((j) => j.type === "turn_context" && j.payload?.model);
|
|
1638
1696
|
if (ctx?.payload?.model) out.model = ctx.payload.model;
|
|
1639
1697
|
for (const j of H) {
|
|
1640
1698
|
const p = j.payload;
|
|
1641
1699
|
if (j.type !== "response_item" || !p || p.type !== "message" || p.role !== "user") continue;
|
|
1642
1700
|
const text = (p.content ?? []).filter((b) => b && b.type === "input_text" && typeof b.text === "string").map((b) => b.text).join("\n").trim();
|
|
1643
|
-
if (!text ||
|
|
1644
|
-
const line = text.split("\n")
|
|
1701
|
+
if (!text || codexInjected(text)) continue;
|
|
1702
|
+
const line = text.split("\n")[0];
|
|
1645
1703
|
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
1646
1704
|
out.title_source = "first-ask";
|
|
1647
1705
|
break;
|
|
1648
1706
|
}
|
|
1649
|
-
const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
|
|
1707
|
+
const times = (boundary && meta ? [meta, ...H, ...T] : H.concat(T)).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
|
|
1650
1708
|
if (times.length) {
|
|
1651
1709
|
out.started_at = Math.min(...times);
|
|
1652
1710
|
out.last_at = Math.max(...times);
|
|
@@ -1654,11 +1712,432 @@ function codexFacts(file) {
|
|
|
1654
1712
|
return out;
|
|
1655
1713
|
}
|
|
1656
1714
|
|
|
1657
|
-
// ../core/src/adapters/
|
|
1658
|
-
import {
|
|
1659
|
-
import { existsSync as
|
|
1715
|
+
// ../core/src/adapters/devin.ts
|
|
1716
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
1717
|
+
import { existsSync as existsSync6, readlinkSync } from "node:fs";
|
|
1660
1718
|
import path6 from "node:path";
|
|
1661
|
-
|
|
1719
|
+
|
|
1720
|
+
// ../core/src/readers/sqlite.ts
|
|
1721
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
1722
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
1723
|
+
import { createRequire } from "node:module";
|
|
1724
|
+
function quiet(fn) {
|
|
1725
|
+
const original = process.emitWarning;
|
|
1726
|
+
try {
|
|
1727
|
+
process.emitWarning = ((warning, ...rest) => {
|
|
1728
|
+
const kind = typeof rest[0] === "string" ? rest[0] : rest[0]?.type;
|
|
1729
|
+
if (kind === "ExperimentalWarning" && /sqlite/i.test(String(warning))) return;
|
|
1730
|
+
original.call(process, warning, ...rest);
|
|
1731
|
+
});
|
|
1732
|
+
return fn();
|
|
1733
|
+
} finally {
|
|
1734
|
+
process.emitWarning = original;
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
var ctorCache;
|
|
1738
|
+
function dbCtor() {
|
|
1739
|
+
if (ctorCache !== void 0) return ctorCache;
|
|
1740
|
+
ctorCache = null;
|
|
1741
|
+
try {
|
|
1742
|
+
const req = createRequire(import.meta.url);
|
|
1743
|
+
const m = quiet(() => req("node:sqlite"));
|
|
1744
|
+
if (typeof m?.DatabaseSync === "function") ctorCache = m.DatabaseSync;
|
|
1745
|
+
} catch {
|
|
1746
|
+
}
|
|
1747
|
+
return ctorCache;
|
|
1748
|
+
}
|
|
1749
|
+
function plain(row) {
|
|
1750
|
+
if (!row || typeof row !== "object") return null;
|
|
1751
|
+
const out = {};
|
|
1752
|
+
for (const [k, v] of Object.entries(row)) {
|
|
1753
|
+
if (typeof v === "bigint") out[k] = Number(v);
|
|
1754
|
+
else if (v instanceof Uint8Array) out[k] = null;
|
|
1755
|
+
else out[k] = v;
|
|
1756
|
+
}
|
|
1757
|
+
return out;
|
|
1758
|
+
}
|
|
1759
|
+
var rows = (v) => {
|
|
1760
|
+
if (!Array.isArray(v)) return null;
|
|
1761
|
+
const out = [];
|
|
1762
|
+
for (const r of v) {
|
|
1763
|
+
const p = plain(r);
|
|
1764
|
+
if (p) out.push(p);
|
|
1765
|
+
}
|
|
1766
|
+
return out;
|
|
1767
|
+
};
|
|
1768
|
+
function viaNode(file, sql, params, o) {
|
|
1769
|
+
const C = dbCtor();
|
|
1770
|
+
if (!C) return null;
|
|
1771
|
+
let db;
|
|
1772
|
+
try {
|
|
1773
|
+
db = quiet(() => new C(file, { readOnly: true, timeout: o.timeoutMs }));
|
|
1774
|
+
} catch {
|
|
1775
|
+
return null;
|
|
1776
|
+
}
|
|
1777
|
+
try {
|
|
1778
|
+
try {
|
|
1779
|
+
db.exec(`PRAGMA busy_timeout = ${Math.round(o.timeoutMs)}`);
|
|
1780
|
+
} catch {
|
|
1781
|
+
}
|
|
1782
|
+
return quiet(() => rows(db.prepare(sql).all(...params)));
|
|
1783
|
+
} catch {
|
|
1784
|
+
return null;
|
|
1785
|
+
} finally {
|
|
1786
|
+
try {
|
|
1787
|
+
db.close();
|
|
1788
|
+
} catch {
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
var CHILD = `const { DatabaseSync } = require("node:sqlite");
|
|
1793
|
+
const [, file, sql, params, ms] = process.argv;
|
|
1794
|
+
const db = new DatabaseSync(file, { readOnly: true, timeout: Number(ms) });
|
|
1795
|
+
try { db.exec("PRAGMA busy_timeout = " + Number(ms)); } catch {}
|
|
1796
|
+
const out = db.prepare(sql).all(...JSON.parse(params));
|
|
1797
|
+
db.close();
|
|
1798
|
+
process.stdout.write(JSON.stringify(out));`;
|
|
1799
|
+
function viaChild(file, sql, params, o) {
|
|
1800
|
+
try {
|
|
1801
|
+
const out = execFileSync3(
|
|
1802
|
+
process.execPath,
|
|
1803
|
+
["--experimental-sqlite", "--no-warnings", "-e", CHILD, file, sql, JSON.stringify(params), String(o.timeoutMs)],
|
|
1804
|
+
{
|
|
1805
|
+
encoding: "utf8",
|
|
1806
|
+
timeout: o.timeoutMs + 3e3,
|
|
1807
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1808
|
+
maxBuffer: o.maxBuffer,
|
|
1809
|
+
windowsHide: true
|
|
1810
|
+
}
|
|
1811
|
+
);
|
|
1812
|
+
return rows(JSON.parse(out || "null"));
|
|
1813
|
+
} catch {
|
|
1814
|
+
return null;
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
var SAFE = /^[A-Za-z0-9 ._:@/+-]{0,256}$/;
|
|
1818
|
+
function inlineParams(sql, params) {
|
|
1819
|
+
if (sql.includes("'")) return null;
|
|
1820
|
+
const holes = sql.split("?").length - 1;
|
|
1821
|
+
if (holes !== params.length) return null;
|
|
1822
|
+
let i = 0;
|
|
1823
|
+
let bad = false;
|
|
1824
|
+
const out = sql.replace(/\?/g, () => {
|
|
1825
|
+
const p = params[i++];
|
|
1826
|
+
if (typeof p === "number") {
|
|
1827
|
+
if (!Number.isFinite(p)) {
|
|
1828
|
+
bad = true;
|
|
1829
|
+
return "NULL";
|
|
1830
|
+
}
|
|
1831
|
+
return String(p);
|
|
1832
|
+
}
|
|
1833
|
+
if (typeof p !== "string" || !SAFE.test(p)) {
|
|
1834
|
+
bad = true;
|
|
1835
|
+
return "NULL";
|
|
1836
|
+
}
|
|
1837
|
+
return `'${p}'`;
|
|
1838
|
+
});
|
|
1839
|
+
return bad ? null : out;
|
|
1840
|
+
}
|
|
1841
|
+
function viaCli(file, sql, params, o) {
|
|
1842
|
+
const text = inlineParams(sql, params);
|
|
1843
|
+
if (!text) return null;
|
|
1844
|
+
try {
|
|
1845
|
+
const out = execFileSync3("sqlite3", ["-readonly", "-json", file, text], {
|
|
1846
|
+
encoding: "utf8",
|
|
1847
|
+
timeout: o.timeoutMs + 3e3,
|
|
1848
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1849
|
+
maxBuffer: o.maxBuffer,
|
|
1850
|
+
windowsHide: true
|
|
1851
|
+
}).trim();
|
|
1852
|
+
if (!out) return [];
|
|
1853
|
+
return rows(JSON.parse(out));
|
|
1854
|
+
} catch {
|
|
1855
|
+
return null;
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
function querySqlite(file, sql, params = [], opts = {}) {
|
|
1859
|
+
const o = {
|
|
1860
|
+
timeoutMs: opts.timeoutMs ?? 2e3,
|
|
1861
|
+
engine: opts.engine ?? "auto",
|
|
1862
|
+
maxBuffer: opts.maxBuffer ?? 32 * 1024 * 1024
|
|
1863
|
+
};
|
|
1864
|
+
if (!file) return null;
|
|
1865
|
+
try {
|
|
1866
|
+
if (!existsSync5(file)) return null;
|
|
1867
|
+
} catch {
|
|
1868
|
+
return null;
|
|
1869
|
+
}
|
|
1870
|
+
const ways = o.engine === "auto" ? [viaNode, viaChild, viaCli] : o.engine === "node" ? [viaNode] : o.engine === "child" ? [viaChild] : [viaCli];
|
|
1871
|
+
for (const way of ways) {
|
|
1872
|
+
const r = way(file, sql, params, o);
|
|
1873
|
+
if (r) return r;
|
|
1874
|
+
}
|
|
1875
|
+
return null;
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
// ../core/src/adapters/devin.ts
|
|
1879
|
+
var NAME4 = "devin";
|
|
1880
|
+
var DEVIN_EVENTS = [
|
|
1881
|
+
"SessionStart",
|
|
1882
|
+
"UserPromptSubmit",
|
|
1883
|
+
"PreToolUse",
|
|
1884
|
+
"PostToolUse",
|
|
1885
|
+
"PermissionRequest",
|
|
1886
|
+
"Stop",
|
|
1887
|
+
"PostCompaction",
|
|
1888
|
+
"SessionEnd"
|
|
1889
|
+
];
|
|
1890
|
+
var OPTS3 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
1891
|
+
var eventsFor3 = (lean) => lean ? DEVIN_EVENTS.filter((e) => e !== "PreToolUse") : [...DEVIN_EVENTS];
|
|
1892
|
+
var INSTALL_NOTE = "Devin drops a whole hooks file on one event name it doesn't know; `/hooks` in `devin` lists what loaded";
|
|
1893
|
+
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";
|
|
1894
|
+
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)`;
|
|
1895
|
+
function foreignEvent(file) {
|
|
1896
|
+
const hooks = readJson(file).hooks;
|
|
1897
|
+
if (!hooks || typeof hooks !== "object" || Array.isArray(hooks)) return void 0;
|
|
1898
|
+
const ok = DEVIN_EVENTS;
|
|
1899
|
+
return Object.keys(hooks).find((k) => !ok.includes(k));
|
|
1900
|
+
}
|
|
1901
|
+
var homeOf = (env) => env.HOME || homeDir();
|
|
1902
|
+
var appData = (env) => env.APPDATA || path6.join(homeOf(env), "AppData", "Roaming");
|
|
1903
|
+
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");
|
|
1904
|
+
var configFile2 = (env) => path6.join(devinConfigDir(env), "config.json");
|
|
1905
|
+
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");
|
|
1906
|
+
var sessionsDb = (env) => path6.join(devinDataDir(env), "sessions.db");
|
|
1907
|
+
var SESSION_ID = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
1908
|
+
var validTool4 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
1909
|
+
var endReason3 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
|
|
1910
|
+
var pickSource2 = (v) => typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
|
|
1911
|
+
function errText2(v) {
|
|
1912
|
+
if (typeof v === "string" && v) return v.slice(0, 2e3);
|
|
1913
|
+
if (v && typeof v === "object") {
|
|
1914
|
+
const e = v;
|
|
1915
|
+
if (typeof e.message === "string" && e.message) return e.message.slice(0, 2e3);
|
|
1916
|
+
if (typeof e.error === "string" && e.error) return e.error.slice(0, 2e3);
|
|
1917
|
+
}
|
|
1918
|
+
return void 0;
|
|
1919
|
+
}
|
|
1920
|
+
var cut = (s, n) => s.length > n ? `${s.slice(0, n).replace(/\s+\S*$/, "")}\u2026` : s;
|
|
1921
|
+
var devin = {
|
|
1922
|
+
name: NAME4,
|
|
1923
|
+
events: DEVIN_EVENTS,
|
|
1924
|
+
detect: (env = process.env) => existsSync6(devinConfigDir(env)) || existsSync6(devinDataDir(env)),
|
|
1925
|
+
configFiles: (env = process.env) => [configFile2(env)],
|
|
1926
|
+
install: (cmd, env = process.env, opts = {}) => {
|
|
1927
|
+
const file = configFile2(env);
|
|
1928
|
+
const r = installClaudeShaped(file, NAME4, eventsFor3(opts.lean), cmd, OPTS3);
|
|
1929
|
+
if (r.skipped) return [r];
|
|
1930
|
+
const bad = foreignEvent(file);
|
|
1931
|
+
return [{ ...r, note: bad ? deadFileNote(bad) : INSTALL_NOTE }];
|
|
1932
|
+
},
|
|
1933
|
+
uninstall: (env = process.env, opts = {}) => [
|
|
1934
|
+
uninstallClaudeShaped(configFile2(env), DEVIN_EVENTS, { created: opts.created?.includes(configFile2(env)) })
|
|
1935
|
+
],
|
|
1936
|
+
installed: (cmd, env = process.env, opts = {}) => {
|
|
1937
|
+
const file = configFile2(env);
|
|
1938
|
+
const state = installedClaudeShaped(file, NAME4, eventsFor3(opts.lean), cmd, OPTS3);
|
|
1939
|
+
const bad = state === "missing" ? void 0 : foreignEvent(file);
|
|
1940
|
+
return [{ file, state: bad ? "inactive" : state, note: bad ? deadFileNote(bad) : CLAUDE_NOTE }];
|
|
1941
|
+
},
|
|
1942
|
+
fromHook(input) {
|
|
1943
|
+
const event = input.argv[1] ?? "";
|
|
1944
|
+
let s = {};
|
|
1945
|
+
try {
|
|
1946
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
1947
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1948
|
+
} catch {
|
|
1949
|
+
}
|
|
1950
|
+
const id = typeof s.session_id === "string" ? s.session_id : "";
|
|
1951
|
+
if (!id) return null;
|
|
1952
|
+
const session = { id, cwd: input.env.DEVIN_PROJECT_DIR ?? input.cwd };
|
|
1953
|
+
const turn_id = typeof s.prompt_id === "string" ? s.prompt_id : void 0;
|
|
1954
|
+
const tool = validTool4(s.tool_name) ? s.tool_name : void 0;
|
|
1955
|
+
const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
|
|
1956
|
+
const response = s.tool_response && typeof s.tool_response === "object" && !Array.isArray(s.tool_response) ? s.tool_response : void 0;
|
|
1957
|
+
const permissionId = () => `perm-${createHash2("sha256").update(`${turn_id ?? ""}|${tool ?? ""}|${JSON.stringify(s.tool_input ?? null)}`).digest("hex").slice(0, 16)}`;
|
|
1958
|
+
const ev = (type, data) => ({ type, data: prune4(data), harnessEvent: event });
|
|
1959
|
+
const events = [];
|
|
1960
|
+
switch (event) {
|
|
1961
|
+
case "SessionStart":
|
|
1962
|
+
events.push(ev("session.started", { source: pickSource2(s.source) }));
|
|
1963
|
+
break;
|
|
1964
|
+
case "UserPromptSubmit":
|
|
1965
|
+
events.push(
|
|
1966
|
+
ev("turn.started", {
|
|
1967
|
+
turn_id,
|
|
1968
|
+
prompt_chars: typeof s.prompt === "string" ? [...s.prompt].length : void 0
|
|
1969
|
+
})
|
|
1970
|
+
);
|
|
1971
|
+
break;
|
|
1972
|
+
case "PreToolUse":
|
|
1973
|
+
if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, input: s.tool_input }));
|
|
1974
|
+
break;
|
|
1975
|
+
case "PostToolUse":
|
|
1976
|
+
if (tool)
|
|
1977
|
+
events.push(
|
|
1978
|
+
ev("tool.ended", {
|
|
1979
|
+
tool,
|
|
1980
|
+
call_id,
|
|
1981
|
+
turn_id,
|
|
1982
|
+
// `ok` is required on tool.ended (PROTOCOL.md §events), so a response
|
|
1983
|
+
// that carries no success flag at all reads as "nothing said it failed".
|
|
1984
|
+
ok: response?.success !== false,
|
|
1985
|
+
error: errText2(response?.error),
|
|
1986
|
+
input: s.tool_input,
|
|
1987
|
+
output: response ? response.output : s.tool_response
|
|
1988
|
+
})
|
|
1989
|
+
);
|
|
1990
|
+
break;
|
|
1991
|
+
case "PermissionRequest":
|
|
1992
|
+
events.push(ev("attention.needed", { attention_id: call_id ?? permissionId(), kind: "permission", tool }));
|
|
1993
|
+
break;
|
|
1994
|
+
case "Stop":
|
|
1995
|
+
events.push(ev("turn.ended", { turn_id, reason: "stop" }));
|
|
1996
|
+
break;
|
|
1997
|
+
case "PostCompaction":
|
|
1998
|
+
events.push(ev("context.compacted", { trigger: "auto" }));
|
|
1999
|
+
break;
|
|
2000
|
+
case "SessionEnd":
|
|
2001
|
+
events.push(ev("session.ended", { reason: endReason3(s.reason) }));
|
|
2002
|
+
break;
|
|
2003
|
+
default:
|
|
2004
|
+
return null;
|
|
2005
|
+
}
|
|
2006
|
+
return {
|
|
2007
|
+
session,
|
|
2008
|
+
events,
|
|
2009
|
+
hints: {},
|
|
2010
|
+
transcript: transcriptRef(sessionsDb(input.env), id)
|
|
2011
|
+
};
|
|
2012
|
+
},
|
|
2013
|
+
readTranscript(file, fromLine) {
|
|
2014
|
+
const ref = splitRef(file);
|
|
2015
|
+
if (!ref) return { turns: [], line: fromLine };
|
|
2016
|
+
return readDevinChain(ref.db, ref.session, fromLine);
|
|
2017
|
+
},
|
|
2018
|
+
facts(session, transcript, _hints, _state, env = process.env) {
|
|
2019
|
+
const out = {};
|
|
2020
|
+
const version = devinVersion(env);
|
|
2021
|
+
if (version) out.harness_version = version;
|
|
2022
|
+
const account = devinAccount(env);
|
|
2023
|
+
if (account) out.account_id = account;
|
|
2024
|
+
const ref = splitRef(transcript) ?? { db: sessionsDb(env), session: session.id };
|
|
2025
|
+
if (!SESSION_ID.test(ref.session)) return out;
|
|
2026
|
+
const rows2 = querySqlite(ref.db, SESSION_SQL, [ref.session]);
|
|
2027
|
+
const row = rows2?.[0];
|
|
2028
|
+
if (!row) return out;
|
|
2029
|
+
Object.assign(out, sessionFacts(row));
|
|
2030
|
+
return out;
|
|
2031
|
+
},
|
|
2032
|
+
backfill(sinceMs, env = process.env) {
|
|
2033
|
+
const rows2 = querySqlite(sessionsDb(env), BACKFILL_SQL, ["text", Math.floor(sinceMs / 1e3)]);
|
|
2034
|
+
if (!rows2) return [];
|
|
2035
|
+
const out = [];
|
|
2036
|
+
for (const row of rows2) {
|
|
2037
|
+
const id = String(row.id);
|
|
2038
|
+
if (!SESSION_ID.test(id)) continue;
|
|
2039
|
+
const { started_at, last_at, ...rest } = sessionFacts(row);
|
|
2040
|
+
if (!started_at || !last_at) continue;
|
|
2041
|
+
out.push({ session: { id, ...rest }, started_at, last_at });
|
|
2042
|
+
}
|
|
2043
|
+
return out;
|
|
2044
|
+
}
|
|
2045
|
+
};
|
|
2046
|
+
function prune4(o) {
|
|
2047
|
+
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
2048
|
+
return o;
|
|
2049
|
+
}
|
|
2050
|
+
var transcriptRef = (db, session) => `${db}#${session}`;
|
|
2051
|
+
function splitRef(ref) {
|
|
2052
|
+
if (!ref) return null;
|
|
2053
|
+
const at = ref.lastIndexOf("#");
|
|
2054
|
+
if (at <= 0) return null;
|
|
2055
|
+
const db = ref.slice(0, at);
|
|
2056
|
+
const session = ref.slice(at + 1);
|
|
2057
|
+
return db && SESSION_ID.test(session) ? { db, session } : null;
|
|
2058
|
+
}
|
|
2059
|
+
var CHAIN_LIMIT = 4e3;
|
|
2060
|
+
var CHAIN_SQL = `WITH RECURSIVE chain(node_id, parent_node_id, chat_message, created_at) AS (
|
|
2061
|
+
SELECT node_id, parent_node_id, chat_message, created_at FROM message_nodes
|
|
2062
|
+
WHERE session_id = ? AND node_id = (SELECT main_chain_id FROM sessions WHERE id = ?)
|
|
2063
|
+
UNION ALL
|
|
2064
|
+
SELECT m.node_id, m.parent_node_id, m.chat_message, m.created_at FROM message_nodes m
|
|
2065
|
+
JOIN chain c ON m.node_id = c.parent_node_id AND m.session_id = ? AND m.node_id >= ?
|
|
2066
|
+
)
|
|
2067
|
+
SELECT node_id, chat_message, created_at FROM chain ORDER BY node_id DESC LIMIT ${CHAIN_LIMIT}`;
|
|
2068
|
+
var SESSION_FIELDS = `s.model, s.working_directory, s.created_at, s.last_activity_at,
|
|
2069
|
+
(SELECT p.content FROM prompt_history p WHERE p.session_id = s.id AND p.is_shell = 0
|
|
2070
|
+
ORDER BY p.timestamp ASC, p.id ASC LIMIT 1) AS first_ask`;
|
|
2071
|
+
var SESSION_SQL = `SELECT ${SESSION_FIELDS} FROM sessions s WHERE s.id = ? LIMIT 1`;
|
|
2072
|
+
var BACKFILL_SQL = `SELECT s.id, ${SESSION_FIELDS} FROM sessions s
|
|
2073
|
+
WHERE s.hidden = 0 AND typeof(s.id) = ? AND s.last_activity_at >= ?
|
|
2074
|
+
ORDER BY s.last_activity_at DESC LIMIT 500`;
|
|
2075
|
+
function sessionFacts(row) {
|
|
2076
|
+
const out = {};
|
|
2077
|
+
if (typeof row.model === "string" && row.model) out.model = row.model;
|
|
2078
|
+
if (typeof row.working_directory === "string" && path6.isAbsolute(row.working_directory))
|
|
2079
|
+
out.cwd = row.working_directory;
|
|
2080
|
+
const started = Number(row.created_at);
|
|
2081
|
+
const last = Number(row.last_activity_at);
|
|
2082
|
+
if (Number.isFinite(started) && started > 0) out.started_at = Math.round(started * 1e3);
|
|
2083
|
+
if (Number.isFinite(last) && last > 0) out.last_at = Math.round(last * 1e3);
|
|
2084
|
+
if (typeof row.first_ask === "string") {
|
|
2085
|
+
const line = row.first_ask.split("\n").find((l) => l.trim()) ?? "";
|
|
2086
|
+
if (line.trim()) {
|
|
2087
|
+
out.title = cut(line.trim(), 80);
|
|
2088
|
+
out.title_source = "first-ask";
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
return out;
|
|
2092
|
+
}
|
|
2093
|
+
function readDevinChain(db, session, fromLine) {
|
|
2094
|
+
if (!SESSION_ID.test(session)) return { turns: [], line: fromLine };
|
|
2095
|
+
const floor = Number.isFinite(fromLine) && fromLine > 0 ? Math.floor(fromLine) : 0;
|
|
2096
|
+
const rows2 = querySqlite(db, CHAIN_SQL, [session, session, session, floor]);
|
|
2097
|
+
if (!rows2?.length) return { turns: [], line: fromLine };
|
|
2098
|
+
const msgs = [];
|
|
2099
|
+
let end = fromLine;
|
|
2100
|
+
for (const row of rows2) {
|
|
2101
|
+
const node = Number(row.node_id);
|
|
2102
|
+
if (!Number.isFinite(node)) continue;
|
|
2103
|
+
if (node + 1 > end) end = node + 1;
|
|
2104
|
+
if (node < fromLine) continue;
|
|
2105
|
+
let j;
|
|
2106
|
+
try {
|
|
2107
|
+
j = JSON.parse(String(row.chat_message ?? ""));
|
|
2108
|
+
} catch {
|
|
2109
|
+
continue;
|
|
2110
|
+
}
|
|
2111
|
+
const text = typeof j.content === "string" ? j.content.trim() : "";
|
|
2112
|
+
if (!text) continue;
|
|
2113
|
+
const secs = Number(row.created_at);
|
|
2114
|
+
const at = Date.parse(String(j.metadata?.created_at ?? "")) || (Number.isFinite(secs) && secs > 0 ? Math.round(secs * 1e3) : 0) || Date.now();
|
|
2115
|
+
if (j.role === "user" && j.metadata?.is_user_input === true) msgs.push({ role: "user", text, at, line: node });
|
|
2116
|
+
else if (j.role === "assistant") msgs.push({ role: "assistant", text, at, line: node });
|
|
2117
|
+
}
|
|
2118
|
+
msgs.sort((a, b) => a.line - b.line);
|
|
2119
|
+
return pairTurns(msgs, end);
|
|
2120
|
+
}
|
|
2121
|
+
function devinVersion(env = process.env) {
|
|
2122
|
+
try {
|
|
2123
|
+
const target = readlinkSync(path6.join(devinDataDir(env), "_versions", "current"));
|
|
2124
|
+
const name = path6.basename(target);
|
|
2125
|
+
return /^[0-9][0-9A-Za-z.+-]{0,63}$/.test(name) ? name : void 0;
|
|
2126
|
+
} catch {
|
|
2127
|
+
return void 0;
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
function devinAccount(env = process.env) {
|
|
2131
|
+
const d = readJson(configFile2(env)).devin;
|
|
2132
|
+
const id = d && typeof d === "object" ? d.org_id : void 0;
|
|
2133
|
+
return typeof id === "string" && /^[A-Za-z0-9_.:-]{1,128}$/.test(id) ? id : void 0;
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
// ../core/src/adapters/gemini-cli.ts
|
|
2137
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
2138
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
|
|
2139
|
+
import path7 from "node:path";
|
|
2140
|
+
var NAME5 = "gemini-cli";
|
|
1662
2141
|
var GEMINI_EVENTS = [
|
|
1663
2142
|
"SessionStart",
|
|
1664
2143
|
"BeforeAgent",
|
|
@@ -1669,11 +2148,11 @@ var GEMINI_EVENTS = [
|
|
|
1669
2148
|
"PreCompress",
|
|
1670
2149
|
"SessionEnd"
|
|
1671
2150
|
];
|
|
1672
|
-
var
|
|
1673
|
-
var geminiDir = (env) => env.GEMINI_CLI_HOME ||
|
|
1674
|
-
var settings = (env) =>
|
|
1675
|
-
var
|
|
1676
|
-
var
|
|
2151
|
+
var OPTS4 = { matcherFor: (e) => /Tool/.test(e) ? ".*" : void 0, timeout: 5e3 };
|
|
2152
|
+
var geminiDir = (env) => env.GEMINI_CLI_HOME || path7.join(homeDir(), ".gemini");
|
|
2153
|
+
var settings = (env) => path7.join(geminiDir(env), "settings.json");
|
|
2154
|
+
var validTool5 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
2155
|
+
var eventsFor4 = (lean) => lean ? GEMINI_EVENTS.filter((e) => e !== "BeforeTool") : [...GEMINI_EVENTS];
|
|
1677
2156
|
function geminiHooksActive(settingsFile) {
|
|
1678
2157
|
let tools;
|
|
1679
2158
|
try {
|
|
@@ -1685,7 +2164,7 @@ function geminiHooksActive(settingsFile) {
|
|
|
1685
2164
|
return { active: false, note: "inactive: tools.enableHooks is false in settings.json" };
|
|
1686
2165
|
let version = "";
|
|
1687
2166
|
try {
|
|
1688
|
-
version =
|
|
2167
|
+
version = execFileSync4("gemini", ["--version"], {
|
|
1689
2168
|
encoding: "utf8",
|
|
1690
2169
|
timeout: 4e3,
|
|
1691
2170
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -1704,21 +2183,21 @@ function geminiHooksActive(settingsFile) {
|
|
|
1704
2183
|
};
|
|
1705
2184
|
}
|
|
1706
2185
|
var geminiCli = {
|
|
1707
|
-
name:
|
|
2186
|
+
name: NAME5,
|
|
1708
2187
|
events: GEMINI_EVENTS,
|
|
1709
2188
|
// ~/.gemini also belongs to Antigravity; Gemini CLI is here when its settings or its chat logs exist.
|
|
1710
|
-
detect: (env = process.env) =>
|
|
2189
|
+
detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(path7.join(geminiDir(env), "tmp")),
|
|
1711
2190
|
configFiles: (env = process.env) => [settings(env)],
|
|
1712
2191
|
install: (cmd, env = process.env, opts = {}) => {
|
|
1713
|
-
const
|
|
2192
|
+
const r = installClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
|
|
1714
2193
|
const a = geminiHooksActive(settings(env));
|
|
1715
|
-
return [a.note && !
|
|
2194
|
+
return [a.note && !r.skipped ? { ...r, note: [r.note, a.note].filter(Boolean).join("; ") } : r];
|
|
1716
2195
|
},
|
|
1717
2196
|
uninstall: (env = process.env, opts = {}) => [
|
|
1718
2197
|
uninstallClaudeShaped(settings(env), GEMINI_EVENTS, { created: opts.created?.includes(settings(env)) })
|
|
1719
2198
|
],
|
|
1720
2199
|
installed: (cmd, env = process.env, opts = {}) => {
|
|
1721
|
-
const state = installedClaudeShaped(settings(env),
|
|
2200
|
+
const state = installedClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
|
|
1722
2201
|
const a = geminiHooksActive(settings(env));
|
|
1723
2202
|
return [
|
|
1724
2203
|
{
|
|
@@ -1732,14 +2211,15 @@ var geminiCli = {
|
|
|
1732
2211
|
const event = input.argv[1] ?? "";
|
|
1733
2212
|
let s = {};
|
|
1734
2213
|
try {
|
|
1735
|
-
|
|
2214
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
2215
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1736
2216
|
} catch {
|
|
1737
2217
|
}
|
|
1738
2218
|
const id = s.session_id;
|
|
1739
2219
|
if (!id) return null;
|
|
1740
2220
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
1741
|
-
const tool =
|
|
1742
|
-
const ev = (type, data) => ({ type, data:
|
|
2221
|
+
const tool = validTool5(s.tool_name) ? s.tool_name : void 0;
|
|
2222
|
+
const ev = (type, data) => ({ type, data: prune5(data), harnessEvent: event });
|
|
1743
2223
|
const events = [];
|
|
1744
2224
|
switch (event) {
|
|
1745
2225
|
case "SessionStart":
|
|
@@ -1797,9 +2277,9 @@ var geminiCli = {
|
|
|
1797
2277
|
default:
|
|
1798
2278
|
return null;
|
|
1799
2279
|
}
|
|
1800
|
-
const
|
|
1801
|
-
if (typeof s.transcript_path === "string")
|
|
1802
|
-
return
|
|
2280
|
+
const r = { session, events, hints: {} };
|
|
2281
|
+
if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
|
|
2282
|
+
return r;
|
|
1803
2283
|
},
|
|
1804
2284
|
readTranscript(file, fromLine) {
|
|
1805
2285
|
let text = "";
|
|
@@ -1820,7 +2300,7 @@ var geminiCli = {
|
|
|
1820
2300
|
const raw = first ? first.content ?? first.message ?? first.text : void 0;
|
|
1821
2301
|
const text = typeof raw === "string" ? raw.trim() : "";
|
|
1822
2302
|
if (text) {
|
|
1823
|
-
const line = text.split("\n")
|
|
2303
|
+
const line = text.split("\n")[0];
|
|
1824
2304
|
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
1825
2305
|
out.title_source = "first-ask";
|
|
1826
2306
|
}
|
|
@@ -1834,13 +2314,13 @@ var geminiCli = {
|
|
|
1834
2314
|
backfill: () => []
|
|
1835
2315
|
// chat logs are keyed by a project hash, not a session id we can name; the first hook of a resumed session reports it
|
|
1836
2316
|
};
|
|
1837
|
-
function
|
|
2317
|
+
function prune5(o) {
|
|
1838
2318
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
1839
2319
|
return o;
|
|
1840
2320
|
}
|
|
1841
2321
|
|
|
1842
2322
|
// ../core/src/adapters/index.ts
|
|
1843
|
-
var ADAPTERS = [claudeCode, codex, geminiCli, antigravity];
|
|
2323
|
+
var ADAPTERS = [claudeCode, codex, geminiCli, antigravity, devin];
|
|
1844
2324
|
var adapterByName = (name) => ADAPTERS.find((a) => a.name === name);
|
|
1845
2325
|
|
|
1846
2326
|
// src/index.ts
|
|
@@ -1849,16 +2329,16 @@ function matches(expect, got, pathStr = "") {
|
|
|
1849
2329
|
if (Array.isArray(expect)) {
|
|
1850
2330
|
if (!Array.isArray(got) || got.length !== expect.length) return `${pathStr}: expected ${expect.length} items`;
|
|
1851
2331
|
for (let i = 0; i < expect.length; i++) {
|
|
1852
|
-
const
|
|
1853
|
-
if (
|
|
2332
|
+
const r = matches(expect[i], got[i], `${pathStr}[${i}]`);
|
|
2333
|
+
if (r) return r;
|
|
1854
2334
|
}
|
|
1855
2335
|
return null;
|
|
1856
2336
|
}
|
|
1857
2337
|
if (expect && typeof expect === "object") {
|
|
1858
2338
|
if (!got || typeof got !== "object") return `${pathStr}: expected an object`;
|
|
1859
2339
|
for (const [k, v] of Object.entries(expect)) {
|
|
1860
|
-
const
|
|
1861
|
-
if (
|
|
2340
|
+
const r = matches(v, got[k], `${pathStr}.${k}`);
|
|
2341
|
+
if (r) return r;
|
|
1862
2342
|
}
|
|
1863
2343
|
return null;
|
|
1864
2344
|
}
|
|
@@ -1873,51 +2353,64 @@ async function runAdapterFixtures(files) {
|
|
|
1873
2353
|
results.push({ file, ok: false, why: `no adapter ${fx.harness}` });
|
|
1874
2354
|
continue;
|
|
1875
2355
|
}
|
|
1876
|
-
const
|
|
2356
|
+
const r = adapter.fromHook({
|
|
1877
2357
|
argv: fx.input.argv,
|
|
1878
2358
|
stdin: fx.input.stdin,
|
|
1879
2359
|
env: fx.input.env ?? {},
|
|
1880
2360
|
cwd: fx.input.cwd ?? "/"
|
|
1881
2361
|
});
|
|
1882
|
-
if (!
|
|
1883
|
-
results.push({ file, ok: false, why: "fromHook returned null" });
|
|
2362
|
+
if (!r) {
|
|
2363
|
+
results.push(fx.expect.length ? { file, ok: false, why: "fromHook returned null" } : { file, ok: true });
|
|
1884
2364
|
continue;
|
|
1885
2365
|
}
|
|
1886
2366
|
let why = matches(
|
|
1887
2367
|
fx.expect.map((e) => e.type),
|
|
1888
|
-
|
|
2368
|
+
r.events.map((e) => e.type),
|
|
1889
2369
|
"types"
|
|
1890
2370
|
);
|
|
1891
2371
|
if (!why)
|
|
1892
2372
|
for (const [i, e] of fx.expect.entries()) {
|
|
1893
|
-
const got =
|
|
1894
|
-
why = matches(e.harness.event, got
|
|
2373
|
+
const got = r.events[i];
|
|
2374
|
+
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`);
|
|
1895
2375
|
if (why) break;
|
|
1896
2376
|
}
|
|
1897
2377
|
results.push(why ? { file, ok: false, why } : { file, ok: true });
|
|
1898
2378
|
}
|
|
1899
|
-
const failed = results.filter((
|
|
1900
|
-
const table = results.map((
|
|
2379
|
+
const failed = results.filter((r) => !r.ok).length;
|
|
2380
|
+
const table = results.map((r) => `${r.ok ? "ok " : "FAIL"} ${r.file}${r.why ? ` \u2014 ${r.why}` : ""}`).join("\n");
|
|
1901
2381
|
return { results, failed, table };
|
|
1902
2382
|
}
|
|
1903
2383
|
|
|
1904
2384
|
// src/cli.ts
|
|
1905
|
-
var target = process.argv[2];
|
|
1906
|
-
if (!target) {
|
|
1907
|
-
process.stdout.write("usage: sessionpipe-conformance <conformance/harness dir | https://receiver>\n");
|
|
1908
|
-
process.exit(2);
|
|
1909
|
-
}
|
|
1910
|
-
if (/^https?:\/\//.test(target)) {
|
|
1911
|
-
process.stdout.write("receiver scenarios land in M3\n");
|
|
1912
|
-
process.exit(2);
|
|
1913
|
-
}
|
|
1914
2385
|
var walk = (d) => readdirSync5(d).flatMap((n) => {
|
|
1915
|
-
const p =
|
|
2386
|
+
const p = path8.join(d, n);
|
|
1916
2387
|
return statSync4(p).isDirectory() ? walk(p) : p.endsWith(".json") ? [p] : [];
|
|
1917
2388
|
});
|
|
1918
|
-
|
|
1919
|
-
|
|
2389
|
+
async function main(argv, io = process) {
|
|
2390
|
+
const target = argv[0];
|
|
2391
|
+
if (!target) {
|
|
2392
|
+
io.stdout.write("usage: sessionpipe-conformance <conformance/harness dir | https://receiver>\n");
|
|
2393
|
+
return 2;
|
|
2394
|
+
}
|
|
2395
|
+
if (/^https?:\/\//.test(target)) {
|
|
2396
|
+
io.stdout.write("receiver scenarios land in M3\n");
|
|
2397
|
+
return 2;
|
|
2398
|
+
}
|
|
2399
|
+
const r = await runAdapterFixtures(walk(target));
|
|
2400
|
+
io.stdout.write(`${r.table}
|
|
1920
2401
|
${r.results.length - r.failed}/${r.results.length} passed
|
|
1921
2402
|
`);
|
|
1922
|
-
|
|
2403
|
+
return r.failed ? 1 : 0;
|
|
2404
|
+
}
|
|
2405
|
+
function isMain() {
|
|
2406
|
+
try {
|
|
2407
|
+
return !!process.argv[1] && import.meta.url === pathToFileURL(realpathSync2(process.argv[1])).href;
|
|
2408
|
+
} catch {
|
|
2409
|
+
return false;
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
if (isMain()) process.exit(await main(process.argv.slice(2)));
|
|
2413
|
+
export {
|
|
2414
|
+
main
|
|
2415
|
+
};
|
|
1923
2416
|
//# sourceMappingURL=cli.js.map
|