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/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// sessionpipe-conformance 0.
|
|
1
|
+
// sessionpipe-conformance 0.10.0 · Apache-2.0 · https://sessionpipe.org
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { readFileSync as readFileSync6 } from "node:fs";
|
|
@@ -18,7 +18,7 @@ import path3 from "node:path";
|
|
|
18
18
|
|
|
19
19
|
// ../core/src/paths.ts
|
|
20
20
|
import os from "node:os";
|
|
21
|
-
var
|
|
21
|
+
var homeDir = () => os.homedir();
|
|
22
22
|
|
|
23
23
|
// ../core/src/readers/files.ts
|
|
24
24
|
import { closeSync, fstatSync, openSync, readdirSync, readSync, statSync } from "node:fs";
|
|
@@ -110,6 +110,19 @@ function recentFiles(root, depth, sinceMs, match) {
|
|
|
110
110
|
return out;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
// ../core/src/adapters/codex-rollout.ts
|
|
114
|
+
function codexSubagent(meta) {
|
|
115
|
+
if (meta?.type !== "session_meta") return false;
|
|
116
|
+
const p = meta.payload;
|
|
117
|
+
return !!p?.source?.subagent;
|
|
118
|
+
}
|
|
119
|
+
function codexReplayBoundary(meta) {
|
|
120
|
+
if (meta?.type !== "session_meta") return void 0;
|
|
121
|
+
const p = meta.payload;
|
|
122
|
+
if (!p || !p.forked_from_id && !p.source?.subagent) return void 0;
|
|
123
|
+
return (record) => typeof p.id === "string" && !!p.id && record?.type === "event_msg" && record.payload?.thread_id === p.id;
|
|
124
|
+
}
|
|
125
|
+
|
|
113
126
|
// ../core/src/readers/transcripts.ts
|
|
114
127
|
function pairTurns(msgs, endLine) {
|
|
115
128
|
const turns = [];
|
|
@@ -132,10 +145,14 @@ ${m.text}`;
|
|
|
132
145
|
}
|
|
133
146
|
if (cur?.assistant) turns.push(cur);
|
|
134
147
|
const line = cur && !cur.assistant ? cur.startLine : endLine;
|
|
135
|
-
|
|
148
|
+
const out = {
|
|
149
|
+
turns: turns.map((t) => ({ user: t.user, assistant: t.assistant, at: t.at, endLine: t.endLine })),
|
|
150
|
+
line
|
|
151
|
+
};
|
|
152
|
+
if (cur && !cur.assistant && cur.user) out.open = { user: cur.user, at: cur.at };
|
|
153
|
+
return out;
|
|
136
154
|
}
|
|
137
|
-
var isInjected = (text) => /^<[a-z_-]
|
|
138
|
-
var endOf = (lines) => lines[lines.length - 1] === "" ? lines.length - 1 : lines.length;
|
|
155
|
+
var isInjected = (text) => /^<[a-z_-]+(?:\s[^>]*)?>/i.test(text) && /<\/[a-z_-]+>\s*$/i.test(text) || /^<!--\s/.test(text) || /^<command-/.test(text);
|
|
139
156
|
var parse = (raw) => {
|
|
140
157
|
try {
|
|
141
158
|
return JSON.parse(raw);
|
|
@@ -143,6 +160,11 @@ var parse = (raw) => {
|
|
|
143
160
|
return null;
|
|
144
161
|
}
|
|
145
162
|
};
|
|
163
|
+
var endOf = (lines) => {
|
|
164
|
+
const last = lines[lines.length - 1];
|
|
165
|
+
if (last === void 0) return 0;
|
|
166
|
+
return last === "" || parse(last) === null ? lines.length - 1 : lines.length;
|
|
167
|
+
};
|
|
146
168
|
var textParts = (c, kinds) => {
|
|
147
169
|
if (typeof c === "string") return c;
|
|
148
170
|
if (!Array.isArray(c)) return "";
|
|
@@ -150,13 +172,21 @@ var textParts = (c, kinds) => {
|
|
|
150
172
|
(b) => b && typeof b === "object" && kinds.includes(b.type ?? "") && typeof b.text === "string"
|
|
151
173
|
).map((b) => b.text).join("\n");
|
|
152
174
|
};
|
|
175
|
+
var queuedPrompt = (a) => {
|
|
176
|
+
if (!a || typeof a !== "object") return "";
|
|
177
|
+
const q = a;
|
|
178
|
+
if (q.type !== "queued_command" || (q.commandMode ?? "prompt") !== "prompt") return "";
|
|
179
|
+
if (q.origin?.kind && q.origin.kind !== "human") return "";
|
|
180
|
+
return textParts(q.prompt, ["text"]).trim();
|
|
181
|
+
};
|
|
153
182
|
function readClaude(lines, from) {
|
|
154
183
|
const msgs = [];
|
|
155
184
|
for (let i = from; i < lines.length; i++) {
|
|
156
185
|
const j = lines[i] ? parse(lines[i]) : null;
|
|
157
186
|
if (!j || j.isSidechain) continue;
|
|
158
187
|
const origin = j.origin;
|
|
159
|
-
if (j.type === "user" && (j.isMeta || origin?.kind && origin.kind !== "human"))
|
|
188
|
+
if (j.type === "user" && (j.isMeta || j.isCompactSummary || j.repeatsCompactSummary || origin?.kind && origin.kind !== "human"))
|
|
189
|
+
continue;
|
|
160
190
|
const at = Date.parse(String(j.timestamp ?? "")) || Date.now();
|
|
161
191
|
const c = j.message?.content;
|
|
162
192
|
if (j.type === "user") {
|
|
@@ -166,6 +196,9 @@ function readClaude(lines, from) {
|
|
|
166
196
|
} else if (j.type === "assistant" && Array.isArray(c)) {
|
|
167
197
|
const text = textParts(c, ["text"]).trim();
|
|
168
198
|
if (text) msgs.push({ role: "assistant", text, at, line: i });
|
|
199
|
+
} else if (j.type === "attachment") {
|
|
200
|
+
const text = queuedPrompt(j.attachment);
|
|
201
|
+
if (text && !isInjected(text)) msgs.push({ role: "user", text, at, line: i });
|
|
169
202
|
}
|
|
170
203
|
}
|
|
171
204
|
return pairTurns(msgs, endOf(lines));
|
|
@@ -175,7 +208,10 @@ function codexInjected(text) {
|
|
|
175
208
|
}
|
|
176
209
|
function readCodex(lines, from) {
|
|
177
210
|
const msgs = [];
|
|
178
|
-
|
|
211
|
+
const boundary = codexReplayBoundary(parse(lines.find((line) => line.trim()) ?? ""));
|
|
212
|
+
const start = boundary ? lines.findIndex((line) => boundary(parse(line))) : 0;
|
|
213
|
+
if (start < 0) return pairTurns(msgs, endOf(lines));
|
|
214
|
+
for (let i = Math.max(from, start); i < lines.length; i++) {
|
|
179
215
|
const j = lines[i] ? parse(lines[i]) : null;
|
|
180
216
|
if (!j || j.type !== "response_item") continue;
|
|
181
217
|
const p = j.payload;
|
|
@@ -213,7 +249,7 @@ function readAntigravity(lines, from) {
|
|
|
213
249
|
const at = Date.parse(String(j.created_at ?? j.timestamp ?? "")) || Date.now();
|
|
214
250
|
if (j.type === "USER_INPUT") {
|
|
215
251
|
const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
|
|
216
|
-
const text = (m ? m[1]
|
|
252
|
+
const text = (m ? m[1] : j.content).trim();
|
|
217
253
|
if (text) msgs.push({ role: "user", text, at, line: i });
|
|
218
254
|
} else if (j.type === "PLANNER_RESPONSE") {
|
|
219
255
|
const text = j.content.trim();
|
|
@@ -261,6 +297,10 @@ function formatJson(value, f) {
|
|
|
261
297
|
const text = JSON.stringify(value, null, f.indent).replace(/\n/g, f.eol);
|
|
262
298
|
return f.finalNewline ? text + f.eol : text;
|
|
263
299
|
}
|
|
300
|
+
function readJson(file) {
|
|
301
|
+
const f = readJsonFile(file);
|
|
302
|
+
return f.ok ? f.value : {};
|
|
303
|
+
}
|
|
264
304
|
function entryFor(event, harness, cmd, o) {
|
|
265
305
|
const matcher = o.matcherFor(event);
|
|
266
306
|
return {
|
|
@@ -330,8 +370,7 @@ function uninstallClaudeShaped(file, events, opts = {}) {
|
|
|
330
370
|
unlinkSync(file);
|
|
331
371
|
return { file, changed: true, note: "removed (sessionpipe created it)" };
|
|
332
372
|
}
|
|
333
|
-
|
|
334
|
-
if (next !== f.raw) writeFileSync(file, next);
|
|
373
|
+
writeFileSync(file, formatJson(settings2, f));
|
|
335
374
|
return { file, changed: true };
|
|
336
375
|
}
|
|
337
376
|
function installedClaudeShaped(file, harness, events, cmd, o) {
|
|
@@ -358,7 +397,7 @@ var HOOK_NAME = "sessionpipe";
|
|
|
358
397
|
var AG_EVENTS = ["PreInvocation", "PostInvocation", "PostToolUse", "Stop"];
|
|
359
398
|
var AG_STALE_PRETOOL = '{"decision":"ask","reason":"sessionpipe: an outdated PreToolUse hook is still loaded. Restart this conversation (or run sessionpipe install) to drop it."}\n';
|
|
360
399
|
var antigravityAnswer = (event) => event === "PreToolUse" ? AG_STALE_PRETOOL : "{}\n";
|
|
361
|
-
var gemini = (env) => env.GEMINI_CLI_HOME || path3.join(
|
|
400
|
+
var gemini = (env) => env.GEMINI_CLI_HOME || path3.join(homeDir(), ".gemini");
|
|
362
401
|
var hooksFile = (env) => path3.join(gemini(env), "config", "hooks.json");
|
|
363
402
|
var projectsDir = (env) => path3.join(gemini(env), "config", "projects");
|
|
364
403
|
var dataDirs = (env) => ["antigravity", "antigravity-cli", "antigravity-ide"].map((d) => path3.join(gemini(env), d));
|
|
@@ -424,7 +463,8 @@ var antigravity = {
|
|
|
424
463
|
const event = input.argv[1] ?? "";
|
|
425
464
|
let s = {};
|
|
426
465
|
try {
|
|
427
|
-
|
|
466
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
467
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
428
468
|
} catch {
|
|
429
469
|
}
|
|
430
470
|
const id = s.conversationId ?? s.session_id ?? s.sessionId;
|
|
@@ -480,19 +520,16 @@ var antigravity = {
|
|
|
480
520
|
}
|
|
481
521
|
return readAntigravity(text.split("\n"), fromLine);
|
|
482
522
|
},
|
|
483
|
-
facts(session, transcript, hints
|
|
523
|
+
facts(session, transcript, hints) {
|
|
484
524
|
if (!transcript) return {};
|
|
485
525
|
return antigravityFacts(transcript, {
|
|
486
526
|
conversation: session.id,
|
|
487
527
|
workspaces: hints.workspaces ?? (session.cwd ? [session.cwd] : []),
|
|
488
|
-
state,
|
|
489
528
|
env: process.env
|
|
490
529
|
});
|
|
491
530
|
},
|
|
492
531
|
backfill(sinceMs, env = process.env) {
|
|
493
|
-
const
|
|
494
|
-
const noState = { get: () => ({}), save: () => {
|
|
495
|
-
} };
|
|
532
|
+
const rows2 = [];
|
|
496
533
|
for (const data of dataDirs(env)) {
|
|
497
534
|
let ids = [];
|
|
498
535
|
try {
|
|
@@ -506,13 +543,13 @@ var antigravity = {
|
|
|
506
543
|
} catch {
|
|
507
544
|
continue;
|
|
508
545
|
}
|
|
509
|
-
const facts = antigravityFacts(f, { conversation: id, workspaces: [],
|
|
546
|
+
const facts = antigravityFacts(f, { conversation: id, workspaces: [], env });
|
|
510
547
|
if (!facts.started_at || !facts.last_at) continue;
|
|
511
548
|
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
512
|
-
|
|
549
|
+
rows2.push({ session: { id, ...rest }, started_at, last_at });
|
|
513
550
|
}
|
|
514
551
|
}
|
|
515
|
-
return
|
|
552
|
+
return rows2;
|
|
516
553
|
}
|
|
517
554
|
};
|
|
518
555
|
function prune(o) {
|
|
@@ -562,7 +599,7 @@ function agProjects(dir) {
|
|
|
562
599
|
}
|
|
563
600
|
const folders = [];
|
|
564
601
|
for (const r of j?.projectResources?.resources ?? []) {
|
|
565
|
-
const f = agPath(r?.folderUri);
|
|
602
|
+
const f = agPath(r?.folderUri ?? r?.gitFolder?.folderUri);
|
|
566
603
|
if (f) folders.push(...agVariants(f));
|
|
567
604
|
}
|
|
568
605
|
if (folders.length) out.push({ id, folders });
|
|
@@ -616,7 +653,7 @@ function antigravityFacts(file, opts) {
|
|
|
616
653
|
for (const j of H) {
|
|
617
654
|
if (j.type !== "USER_INPUT" || typeof j.content !== "string") continue;
|
|
618
655
|
const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
|
|
619
|
-
const text = (m ? m[1]
|
|
656
|
+
const text = (m ? m[1] : j.content).trim();
|
|
620
657
|
const line = (text.split("\n").find((l) => l.trim()) ?? "").trim();
|
|
621
658
|
if (!line || /^<[a-z_-]+>/i.test(line)) continue;
|
|
622
659
|
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
@@ -664,10 +701,9 @@ function gitFacts(cwd) {
|
|
|
664
701
|
return out;
|
|
665
702
|
}
|
|
666
703
|
function stripCredentials(url) {
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
return u.replace(/^https?:\/\//, "").replace(/\.git$/, "");
|
|
704
|
+
const ssh = /^(?:ssh:\/\/)?git@([^:/]+)[:/](.+?)(?:\.git)?$/.exec(url);
|
|
705
|
+
if (ssh) return `${ssh[1]}/${ssh[2]}`;
|
|
706
|
+
return url.replace(/\/\/[^/@]*@/, "//").replace(/^https?:\/\//, "").replace(/\.git$/, "");
|
|
671
707
|
}
|
|
672
708
|
|
|
673
709
|
// ../core/src/adapters/claude-code.ts
|
|
@@ -698,7 +734,7 @@ var OPTS = {
|
|
|
698
734
|
timeoutFor: (e) => e === "PermissionRequest" ? 130 : void 0
|
|
699
735
|
};
|
|
700
736
|
var DESKTOP_ID = /^local_[A-Za-z0-9-]{1,64}$/;
|
|
701
|
-
function claudeDirs(env = process.env, home = env.HOME ||
|
|
737
|
+
function claudeDirs(env = process.env, home = env.HOME || homeDir()) {
|
|
702
738
|
const out = [];
|
|
703
739
|
const add = (d) => {
|
|
704
740
|
if (!d) return;
|
|
@@ -739,11 +775,13 @@ var claudeCode = {
|
|
|
739
775
|
const event = input.argv[1] ?? "";
|
|
740
776
|
let s = {};
|
|
741
777
|
try {
|
|
742
|
-
|
|
778
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
779
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
743
780
|
} catch {
|
|
744
781
|
}
|
|
745
782
|
const id = s.session_id ?? s.sessionId;
|
|
746
783
|
if (!id) return null;
|
|
784
|
+
if (notClaudeCode(String(id), s)) return null;
|
|
747
785
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
748
786
|
if (typeof s.model === "string") session.model = s.model;
|
|
749
787
|
if (typeof s.agent_id === "string" && event.startsWith("Subagent") === false && s.agent_id)
|
|
@@ -760,14 +798,28 @@ var claudeCode = {
|
|
|
760
798
|
case "SessionStart":
|
|
761
799
|
events.push(ev("session.started", { source: pickSource(s.source ?? s.trigger) }));
|
|
762
800
|
break;
|
|
763
|
-
case "UserPromptSubmit":
|
|
801
|
+
case "UserPromptSubmit": {
|
|
802
|
+
const prompt = typeof s.prompt === "string" ? s.prompt : void 0;
|
|
764
803
|
events.push(
|
|
765
804
|
ev("turn.started", {
|
|
766
805
|
turn_id,
|
|
767
|
-
prompt_chars:
|
|
806
|
+
prompt_chars: prompt === void 0 ? void 0 : [...prompt].length,
|
|
807
|
+
// Claude Code's hook schema documents `source`
|
|
808
|
+
// (user | sdk | system | loop_wakeup | schedule_wakeup | poll_event) and says a
|
|
809
|
+
// payload may omit it while the field rolls out; 2.1.304's payload has no such
|
|
810
|
+
// key at all. Forwarded verbatim, shape-guarded only: the list is the harness's
|
|
811
|
+
// to extend.
|
|
812
|
+
submitted_by: harnessWord(s.source),
|
|
813
|
+
// One of Claude Code's own prompt wrappers: the prompt is a block the harness
|
|
814
|
+
// wrote, so it is not the person's words. Absent is unknown, never "the person" —
|
|
815
|
+
// and `true` is never "no person caused this turn", because a Talk or
|
|
816
|
+
// `sessionpipe wait` delivery hands the person's own message over inside a
|
|
817
|
+
// <task-notification> (PROTOCOL.md §4).
|
|
818
|
+
injected: prompt !== void 0 && injectedBlock(prompt) ? true : void 0
|
|
768
819
|
})
|
|
769
820
|
);
|
|
770
821
|
break;
|
|
822
|
+
}
|
|
771
823
|
case "PreToolUse":
|
|
772
824
|
if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, agent_id, input: s.tool_input }));
|
|
773
825
|
break;
|
|
@@ -822,7 +874,13 @@ var claudeCode = {
|
|
|
822
874
|
);
|
|
823
875
|
break;
|
|
824
876
|
case "Stop":
|
|
825
|
-
events.push(
|
|
877
|
+
events.push(
|
|
878
|
+
ev("turn.ended", {
|
|
879
|
+
turn_id,
|
|
880
|
+
reason: "stop",
|
|
881
|
+
background: Array.isArray(s.background_tasks) ? s.background_tasks.length : void 0
|
|
882
|
+
})
|
|
883
|
+
);
|
|
826
884
|
break;
|
|
827
885
|
case "StopFailure":
|
|
828
886
|
events.push(
|
|
@@ -862,8 +920,11 @@ var claudeCode = {
|
|
|
862
920
|
return null;
|
|
863
921
|
}
|
|
864
922
|
const hints = {};
|
|
923
|
+
if (event === "UserPromptSubmit" && typeof s.prompt === "string") hints.prompt = s.prompt;
|
|
865
924
|
const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
|
|
866
925
|
if (host && DESKTOP_ID.test(host)) hints.desktop_id = host;
|
|
926
|
+
const attended = input.env.CLAUDE_CODE_SESSION_ATTENDED;
|
|
927
|
+
if (attended === "1" || attended === "0") hints.attended = attended === "1" || !!input.env.SESSIONPIPE_CONTROL_JOB;
|
|
867
928
|
if (event !== "PreToolUse" && event !== "PostToolUse") hints.claude_pid = claudePid(input.env);
|
|
868
929
|
const r = { session, events, hints };
|
|
869
930
|
if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
|
|
@@ -884,10 +945,11 @@ var claudeCode = {
|
|
|
884
945
|
const cwd = out.cwd ?? session.cwd;
|
|
885
946
|
Object.assign(out, gitFacts(cwd), out.branch ? { branch: out.branch } : {});
|
|
886
947
|
if (!out.url && typeof hints.desktop_id === "string") out.url = `claude://code/continue/${hints.desktop_id}`;
|
|
948
|
+
if (typeof hints.attended === "boolean") out.attended = hints.attended || !!out.url;
|
|
887
949
|
return out;
|
|
888
950
|
},
|
|
889
951
|
backfill(sinceMs, env = process.env) {
|
|
890
|
-
const
|
|
952
|
+
const rows2 = [];
|
|
891
953
|
const files = [];
|
|
892
954
|
for (const d of claudeDirs(env))
|
|
893
955
|
files.push(...recentFiles(path4.join(d, "projects"), 1, sinceMs, (n) => /^[0-9a-f-]{36}\.jsonl$/.test(n)));
|
|
@@ -899,22 +961,29 @@ var claudeCode = {
|
|
|
899
961
|
if (!facts.started_at || !facts.last_at) continue;
|
|
900
962
|
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
901
963
|
const cwd = rest.cwd;
|
|
902
|
-
|
|
964
|
+
rows2.push({
|
|
903
965
|
session: { id, ...rest, ...gitFacts(cwd), ...rest.branch ? { branch: rest.branch } : {} },
|
|
904
966
|
started_at,
|
|
905
967
|
last_at
|
|
906
968
|
});
|
|
907
969
|
}
|
|
908
|
-
return
|
|
970
|
+
return rows2;
|
|
909
971
|
}
|
|
910
972
|
};
|
|
911
973
|
function prune2(o) {
|
|
912
974
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
913
975
|
return o;
|
|
914
976
|
}
|
|
977
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
978
|
+
function notClaudeCode(id, s) {
|
|
979
|
+
return typeof s.transcript_path !== "string" && !UUID.test(id);
|
|
980
|
+
}
|
|
915
981
|
function pickSource(v) {
|
|
916
982
|
return typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
|
|
917
983
|
}
|
|
984
|
+
var harnessWord = (v) => typeof v === "string" && /^[A-Za-z0-9][A-Za-z0-9_.-]{0,39}$/.test(v) ? v : void 0;
|
|
985
|
+
var WRAPPER = /^<(?:task-notification|cross-session-message|system-reminder|local-command-stdout|command-[a-z-]+)[\s/>]/i;
|
|
986
|
+
var injectedBlock = (prompt) => WRAPPER.test(prompt.trim());
|
|
918
987
|
function errText(v) {
|
|
919
988
|
if (typeof v === "string") return v.slice(0, 2e3);
|
|
920
989
|
if (v && typeof v === "object") {
|
|
@@ -1017,23 +1086,47 @@ function parentOf() {
|
|
|
1017
1086
|
}
|
|
1018
1087
|
return (pid) => table.get(pid) || 0;
|
|
1019
1088
|
}
|
|
1020
|
-
var
|
|
1021
|
-
function
|
|
1022
|
-
const hit = accountCache.get(dir);
|
|
1023
|
-
if (hit !== void 0) return hit;
|
|
1089
|
+
var EMAIL_RE = /^[^@\s]{1,64}@[^@\s]{1,189}$/;
|
|
1090
|
+
function claudeLogin(dir, home = homeDir()) {
|
|
1024
1091
|
const files = [path4.join(dir, ".claude.json")];
|
|
1025
|
-
if (path4.resolve(dir) === path4.join(
|
|
1026
|
-
let acct = null;
|
|
1092
|
+
if (path4.resolve(dir) === path4.join(home, ".claude")) files.unshift(path4.join(home, ".claude.json"));
|
|
1027
1093
|
for (const f of files) {
|
|
1028
1094
|
try {
|
|
1029
1095
|
const a = JSON.parse(readFileSync3(f, "utf8")).oauthAccount;
|
|
1030
|
-
if (a?.accountUuid) {
|
|
1031
|
-
|
|
1032
|
-
|
|
1096
|
+
if (typeof a?.accountUuid === "string" && a.accountUuid) {
|
|
1097
|
+
const email = typeof a.emailAddress === "string" && EMAIL_RE.test(a.emailAddress) ? a.emailAddress : void 0;
|
|
1098
|
+
return { id: a.accountUuid.slice(0, 128), ...email ? { email } : {} };
|
|
1033
1099
|
}
|
|
1034
1100
|
} catch {
|
|
1035
1101
|
}
|
|
1036
1102
|
}
|
|
1103
|
+
return apiAccount(dir);
|
|
1104
|
+
}
|
|
1105
|
+
var ANTHROPIC_HOST = /(^|\.)anthropic\.com$/i;
|
|
1106
|
+
function apiAccount(dir) {
|
|
1107
|
+
let base;
|
|
1108
|
+
try {
|
|
1109
|
+
base = JSON.parse(readFileSync3(settingsIn(dir), "utf8")).env?.ANTHROPIC_BASE_URL;
|
|
1110
|
+
} catch {
|
|
1111
|
+
return null;
|
|
1112
|
+
}
|
|
1113
|
+
if (typeof base !== "string") return null;
|
|
1114
|
+
let u;
|
|
1115
|
+
try {
|
|
1116
|
+
u = new URL(base.trim());
|
|
1117
|
+
} catch {
|
|
1118
|
+
return null;
|
|
1119
|
+
}
|
|
1120
|
+
if (u.protocol !== "https:" && u.protocol !== "http:" || !u.hostname || ANTHROPIC_HOST.test(u.hostname))
|
|
1121
|
+
return null;
|
|
1122
|
+
const origin = `${u.protocol}//${u.host}`;
|
|
1123
|
+
return { id: `api-${createHash("sha256").update(origin).digest("hex").slice(0, 16)}`, name: u.host };
|
|
1124
|
+
}
|
|
1125
|
+
var accountCache = /* @__PURE__ */ new Map();
|
|
1126
|
+
function claudeAccount(dir) {
|
|
1127
|
+
const hit = accountCache.get(dir);
|
|
1128
|
+
if (hit !== void 0) return hit;
|
|
1129
|
+
const acct = claudeLogin(dir)?.id ?? null;
|
|
1037
1130
|
accountCache.set(dir, acct);
|
|
1038
1131
|
return acct;
|
|
1039
1132
|
}
|
|
@@ -1253,18 +1346,13 @@ function parseArgv(raw) {
|
|
|
1253
1346
|
function readNotify(text) {
|
|
1254
1347
|
const { head, tail, at } = split(text);
|
|
1255
1348
|
const m = NOTIFY_LINE.exec(head);
|
|
1256
|
-
if (m)
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
index: m.index,
|
|
1261
|
-
length: m[0].length,
|
|
1262
|
-
misplaced: false,
|
|
1263
|
-
other: !OURS2.test(m[1] ?? "")
|
|
1264
|
-
};
|
|
1349
|
+
if (m) {
|
|
1350
|
+
const raw = m[1];
|
|
1351
|
+
return { raw, argv: parseArgv(raw), index: m.index, length: m[0].length, misplaced: false, other: !OURS2.test(raw) };
|
|
1352
|
+
}
|
|
1265
1353
|
const k = NOTIFY_KEY.exec(head);
|
|
1266
1354
|
if (k) {
|
|
1267
|
-
const rest = head.slice(k.index).split("\n")[0]
|
|
1355
|
+
const rest = head.slice(k.index).split("\n")[0];
|
|
1268
1356
|
return {
|
|
1269
1357
|
raw: rest.replace(NOTIFY_KEY, "").trim(),
|
|
1270
1358
|
argv: null,
|
|
@@ -1275,15 +1363,11 @@ function readNotify(text) {
|
|
|
1275
1363
|
};
|
|
1276
1364
|
}
|
|
1277
1365
|
const t = NOTIFY_LINE.exec(tail);
|
|
1278
|
-
if (t
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
argv: parseArgv(t[
|
|
1282
|
-
|
|
1283
|
-
length: t[0].length,
|
|
1284
|
-
misplaced: true,
|
|
1285
|
-
other: false
|
|
1286
|
-
};
|
|
1366
|
+
if (t) {
|
|
1367
|
+
const raw = t[1];
|
|
1368
|
+
if (OURS2.test(raw))
|
|
1369
|
+
return { raw, argv: parseArgv(raw), index: at + t.index, length: t[0].length, misplaced: true, other: false };
|
|
1370
|
+
}
|
|
1287
1371
|
return null;
|
|
1288
1372
|
}
|
|
1289
1373
|
function dropLine(text, index, length) {
|
|
@@ -1302,9 +1386,7 @@ function withNotify(text, line) {
|
|
|
1302
1386
|
if (n?.misplaced) return insertTopLevel(dropLine(text, n.index, n.length), line);
|
|
1303
1387
|
return insertTopLevel(text, line);
|
|
1304
1388
|
}
|
|
1305
|
-
function replaceNotify(text, line) {
|
|
1306
|
-
const n = readNotify(text);
|
|
1307
|
-
if (!n || n.misplaced || !n.length) return null;
|
|
1389
|
+
function replaceNotify(text, n, line) {
|
|
1308
1390
|
return text.slice(0, n.index) + line + text.slice(n.index + n.length);
|
|
1309
1391
|
}
|
|
1310
1392
|
function withoutNotify(text) {
|
|
@@ -1345,7 +1427,7 @@ var CODEX_EVENTS = [
|
|
|
1345
1427
|
];
|
|
1346
1428
|
var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
1347
1429
|
var eventsFor2 = (lean) => lean ? CODEX_EVENTS.filter((e) => e !== "PreToolUse") : [...CODEX_EVENTS];
|
|
1348
|
-
var codexHome = (env) => env.CODEX_HOME || path5.join(
|
|
1430
|
+
var codexHome = (env) => env.CODEX_HOME || path5.join(homeDir(), ".codex");
|
|
1349
1431
|
var hooksFile2 = (env) => path5.join(codexHome(env), "hooks.json");
|
|
1350
1432
|
var configFile = (env) => path5.join(codexHome(env), "config.toml");
|
|
1351
1433
|
var sessionsDir = (env) => path5.join(codexHome(env), "sessions");
|
|
@@ -1383,11 +1465,8 @@ function installNotify(cmd, env) {
|
|
|
1383
1465
|
return { file, changed: false, note: "notify current (after Codex Computer Use)" };
|
|
1384
1466
|
}
|
|
1385
1467
|
if (!prev || OURS2.test(JSON.stringify(prev))) {
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
write(next);
|
|
1389
|
-
return { file, changed: true, note: "chained after Codex Computer Use (--previous-notify)" };
|
|
1390
|
-
}
|
|
1468
|
+
write(replaceNotify(text, n, `notify = ${withPreviousRaw(n.raw, argv)}`));
|
|
1469
|
+
return { file, changed: true, note: "chained after Codex Computer Use (--previous-notify)" };
|
|
1391
1470
|
}
|
|
1392
1471
|
}
|
|
1393
1472
|
return {
|
|
@@ -1416,8 +1495,7 @@ function uninstallNotify(env) {
|
|
|
1416
1495
|
}
|
|
1417
1496
|
const prev = previousNotify(n.argv);
|
|
1418
1497
|
if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev))) {
|
|
1419
|
-
|
|
1420
|
-
if (next) writeFileSync3(file, next);
|
|
1498
|
+
writeFileSync3(file, replaceNotify(text, n, `notify = ${stripPreviousRaw(n.raw)}`));
|
|
1421
1499
|
return { file, changed: true };
|
|
1422
1500
|
}
|
|
1423
1501
|
return { file, changed: false };
|
|
@@ -1447,6 +1525,13 @@ function findRollout(threadId, env = process.env) {
|
|
|
1447
1525
|
walk(sessionsDir(env), 0);
|
|
1448
1526
|
return best;
|
|
1449
1527
|
}
|
|
1528
|
+
function rolloutMeta(file) {
|
|
1529
|
+
return scanLines(file, (line) => parseLines([line])[0] ?? {});
|
|
1530
|
+
}
|
|
1531
|
+
function ofAnotherThread(file, id) {
|
|
1532
|
+
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);
|
|
1533
|
+
return !!m?.[1] && m[1].toLowerCase() !== id.toLowerCase();
|
|
1534
|
+
}
|
|
1450
1535
|
var codex = {
|
|
1451
1536
|
name: NAME3,
|
|
1452
1537
|
events: CODEX_EVENTS,
|
|
@@ -1489,11 +1574,14 @@ var codex = {
|
|
|
1489
1574
|
if (event === "notify") {
|
|
1490
1575
|
const raw = input.argv.find((a) => a.startsWith("{")) ?? input.stdin;
|
|
1491
1576
|
try {
|
|
1492
|
-
|
|
1577
|
+
const j = raw ? JSON.parse(raw) : {};
|
|
1578
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1493
1579
|
} catch {
|
|
1494
1580
|
}
|
|
1495
1581
|
const id2 = s["thread-id"] ?? s.thread_id ?? s["session-id"] ?? s.session_id;
|
|
1496
1582
|
if (!id2) return null;
|
|
1583
|
+
const file = findRollout(String(id2), input.env);
|
|
1584
|
+
if (!file || codexSubagent(rolloutMeta(file))) return null;
|
|
1497
1585
|
const r2 = {
|
|
1498
1586
|
session: { id: String(id2), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd },
|
|
1499
1587
|
events: [
|
|
@@ -1503,18 +1591,19 @@ var codex = {
|
|
|
1503
1591
|
harnessEvent: "notify"
|
|
1504
1592
|
}
|
|
1505
1593
|
],
|
|
1506
|
-
hints: {}
|
|
1594
|
+
hints: {},
|
|
1595
|
+
transcript: file
|
|
1507
1596
|
};
|
|
1508
|
-
const file = findRollout(String(id2), input.env);
|
|
1509
|
-
if (file) r2.transcript = file;
|
|
1510
1597
|
return r2;
|
|
1511
1598
|
}
|
|
1512
1599
|
try {
|
|
1513
|
-
|
|
1600
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
1601
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1514
1602
|
} catch {
|
|
1515
1603
|
}
|
|
1516
|
-
const id = s.session_id;
|
|
1604
|
+
const id = s.session_id ?? s.thread_id ?? s.conversation_id ?? s["thread-id"] ?? s["session-id"];
|
|
1517
1605
|
if (!id) return null;
|
|
1606
|
+
if (s.transcript_path === null) return null;
|
|
1518
1607
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
1519
1608
|
if (typeof s.model === "string") session.model = s.model;
|
|
1520
1609
|
const turn_id = typeof s.turn_id === "string" ? s.turn_id : void 0;
|
|
@@ -1586,7 +1675,8 @@ var codex = {
|
|
|
1586
1675
|
return null;
|
|
1587
1676
|
}
|
|
1588
1677
|
const r = { session, events, hints: {} };
|
|
1589
|
-
if (typeof s.transcript_path === "string"
|
|
1678
|
+
if (typeof s.transcript_path === "string" && !ofAnotherThread(s.transcript_path, String(id)))
|
|
1679
|
+
r.transcript = s.transcript_path;
|
|
1590
1680
|
return r;
|
|
1591
1681
|
},
|
|
1592
1682
|
readTranscript(file, fromLine) {
|
|
@@ -1604,16 +1694,17 @@ var codex = {
|
|
|
1604
1694
|
return out;
|
|
1605
1695
|
},
|
|
1606
1696
|
backfill(sinceMs, env = process.env) {
|
|
1607
|
-
const
|
|
1697
|
+
const rows2 = [];
|
|
1608
1698
|
for (const f of recentFiles(sessionsDir(env), 3, sinceMs, (n) => /^rollout-.*\.jsonl$/.test(n))) {
|
|
1609
1699
|
const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(path5.basename(f));
|
|
1610
1700
|
if (!m?.[1]) continue;
|
|
1701
|
+
if (codexSubagent(rolloutMeta(f))) continue;
|
|
1611
1702
|
const facts = codexFacts(f);
|
|
1612
1703
|
if (!facts.started_at || !facts.last_at) continue;
|
|
1613
1704
|
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
1614
|
-
|
|
1705
|
+
rows2.push({ session: { id: m[1], ...rest }, started_at, last_at });
|
|
1615
1706
|
}
|
|
1616
|
-
return
|
|
1707
|
+
return rows2;
|
|
1617
1708
|
}
|
|
1618
1709
|
};
|
|
1619
1710
|
function prune3(o) {
|
|
@@ -1622,10 +1713,11 @@ function prune3(o) {
|
|
|
1622
1713
|
}
|
|
1623
1714
|
function codexFacts(file) {
|
|
1624
1715
|
const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
|
|
1625
|
-
|
|
1626
|
-
|
|
1716
|
+
let H = parseLines(head);
|
|
1717
|
+
let T = parseLines(tail);
|
|
1627
1718
|
const out = {};
|
|
1628
1719
|
const meta = H.find((j) => j.type === "session_meta" && j.payload);
|
|
1720
|
+
const boundary = codexReplayBoundary(meta);
|
|
1629
1721
|
if (meta?.payload) {
|
|
1630
1722
|
const p = meta.payload;
|
|
1631
1723
|
if (p.cwd) out.cwd = p.cwd;
|
|
@@ -1633,6 +1725,13 @@ function codexFacts(file) {
|
|
|
1633
1725
|
if (p.git?.branch) out.branch = p.git.branch;
|
|
1634
1726
|
if (p.git?.repository_url) out.repo = String(p.git.repository_url).replace(/\/\/[^/@]*@/, "//");
|
|
1635
1727
|
}
|
|
1728
|
+
if (boundary) {
|
|
1729
|
+
const h = H.findIndex(boundary);
|
|
1730
|
+
const t = T.findIndex(boundary);
|
|
1731
|
+
H = h < 0 ? [] : H.slice(h);
|
|
1732
|
+
const found = h >= 0 || t >= 0 || scanLines(file, (line) => boundary(parseLines([line])[0] ?? null) ? true : null, Number.POSITIVE_INFINITY);
|
|
1733
|
+
T = t >= 0 ? T.slice(t) : found ? T : [];
|
|
1734
|
+
}
|
|
1636
1735
|
const ctx = H.concat(T).reverse().find((j) => j.type === "turn_context" && j.payload?.model);
|
|
1637
1736
|
if (ctx?.payload?.model) out.model = ctx.payload.model;
|
|
1638
1737
|
for (const j of H) {
|
|
@@ -1640,12 +1739,12 @@ function codexFacts(file) {
|
|
|
1640
1739
|
if (j.type !== "response_item" || !p || p.type !== "message" || p.role !== "user") continue;
|
|
1641
1740
|
const text = (p.content ?? []).filter((b) => b && b.type === "input_text" && typeof b.text === "string").map((b) => b.text).join("\n").trim();
|
|
1642
1741
|
if (!text || codexInjected(text)) continue;
|
|
1643
|
-
const line = text.split("\n")
|
|
1742
|
+
const line = text.split("\n")[0];
|
|
1644
1743
|
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
1645
1744
|
out.title_source = "first-ask";
|
|
1646
1745
|
break;
|
|
1647
1746
|
}
|
|
1648
|
-
const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
|
|
1747
|
+
const times = (boundary && meta ? [meta, ...H, ...T] : H.concat(T)).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
|
|
1649
1748
|
if (times.length) {
|
|
1650
1749
|
out.started_at = Math.min(...times);
|
|
1651
1750
|
out.last_at = Math.max(...times);
|
|
@@ -1653,11 +1752,432 @@ function codexFacts(file) {
|
|
|
1653
1752
|
return out;
|
|
1654
1753
|
}
|
|
1655
1754
|
|
|
1656
|
-
// ../core/src/adapters/
|
|
1657
|
-
import {
|
|
1658
|
-
import { existsSync as
|
|
1755
|
+
// ../core/src/adapters/devin.ts
|
|
1756
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
1757
|
+
import { existsSync as existsSync6, readlinkSync } from "node:fs";
|
|
1659
1758
|
import path6 from "node:path";
|
|
1660
|
-
|
|
1759
|
+
|
|
1760
|
+
// ../core/src/readers/sqlite.ts
|
|
1761
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
1762
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
1763
|
+
import { createRequire } from "node:module";
|
|
1764
|
+
function quiet(fn) {
|
|
1765
|
+
const original = process.emitWarning;
|
|
1766
|
+
try {
|
|
1767
|
+
process.emitWarning = ((warning, ...rest) => {
|
|
1768
|
+
const kind = typeof rest[0] === "string" ? rest[0] : rest[0]?.type;
|
|
1769
|
+
if (kind === "ExperimentalWarning" && /sqlite/i.test(String(warning))) return;
|
|
1770
|
+
original.call(process, warning, ...rest);
|
|
1771
|
+
});
|
|
1772
|
+
return fn();
|
|
1773
|
+
} finally {
|
|
1774
|
+
process.emitWarning = original;
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
var ctorCache;
|
|
1778
|
+
function dbCtor() {
|
|
1779
|
+
if (ctorCache !== void 0) return ctorCache;
|
|
1780
|
+
ctorCache = null;
|
|
1781
|
+
try {
|
|
1782
|
+
const req = createRequire(import.meta.url);
|
|
1783
|
+
const m = quiet(() => req("node:sqlite"));
|
|
1784
|
+
if (typeof m?.DatabaseSync === "function") ctorCache = m.DatabaseSync;
|
|
1785
|
+
} catch {
|
|
1786
|
+
}
|
|
1787
|
+
return ctorCache;
|
|
1788
|
+
}
|
|
1789
|
+
function plain(row) {
|
|
1790
|
+
if (!row || typeof row !== "object") return null;
|
|
1791
|
+
const out = {};
|
|
1792
|
+
for (const [k, v] of Object.entries(row)) {
|
|
1793
|
+
if (typeof v === "bigint") out[k] = Number(v);
|
|
1794
|
+
else if (v instanceof Uint8Array) out[k] = null;
|
|
1795
|
+
else out[k] = v;
|
|
1796
|
+
}
|
|
1797
|
+
return out;
|
|
1798
|
+
}
|
|
1799
|
+
var rows = (v) => {
|
|
1800
|
+
if (!Array.isArray(v)) return null;
|
|
1801
|
+
const out = [];
|
|
1802
|
+
for (const r of v) {
|
|
1803
|
+
const p = plain(r);
|
|
1804
|
+
if (p) out.push(p);
|
|
1805
|
+
}
|
|
1806
|
+
return out;
|
|
1807
|
+
};
|
|
1808
|
+
function viaNode(file, sql, params, o) {
|
|
1809
|
+
const C = dbCtor();
|
|
1810
|
+
if (!C) return null;
|
|
1811
|
+
let db;
|
|
1812
|
+
try {
|
|
1813
|
+
db = quiet(() => new C(file, { readOnly: true, timeout: o.timeoutMs }));
|
|
1814
|
+
} catch {
|
|
1815
|
+
return null;
|
|
1816
|
+
}
|
|
1817
|
+
try {
|
|
1818
|
+
try {
|
|
1819
|
+
db.exec(`PRAGMA busy_timeout = ${Math.round(o.timeoutMs)}`);
|
|
1820
|
+
} catch {
|
|
1821
|
+
}
|
|
1822
|
+
return quiet(() => rows(db.prepare(sql).all(...params)));
|
|
1823
|
+
} catch {
|
|
1824
|
+
return null;
|
|
1825
|
+
} finally {
|
|
1826
|
+
try {
|
|
1827
|
+
db.close();
|
|
1828
|
+
} catch {
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
var CHILD = `const { DatabaseSync } = require("node:sqlite");
|
|
1833
|
+
const [, file, sql, params, ms] = process.argv;
|
|
1834
|
+
const db = new DatabaseSync(file, { readOnly: true, timeout: Number(ms) });
|
|
1835
|
+
try { db.exec("PRAGMA busy_timeout = " + Number(ms)); } catch {}
|
|
1836
|
+
const out = db.prepare(sql).all(...JSON.parse(params));
|
|
1837
|
+
db.close();
|
|
1838
|
+
process.stdout.write(JSON.stringify(out));`;
|
|
1839
|
+
function viaChild(file, sql, params, o) {
|
|
1840
|
+
try {
|
|
1841
|
+
const out = execFileSync3(
|
|
1842
|
+
process.execPath,
|
|
1843
|
+
["--experimental-sqlite", "--no-warnings", "-e", CHILD, file, sql, JSON.stringify(params), String(o.timeoutMs)],
|
|
1844
|
+
{
|
|
1845
|
+
encoding: "utf8",
|
|
1846
|
+
timeout: o.timeoutMs + 3e3,
|
|
1847
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1848
|
+
maxBuffer: o.maxBuffer,
|
|
1849
|
+
windowsHide: true
|
|
1850
|
+
}
|
|
1851
|
+
);
|
|
1852
|
+
return rows(JSON.parse(out || "null"));
|
|
1853
|
+
} catch {
|
|
1854
|
+
return null;
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
var SAFE = /^[A-Za-z0-9 ._:@/+-]{0,256}$/;
|
|
1858
|
+
function inlineParams(sql, params) {
|
|
1859
|
+
if (sql.includes("'")) return null;
|
|
1860
|
+
const holes = sql.split("?").length - 1;
|
|
1861
|
+
if (holes !== params.length) return null;
|
|
1862
|
+
let i = 0;
|
|
1863
|
+
let bad = false;
|
|
1864
|
+
const out = sql.replace(/\?/g, () => {
|
|
1865
|
+
const p = params[i++];
|
|
1866
|
+
if (typeof p === "number") {
|
|
1867
|
+
if (!Number.isFinite(p)) {
|
|
1868
|
+
bad = true;
|
|
1869
|
+
return "NULL";
|
|
1870
|
+
}
|
|
1871
|
+
return String(p);
|
|
1872
|
+
}
|
|
1873
|
+
if (typeof p !== "string" || !SAFE.test(p)) {
|
|
1874
|
+
bad = true;
|
|
1875
|
+
return "NULL";
|
|
1876
|
+
}
|
|
1877
|
+
return `'${p}'`;
|
|
1878
|
+
});
|
|
1879
|
+
return bad ? null : out;
|
|
1880
|
+
}
|
|
1881
|
+
function viaCli(file, sql, params, o) {
|
|
1882
|
+
const text = inlineParams(sql, params);
|
|
1883
|
+
if (!text) return null;
|
|
1884
|
+
try {
|
|
1885
|
+
const out = execFileSync3("sqlite3", ["-readonly", "-json", file, text], {
|
|
1886
|
+
encoding: "utf8",
|
|
1887
|
+
timeout: o.timeoutMs + 3e3,
|
|
1888
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1889
|
+
maxBuffer: o.maxBuffer,
|
|
1890
|
+
windowsHide: true
|
|
1891
|
+
}).trim();
|
|
1892
|
+
if (!out) return [];
|
|
1893
|
+
return rows(JSON.parse(out));
|
|
1894
|
+
} catch {
|
|
1895
|
+
return null;
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
function querySqlite(file, sql, params = [], opts = {}) {
|
|
1899
|
+
const o = {
|
|
1900
|
+
timeoutMs: opts.timeoutMs ?? 2e3,
|
|
1901
|
+
engine: opts.engine ?? "auto",
|
|
1902
|
+
maxBuffer: opts.maxBuffer ?? 32 * 1024 * 1024
|
|
1903
|
+
};
|
|
1904
|
+
if (!file) return null;
|
|
1905
|
+
try {
|
|
1906
|
+
if (!existsSync5(file)) return null;
|
|
1907
|
+
} catch {
|
|
1908
|
+
return null;
|
|
1909
|
+
}
|
|
1910
|
+
const ways = o.engine === "auto" ? [viaNode, viaChild, viaCli] : o.engine === "node" ? [viaNode] : o.engine === "child" ? [viaChild] : [viaCli];
|
|
1911
|
+
for (const way of ways) {
|
|
1912
|
+
const r = way(file, sql, params, o);
|
|
1913
|
+
if (r) return r;
|
|
1914
|
+
}
|
|
1915
|
+
return null;
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
// ../core/src/adapters/devin.ts
|
|
1919
|
+
var NAME4 = "devin";
|
|
1920
|
+
var DEVIN_EVENTS = [
|
|
1921
|
+
"SessionStart",
|
|
1922
|
+
"UserPromptSubmit",
|
|
1923
|
+
"PreToolUse",
|
|
1924
|
+
"PostToolUse",
|
|
1925
|
+
"PermissionRequest",
|
|
1926
|
+
"Stop",
|
|
1927
|
+
"PostCompaction",
|
|
1928
|
+
"SessionEnd"
|
|
1929
|
+
];
|
|
1930
|
+
var OPTS3 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
1931
|
+
var eventsFor3 = (lean) => lean ? DEVIN_EVENTS.filter((e) => e !== "PreToolUse") : [...DEVIN_EVENTS];
|
|
1932
|
+
var INSTALL_NOTE = "Devin drops a whole hooks file on one event name it doesn't know; `/hooks` in `devin` lists what loaded";
|
|
1933
|
+
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";
|
|
1934
|
+
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)`;
|
|
1935
|
+
function foreignEvent(file) {
|
|
1936
|
+
const hooks = readJson(file).hooks;
|
|
1937
|
+
if (!hooks || typeof hooks !== "object" || Array.isArray(hooks)) return void 0;
|
|
1938
|
+
const ok = DEVIN_EVENTS;
|
|
1939
|
+
return Object.keys(hooks).find((k) => !ok.includes(k));
|
|
1940
|
+
}
|
|
1941
|
+
var homeOf = (env) => env.HOME || homeDir();
|
|
1942
|
+
var appData = (env) => env.APPDATA || path6.join(homeOf(env), "AppData", "Roaming");
|
|
1943
|
+
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");
|
|
1944
|
+
var configFile2 = (env) => path6.join(devinConfigDir(env), "config.json");
|
|
1945
|
+
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");
|
|
1946
|
+
var sessionsDb = (env) => path6.join(devinDataDir(env), "sessions.db");
|
|
1947
|
+
var SESSION_ID = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
1948
|
+
var validTool4 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
1949
|
+
var endReason3 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
|
|
1950
|
+
var pickSource2 = (v) => typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
|
|
1951
|
+
function errText2(v) {
|
|
1952
|
+
if (typeof v === "string" && v) return v.slice(0, 2e3);
|
|
1953
|
+
if (v && typeof v === "object") {
|
|
1954
|
+
const e = v;
|
|
1955
|
+
if (typeof e.message === "string" && e.message) return e.message.slice(0, 2e3);
|
|
1956
|
+
if (typeof e.error === "string" && e.error) return e.error.slice(0, 2e3);
|
|
1957
|
+
}
|
|
1958
|
+
return void 0;
|
|
1959
|
+
}
|
|
1960
|
+
var cut = (s, n) => s.length > n ? `${s.slice(0, n).replace(/\s+\S*$/, "")}\u2026` : s;
|
|
1961
|
+
var devin = {
|
|
1962
|
+
name: NAME4,
|
|
1963
|
+
events: DEVIN_EVENTS,
|
|
1964
|
+
detect: (env = process.env) => existsSync6(devinConfigDir(env)) || existsSync6(devinDataDir(env)),
|
|
1965
|
+
configFiles: (env = process.env) => [configFile2(env)],
|
|
1966
|
+
install: (cmd, env = process.env, opts = {}) => {
|
|
1967
|
+
const file = configFile2(env);
|
|
1968
|
+
const r = installClaudeShaped(file, NAME4, eventsFor3(opts.lean), cmd, OPTS3);
|
|
1969
|
+
if (r.skipped) return [r];
|
|
1970
|
+
const bad = foreignEvent(file);
|
|
1971
|
+
return [{ ...r, note: bad ? deadFileNote(bad) : INSTALL_NOTE }];
|
|
1972
|
+
},
|
|
1973
|
+
uninstall: (env = process.env, opts = {}) => [
|
|
1974
|
+
uninstallClaudeShaped(configFile2(env), DEVIN_EVENTS, { created: opts.created?.includes(configFile2(env)) })
|
|
1975
|
+
],
|
|
1976
|
+
installed: (cmd, env = process.env, opts = {}) => {
|
|
1977
|
+
const file = configFile2(env);
|
|
1978
|
+
const state = installedClaudeShaped(file, NAME4, eventsFor3(opts.lean), cmd, OPTS3);
|
|
1979
|
+
const bad = state === "missing" ? void 0 : foreignEvent(file);
|
|
1980
|
+
return [{ file, state: bad ? "inactive" : state, note: bad ? deadFileNote(bad) : CLAUDE_NOTE }];
|
|
1981
|
+
},
|
|
1982
|
+
fromHook(input) {
|
|
1983
|
+
const event = input.argv[1] ?? "";
|
|
1984
|
+
let s = {};
|
|
1985
|
+
try {
|
|
1986
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
1987
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1988
|
+
} catch {
|
|
1989
|
+
}
|
|
1990
|
+
const id = typeof s.session_id === "string" ? s.session_id : "";
|
|
1991
|
+
if (!id) return null;
|
|
1992
|
+
const session = { id, cwd: input.env.DEVIN_PROJECT_DIR ?? input.cwd };
|
|
1993
|
+
const turn_id = typeof s.prompt_id === "string" ? s.prompt_id : void 0;
|
|
1994
|
+
const tool = validTool4(s.tool_name) ? s.tool_name : void 0;
|
|
1995
|
+
const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
|
|
1996
|
+
const response = s.tool_response && typeof s.tool_response === "object" && !Array.isArray(s.tool_response) ? s.tool_response : void 0;
|
|
1997
|
+
const permissionId = () => `perm-${createHash2("sha256").update(`${turn_id ?? ""}|${tool ?? ""}|${JSON.stringify(s.tool_input ?? null)}`).digest("hex").slice(0, 16)}`;
|
|
1998
|
+
const ev = (type, data) => ({ type, data: prune4(data), harnessEvent: event });
|
|
1999
|
+
const events = [];
|
|
2000
|
+
switch (event) {
|
|
2001
|
+
case "SessionStart":
|
|
2002
|
+
events.push(ev("session.started", { source: pickSource2(s.source) }));
|
|
2003
|
+
break;
|
|
2004
|
+
case "UserPromptSubmit":
|
|
2005
|
+
events.push(
|
|
2006
|
+
ev("turn.started", {
|
|
2007
|
+
turn_id,
|
|
2008
|
+
prompt_chars: typeof s.prompt === "string" ? [...s.prompt].length : void 0
|
|
2009
|
+
})
|
|
2010
|
+
);
|
|
2011
|
+
break;
|
|
2012
|
+
case "PreToolUse":
|
|
2013
|
+
if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, input: s.tool_input }));
|
|
2014
|
+
break;
|
|
2015
|
+
case "PostToolUse":
|
|
2016
|
+
if (tool)
|
|
2017
|
+
events.push(
|
|
2018
|
+
ev("tool.ended", {
|
|
2019
|
+
tool,
|
|
2020
|
+
call_id,
|
|
2021
|
+
turn_id,
|
|
2022
|
+
// `ok` is required on tool.ended (PROTOCOL.md §events), so a response
|
|
2023
|
+
// that carries no success flag at all reads as "nothing said it failed".
|
|
2024
|
+
ok: response?.success !== false,
|
|
2025
|
+
error: errText2(response?.error),
|
|
2026
|
+
input: s.tool_input,
|
|
2027
|
+
output: response ? response.output : s.tool_response
|
|
2028
|
+
})
|
|
2029
|
+
);
|
|
2030
|
+
break;
|
|
2031
|
+
case "PermissionRequest":
|
|
2032
|
+
events.push(ev("attention.needed", { attention_id: call_id ?? permissionId(), kind: "permission", tool }));
|
|
2033
|
+
break;
|
|
2034
|
+
case "Stop":
|
|
2035
|
+
events.push(ev("turn.ended", { turn_id, reason: "stop" }));
|
|
2036
|
+
break;
|
|
2037
|
+
case "PostCompaction":
|
|
2038
|
+
events.push(ev("context.compacted", { trigger: "auto" }));
|
|
2039
|
+
break;
|
|
2040
|
+
case "SessionEnd":
|
|
2041
|
+
events.push(ev("session.ended", { reason: endReason3(s.reason) }));
|
|
2042
|
+
break;
|
|
2043
|
+
default:
|
|
2044
|
+
return null;
|
|
2045
|
+
}
|
|
2046
|
+
return {
|
|
2047
|
+
session,
|
|
2048
|
+
events,
|
|
2049
|
+
hints: {},
|
|
2050
|
+
transcript: transcriptRef(sessionsDb(input.env), id)
|
|
2051
|
+
};
|
|
2052
|
+
},
|
|
2053
|
+
readTranscript(file, fromLine) {
|
|
2054
|
+
const ref = splitRef(file);
|
|
2055
|
+
if (!ref) return { turns: [], line: fromLine };
|
|
2056
|
+
return readDevinChain(ref.db, ref.session, fromLine);
|
|
2057
|
+
},
|
|
2058
|
+
facts(session, transcript, _hints, _state, env = process.env) {
|
|
2059
|
+
const out = {};
|
|
2060
|
+
const version = devinVersion(env);
|
|
2061
|
+
if (version) out.harness_version = version;
|
|
2062
|
+
const account = devinAccount(env);
|
|
2063
|
+
if (account) out.account_id = account;
|
|
2064
|
+
const ref = splitRef(transcript) ?? { db: sessionsDb(env), session: session.id };
|
|
2065
|
+
if (!SESSION_ID.test(ref.session)) return out;
|
|
2066
|
+
const rows2 = querySqlite(ref.db, SESSION_SQL, [ref.session]);
|
|
2067
|
+
const row = rows2?.[0];
|
|
2068
|
+
if (!row) return out;
|
|
2069
|
+
Object.assign(out, sessionFacts(row));
|
|
2070
|
+
return out;
|
|
2071
|
+
},
|
|
2072
|
+
backfill(sinceMs, env = process.env) {
|
|
2073
|
+
const rows2 = querySqlite(sessionsDb(env), BACKFILL_SQL, ["text", Math.floor(sinceMs / 1e3)]);
|
|
2074
|
+
if (!rows2) return [];
|
|
2075
|
+
const out = [];
|
|
2076
|
+
for (const row of rows2) {
|
|
2077
|
+
const id = String(row.id);
|
|
2078
|
+
if (!SESSION_ID.test(id)) continue;
|
|
2079
|
+
const { started_at, last_at, ...rest } = sessionFacts(row);
|
|
2080
|
+
if (!started_at || !last_at) continue;
|
|
2081
|
+
out.push({ session: { id, ...rest }, started_at, last_at });
|
|
2082
|
+
}
|
|
2083
|
+
return out;
|
|
2084
|
+
}
|
|
2085
|
+
};
|
|
2086
|
+
function prune4(o) {
|
|
2087
|
+
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
2088
|
+
return o;
|
|
2089
|
+
}
|
|
2090
|
+
var transcriptRef = (db, session) => `${db}#${session}`;
|
|
2091
|
+
function splitRef(ref) {
|
|
2092
|
+
if (!ref) return null;
|
|
2093
|
+
const at = ref.lastIndexOf("#");
|
|
2094
|
+
if (at <= 0) return null;
|
|
2095
|
+
const db = ref.slice(0, at);
|
|
2096
|
+
const session = ref.slice(at + 1);
|
|
2097
|
+
return db && SESSION_ID.test(session) ? { db, session } : null;
|
|
2098
|
+
}
|
|
2099
|
+
var CHAIN_LIMIT = 4e3;
|
|
2100
|
+
var CHAIN_SQL = `WITH RECURSIVE chain(node_id, parent_node_id, chat_message, created_at) AS (
|
|
2101
|
+
SELECT node_id, parent_node_id, chat_message, created_at FROM message_nodes
|
|
2102
|
+
WHERE session_id = ? AND node_id = (SELECT main_chain_id FROM sessions WHERE id = ?)
|
|
2103
|
+
UNION ALL
|
|
2104
|
+
SELECT m.node_id, m.parent_node_id, m.chat_message, m.created_at FROM message_nodes m
|
|
2105
|
+
JOIN chain c ON m.node_id = c.parent_node_id AND m.session_id = ? AND m.node_id >= ?
|
|
2106
|
+
)
|
|
2107
|
+
SELECT node_id, chat_message, created_at FROM chain ORDER BY node_id DESC LIMIT ${CHAIN_LIMIT}`;
|
|
2108
|
+
var SESSION_FIELDS = `s.model, s.working_directory, s.created_at, s.last_activity_at,
|
|
2109
|
+
(SELECT p.content FROM prompt_history p WHERE p.session_id = s.id AND p.is_shell = 0
|
|
2110
|
+
ORDER BY p.timestamp ASC, p.id ASC LIMIT 1) AS first_ask`;
|
|
2111
|
+
var SESSION_SQL = `SELECT ${SESSION_FIELDS} FROM sessions s WHERE s.id = ? LIMIT 1`;
|
|
2112
|
+
var BACKFILL_SQL = `SELECT s.id, ${SESSION_FIELDS} FROM sessions s
|
|
2113
|
+
WHERE s.hidden = 0 AND typeof(s.id) = ? AND s.last_activity_at >= ?
|
|
2114
|
+
ORDER BY s.last_activity_at DESC LIMIT 500`;
|
|
2115
|
+
function sessionFacts(row) {
|
|
2116
|
+
const out = {};
|
|
2117
|
+
if (typeof row.model === "string" && row.model) out.model = row.model;
|
|
2118
|
+
if (typeof row.working_directory === "string" && path6.isAbsolute(row.working_directory))
|
|
2119
|
+
out.cwd = row.working_directory;
|
|
2120
|
+
const started = Number(row.created_at);
|
|
2121
|
+
const last = Number(row.last_activity_at);
|
|
2122
|
+
if (Number.isFinite(started) && started > 0) out.started_at = Math.round(started * 1e3);
|
|
2123
|
+
if (Number.isFinite(last) && last > 0) out.last_at = Math.round(last * 1e3);
|
|
2124
|
+
if (typeof row.first_ask === "string") {
|
|
2125
|
+
const line = row.first_ask.split("\n").find((l) => l.trim()) ?? "";
|
|
2126
|
+
if (line.trim()) {
|
|
2127
|
+
out.title = cut(line.trim(), 80);
|
|
2128
|
+
out.title_source = "first-ask";
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
return out;
|
|
2132
|
+
}
|
|
2133
|
+
function readDevinChain(db, session, fromLine) {
|
|
2134
|
+
if (!SESSION_ID.test(session)) return { turns: [], line: fromLine };
|
|
2135
|
+
const floor = Number.isFinite(fromLine) && fromLine > 0 ? Math.floor(fromLine) : 0;
|
|
2136
|
+
const rows2 = querySqlite(db, CHAIN_SQL, [session, session, session, floor]);
|
|
2137
|
+
if (!rows2?.length) return { turns: [], line: fromLine };
|
|
2138
|
+
const msgs = [];
|
|
2139
|
+
let end = fromLine;
|
|
2140
|
+
for (const row of rows2) {
|
|
2141
|
+
const node = Number(row.node_id);
|
|
2142
|
+
if (!Number.isFinite(node)) continue;
|
|
2143
|
+
if (node + 1 > end) end = node + 1;
|
|
2144
|
+
if (node < fromLine) continue;
|
|
2145
|
+
let j;
|
|
2146
|
+
try {
|
|
2147
|
+
j = JSON.parse(String(row.chat_message ?? ""));
|
|
2148
|
+
} catch {
|
|
2149
|
+
continue;
|
|
2150
|
+
}
|
|
2151
|
+
const text = typeof j.content === "string" ? j.content.trim() : "";
|
|
2152
|
+
if (!text) continue;
|
|
2153
|
+
const secs = Number(row.created_at);
|
|
2154
|
+
const at = Date.parse(String(j.metadata?.created_at ?? "")) || (Number.isFinite(secs) && secs > 0 ? Math.round(secs * 1e3) : 0) || Date.now();
|
|
2155
|
+
if (j.role === "user" && j.metadata?.is_user_input === true) msgs.push({ role: "user", text, at, line: node });
|
|
2156
|
+
else if (j.role === "assistant") msgs.push({ role: "assistant", text, at, line: node });
|
|
2157
|
+
}
|
|
2158
|
+
msgs.sort((a, b) => a.line - b.line);
|
|
2159
|
+
return pairTurns(msgs, end);
|
|
2160
|
+
}
|
|
2161
|
+
function devinVersion(env = process.env) {
|
|
2162
|
+
try {
|
|
2163
|
+
const target = readlinkSync(path6.join(devinDataDir(env), "_versions", "current"));
|
|
2164
|
+
const name = path6.basename(target);
|
|
2165
|
+
return /^[0-9][0-9A-Za-z.+-]{0,63}$/.test(name) ? name : void 0;
|
|
2166
|
+
} catch {
|
|
2167
|
+
return void 0;
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
function devinAccount(env = process.env) {
|
|
2171
|
+
const d = readJson(configFile2(env)).devin;
|
|
2172
|
+
const id = d && typeof d === "object" ? d.org_id : void 0;
|
|
2173
|
+
return typeof id === "string" && /^[A-Za-z0-9_.:-]{1,128}$/.test(id) ? id : void 0;
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
// ../core/src/adapters/gemini-cli.ts
|
|
2177
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
2178
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
|
|
2179
|
+
import path7 from "node:path";
|
|
2180
|
+
var NAME5 = "gemini-cli";
|
|
1661
2181
|
var GEMINI_EVENTS = [
|
|
1662
2182
|
"SessionStart",
|
|
1663
2183
|
"BeforeAgent",
|
|
@@ -1668,11 +2188,11 @@ var GEMINI_EVENTS = [
|
|
|
1668
2188
|
"PreCompress",
|
|
1669
2189
|
"SessionEnd"
|
|
1670
2190
|
];
|
|
1671
|
-
var
|
|
1672
|
-
var geminiDir = (env) => env.GEMINI_CLI_HOME ||
|
|
1673
|
-
var settings = (env) =>
|
|
1674
|
-
var
|
|
1675
|
-
var
|
|
2191
|
+
var OPTS4 = { matcherFor: (e) => /Tool/.test(e) ? ".*" : void 0, timeout: 5e3 };
|
|
2192
|
+
var geminiDir = (env) => env.GEMINI_CLI_HOME || path7.join(homeDir(), ".gemini");
|
|
2193
|
+
var settings = (env) => path7.join(geminiDir(env), "settings.json");
|
|
2194
|
+
var validTool5 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
2195
|
+
var eventsFor4 = (lean) => lean ? GEMINI_EVENTS.filter((e) => e !== "BeforeTool") : [...GEMINI_EVENTS];
|
|
1676
2196
|
function geminiHooksActive(settingsFile) {
|
|
1677
2197
|
let tools;
|
|
1678
2198
|
try {
|
|
@@ -1684,7 +2204,7 @@ function geminiHooksActive(settingsFile) {
|
|
|
1684
2204
|
return { active: false, note: "inactive: tools.enableHooks is false in settings.json" };
|
|
1685
2205
|
let version = "";
|
|
1686
2206
|
try {
|
|
1687
|
-
version =
|
|
2207
|
+
version = execFileSync4("gemini", ["--version"], {
|
|
1688
2208
|
encoding: "utf8",
|
|
1689
2209
|
timeout: 4e3,
|
|
1690
2210
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -1703,13 +2223,13 @@ function geminiHooksActive(settingsFile) {
|
|
|
1703
2223
|
};
|
|
1704
2224
|
}
|
|
1705
2225
|
var geminiCli = {
|
|
1706
|
-
name:
|
|
2226
|
+
name: NAME5,
|
|
1707
2227
|
events: GEMINI_EVENTS,
|
|
1708
2228
|
// ~/.gemini also belongs to Antigravity; Gemini CLI is here when its settings or its chat logs exist.
|
|
1709
|
-
detect: (env = process.env) =>
|
|
2229
|
+
detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(path7.join(geminiDir(env), "tmp")),
|
|
1710
2230
|
configFiles: (env = process.env) => [settings(env)],
|
|
1711
2231
|
install: (cmd, env = process.env, opts = {}) => {
|
|
1712
|
-
const r = installClaudeShaped(settings(env),
|
|
2232
|
+
const r = installClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
|
|
1713
2233
|
const a = geminiHooksActive(settings(env));
|
|
1714
2234
|
return [a.note && !r.skipped ? { ...r, note: [r.note, a.note].filter(Boolean).join("; ") } : r];
|
|
1715
2235
|
},
|
|
@@ -1717,7 +2237,7 @@ var geminiCli = {
|
|
|
1717
2237
|
uninstallClaudeShaped(settings(env), GEMINI_EVENTS, { created: opts.created?.includes(settings(env)) })
|
|
1718
2238
|
],
|
|
1719
2239
|
installed: (cmd, env = process.env, opts = {}) => {
|
|
1720
|
-
const state = installedClaudeShaped(settings(env),
|
|
2240
|
+
const state = installedClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
|
|
1721
2241
|
const a = geminiHooksActive(settings(env));
|
|
1722
2242
|
return [
|
|
1723
2243
|
{
|
|
@@ -1731,14 +2251,15 @@ var geminiCli = {
|
|
|
1731
2251
|
const event = input.argv[1] ?? "";
|
|
1732
2252
|
let s = {};
|
|
1733
2253
|
try {
|
|
1734
|
-
|
|
2254
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
2255
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1735
2256
|
} catch {
|
|
1736
2257
|
}
|
|
1737
2258
|
const id = s.session_id;
|
|
1738
2259
|
if (!id) return null;
|
|
1739
2260
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
1740
|
-
const tool =
|
|
1741
|
-
const ev = (type, data) => ({ type, data:
|
|
2261
|
+
const tool = validTool5(s.tool_name) ? s.tool_name : void 0;
|
|
2262
|
+
const ev = (type, data) => ({ type, data: prune5(data), harnessEvent: event });
|
|
1742
2263
|
const events = [];
|
|
1743
2264
|
switch (event) {
|
|
1744
2265
|
case "SessionStart":
|
|
@@ -1819,7 +2340,7 @@ var geminiCli = {
|
|
|
1819
2340
|
const raw = first ? first.content ?? first.message ?? first.text : void 0;
|
|
1820
2341
|
const text = typeof raw === "string" ? raw.trim() : "";
|
|
1821
2342
|
if (text) {
|
|
1822
|
-
const line = text.split("\n")
|
|
2343
|
+
const line = text.split("\n")[0];
|
|
1823
2344
|
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
1824
2345
|
out.title_source = "first-ask";
|
|
1825
2346
|
}
|
|
@@ -1833,15 +2354,31 @@ var geminiCli = {
|
|
|
1833
2354
|
backfill: () => []
|
|
1834
2355
|
// chat logs are keyed by a project hash, not a session id we can name; the first hook of a resumed session reports it
|
|
1835
2356
|
};
|
|
1836
|
-
function
|
|
2357
|
+
function prune5(o) {
|
|
1837
2358
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
1838
2359
|
return o;
|
|
1839
2360
|
}
|
|
1840
2361
|
|
|
1841
2362
|
// ../core/src/adapters/index.ts
|
|
1842
|
-
var ADAPTERS = [claudeCode, codex, geminiCli, antigravity];
|
|
2363
|
+
var ADAPTERS = [claudeCode, codex, geminiCli, antigravity, devin];
|
|
1843
2364
|
var adapterByName = (name) => ADAPTERS.find((a) => a.name === name);
|
|
1844
2365
|
|
|
2366
|
+
// ../core/src/launched-by.ts
|
|
2367
|
+
var LAUNCHED_BY_ENV = "SESSIONPIPE_LAUNCHED_BY";
|
|
2368
|
+
var LAUNCHED_BY = /^[a-z][a-z0-9-]{0,39}:[A-Za-z0-9._:-]{1,128}$/;
|
|
2369
|
+
function launchedBy(env) {
|
|
2370
|
+
const v = env[LAUNCHED_BY_ENV]?.trim();
|
|
2371
|
+
return v && LAUNCHED_BY.test(v) ? v : void 0;
|
|
2372
|
+
}
|
|
2373
|
+
function readHook(adapter, input) {
|
|
2374
|
+
const r = adapter.fromHook(input);
|
|
2375
|
+
if (r && !r.session.parent_id) {
|
|
2376
|
+
const launcher = launchedBy(input.env);
|
|
2377
|
+
if (launcher && launcher !== `${adapter.name}:${r.session.id}`) r.session.launched_by = launcher;
|
|
2378
|
+
}
|
|
2379
|
+
return r;
|
|
2380
|
+
}
|
|
2381
|
+
|
|
1845
2382
|
// src/index.ts
|
|
1846
2383
|
function matches(expect, got, pathStr = "") {
|
|
1847
2384
|
if (expect === "*") return null;
|
|
@@ -1872,14 +2409,14 @@ async function runAdapterFixtures(files) {
|
|
|
1872
2409
|
results.push({ file, ok: false, why: `no adapter ${fx.harness}` });
|
|
1873
2410
|
continue;
|
|
1874
2411
|
}
|
|
1875
|
-
const r = adapter
|
|
2412
|
+
const r = readHook(adapter, {
|
|
1876
2413
|
argv: fx.input.argv,
|
|
1877
2414
|
stdin: fx.input.stdin,
|
|
1878
2415
|
env: fx.input.env ?? {},
|
|
1879
2416
|
cwd: fx.input.cwd ?? "/"
|
|
1880
2417
|
});
|
|
1881
2418
|
if (!r) {
|
|
1882
|
-
results.push({ file, ok: false, why: "fromHook returned null" });
|
|
2419
|
+
results.push(fx.expect.length ? { file, ok: false, why: "fromHook returned null" } : { file, ok: true });
|
|
1883
2420
|
continue;
|
|
1884
2421
|
}
|
|
1885
2422
|
let why = matches(
|
|
@@ -1890,7 +2427,7 @@ async function runAdapterFixtures(files) {
|
|
|
1890
2427
|
if (!why)
|
|
1891
2428
|
for (const [i, e] of fx.expect.entries()) {
|
|
1892
2429
|
const got = r.events[i];
|
|
1893
|
-
why = matches(e.harness.event, got
|
|
2430
|
+
why = matches(e.harness.event, got.harnessEvent, `[${i}].harness.event`) ?? matches(e.data, got.data, `[${i}].data`) ?? matches({ ...e.session, seq: "*" }, r.session, `[${i}].session`);
|
|
1894
2431
|
if (why) break;
|
|
1895
2432
|
}
|
|
1896
2433
|
results.push(why ? { file, ok: false, why } : { file, ok: true });
|