sessionpipe-conformance 0.7.0 → 0.10.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 +713 -162
- package/dist/cli.js.map +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +647 -110
- 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.10.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,6 +202,9 @@ 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));
|
|
@@ -180,7 +214,10 @@ function codexInjected(text) {
|
|
|
180
214
|
}
|
|
181
215
|
function readCodex(lines, from) {
|
|
182
216
|
const msgs = [];
|
|
183
|
-
|
|
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++) {
|
|
184
221
|
const j = lines[i] ? parse(lines[i]) : null;
|
|
185
222
|
if (!j || j.type !== "response_item") continue;
|
|
186
223
|
const p = j.payload;
|
|
@@ -218,7 +255,7 @@ function readAntigravity(lines, from) {
|
|
|
218
255
|
const at = Date.parse(String(j.created_at ?? j.timestamp ?? "")) || Date.now();
|
|
219
256
|
if (j.type === "USER_INPUT") {
|
|
220
257
|
const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
|
|
221
|
-
const text = (m ? m[1]
|
|
258
|
+
const text = (m ? m[1] : j.content).trim();
|
|
222
259
|
if (text) msgs.push({ role: "user", text, at, line: i });
|
|
223
260
|
} else if (j.type === "PLANNER_RESPONSE") {
|
|
224
261
|
const text = j.content.trim();
|
|
@@ -266,6 +303,10 @@ function formatJson(value, f) {
|
|
|
266
303
|
const text = JSON.stringify(value, null, f.indent).replace(/\n/g, f.eol);
|
|
267
304
|
return f.finalNewline ? text + f.eol : text;
|
|
268
305
|
}
|
|
306
|
+
function readJson(file) {
|
|
307
|
+
const f = readJsonFile(file);
|
|
308
|
+
return f.ok ? f.value : {};
|
|
309
|
+
}
|
|
269
310
|
function entryFor(event, harness, cmd, o) {
|
|
270
311
|
const matcher = o.matcherFor(event);
|
|
271
312
|
return {
|
|
@@ -335,8 +376,7 @@ function uninstallClaudeShaped(file, events, opts = {}) {
|
|
|
335
376
|
unlinkSync(file);
|
|
336
377
|
return { file, changed: true, note: "removed (sessionpipe created it)" };
|
|
337
378
|
}
|
|
338
|
-
|
|
339
|
-
if (next !== f.raw) writeFileSync(file, next);
|
|
379
|
+
writeFileSync(file, formatJson(settings2, f));
|
|
340
380
|
return { file, changed: true };
|
|
341
381
|
}
|
|
342
382
|
function installedClaudeShaped(file, harness, events, cmd, o) {
|
|
@@ -363,7 +403,7 @@ var HOOK_NAME = "sessionpipe";
|
|
|
363
403
|
var AG_EVENTS = ["PreInvocation", "PostInvocation", "PostToolUse", "Stop"];
|
|
364
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';
|
|
365
405
|
var antigravityAnswer = (event) => event === "PreToolUse" ? AG_STALE_PRETOOL : "{}\n";
|
|
366
|
-
var gemini = (env) => env.GEMINI_CLI_HOME || path3.join(
|
|
406
|
+
var gemini = (env) => env.GEMINI_CLI_HOME || path3.join(homeDir(), ".gemini");
|
|
367
407
|
var hooksFile = (env) => path3.join(gemini(env), "config", "hooks.json");
|
|
368
408
|
var projectsDir = (env) => path3.join(gemini(env), "config", "projects");
|
|
369
409
|
var dataDirs = (env) => ["antigravity", "antigravity-cli", "antigravity-ide"].map((d) => path3.join(gemini(env), d));
|
|
@@ -429,7 +469,8 @@ var antigravity = {
|
|
|
429
469
|
const event = input.argv[1] ?? "";
|
|
430
470
|
let s = {};
|
|
431
471
|
try {
|
|
432
|
-
|
|
472
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
473
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
433
474
|
} catch {
|
|
434
475
|
}
|
|
435
476
|
const id = s.conversationId ?? s.session_id ?? s.sessionId;
|
|
@@ -472,9 +513,9 @@ var antigravity = {
|
|
|
472
513
|
default:
|
|
473
514
|
return { session, events: [], stdout };
|
|
474
515
|
}
|
|
475
|
-
const
|
|
476
|
-
if (typeof s.transcriptPath === "string")
|
|
477
|
-
return
|
|
516
|
+
const r = { session, events, stdout, hints: { workspaces } };
|
|
517
|
+
if (typeof s.transcriptPath === "string") r.transcript = s.transcriptPath;
|
|
518
|
+
return r;
|
|
478
519
|
},
|
|
479
520
|
readTranscript(file, fromLine) {
|
|
480
521
|
let text = "";
|
|
@@ -485,19 +526,16 @@ var antigravity = {
|
|
|
485
526
|
}
|
|
486
527
|
return readAntigravity(text.split("\n"), fromLine);
|
|
487
528
|
},
|
|
488
|
-
facts(session, transcript, hints
|
|
529
|
+
facts(session, transcript, hints) {
|
|
489
530
|
if (!transcript) return {};
|
|
490
531
|
return antigravityFacts(transcript, {
|
|
491
532
|
conversation: session.id,
|
|
492
533
|
workspaces: hints.workspaces ?? (session.cwd ? [session.cwd] : []),
|
|
493
|
-
state,
|
|
494
534
|
env: process.env
|
|
495
535
|
});
|
|
496
536
|
},
|
|
497
537
|
backfill(sinceMs, env = process.env) {
|
|
498
|
-
const
|
|
499
|
-
const noState = { get: () => ({}), save: () => {
|
|
500
|
-
} };
|
|
538
|
+
const rows2 = [];
|
|
501
539
|
for (const data of dataDirs(env)) {
|
|
502
540
|
let ids = [];
|
|
503
541
|
try {
|
|
@@ -511,13 +549,13 @@ var antigravity = {
|
|
|
511
549
|
} catch {
|
|
512
550
|
continue;
|
|
513
551
|
}
|
|
514
|
-
const facts = antigravityFacts(f, { conversation: id, workspaces: [],
|
|
552
|
+
const facts = antigravityFacts(f, { conversation: id, workspaces: [], env });
|
|
515
553
|
if (!facts.started_at || !facts.last_at) continue;
|
|
516
554
|
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
517
|
-
|
|
555
|
+
rows2.push({ session: { id, ...rest }, started_at, last_at });
|
|
518
556
|
}
|
|
519
557
|
}
|
|
520
|
-
return
|
|
558
|
+
return rows2;
|
|
521
559
|
}
|
|
522
560
|
};
|
|
523
561
|
function prune(o) {
|
|
@@ -542,8 +580,8 @@ var agWorkspaces = (paths) => (Array.isArray(paths) ? paths : []).map(agPath).fi
|
|
|
542
580
|
function agVariants(p) {
|
|
543
581
|
const out = [p];
|
|
544
582
|
try {
|
|
545
|
-
const
|
|
546
|
-
if (
|
|
583
|
+
const r = realpathSync.native(p);
|
|
584
|
+
if (r !== p) out.push(r);
|
|
547
585
|
} catch {
|
|
548
586
|
}
|
|
549
587
|
return out;
|
|
@@ -566,8 +604,8 @@ function agProjects(dir) {
|
|
|
566
604
|
continue;
|
|
567
605
|
}
|
|
568
606
|
const folders = [];
|
|
569
|
-
for (const
|
|
570
|
-
const f = agPath(
|
|
607
|
+
for (const r of j?.projectResources?.resources ?? []) {
|
|
608
|
+
const f = agPath(r?.folderUri ?? r?.gitFolder?.folderUri);
|
|
571
609
|
if (f) folders.push(...agVariants(f));
|
|
572
610
|
}
|
|
573
611
|
if (folders.length) out.push({ id, folders });
|
|
@@ -621,7 +659,7 @@ function antigravityFacts(file, opts) {
|
|
|
621
659
|
for (const j of H) {
|
|
622
660
|
if (j.type !== "USER_INPUT" || typeof j.content !== "string") continue;
|
|
623
661
|
const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
|
|
624
|
-
const text = (m ? m[1]
|
|
662
|
+
const text = (m ? m[1] : j.content).trim();
|
|
625
663
|
const line = (text.split("\n").find((l) => l.trim()) ?? "").trim();
|
|
626
664
|
if (!line || /^<[a-z_-]+>/i.test(line)) continue;
|
|
627
665
|
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
@@ -669,10 +707,9 @@ function gitFacts(cwd) {
|
|
|
669
707
|
return out;
|
|
670
708
|
}
|
|
671
709
|
function stripCredentials(url) {
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
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$/, "");
|
|
676
713
|
}
|
|
677
714
|
|
|
678
715
|
// ../core/src/adapters/claude-code.ts
|
|
@@ -703,12 +740,12 @@ var OPTS = {
|
|
|
703
740
|
timeoutFor: (e) => e === "PermissionRequest" ? 130 : void 0
|
|
704
741
|
};
|
|
705
742
|
var DESKTOP_ID = /^local_[A-Za-z0-9-]{1,64}$/;
|
|
706
|
-
function claudeDirs(env = process.env, home = env.HOME ||
|
|
743
|
+
function claudeDirs(env = process.env, home = env.HOME || homeDir()) {
|
|
707
744
|
const out = [];
|
|
708
745
|
const add = (d) => {
|
|
709
746
|
if (!d) return;
|
|
710
|
-
const
|
|
711
|
-
if (!out.includes(
|
|
747
|
+
const r = path4.resolve(d);
|
|
748
|
+
if (!out.includes(r)) out.push(r);
|
|
712
749
|
};
|
|
713
750
|
add(path4.join(home, ".claude"));
|
|
714
751
|
add(env.CLAUDE_CONFIG_DIR);
|
|
@@ -725,7 +762,7 @@ function claudeDirs(env = process.env, home = env.HOME || HOME) {
|
|
|
725
762
|
var settingsIn = (dir) => path4.join(dir, "settings.json");
|
|
726
763
|
var configDirOf = (transcript) => path4.dirname(path4.dirname(path4.dirname(transcript)));
|
|
727
764
|
var validTool2 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
728
|
-
var endReason = (
|
|
765
|
+
var endReason = (r) => r === "clear" || r === "logout" || r === "exit" ? r : r === "prompt_input_exit" ? "exit" : "other";
|
|
729
766
|
var notifKind = (t) => t === "permission_prompt" ? "permission" : t === "idle_prompt" ? "idle" : t === "elicitation_dialog" ? "elicitation" : "question";
|
|
730
767
|
var claudeCode = {
|
|
731
768
|
name: NAME2,
|
|
@@ -744,11 +781,13 @@ var claudeCode = {
|
|
|
744
781
|
const event = input.argv[1] ?? "";
|
|
745
782
|
let s = {};
|
|
746
783
|
try {
|
|
747
|
-
|
|
784
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
785
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
748
786
|
} catch {
|
|
749
787
|
}
|
|
750
788
|
const id = s.session_id ?? s.sessionId;
|
|
751
789
|
if (!id) return null;
|
|
790
|
+
if (notClaudeCode(String(id), s)) return null;
|
|
752
791
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
753
792
|
if (typeof s.model === "string") session.model = s.model;
|
|
754
793
|
if (typeof s.agent_id === "string" && event.startsWith("Subagent") === false && s.agent_id)
|
|
@@ -765,14 +804,28 @@ var claudeCode = {
|
|
|
765
804
|
case "SessionStart":
|
|
766
805
|
events.push(ev("session.started", { source: pickSource(s.source ?? s.trigger) }));
|
|
767
806
|
break;
|
|
768
|
-
case "UserPromptSubmit":
|
|
807
|
+
case "UserPromptSubmit": {
|
|
808
|
+
const prompt = typeof s.prompt === "string" ? s.prompt : void 0;
|
|
769
809
|
events.push(
|
|
770
810
|
ev("turn.started", {
|
|
771
811
|
turn_id,
|
|
772
|
-
prompt_chars:
|
|
812
|
+
prompt_chars: prompt === void 0 ? void 0 : [...prompt].length,
|
|
813
|
+
// Claude Code's hook schema documents `source`
|
|
814
|
+
// (user | sdk | system | loop_wakeup | schedule_wakeup | poll_event) and says a
|
|
815
|
+
// payload may omit it while the field rolls out; 2.1.304's payload has no such
|
|
816
|
+
// key at all. Forwarded verbatim, shape-guarded only: the list is the harness's
|
|
817
|
+
// to extend.
|
|
818
|
+
submitted_by: harnessWord(s.source),
|
|
819
|
+
// One of Claude Code's own prompt wrappers: the prompt is a block the harness
|
|
820
|
+
// wrote, so it is not the person's words. Absent is unknown, never "the person" —
|
|
821
|
+
// and `true` is never "no person caused this turn", because a Talk or
|
|
822
|
+
// `sessionpipe wait` delivery hands the person's own message over inside a
|
|
823
|
+
// <task-notification> (PROTOCOL.md §4).
|
|
824
|
+
injected: prompt !== void 0 && injectedBlock(prompt) ? true : void 0
|
|
773
825
|
})
|
|
774
826
|
);
|
|
775
827
|
break;
|
|
828
|
+
}
|
|
776
829
|
case "PreToolUse":
|
|
777
830
|
if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, agent_id, input: s.tool_input }));
|
|
778
831
|
break;
|
|
@@ -827,7 +880,13 @@ var claudeCode = {
|
|
|
827
880
|
);
|
|
828
881
|
break;
|
|
829
882
|
case "Stop":
|
|
830
|
-
events.push(
|
|
883
|
+
events.push(
|
|
884
|
+
ev("turn.ended", {
|
|
885
|
+
turn_id,
|
|
886
|
+
reason: "stop",
|
|
887
|
+
background: Array.isArray(s.background_tasks) ? s.background_tasks.length : void 0
|
|
888
|
+
})
|
|
889
|
+
);
|
|
831
890
|
break;
|
|
832
891
|
case "StopFailure":
|
|
833
892
|
events.push(
|
|
@@ -867,12 +926,15 @@ var claudeCode = {
|
|
|
867
926
|
return null;
|
|
868
927
|
}
|
|
869
928
|
const hints = {};
|
|
929
|
+
if (event === "UserPromptSubmit" && typeof s.prompt === "string") hints.prompt = s.prompt;
|
|
870
930
|
const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
|
|
871
931
|
if (host && DESKTOP_ID.test(host)) hints.desktop_id = host;
|
|
932
|
+
const attended = input.env.CLAUDE_CODE_SESSION_ATTENDED;
|
|
933
|
+
if (attended === "1" || attended === "0") hints.attended = attended === "1" || !!input.env.SESSIONPIPE_CONTROL_JOB;
|
|
872
934
|
if (event !== "PreToolUse" && event !== "PostToolUse") hints.claude_pid = claudePid(input.env);
|
|
873
|
-
const
|
|
874
|
-
if (typeof s.transcript_path === "string")
|
|
875
|
-
return
|
|
935
|
+
const r = { session, events, hints };
|
|
936
|
+
if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
|
|
937
|
+
return r;
|
|
876
938
|
},
|
|
877
939
|
readTranscript(file, fromLine) {
|
|
878
940
|
let text = "";
|
|
@@ -889,10 +951,11 @@ var claudeCode = {
|
|
|
889
951
|
const cwd = out.cwd ?? session.cwd;
|
|
890
952
|
Object.assign(out, gitFacts(cwd), out.branch ? { branch: out.branch } : {});
|
|
891
953
|
if (!out.url && typeof hints.desktop_id === "string") out.url = `claude://code/continue/${hints.desktop_id}`;
|
|
954
|
+
if (typeof hints.attended === "boolean") out.attended = hints.attended || !!out.url;
|
|
892
955
|
return out;
|
|
893
956
|
},
|
|
894
957
|
backfill(sinceMs, env = process.env) {
|
|
895
|
-
const
|
|
958
|
+
const rows2 = [];
|
|
896
959
|
const files = [];
|
|
897
960
|
for (const d of claudeDirs(env))
|
|
898
961
|
files.push(...recentFiles(path4.join(d, "projects"), 1, sinceMs, (n) => /^[0-9a-f-]{36}\.jsonl$/.test(n)));
|
|
@@ -904,22 +967,29 @@ var claudeCode = {
|
|
|
904
967
|
if (!facts.started_at || !facts.last_at) continue;
|
|
905
968
|
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
906
969
|
const cwd = rest.cwd;
|
|
907
|
-
|
|
970
|
+
rows2.push({
|
|
908
971
|
session: { id, ...rest, ...gitFacts(cwd), ...rest.branch ? { branch: rest.branch } : {} },
|
|
909
972
|
started_at,
|
|
910
973
|
last_at
|
|
911
974
|
});
|
|
912
975
|
}
|
|
913
|
-
return
|
|
976
|
+
return rows2;
|
|
914
977
|
}
|
|
915
978
|
};
|
|
916
979
|
function prune2(o) {
|
|
917
980
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
918
981
|
return o;
|
|
919
982
|
}
|
|
983
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
984
|
+
function notClaudeCode(id, s) {
|
|
985
|
+
return typeof s.transcript_path !== "string" && !UUID.test(id);
|
|
986
|
+
}
|
|
920
987
|
function pickSource(v) {
|
|
921
988
|
return typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
|
|
922
989
|
}
|
|
990
|
+
var harnessWord = (v) => typeof v === "string" && /^[A-Za-z0-9][A-Za-z0-9_.-]{0,39}$/.test(v) ? v : void 0;
|
|
991
|
+
var WRAPPER = /^<(?:task-notification|cross-session-message|system-reminder|local-command-stdout|command-[a-z-]+)[\s/>]/i;
|
|
992
|
+
var injectedBlock = (prompt) => WRAPPER.test(prompt.trim());
|
|
923
993
|
function errText(v) {
|
|
924
994
|
if (typeof v === "string") return v.slice(0, 2e3);
|
|
925
995
|
if (v && typeof v === "object") {
|
|
@@ -943,13 +1013,13 @@ var NAME_RANK = {
|
|
|
943
1013
|
};
|
|
944
1014
|
function registryEntry(j) {
|
|
945
1015
|
if (!j || typeof j !== "object") return null;
|
|
946
|
-
const
|
|
947
|
-
const source =
|
|
1016
|
+
const r = j;
|
|
1017
|
+
const source = r.nameSource ? NAME_RANK[r.nameSource] : void 0;
|
|
948
1018
|
return {
|
|
949
|
-
sessionId:
|
|
950
|
-
name: source && typeof
|
|
1019
|
+
sessionId: r.sessionId ? String(r.sessionId) : null,
|
|
1020
|
+
name: source && typeof r.name === "string" && r.name.trim() ? r.name.trim() : null,
|
|
951
1021
|
name_source: source ?? null,
|
|
952
|
-
bridge: typeof
|
|
1022
|
+
bridge: typeof r.bridgeSessionId === "string" ? r.bridgeSessionId : null
|
|
953
1023
|
};
|
|
954
1024
|
}
|
|
955
1025
|
var liveCache = /* @__PURE__ */ new Map();
|
|
@@ -1022,23 +1092,47 @@ function parentOf() {
|
|
|
1022
1092
|
}
|
|
1023
1093
|
return (pid) => table.get(pid) || 0;
|
|
1024
1094
|
}
|
|
1025
|
-
var
|
|
1026
|
-
function
|
|
1027
|
-
const hit = accountCache.get(dir);
|
|
1028
|
-
if (hit !== void 0) return hit;
|
|
1095
|
+
var EMAIL_RE = /^[^@\s]{1,64}@[^@\s]{1,189}$/;
|
|
1096
|
+
function claudeLogin(dir, home = homeDir()) {
|
|
1029
1097
|
const files = [path4.join(dir, ".claude.json")];
|
|
1030
|
-
if (path4.resolve(dir) === path4.join(
|
|
1031
|
-
let acct = null;
|
|
1098
|
+
if (path4.resolve(dir) === path4.join(home, ".claude")) files.unshift(path4.join(home, ".claude.json"));
|
|
1032
1099
|
for (const f of files) {
|
|
1033
1100
|
try {
|
|
1034
1101
|
const a = JSON.parse(readFileSync3(f, "utf8")).oauthAccount;
|
|
1035
|
-
if (a?.accountUuid) {
|
|
1036
|
-
|
|
1037
|
-
|
|
1102
|
+
if (typeof a?.accountUuid === "string" && a.accountUuid) {
|
|
1103
|
+
const email = typeof a.emailAddress === "string" && EMAIL_RE.test(a.emailAddress) ? a.emailAddress : void 0;
|
|
1104
|
+
return { id: a.accountUuid.slice(0, 128), ...email ? { email } : {} };
|
|
1038
1105
|
}
|
|
1039
1106
|
} catch {
|
|
1040
1107
|
}
|
|
1041
1108
|
}
|
|
1109
|
+
return apiAccount(dir);
|
|
1110
|
+
}
|
|
1111
|
+
var ANTHROPIC_HOST = /(^|\.)anthropic\.com$/i;
|
|
1112
|
+
function apiAccount(dir) {
|
|
1113
|
+
let base;
|
|
1114
|
+
try {
|
|
1115
|
+
base = JSON.parse(readFileSync3(settingsIn(dir), "utf8")).env?.ANTHROPIC_BASE_URL;
|
|
1116
|
+
} catch {
|
|
1117
|
+
return null;
|
|
1118
|
+
}
|
|
1119
|
+
if (typeof base !== "string") return null;
|
|
1120
|
+
let u;
|
|
1121
|
+
try {
|
|
1122
|
+
u = new URL(base.trim());
|
|
1123
|
+
} catch {
|
|
1124
|
+
return null;
|
|
1125
|
+
}
|
|
1126
|
+
if (u.protocol !== "https:" && u.protocol !== "http:" || !u.hostname || ANTHROPIC_HOST.test(u.hostname))
|
|
1127
|
+
return null;
|
|
1128
|
+
const origin = `${u.protocol}//${u.host}`;
|
|
1129
|
+
return { id: `api-${createHash("sha256").update(origin).digest("hex").slice(0, 16)}`, name: u.host };
|
|
1130
|
+
}
|
|
1131
|
+
var accountCache = /* @__PURE__ */ new Map();
|
|
1132
|
+
function claudeAccount(dir) {
|
|
1133
|
+
const hit = accountCache.get(dir);
|
|
1134
|
+
if (hit !== void 0) return hit;
|
|
1135
|
+
const acct = claudeLogin(dir)?.id ?? null;
|
|
1042
1136
|
accountCache.set(dir, acct);
|
|
1043
1137
|
return acct;
|
|
1044
1138
|
}
|
|
@@ -1258,18 +1352,13 @@ function parseArgv(raw) {
|
|
|
1258
1352
|
function readNotify(text) {
|
|
1259
1353
|
const { head, tail, at } = split(text);
|
|
1260
1354
|
const m = NOTIFY_LINE.exec(head);
|
|
1261
|
-
if (m)
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
index: m.index,
|
|
1266
|
-
length: m[0].length,
|
|
1267
|
-
misplaced: false,
|
|
1268
|
-
other: !OURS2.test(m[1] ?? "")
|
|
1269
|
-
};
|
|
1355
|
+
if (m) {
|
|
1356
|
+
const raw = m[1];
|
|
1357
|
+
return { raw, argv: parseArgv(raw), index: m.index, length: m[0].length, misplaced: false, other: !OURS2.test(raw) };
|
|
1358
|
+
}
|
|
1270
1359
|
const k = NOTIFY_KEY.exec(head);
|
|
1271
1360
|
if (k) {
|
|
1272
|
-
const rest = head.slice(k.index).split("\n")[0]
|
|
1361
|
+
const rest = head.slice(k.index).split("\n")[0];
|
|
1273
1362
|
return {
|
|
1274
1363
|
raw: rest.replace(NOTIFY_KEY, "").trim(),
|
|
1275
1364
|
argv: null,
|
|
@@ -1280,15 +1369,11 @@ function readNotify(text) {
|
|
|
1280
1369
|
};
|
|
1281
1370
|
}
|
|
1282
1371
|
const t = NOTIFY_LINE.exec(tail);
|
|
1283
|
-
if (t
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
argv: parseArgv(t[
|
|
1287
|
-
|
|
1288
|
-
length: t[0].length,
|
|
1289
|
-
misplaced: true,
|
|
1290
|
-
other: false
|
|
1291
|
-
};
|
|
1372
|
+
if (t) {
|
|
1373
|
+
const raw = t[1];
|
|
1374
|
+
if (OURS2.test(raw))
|
|
1375
|
+
return { raw, argv: parseArgv(raw), index: at + t.index, length: t[0].length, misplaced: true, other: false };
|
|
1376
|
+
}
|
|
1292
1377
|
return null;
|
|
1293
1378
|
}
|
|
1294
1379
|
function dropLine(text, index, length) {
|
|
@@ -1307,9 +1392,7 @@ function withNotify(text, line) {
|
|
|
1307
1392
|
if (n?.misplaced) return insertTopLevel(dropLine(text, n.index, n.length), line);
|
|
1308
1393
|
return insertTopLevel(text, line);
|
|
1309
1394
|
}
|
|
1310
|
-
function replaceNotify(text, line) {
|
|
1311
|
-
const n = readNotify(text);
|
|
1312
|
-
if (!n || n.misplaced || !n.length) return null;
|
|
1395
|
+
function replaceNotify(text, n, line) {
|
|
1313
1396
|
return text.slice(0, n.index) + line + text.slice(n.index + n.length);
|
|
1314
1397
|
}
|
|
1315
1398
|
function withoutNotify(text) {
|
|
@@ -1350,12 +1433,12 @@ var CODEX_EVENTS = [
|
|
|
1350
1433
|
];
|
|
1351
1434
|
var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
1352
1435
|
var eventsFor2 = (lean) => lean ? CODEX_EVENTS.filter((e) => e !== "PreToolUse") : [...CODEX_EVENTS];
|
|
1353
|
-
var codexHome = (env) => env.CODEX_HOME || path5.join(
|
|
1436
|
+
var codexHome = (env) => env.CODEX_HOME || path5.join(homeDir(), ".codex");
|
|
1354
1437
|
var hooksFile2 = (env) => path5.join(codexHome(env), "hooks.json");
|
|
1355
1438
|
var configFile = (env) => path5.join(codexHome(env), "config.toml");
|
|
1356
1439
|
var sessionsDir = (env) => path5.join(codexHome(env), "sessions");
|
|
1357
1440
|
var validTool3 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
1358
|
-
var endReason2 = (
|
|
1441
|
+
var endReason2 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
|
|
1359
1442
|
function installNotify(cmd, env) {
|
|
1360
1443
|
const file = configFile(env);
|
|
1361
1444
|
let text = "";
|
|
@@ -1388,11 +1471,8 @@ function installNotify(cmd, env) {
|
|
|
1388
1471
|
return { file, changed: false, note: "notify current (after Codex Computer Use)" };
|
|
1389
1472
|
}
|
|
1390
1473
|
if (!prev || OURS2.test(JSON.stringify(prev))) {
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
write(next);
|
|
1394
|
-
return { file, changed: true, note: "chained after Codex Computer Use (--previous-notify)" };
|
|
1395
|
-
}
|
|
1474
|
+
write(replaceNotify(text, n, `notify = ${withPreviousRaw(n.raw, argv)}`));
|
|
1475
|
+
return { file, changed: true, note: "chained after Codex Computer Use (--previous-notify)" };
|
|
1396
1476
|
}
|
|
1397
1477
|
}
|
|
1398
1478
|
return {
|
|
@@ -1421,8 +1501,7 @@ function uninstallNotify(env) {
|
|
|
1421
1501
|
}
|
|
1422
1502
|
const prev = previousNotify(n.argv);
|
|
1423
1503
|
if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev))) {
|
|
1424
|
-
|
|
1425
|
-
if (next) writeFileSync3(file, next);
|
|
1504
|
+
writeFileSync3(file, replaceNotify(text, n, `notify = ${stripPreviousRaw(n.raw)}`));
|
|
1426
1505
|
return { file, changed: true };
|
|
1427
1506
|
}
|
|
1428
1507
|
return { file, changed: false };
|
|
@@ -1452,6 +1531,13 @@ function findRollout(threadId, env = process.env) {
|
|
|
1452
1531
|
walk2(sessionsDir(env), 0);
|
|
1453
1532
|
return best;
|
|
1454
1533
|
}
|
|
1534
|
+
function rolloutMeta(file) {
|
|
1535
|
+
return scanLines(file, (line) => parseLines([line])[0] ?? {});
|
|
1536
|
+
}
|
|
1537
|
+
function ofAnotherThread(file, id) {
|
|
1538
|
+
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);
|
|
1539
|
+
return !!m?.[1] && m[1].toLowerCase() !== id.toLowerCase();
|
|
1540
|
+
}
|
|
1455
1541
|
var codex = {
|
|
1456
1542
|
name: NAME3,
|
|
1457
1543
|
events: CODEX_EVENTS,
|
|
@@ -1494,12 +1580,15 @@ var codex = {
|
|
|
1494
1580
|
if (event === "notify") {
|
|
1495
1581
|
const raw = input.argv.find((a) => a.startsWith("{")) ?? input.stdin;
|
|
1496
1582
|
try {
|
|
1497
|
-
|
|
1583
|
+
const j = raw ? JSON.parse(raw) : {};
|
|
1584
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1498
1585
|
} catch {
|
|
1499
1586
|
}
|
|
1500
1587
|
const id2 = s["thread-id"] ?? s.thread_id ?? s["session-id"] ?? s.session_id;
|
|
1501
1588
|
if (!id2) return null;
|
|
1502
|
-
const
|
|
1589
|
+
const file = findRollout(String(id2), input.env);
|
|
1590
|
+
if (!file || codexSubagent(rolloutMeta(file))) return null;
|
|
1591
|
+
const r2 = {
|
|
1503
1592
|
session: { id: String(id2), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd },
|
|
1504
1593
|
events: [
|
|
1505
1594
|
{
|
|
@@ -1508,18 +1597,19 @@ var codex = {
|
|
|
1508
1597
|
harnessEvent: "notify"
|
|
1509
1598
|
}
|
|
1510
1599
|
],
|
|
1511
|
-
hints: {}
|
|
1600
|
+
hints: {},
|
|
1601
|
+
transcript: file
|
|
1512
1602
|
};
|
|
1513
|
-
|
|
1514
|
-
if (file) r3.transcript = file;
|
|
1515
|
-
return r3;
|
|
1603
|
+
return r2;
|
|
1516
1604
|
}
|
|
1517
1605
|
try {
|
|
1518
|
-
|
|
1606
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
1607
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1519
1608
|
} catch {
|
|
1520
1609
|
}
|
|
1521
|
-
const id = s.session_id;
|
|
1610
|
+
const id = s.session_id ?? s.thread_id ?? s.conversation_id ?? s["thread-id"] ?? s["session-id"];
|
|
1522
1611
|
if (!id) return null;
|
|
1612
|
+
if (s.transcript_path === null) return null;
|
|
1523
1613
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
1524
1614
|
if (typeof s.model === "string") session.model = s.model;
|
|
1525
1615
|
const turn_id = typeof s.turn_id === "string" ? s.turn_id : void 0;
|
|
@@ -1590,9 +1680,10 @@ var codex = {
|
|
|
1590
1680
|
default:
|
|
1591
1681
|
return null;
|
|
1592
1682
|
}
|
|
1593
|
-
const
|
|
1594
|
-
if (typeof s.transcript_path === "string"
|
|
1595
|
-
|
|
1683
|
+
const r = { session, events, hints: {} };
|
|
1684
|
+
if (typeof s.transcript_path === "string" && !ofAnotherThread(s.transcript_path, String(id)))
|
|
1685
|
+
r.transcript = s.transcript_path;
|
|
1686
|
+
return r;
|
|
1596
1687
|
},
|
|
1597
1688
|
readTranscript(file, fromLine) {
|
|
1598
1689
|
let text = "";
|
|
@@ -1609,16 +1700,17 @@ var codex = {
|
|
|
1609
1700
|
return out;
|
|
1610
1701
|
},
|
|
1611
1702
|
backfill(sinceMs, env = process.env) {
|
|
1612
|
-
const
|
|
1703
|
+
const rows2 = [];
|
|
1613
1704
|
for (const f of recentFiles(sessionsDir(env), 3, sinceMs, (n) => /^rollout-.*\.jsonl$/.test(n))) {
|
|
1614
1705
|
const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(path5.basename(f));
|
|
1615
1706
|
if (!m?.[1]) continue;
|
|
1707
|
+
if (codexSubagent(rolloutMeta(f))) continue;
|
|
1616
1708
|
const facts = codexFacts(f);
|
|
1617
1709
|
if (!facts.started_at || !facts.last_at) continue;
|
|
1618
1710
|
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
1619
|
-
|
|
1711
|
+
rows2.push({ session: { id: m[1], ...rest }, started_at, last_at });
|
|
1620
1712
|
}
|
|
1621
|
-
return
|
|
1713
|
+
return rows2;
|
|
1622
1714
|
}
|
|
1623
1715
|
};
|
|
1624
1716
|
function prune3(o) {
|
|
@@ -1627,10 +1719,11 @@ function prune3(o) {
|
|
|
1627
1719
|
}
|
|
1628
1720
|
function codexFacts(file) {
|
|
1629
1721
|
const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
|
|
1630
|
-
|
|
1631
|
-
|
|
1722
|
+
let H = parseLines(head);
|
|
1723
|
+
let T = parseLines(tail);
|
|
1632
1724
|
const out = {};
|
|
1633
1725
|
const meta = H.find((j) => j.type === "session_meta" && j.payload);
|
|
1726
|
+
const boundary = codexReplayBoundary(meta);
|
|
1634
1727
|
if (meta?.payload) {
|
|
1635
1728
|
const p = meta.payload;
|
|
1636
1729
|
if (p.cwd) out.cwd = p.cwd;
|
|
@@ -1638,6 +1731,13 @@ function codexFacts(file) {
|
|
|
1638
1731
|
if (p.git?.branch) out.branch = p.git.branch;
|
|
1639
1732
|
if (p.git?.repository_url) out.repo = String(p.git.repository_url).replace(/\/\/[^/@]*@/, "//");
|
|
1640
1733
|
}
|
|
1734
|
+
if (boundary) {
|
|
1735
|
+
const h = H.findIndex(boundary);
|
|
1736
|
+
const t = T.findIndex(boundary);
|
|
1737
|
+
H = h < 0 ? [] : H.slice(h);
|
|
1738
|
+
const found = h >= 0 || t >= 0 || scanLines(file, (line) => boundary(parseLines([line])[0] ?? null) ? true : null, Number.POSITIVE_INFINITY);
|
|
1739
|
+
T = t >= 0 ? T.slice(t) : found ? T : [];
|
|
1740
|
+
}
|
|
1641
1741
|
const ctx = H.concat(T).reverse().find((j) => j.type === "turn_context" && j.payload?.model);
|
|
1642
1742
|
if (ctx?.payload?.model) out.model = ctx.payload.model;
|
|
1643
1743
|
for (const j of H) {
|
|
@@ -1645,12 +1745,12 @@ function codexFacts(file) {
|
|
|
1645
1745
|
if (j.type !== "response_item" || !p || p.type !== "message" || p.role !== "user") continue;
|
|
1646
1746
|
const text = (p.content ?? []).filter((b) => b && b.type === "input_text" && typeof b.text === "string").map((b) => b.text).join("\n").trim();
|
|
1647
1747
|
if (!text || codexInjected(text)) continue;
|
|
1648
|
-
const line = text.split("\n")
|
|
1748
|
+
const line = text.split("\n")[0];
|
|
1649
1749
|
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
1650
1750
|
out.title_source = "first-ask";
|
|
1651
1751
|
break;
|
|
1652
1752
|
}
|
|
1653
|
-
const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
|
|
1753
|
+
const times = (boundary && meta ? [meta, ...H, ...T] : H.concat(T)).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
|
|
1654
1754
|
if (times.length) {
|
|
1655
1755
|
out.started_at = Math.min(...times);
|
|
1656
1756
|
out.last_at = Math.max(...times);
|
|
@@ -1658,11 +1758,432 @@ function codexFacts(file) {
|
|
|
1658
1758
|
return out;
|
|
1659
1759
|
}
|
|
1660
1760
|
|
|
1661
|
-
// ../core/src/adapters/
|
|
1662
|
-
import {
|
|
1663
|
-
import { existsSync as
|
|
1761
|
+
// ../core/src/adapters/devin.ts
|
|
1762
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
1763
|
+
import { existsSync as existsSync6, readlinkSync } from "node:fs";
|
|
1664
1764
|
import path6 from "node:path";
|
|
1665
|
-
|
|
1765
|
+
|
|
1766
|
+
// ../core/src/readers/sqlite.ts
|
|
1767
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
1768
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
1769
|
+
import { createRequire } from "node:module";
|
|
1770
|
+
function quiet(fn) {
|
|
1771
|
+
const original = process.emitWarning;
|
|
1772
|
+
try {
|
|
1773
|
+
process.emitWarning = ((warning, ...rest) => {
|
|
1774
|
+
const kind = typeof rest[0] === "string" ? rest[0] : rest[0]?.type;
|
|
1775
|
+
if (kind === "ExperimentalWarning" && /sqlite/i.test(String(warning))) return;
|
|
1776
|
+
original.call(process, warning, ...rest);
|
|
1777
|
+
});
|
|
1778
|
+
return fn();
|
|
1779
|
+
} finally {
|
|
1780
|
+
process.emitWarning = original;
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
var ctorCache;
|
|
1784
|
+
function dbCtor() {
|
|
1785
|
+
if (ctorCache !== void 0) return ctorCache;
|
|
1786
|
+
ctorCache = null;
|
|
1787
|
+
try {
|
|
1788
|
+
const req = createRequire(import.meta.url);
|
|
1789
|
+
const m = quiet(() => req("node:sqlite"));
|
|
1790
|
+
if (typeof m?.DatabaseSync === "function") ctorCache = m.DatabaseSync;
|
|
1791
|
+
} catch {
|
|
1792
|
+
}
|
|
1793
|
+
return ctorCache;
|
|
1794
|
+
}
|
|
1795
|
+
function plain(row) {
|
|
1796
|
+
if (!row || typeof row !== "object") return null;
|
|
1797
|
+
const out = {};
|
|
1798
|
+
for (const [k, v] of Object.entries(row)) {
|
|
1799
|
+
if (typeof v === "bigint") out[k] = Number(v);
|
|
1800
|
+
else if (v instanceof Uint8Array) out[k] = null;
|
|
1801
|
+
else out[k] = v;
|
|
1802
|
+
}
|
|
1803
|
+
return out;
|
|
1804
|
+
}
|
|
1805
|
+
var rows = (v) => {
|
|
1806
|
+
if (!Array.isArray(v)) return null;
|
|
1807
|
+
const out = [];
|
|
1808
|
+
for (const r of v) {
|
|
1809
|
+
const p = plain(r);
|
|
1810
|
+
if (p) out.push(p);
|
|
1811
|
+
}
|
|
1812
|
+
return out;
|
|
1813
|
+
};
|
|
1814
|
+
function viaNode(file, sql, params, o) {
|
|
1815
|
+
const C = dbCtor();
|
|
1816
|
+
if (!C) return null;
|
|
1817
|
+
let db;
|
|
1818
|
+
try {
|
|
1819
|
+
db = quiet(() => new C(file, { readOnly: true, timeout: o.timeoutMs }));
|
|
1820
|
+
} catch {
|
|
1821
|
+
return null;
|
|
1822
|
+
}
|
|
1823
|
+
try {
|
|
1824
|
+
try {
|
|
1825
|
+
db.exec(`PRAGMA busy_timeout = ${Math.round(o.timeoutMs)}`);
|
|
1826
|
+
} catch {
|
|
1827
|
+
}
|
|
1828
|
+
return quiet(() => rows(db.prepare(sql).all(...params)));
|
|
1829
|
+
} catch {
|
|
1830
|
+
return null;
|
|
1831
|
+
} finally {
|
|
1832
|
+
try {
|
|
1833
|
+
db.close();
|
|
1834
|
+
} catch {
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
var CHILD = `const { DatabaseSync } = require("node:sqlite");
|
|
1839
|
+
const [, file, sql, params, ms] = process.argv;
|
|
1840
|
+
const db = new DatabaseSync(file, { readOnly: true, timeout: Number(ms) });
|
|
1841
|
+
try { db.exec("PRAGMA busy_timeout = " + Number(ms)); } catch {}
|
|
1842
|
+
const out = db.prepare(sql).all(...JSON.parse(params));
|
|
1843
|
+
db.close();
|
|
1844
|
+
process.stdout.write(JSON.stringify(out));`;
|
|
1845
|
+
function viaChild(file, sql, params, o) {
|
|
1846
|
+
try {
|
|
1847
|
+
const out = execFileSync3(
|
|
1848
|
+
process.execPath,
|
|
1849
|
+
["--experimental-sqlite", "--no-warnings", "-e", CHILD, file, sql, JSON.stringify(params), String(o.timeoutMs)],
|
|
1850
|
+
{
|
|
1851
|
+
encoding: "utf8",
|
|
1852
|
+
timeout: o.timeoutMs + 3e3,
|
|
1853
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1854
|
+
maxBuffer: o.maxBuffer,
|
|
1855
|
+
windowsHide: true
|
|
1856
|
+
}
|
|
1857
|
+
);
|
|
1858
|
+
return rows(JSON.parse(out || "null"));
|
|
1859
|
+
} catch {
|
|
1860
|
+
return null;
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
var SAFE = /^[A-Za-z0-9 ._:@/+-]{0,256}$/;
|
|
1864
|
+
function inlineParams(sql, params) {
|
|
1865
|
+
if (sql.includes("'")) return null;
|
|
1866
|
+
const holes = sql.split("?").length - 1;
|
|
1867
|
+
if (holes !== params.length) return null;
|
|
1868
|
+
let i = 0;
|
|
1869
|
+
let bad = false;
|
|
1870
|
+
const out = sql.replace(/\?/g, () => {
|
|
1871
|
+
const p = params[i++];
|
|
1872
|
+
if (typeof p === "number") {
|
|
1873
|
+
if (!Number.isFinite(p)) {
|
|
1874
|
+
bad = true;
|
|
1875
|
+
return "NULL";
|
|
1876
|
+
}
|
|
1877
|
+
return String(p);
|
|
1878
|
+
}
|
|
1879
|
+
if (typeof p !== "string" || !SAFE.test(p)) {
|
|
1880
|
+
bad = true;
|
|
1881
|
+
return "NULL";
|
|
1882
|
+
}
|
|
1883
|
+
return `'${p}'`;
|
|
1884
|
+
});
|
|
1885
|
+
return bad ? null : out;
|
|
1886
|
+
}
|
|
1887
|
+
function viaCli(file, sql, params, o) {
|
|
1888
|
+
const text = inlineParams(sql, params);
|
|
1889
|
+
if (!text) return null;
|
|
1890
|
+
try {
|
|
1891
|
+
const out = execFileSync3("sqlite3", ["-readonly", "-json", file, text], {
|
|
1892
|
+
encoding: "utf8",
|
|
1893
|
+
timeout: o.timeoutMs + 3e3,
|
|
1894
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1895
|
+
maxBuffer: o.maxBuffer,
|
|
1896
|
+
windowsHide: true
|
|
1897
|
+
}).trim();
|
|
1898
|
+
if (!out) return [];
|
|
1899
|
+
return rows(JSON.parse(out));
|
|
1900
|
+
} catch {
|
|
1901
|
+
return null;
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
function querySqlite(file, sql, params = [], opts = {}) {
|
|
1905
|
+
const o = {
|
|
1906
|
+
timeoutMs: opts.timeoutMs ?? 2e3,
|
|
1907
|
+
engine: opts.engine ?? "auto",
|
|
1908
|
+
maxBuffer: opts.maxBuffer ?? 32 * 1024 * 1024
|
|
1909
|
+
};
|
|
1910
|
+
if (!file) return null;
|
|
1911
|
+
try {
|
|
1912
|
+
if (!existsSync5(file)) return null;
|
|
1913
|
+
} catch {
|
|
1914
|
+
return null;
|
|
1915
|
+
}
|
|
1916
|
+
const ways = o.engine === "auto" ? [viaNode, viaChild, viaCli] : o.engine === "node" ? [viaNode] : o.engine === "child" ? [viaChild] : [viaCli];
|
|
1917
|
+
for (const way of ways) {
|
|
1918
|
+
const r = way(file, sql, params, o);
|
|
1919
|
+
if (r) return r;
|
|
1920
|
+
}
|
|
1921
|
+
return null;
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
// ../core/src/adapters/devin.ts
|
|
1925
|
+
var NAME4 = "devin";
|
|
1926
|
+
var DEVIN_EVENTS = [
|
|
1927
|
+
"SessionStart",
|
|
1928
|
+
"UserPromptSubmit",
|
|
1929
|
+
"PreToolUse",
|
|
1930
|
+
"PostToolUse",
|
|
1931
|
+
"PermissionRequest",
|
|
1932
|
+
"Stop",
|
|
1933
|
+
"PostCompaction",
|
|
1934
|
+
"SessionEnd"
|
|
1935
|
+
];
|
|
1936
|
+
var OPTS3 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
1937
|
+
var eventsFor3 = (lean) => lean ? DEVIN_EVENTS.filter((e) => e !== "PreToolUse") : [...DEVIN_EVENTS];
|
|
1938
|
+
var INSTALL_NOTE = "Devin drops a whole hooks file on one event name it doesn't know; `/hooks` in `devin` lists what loaded";
|
|
1939
|
+
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";
|
|
1940
|
+
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)`;
|
|
1941
|
+
function foreignEvent(file) {
|
|
1942
|
+
const hooks = readJson(file).hooks;
|
|
1943
|
+
if (!hooks || typeof hooks !== "object" || Array.isArray(hooks)) return void 0;
|
|
1944
|
+
const ok = DEVIN_EVENTS;
|
|
1945
|
+
return Object.keys(hooks).find((k) => !ok.includes(k));
|
|
1946
|
+
}
|
|
1947
|
+
var homeOf = (env) => env.HOME || homeDir();
|
|
1948
|
+
var appData = (env) => env.APPDATA || path6.join(homeOf(env), "AppData", "Roaming");
|
|
1949
|
+
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");
|
|
1950
|
+
var configFile2 = (env) => path6.join(devinConfigDir(env), "config.json");
|
|
1951
|
+
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");
|
|
1952
|
+
var sessionsDb = (env) => path6.join(devinDataDir(env), "sessions.db");
|
|
1953
|
+
var SESSION_ID = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
1954
|
+
var validTool4 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
1955
|
+
var endReason3 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
|
|
1956
|
+
var pickSource2 = (v) => typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
|
|
1957
|
+
function errText2(v) {
|
|
1958
|
+
if (typeof v === "string" && v) return v.slice(0, 2e3);
|
|
1959
|
+
if (v && typeof v === "object") {
|
|
1960
|
+
const e = v;
|
|
1961
|
+
if (typeof e.message === "string" && e.message) return e.message.slice(0, 2e3);
|
|
1962
|
+
if (typeof e.error === "string" && e.error) return e.error.slice(0, 2e3);
|
|
1963
|
+
}
|
|
1964
|
+
return void 0;
|
|
1965
|
+
}
|
|
1966
|
+
var cut = (s, n) => s.length > n ? `${s.slice(0, n).replace(/\s+\S*$/, "")}\u2026` : s;
|
|
1967
|
+
var devin = {
|
|
1968
|
+
name: NAME4,
|
|
1969
|
+
events: DEVIN_EVENTS,
|
|
1970
|
+
detect: (env = process.env) => existsSync6(devinConfigDir(env)) || existsSync6(devinDataDir(env)),
|
|
1971
|
+
configFiles: (env = process.env) => [configFile2(env)],
|
|
1972
|
+
install: (cmd, env = process.env, opts = {}) => {
|
|
1973
|
+
const file = configFile2(env);
|
|
1974
|
+
const r = installClaudeShaped(file, NAME4, eventsFor3(opts.lean), cmd, OPTS3);
|
|
1975
|
+
if (r.skipped) return [r];
|
|
1976
|
+
const bad = foreignEvent(file);
|
|
1977
|
+
return [{ ...r, note: bad ? deadFileNote(bad) : INSTALL_NOTE }];
|
|
1978
|
+
},
|
|
1979
|
+
uninstall: (env = process.env, opts = {}) => [
|
|
1980
|
+
uninstallClaudeShaped(configFile2(env), DEVIN_EVENTS, { created: opts.created?.includes(configFile2(env)) })
|
|
1981
|
+
],
|
|
1982
|
+
installed: (cmd, env = process.env, opts = {}) => {
|
|
1983
|
+
const file = configFile2(env);
|
|
1984
|
+
const state = installedClaudeShaped(file, NAME4, eventsFor3(opts.lean), cmd, OPTS3);
|
|
1985
|
+
const bad = state === "missing" ? void 0 : foreignEvent(file);
|
|
1986
|
+
return [{ file, state: bad ? "inactive" : state, note: bad ? deadFileNote(bad) : CLAUDE_NOTE }];
|
|
1987
|
+
},
|
|
1988
|
+
fromHook(input) {
|
|
1989
|
+
const event = input.argv[1] ?? "";
|
|
1990
|
+
let s = {};
|
|
1991
|
+
try {
|
|
1992
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
1993
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1994
|
+
} catch {
|
|
1995
|
+
}
|
|
1996
|
+
const id = typeof s.session_id === "string" ? s.session_id : "";
|
|
1997
|
+
if (!id) return null;
|
|
1998
|
+
const session = { id, cwd: input.env.DEVIN_PROJECT_DIR ?? input.cwd };
|
|
1999
|
+
const turn_id = typeof s.prompt_id === "string" ? s.prompt_id : void 0;
|
|
2000
|
+
const tool = validTool4(s.tool_name) ? s.tool_name : void 0;
|
|
2001
|
+
const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
|
|
2002
|
+
const response = s.tool_response && typeof s.tool_response === "object" && !Array.isArray(s.tool_response) ? s.tool_response : void 0;
|
|
2003
|
+
const permissionId = () => `perm-${createHash2("sha256").update(`${turn_id ?? ""}|${tool ?? ""}|${JSON.stringify(s.tool_input ?? null)}`).digest("hex").slice(0, 16)}`;
|
|
2004
|
+
const ev = (type, data) => ({ type, data: prune4(data), harnessEvent: event });
|
|
2005
|
+
const events = [];
|
|
2006
|
+
switch (event) {
|
|
2007
|
+
case "SessionStart":
|
|
2008
|
+
events.push(ev("session.started", { source: pickSource2(s.source) }));
|
|
2009
|
+
break;
|
|
2010
|
+
case "UserPromptSubmit":
|
|
2011
|
+
events.push(
|
|
2012
|
+
ev("turn.started", {
|
|
2013
|
+
turn_id,
|
|
2014
|
+
prompt_chars: typeof s.prompt === "string" ? [...s.prompt].length : void 0
|
|
2015
|
+
})
|
|
2016
|
+
);
|
|
2017
|
+
break;
|
|
2018
|
+
case "PreToolUse":
|
|
2019
|
+
if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, input: s.tool_input }));
|
|
2020
|
+
break;
|
|
2021
|
+
case "PostToolUse":
|
|
2022
|
+
if (tool)
|
|
2023
|
+
events.push(
|
|
2024
|
+
ev("tool.ended", {
|
|
2025
|
+
tool,
|
|
2026
|
+
call_id,
|
|
2027
|
+
turn_id,
|
|
2028
|
+
// `ok` is required on tool.ended (PROTOCOL.md §events), so a response
|
|
2029
|
+
// that carries no success flag at all reads as "nothing said it failed".
|
|
2030
|
+
ok: response?.success !== false,
|
|
2031
|
+
error: errText2(response?.error),
|
|
2032
|
+
input: s.tool_input,
|
|
2033
|
+
output: response ? response.output : s.tool_response
|
|
2034
|
+
})
|
|
2035
|
+
);
|
|
2036
|
+
break;
|
|
2037
|
+
case "PermissionRequest":
|
|
2038
|
+
events.push(ev("attention.needed", { attention_id: call_id ?? permissionId(), kind: "permission", tool }));
|
|
2039
|
+
break;
|
|
2040
|
+
case "Stop":
|
|
2041
|
+
events.push(ev("turn.ended", { turn_id, reason: "stop" }));
|
|
2042
|
+
break;
|
|
2043
|
+
case "PostCompaction":
|
|
2044
|
+
events.push(ev("context.compacted", { trigger: "auto" }));
|
|
2045
|
+
break;
|
|
2046
|
+
case "SessionEnd":
|
|
2047
|
+
events.push(ev("session.ended", { reason: endReason3(s.reason) }));
|
|
2048
|
+
break;
|
|
2049
|
+
default:
|
|
2050
|
+
return null;
|
|
2051
|
+
}
|
|
2052
|
+
return {
|
|
2053
|
+
session,
|
|
2054
|
+
events,
|
|
2055
|
+
hints: {},
|
|
2056
|
+
transcript: transcriptRef(sessionsDb(input.env), id)
|
|
2057
|
+
};
|
|
2058
|
+
},
|
|
2059
|
+
readTranscript(file, fromLine) {
|
|
2060
|
+
const ref = splitRef(file);
|
|
2061
|
+
if (!ref) return { turns: [], line: fromLine };
|
|
2062
|
+
return readDevinChain(ref.db, ref.session, fromLine);
|
|
2063
|
+
},
|
|
2064
|
+
facts(session, transcript, _hints, _state, env = process.env) {
|
|
2065
|
+
const out = {};
|
|
2066
|
+
const version = devinVersion(env);
|
|
2067
|
+
if (version) out.harness_version = version;
|
|
2068
|
+
const account = devinAccount(env);
|
|
2069
|
+
if (account) out.account_id = account;
|
|
2070
|
+
const ref = splitRef(transcript) ?? { db: sessionsDb(env), session: session.id };
|
|
2071
|
+
if (!SESSION_ID.test(ref.session)) return out;
|
|
2072
|
+
const rows2 = querySqlite(ref.db, SESSION_SQL, [ref.session]);
|
|
2073
|
+
const row = rows2?.[0];
|
|
2074
|
+
if (!row) return out;
|
|
2075
|
+
Object.assign(out, sessionFacts(row));
|
|
2076
|
+
return out;
|
|
2077
|
+
},
|
|
2078
|
+
backfill(sinceMs, env = process.env) {
|
|
2079
|
+
const rows2 = querySqlite(sessionsDb(env), BACKFILL_SQL, ["text", Math.floor(sinceMs / 1e3)]);
|
|
2080
|
+
if (!rows2) return [];
|
|
2081
|
+
const out = [];
|
|
2082
|
+
for (const row of rows2) {
|
|
2083
|
+
const id = String(row.id);
|
|
2084
|
+
if (!SESSION_ID.test(id)) continue;
|
|
2085
|
+
const { started_at, last_at, ...rest } = sessionFacts(row);
|
|
2086
|
+
if (!started_at || !last_at) continue;
|
|
2087
|
+
out.push({ session: { id, ...rest }, started_at, last_at });
|
|
2088
|
+
}
|
|
2089
|
+
return out;
|
|
2090
|
+
}
|
|
2091
|
+
};
|
|
2092
|
+
function prune4(o) {
|
|
2093
|
+
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
2094
|
+
return o;
|
|
2095
|
+
}
|
|
2096
|
+
var transcriptRef = (db, session) => `${db}#${session}`;
|
|
2097
|
+
function splitRef(ref) {
|
|
2098
|
+
if (!ref) return null;
|
|
2099
|
+
const at = ref.lastIndexOf("#");
|
|
2100
|
+
if (at <= 0) return null;
|
|
2101
|
+
const db = ref.slice(0, at);
|
|
2102
|
+
const session = ref.slice(at + 1);
|
|
2103
|
+
return db && SESSION_ID.test(session) ? { db, session } : null;
|
|
2104
|
+
}
|
|
2105
|
+
var CHAIN_LIMIT = 4e3;
|
|
2106
|
+
var CHAIN_SQL = `WITH RECURSIVE chain(node_id, parent_node_id, chat_message, created_at) AS (
|
|
2107
|
+
SELECT node_id, parent_node_id, chat_message, created_at FROM message_nodes
|
|
2108
|
+
WHERE session_id = ? AND node_id = (SELECT main_chain_id FROM sessions WHERE id = ?)
|
|
2109
|
+
UNION ALL
|
|
2110
|
+
SELECT m.node_id, m.parent_node_id, m.chat_message, m.created_at FROM message_nodes m
|
|
2111
|
+
JOIN chain c ON m.node_id = c.parent_node_id AND m.session_id = ? AND m.node_id >= ?
|
|
2112
|
+
)
|
|
2113
|
+
SELECT node_id, chat_message, created_at FROM chain ORDER BY node_id DESC LIMIT ${CHAIN_LIMIT}`;
|
|
2114
|
+
var SESSION_FIELDS = `s.model, s.working_directory, s.created_at, s.last_activity_at,
|
|
2115
|
+
(SELECT p.content FROM prompt_history p WHERE p.session_id = s.id AND p.is_shell = 0
|
|
2116
|
+
ORDER BY p.timestamp ASC, p.id ASC LIMIT 1) AS first_ask`;
|
|
2117
|
+
var SESSION_SQL = `SELECT ${SESSION_FIELDS} FROM sessions s WHERE s.id = ? LIMIT 1`;
|
|
2118
|
+
var BACKFILL_SQL = `SELECT s.id, ${SESSION_FIELDS} FROM sessions s
|
|
2119
|
+
WHERE s.hidden = 0 AND typeof(s.id) = ? AND s.last_activity_at >= ?
|
|
2120
|
+
ORDER BY s.last_activity_at DESC LIMIT 500`;
|
|
2121
|
+
function sessionFacts(row) {
|
|
2122
|
+
const out = {};
|
|
2123
|
+
if (typeof row.model === "string" && row.model) out.model = row.model;
|
|
2124
|
+
if (typeof row.working_directory === "string" && path6.isAbsolute(row.working_directory))
|
|
2125
|
+
out.cwd = row.working_directory;
|
|
2126
|
+
const started = Number(row.created_at);
|
|
2127
|
+
const last = Number(row.last_activity_at);
|
|
2128
|
+
if (Number.isFinite(started) && started > 0) out.started_at = Math.round(started * 1e3);
|
|
2129
|
+
if (Number.isFinite(last) && last > 0) out.last_at = Math.round(last * 1e3);
|
|
2130
|
+
if (typeof row.first_ask === "string") {
|
|
2131
|
+
const line = row.first_ask.split("\n").find((l) => l.trim()) ?? "";
|
|
2132
|
+
if (line.trim()) {
|
|
2133
|
+
out.title = cut(line.trim(), 80);
|
|
2134
|
+
out.title_source = "first-ask";
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
return out;
|
|
2138
|
+
}
|
|
2139
|
+
function readDevinChain(db, session, fromLine) {
|
|
2140
|
+
if (!SESSION_ID.test(session)) return { turns: [], line: fromLine };
|
|
2141
|
+
const floor = Number.isFinite(fromLine) && fromLine > 0 ? Math.floor(fromLine) : 0;
|
|
2142
|
+
const rows2 = querySqlite(db, CHAIN_SQL, [session, session, session, floor]);
|
|
2143
|
+
if (!rows2?.length) return { turns: [], line: fromLine };
|
|
2144
|
+
const msgs = [];
|
|
2145
|
+
let end = fromLine;
|
|
2146
|
+
for (const row of rows2) {
|
|
2147
|
+
const node = Number(row.node_id);
|
|
2148
|
+
if (!Number.isFinite(node)) continue;
|
|
2149
|
+
if (node + 1 > end) end = node + 1;
|
|
2150
|
+
if (node < fromLine) continue;
|
|
2151
|
+
let j;
|
|
2152
|
+
try {
|
|
2153
|
+
j = JSON.parse(String(row.chat_message ?? ""));
|
|
2154
|
+
} catch {
|
|
2155
|
+
continue;
|
|
2156
|
+
}
|
|
2157
|
+
const text = typeof j.content === "string" ? j.content.trim() : "";
|
|
2158
|
+
if (!text) continue;
|
|
2159
|
+
const secs = Number(row.created_at);
|
|
2160
|
+
const at = Date.parse(String(j.metadata?.created_at ?? "")) || (Number.isFinite(secs) && secs > 0 ? Math.round(secs * 1e3) : 0) || Date.now();
|
|
2161
|
+
if (j.role === "user" && j.metadata?.is_user_input === true) msgs.push({ role: "user", text, at, line: node });
|
|
2162
|
+
else if (j.role === "assistant") msgs.push({ role: "assistant", text, at, line: node });
|
|
2163
|
+
}
|
|
2164
|
+
msgs.sort((a, b) => a.line - b.line);
|
|
2165
|
+
return pairTurns(msgs, end);
|
|
2166
|
+
}
|
|
2167
|
+
function devinVersion(env = process.env) {
|
|
2168
|
+
try {
|
|
2169
|
+
const target = readlinkSync(path6.join(devinDataDir(env), "_versions", "current"));
|
|
2170
|
+
const name = path6.basename(target);
|
|
2171
|
+
return /^[0-9][0-9A-Za-z.+-]{0,63}$/.test(name) ? name : void 0;
|
|
2172
|
+
} catch {
|
|
2173
|
+
return void 0;
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
function devinAccount(env = process.env) {
|
|
2177
|
+
const d = readJson(configFile2(env)).devin;
|
|
2178
|
+
const id = d && typeof d === "object" ? d.org_id : void 0;
|
|
2179
|
+
return typeof id === "string" && /^[A-Za-z0-9_.:-]{1,128}$/.test(id) ? id : void 0;
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
// ../core/src/adapters/gemini-cli.ts
|
|
2183
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
2184
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
|
|
2185
|
+
import path7 from "node:path";
|
|
2186
|
+
var NAME5 = "gemini-cli";
|
|
1666
2187
|
var GEMINI_EVENTS = [
|
|
1667
2188
|
"SessionStart",
|
|
1668
2189
|
"BeforeAgent",
|
|
@@ -1673,11 +2194,11 @@ var GEMINI_EVENTS = [
|
|
|
1673
2194
|
"PreCompress",
|
|
1674
2195
|
"SessionEnd"
|
|
1675
2196
|
];
|
|
1676
|
-
var
|
|
1677
|
-
var geminiDir = (env) => env.GEMINI_CLI_HOME ||
|
|
1678
|
-
var settings = (env) =>
|
|
1679
|
-
var
|
|
1680
|
-
var
|
|
2197
|
+
var OPTS4 = { matcherFor: (e) => /Tool/.test(e) ? ".*" : void 0, timeout: 5e3 };
|
|
2198
|
+
var geminiDir = (env) => env.GEMINI_CLI_HOME || path7.join(homeDir(), ".gemini");
|
|
2199
|
+
var settings = (env) => path7.join(geminiDir(env), "settings.json");
|
|
2200
|
+
var validTool5 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
2201
|
+
var eventsFor4 = (lean) => lean ? GEMINI_EVENTS.filter((e) => e !== "BeforeTool") : [...GEMINI_EVENTS];
|
|
1681
2202
|
function geminiHooksActive(settingsFile) {
|
|
1682
2203
|
let tools;
|
|
1683
2204
|
try {
|
|
@@ -1689,7 +2210,7 @@ function geminiHooksActive(settingsFile) {
|
|
|
1689
2210
|
return { active: false, note: "inactive: tools.enableHooks is false in settings.json" };
|
|
1690
2211
|
let version = "";
|
|
1691
2212
|
try {
|
|
1692
|
-
version =
|
|
2213
|
+
version = execFileSync4("gemini", ["--version"], {
|
|
1693
2214
|
encoding: "utf8",
|
|
1694
2215
|
timeout: 4e3,
|
|
1695
2216
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -1708,21 +2229,21 @@ function geminiHooksActive(settingsFile) {
|
|
|
1708
2229
|
};
|
|
1709
2230
|
}
|
|
1710
2231
|
var geminiCli = {
|
|
1711
|
-
name:
|
|
2232
|
+
name: NAME5,
|
|
1712
2233
|
events: GEMINI_EVENTS,
|
|
1713
2234
|
// ~/.gemini also belongs to Antigravity; Gemini CLI is here when its settings or its chat logs exist.
|
|
1714
|
-
detect: (env = process.env) =>
|
|
2235
|
+
detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(path7.join(geminiDir(env), "tmp")),
|
|
1715
2236
|
configFiles: (env = process.env) => [settings(env)],
|
|
1716
2237
|
install: (cmd, env = process.env, opts = {}) => {
|
|
1717
|
-
const
|
|
2238
|
+
const r = installClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
|
|
1718
2239
|
const a = geminiHooksActive(settings(env));
|
|
1719
|
-
return [a.note && !
|
|
2240
|
+
return [a.note && !r.skipped ? { ...r, note: [r.note, a.note].filter(Boolean).join("; ") } : r];
|
|
1720
2241
|
},
|
|
1721
2242
|
uninstall: (env = process.env, opts = {}) => [
|
|
1722
2243
|
uninstallClaudeShaped(settings(env), GEMINI_EVENTS, { created: opts.created?.includes(settings(env)) })
|
|
1723
2244
|
],
|
|
1724
2245
|
installed: (cmd, env = process.env, opts = {}) => {
|
|
1725
|
-
const state = installedClaudeShaped(settings(env),
|
|
2246
|
+
const state = installedClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
|
|
1726
2247
|
const a = geminiHooksActive(settings(env));
|
|
1727
2248
|
return [
|
|
1728
2249
|
{
|
|
@@ -1736,14 +2257,15 @@ var geminiCli = {
|
|
|
1736
2257
|
const event = input.argv[1] ?? "";
|
|
1737
2258
|
let s = {};
|
|
1738
2259
|
try {
|
|
1739
|
-
|
|
2260
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
2261
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1740
2262
|
} catch {
|
|
1741
2263
|
}
|
|
1742
2264
|
const id = s.session_id;
|
|
1743
2265
|
if (!id) return null;
|
|
1744
2266
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
1745
|
-
const tool =
|
|
1746
|
-
const ev = (type, data) => ({ type, data:
|
|
2267
|
+
const tool = validTool5(s.tool_name) ? s.tool_name : void 0;
|
|
2268
|
+
const ev = (type, data) => ({ type, data: prune5(data), harnessEvent: event });
|
|
1747
2269
|
const events = [];
|
|
1748
2270
|
switch (event) {
|
|
1749
2271
|
case "SessionStart":
|
|
@@ -1801,9 +2323,9 @@ var geminiCli = {
|
|
|
1801
2323
|
default:
|
|
1802
2324
|
return null;
|
|
1803
2325
|
}
|
|
1804
|
-
const
|
|
1805
|
-
if (typeof s.transcript_path === "string")
|
|
1806
|
-
return
|
|
2326
|
+
const r = { session, events, hints: {} };
|
|
2327
|
+
if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
|
|
2328
|
+
return r;
|
|
1807
2329
|
},
|
|
1808
2330
|
readTranscript(file, fromLine) {
|
|
1809
2331
|
let text = "";
|
|
@@ -1824,7 +2346,7 @@ var geminiCli = {
|
|
|
1824
2346
|
const raw = first ? first.content ?? first.message ?? first.text : void 0;
|
|
1825
2347
|
const text = typeof raw === "string" ? raw.trim() : "";
|
|
1826
2348
|
if (text) {
|
|
1827
|
-
const line = text.split("\n")
|
|
2349
|
+
const line = text.split("\n")[0];
|
|
1828
2350
|
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
1829
2351
|
out.title_source = "first-ask";
|
|
1830
2352
|
}
|
|
@@ -1838,31 +2360,47 @@ var geminiCli = {
|
|
|
1838
2360
|
backfill: () => []
|
|
1839
2361
|
// chat logs are keyed by a project hash, not a session id we can name; the first hook of a resumed session reports it
|
|
1840
2362
|
};
|
|
1841
|
-
function
|
|
2363
|
+
function prune5(o) {
|
|
1842
2364
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
1843
2365
|
return o;
|
|
1844
2366
|
}
|
|
1845
2367
|
|
|
1846
2368
|
// ../core/src/adapters/index.ts
|
|
1847
|
-
var ADAPTERS = [claudeCode, codex, geminiCli, antigravity];
|
|
2369
|
+
var ADAPTERS = [claudeCode, codex, geminiCli, antigravity, devin];
|
|
1848
2370
|
var adapterByName = (name) => ADAPTERS.find((a) => a.name === name);
|
|
1849
2371
|
|
|
2372
|
+
// ../core/src/launched-by.ts
|
|
2373
|
+
var LAUNCHED_BY_ENV = "SESSIONPIPE_LAUNCHED_BY";
|
|
2374
|
+
var LAUNCHED_BY = /^[a-z][a-z0-9-]{0,39}:[A-Za-z0-9._:-]{1,128}$/;
|
|
2375
|
+
function launchedBy(env) {
|
|
2376
|
+
const v = env[LAUNCHED_BY_ENV]?.trim();
|
|
2377
|
+
return v && LAUNCHED_BY.test(v) ? v : void 0;
|
|
2378
|
+
}
|
|
2379
|
+
function readHook(adapter, input) {
|
|
2380
|
+
const r = adapter.fromHook(input);
|
|
2381
|
+
if (r && !r.session.parent_id) {
|
|
2382
|
+
const launcher = launchedBy(input.env);
|
|
2383
|
+
if (launcher && launcher !== `${adapter.name}:${r.session.id}`) r.session.launched_by = launcher;
|
|
2384
|
+
}
|
|
2385
|
+
return r;
|
|
2386
|
+
}
|
|
2387
|
+
|
|
1850
2388
|
// src/index.ts
|
|
1851
2389
|
function matches(expect, got, pathStr = "") {
|
|
1852
2390
|
if (expect === "*") return null;
|
|
1853
2391
|
if (Array.isArray(expect)) {
|
|
1854
2392
|
if (!Array.isArray(got) || got.length !== expect.length) return `${pathStr}: expected ${expect.length} items`;
|
|
1855
2393
|
for (let i = 0; i < expect.length; i++) {
|
|
1856
|
-
const
|
|
1857
|
-
if (
|
|
2394
|
+
const r = matches(expect[i], got[i], `${pathStr}[${i}]`);
|
|
2395
|
+
if (r) return r;
|
|
1858
2396
|
}
|
|
1859
2397
|
return null;
|
|
1860
2398
|
}
|
|
1861
2399
|
if (expect && typeof expect === "object") {
|
|
1862
2400
|
if (!got || typeof got !== "object") return `${pathStr}: expected an object`;
|
|
1863
2401
|
for (const [k, v] of Object.entries(expect)) {
|
|
1864
|
-
const
|
|
1865
|
-
if (
|
|
2402
|
+
const r = matches(v, got[k], `${pathStr}.${k}`);
|
|
2403
|
+
if (r) return r;
|
|
1866
2404
|
}
|
|
1867
2405
|
return null;
|
|
1868
2406
|
}
|
|
@@ -1877,51 +2415,64 @@ async function runAdapterFixtures(files) {
|
|
|
1877
2415
|
results.push({ file, ok: false, why: `no adapter ${fx.harness}` });
|
|
1878
2416
|
continue;
|
|
1879
2417
|
}
|
|
1880
|
-
const
|
|
2418
|
+
const r = readHook(adapter, {
|
|
1881
2419
|
argv: fx.input.argv,
|
|
1882
2420
|
stdin: fx.input.stdin,
|
|
1883
2421
|
env: fx.input.env ?? {},
|
|
1884
2422
|
cwd: fx.input.cwd ?? "/"
|
|
1885
2423
|
});
|
|
1886
|
-
if (!
|
|
1887
|
-
results.push({ file, ok: false, why: "fromHook returned null" });
|
|
2424
|
+
if (!r) {
|
|
2425
|
+
results.push(fx.expect.length ? { file, ok: false, why: "fromHook returned null" } : { file, ok: true });
|
|
1888
2426
|
continue;
|
|
1889
2427
|
}
|
|
1890
2428
|
let why = matches(
|
|
1891
2429
|
fx.expect.map((e) => e.type),
|
|
1892
|
-
|
|
2430
|
+
r.events.map((e) => e.type),
|
|
1893
2431
|
"types"
|
|
1894
2432
|
);
|
|
1895
2433
|
if (!why)
|
|
1896
2434
|
for (const [i, e] of fx.expect.entries()) {
|
|
1897
|
-
const got =
|
|
1898
|
-
why = matches(e.harness.event, got
|
|
2435
|
+
const got = r.events[i];
|
|
2436
|
+
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`);
|
|
1899
2437
|
if (why) break;
|
|
1900
2438
|
}
|
|
1901
2439
|
results.push(why ? { file, ok: false, why } : { file, ok: true });
|
|
1902
2440
|
}
|
|
1903
|
-
const failed = results.filter((
|
|
1904
|
-
const table = results.map((
|
|
2441
|
+
const failed = results.filter((r) => !r.ok).length;
|
|
2442
|
+
const table = results.map((r) => `${r.ok ? "ok " : "FAIL"} ${r.file}${r.why ? ` \u2014 ${r.why}` : ""}`).join("\n");
|
|
1905
2443
|
return { results, failed, table };
|
|
1906
2444
|
}
|
|
1907
2445
|
|
|
1908
2446
|
// src/cli.ts
|
|
1909
|
-
var target = process.argv[2];
|
|
1910
|
-
if (!target) {
|
|
1911
|
-
process.stdout.write("usage: sessionpipe-conformance <conformance/harness dir | https://receiver>\n");
|
|
1912
|
-
process.exit(2);
|
|
1913
|
-
}
|
|
1914
|
-
if (/^https?:\/\//.test(target)) {
|
|
1915
|
-
process.stdout.write("receiver scenarios land in M3\n");
|
|
1916
|
-
process.exit(2);
|
|
1917
|
-
}
|
|
1918
2447
|
var walk = (d) => readdirSync5(d).flatMap((n) => {
|
|
1919
|
-
const p =
|
|
2448
|
+
const p = path8.join(d, n);
|
|
1920
2449
|
return statSync4(p).isDirectory() ? walk(p) : p.endsWith(".json") ? [p] : [];
|
|
1921
2450
|
});
|
|
1922
|
-
|
|
1923
|
-
|
|
2451
|
+
async function main(argv, io = process) {
|
|
2452
|
+
const target = argv[0];
|
|
2453
|
+
if (!target) {
|
|
2454
|
+
io.stdout.write("usage: sessionpipe-conformance <conformance/harness dir | https://receiver>\n");
|
|
2455
|
+
return 2;
|
|
2456
|
+
}
|
|
2457
|
+
if (/^https?:\/\//.test(target)) {
|
|
2458
|
+
io.stdout.write("receiver scenarios land in M3\n");
|
|
2459
|
+
return 2;
|
|
2460
|
+
}
|
|
2461
|
+
const r = await runAdapterFixtures(walk(target));
|
|
2462
|
+
io.stdout.write(`${r.table}
|
|
1924
2463
|
${r.results.length - r.failed}/${r.results.length} passed
|
|
1925
2464
|
`);
|
|
1926
|
-
|
|
2465
|
+
return r.failed ? 1 : 0;
|
|
2466
|
+
}
|
|
2467
|
+
function isMain() {
|
|
2468
|
+
try {
|
|
2469
|
+
return !!process.argv[1] && import.meta.url === pathToFileURL(realpathSync2(process.argv[1])).href;
|
|
2470
|
+
} catch {
|
|
2471
|
+
return false;
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
if (isMain()) process.exit(await main(process.argv.slice(2)));
|
|
2475
|
+
export {
|
|
2476
|
+
main
|
|
2477
|
+
};
|
|
1927
2478
|
//# sourceMappingURL=cli.js.map
|