sessionpipe-core 0.6.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/control.js +54 -4
- package/dist/control.js.map +2 -2
- package/dist/index.js +1690 -831
- package/dist/index.js.map +4 -4
- package/dist/schema/v1.d.ts +139 -71
- package/dist/schema/v1.d.ts.map +1 -1
- package/dist/schema.js +56 -5
- package/dist/schema.js.map +2 -2
- package/dist/src/adapters/antigravity-control.d.ts +58 -0
- package/dist/src/adapters/antigravity-control.d.ts.map +1 -0
- package/dist/src/adapters/antigravity.d.ts +1 -2
- package/dist/src/adapters/antigravity.d.ts.map +1 -1
- package/dist/src/adapters/claude-code-control.d.ts +7 -26
- package/dist/src/adapters/claude-code-control.d.ts.map +1 -1
- package/dist/src/adapters/claude-code.d.ts +10 -0
- package/dist/src/adapters/claude-code.d.ts.map +1 -1
- package/dist/src/adapters/claude-shaped.d.ts.map +1 -1
- package/dist/src/adapters/codex-config.d.ts +2 -2
- package/dist/src/adapters/codex-config.d.ts.map +1 -1
- package/dist/src/adapters/codex-control.d.ts +62 -0
- package/dist/src/adapters/codex-control.d.ts.map +1 -0
- package/dist/src/adapters/codex-rollout.d.ts +8 -0
- package/dist/src/adapters/codex-rollout.d.ts.map +1 -0
- package/dist/src/adapters/codex.d.ts +5 -1
- package/dist/src/adapters/codex.d.ts.map +1 -1
- package/dist/src/adapters/devin.d.ts +32 -0
- package/dist/src/adapters/devin.d.ts.map +1 -0
- package/dist/src/adapters/gemini-cli.d.ts.map +1 -1
- package/dist/src/adapters/headless.d.ts +70 -0
- package/dist/src/adapters/headless.d.ts.map +1 -0
- package/dist/src/adapters/index.d.ts +10 -2
- package/dist/src/adapters/index.d.ts.map +1 -1
- package/dist/src/adapters/types.d.ts +9 -1
- package/dist/src/adapters/types.d.ts.map +1 -1
- package/dist/src/control/verify.d.ts +27 -0
- package/dist/src/control/verify.d.ts.map +1 -1
- package/dist/src/index.d.ts +3 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/outbox.d.ts +13 -2
- package/dist/src/outbox.d.ts.map +1 -1
- package/dist/src/paths.d.ts +3 -1
- package/dist/src/paths.d.ts.map +1 -1
- package/dist/src/privacy/filter.d.ts +2 -0
- package/dist/src/privacy/filter.d.ts.map +1 -1
- package/dist/src/privacy/index.d.ts +1 -1
- package/dist/src/privacy/index.d.ts.map +1 -1
- package/dist/src/readers/files.d.ts.map +1 -1
- package/dist/src/readers/git.d.ts.map +1 -1
- package/dist/src/readers/sqlite.d.ts +23 -0
- package/dist/src/readers/sqlite.d.ts.map +1 -0
- package/dist/src/readers/transcripts.d.ts +9 -2
- package/dist/src/readers/transcripts.d.ts.map +1 -1
- package/dist/src/short-id.d.ts +2 -0
- package/dist/src/short-id.d.ts.map +1 -0
- package/dist/src/sinks/file.d.ts.map +1 -1
- package/dist/src/sinks/stdout.d.ts.map +1 -1
- package/dist/src/types.d.ts +1 -0
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/ulid.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// sessionpipe-core 0.
|
|
1
|
+
// sessionpipe-core 0.9.0 · Apache-2.0 · https://sessionpipe.org
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __export = (target, all) => {
|
|
4
4
|
for (var name in all)
|
|
@@ -21,20 +21,20 @@ import path4 from "node:path";
|
|
|
21
21
|
// src/paths.ts
|
|
22
22
|
import os from "node:os";
|
|
23
23
|
import path from "node:path";
|
|
24
|
-
var
|
|
24
|
+
var homeDir = () => os.homedir();
|
|
25
25
|
function configFile(env = process.env) {
|
|
26
26
|
if (env.SESSIONPIPE_CONFIG) return path.resolve(env.SESSIONPIPE_CONFIG);
|
|
27
27
|
if (process.platform === "win32")
|
|
28
|
-
return path.join(env.APPDATA || path.join(
|
|
29
|
-
return path.join(env.XDG_CONFIG_HOME || path.join(
|
|
28
|
+
return path.join(env.APPDATA || path.join(homeDir(), "AppData", "Roaming"), "sessionpipe", "config.json");
|
|
29
|
+
return path.join(env.XDG_CONFIG_HOME || path.join(homeDir(), ".config"), "sessionpipe", "config.json");
|
|
30
30
|
}
|
|
31
31
|
function stateDir(env = process.env) {
|
|
32
32
|
if (env.SESSIONPIPE_STATE) return path.resolve(env.SESSIONPIPE_STATE);
|
|
33
33
|
if (process.platform === "win32")
|
|
34
|
-
return path.join(env.LOCALAPPDATA || path.join(
|
|
35
|
-
return path.join(env.XDG_STATE_HOME || path.join(
|
|
34
|
+
return path.join(env.LOCALAPPDATA || path.join(homeDir(), "AppData", "Local"), "sessionpipe");
|
|
35
|
+
return path.join(env.XDG_STATE_HOME || path.join(homeDir(), ".local", "state"), "sessionpipe");
|
|
36
36
|
}
|
|
37
|
-
function tilde(p, home =
|
|
37
|
+
function tilde(p, home = homeDir()) {
|
|
38
38
|
if (!p) return p;
|
|
39
39
|
if (p === home) return "~";
|
|
40
40
|
return p.startsWith(home + path.sep) || p.startsWith(`${home}/`) ? `~${p.slice(home.length)}` : p;
|
|
@@ -131,6 +131,19 @@ function recentFiles(root, depth, sinceMs, match) {
|
|
|
131
131
|
return out;
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
// src/adapters/codex-rollout.ts
|
|
135
|
+
function codexSubagent(meta) {
|
|
136
|
+
if (meta?.type !== "session_meta") return false;
|
|
137
|
+
const p = meta.payload;
|
|
138
|
+
return !!p?.source?.subagent;
|
|
139
|
+
}
|
|
140
|
+
function codexReplayBoundary(meta) {
|
|
141
|
+
if (meta?.type !== "session_meta") return void 0;
|
|
142
|
+
const p = meta.payload;
|
|
143
|
+
if (!p || !p.forked_from_id && !p.source?.subagent) return void 0;
|
|
144
|
+
return (record) => typeof p.id === "string" && !!p.id && record?.type === "event_msg" && record.payload?.thread_id === p.id;
|
|
145
|
+
}
|
|
146
|
+
|
|
134
147
|
// src/readers/transcripts.ts
|
|
135
148
|
function pairTurns(msgs, endLine) {
|
|
136
149
|
const turns = [];
|
|
@@ -153,10 +166,14 @@ ${m.text}`;
|
|
|
153
166
|
}
|
|
154
167
|
if (cur?.assistant) turns.push(cur);
|
|
155
168
|
const line = cur && !cur.assistant ? cur.startLine : endLine;
|
|
156
|
-
|
|
169
|
+
const out = {
|
|
170
|
+
turns: turns.map((t) => ({ user: t.user, assistant: t.assistant, at: t.at, endLine: t.endLine })),
|
|
171
|
+
line
|
|
172
|
+
};
|
|
173
|
+
if (cur && !cur.assistant && cur.user) out.open = { user: cur.user, at: cur.at };
|
|
174
|
+
return out;
|
|
157
175
|
}
|
|
158
|
-
var isInjected = (text) => /^<[a-z_-]
|
|
159
|
-
var endOf = (lines) => lines[lines.length - 1] === "" ? lines.length - 1 : lines.length;
|
|
176
|
+
var isInjected = (text) => /^<[a-z_-]+(?:\s[^>]*)?>/i.test(text) && /<\/[a-z_-]+>\s*$/i.test(text) || /^<!--\s/.test(text) || /^<command-/.test(text);
|
|
160
177
|
var parse = (raw) => {
|
|
161
178
|
try {
|
|
162
179
|
return JSON.parse(raw);
|
|
@@ -164,6 +181,11 @@ var parse = (raw) => {
|
|
|
164
181
|
return null;
|
|
165
182
|
}
|
|
166
183
|
};
|
|
184
|
+
var endOf = (lines) => {
|
|
185
|
+
const last = lines[lines.length - 1];
|
|
186
|
+
if (last === void 0) return 0;
|
|
187
|
+
return last === "" || parse(last) === null ? lines.length - 1 : lines.length;
|
|
188
|
+
};
|
|
167
189
|
var textParts = (c, kinds) => {
|
|
168
190
|
if (typeof c === "string") return c;
|
|
169
191
|
if (!Array.isArray(c)) return "";
|
|
@@ -171,13 +193,21 @@ var textParts = (c, kinds) => {
|
|
|
171
193
|
(b) => b && typeof b === "object" && kinds.includes(b.type ?? "") && typeof b.text === "string"
|
|
172
194
|
).map((b) => b.text).join("\n");
|
|
173
195
|
};
|
|
196
|
+
var queuedPrompt = (a) => {
|
|
197
|
+
if (!a || typeof a !== "object") return "";
|
|
198
|
+
const q = a;
|
|
199
|
+
if (q.type !== "queued_command" || (q.commandMode ?? "prompt") !== "prompt") return "";
|
|
200
|
+
if (q.origin?.kind && q.origin.kind !== "human") return "";
|
|
201
|
+
return textParts(q.prompt, ["text"]).trim();
|
|
202
|
+
};
|
|
174
203
|
function readClaude(lines, from) {
|
|
175
204
|
const msgs = [];
|
|
176
205
|
for (let i = from; i < lines.length; i++) {
|
|
177
206
|
const j = lines[i] ? parse(lines[i]) : null;
|
|
178
207
|
if (!j || j.isSidechain) continue;
|
|
179
208
|
const origin = j.origin;
|
|
180
|
-
if (j.type === "user" && (j.isMeta || origin?.kind && origin.kind !== "human"))
|
|
209
|
+
if (j.type === "user" && (j.isMeta || j.isCompactSummary || j.repeatsCompactSummary || origin?.kind && origin.kind !== "human"))
|
|
210
|
+
continue;
|
|
181
211
|
const at = Date.parse(String(j.timestamp ?? "")) || Date.now();
|
|
182
212
|
const c = j.message?.content;
|
|
183
213
|
if (j.type === "user") {
|
|
@@ -187,13 +217,22 @@ function readClaude(lines, from) {
|
|
|
187
217
|
} else if (j.type === "assistant" && Array.isArray(c)) {
|
|
188
218
|
const text = textParts(c, ["text"]).trim();
|
|
189
219
|
if (text) msgs.push({ role: "assistant", text, at, line: i });
|
|
220
|
+
} else if (j.type === "attachment") {
|
|
221
|
+
const text = queuedPrompt(j.attachment);
|
|
222
|
+
if (text && !isInjected(text)) msgs.push({ role: "user", text, at, line: i });
|
|
190
223
|
}
|
|
191
224
|
}
|
|
192
225
|
return pairTurns(msgs, endOf(lines));
|
|
193
226
|
}
|
|
227
|
+
function codexInjected(text) {
|
|
228
|
+
return /^<[a-z_-]+>/i.test(text) || /^# AGENTS\.md instructions\b/i.test(text);
|
|
229
|
+
}
|
|
194
230
|
function readCodex(lines, from) {
|
|
195
231
|
const msgs = [];
|
|
196
|
-
|
|
232
|
+
const boundary = codexReplayBoundary(parse(lines.find((line) => line.trim()) ?? ""));
|
|
233
|
+
const start = boundary ? lines.findIndex((line) => boundary(parse(line))) : 0;
|
|
234
|
+
if (start < 0) return pairTurns(msgs, endOf(lines));
|
|
235
|
+
for (let i = Math.max(from, start); i < lines.length; i++) {
|
|
197
236
|
const j = lines[i] ? parse(lines[i]) : null;
|
|
198
237
|
if (!j || j.type !== "response_item") continue;
|
|
199
238
|
const p = j.payload;
|
|
@@ -202,7 +241,7 @@ function readCodex(lines, from) {
|
|
|
202
241
|
const text = textParts(p.content, ["input_text", "output_text"]).trim();
|
|
203
242
|
if (!text) continue;
|
|
204
243
|
if (p.role === "user") {
|
|
205
|
-
if (
|
|
244
|
+
if (codexInjected(text)) continue;
|
|
206
245
|
msgs.push({ role: "user", text, at, line: i });
|
|
207
246
|
} else if (p.role === "assistant") msgs.push({ role: "assistant", text, at, line: i });
|
|
208
247
|
}
|
|
@@ -231,7 +270,7 @@ function readAntigravity(lines, from) {
|
|
|
231
270
|
const at = Date.parse(String(j.created_at ?? j.timestamp ?? "")) || Date.now();
|
|
232
271
|
if (j.type === "USER_INPUT") {
|
|
233
272
|
const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
|
|
234
|
-
const text = (m ? m[1]
|
|
273
|
+
const text = (m ? m[1] : j.content).trim();
|
|
235
274
|
if (text) msgs.push({ role: "user", text, at, line: i });
|
|
236
275
|
} else if (j.type === "PLANNER_RESPONSE") {
|
|
237
276
|
const text = j.content.trim();
|
|
@@ -279,6 +318,10 @@ function formatJson(value, f) {
|
|
|
279
318
|
const text = JSON.stringify(value, null, f.indent).replace(/\n/g, f.eol);
|
|
280
319
|
return f.finalNewline ? text + f.eol : text;
|
|
281
320
|
}
|
|
321
|
+
function readJson(file) {
|
|
322
|
+
const f = readJsonFile(file);
|
|
323
|
+
return f.ok ? f.value : {};
|
|
324
|
+
}
|
|
282
325
|
function entryFor(event, harness, cmd, o) {
|
|
283
326
|
const matcher = o.matcherFor(event);
|
|
284
327
|
return {
|
|
@@ -348,8 +391,7 @@ function uninstallClaudeShaped(file, events, opts = {}) {
|
|
|
348
391
|
unlinkSync(file);
|
|
349
392
|
return { file, changed: true, note: "removed (sessionpipe created it)" };
|
|
350
393
|
}
|
|
351
|
-
|
|
352
|
-
if (next !== f.raw) writeFileSync(file, next);
|
|
394
|
+
writeFileSync(file, formatJson(settings2, f));
|
|
353
395
|
return { file, changed: true };
|
|
354
396
|
}
|
|
355
397
|
function installedClaudeShaped(file, harness, events, cmd, o) {
|
|
@@ -376,7 +418,7 @@ var HOOK_NAME = "sessionpipe";
|
|
|
376
418
|
var AG_EVENTS = ["PreInvocation", "PostInvocation", "PostToolUse", "Stop"];
|
|
377
419
|
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';
|
|
378
420
|
var antigravityAnswer = (event) => event === "PreToolUse" ? AG_STALE_PRETOOL : "{}\n";
|
|
379
|
-
var gemini = (env) => env.GEMINI_CLI_HOME || path4.join(
|
|
421
|
+
var gemini = (env) => env.GEMINI_CLI_HOME || path4.join(homeDir(), ".gemini");
|
|
380
422
|
var hooksFile = (env) => path4.join(gemini(env), "config", "hooks.json");
|
|
381
423
|
var projectsDir = (env) => path4.join(gemini(env), "config", "projects");
|
|
382
424
|
var dataDirs = (env) => ["antigravity", "antigravity-cli", "antigravity-ide"].map((d) => path4.join(gemini(env), d));
|
|
@@ -442,7 +484,8 @@ var antigravity = {
|
|
|
442
484
|
const event = input.argv[1] ?? "";
|
|
443
485
|
let s = {};
|
|
444
486
|
try {
|
|
445
|
-
|
|
487
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
488
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
446
489
|
} catch {
|
|
447
490
|
}
|
|
448
491
|
const id = s.conversationId ?? s.session_id ?? s.sessionId;
|
|
@@ -498,19 +541,16 @@ var antigravity = {
|
|
|
498
541
|
}
|
|
499
542
|
return readAntigravity(text.split("\n"), fromLine);
|
|
500
543
|
},
|
|
501
|
-
facts(session, transcript, hints
|
|
544
|
+
facts(session, transcript, hints) {
|
|
502
545
|
if (!transcript) return {};
|
|
503
546
|
return antigravityFacts(transcript, {
|
|
504
547
|
conversation: session.id,
|
|
505
548
|
workspaces: hints.workspaces ?? (session.cwd ? [session.cwd] : []),
|
|
506
|
-
state,
|
|
507
549
|
env: process.env
|
|
508
550
|
});
|
|
509
551
|
},
|
|
510
552
|
backfill(sinceMs, env = process.env) {
|
|
511
|
-
const
|
|
512
|
-
const noState = { get: () => ({}), save: () => {
|
|
513
|
-
} };
|
|
553
|
+
const rows2 = [];
|
|
514
554
|
for (const data of dataDirs(env)) {
|
|
515
555
|
let ids = [];
|
|
516
556
|
try {
|
|
@@ -524,13 +564,13 @@ var antigravity = {
|
|
|
524
564
|
} catch {
|
|
525
565
|
continue;
|
|
526
566
|
}
|
|
527
|
-
const facts = antigravityFacts(f, { conversation: id, workspaces: [],
|
|
567
|
+
const facts = antigravityFacts(f, { conversation: id, workspaces: [], env });
|
|
528
568
|
if (!facts.started_at || !facts.last_at) continue;
|
|
529
569
|
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
530
|
-
|
|
570
|
+
rows2.push({ session: { id, ...rest }, started_at, last_at });
|
|
531
571
|
}
|
|
532
572
|
}
|
|
533
|
-
return
|
|
573
|
+
return rows2;
|
|
534
574
|
}
|
|
535
575
|
};
|
|
536
576
|
function prune(o) {
|
|
@@ -580,7 +620,7 @@ function agProjects(dir) {
|
|
|
580
620
|
}
|
|
581
621
|
const folders = [];
|
|
582
622
|
for (const r of j?.projectResources?.resources ?? []) {
|
|
583
|
-
const f = agPath(r?.folderUri);
|
|
623
|
+
const f = agPath(r?.folderUri ?? r?.gitFolder?.folderUri);
|
|
584
624
|
if (f) folders.push(...agVariants(f));
|
|
585
625
|
}
|
|
586
626
|
if (folders.length) out.push({ id, folders });
|
|
@@ -634,7 +674,7 @@ function antigravityFacts(file, opts) {
|
|
|
634
674
|
for (const j of H) {
|
|
635
675
|
if (j.type !== "USER_INPUT" || typeof j.content !== "string") continue;
|
|
636
676
|
const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
|
|
637
|
-
const text = (m ? m[1]
|
|
677
|
+
const text = (m ? m[1] : j.content).trim();
|
|
638
678
|
const line = (text.split("\n").find((l) => l.trim()) ?? "").trim();
|
|
639
679
|
if (!line || /^<[a-z_-]+>/i.test(line)) continue;
|
|
640
680
|
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
@@ -649,11 +689,288 @@ function antigravityFacts(file, opts) {
|
|
|
649
689
|
return out;
|
|
650
690
|
}
|
|
651
691
|
|
|
692
|
+
// src/adapters/antigravity-control.ts
|
|
693
|
+
var antigravity_control_exports = {};
|
|
694
|
+
__export(antigravity_control_exports, {
|
|
695
|
+
AgyReading: () => AgyReading,
|
|
696
|
+
agyArgs: () => agyArgs,
|
|
697
|
+
agyEnv: () => agyEnv,
|
|
698
|
+
agyError: () => agyError,
|
|
699
|
+
agyLine: () => agyLine,
|
|
700
|
+
agyModeFlags: () => agyModeFlags,
|
|
701
|
+
findAgy: () => findAgy,
|
|
702
|
+
reading: () => reading
|
|
703
|
+
});
|
|
704
|
+
import os2 from "node:os";
|
|
705
|
+
import path6 from "node:path";
|
|
706
|
+
|
|
707
|
+
// src/adapters/headless.ts
|
|
708
|
+
import { spawn } from "node:child_process";
|
|
709
|
+
import { accessSync, constants, statSync as statSync3 } from "node:fs";
|
|
710
|
+
import path5 from "node:path";
|
|
711
|
+
var JOB_TIMEOUT_MS = 30 * 6e4;
|
|
712
|
+
var OUT_CAP = 1024 * 1024;
|
|
713
|
+
function isExe(p) {
|
|
714
|
+
try {
|
|
715
|
+
if (!statSync3(p).isFile()) return false;
|
|
716
|
+
accessSync(p, constants.X_OK);
|
|
717
|
+
return true;
|
|
718
|
+
} catch {
|
|
719
|
+
return false;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
function findBinary(o) {
|
|
723
|
+
if (o.override && isExe(o.override)) return o.override;
|
|
724
|
+
for (const d of String(o.env?.PATH || "").split(path5.delimiter)) {
|
|
725
|
+
if (!d || !path5.isAbsolute(d)) continue;
|
|
726
|
+
for (const n of o.names) if (isExe(path5.join(d, n))) return path5.join(d, n);
|
|
727
|
+
}
|
|
728
|
+
for (const d of o.dirs) for (const n of o.names) if (isExe(path5.join(d, n))) return path5.join(d, n);
|
|
729
|
+
return null;
|
|
730
|
+
}
|
|
731
|
+
function jobEnv(env = process.env) {
|
|
732
|
+
return { ...env, SESSIONPIPE_CONTROL_JOB: "1" };
|
|
733
|
+
}
|
|
734
|
+
function runHeadless(bin, args, o) {
|
|
735
|
+
return new Promise((resolve) => {
|
|
736
|
+
let out = [];
|
|
737
|
+
let outLen = 0;
|
|
738
|
+
let tooBig = false;
|
|
739
|
+
let errTail = "";
|
|
740
|
+
let timedOut = false;
|
|
741
|
+
let settled = false;
|
|
742
|
+
let child;
|
|
743
|
+
try {
|
|
744
|
+
child = spawn(bin, args, {
|
|
745
|
+
cwd: o.cwd,
|
|
746
|
+
env: o.env,
|
|
747
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
748
|
+
detached: process.platform !== "win32",
|
|
749
|
+
windowsHide: true
|
|
750
|
+
});
|
|
751
|
+
} catch (e) {
|
|
752
|
+
resolve({ error: e.message });
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
const kill = (sig) => {
|
|
756
|
+
try {
|
|
757
|
+
if (process.platform !== "win32" && child.pid) process.kill(-child.pid, sig);
|
|
758
|
+
else child.kill(sig);
|
|
759
|
+
} catch {
|
|
760
|
+
try {
|
|
761
|
+
child.kill(sig);
|
|
762
|
+
} catch {
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
};
|
|
766
|
+
const keep = (line) => {
|
|
767
|
+
if (!o.keep?.(line)) return;
|
|
768
|
+
out = [Buffer.from(line)];
|
|
769
|
+
outLen = line.length;
|
|
770
|
+
};
|
|
771
|
+
let partial = "";
|
|
772
|
+
child.stdout?.on("data", (b) => {
|
|
773
|
+
if (o.onLine) {
|
|
774
|
+
const chunk = partial + b.toString("utf8");
|
|
775
|
+
const lines = chunk.split("\n");
|
|
776
|
+
partial = lines.pop();
|
|
777
|
+
for (const line of lines) {
|
|
778
|
+
if (!line.trim()) continue;
|
|
779
|
+
try {
|
|
780
|
+
o.onLine(line);
|
|
781
|
+
} catch {
|
|
782
|
+
}
|
|
783
|
+
keep(line);
|
|
784
|
+
}
|
|
785
|
+
if (partial.length > OUT_CAP) partial = "";
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
if (tooBig) return;
|
|
789
|
+
if (outLen + b.length > OUT_CAP) {
|
|
790
|
+
tooBig = true;
|
|
791
|
+
out = [];
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
out.push(b);
|
|
795
|
+
outLen += b.length;
|
|
796
|
+
});
|
|
797
|
+
child.stderr?.on("data", (b) => {
|
|
798
|
+
errTail = (errTail + b.toString("utf8")).slice(-4e3);
|
|
799
|
+
});
|
|
800
|
+
const timer = setTimeout(() => {
|
|
801
|
+
timedOut = true;
|
|
802
|
+
kill("SIGTERM");
|
|
803
|
+
setTimeout(() => kill("SIGKILL"), 1e4).unref();
|
|
804
|
+
}, o.timeoutMs ?? JOB_TIMEOUT_MS);
|
|
805
|
+
const result = (code, signal) => ({
|
|
806
|
+
code,
|
|
807
|
+
signal,
|
|
808
|
+
stdout: tooBig ? null : Buffer.concat(out).toString("utf8"),
|
|
809
|
+
tooBig,
|
|
810
|
+
stderr: errTail,
|
|
811
|
+
timedOut
|
|
812
|
+
});
|
|
813
|
+
const finish = (r) => {
|
|
814
|
+
if (settled) return;
|
|
815
|
+
settled = true;
|
|
816
|
+
clearTimeout(timer);
|
|
817
|
+
resolve(r);
|
|
818
|
+
};
|
|
819
|
+
child.on("error", (e) => finish({ error: e.message }));
|
|
820
|
+
child.on("exit", (code, signal) => setTimeout(() => finish(result(code, signal)), 2e3).unref());
|
|
821
|
+
child.on("close", (code, signal) => {
|
|
822
|
+
if (o.onLine && partial.trim()) {
|
|
823
|
+
try {
|
|
824
|
+
o.onLine(partial);
|
|
825
|
+
} catch {
|
|
826
|
+
}
|
|
827
|
+
keep(partial);
|
|
828
|
+
partial = "";
|
|
829
|
+
}
|
|
830
|
+
finish(result(code, signal));
|
|
831
|
+
});
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
function stderrTail(stderr, n = 3) {
|
|
835
|
+
return String(stderr || "").trim().split("\n").slice(-n).join(" ").trim();
|
|
836
|
+
}
|
|
837
|
+
function deniedNote(names, count) {
|
|
838
|
+
if (!count) return "";
|
|
839
|
+
return `
|
|
840
|
+
|
|
841
|
+
(Not allowed on this machine, since nobody was there to approve: ${names.join(", ") || `${count} action(s)`}.)`;
|
|
842
|
+
}
|
|
843
|
+
function runFailure(label, res) {
|
|
844
|
+
if (res.error) return `couldn't start ${label}: ${res.error}`;
|
|
845
|
+
if (res.timedOut) return `${label} was still working after 30 minutes, so it was stopped`;
|
|
846
|
+
return null;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// src/adapters/antigravity-control.ts
|
|
850
|
+
var LABEL = "Antigravity";
|
|
851
|
+
function findAgy(env = process.env, home = os2.homedir()) {
|
|
852
|
+
return findBinary({
|
|
853
|
+
override: env.SESSIONPIPE_AGY,
|
|
854
|
+
names: process.platform === "win32" ? ["agy.exe", "agy.cmd"] : ["agy"],
|
|
855
|
+
env,
|
|
856
|
+
dirs: [path6.join(home, ".local", "bin"), "/opt/homebrew/bin", "/usr/local/bin"]
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
function agyModeFlags(mode) {
|
|
860
|
+
return mode === "auto" ? ["--mode", "accept-edits"] : [];
|
|
861
|
+
}
|
|
862
|
+
function agyArgs(o) {
|
|
863
|
+
const prompt = o.prompt.startsWith("-") ? ` ${o.prompt}` : o.prompt;
|
|
864
|
+
return [
|
|
865
|
+
...o.resume ? ["--conversation", o.resume] : [],
|
|
866
|
+
"-p",
|
|
867
|
+
prompt,
|
|
868
|
+
"--output-format",
|
|
869
|
+
"stream-json",
|
|
870
|
+
...agyModeFlags(o.mode)
|
|
871
|
+
];
|
|
872
|
+
}
|
|
873
|
+
var agyEnv = (env = process.env) => jobEnv(env);
|
|
874
|
+
function agyLine(line) {
|
|
875
|
+
let j;
|
|
876
|
+
try {
|
|
877
|
+
j = JSON.parse(line);
|
|
878
|
+
} catch {
|
|
879
|
+
return null;
|
|
880
|
+
}
|
|
881
|
+
if (!j || typeof j !== "object") return null;
|
|
882
|
+
const str = (v) => typeof v === "string" ? v : "";
|
|
883
|
+
const out = {};
|
|
884
|
+
if (j.event === "init") {
|
|
885
|
+
if (str(j.conversation_id)) out.session = str(j.conversation_id);
|
|
886
|
+
} else if (j.event === "step_update") {
|
|
887
|
+
const s = j.step_update ?? {};
|
|
888
|
+
if (str(s.conversation_id)) out.session = str(s.conversation_id);
|
|
889
|
+
if (s.step_type === "agent_response" && str(s.text_delta))
|
|
890
|
+
out.delta = { step: Number(s.step_index ?? 0), text: str(s.text_delta) };
|
|
891
|
+
if (s.step_type === "error_message") {
|
|
892
|
+
const why = str(s.text_delta) || str(s.error) || str(s.message);
|
|
893
|
+
if (why) out.note = why;
|
|
894
|
+
}
|
|
895
|
+
} else if (j.event === "result") {
|
|
896
|
+
const r = j.result ?? {};
|
|
897
|
+
if (str(r.conversation_id)) out.session = str(r.conversation_id);
|
|
898
|
+
out.result = {
|
|
899
|
+
status: str(r.status),
|
|
900
|
+
response: str(r.response),
|
|
901
|
+
error: str(r.error),
|
|
902
|
+
denied: Array.isArray(r.denied_actions) ? r.denied_actions : []
|
|
903
|
+
};
|
|
904
|
+
} else return null;
|
|
905
|
+
return Object.keys(out).length ? out : null;
|
|
906
|
+
}
|
|
907
|
+
function deniedName(d) {
|
|
908
|
+
if (typeof d === "string") return d.slice(0, 80);
|
|
909
|
+
if (!d || typeof d !== "object") return null;
|
|
910
|
+
const o = d;
|
|
911
|
+
for (const k of ["tool_name", "toolName", "name", "tool", "action", "type"])
|
|
912
|
+
if (typeof o[k] === "string" && o[k]) return o[k].slice(0, 80);
|
|
913
|
+
return null;
|
|
914
|
+
}
|
|
915
|
+
function agyError(stderr) {
|
|
916
|
+
const s = String(stderr || "");
|
|
917
|
+
const i = s.lastIndexOf("AGY_ERROR:");
|
|
918
|
+
if (i < 0) return "";
|
|
919
|
+
const raw = s.slice(i + "AGY_ERROR:".length).trim();
|
|
920
|
+
try {
|
|
921
|
+
const j = JSON.parse(raw);
|
|
922
|
+
for (const k of ["message", "error", "detail"]) if (typeof j[k] === "string" && j[k]) return j[k];
|
|
923
|
+
} catch {
|
|
924
|
+
}
|
|
925
|
+
return raw.split("\n")[0].slice(0, 300);
|
|
926
|
+
}
|
|
927
|
+
var AgyReading = class {
|
|
928
|
+
session = null;
|
|
929
|
+
lines = 0;
|
|
930
|
+
buf = "";
|
|
931
|
+
step = null;
|
|
932
|
+
result = null;
|
|
933
|
+
note = null;
|
|
934
|
+
get text() {
|
|
935
|
+
return this.buf;
|
|
936
|
+
}
|
|
937
|
+
feed(line) {
|
|
938
|
+
this.lines++;
|
|
939
|
+
const p = agyLine(line);
|
|
940
|
+
if (!p) return false;
|
|
941
|
+
if (p.session && !this.session) this.session = p.session;
|
|
942
|
+
if (p.note) this.note = p.note;
|
|
943
|
+
if (p.result) this.result = p.result;
|
|
944
|
+
if (!p.delta) return false;
|
|
945
|
+
if (this.step !== null && p.delta.step !== this.step && this.buf && !this.buf.endsWith("\n\n")) this.buf += "\n\n";
|
|
946
|
+
this.step = p.delta.step;
|
|
947
|
+
this.buf += p.delta.text;
|
|
948
|
+
return true;
|
|
949
|
+
}
|
|
950
|
+
outcome(res) {
|
|
951
|
+
const broke = runFailure(LABEL, res);
|
|
952
|
+
if (broke) return { failed: broke };
|
|
953
|
+
const r = this.result;
|
|
954
|
+
if (!r) {
|
|
955
|
+
const why = agyError(res.stderr) || this.note || "";
|
|
956
|
+
return {
|
|
957
|
+
failed: `Antigravity exited (${res.signal || `code ${res.code}`}) without an answer${why ? `: ${why}` : ""}`
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
if (r.status !== "SUCCESS")
|
|
961
|
+
return { failed: r.error || agyError(res.stderr) || this.note || "Antigravity reported an error" };
|
|
962
|
+
const names = [...new Set(r.denied.map(deniedName).filter((n) => !!n))];
|
|
963
|
+
const text = r.response.trim() ? r.response.trim() : this.buf.trim() || "(Antigravity finished without a written reply.)";
|
|
964
|
+
return { reply: text + deniedNote(names, r.denied.length) };
|
|
965
|
+
}
|
|
966
|
+
};
|
|
967
|
+
var reading = () => new AgyReading();
|
|
968
|
+
|
|
652
969
|
// src/adapters/claude-code.ts
|
|
653
970
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
654
971
|
import { createHash } from "node:crypto";
|
|
655
|
-
import { existsSync as existsSync3, readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as
|
|
656
|
-
import
|
|
972
|
+
import { existsSync as existsSync3, readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as statSync4 } from "node:fs";
|
|
973
|
+
import path7 from "node:path";
|
|
657
974
|
|
|
658
975
|
// src/readers/git.ts
|
|
659
976
|
import { execFileSync } from "node:child_process";
|
|
@@ -682,10 +999,9 @@ function gitFacts(cwd) {
|
|
|
682
999
|
return out;
|
|
683
1000
|
}
|
|
684
1001
|
function stripCredentials(url) {
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
return u.replace(/^https?:\/\//, "").replace(/\.git$/, "");
|
|
1002
|
+
const ssh = /^(?:ssh:\/\/)?git@([^:/]+)[:/](.+?)(?:\.git)?$/.exec(url);
|
|
1003
|
+
if (ssh) return `${ssh[1]}/${ssh[2]}`;
|
|
1004
|
+
return url.replace(/\/\/[^/@]*@/, "//").replace(/^https?:\/\//, "").replace(/\.git$/, "");
|
|
689
1005
|
}
|
|
690
1006
|
|
|
691
1007
|
// src/adapters/claude-code.ts
|
|
@@ -716,27 +1032,27 @@ var OPTS = {
|
|
|
716
1032
|
timeoutFor: (e) => e === "PermissionRequest" ? 130 : void 0
|
|
717
1033
|
};
|
|
718
1034
|
var DESKTOP_ID = /^local_[A-Za-z0-9-]{1,64}$/;
|
|
719
|
-
function claudeDirs(env = process.env, home = env.HOME ||
|
|
1035
|
+
function claudeDirs(env = process.env, home = env.HOME || homeDir()) {
|
|
720
1036
|
const out = [];
|
|
721
1037
|
const add = (d) => {
|
|
722
1038
|
if (!d) return;
|
|
723
|
-
const r =
|
|
1039
|
+
const r = path7.resolve(d);
|
|
724
1040
|
if (!out.includes(r)) out.push(r);
|
|
725
1041
|
};
|
|
726
|
-
add(
|
|
1042
|
+
add(path7.join(home, ".claude"));
|
|
727
1043
|
add(env.CLAUDE_CONFIG_DIR);
|
|
728
1044
|
try {
|
|
729
1045
|
for (const e of readdirSync3(home, { withFileTypes: true })) {
|
|
730
1046
|
if (!e.isDirectory() || !/^\.claude[-_.]/.test(e.name)) continue;
|
|
731
|
-
const d =
|
|
732
|
-
if (existsSync3(
|
|
1047
|
+
const d = path7.join(home, e.name);
|
|
1048
|
+
if (existsSync3(path7.join(d, "projects")) || existsSync3(path7.join(d, "settings.json"))) add(d);
|
|
733
1049
|
}
|
|
734
1050
|
} catch {
|
|
735
1051
|
}
|
|
736
1052
|
return out.filter((d, i) => i === 0 || existsSync3(d));
|
|
737
1053
|
}
|
|
738
|
-
var settingsIn = (dir) =>
|
|
739
|
-
var configDirOf = (transcript) =>
|
|
1054
|
+
var settingsIn = (dir) => path7.join(dir, "settings.json");
|
|
1055
|
+
var configDirOf = (transcript) => path7.dirname(path7.dirname(path7.dirname(transcript)));
|
|
740
1056
|
var validTool2 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
741
1057
|
var endReason = (r) => r === "clear" || r === "logout" || r === "exit" ? r : r === "prompt_input_exit" ? "exit" : "other";
|
|
742
1058
|
var notifKind = (t) => t === "permission_prompt" ? "permission" : t === "idle_prompt" ? "idle" : t === "elicitation_dialog" ? "elicitation" : "question";
|
|
@@ -757,11 +1073,13 @@ var claudeCode = {
|
|
|
757
1073
|
const event = input.argv[1] ?? "";
|
|
758
1074
|
let s = {};
|
|
759
1075
|
try {
|
|
760
|
-
|
|
1076
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
1077
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
761
1078
|
} catch {
|
|
762
1079
|
}
|
|
763
1080
|
const id = s.session_id ?? s.sessionId;
|
|
764
1081
|
if (!id) return null;
|
|
1082
|
+
if (notClaudeCode(String(id), s)) return null;
|
|
765
1083
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
766
1084
|
if (typeof s.model === "string") session.model = s.model;
|
|
767
1085
|
if (typeof s.agent_id === "string" && event.startsWith("Subagent") === false && s.agent_id)
|
|
@@ -880,6 +1198,7 @@ var claudeCode = {
|
|
|
880
1198
|
return null;
|
|
881
1199
|
}
|
|
882
1200
|
const hints = {};
|
|
1201
|
+
if (event === "UserPromptSubmit" && typeof s.prompt === "string") hints.prompt = s.prompt;
|
|
883
1202
|
const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
|
|
884
1203
|
if (host && DESKTOP_ID.test(host)) hints.desktop_id = host;
|
|
885
1204
|
if (event !== "PreToolUse" && event !== "PostToolUse") hints.claude_pid = claudePid(input.env);
|
|
@@ -905,31 +1224,35 @@ var claudeCode = {
|
|
|
905
1224
|
return out;
|
|
906
1225
|
},
|
|
907
1226
|
backfill(sinceMs, env = process.env) {
|
|
908
|
-
const
|
|
1227
|
+
const rows2 = [];
|
|
909
1228
|
const files = [];
|
|
910
1229
|
for (const d of claudeDirs(env))
|
|
911
|
-
files.push(...recentFiles(
|
|
1230
|
+
files.push(...recentFiles(path7.join(d, "projects"), 1, sinceMs, (n) => /^[0-9a-f-]{36}\.jsonl$/.test(n)));
|
|
912
1231
|
const noState = { get: () => ({}), save: () => {
|
|
913
1232
|
} };
|
|
914
1233
|
for (const f of files) {
|
|
915
|
-
const id =
|
|
1234
|
+
const id = path7.basename(f, ".jsonl");
|
|
916
1235
|
const facts = claudeFacts(f, id, {}, noState);
|
|
917
1236
|
if (!facts.started_at || !facts.last_at) continue;
|
|
918
1237
|
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
919
1238
|
const cwd = rest.cwd;
|
|
920
|
-
|
|
1239
|
+
rows2.push({
|
|
921
1240
|
session: { id, ...rest, ...gitFacts(cwd), ...rest.branch ? { branch: rest.branch } : {} },
|
|
922
1241
|
started_at,
|
|
923
1242
|
last_at
|
|
924
1243
|
});
|
|
925
1244
|
}
|
|
926
|
-
return
|
|
1245
|
+
return rows2;
|
|
927
1246
|
}
|
|
928
1247
|
};
|
|
929
1248
|
function prune2(o) {
|
|
930
1249
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
931
1250
|
return o;
|
|
932
1251
|
}
|
|
1252
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1253
|
+
function notClaudeCode(id, s) {
|
|
1254
|
+
return typeof s.transcript_path !== "string" && !UUID.test(id);
|
|
1255
|
+
}
|
|
933
1256
|
function pickSource(v) {
|
|
934
1257
|
return typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
|
|
935
1258
|
}
|
|
@@ -972,12 +1295,12 @@ function claudeLive(dir) {
|
|
|
972
1295
|
const map = /* @__PURE__ */ new Map();
|
|
973
1296
|
let names = [];
|
|
974
1297
|
try {
|
|
975
|
-
names = readdirSync3(
|
|
1298
|
+
names = readdirSync3(path7.join(dir, "sessions")).filter((n) => n.endsWith(".json"));
|
|
976
1299
|
} catch {
|
|
977
1300
|
}
|
|
978
1301
|
for (const n of names) {
|
|
979
1302
|
try {
|
|
980
|
-
const e = registryEntry(JSON.parse(readFileSync3(
|
|
1303
|
+
const e = registryEntry(JSON.parse(readFileSync3(path7.join(dir, "sessions", n), "utf8")));
|
|
981
1304
|
if (e?.sessionId) map.set(e.sessionId, e);
|
|
982
1305
|
} catch {
|
|
983
1306
|
}
|
|
@@ -988,7 +1311,7 @@ function claudeLive(dir) {
|
|
|
988
1311
|
function claudeLiveByPid(dir, pid) {
|
|
989
1312
|
if (!pid) return null;
|
|
990
1313
|
try {
|
|
991
|
-
return registryEntry(JSON.parse(readFileSync3(
|
|
1314
|
+
return registryEntry(JSON.parse(readFileSync3(path7.join(dir, "sessions", `${pid}.json`), "utf8")));
|
|
992
1315
|
} catch {
|
|
993
1316
|
return null;
|
|
994
1317
|
}
|
|
@@ -996,7 +1319,7 @@ function claudeLiveByPid(dir, pid) {
|
|
|
996
1319
|
function claudePid(env = process.env) {
|
|
997
1320
|
try {
|
|
998
1321
|
const dirs = claudeDirs(env);
|
|
999
|
-
const has = (pid2) => dirs.some((d) => existsSync3(
|
|
1322
|
+
const has = (pid2) => dirs.some((d) => existsSync3(path7.join(d, "sessions", `${pid2}.json`)));
|
|
1000
1323
|
let pid = process.ppid;
|
|
1001
1324
|
if (has(pid)) return pid;
|
|
1002
1325
|
const parent = parentOf();
|
|
@@ -1035,26 +1358,41 @@ function parentOf() {
|
|
|
1035
1358
|
}
|
|
1036
1359
|
return (pid) => table.get(pid) || 0;
|
|
1037
1360
|
}
|
|
1038
|
-
var
|
|
1039
|
-
function
|
|
1040
|
-
const
|
|
1041
|
-
if (
|
|
1042
|
-
const files = [path5.join(dir, ".claude.json")];
|
|
1043
|
-
if (path5.resolve(dir) === path5.join(HOME, ".claude")) files.unshift(path5.join(HOME, ".claude.json"));
|
|
1044
|
-
let acct = null;
|
|
1361
|
+
var EMAIL_RE = /^[^@\s]{1,64}@[^@\s]{1,189}$/;
|
|
1362
|
+
function claudeLogin(dir, home = homeDir()) {
|
|
1363
|
+
const files = [path7.join(dir, ".claude.json")];
|
|
1364
|
+
if (path7.resolve(dir) === path7.join(home, ".claude")) files.unshift(path7.join(home, ".claude.json"));
|
|
1045
1365
|
for (const f of files) {
|
|
1046
1366
|
try {
|
|
1047
1367
|
const a = JSON.parse(readFileSync3(f, "utf8")).oauthAccount;
|
|
1048
|
-
if (a?.accountUuid) {
|
|
1049
|
-
|
|
1050
|
-
|
|
1368
|
+
if (typeof a?.accountUuid === "string" && a.accountUuid) {
|
|
1369
|
+
const email = typeof a.emailAddress === "string" && EMAIL_RE.test(a.emailAddress) ? a.emailAddress : void 0;
|
|
1370
|
+
return { id: a.accountUuid.slice(0, 128), ...email ? { email } : {} };
|
|
1051
1371
|
}
|
|
1052
1372
|
} catch {
|
|
1053
1373
|
}
|
|
1054
1374
|
}
|
|
1375
|
+
return null;
|
|
1376
|
+
}
|
|
1377
|
+
var accountCache = /* @__PURE__ */ new Map();
|
|
1378
|
+
function claudeAccount(dir) {
|
|
1379
|
+
const hit = accountCache.get(dir);
|
|
1380
|
+
if (hit !== void 0) return hit;
|
|
1381
|
+
const acct = claudeLogin(dir)?.id ?? null;
|
|
1055
1382
|
accountCache.set(dir, acct);
|
|
1056
1383
|
return acct;
|
|
1057
1384
|
}
|
|
1385
|
+
function claudeAccounts(dirs, home = homeDir()) {
|
|
1386
|
+
const out = [];
|
|
1387
|
+
for (const d of dirs) {
|
|
1388
|
+
const l = claudeLogin(d, home);
|
|
1389
|
+
if (!l) continue;
|
|
1390
|
+
const seen = out.find((a) => a.id === l.id);
|
|
1391
|
+
if (!seen) out.push({ harness: NAME2, ...l });
|
|
1392
|
+
else if (!seen.email && l.email) seen.email = l.email;
|
|
1393
|
+
}
|
|
1394
|
+
return out;
|
|
1395
|
+
}
|
|
1058
1396
|
var LINK_RE = /^https:\/\/claude\.ai\/code\/session_[A-Za-z0-9]+$/;
|
|
1059
1397
|
function transcriptLink(records) {
|
|
1060
1398
|
for (let i = records.length - 1; i >= 0; i--) {
|
|
@@ -1092,7 +1430,7 @@ function scanTitles(file, from, found) {
|
|
|
1092
1430
|
const out = { custom: found.custom ?? null, ai: found.ai ?? null, upto: from };
|
|
1093
1431
|
let size = 0;
|
|
1094
1432
|
try {
|
|
1095
|
-
size =
|
|
1433
|
+
size = statSync4(file).size;
|
|
1096
1434
|
} catch {
|
|
1097
1435
|
return out;
|
|
1098
1436
|
}
|
|
@@ -1125,7 +1463,7 @@ function claudeFacts(file, sessionId, hints, state) {
|
|
|
1125
1463
|
const H = parseLines(head);
|
|
1126
1464
|
const T = parseLines(tail);
|
|
1127
1465
|
const out = {};
|
|
1128
|
-
const id = sessionId ||
|
|
1466
|
+
const id = sessionId || path7.basename(file, ".jsonl");
|
|
1129
1467
|
const mem = state.get(NAME2, id);
|
|
1130
1468
|
let custom = null;
|
|
1131
1469
|
let ai = null;
|
|
@@ -1135,7 +1473,7 @@ function claudeFacts(file, sessionId, hints, state) {
|
|
|
1135
1473
|
}
|
|
1136
1474
|
let size = 0;
|
|
1137
1475
|
try {
|
|
1138
|
-
size =
|
|
1476
|
+
size = statSync4(file).size;
|
|
1139
1477
|
} catch {
|
|
1140
1478
|
}
|
|
1141
1479
|
if (!custom && !ai && size > 64 * 1024 + 256 * 1024) {
|
|
@@ -1205,58 +1543,375 @@ function claudeFacts(file, sessionId, hints, state) {
|
|
|
1205
1543
|
return out;
|
|
1206
1544
|
}
|
|
1207
1545
|
|
|
1208
|
-
// src/adapters/
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
function firstTableAt(text) {
|
|
1250
|
-
const st = { depth: 0, str: null };
|
|
1251
|
-
let pos = 0;
|
|
1252
|
-
for (const line of text.split("\n")) {
|
|
1253
|
-
if (!st.str && st.depth === 0 && /^[ \t]*\[/.test(line)) return pos;
|
|
1254
|
-
scan(line, st);
|
|
1255
|
-
pos += line.length + 1;
|
|
1546
|
+
// src/adapters/claude-code-control.ts
|
|
1547
|
+
var claude_code_control_exports = {};
|
|
1548
|
+
__export(claude_code_control_exports, {
|
|
1549
|
+
JOB_TIMEOUT_MS: () => JOB_TIMEOUT_MS,
|
|
1550
|
+
claudeEnv: () => claudeEnv,
|
|
1551
|
+
claudeLogin: () => claudeLogin2,
|
|
1552
|
+
cmpVersion: () => cmpVersion,
|
|
1553
|
+
detectCaps: () => detectCaps,
|
|
1554
|
+
findClaude: () => findClaude,
|
|
1555
|
+
findTranscript: () => findTranscript,
|
|
1556
|
+
isUuid: () => isUuid,
|
|
1557
|
+
liveProcess: () => liveProcess,
|
|
1558
|
+
loginCommand: () => loginCommand,
|
|
1559
|
+
loginHint: () => loginHint,
|
|
1560
|
+
modeFlags: () => modeFlags,
|
|
1561
|
+
parseAuthStatus: () => parseAuthStatus,
|
|
1562
|
+
parseCaps: () => parseCaps,
|
|
1563
|
+
parseResult: () => parseResult,
|
|
1564
|
+
recentlyWritten: () => recentlyWritten,
|
|
1565
|
+
runClaude: () => runClaude,
|
|
1566
|
+
sessionFolder: () => sessionFolder
|
|
1567
|
+
});
|
|
1568
|
+
import { execFile, execFileSync as execFileSync3 } from "node:child_process";
|
|
1569
|
+
import { closeSync as closeSync2, openSync as openSync2, readdirSync as readdirSync4, readFileSync as readFileSync4, readSync as readSync2, statSync as statSync5 } from "node:fs";
|
|
1570
|
+
import os3 from "node:os";
|
|
1571
|
+
import path8 from "node:path";
|
|
1572
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1573
|
+
var LIVE_WRITE_MS = 9e4;
|
|
1574
|
+
var cmpVersion = (a, b) => {
|
|
1575
|
+
const pa = a.split(".").map(Number);
|
|
1576
|
+
const pb = b.split(".").map(Number);
|
|
1577
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++)
|
|
1578
|
+
if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
1579
|
+
return 0;
|
|
1580
|
+
};
|
|
1581
|
+
function findClaude(env = process.env, home = os3.homedir()) {
|
|
1582
|
+
if (env.SESSIONPIPE_CLAUDE && isExe(env.SESSIONPIPE_CLAUDE)) return env.SESSIONPIPE_CLAUDE;
|
|
1583
|
+
const names = process.platform === "win32" ? ["claude.exe", "claude.cmd"] : ["claude"];
|
|
1584
|
+
for (const d of String(env.PATH || "").split(path8.delimiter)) {
|
|
1585
|
+
if (!d || !path8.isAbsolute(d)) continue;
|
|
1586
|
+
for (const n of names) if (isExe(path8.join(d, n))) return path8.join(d, n);
|
|
1256
1587
|
}
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1588
|
+
for (const p of [
|
|
1589
|
+
path8.join(home, ".claude", "local", "claude"),
|
|
1590
|
+
path8.join(home, ".local", "bin", "claude"),
|
|
1591
|
+
"/opt/homebrew/bin/claude",
|
|
1592
|
+
"/usr/local/bin/claude"
|
|
1593
|
+
])
|
|
1594
|
+
if (isExe(p)) return p;
|
|
1595
|
+
const app = path8.join(home, "Library", "Application Support", "Claude", "claude-code");
|
|
1596
|
+
let versions = [];
|
|
1597
|
+
try {
|
|
1598
|
+
versions = readdirSync4(app).filter((n) => /^\d+(\.\d+)*$/.test(n)).sort(cmpVersion).reverse();
|
|
1599
|
+
} catch {
|
|
1600
|
+
}
|
|
1601
|
+
for (const v of versions) {
|
|
1602
|
+
const p = path8.join(app, v, "claude.app", "Contents", "MacOS", "claude");
|
|
1603
|
+
if (isExe(p)) return p;
|
|
1604
|
+
}
|
|
1605
|
+
return null;
|
|
1606
|
+
}
|
|
1607
|
+
function optionBlock(help, flag) {
|
|
1608
|
+
const lines = help.split("\n");
|
|
1609
|
+
const re = new RegExp(`^\\s{0,4}(?:-\\w, )?${flag.replace(/-/g, "\\-")}(?![\\w-])`);
|
|
1610
|
+
const start = lines.findIndex((l) => re.test(l));
|
|
1611
|
+
if (start < 0) return null;
|
|
1612
|
+
const out = [lines[start]];
|
|
1613
|
+
for (let i = start + 1; i < lines.length && lines[i]?.trim() && !/^\s{0,4}-/.test(lines[i]); i++)
|
|
1614
|
+
out.push(lines[i]);
|
|
1615
|
+
return out.join(" ");
|
|
1616
|
+
}
|
|
1617
|
+
function parseCaps(help) {
|
|
1618
|
+
const pm = optionBlock(help, "--permission-mode");
|
|
1619
|
+
const pp = optionBlock(help, "--permission-prompts");
|
|
1620
|
+
return {
|
|
1621
|
+
hasMode: !!pm,
|
|
1622
|
+
modes: pm ? [...pm.matchAll(/"([A-Za-z]+)"/g)].map((m) => m[1]) : [],
|
|
1623
|
+
promptsNone: !!pp && /"none"/.test(pp),
|
|
1624
|
+
fork: !!optionBlock(help, "--fork-session"),
|
|
1625
|
+
stream: !!optionBlock(help, "--include-partial-messages")
|
|
1626
|
+
};
|
|
1627
|
+
}
|
|
1628
|
+
function modeFlags(mode, caps) {
|
|
1629
|
+
if (mode === "auto") {
|
|
1630
|
+
if (!caps.modes.includes("auto"))
|
|
1631
|
+
return { error: "this machine is in auto mode, but its Claude Code has no auto permission mode" };
|
|
1632
|
+
return { args: ["--permission-mode", "auto"] };
|
|
1633
|
+
}
|
|
1634
|
+
if (caps.modes.includes("dontAsk")) return { args: ["--permission-mode", "dontAsk"] };
|
|
1635
|
+
const args = [];
|
|
1636
|
+
if (caps.hasMode)
|
|
1637
|
+
args.push(
|
|
1638
|
+
"--permission-mode",
|
|
1639
|
+
caps.modes.includes("manual") && !caps.modes.includes("default") ? "manual" : "default"
|
|
1640
|
+
);
|
|
1641
|
+
if (caps.promptsNone) args.push("--permission-prompts", "none");
|
|
1642
|
+
return { args };
|
|
1643
|
+
}
|
|
1644
|
+
function claudeEnv(configDir, env = process.env) {
|
|
1645
|
+
const e = { ...env };
|
|
1646
|
+
for (const k of [
|
|
1647
|
+
"CLAUDECODE",
|
|
1648
|
+
"CLAUDE_CODE_SESSION_ID",
|
|
1649
|
+
"CLAUDE_CODE_HOST_SESSION_ID",
|
|
1650
|
+
"CLAUDE_CODE_ENTRYPOINT",
|
|
1651
|
+
"CLAUDE_CODE_SESSION_ATTENDED",
|
|
1652
|
+
"CLAUDE_CODE_SSE_PORT"
|
|
1653
|
+
])
|
|
1654
|
+
delete e[k];
|
|
1655
|
+
e.SESSIONPIPE_CONTROL_JOB = "1";
|
|
1656
|
+
if (configDir !== void 0) {
|
|
1657
|
+
if (path8.resolve(configDir) === path8.resolve(os3.homedir(), ".claude")) delete e.CLAUDE_CONFIG_DIR;
|
|
1658
|
+
else e.CLAUDE_CONFIG_DIR = configDir;
|
|
1659
|
+
}
|
|
1660
|
+
return e;
|
|
1661
|
+
}
|
|
1662
|
+
function parseAuthStatus(stdout) {
|
|
1663
|
+
const text = String(stdout || "").trim();
|
|
1664
|
+
const start = text.indexOf("{");
|
|
1665
|
+
if (start < 0) return { loggedIn: null };
|
|
1666
|
+
try {
|
|
1667
|
+
const j = JSON.parse(text.slice(start));
|
|
1668
|
+
if (typeof j.loggedIn !== "boolean") return { loggedIn: null };
|
|
1669
|
+
return {
|
|
1670
|
+
loggedIn: j.loggedIn,
|
|
1671
|
+
...typeof j.authMethod === "string" ? { method: j.authMethod } : {},
|
|
1672
|
+
...typeof j.subscriptionType === "string" ? { subscription: j.subscriptionType } : {}
|
|
1673
|
+
};
|
|
1674
|
+
} catch {
|
|
1675
|
+
return { loggedIn: null };
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
function claudeLogin2(bin, configDir, env = process.env) {
|
|
1679
|
+
return new Promise((resolve) => {
|
|
1680
|
+
execFile(
|
|
1681
|
+
bin,
|
|
1682
|
+
["auth", "status", "--json"],
|
|
1683
|
+
{ encoding: "utf8", timeout: 2e4, env: claudeEnv(configDir, env), maxBuffer: 256 * 1024 },
|
|
1684
|
+
// `auth status` may exit non-zero when signed out; its JSON still says so.
|
|
1685
|
+
(_err, stdout) => resolve(parseAuthStatus(stdout))
|
|
1686
|
+
);
|
|
1687
|
+
});
|
|
1688
|
+
}
|
|
1689
|
+
function loginCommand(configDir) {
|
|
1690
|
+
const home = os3.homedir();
|
|
1691
|
+
const isDefault = !configDir || path8.resolve(configDir) === path8.resolve(home, ".claude");
|
|
1692
|
+
return isDefault ? "`claude auth login`" : `\`CLAUDE_CONFIG_DIR=${configDir.replace(home, "~")} claude auth login\``;
|
|
1693
|
+
}
|
|
1694
|
+
function loginHint(configDir, platform = process.platform) {
|
|
1695
|
+
const shown = configDir ? configDir.replace(os3.homedir(), "~") : "~/.claude";
|
|
1696
|
+
const login = loginCommand(configDir);
|
|
1697
|
+
if (platform === "darwin")
|
|
1698
|
+
return `Claude Code (${shown}) isn't signed in as this machine's background service sees it. On a Mac its login lives in the login keychain, which a background service can read only while someone is logged in at the screen: log in at the Mac, or run ${login} there.`;
|
|
1699
|
+
return `Claude Code (${shown}) isn't signed in on this machine: run ${login} there.`;
|
|
1700
|
+
}
|
|
1701
|
+
function detectCaps(bin) {
|
|
1702
|
+
let help = "";
|
|
1703
|
+
try {
|
|
1704
|
+
help = execFileSync3(bin, ["--help"], {
|
|
1705
|
+
encoding: "utf8",
|
|
1706
|
+
timeout: 3e4,
|
|
1707
|
+
env: claudeEnv(),
|
|
1708
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1709
|
+
maxBuffer: 4 * 1024 * 1024
|
|
1710
|
+
});
|
|
1711
|
+
} catch (e) {
|
|
1712
|
+
help = String(e?.stdout || "");
|
|
1713
|
+
}
|
|
1714
|
+
return parseCaps(help);
|
|
1715
|
+
}
|
|
1716
|
+
function findTranscript(session, dirs) {
|
|
1717
|
+
if (!UUID_RE.test(session)) return null;
|
|
1718
|
+
for (const dir of dirs) {
|
|
1719
|
+
let projects = [];
|
|
1720
|
+
try {
|
|
1721
|
+
projects = readdirSync4(path8.join(dir, "projects"), { withFileTypes: true });
|
|
1722
|
+
} catch {
|
|
1723
|
+
continue;
|
|
1724
|
+
}
|
|
1725
|
+
for (const p of projects) {
|
|
1726
|
+
if (!p.isDirectory()) continue;
|
|
1727
|
+
const file = path8.join(dir, "projects", p.name, `${session}.jsonl`);
|
|
1728
|
+
try {
|
|
1729
|
+
if (statSync5(file).isFile()) return { file, configDir: dir, project: p.name };
|
|
1730
|
+
} catch {
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
return null;
|
|
1735
|
+
}
|
|
1736
|
+
var projectName = (cwd) => cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
1737
|
+
function sessionFolder(t, cap2 = 8 * 1024 * 1024) {
|
|
1738
|
+
let first = null;
|
|
1739
|
+
let count = 0;
|
|
1740
|
+
let fd;
|
|
1741
|
+
const look = (line) => {
|
|
1742
|
+
if (line.indexOf('"cwd"') < 0) return null;
|
|
1743
|
+
let j;
|
|
1744
|
+
try {
|
|
1745
|
+
j = JSON.parse(line.toString("utf8"));
|
|
1746
|
+
} catch {
|
|
1747
|
+
return null;
|
|
1748
|
+
}
|
|
1749
|
+
if (!j || typeof j.cwd !== "string" || !path8.isAbsolute(j.cwd)) return null;
|
|
1750
|
+
if (projectName(j.cwd) === t.project) return j.cwd;
|
|
1751
|
+
first ??= j.cwd;
|
|
1752
|
+
count++;
|
|
1753
|
+
return null;
|
|
1754
|
+
};
|
|
1755
|
+
try {
|
|
1756
|
+
fd = openSync2(t.file, "r");
|
|
1757
|
+
const buf = Buffer.alloc(256 * 1024);
|
|
1758
|
+
let pos = 0;
|
|
1759
|
+
let rest = Buffer.alloc(0);
|
|
1760
|
+
let skipping = false;
|
|
1761
|
+
while (pos < cap2 && count < 50) {
|
|
1762
|
+
const n = readSync2(fd, buf, 0, buf.length, pos);
|
|
1763
|
+
if (!n) {
|
|
1764
|
+
if (!skipping && rest.length) {
|
|
1765
|
+
const hit = look(rest);
|
|
1766
|
+
if (hit) return hit;
|
|
1767
|
+
}
|
|
1768
|
+
break;
|
|
1769
|
+
}
|
|
1770
|
+
pos += n;
|
|
1771
|
+
let chunk = rest.length ? Buffer.concat([rest, buf.subarray(0, n)]) : buf.subarray(0, n);
|
|
1772
|
+
let nl = chunk.indexOf(10);
|
|
1773
|
+
while (nl >= 0) {
|
|
1774
|
+
const line = chunk.subarray(0, nl);
|
|
1775
|
+
chunk = chunk.subarray(nl + 1);
|
|
1776
|
+
if (skipping) skipping = false;
|
|
1777
|
+
else {
|
|
1778
|
+
const hit = look(line);
|
|
1779
|
+
if (hit) return hit;
|
|
1780
|
+
}
|
|
1781
|
+
nl = chunk.indexOf(10);
|
|
1782
|
+
}
|
|
1783
|
+
if (chunk.length > 2 * 1024 * 1024) {
|
|
1784
|
+
skipping = true;
|
|
1785
|
+
rest = Buffer.alloc(0);
|
|
1786
|
+
} else rest = skipping ? Buffer.alloc(0) : Buffer.from(chunk);
|
|
1787
|
+
}
|
|
1788
|
+
} catch {
|
|
1789
|
+
} finally {
|
|
1790
|
+
if (fd !== void 0)
|
|
1791
|
+
try {
|
|
1792
|
+
closeSync2(fd);
|
|
1793
|
+
} catch {
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
return first;
|
|
1797
|
+
}
|
|
1798
|
+
function pidAlive(pid) {
|
|
1799
|
+
if (!Number.isSafeInteger(pid) || pid <= 1) return false;
|
|
1800
|
+
try {
|
|
1801
|
+
process.kill(pid, 0);
|
|
1802
|
+
return true;
|
|
1803
|
+
} catch (e) {
|
|
1804
|
+
return e.code === "EPERM";
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
function liveProcess(session, configDir) {
|
|
1808
|
+
let names = [];
|
|
1809
|
+
try {
|
|
1810
|
+
names = readdirSync4(path8.join(configDir, "sessions")).filter((n) => /^\d+\.json$/.test(n));
|
|
1811
|
+
} catch {
|
|
1812
|
+
return false;
|
|
1813
|
+
}
|
|
1814
|
+
for (const n of names) {
|
|
1815
|
+
try {
|
|
1816
|
+
const j = JSON.parse(readFileSync4(path8.join(configDir, "sessions", n), "utf8"));
|
|
1817
|
+
if (String(j.sessionId) !== session) continue;
|
|
1818
|
+
if (pidAlive(Number(j.pid ?? n.slice(0, -5)))) return true;
|
|
1819
|
+
} catch {
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
return false;
|
|
1823
|
+
}
|
|
1824
|
+
function recentlyWritten(file, ourLastEnd, now = Date.now()) {
|
|
1825
|
+
let m;
|
|
1826
|
+
try {
|
|
1827
|
+
m = statSync5(file).mtimeMs;
|
|
1828
|
+
} catch {
|
|
1829
|
+
return false;
|
|
1830
|
+
}
|
|
1831
|
+
if (now - m > LIVE_WRITE_MS) return false;
|
|
1832
|
+
return !(ourLastEnd && m <= ourLastEnd + 2e3);
|
|
1833
|
+
}
|
|
1834
|
+
function runClaude(bin, args, o) {
|
|
1835
|
+
return runHeadless(bin, args, { ...o, keep: o.keep ?? ((line) => line.includes('"type":"result"')) });
|
|
1836
|
+
}
|
|
1837
|
+
function parseResult(stdout) {
|
|
1838
|
+
const s = String(stdout || "").trim();
|
|
1839
|
+
if (!s) return null;
|
|
1840
|
+
const pick = (j) => {
|
|
1841
|
+
if (Array.isArray(j)) {
|
|
1842
|
+
for (let i = j.length - 1; i >= 0; i--) if (j[i]?.type === "result") return j[i];
|
|
1843
|
+
return null;
|
|
1844
|
+
}
|
|
1845
|
+
return j && typeof j === "object" ? j : null;
|
|
1846
|
+
};
|
|
1847
|
+
try {
|
|
1848
|
+
return pick(JSON.parse(s));
|
|
1849
|
+
} catch {
|
|
1850
|
+
}
|
|
1851
|
+
const lines = s.split("\n");
|
|
1852
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
1853
|
+
try {
|
|
1854
|
+
const j = pick(JSON.parse(lines[i]));
|
|
1855
|
+
if (j && (j.type === "result" || "result" in j)) return j;
|
|
1856
|
+
} catch {
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
return null;
|
|
1860
|
+
}
|
|
1861
|
+
var isUuid = (s) => typeof s === "string" && UUID_RE.test(s);
|
|
1862
|
+
|
|
1863
|
+
// src/adapters/codex.ts
|
|
1864
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1865
|
+
import path9 from "node:path";
|
|
1866
|
+
|
|
1867
|
+
// src/adapters/codex-config.ts
|
|
1868
|
+
var NOTIFY_LINE = /^[ \t]*notify[ \t]*=[ \t]*(\[.*\])[ \t]*(?:#.*)?$/m;
|
|
1869
|
+
var NOTIFY_KEY = /^[ \t]*notify[ \t]*=/m;
|
|
1870
|
+
var OURS2 = /(?:hook\.js|sessionpipe-hook)",\s*"codex",\s*"notify"/;
|
|
1871
|
+
function scan(line, st) {
|
|
1872
|
+
let i = 0;
|
|
1873
|
+
while (i < line.length) {
|
|
1874
|
+
if (st.str) {
|
|
1875
|
+
const j = line.indexOf(st.str, i);
|
|
1876
|
+
if (j < 0) return;
|
|
1877
|
+
i = j + 3;
|
|
1878
|
+
st.str = null;
|
|
1879
|
+
continue;
|
|
1880
|
+
}
|
|
1881
|
+
const ch = line[i];
|
|
1882
|
+
if (ch === "#") return;
|
|
1883
|
+
if (line.startsWith('"""', i) || line.startsWith("'''", i)) {
|
|
1884
|
+
st.str = line.slice(i, i + 3);
|
|
1885
|
+
i += 3;
|
|
1886
|
+
continue;
|
|
1887
|
+
}
|
|
1888
|
+
if (ch === '"') {
|
|
1889
|
+
i++;
|
|
1890
|
+
while (i < line.length && line[i] !== '"') i += line[i] === "\\" ? 2 : 1;
|
|
1891
|
+
i++;
|
|
1892
|
+
continue;
|
|
1893
|
+
}
|
|
1894
|
+
if (ch === "'") {
|
|
1895
|
+
const j = line.indexOf("'", i + 1);
|
|
1896
|
+
i = j < 0 ? line.length : j + 1;
|
|
1897
|
+
continue;
|
|
1898
|
+
}
|
|
1899
|
+
if (ch === "[") st.depth++;
|
|
1900
|
+
else if (ch === "]") st.depth = Math.max(0, st.depth - 1);
|
|
1901
|
+
i++;
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
function firstTableAt(text) {
|
|
1905
|
+
const st = { depth: 0, str: null };
|
|
1906
|
+
let pos = 0;
|
|
1907
|
+
for (const line of text.split("\n")) {
|
|
1908
|
+
if (!st.str && st.depth === 0 && /^[ \t]*\[/.test(line)) return pos;
|
|
1909
|
+
scan(line, st);
|
|
1910
|
+
pos += line.length + 1;
|
|
1911
|
+
}
|
|
1912
|
+
return -1;
|
|
1913
|
+
}
|
|
1914
|
+
function split(text) {
|
|
1260
1915
|
const at = firstTableAt(text);
|
|
1261
1916
|
return at < 0 ? { head: text, tail: "", at: text.length } : { head: text.slice(0, at), tail: text.slice(at), at };
|
|
1262
1917
|
}
|
|
@@ -1271,18 +1926,13 @@ function parseArgv(raw) {
|
|
|
1271
1926
|
function readNotify(text) {
|
|
1272
1927
|
const { head, tail, at } = split(text);
|
|
1273
1928
|
const m = NOTIFY_LINE.exec(head);
|
|
1274
|
-
if (m)
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
index: m.index,
|
|
1279
|
-
length: m[0].length,
|
|
1280
|
-
misplaced: false,
|
|
1281
|
-
other: !OURS2.test(m[1] ?? "")
|
|
1282
|
-
};
|
|
1929
|
+
if (m) {
|
|
1930
|
+
const raw = m[1];
|
|
1931
|
+
return { raw, argv: parseArgv(raw), index: m.index, length: m[0].length, misplaced: false, other: !OURS2.test(raw) };
|
|
1932
|
+
}
|
|
1283
1933
|
const k = NOTIFY_KEY.exec(head);
|
|
1284
1934
|
if (k) {
|
|
1285
|
-
const rest = head.slice(k.index).split("\n")[0]
|
|
1935
|
+
const rest = head.slice(k.index).split("\n")[0];
|
|
1286
1936
|
return {
|
|
1287
1937
|
raw: rest.replace(NOTIFY_KEY, "").trim(),
|
|
1288
1938
|
argv: null,
|
|
@@ -1293,15 +1943,11 @@ function readNotify(text) {
|
|
|
1293
1943
|
};
|
|
1294
1944
|
}
|
|
1295
1945
|
const t = NOTIFY_LINE.exec(tail);
|
|
1296
|
-
if (t
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
argv: parseArgv(t[
|
|
1300
|
-
|
|
1301
|
-
length: t[0].length,
|
|
1302
|
-
misplaced: true,
|
|
1303
|
-
other: false
|
|
1304
|
-
};
|
|
1946
|
+
if (t) {
|
|
1947
|
+
const raw = t[1];
|
|
1948
|
+
if (OURS2.test(raw))
|
|
1949
|
+
return { raw, argv: parseArgv(raw), index: at + t.index, length: t[0].length, misplaced: true, other: false };
|
|
1950
|
+
}
|
|
1305
1951
|
return null;
|
|
1306
1952
|
}
|
|
1307
1953
|
function dropLine(text, index, length) {
|
|
@@ -1320,9 +1966,7 @@ function withNotify(text, line) {
|
|
|
1320
1966
|
if (n?.misplaced) return insertTopLevel(dropLine(text, n.index, n.length), line);
|
|
1321
1967
|
return insertTopLevel(text, line);
|
|
1322
1968
|
}
|
|
1323
|
-
function replaceNotify(text, line) {
|
|
1324
|
-
const n = readNotify(text);
|
|
1325
|
-
if (!n || n.misplaced || !n.length) return null;
|
|
1969
|
+
function replaceNotify(text, n, line) {
|
|
1326
1970
|
return text.slice(0, n.index) + line + text.slice(n.index + n.length);
|
|
1327
1971
|
}
|
|
1328
1972
|
function withoutNotify(text) {
|
|
@@ -1363,24 +2007,24 @@ var CODEX_EVENTS = [
|
|
|
1363
2007
|
];
|
|
1364
2008
|
var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
1365
2009
|
var eventsFor2 = (lean) => lean ? CODEX_EVENTS.filter((e) => e !== "PreToolUse") : [...CODEX_EVENTS];
|
|
1366
|
-
var codexHome = (env) => env.CODEX_HOME ||
|
|
1367
|
-
var hooksFile2 = (env) =>
|
|
1368
|
-
var configFile2 = (env) =>
|
|
1369
|
-
var sessionsDir = (env) =>
|
|
2010
|
+
var codexHome = (env) => env.CODEX_HOME || path9.join(homeDir(), ".codex");
|
|
2011
|
+
var hooksFile2 = (env) => path9.join(codexHome(env), "hooks.json");
|
|
2012
|
+
var configFile2 = (env) => path9.join(codexHome(env), "config.toml");
|
|
2013
|
+
var sessionsDir = (env) => path9.join(codexHome(env), "sessions");
|
|
1370
2014
|
var validTool3 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
1371
2015
|
var endReason2 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
|
|
1372
2016
|
function installNotify(cmd, env) {
|
|
1373
2017
|
const file = configFile2(env);
|
|
1374
2018
|
let text = "";
|
|
1375
2019
|
try {
|
|
1376
|
-
text =
|
|
2020
|
+
text = readFileSync5(file, "utf8");
|
|
1377
2021
|
} catch {
|
|
1378
2022
|
}
|
|
1379
2023
|
const argv = cmd([NAME3, "notify"]).argv;
|
|
1380
2024
|
const line = `notify = ${JSON.stringify(argv)}`;
|
|
1381
2025
|
const n = readNotify(text);
|
|
1382
2026
|
const write = (next) => {
|
|
1383
|
-
mkdirSync3(
|
|
2027
|
+
mkdirSync3(path9.dirname(file), { recursive: true });
|
|
1384
2028
|
writeFileSync3(file, next);
|
|
1385
2029
|
};
|
|
1386
2030
|
if (n && !n.other) {
|
|
@@ -1401,11 +2045,8 @@ function installNotify(cmd, env) {
|
|
|
1401
2045
|
return { file, changed: false, note: "notify current (after Codex Computer Use)" };
|
|
1402
2046
|
}
|
|
1403
2047
|
if (!prev || OURS2.test(JSON.stringify(prev))) {
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
write(next);
|
|
1407
|
-
return { file, changed: true, note: "chained after Codex Computer Use (--previous-notify)" };
|
|
1408
|
-
}
|
|
2048
|
+
write(replaceNotify(text, n, `notify = ${withPreviousRaw(n.raw, argv)}`));
|
|
2049
|
+
return { file, changed: true, note: "chained after Codex Computer Use (--previous-notify)" };
|
|
1409
2050
|
}
|
|
1410
2051
|
}
|
|
1411
2052
|
return {
|
|
@@ -1422,7 +2063,7 @@ function uninstallNotify(env) {
|
|
|
1422
2063
|
const file = configFile2(env);
|
|
1423
2064
|
let text;
|
|
1424
2065
|
try {
|
|
1425
|
-
text =
|
|
2066
|
+
text = readFileSync5(file, "utf8");
|
|
1426
2067
|
} catch {
|
|
1427
2068
|
return { file, changed: false };
|
|
1428
2069
|
}
|
|
@@ -1434,24 +2075,24 @@ function uninstallNotify(env) {
|
|
|
1434
2075
|
}
|
|
1435
2076
|
const prev = previousNotify(n.argv);
|
|
1436
2077
|
if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev))) {
|
|
1437
|
-
|
|
1438
|
-
if (next) writeFileSync3(file, next);
|
|
2078
|
+
writeFileSync3(file, replaceNotify(text, n, `notify = ${stripPreviousRaw(n.raw)}`));
|
|
1439
2079
|
return { file, changed: true };
|
|
1440
2080
|
}
|
|
1441
2081
|
return { file, changed: false };
|
|
1442
2082
|
}
|
|
1443
|
-
function findRollout(threadId, env) {
|
|
2083
|
+
function findRollout(threadId, env = process.env) {
|
|
2084
|
+
if (!/^[A-Za-z0-9-]{8,128}$/.test(threadId)) return null;
|
|
1444
2085
|
let best = null;
|
|
1445
2086
|
const walk = (dir, depth) => {
|
|
1446
2087
|
let names = [];
|
|
1447
2088
|
try {
|
|
1448
|
-
names =
|
|
2089
|
+
names = readdirSync5(dir);
|
|
1449
2090
|
} catch {
|
|
1450
2091
|
return;
|
|
1451
2092
|
}
|
|
1452
2093
|
for (const n of names.sort().reverse()) {
|
|
1453
|
-
const p =
|
|
1454
|
-
if (n.endsWith(
|
|
2094
|
+
const p = path9.join(dir, n);
|
|
2095
|
+
if (n.endsWith(`-${threadId}.jsonl`)) {
|
|
1455
2096
|
best = p;
|
|
1456
2097
|
return;
|
|
1457
2098
|
}
|
|
@@ -1464,6 +2105,13 @@ function findRollout(threadId, env) {
|
|
|
1464
2105
|
walk(sessionsDir(env), 0);
|
|
1465
2106
|
return best;
|
|
1466
2107
|
}
|
|
2108
|
+
function rolloutMeta(file) {
|
|
2109
|
+
return scanLines(file, (line) => parseLines([line])[0] ?? {});
|
|
2110
|
+
}
|
|
2111
|
+
function ofAnotherThread(file, id) {
|
|
2112
|
+
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);
|
|
2113
|
+
return !!m?.[1] && m[1].toLowerCase() !== id.toLowerCase();
|
|
2114
|
+
}
|
|
1467
2115
|
var codex = {
|
|
1468
2116
|
name: NAME3,
|
|
1469
2117
|
events: CODEX_EVENTS,
|
|
@@ -1487,7 +2135,7 @@ var codex = {
|
|
|
1487
2135
|
];
|
|
1488
2136
|
let text = "";
|
|
1489
2137
|
try {
|
|
1490
|
-
text =
|
|
2138
|
+
text = readFileSync5(configFile2(env), "utf8");
|
|
1491
2139
|
} catch {
|
|
1492
2140
|
}
|
|
1493
2141
|
const n = readNotify(text);
|
|
@@ -1506,11 +2154,14 @@ var codex = {
|
|
|
1506
2154
|
if (event === "notify") {
|
|
1507
2155
|
const raw = input.argv.find((a) => a.startsWith("{")) ?? input.stdin;
|
|
1508
2156
|
try {
|
|
1509
|
-
|
|
2157
|
+
const j = raw ? JSON.parse(raw) : {};
|
|
2158
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1510
2159
|
} catch {
|
|
1511
2160
|
}
|
|
1512
2161
|
const id2 = s["thread-id"] ?? s.thread_id ?? s["session-id"] ?? s.session_id;
|
|
1513
2162
|
if (!id2) return null;
|
|
2163
|
+
const file = findRollout(String(id2), input.env);
|
|
2164
|
+
if (!file || codexSubagent(rolloutMeta(file))) return null;
|
|
1514
2165
|
const r2 = {
|
|
1515
2166
|
session: { id: String(id2), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd },
|
|
1516
2167
|
events: [
|
|
@@ -1520,18 +2171,19 @@ var codex = {
|
|
|
1520
2171
|
harnessEvent: "notify"
|
|
1521
2172
|
}
|
|
1522
2173
|
],
|
|
1523
|
-
hints: {}
|
|
2174
|
+
hints: {},
|
|
2175
|
+
transcript: file
|
|
1524
2176
|
};
|
|
1525
|
-
const file = findRollout(String(id2), input.env);
|
|
1526
|
-
if (file) r2.transcript = file;
|
|
1527
2177
|
return r2;
|
|
1528
2178
|
}
|
|
1529
2179
|
try {
|
|
1530
|
-
|
|
2180
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
2181
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1531
2182
|
} catch {
|
|
1532
2183
|
}
|
|
1533
2184
|
const id = s.session_id;
|
|
1534
2185
|
if (!id) return null;
|
|
2186
|
+
if (s.transcript_path === null) return null;
|
|
1535
2187
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
1536
2188
|
if (typeof s.model === "string") session.model = s.model;
|
|
1537
2189
|
const turn_id = typeof s.turn_id === "string" ? s.turn_id : void 0;
|
|
@@ -1591,230 +2243,20 @@ var codex = {
|
|
|
1591
2243
|
})
|
|
1592
2244
|
);
|
|
1593
2245
|
break;
|
|
1594
|
-
case "PreCompact":
|
|
1595
|
-
events.push(ev("context.compacted", { trigger: s.trigger === "manual" ? "manual" : "auto" }));
|
|
1596
|
-
break;
|
|
1597
|
-
case "PostCompact":
|
|
1598
|
-
break;
|
|
1599
|
-
case "SessionEnd":
|
|
1600
|
-
events.push(ev("session.ended", { reason: endReason2(s.reason) }));
|
|
1601
|
-
break;
|
|
1602
|
-
default:
|
|
1603
|
-
return null;
|
|
1604
|
-
}
|
|
1605
|
-
const r = { session, events, hints: {} };
|
|
1606
|
-
if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
|
|
1607
|
-
return r;
|
|
1608
|
-
},
|
|
1609
|
-
readTranscript(file, fromLine) {
|
|
1610
|
-
let text = "";
|
|
1611
|
-
try {
|
|
1612
|
-
text = readFileSync4(file, "utf8");
|
|
1613
|
-
} catch {
|
|
1614
|
-
return { turns: [], line: fromLine };
|
|
1615
|
-
}
|
|
1616
|
-
return readCodex(text.split("\n"), fromLine);
|
|
1617
|
-
},
|
|
1618
|
-
facts(session, transcript) {
|
|
1619
|
-
const out = {};
|
|
1620
|
-
if (transcript) Object.assign(out, codexFacts(transcript));
|
|
1621
|
-
return out;
|
|
1622
|
-
},
|
|
1623
|
-
backfill(sinceMs, env = process.env) {
|
|
1624
|
-
const rows = [];
|
|
1625
|
-
for (const f of recentFiles(sessionsDir(env), 3, sinceMs, (n) => /^rollout-.*\.jsonl$/.test(n))) {
|
|
1626
|
-
const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(path6.basename(f));
|
|
1627
|
-
if (!m?.[1]) continue;
|
|
1628
|
-
const facts = codexFacts(f);
|
|
1629
|
-
if (!facts.started_at || !facts.last_at) continue;
|
|
1630
|
-
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
1631
|
-
rows.push({ session: { id: m[1], ...rest }, started_at, last_at });
|
|
1632
|
-
}
|
|
1633
|
-
return rows;
|
|
1634
|
-
}
|
|
1635
|
-
};
|
|
1636
|
-
function prune3(o) {
|
|
1637
|
-
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
1638
|
-
return o;
|
|
1639
|
-
}
|
|
1640
|
-
function codexFacts(file) {
|
|
1641
|
-
const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
|
|
1642
|
-
const H = parseLines(head);
|
|
1643
|
-
const T = parseLines(tail);
|
|
1644
|
-
const out = {};
|
|
1645
|
-
const meta = H.find((j) => j.type === "session_meta" && j.payload);
|
|
1646
|
-
if (meta?.payload) {
|
|
1647
|
-
const p = meta.payload;
|
|
1648
|
-
if (p.cwd) out.cwd = p.cwd;
|
|
1649
|
-
if (p.cli_version) out.harness_version = String(p.cli_version);
|
|
1650
|
-
if (p.git?.branch) out.branch = p.git.branch;
|
|
1651
|
-
if (p.git?.repository_url) out.repo = String(p.git.repository_url).replace(/\/\/[^/@]*@/, "//");
|
|
1652
|
-
}
|
|
1653
|
-
const ctx = H.concat(T).reverse().find((j) => j.type === "turn_context" && j.payload?.model);
|
|
1654
|
-
if (ctx?.payload?.model) out.model = ctx.payload.model;
|
|
1655
|
-
for (const j of H) {
|
|
1656
|
-
const p = j.payload;
|
|
1657
|
-
if (j.type !== "response_item" || !p || p.type !== "message" || p.role !== "user") continue;
|
|
1658
|
-
const text = (p.content ?? []).filter((b) => b && b.type === "input_text" && typeof b.text === "string").map((b) => b.text).join("\n").trim();
|
|
1659
|
-
if (!text || /^<[a-z_-]+>/i.test(text)) continue;
|
|
1660
|
-
const line = text.split("\n").find((l) => l.trim()) ?? "";
|
|
1661
|
-
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
1662
|
-
out.title_source = "first-ask";
|
|
1663
|
-
break;
|
|
1664
|
-
}
|
|
1665
|
-
const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
|
|
1666
|
-
if (times.length) {
|
|
1667
|
-
out.started_at = Math.min(...times);
|
|
1668
|
-
out.last_at = Math.max(...times);
|
|
1669
|
-
}
|
|
1670
|
-
return out;
|
|
1671
|
-
}
|
|
1672
|
-
|
|
1673
|
-
// src/adapters/gemini-cli.ts
|
|
1674
|
-
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
1675
|
-
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
|
|
1676
|
-
import path7 from "node:path";
|
|
1677
|
-
var NAME4 = "gemini-cli";
|
|
1678
|
-
var GEMINI_EVENTS = [
|
|
1679
|
-
"SessionStart",
|
|
1680
|
-
"BeforeAgent",
|
|
1681
|
-
"BeforeTool",
|
|
1682
|
-
"AfterTool",
|
|
1683
|
-
"Notification",
|
|
1684
|
-
"AfterAgent",
|
|
1685
|
-
"PreCompress",
|
|
1686
|
-
"SessionEnd"
|
|
1687
|
-
];
|
|
1688
|
-
var OPTS3 = { matcherFor: (e) => /Tool/.test(e) ? ".*" : void 0, timeout: 5e3 };
|
|
1689
|
-
var geminiDir = (env) => env.GEMINI_CLI_HOME || path7.join(HOME, ".gemini");
|
|
1690
|
-
var settings = (env) => path7.join(geminiDir(env), "settings.json");
|
|
1691
|
-
var validTool4 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
1692
|
-
var eventsFor3 = (lean) => lean ? GEMINI_EVENTS.filter((e) => e !== "BeforeTool") : [...GEMINI_EVENTS];
|
|
1693
|
-
function geminiHooksActive(settingsFile) {
|
|
1694
|
-
let tools;
|
|
1695
|
-
try {
|
|
1696
|
-
tools = JSON.parse(readFileSync5(settingsFile, "utf8")).tools;
|
|
1697
|
-
} catch {
|
|
1698
|
-
}
|
|
1699
|
-
if (tools?.enableHooks === true) return { active: true };
|
|
1700
|
-
if (tools?.enableHooks === false)
|
|
1701
|
-
return { active: false, note: "inactive: tools.enableHooks is false in settings.json" };
|
|
1702
|
-
let version = "";
|
|
1703
|
-
try {
|
|
1704
|
-
version = execFileSync3("gemini", ["--version"], {
|
|
1705
|
-
encoding: "utf8",
|
|
1706
|
-
timeout: 4e3,
|
|
1707
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
1708
|
-
}).trim();
|
|
1709
|
-
} catch {
|
|
1710
|
-
return {
|
|
1711
|
-
active: true,
|
|
1712
|
-
note: "could not run `gemini --version`; hooks need tools.enableHooks on versions before 0.46"
|
|
1713
|
-
};
|
|
1714
|
-
}
|
|
1715
|
-
const m = /(\d+)\.(\d+)/.exec(version);
|
|
1716
|
-
if (m && (Number(m[1]) > 0 || Number(m[2]) >= 46)) return { active: true };
|
|
1717
|
-
return {
|
|
1718
|
-
active: false,
|
|
1719
|
-
note: `inactive: Gemini CLI ${version || "?"} runs hooks only with "tools": {"enableHooks": true} in settings.json`
|
|
1720
|
-
};
|
|
1721
|
-
}
|
|
1722
|
-
var geminiCli = {
|
|
1723
|
-
name: NAME4,
|
|
1724
|
-
events: GEMINI_EVENTS,
|
|
1725
|
-
// ~/.gemini also belongs to Antigravity; Gemini CLI is here when its settings or its chat logs exist.
|
|
1726
|
-
detect: (env = process.env) => existsSync5(settings(env)) || existsSync5(path7.join(geminiDir(env), "tmp")),
|
|
1727
|
-
configFiles: (env = process.env) => [settings(env)],
|
|
1728
|
-
install: (cmd, env = process.env, opts = {}) => {
|
|
1729
|
-
const r = installClaudeShaped(settings(env), NAME4, eventsFor3(opts.lean), cmd, OPTS3);
|
|
1730
|
-
const a = geminiHooksActive(settings(env));
|
|
1731
|
-
return [a.note && !r.skipped ? { ...r, note: [r.note, a.note].filter(Boolean).join("; ") } : r];
|
|
1732
|
-
},
|
|
1733
|
-
uninstall: (env = process.env, opts = {}) => [
|
|
1734
|
-
uninstallClaudeShaped(settings(env), GEMINI_EVENTS, { created: opts.created?.includes(settings(env)) })
|
|
1735
|
-
],
|
|
1736
|
-
installed: (cmd, env = process.env, opts = {}) => {
|
|
1737
|
-
const state = installedClaudeShaped(settings(env), NAME4, eventsFor3(opts.lean), cmd, OPTS3);
|
|
1738
|
-
const a = geminiHooksActive(settings(env));
|
|
1739
|
-
return [
|
|
1740
|
-
{
|
|
1741
|
-
file: settings(env),
|
|
1742
|
-
state: state === "current" && !a.active ? "inactive" : state,
|
|
1743
|
-
...a.note ? { note: a.note } : {}
|
|
1744
|
-
}
|
|
1745
|
-
];
|
|
1746
|
-
},
|
|
1747
|
-
fromHook(input) {
|
|
1748
|
-
const event = input.argv[1] ?? "";
|
|
1749
|
-
let s = {};
|
|
1750
|
-
try {
|
|
1751
|
-
s = input.stdin ? JSON.parse(input.stdin) : {};
|
|
1752
|
-
} catch {
|
|
1753
|
-
}
|
|
1754
|
-
const id = s.session_id;
|
|
1755
|
-
if (!id) return null;
|
|
1756
|
-
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
1757
|
-
const tool = validTool4(s.tool_name) ? s.tool_name : void 0;
|
|
1758
|
-
const ev = (type, data) => ({ type, data: prune4(data), harnessEvent: event });
|
|
1759
|
-
const events = [];
|
|
1760
|
-
switch (event) {
|
|
1761
|
-
case "SessionStart":
|
|
1762
|
-
events.push(
|
|
1763
|
-
ev("session.started", {
|
|
1764
|
-
source: typeof s.source === "string" && ["startup", "resume", "clear"].includes(s.source) ? s.source : "unknown"
|
|
1765
|
-
})
|
|
1766
|
-
);
|
|
1767
|
-
break;
|
|
1768
|
-
case "BeforeAgent":
|
|
1769
|
-
events.push(
|
|
1770
|
-
ev("turn.started", { prompt_chars: typeof s.prompt === "string" ? [...s.prompt].length : void 0 })
|
|
1771
|
-
);
|
|
1772
|
-
break;
|
|
1773
|
-
case "BeforeTool":
|
|
1774
|
-
if (tool) events.push(ev("tool.started", { tool, input: s.tool_input }));
|
|
1775
|
-
break;
|
|
1776
|
-
case "AfterTool": {
|
|
1777
|
-
const resp = s.tool_response;
|
|
1778
|
-
const err = resp && typeof resp === "object" && resp.error ? String(typeof resp.error === "string" ? resp.error : JSON.stringify(resp.error)).slice(0, 2e3) : void 0;
|
|
1779
|
-
if (tool)
|
|
1780
|
-
events.push(
|
|
1781
|
-
ev("tool.ended", {
|
|
1782
|
-
tool,
|
|
1783
|
-
ok: !err,
|
|
1784
|
-
error: err,
|
|
1785
|
-
input: s.tool_input,
|
|
1786
|
-
output: resp?.llmContent ?? s.tool_response
|
|
1787
|
-
})
|
|
1788
|
-
);
|
|
1789
|
-
break;
|
|
1790
|
-
}
|
|
1791
|
-
case "Notification":
|
|
1792
|
-
events.push(
|
|
1793
|
-
ev("attention.needed", {
|
|
1794
|
-
attention_id: `notif-${Date.now()}`,
|
|
1795
|
-
kind: s.notification_type === "ToolPermission" ? "permission" : "question",
|
|
1796
|
-
message: typeof s.message === "string" ? s.message.slice(0, 200) : void 0
|
|
1797
|
-
})
|
|
1798
|
-
);
|
|
1799
|
-
break;
|
|
1800
|
-
case "AfterAgent":
|
|
1801
|
-
events.push(ev("turn.ended", { reason: "stop" }));
|
|
1802
|
-
break;
|
|
1803
|
-
case "PreCompress":
|
|
2246
|
+
case "PreCompact":
|
|
1804
2247
|
events.push(ev("context.compacted", { trigger: s.trigger === "manual" ? "manual" : "auto" }));
|
|
1805
2248
|
break;
|
|
2249
|
+
case "PostCompact":
|
|
2250
|
+
break;
|
|
1806
2251
|
case "SessionEnd":
|
|
1807
|
-
events.push(
|
|
1808
|
-
ev("session.ended", {
|
|
1809
|
-
reason: s.reason === "clear" || s.reason === "logout" || s.reason === "exit" ? s.reason : s.reason === "prompt_input_exit" ? "exit" : "other"
|
|
1810
|
-
})
|
|
1811
|
-
);
|
|
2252
|
+
events.push(ev("session.ended", { reason: endReason2(s.reason) }));
|
|
1812
2253
|
break;
|
|
1813
2254
|
default:
|
|
1814
2255
|
return null;
|
|
1815
2256
|
}
|
|
1816
2257
|
const r = { session, events, hints: {} };
|
|
1817
|
-
if (typeof s.transcript_path === "string"
|
|
2258
|
+
if (typeof s.transcript_path === "string" && !ofAnotherThread(s.transcript_path, String(id)))
|
|
2259
|
+
r.transcript = s.transcript_path;
|
|
1818
2260
|
return r;
|
|
1819
2261
|
},
|
|
1820
2262
|
readTranscript(file, fromLine) {
|
|
@@ -1824,469 +2266,827 @@ var geminiCli = {
|
|
|
1824
2266
|
} catch {
|
|
1825
2267
|
return { turns: [], line: fromLine };
|
|
1826
2268
|
}
|
|
1827
|
-
return
|
|
2269
|
+
return readCodex(text.split("\n"), fromLine);
|
|
1828
2270
|
},
|
|
1829
|
-
facts(
|
|
2271
|
+
facts(session, transcript) {
|
|
1830
2272
|
const out = {};
|
|
1831
|
-
if (
|
|
1832
|
-
const { head, tail } = ends(transcript, 64 * 1024, 64 * 1024);
|
|
1833
|
-
const H = parseLines(head);
|
|
1834
|
-
const T = parseLines(tail);
|
|
1835
|
-
const first = H.find((j) => j.type === "user");
|
|
1836
|
-
const raw = first ? first.content ?? first.message ?? first.text : void 0;
|
|
1837
|
-
const text = typeof raw === "string" ? raw.trim() : "";
|
|
1838
|
-
if (text) {
|
|
1839
|
-
const line = text.split("\n").find((l) => l.trim()) ?? "";
|
|
1840
|
-
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
1841
|
-
out.title_source = "first-ask";
|
|
1842
|
-
}
|
|
1843
|
-
const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
|
|
1844
|
-
if (times.length) {
|
|
1845
|
-
out.started_at = Math.min(...times);
|
|
1846
|
-
out.last_at = Math.max(...times);
|
|
1847
|
-
}
|
|
2273
|
+
if (transcript) Object.assign(out, codexFacts(transcript));
|
|
1848
2274
|
return out;
|
|
1849
2275
|
},
|
|
1850
|
-
backfill
|
|
1851
|
-
|
|
2276
|
+
backfill(sinceMs, env = process.env) {
|
|
2277
|
+
const rows2 = [];
|
|
2278
|
+
for (const f of recentFiles(sessionsDir(env), 3, sinceMs, (n) => /^rollout-.*\.jsonl$/.test(n))) {
|
|
2279
|
+
const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(path9.basename(f));
|
|
2280
|
+
if (!m?.[1]) continue;
|
|
2281
|
+
if (codexSubagent(rolloutMeta(f))) continue;
|
|
2282
|
+
const facts = codexFacts(f);
|
|
2283
|
+
if (!facts.started_at || !facts.last_at) continue;
|
|
2284
|
+
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
2285
|
+
rows2.push({ session: { id: m[1], ...rest }, started_at, last_at });
|
|
2286
|
+
}
|
|
2287
|
+
return rows2;
|
|
2288
|
+
}
|
|
1852
2289
|
};
|
|
1853
|
-
function
|
|
2290
|
+
function prune3(o) {
|
|
1854
2291
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
1855
2292
|
return o;
|
|
1856
2293
|
}
|
|
2294
|
+
function codexFacts(file) {
|
|
2295
|
+
const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
|
|
2296
|
+
let H = parseLines(head);
|
|
2297
|
+
let T = parseLines(tail);
|
|
2298
|
+
const out = {};
|
|
2299
|
+
const meta = H.find((j) => j.type === "session_meta" && j.payload);
|
|
2300
|
+
const boundary = codexReplayBoundary(meta);
|
|
2301
|
+
if (meta?.payload) {
|
|
2302
|
+
const p = meta.payload;
|
|
2303
|
+
if (p.cwd) out.cwd = p.cwd;
|
|
2304
|
+
if (p.cli_version) out.harness_version = String(p.cli_version);
|
|
2305
|
+
if (p.git?.branch) out.branch = p.git.branch;
|
|
2306
|
+
if (p.git?.repository_url) out.repo = String(p.git.repository_url).replace(/\/\/[^/@]*@/, "//");
|
|
2307
|
+
}
|
|
2308
|
+
if (boundary) {
|
|
2309
|
+
const h = H.findIndex(boundary);
|
|
2310
|
+
const t = T.findIndex(boundary);
|
|
2311
|
+
H = h < 0 ? [] : H.slice(h);
|
|
2312
|
+
const found = h >= 0 || t >= 0 || scanLines(file, (line) => boundary(parseLines([line])[0] ?? null) ? true : null, Number.POSITIVE_INFINITY);
|
|
2313
|
+
T = t >= 0 ? T.slice(t) : found ? T : [];
|
|
2314
|
+
}
|
|
2315
|
+
const ctx = H.concat(T).reverse().find((j) => j.type === "turn_context" && j.payload?.model);
|
|
2316
|
+
if (ctx?.payload?.model) out.model = ctx.payload.model;
|
|
2317
|
+
for (const j of H) {
|
|
2318
|
+
const p = j.payload;
|
|
2319
|
+
if (j.type !== "response_item" || !p || p.type !== "message" || p.role !== "user") continue;
|
|
2320
|
+
const text = (p.content ?? []).filter((b) => b && b.type === "input_text" && typeof b.text === "string").map((b) => b.text).join("\n").trim();
|
|
2321
|
+
if (!text || codexInjected(text)) continue;
|
|
2322
|
+
const line = text.split("\n")[0];
|
|
2323
|
+
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
2324
|
+
out.title_source = "first-ask";
|
|
2325
|
+
break;
|
|
2326
|
+
}
|
|
2327
|
+
const times = (boundary && meta ? [meta, ...H, ...T] : H.concat(T)).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
|
|
2328
|
+
if (times.length) {
|
|
2329
|
+
out.started_at = Math.min(...times);
|
|
2330
|
+
out.last_at = Math.max(...times);
|
|
2331
|
+
}
|
|
2332
|
+
return out;
|
|
2333
|
+
}
|
|
1857
2334
|
|
|
1858
|
-
// src/adapters/
|
|
1859
|
-
var
|
|
1860
|
-
__export(
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
parseAuthStatus: () => parseAuthStatus,
|
|
1873
|
-
parseCaps: () => parseCaps,
|
|
1874
|
-
parseResult: () => parseResult,
|
|
1875
|
-
recentlyWritten: () => recentlyWritten,
|
|
1876
|
-
runClaude: () => runClaude,
|
|
1877
|
-
sessionFolder: () => sessionFolder
|
|
2335
|
+
// src/adapters/codex-control.ts
|
|
2336
|
+
var codex_control_exports = {};
|
|
2337
|
+
__export(codex_control_exports, {
|
|
2338
|
+
CodexReading: () => CodexReading,
|
|
2339
|
+
LOGIN_HINT: () => LOGIN_HINT,
|
|
2340
|
+
codexArgs: () => codexArgs,
|
|
2341
|
+
codexEnv: () => codexEnv,
|
|
2342
|
+
codexFacts: () => codexFacts,
|
|
2343
|
+
codexLine: () => codexLine,
|
|
2344
|
+
codexLogin: () => codexLogin,
|
|
2345
|
+
codexModeFlags: () => codexModeFlags,
|
|
2346
|
+
findCodex: () => findCodex,
|
|
2347
|
+
findRollout: () => findRollout,
|
|
2348
|
+
reading: () => reading2
|
|
1878
2349
|
});
|
|
1879
|
-
import { execFile
|
|
1880
|
-
import
|
|
1881
|
-
import
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
accessSync(p, constants.X_OK);
|
|
1891
|
-
return true;
|
|
1892
|
-
} catch {
|
|
1893
|
-
return false;
|
|
1894
|
-
}
|
|
2350
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
2351
|
+
import os4 from "node:os";
|
|
2352
|
+
import path10 from "node:path";
|
|
2353
|
+
var LABEL2 = "Codex";
|
|
2354
|
+
function findCodex(env = process.env, home = os4.homedir(), nodeDir = path10.dirname(process.execPath)) {
|
|
2355
|
+
return findBinary({
|
|
2356
|
+
override: env.SESSIONPIPE_CODEX,
|
|
2357
|
+
names: process.platform === "win32" ? ["codex.exe", "codex.cmd"] : ["codex"],
|
|
2358
|
+
env,
|
|
2359
|
+
dirs: [nodeDir, path10.join(home, ".local", "bin"), "/opt/homebrew/bin", "/usr/local/bin"]
|
|
2360
|
+
});
|
|
1895
2361
|
}
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
2362
|
+
function codexModeFlags(mode, resume = false) {
|
|
2363
|
+
if (resume)
|
|
2364
|
+
return mode === "auto" ? [
|
|
2365
|
+
"-c",
|
|
2366
|
+
'sandbox_mode="workspace-write"',
|
|
2367
|
+
"-c",
|
|
2368
|
+
'approval_policy="on-request"',
|
|
2369
|
+
"-c",
|
|
2370
|
+
'approvals_reviewer="auto_review"'
|
|
2371
|
+
] : ["-c", 'sandbox_mode="workspace-write"'];
|
|
2372
|
+
return mode === "auto" ? ["--approve-for-me"] : ["-s", "workspace-write"];
|
|
2373
|
+
}
|
|
2374
|
+
function codexArgs(o) {
|
|
2375
|
+
const common = ["--json", "--skip-git-repo-check"];
|
|
2376
|
+
if (o.resume) return ["exec", "resume", ...common, ...codexModeFlags(o.mode, true), "--", o.resume, o.prompt];
|
|
2377
|
+
return ["exec", ...common, ...o.cwd ? ["-C", o.cwd] : [], ...codexModeFlags(o.mode), "--", o.prompt];
|
|
2378
|
+
}
|
|
2379
|
+
var codexEnv = (env = process.env) => jobEnv(env);
|
|
2380
|
+
var LOGIN_HINT = "Codex isn't signed in on this machine: run `codex login --device-auth` there.";
|
|
2381
|
+
function codexLogin(bin, env = process.env) {
|
|
2382
|
+
if (env.CODEX_API_KEY) return Promise.resolve({ loggedIn: true, method: "api key" });
|
|
2383
|
+
return new Promise((resolve) => {
|
|
2384
|
+
execFile2(
|
|
2385
|
+
bin,
|
|
2386
|
+
["login", "status"],
|
|
2387
|
+
{ encoding: "utf8", timeout: 2e4, env: codexEnv(env), maxBuffer: 256 * 1024 },
|
|
2388
|
+
(err) => {
|
|
2389
|
+
if (!err) return resolve({ loggedIn: true });
|
|
2390
|
+
const code = err.code;
|
|
2391
|
+
resolve(code === 1 ? { loggedIn: false } : { loggedIn: null });
|
|
2392
|
+
}
|
|
2393
|
+
);
|
|
2394
|
+
});
|
|
2395
|
+
}
|
|
2396
|
+
function codexLine(line) {
|
|
2397
|
+
let j;
|
|
1919
2398
|
try {
|
|
1920
|
-
|
|
2399
|
+
j = JSON.parse(line);
|
|
1921
2400
|
} catch {
|
|
2401
|
+
return null;
|
|
1922
2402
|
}
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
2403
|
+
if (!j || typeof j !== "object") return null;
|
|
2404
|
+
const str = (v) => typeof v === "string" ? v : "";
|
|
2405
|
+
switch (j.type) {
|
|
2406
|
+
case "thread.started":
|
|
2407
|
+
return str(j.thread_id) ? { session: str(j.thread_id) } : null;
|
|
2408
|
+
case "item.completed": {
|
|
2409
|
+
const it = j.item;
|
|
2410
|
+
if (it?.type === "agent_message" && str(it.text)) return { message: str(it.text) };
|
|
2411
|
+
if (it?.type === "error" && str(it.message)) return { warning: str(it.message) };
|
|
2412
|
+
return null;
|
|
2413
|
+
}
|
|
2414
|
+
case "turn.completed":
|
|
2415
|
+
return { done: true };
|
|
2416
|
+
case "turn.failed":
|
|
2417
|
+
return { done: true, failed: str(j.error?.message) || "Codex reported an error" };
|
|
2418
|
+
case "error":
|
|
2419
|
+
return str(j.message) ? { note: str(j.message) } : null;
|
|
2420
|
+
default:
|
|
2421
|
+
return null;
|
|
1926
2422
|
}
|
|
1927
|
-
return null;
|
|
1928
|
-
}
|
|
1929
|
-
function optionBlock(help, flag) {
|
|
1930
|
-
const lines = help.split("\n");
|
|
1931
|
-
const re = new RegExp(`^\\s{0,4}(?:-\\w, )?${flag.replace(/-/g, "\\-")}(?![\\w-])`);
|
|
1932
|
-
const start = lines.findIndex((l) => re.test(l));
|
|
1933
|
-
if (start < 0) return null;
|
|
1934
|
-
const out = [lines[start]];
|
|
1935
|
-
for (let i = start + 1; i < lines.length && lines[i]?.trim() && !/^\s{0,4}-/.test(lines[i]); i++)
|
|
1936
|
-
out.push(lines[i]);
|
|
1937
|
-
return out.join(" ");
|
|
1938
|
-
}
|
|
1939
|
-
function parseCaps(help) {
|
|
1940
|
-
const pm = optionBlock(help, "--permission-mode");
|
|
1941
|
-
const pp = optionBlock(help, "--permission-prompts");
|
|
1942
|
-
return {
|
|
1943
|
-
hasMode: !!pm,
|
|
1944
|
-
modes: pm ? [...pm.matchAll(/"([A-Za-z]+)"/g)].map((m) => m[1]) : [],
|
|
1945
|
-
promptsNone: !!pp && /"none"/.test(pp),
|
|
1946
|
-
fork: !!optionBlock(help, "--fork-session"),
|
|
1947
|
-
stream: !!optionBlock(help, "--include-partial-messages")
|
|
1948
|
-
};
|
|
1949
2423
|
}
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
2424
|
+
var CodexReading = class {
|
|
2425
|
+
session = null;
|
|
2426
|
+
lines = 0;
|
|
2427
|
+
warnings = [];
|
|
2428
|
+
messages = [];
|
|
2429
|
+
done = false;
|
|
2430
|
+
failed = null;
|
|
2431
|
+
note = null;
|
|
2432
|
+
get text() {
|
|
2433
|
+
return this.messages.join("\n\n");
|
|
2434
|
+
}
|
|
2435
|
+
feed(line) {
|
|
2436
|
+
this.lines++;
|
|
2437
|
+
const p = codexLine(line);
|
|
2438
|
+
if (!p) return false;
|
|
2439
|
+
if (p.session && !this.session) this.session = p.session;
|
|
2440
|
+
if (p.note) this.note = p.note;
|
|
2441
|
+
if (p.warning && this.warnings.length < 20) this.warnings.push(p.warning);
|
|
2442
|
+
if (p.failed) this.failed = p.failed;
|
|
2443
|
+
if (p.done) this.done = true;
|
|
2444
|
+
if (p.message?.trim()) {
|
|
2445
|
+
this.messages.push(p.message.trim());
|
|
2446
|
+
return true;
|
|
2447
|
+
}
|
|
2448
|
+
return false;
|
|
1955
2449
|
}
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
)
|
|
1963
|
-
|
|
1964
|
-
|
|
2450
|
+
/** A failure is `turn.failed`, or a run that exits non-zero having said nothing; an
|
|
2451
|
+
* `error` item before a turn that then succeeds is only a warning. */
|
|
2452
|
+
outcome(res) {
|
|
2453
|
+
const broke = runFailure(LABEL2, res);
|
|
2454
|
+
if (broke) return { failed: broke };
|
|
2455
|
+
if (this.failed) return { failed: this.failed };
|
|
2456
|
+
if (this.done || res.code === 0 || this.messages.length)
|
|
2457
|
+
return { reply: this.text || "(Codex finished without a written reply.)" };
|
|
2458
|
+
const why = this.note || codexStderr(res.stderr) || this.warnings.at(-1) || "";
|
|
2459
|
+
return { failed: `Codex exited (${res.signal || `code ${res.code}`}) without an answer${why ? `: ${why}` : ""}` };
|
|
2460
|
+
}
|
|
2461
|
+
};
|
|
2462
|
+
function codexStderr(stderr) {
|
|
2463
|
+
return stderrTail(
|
|
2464
|
+
String(stderr || "").split("\n").filter((l) => !/^Reading additional input from stdin/.test(l.trim())).join("\n")
|
|
2465
|
+
);
|
|
1965
2466
|
}
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
2467
|
+
var reading2 = () => new CodexReading();
|
|
2468
|
+
|
|
2469
|
+
// src/adapters/devin.ts
|
|
2470
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
2471
|
+
import { existsSync as existsSync6, readlinkSync } from "node:fs";
|
|
2472
|
+
import path11 from "node:path";
|
|
2473
|
+
|
|
2474
|
+
// src/readers/sqlite.ts
|
|
2475
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
2476
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
2477
|
+
import { createRequire } from "node:module";
|
|
2478
|
+
function quiet(fn) {
|
|
2479
|
+
const original = process.emitWarning;
|
|
2480
|
+
try {
|
|
2481
|
+
process.emitWarning = ((warning, ...rest) => {
|
|
2482
|
+
const kind = typeof rest[0] === "string" ? rest[0] : rest[0]?.type;
|
|
2483
|
+
if (kind === "ExperimentalWarning" && /sqlite/i.test(String(warning))) return;
|
|
2484
|
+
original.call(process, warning, ...rest);
|
|
2485
|
+
});
|
|
2486
|
+
return fn();
|
|
2487
|
+
} finally {
|
|
2488
|
+
process.emitWarning = original;
|
|
1981
2489
|
}
|
|
1982
|
-
return e;
|
|
1983
2490
|
}
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
2491
|
+
var ctorCache;
|
|
2492
|
+
function dbCtor() {
|
|
2493
|
+
if (ctorCache !== void 0) return ctorCache;
|
|
2494
|
+
ctorCache = null;
|
|
1988
2495
|
try {
|
|
1989
|
-
const
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
loggedIn: j.loggedIn,
|
|
1993
|
-
...typeof j.authMethod === "string" ? { method: j.authMethod } : {},
|
|
1994
|
-
...typeof j.subscriptionType === "string" ? { subscription: j.subscriptionType } : {}
|
|
1995
|
-
};
|
|
2496
|
+
const req = createRequire(import.meta.url);
|
|
2497
|
+
const m = quiet(() => req("node:sqlite"));
|
|
2498
|
+
if (typeof m?.DatabaseSync === "function") ctorCache = m.DatabaseSync;
|
|
1996
2499
|
} catch {
|
|
1997
|
-
return { loggedIn: null };
|
|
1998
2500
|
}
|
|
2501
|
+
return ctorCache;
|
|
1999
2502
|
}
|
|
2000
|
-
function
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
});
|
|
2010
|
-
}
|
|
2011
|
-
function loginCommand(configDir) {
|
|
2012
|
-
const home = os2.homedir();
|
|
2013
|
-
const isDefault = !configDir || path8.resolve(configDir) === path8.resolve(home, ".claude");
|
|
2014
|
-
return isDefault ? "`claude auth login`" : `\`CLAUDE_CONFIG_DIR=${configDir.replace(home, "~")} claude auth login\``;
|
|
2015
|
-
}
|
|
2016
|
-
function loginHint(configDir, platform = process.platform) {
|
|
2017
|
-
const shown = configDir ? configDir.replace(os2.homedir(), "~") : "~/.claude";
|
|
2018
|
-
const login = loginCommand(configDir);
|
|
2019
|
-
if (platform === "darwin")
|
|
2020
|
-
return `Claude Code (${shown}) isn't signed in as this machine's background service sees it. On a Mac its login lives in the login keychain, which a background service can read only while someone is logged in at the screen: log in at the Mac, or run ${login} there.`;
|
|
2021
|
-
return `Claude Code (${shown}) isn't signed in on this machine: run ${login} there.`;
|
|
2503
|
+
function plain(row) {
|
|
2504
|
+
if (!row || typeof row !== "object") return null;
|
|
2505
|
+
const out = {};
|
|
2506
|
+
for (const [k, v] of Object.entries(row)) {
|
|
2507
|
+
if (typeof v === "bigint") out[k] = Number(v);
|
|
2508
|
+
else if (v instanceof Uint8Array) out[k] = null;
|
|
2509
|
+
else out[k] = v;
|
|
2510
|
+
}
|
|
2511
|
+
return out;
|
|
2022
2512
|
}
|
|
2023
|
-
|
|
2024
|
-
|
|
2513
|
+
var rows = (v) => {
|
|
2514
|
+
if (!Array.isArray(v)) return null;
|
|
2515
|
+
const out = [];
|
|
2516
|
+
for (const r of v) {
|
|
2517
|
+
const p = plain(r);
|
|
2518
|
+
if (p) out.push(p);
|
|
2519
|
+
}
|
|
2520
|
+
return out;
|
|
2521
|
+
};
|
|
2522
|
+
function viaNode(file, sql, params, o) {
|
|
2523
|
+
const C = dbCtor();
|
|
2524
|
+
if (!C) return null;
|
|
2525
|
+
let db;
|
|
2025
2526
|
try {
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
env: claudeEnv(),
|
|
2030
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
2031
|
-
maxBuffer: 4 * 1024 * 1024
|
|
2032
|
-
});
|
|
2033
|
-
} catch (e) {
|
|
2034
|
-
help = String(e?.stdout || "");
|
|
2527
|
+
db = quiet(() => new C(file, { readOnly: true, timeout: o.timeoutMs }));
|
|
2528
|
+
} catch {
|
|
2529
|
+
return null;
|
|
2035
2530
|
}
|
|
2036
|
-
|
|
2037
|
-
}
|
|
2038
|
-
function findTranscript(session, dirs) {
|
|
2039
|
-
if (!UUID_RE.test(session)) return null;
|
|
2040
|
-
for (const dir of dirs) {
|
|
2041
|
-
let projects = [];
|
|
2531
|
+
try {
|
|
2042
2532
|
try {
|
|
2043
|
-
|
|
2533
|
+
db.exec(`PRAGMA busy_timeout = ${Math.round(o.timeoutMs)}`);
|
|
2044
2534
|
} catch {
|
|
2045
|
-
continue;
|
|
2046
|
-
}
|
|
2047
|
-
for (const p of projects) {
|
|
2048
|
-
if (!p.isDirectory()) continue;
|
|
2049
|
-
const file = path8.join(dir, "projects", p.name, `${session}.jsonl`);
|
|
2050
|
-
try {
|
|
2051
|
-
if (statSync4(file).isFile()) return { file, configDir: dir, project: p.name };
|
|
2052
|
-
} catch {
|
|
2053
|
-
}
|
|
2054
2535
|
}
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
function sessionFolder(t, cap2 = 8 * 1024 * 1024) {
|
|
2060
|
-
let first = null;
|
|
2061
|
-
let count = 0;
|
|
2062
|
-
let fd;
|
|
2063
|
-
const look = (line) => {
|
|
2064
|
-
if (line.indexOf('"cwd"') < 0) return null;
|
|
2065
|
-
let j;
|
|
2536
|
+
return quiet(() => rows(db.prepare(sql).all(...params)));
|
|
2537
|
+
} catch {
|
|
2538
|
+
return null;
|
|
2539
|
+
} finally {
|
|
2066
2540
|
try {
|
|
2067
|
-
|
|
2541
|
+
db.close();
|
|
2068
2542
|
} catch {
|
|
2069
|
-
return null;
|
|
2070
2543
|
}
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
var CHILD = `const { DatabaseSync } = require("node:sqlite");
|
|
2547
|
+
const [, file, sql, params, ms] = process.argv;
|
|
2548
|
+
const db = new DatabaseSync(file, { readOnly: true, timeout: Number(ms) });
|
|
2549
|
+
try { db.exec("PRAGMA busy_timeout = " + Number(ms)); } catch {}
|
|
2550
|
+
const out = db.prepare(sql).all(...JSON.parse(params));
|
|
2551
|
+
db.close();
|
|
2552
|
+
process.stdout.write(JSON.stringify(out));`;
|
|
2553
|
+
function viaChild(file, sql, params, o) {
|
|
2077
2554
|
try {
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
const hit = look(rest);
|
|
2088
|
-
if (hit) return hit;
|
|
2089
|
-
}
|
|
2090
|
-
break;
|
|
2091
|
-
}
|
|
2092
|
-
pos += n;
|
|
2093
|
-
let chunk = rest.length ? Buffer.concat([rest, buf.subarray(0, n)]) : buf.subarray(0, n);
|
|
2094
|
-
let nl = chunk.indexOf(10);
|
|
2095
|
-
while (nl >= 0) {
|
|
2096
|
-
const line = chunk.subarray(0, nl);
|
|
2097
|
-
chunk = chunk.subarray(nl + 1);
|
|
2098
|
-
if (skipping) skipping = false;
|
|
2099
|
-
else {
|
|
2100
|
-
const hit = look(line);
|
|
2101
|
-
if (hit) return hit;
|
|
2102
|
-
}
|
|
2103
|
-
nl = chunk.indexOf(10);
|
|
2555
|
+
const out = execFileSync4(
|
|
2556
|
+
process.execPath,
|
|
2557
|
+
["--experimental-sqlite", "--no-warnings", "-e", CHILD, file, sql, JSON.stringify(params), String(o.timeoutMs)],
|
|
2558
|
+
{
|
|
2559
|
+
encoding: "utf8",
|
|
2560
|
+
timeout: o.timeoutMs + 3e3,
|
|
2561
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2562
|
+
maxBuffer: o.maxBuffer,
|
|
2563
|
+
windowsHide: true
|
|
2104
2564
|
}
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
rest = Buffer.alloc(0);
|
|
2108
|
-
} else rest = skipping ? Buffer.alloc(0) : Buffer.from(chunk);
|
|
2109
|
-
}
|
|
2565
|
+
);
|
|
2566
|
+
return rows(JSON.parse(out || "null"));
|
|
2110
2567
|
} catch {
|
|
2111
|
-
|
|
2112
|
-
if (fd !== void 0)
|
|
2113
|
-
try {
|
|
2114
|
-
closeSync2(fd);
|
|
2115
|
-
} catch {
|
|
2116
|
-
}
|
|
2568
|
+
return null;
|
|
2117
2569
|
}
|
|
2118
|
-
return first;
|
|
2119
2570
|
}
|
|
2120
|
-
|
|
2121
|
-
|
|
2571
|
+
var SAFE = /^[A-Za-z0-9 ._:@/+-]{0,256}$/;
|
|
2572
|
+
function inlineParams(sql, params) {
|
|
2573
|
+
if (sql.includes("'")) return null;
|
|
2574
|
+
const holes = sql.split("?").length - 1;
|
|
2575
|
+
if (holes !== params.length) return null;
|
|
2576
|
+
let i = 0;
|
|
2577
|
+
let bad = false;
|
|
2578
|
+
const out = sql.replace(/\?/g, () => {
|
|
2579
|
+
const p = params[i++];
|
|
2580
|
+
if (typeof p === "number") {
|
|
2581
|
+
if (!Number.isFinite(p)) {
|
|
2582
|
+
bad = true;
|
|
2583
|
+
return "NULL";
|
|
2584
|
+
}
|
|
2585
|
+
return String(p);
|
|
2586
|
+
}
|
|
2587
|
+
if (typeof p !== "string" || !SAFE.test(p)) {
|
|
2588
|
+
bad = true;
|
|
2589
|
+
return "NULL";
|
|
2590
|
+
}
|
|
2591
|
+
return `'${p}'`;
|
|
2592
|
+
});
|
|
2593
|
+
return bad ? null : out;
|
|
2594
|
+
}
|
|
2595
|
+
function viaCli(file, sql, params, o) {
|
|
2596
|
+
const text = inlineParams(sql, params);
|
|
2597
|
+
if (!text) return null;
|
|
2122
2598
|
try {
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2599
|
+
const out = execFileSync4("sqlite3", ["-readonly", "-json", file, text], {
|
|
2600
|
+
encoding: "utf8",
|
|
2601
|
+
timeout: o.timeoutMs + 3e3,
|
|
2602
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2603
|
+
maxBuffer: o.maxBuffer,
|
|
2604
|
+
windowsHide: true
|
|
2605
|
+
}).trim();
|
|
2606
|
+
if (!out) return [];
|
|
2607
|
+
return rows(JSON.parse(out));
|
|
2608
|
+
} catch {
|
|
2609
|
+
return null;
|
|
2127
2610
|
}
|
|
2128
2611
|
}
|
|
2129
|
-
function
|
|
2130
|
-
|
|
2612
|
+
function querySqlite(file, sql, params = [], opts = {}) {
|
|
2613
|
+
const o = {
|
|
2614
|
+
timeoutMs: opts.timeoutMs ?? 2e3,
|
|
2615
|
+
engine: opts.engine ?? "auto",
|
|
2616
|
+
maxBuffer: opts.maxBuffer ?? 32 * 1024 * 1024
|
|
2617
|
+
};
|
|
2618
|
+
if (!file) return null;
|
|
2131
2619
|
try {
|
|
2132
|
-
|
|
2620
|
+
if (!existsSync5(file)) return null;
|
|
2133
2621
|
} catch {
|
|
2134
|
-
return
|
|
2622
|
+
return null;
|
|
2135
2623
|
}
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
if (pidAlive(Number(j.pid ?? n.slice(0, -5)))) return true;
|
|
2141
|
-
} catch {
|
|
2142
|
-
}
|
|
2624
|
+
const ways = o.engine === "auto" ? [viaNode, viaChild, viaCli] : o.engine === "node" ? [viaNode] : o.engine === "child" ? [viaChild] : [viaCli];
|
|
2625
|
+
for (const way of ways) {
|
|
2626
|
+
const r = way(file, sql, params, o);
|
|
2627
|
+
if (r) return r;
|
|
2143
2628
|
}
|
|
2144
|
-
return
|
|
2629
|
+
return null;
|
|
2145
2630
|
}
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2631
|
+
|
|
2632
|
+
// src/adapters/devin.ts
|
|
2633
|
+
var NAME4 = "devin";
|
|
2634
|
+
var DEVIN_EVENTS = [
|
|
2635
|
+
"SessionStart",
|
|
2636
|
+
"UserPromptSubmit",
|
|
2637
|
+
"PreToolUse",
|
|
2638
|
+
"PostToolUse",
|
|
2639
|
+
"PermissionRequest",
|
|
2640
|
+
"Stop",
|
|
2641
|
+
"PostCompaction",
|
|
2642
|
+
"SessionEnd"
|
|
2643
|
+
];
|
|
2644
|
+
var OPTS3 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
2645
|
+
var eventsFor3 = (lean) => lean ? DEVIN_EVENTS.filter((e) => e !== "PreToolUse") : [...DEVIN_EVENTS];
|
|
2646
|
+
var INSTALL_NOTE = "Devin drops a whole hooks file on one event name it doesn't know; `/hooks` in `devin` lists what loaded";
|
|
2647
|
+
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";
|
|
2648
|
+
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)`;
|
|
2649
|
+
function foreignEvent(file) {
|
|
2650
|
+
const hooks = readJson(file).hooks;
|
|
2651
|
+
if (!hooks || typeof hooks !== "object" || Array.isArray(hooks)) return void 0;
|
|
2652
|
+
const ok = DEVIN_EVENTS;
|
|
2653
|
+
return Object.keys(hooks).find((k) => !ok.includes(k));
|
|
2654
|
+
}
|
|
2655
|
+
var homeOf = (env) => env.HOME || homeDir();
|
|
2656
|
+
var appData = (env) => env.APPDATA || path11.join(homeOf(env), "AppData", "Roaming");
|
|
2657
|
+
var devinConfigDir = (env = process.env) => process.platform === "win32" ? path11.join(appData(env), "devin") : path11.join(env.XDG_CONFIG_HOME || path11.join(homeOf(env), ".config"), "devin");
|
|
2658
|
+
var configFile3 = (env) => path11.join(devinConfigDir(env), "config.json");
|
|
2659
|
+
var devinDataDir = (env = process.env) => process.platform === "win32" ? path11.join(appData(env), "devin", "cli") : path11.join(env.XDG_DATA_HOME || path11.join(homeOf(env), ".local", "share"), "devin", "cli");
|
|
2660
|
+
var sessionsDb = (env) => path11.join(devinDataDir(env), "sessions.db");
|
|
2661
|
+
var SESSION_ID = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
2662
|
+
var validTool4 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
2663
|
+
var endReason3 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
|
|
2664
|
+
var pickSource2 = (v) => typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
|
|
2665
|
+
function errText2(v) {
|
|
2666
|
+
if (typeof v === "string" && v) return v.slice(0, 2e3);
|
|
2667
|
+
if (v && typeof v === "object") {
|
|
2668
|
+
const e = v;
|
|
2669
|
+
if (typeof e.message === "string" && e.message) return e.message.slice(0, 2e3);
|
|
2670
|
+
if (typeof e.error === "string" && e.error) return e.error.slice(0, 2e3);
|
|
2152
2671
|
}
|
|
2153
|
-
|
|
2154
|
-
return !(ourLastEnd && m <= ourLastEnd + 2e3);
|
|
2672
|
+
return void 0;
|
|
2155
2673
|
}
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2674
|
+
var cut = (s, n) => s.length > n ? `${s.slice(0, n).replace(/\s+\S*$/, "")}\u2026` : s;
|
|
2675
|
+
var devin = {
|
|
2676
|
+
name: NAME4,
|
|
2677
|
+
events: DEVIN_EVENTS,
|
|
2678
|
+
detect: (env = process.env) => existsSync6(devinConfigDir(env)) || existsSync6(devinDataDir(env)),
|
|
2679
|
+
configFiles: (env = process.env) => [configFile3(env)],
|
|
2680
|
+
install: (cmd, env = process.env, opts = {}) => {
|
|
2681
|
+
const file = configFile3(env);
|
|
2682
|
+
const r = installClaudeShaped(file, NAME4, eventsFor3(opts.lean), cmd, OPTS3);
|
|
2683
|
+
if (r.skipped) return [r];
|
|
2684
|
+
const bad = foreignEvent(file);
|
|
2685
|
+
return [{ ...r, note: bad ? deadFileNote(bad) : INSTALL_NOTE }];
|
|
2686
|
+
},
|
|
2687
|
+
uninstall: (env = process.env, opts = {}) => [
|
|
2688
|
+
uninstallClaudeShaped(configFile3(env), DEVIN_EVENTS, { created: opts.created?.includes(configFile3(env)) })
|
|
2689
|
+
],
|
|
2690
|
+
installed: (cmd, env = process.env, opts = {}) => {
|
|
2691
|
+
const file = configFile3(env);
|
|
2692
|
+
const state = installedClaudeShaped(file, NAME4, eventsFor3(opts.lean), cmd, OPTS3);
|
|
2693
|
+
const bad = state === "missing" ? void 0 : foreignEvent(file);
|
|
2694
|
+
return [{ file, state: bad ? "inactive" : state, note: bad ? deadFileNote(bad) : CLAUDE_NOTE }];
|
|
2695
|
+
},
|
|
2696
|
+
fromHook(input) {
|
|
2697
|
+
const event = input.argv[1] ?? "";
|
|
2698
|
+
let s = {};
|
|
2165
2699
|
try {
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
2170
|
-
detached: process.platform !== "win32",
|
|
2171
|
-
windowsHide: true
|
|
2172
|
-
});
|
|
2173
|
-
} catch (e) {
|
|
2174
|
-
resolve({ error: e.message });
|
|
2175
|
-
return;
|
|
2700
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
2701
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
2702
|
+
} catch {
|
|
2176
2703
|
}
|
|
2177
|
-
const
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
}
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2704
|
+
const id = typeof s.session_id === "string" ? s.session_id : "";
|
|
2705
|
+
if (!id) return null;
|
|
2706
|
+
const session = { id, cwd: input.env.DEVIN_PROJECT_DIR ?? input.cwd };
|
|
2707
|
+
const turn_id = typeof s.prompt_id === "string" ? s.prompt_id : void 0;
|
|
2708
|
+
const tool = validTool4(s.tool_name) ? s.tool_name : void 0;
|
|
2709
|
+
const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
|
|
2710
|
+
const response = s.tool_response && typeof s.tool_response === "object" && !Array.isArray(s.tool_response) ? s.tool_response : void 0;
|
|
2711
|
+
const permissionId = () => `perm-${createHash2("sha256").update(`${turn_id ?? ""}|${tool ?? ""}|${JSON.stringify(s.tool_input ?? null)}`).digest("hex").slice(0, 16)}`;
|
|
2712
|
+
const ev = (type, data) => ({ type, data: prune4(data), harnessEvent: event });
|
|
2713
|
+
const events = [];
|
|
2714
|
+
switch (event) {
|
|
2715
|
+
case "SessionStart":
|
|
2716
|
+
events.push(ev("session.started", { source: pickSource2(s.source) }));
|
|
2717
|
+
break;
|
|
2718
|
+
case "UserPromptSubmit":
|
|
2719
|
+
events.push(
|
|
2720
|
+
ev("turn.started", {
|
|
2721
|
+
turn_id,
|
|
2722
|
+
prompt_chars: typeof s.prompt === "string" ? [...s.prompt].length : void 0
|
|
2723
|
+
})
|
|
2724
|
+
);
|
|
2725
|
+
break;
|
|
2726
|
+
case "PreToolUse":
|
|
2727
|
+
if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, input: s.tool_input }));
|
|
2728
|
+
break;
|
|
2729
|
+
case "PostToolUse":
|
|
2730
|
+
if (tool)
|
|
2731
|
+
events.push(
|
|
2732
|
+
ev("tool.ended", {
|
|
2733
|
+
tool,
|
|
2734
|
+
call_id,
|
|
2735
|
+
turn_id,
|
|
2736
|
+
// `ok` is required on tool.ended (PROTOCOL.md §events), so a response
|
|
2737
|
+
// that carries no success flag at all reads as "nothing said it failed".
|
|
2738
|
+
ok: response?.success !== false,
|
|
2739
|
+
error: errText2(response?.error),
|
|
2740
|
+
input: s.tool_input,
|
|
2741
|
+
output: response ? response.output : s.tool_response
|
|
2742
|
+
})
|
|
2743
|
+
);
|
|
2744
|
+
break;
|
|
2745
|
+
case "PermissionRequest":
|
|
2746
|
+
events.push(ev("attention.needed", { attention_id: call_id ?? permissionId(), kind: "permission", tool }));
|
|
2747
|
+
break;
|
|
2748
|
+
case "Stop":
|
|
2749
|
+
events.push(ev("turn.ended", { turn_id, reason: "stop" }));
|
|
2750
|
+
break;
|
|
2751
|
+
case "PostCompaction":
|
|
2752
|
+
events.push(ev("context.compacted", { trigger: "auto" }));
|
|
2753
|
+
break;
|
|
2754
|
+
case "SessionEnd":
|
|
2755
|
+
events.push(ev("session.ended", { reason: endReason3(s.reason) }));
|
|
2756
|
+
break;
|
|
2757
|
+
default:
|
|
2758
|
+
return null;
|
|
2759
|
+
}
|
|
2760
|
+
return {
|
|
2761
|
+
session,
|
|
2762
|
+
events,
|
|
2763
|
+
hints: {},
|
|
2764
|
+
transcript: transcriptRef(sessionsDb(input.env), id)
|
|
2238
2765
|
};
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2766
|
+
},
|
|
2767
|
+
readTranscript(file, fromLine) {
|
|
2768
|
+
const ref = splitRef(file);
|
|
2769
|
+
if (!ref) return { turns: [], line: fromLine };
|
|
2770
|
+
return readDevinChain(ref.db, ref.session, fromLine);
|
|
2771
|
+
},
|
|
2772
|
+
facts(session, transcript, _hints, _state, env = process.env) {
|
|
2773
|
+
const out = {};
|
|
2774
|
+
const version = devinVersion(env);
|
|
2775
|
+
if (version) out.harness_version = version;
|
|
2776
|
+
const account = devinAccount(env);
|
|
2777
|
+
if (account) out.account_id = account;
|
|
2778
|
+
const ref = splitRef(transcript) ?? { db: sessionsDb(env), session: session.id };
|
|
2779
|
+
if (!SESSION_ID.test(ref.session)) return out;
|
|
2780
|
+
const rows2 = querySqlite(ref.db, SESSION_SQL, [ref.session]);
|
|
2781
|
+
const row = rows2?.[0];
|
|
2782
|
+
if (!row) return out;
|
|
2783
|
+
Object.assign(out, sessionFacts(row));
|
|
2784
|
+
return out;
|
|
2785
|
+
},
|
|
2786
|
+
backfill(sinceMs, env = process.env) {
|
|
2787
|
+
const rows2 = querySqlite(sessionsDb(env), BACKFILL_SQL, ["text", Math.floor(sinceMs / 1e3)]);
|
|
2788
|
+
if (!rows2) return [];
|
|
2789
|
+
const out = [];
|
|
2790
|
+
for (const row of rows2) {
|
|
2791
|
+
const id = String(row.id);
|
|
2792
|
+
if (!SESSION_ID.test(id)) continue;
|
|
2793
|
+
const { started_at, last_at, ...rest } = sessionFacts(row);
|
|
2794
|
+
if (!started_at || !last_at) continue;
|
|
2795
|
+
out.push({ session: { id, ...rest }, started_at, last_at });
|
|
2796
|
+
}
|
|
2797
|
+
return out;
|
|
2798
|
+
}
|
|
2799
|
+
};
|
|
2800
|
+
function prune4(o) {
|
|
2801
|
+
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
2802
|
+
return o;
|
|
2256
2803
|
}
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
if (!
|
|
2260
|
-
const
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2804
|
+
var transcriptRef = (db, session) => `${db}#${session}`;
|
|
2805
|
+
function splitRef(ref) {
|
|
2806
|
+
if (!ref) return null;
|
|
2807
|
+
const at = ref.lastIndexOf("#");
|
|
2808
|
+
if (at <= 0) return null;
|
|
2809
|
+
const db = ref.slice(0, at);
|
|
2810
|
+
const session = ref.slice(at + 1);
|
|
2811
|
+
return db && SESSION_ID.test(session) ? { db, session } : null;
|
|
2812
|
+
}
|
|
2813
|
+
var CHAIN_LIMIT = 4e3;
|
|
2814
|
+
var CHAIN_SQL = `WITH RECURSIVE chain(node_id, parent_node_id, chat_message, created_at) AS (
|
|
2815
|
+
SELECT node_id, parent_node_id, chat_message, created_at FROM message_nodes
|
|
2816
|
+
WHERE session_id = ? AND node_id = (SELECT main_chain_id FROM sessions WHERE id = ?)
|
|
2817
|
+
UNION ALL
|
|
2818
|
+
SELECT m.node_id, m.parent_node_id, m.chat_message, m.created_at FROM message_nodes m
|
|
2819
|
+
JOIN chain c ON m.node_id = c.parent_node_id AND m.session_id = ? AND m.node_id >= ?
|
|
2820
|
+
)
|
|
2821
|
+
SELECT node_id, chat_message, created_at FROM chain ORDER BY node_id DESC LIMIT ${CHAIN_LIMIT}`;
|
|
2822
|
+
var SESSION_FIELDS = `s.model, s.working_directory, s.created_at, s.last_activity_at,
|
|
2823
|
+
(SELECT p.content FROM prompt_history p WHERE p.session_id = s.id AND p.is_shell = 0
|
|
2824
|
+
ORDER BY p.timestamp ASC, p.id ASC LIMIT 1) AS first_ask`;
|
|
2825
|
+
var SESSION_SQL = `SELECT ${SESSION_FIELDS} FROM sessions s WHERE s.id = ? LIMIT 1`;
|
|
2826
|
+
var BACKFILL_SQL = `SELECT s.id, ${SESSION_FIELDS} FROM sessions s
|
|
2827
|
+
WHERE s.hidden = 0 AND typeof(s.id) = ? AND s.last_activity_at >= ?
|
|
2828
|
+
ORDER BY s.last_activity_at DESC LIMIT 500`;
|
|
2829
|
+
function sessionFacts(row) {
|
|
2830
|
+
const out = {};
|
|
2831
|
+
if (typeof row.model === "string" && row.model) out.model = row.model;
|
|
2832
|
+
if (typeof row.working_directory === "string" && path11.isAbsolute(row.working_directory))
|
|
2833
|
+
out.cwd = row.working_directory;
|
|
2834
|
+
const started = Number(row.created_at);
|
|
2835
|
+
const last = Number(row.last_activity_at);
|
|
2836
|
+
if (Number.isFinite(started) && started > 0) out.started_at = Math.round(started * 1e3);
|
|
2837
|
+
if (Number.isFinite(last) && last > 0) out.last_at = Math.round(last * 1e3);
|
|
2838
|
+
if (typeof row.first_ask === "string") {
|
|
2839
|
+
const line = row.first_ask.split("\n").find((l) => l.trim()) ?? "";
|
|
2840
|
+
if (line.trim()) {
|
|
2841
|
+
out.title = cut(line.trim(), 80);
|
|
2842
|
+
out.title_source = "first-ask";
|
|
2264
2843
|
}
|
|
2265
|
-
|
|
2266
|
-
|
|
2844
|
+
}
|
|
2845
|
+
return out;
|
|
2846
|
+
}
|
|
2847
|
+
function readDevinChain(db, session, fromLine) {
|
|
2848
|
+
if (!SESSION_ID.test(session)) return { turns: [], line: fromLine };
|
|
2849
|
+
const floor = Number.isFinite(fromLine) && fromLine > 0 ? Math.floor(fromLine) : 0;
|
|
2850
|
+
const rows2 = querySqlite(db, CHAIN_SQL, [session, session, session, floor]);
|
|
2851
|
+
if (!rows2?.length) return { turns: [], line: fromLine };
|
|
2852
|
+
const msgs = [];
|
|
2853
|
+
let end = fromLine;
|
|
2854
|
+
for (const row of rows2) {
|
|
2855
|
+
const node = Number(row.node_id);
|
|
2856
|
+
if (!Number.isFinite(node)) continue;
|
|
2857
|
+
if (node + 1 > end) end = node + 1;
|
|
2858
|
+
if (node < fromLine) continue;
|
|
2859
|
+
let j;
|
|
2860
|
+
try {
|
|
2861
|
+
j = JSON.parse(String(row.chat_message ?? ""));
|
|
2862
|
+
} catch {
|
|
2863
|
+
continue;
|
|
2864
|
+
}
|
|
2865
|
+
const text = typeof j.content === "string" ? j.content.trim() : "";
|
|
2866
|
+
if (!text) continue;
|
|
2867
|
+
const secs = Number(row.created_at);
|
|
2868
|
+
const at = Date.parse(String(j.metadata?.created_at ?? "")) || (Number.isFinite(secs) && secs > 0 ? Math.round(secs * 1e3) : 0) || Date.now();
|
|
2869
|
+
if (j.role === "user" && j.metadata?.is_user_input === true) msgs.push({ role: "user", text, at, line: node });
|
|
2870
|
+
else if (j.role === "assistant") msgs.push({ role: "assistant", text, at, line: node });
|
|
2871
|
+
}
|
|
2872
|
+
msgs.sort((a, b) => a.line - b.line);
|
|
2873
|
+
return pairTurns(msgs, end);
|
|
2874
|
+
}
|
|
2875
|
+
function devinVersion(env = process.env) {
|
|
2267
2876
|
try {
|
|
2268
|
-
|
|
2877
|
+
const target = readlinkSync(path11.join(devinDataDir(env), "_versions", "current"));
|
|
2878
|
+
const name = path11.basename(target);
|
|
2879
|
+
return /^[0-9][0-9A-Za-z.+-]{0,63}$/.test(name) ? name : void 0;
|
|
2269
2880
|
} catch {
|
|
2881
|
+
return void 0;
|
|
2270
2882
|
}
|
|
2271
|
-
|
|
2272
|
-
|
|
2883
|
+
}
|
|
2884
|
+
function devinAccount(env = process.env) {
|
|
2885
|
+
const d = readJson(configFile3(env)).devin;
|
|
2886
|
+
const id = d && typeof d === "object" ? d.org_id : void 0;
|
|
2887
|
+
return typeof id === "string" && /^[A-Za-z0-9_.:-]{1,128}$/.test(id) ? id : void 0;
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
// src/adapters/gemini-cli.ts
|
|
2891
|
+
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
2892
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
|
|
2893
|
+
import path12 from "node:path";
|
|
2894
|
+
var NAME5 = "gemini-cli";
|
|
2895
|
+
var GEMINI_EVENTS = [
|
|
2896
|
+
"SessionStart",
|
|
2897
|
+
"BeforeAgent",
|
|
2898
|
+
"BeforeTool",
|
|
2899
|
+
"AfterTool",
|
|
2900
|
+
"Notification",
|
|
2901
|
+
"AfterAgent",
|
|
2902
|
+
"PreCompress",
|
|
2903
|
+
"SessionEnd"
|
|
2904
|
+
];
|
|
2905
|
+
var OPTS4 = { matcherFor: (e) => /Tool/.test(e) ? ".*" : void 0, timeout: 5e3 };
|
|
2906
|
+
var geminiDir = (env) => env.GEMINI_CLI_HOME || path12.join(homeDir(), ".gemini");
|
|
2907
|
+
var settings = (env) => path12.join(geminiDir(env), "settings.json");
|
|
2908
|
+
var validTool5 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
2909
|
+
var eventsFor4 = (lean) => lean ? GEMINI_EVENTS.filter((e) => e !== "BeforeTool") : [...GEMINI_EVENTS];
|
|
2910
|
+
function geminiHooksActive(settingsFile) {
|
|
2911
|
+
let tools;
|
|
2912
|
+
try {
|
|
2913
|
+
tools = JSON.parse(readFileSync6(settingsFile, "utf8")).tools;
|
|
2914
|
+
} catch {
|
|
2915
|
+
}
|
|
2916
|
+
if (tools?.enableHooks === true) return { active: true };
|
|
2917
|
+
if (tools?.enableHooks === false)
|
|
2918
|
+
return { active: false, note: "inactive: tools.enableHooks is false in settings.json" };
|
|
2919
|
+
let version = "";
|
|
2920
|
+
try {
|
|
2921
|
+
version = execFileSync5("gemini", ["--version"], {
|
|
2922
|
+
encoding: "utf8",
|
|
2923
|
+
timeout: 4e3,
|
|
2924
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
2925
|
+
}).trim();
|
|
2926
|
+
} catch {
|
|
2927
|
+
return {
|
|
2928
|
+
active: true,
|
|
2929
|
+
note: "could not run `gemini --version`; hooks need tools.enableHooks on versions before 0.46"
|
|
2930
|
+
};
|
|
2931
|
+
}
|
|
2932
|
+
const m = /(\d+)\.(\d+)/.exec(version);
|
|
2933
|
+
if (m && (Number(m[1]) > 0 || Number(m[2]) >= 46)) return { active: true };
|
|
2934
|
+
return {
|
|
2935
|
+
active: false,
|
|
2936
|
+
note: `inactive: Gemini CLI ${version || "?"} runs hooks only with "tools": {"enableHooks": true} in settings.json`
|
|
2937
|
+
};
|
|
2938
|
+
}
|
|
2939
|
+
var geminiCli = {
|
|
2940
|
+
name: NAME5,
|
|
2941
|
+
events: GEMINI_EVENTS,
|
|
2942
|
+
// ~/.gemini also belongs to Antigravity; Gemini CLI is here when its settings or its chat logs exist.
|
|
2943
|
+
detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(path12.join(geminiDir(env), "tmp")),
|
|
2944
|
+
configFiles: (env = process.env) => [settings(env)],
|
|
2945
|
+
install: (cmd, env = process.env, opts = {}) => {
|
|
2946
|
+
const r = installClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
|
|
2947
|
+
const a = geminiHooksActive(settings(env));
|
|
2948
|
+
return [a.note && !r.skipped ? { ...r, note: [r.note, a.note].filter(Boolean).join("; ") } : r];
|
|
2949
|
+
},
|
|
2950
|
+
uninstall: (env = process.env, opts = {}) => [
|
|
2951
|
+
uninstallClaudeShaped(settings(env), GEMINI_EVENTS, { created: opts.created?.includes(settings(env)) })
|
|
2952
|
+
],
|
|
2953
|
+
installed: (cmd, env = process.env, opts = {}) => {
|
|
2954
|
+
const state = installedClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
|
|
2955
|
+
const a = geminiHooksActive(settings(env));
|
|
2956
|
+
return [
|
|
2957
|
+
{
|
|
2958
|
+
file: settings(env),
|
|
2959
|
+
state: state === "current" && !a.active ? "inactive" : state,
|
|
2960
|
+
...a.note ? { note: a.note } : {}
|
|
2961
|
+
}
|
|
2962
|
+
];
|
|
2963
|
+
},
|
|
2964
|
+
fromHook(input) {
|
|
2965
|
+
const event = input.argv[1] ?? "";
|
|
2966
|
+
let s = {};
|
|
2273
2967
|
try {
|
|
2274
|
-
const j =
|
|
2275
|
-
if (j &&
|
|
2968
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
2969
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
2276
2970
|
} catch {
|
|
2277
2971
|
}
|
|
2278
|
-
|
|
2279
|
-
|
|
2972
|
+
const id = s.session_id;
|
|
2973
|
+
if (!id) return null;
|
|
2974
|
+
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
2975
|
+
const tool = validTool5(s.tool_name) ? s.tool_name : void 0;
|
|
2976
|
+
const ev = (type, data) => ({ type, data: prune5(data), harnessEvent: event });
|
|
2977
|
+
const events = [];
|
|
2978
|
+
switch (event) {
|
|
2979
|
+
case "SessionStart":
|
|
2980
|
+
events.push(
|
|
2981
|
+
ev("session.started", {
|
|
2982
|
+
source: typeof s.source === "string" && ["startup", "resume", "clear"].includes(s.source) ? s.source : "unknown"
|
|
2983
|
+
})
|
|
2984
|
+
);
|
|
2985
|
+
break;
|
|
2986
|
+
case "BeforeAgent":
|
|
2987
|
+
events.push(
|
|
2988
|
+
ev("turn.started", { prompt_chars: typeof s.prompt === "string" ? [...s.prompt].length : void 0 })
|
|
2989
|
+
);
|
|
2990
|
+
break;
|
|
2991
|
+
case "BeforeTool":
|
|
2992
|
+
if (tool) events.push(ev("tool.started", { tool, input: s.tool_input }));
|
|
2993
|
+
break;
|
|
2994
|
+
case "AfterTool": {
|
|
2995
|
+
const resp = s.tool_response;
|
|
2996
|
+
const err = resp && typeof resp === "object" && resp.error ? String(typeof resp.error === "string" ? resp.error : JSON.stringify(resp.error)).slice(0, 2e3) : void 0;
|
|
2997
|
+
if (tool)
|
|
2998
|
+
events.push(
|
|
2999
|
+
ev("tool.ended", {
|
|
3000
|
+
tool,
|
|
3001
|
+
ok: !err,
|
|
3002
|
+
error: err,
|
|
3003
|
+
input: s.tool_input,
|
|
3004
|
+
output: resp?.llmContent ?? s.tool_response
|
|
3005
|
+
})
|
|
3006
|
+
);
|
|
3007
|
+
break;
|
|
3008
|
+
}
|
|
3009
|
+
case "Notification":
|
|
3010
|
+
events.push(
|
|
3011
|
+
ev("attention.needed", {
|
|
3012
|
+
attention_id: `notif-${Date.now()}`,
|
|
3013
|
+
kind: s.notification_type === "ToolPermission" ? "permission" : "question",
|
|
3014
|
+
message: typeof s.message === "string" ? s.message.slice(0, 200) : void 0
|
|
3015
|
+
})
|
|
3016
|
+
);
|
|
3017
|
+
break;
|
|
3018
|
+
case "AfterAgent":
|
|
3019
|
+
events.push(ev("turn.ended", { reason: "stop" }));
|
|
3020
|
+
break;
|
|
3021
|
+
case "PreCompress":
|
|
3022
|
+
events.push(ev("context.compacted", { trigger: s.trigger === "manual" ? "manual" : "auto" }));
|
|
3023
|
+
break;
|
|
3024
|
+
case "SessionEnd":
|
|
3025
|
+
events.push(
|
|
3026
|
+
ev("session.ended", {
|
|
3027
|
+
reason: s.reason === "clear" || s.reason === "logout" || s.reason === "exit" ? s.reason : s.reason === "prompt_input_exit" ? "exit" : "other"
|
|
3028
|
+
})
|
|
3029
|
+
);
|
|
3030
|
+
break;
|
|
3031
|
+
default:
|
|
3032
|
+
return null;
|
|
3033
|
+
}
|
|
3034
|
+
const r = { session, events, hints: {} };
|
|
3035
|
+
if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
|
|
3036
|
+
return r;
|
|
3037
|
+
},
|
|
3038
|
+
readTranscript(file, fromLine) {
|
|
3039
|
+
let text = "";
|
|
3040
|
+
try {
|
|
3041
|
+
text = readFileSync6(file, "utf8");
|
|
3042
|
+
} catch {
|
|
3043
|
+
return { turns: [], line: fromLine };
|
|
3044
|
+
}
|
|
3045
|
+
return readGemini(text.split("\n"), fromLine);
|
|
3046
|
+
},
|
|
3047
|
+
facts(_session, transcript) {
|
|
3048
|
+
const out = {};
|
|
3049
|
+
if (!transcript) return out;
|
|
3050
|
+
const { head, tail } = ends(transcript, 64 * 1024, 64 * 1024);
|
|
3051
|
+
const H = parseLines(head);
|
|
3052
|
+
const T = parseLines(tail);
|
|
3053
|
+
const first = H.find((j) => j.type === "user");
|
|
3054
|
+
const raw = first ? first.content ?? first.message ?? first.text : void 0;
|
|
3055
|
+
const text = typeof raw === "string" ? raw.trim() : "";
|
|
3056
|
+
if (text) {
|
|
3057
|
+
const line = text.split("\n")[0];
|
|
3058
|
+
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
3059
|
+
out.title_source = "first-ask";
|
|
3060
|
+
}
|
|
3061
|
+
const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
|
|
3062
|
+
if (times.length) {
|
|
3063
|
+
out.started_at = Math.min(...times);
|
|
3064
|
+
out.last_at = Math.max(...times);
|
|
3065
|
+
}
|
|
3066
|
+
return out;
|
|
3067
|
+
},
|
|
3068
|
+
backfill: () => []
|
|
3069
|
+
// chat logs are keyed by a project hash, not a session id we can name; the first hook of a resumed session reports it
|
|
3070
|
+
};
|
|
3071
|
+
function prune5(o) {
|
|
3072
|
+
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
3073
|
+
return o;
|
|
2280
3074
|
}
|
|
2281
|
-
var isUuid = (s) => typeof s === "string" && UUID_RE.test(s);
|
|
2282
3075
|
|
|
2283
3076
|
// src/adapters/index.ts
|
|
2284
|
-
var ADAPTERS = [claudeCode, codex, geminiCli, antigravity];
|
|
3077
|
+
var ADAPTERS = [claudeCode, codex, geminiCli, antigravity, devin];
|
|
2285
3078
|
var adapterByName = (name) => ADAPTERS.find((a) => a.name === name);
|
|
3079
|
+
function drivableHarnesses(env = process.env) {
|
|
3080
|
+
return [
|
|
3081
|
+
...findClaude(env) ? ["claude-code"] : [],
|
|
3082
|
+
...findCodex(env) ? ["codex"] : [],
|
|
3083
|
+
...findAgy(env) ? ["antigravity"] : []
|
|
3084
|
+
];
|
|
3085
|
+
}
|
|
2286
3086
|
|
|
2287
3087
|
// src/config.ts
|
|
2288
|
-
import { chmodSync, existsSync as
|
|
2289
|
-
import
|
|
3088
|
+
import { chmodSync, existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
|
|
3089
|
+
import path13 from "node:path";
|
|
2290
3090
|
var DEFAULT_CONFIG = { sinks: [], harnesses: {}, update_check: true, keep_days: 30 };
|
|
2291
3091
|
function readConfig(file = configFile()) {
|
|
2292
3092
|
try {
|
|
@@ -2302,10 +3102,10 @@ function readConfig(file = configFile()) {
|
|
|
2302
3102
|
}
|
|
2303
3103
|
}
|
|
2304
3104
|
function writeConfig(c, file = configFile()) {
|
|
2305
|
-
mkdirSync4(
|
|
3105
|
+
mkdirSync4(path13.dirname(file), { recursive: true, mode: 448 });
|
|
2306
3106
|
writeFileSync4(file, `${JSON.stringify(c, null, 2)}
|
|
2307
3107
|
`, { mode: 384 });
|
|
2308
|
-
if (process.platform !== "win32" &&
|
|
3108
|
+
if (process.platform !== "win32" && existsSync8(file)) {
|
|
2309
3109
|
try {
|
|
2310
3110
|
chmodSync(file, 384);
|
|
2311
3111
|
} catch {
|
|
@@ -2334,7 +3134,7 @@ function encodeTime(t) {
|
|
|
2334
3134
|
function ulid(now = Date.now()) {
|
|
2335
3135
|
if (now === lastTime) {
|
|
2336
3136
|
for (let i = lastRand.length - 1; i >= 0; i--) {
|
|
2337
|
-
const v =
|
|
3137
|
+
const v = lastRand[i] + 1;
|
|
2338
3138
|
if (v < 32) {
|
|
2339
3139
|
lastRand[i] = v;
|
|
2340
3140
|
break;
|
|
@@ -2343,8 +3143,7 @@ function ulid(now = Date.now()) {
|
|
|
2343
3143
|
}
|
|
2344
3144
|
} else {
|
|
2345
3145
|
lastTime = now;
|
|
2346
|
-
|
|
2347
|
-
lastRand = Array.from({ length: 16 }, (_, i) => (b[i] ?? 0) % 32);
|
|
3146
|
+
lastRand = Array.from(randomBytes(16), (b) => b % 32);
|
|
2348
3147
|
}
|
|
2349
3148
|
return encodeTime(now) + lastRand.map((v) => ALPHABET[v]).join("");
|
|
2350
3149
|
}
|
|
@@ -2372,53 +3171,56 @@ function makeEvent(a, ctx) {
|
|
|
2372
3171
|
import {
|
|
2373
3172
|
appendFileSync,
|
|
2374
3173
|
closeSync as closeSync3,
|
|
2375
|
-
existsSync as
|
|
3174
|
+
existsSync as existsSync9,
|
|
2376
3175
|
mkdirSync as mkdirSync5,
|
|
2377
3176
|
openSync as openSync3,
|
|
2378
3177
|
readdirSync as readdirSync6,
|
|
2379
3178
|
readFileSync as readFileSync8,
|
|
2380
3179
|
readSync as readSync3,
|
|
2381
3180
|
renameSync,
|
|
2382
|
-
statSync as
|
|
3181
|
+
statSync as statSync6,
|
|
2383
3182
|
unlinkSync as unlinkSync3,
|
|
2384
3183
|
writeFileSync as writeFileSync5
|
|
2385
3184
|
} from "node:fs";
|
|
2386
|
-
import
|
|
3185
|
+
import path14 from "node:path";
|
|
3186
|
+
var READ_CHUNK = 1024 * 1024;
|
|
3187
|
+
var READ_MAX = 8 * 1024 * 1024;
|
|
2387
3188
|
var Outbox = class {
|
|
2388
3189
|
constructor(root) {
|
|
2389
3190
|
this.root = root;
|
|
2390
3191
|
}
|
|
2391
3192
|
root;
|
|
2392
3193
|
dir(harness) {
|
|
2393
|
-
return
|
|
3194
|
+
return path14.join(this.root, "outbox", safeId(harness));
|
|
2394
3195
|
}
|
|
2395
3196
|
file(ref) {
|
|
2396
|
-
return
|
|
3197
|
+
return path14.join(this.dir(ref.harness), `${safeId(ref.session)}.jsonl`);
|
|
2397
3198
|
}
|
|
2398
3199
|
seqFile(ref) {
|
|
2399
|
-
return
|
|
3200
|
+
return path14.join(this.dir(ref.harness), `${safeId(ref.session)}.seq`);
|
|
2400
3201
|
}
|
|
2401
3202
|
/** The next seq for a session, persisted (survives a hibernating machine and a
|
|
2402
3203
|
* restart; an in-memory counter would restart at 0 and collide). */
|
|
2403
3204
|
nextSeq(ref) {
|
|
2404
3205
|
const f = this.seqFile(ref);
|
|
2405
|
-
let n =
|
|
3206
|
+
let n = -1;
|
|
2406
3207
|
try {
|
|
2407
|
-
|
|
3208
|
+
const t = readFileSync8(f, "utf8").trim();
|
|
3209
|
+
if (/^\d+$/.test(t)) n = Number(t);
|
|
2408
3210
|
} catch {
|
|
2409
|
-
n = this.lastSeqInFile(ref) + 1;
|
|
2410
3211
|
}
|
|
2411
|
-
|
|
3212
|
+
if (n < 0) n = this.lastSeqInFile(ref) + 1;
|
|
3213
|
+
mkdirSync5(path14.dirname(f), { recursive: true, mode: 448 });
|
|
2412
3214
|
writeFileSync5(f, String(n + 1), { mode: 384 });
|
|
2413
3215
|
return n;
|
|
2414
3216
|
}
|
|
2415
3217
|
lastSeqInFile(ref) {
|
|
2416
3218
|
const f = this.file(ref);
|
|
2417
|
-
if (!
|
|
3219
|
+
if (!existsSync9(f)) return -1;
|
|
2418
3220
|
const lines = readFileSync8(f, "utf8").trim().split("\n");
|
|
2419
3221
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
2420
3222
|
try {
|
|
2421
|
-
const j = JSON.parse(lines[i]
|
|
3223
|
+
const j = JSON.parse(lines[i]);
|
|
2422
3224
|
if (typeof j.session?.seq === "number") return j.session.seq;
|
|
2423
3225
|
} catch {
|
|
2424
3226
|
}
|
|
@@ -2434,10 +3236,10 @@ var Outbox = class {
|
|
|
2434
3236
|
/** Every session file, newest first. */
|
|
2435
3237
|
sessions() {
|
|
2436
3238
|
const out = [];
|
|
2437
|
-
const base =
|
|
2438
|
-
if (!
|
|
3239
|
+
const base = path14.join(this.root, "outbox");
|
|
3240
|
+
if (!existsSync9(base)) return [];
|
|
2439
3241
|
for (const h of readdirSync6(base)) {
|
|
2440
|
-
const d =
|
|
3242
|
+
const d = path14.join(base, h);
|
|
2441
3243
|
let names = [];
|
|
2442
3244
|
try {
|
|
2443
3245
|
names = readdirSync6(d).filter((n) => n.endsWith(".jsonl"));
|
|
@@ -2446,7 +3248,7 @@ var Outbox = class {
|
|
|
2446
3248
|
}
|
|
2447
3249
|
for (const n of names) {
|
|
2448
3250
|
try {
|
|
2449
|
-
out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime:
|
|
3251
|
+
out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime: statSync6(path14.join(d, n)).mtimeMs });
|
|
2450
3252
|
} catch {
|
|
2451
3253
|
}
|
|
2452
3254
|
}
|
|
@@ -2454,31 +3256,43 @@ var Outbox = class {
|
|
|
2454
3256
|
return out.sort((a, b) => b.mtime - a.mtime).map((x) => x.ref);
|
|
2455
3257
|
}
|
|
2456
3258
|
/** Read events after byte offset `from`; returns them and the new offset. Lines
|
|
2457
|
-
* are read whole; a partial trailing line (a write in progress) is left.
|
|
3259
|
+
* are read whole; a partial trailing line (a write in progress) is left. A read
|
|
3260
|
+
* takes READ_CHUNK bytes at a time, not the rest of the file: a session file grows
|
|
3261
|
+
* to tens of MB and a drain takes it in batches of 50. It reads on until the batch
|
|
3262
|
+
* is full or the file ends, so lines that parse to nothing never stop a drain; one
|
|
3263
|
+
* line longer than the chunk gets a larger one, up to READ_MAX. */
|
|
2458
3264
|
read(ref, from, maxEvents = 500) {
|
|
2459
3265
|
const f = this.file(ref);
|
|
2460
|
-
if (!
|
|
2461
|
-
const size =
|
|
3266
|
+
if (!existsSync9(f)) return { events: [], offset: from };
|
|
3267
|
+
const size = statSync6(f).size;
|
|
2462
3268
|
if (from >= size) return { events: [], offset: from };
|
|
2463
3269
|
const fd = openSync3(f, "r");
|
|
2464
3270
|
try {
|
|
2465
|
-
const buf = Buffer.alloc(Math.min(size - from, 8 * 1024 * 1024));
|
|
2466
|
-
const n = readSync3(fd, buf, 0, buf.length, from);
|
|
2467
|
-
const text = buf.subarray(0, n).toString("utf8");
|
|
2468
3271
|
const events = [];
|
|
2469
3272
|
let offset = from;
|
|
2470
|
-
let
|
|
2471
|
-
while (events.length < maxEvents) {
|
|
2472
|
-
const
|
|
2473
|
-
|
|
2474
|
-
const
|
|
2475
|
-
pos =
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
3273
|
+
let chunk = READ_CHUNK;
|
|
3274
|
+
while (events.length < maxEvents && offset < size) {
|
|
3275
|
+
const buf = Buffer.alloc(Math.min(size - offset, chunk));
|
|
3276
|
+
const n = readSync3(fd, buf, 0, buf.length, offset);
|
|
3277
|
+
const text = buf.subarray(0, n).toString("utf8");
|
|
3278
|
+
let pos = 0;
|
|
3279
|
+
let used = 0;
|
|
3280
|
+
while (events.length < maxEvents) {
|
|
3281
|
+
const nl = text.indexOf("\n", pos);
|
|
3282
|
+
if (nl < 0) break;
|
|
3283
|
+
const line = text.slice(pos, nl);
|
|
3284
|
+
pos = nl + 1;
|
|
3285
|
+
used += Buffer.byteLength(line, "utf8") + 1;
|
|
3286
|
+
if (!line.trim()) continue;
|
|
3287
|
+
try {
|
|
3288
|
+
events.push(JSON.parse(line));
|
|
3289
|
+
} catch {
|
|
3290
|
+
}
|
|
2481
3291
|
}
|
|
3292
|
+
offset += used;
|
|
3293
|
+
if (used) continue;
|
|
3294
|
+
if (offset + n >= size || chunk >= READ_MAX) break;
|
|
3295
|
+
chunk *= 2;
|
|
2482
3296
|
}
|
|
2483
3297
|
return { events, offset };
|
|
2484
3298
|
} finally {
|
|
@@ -2492,7 +3306,7 @@ var Outbox = class {
|
|
|
2492
3306
|
for (const ref of this.sessions()) {
|
|
2493
3307
|
const f = this.file(ref);
|
|
2494
3308
|
try {
|
|
2495
|
-
if (
|
|
3309
|
+
if (statSync6(f).mtimeMs < cutoff) {
|
|
2496
3310
|
unlinkSync3(f);
|
|
2497
3311
|
try {
|
|
2498
3312
|
unlinkSync3(this.seqFile(ref));
|
|
@@ -2522,7 +3336,7 @@ var Cursors = class {
|
|
|
2522
3336
|
root;
|
|
2523
3337
|
sink;
|
|
2524
3338
|
file() {
|
|
2525
|
-
return
|
|
3339
|
+
return path14.join(this.root, "cursors", `${safeId(this.sink)}.json`);
|
|
2526
3340
|
}
|
|
2527
3341
|
read() {
|
|
2528
3342
|
try {
|
|
@@ -2533,7 +3347,7 @@ var Cursors = class {
|
|
|
2533
3347
|
}
|
|
2534
3348
|
write(c) {
|
|
2535
3349
|
const f = this.file();
|
|
2536
|
-
mkdirSync5(
|
|
3350
|
+
mkdirSync5(path14.dirname(f), { recursive: true, mode: 448 });
|
|
2537
3351
|
const tmp = `${f}.${process.pid}.tmp`;
|
|
2538
3352
|
writeFileSync5(tmp, JSON.stringify(c), { mode: 384 });
|
|
2539
3353
|
renameSync(tmp, f);
|
|
@@ -2543,11 +3357,14 @@ var Cursors = class {
|
|
|
2543
3357
|
}
|
|
2544
3358
|
};
|
|
2545
3359
|
function takeLock(root, ref, staleMs = 5 * 6e4) {
|
|
2546
|
-
|
|
3360
|
+
return takeNamedLock(root, `${safeId(ref.harness)}-${safeId(ref.session)}`, staleMs);
|
|
3361
|
+
}
|
|
3362
|
+
function takeNamedLock(root, name, staleMs = 5 * 6e4) {
|
|
3363
|
+
const dir = path14.join(root, "locks");
|
|
2547
3364
|
mkdirSync5(dir, { recursive: true, mode: 448 });
|
|
2548
|
-
const f =
|
|
3365
|
+
const f = path14.join(dir, `${name}.lock`);
|
|
2549
3366
|
try {
|
|
2550
|
-
if (Date.now() -
|
|
3367
|
+
if (Date.now() - statSync6(f).mtimeMs > staleMs || !processAlive(Number(readFileSync8(f, "utf8")))) unlinkSync3(f);
|
|
2551
3368
|
else return null;
|
|
2552
3369
|
} catch {
|
|
2553
3370
|
}
|
|
@@ -2563,9 +3380,18 @@ function takeLock(root, ref, staleMs = 5 * 6e4) {
|
|
|
2563
3380
|
}
|
|
2564
3381
|
};
|
|
2565
3382
|
}
|
|
3383
|
+
function processAlive(pid) {
|
|
3384
|
+
if (!Number.isInteger(pid) || pid <= 0) return true;
|
|
3385
|
+
try {
|
|
3386
|
+
process.kill(pid, 0);
|
|
3387
|
+
return true;
|
|
3388
|
+
} catch (e) {
|
|
3389
|
+
return e.code === "EPERM";
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
2566
3392
|
|
|
2567
3393
|
// src/privacy/pii.ts
|
|
2568
|
-
import { createHash as
|
|
3394
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
2569
3395
|
var RULESET_PII = "pii@1";
|
|
2570
3396
|
var EMAIL = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g;
|
|
2571
3397
|
var PHONE = /(?<![\w.])(?:\+\d{1,3}[ .-]?)?(?:\(\d{2,4}\)[ .-]?)?\d{2,4}(?:[ .-]?\d{2,4}){1,3}(?!\w)/g;
|
|
@@ -2587,7 +3413,7 @@ function reducePii(text, home) {
|
|
|
2587
3413
|
return out;
|
|
2588
3414
|
}
|
|
2589
3415
|
function hashMachine(name) {
|
|
2590
|
-
return
|
|
3416
|
+
return createHash3("sha256").update(name).digest("hex").slice(0, 8);
|
|
2591
3417
|
}
|
|
2592
3418
|
function reducePiiDeep(v, home) {
|
|
2593
3419
|
if (typeof v === "string") return reducePii(v, home);
|
|
@@ -2676,12 +3502,12 @@ function redactAssignments(text) {
|
|
|
2676
3502
|
let last = 0;
|
|
2677
3503
|
NAME_SEP.lastIndex = 0;
|
|
2678
3504
|
for (let m = NAME_SEP.exec(text); m; m = NAME_SEP.exec(text)) {
|
|
2679
|
-
const name = m[1]
|
|
3505
|
+
const name = m[1];
|
|
2680
3506
|
if (!isSecretName(name)) continue;
|
|
2681
3507
|
VALUE.lastIndex = NAME_SEP.lastIndex;
|
|
2682
3508
|
const v = VALUE.exec(text);
|
|
2683
3509
|
if (!v) continue;
|
|
2684
|
-
const value = v[1] ?? v[2] ?? v[3] ?? v[4]
|
|
3510
|
+
const value = v[1] ?? v[2] ?? v[3] ?? v[4];
|
|
2685
3511
|
NAME_SEP.lastIndex = VALUE.lastIndex;
|
|
2686
3512
|
if (isPlaceholder(value) || value.includes(REDACTED)) continue;
|
|
2687
3513
|
if (/tokens?$/i.test(name) && /^\d+(?:[.,]\d+)*[km]?$/i.test(value)) continue;
|
|
@@ -2737,7 +3563,7 @@ function redactSecrets(text) {
|
|
|
2737
3563
|
let out = text;
|
|
2738
3564
|
for (const re of TOKEN_PATTERNS) out = out.replace(re, REDACTED);
|
|
2739
3565
|
out = out.replace(URL_CREDENTIALS, (_m, head, _pw, at) => `${head}${REDACTED}${at}`);
|
|
2740
|
-
out = out.replace(AUTH_HEADER, (
|
|
3566
|
+
out = out.replace(AUTH_HEADER, (_m, head) => `${head}${REDACTED}`);
|
|
2741
3567
|
out = redactAssignments(out);
|
|
2742
3568
|
out = out.replace(
|
|
2743
3569
|
PROSE,
|
|
@@ -2769,6 +3595,7 @@ var TYPE_TIER = {
|
|
|
2769
3595
|
"turn.started": 0,
|
|
2770
3596
|
"turn.ended": 0,
|
|
2771
3597
|
"turn.transcript": 2,
|
|
3598
|
+
"turn.progress": 2,
|
|
2772
3599
|
"tool.started": 1,
|
|
2773
3600
|
"tool.ended": 1,
|
|
2774
3601
|
"files.changed": 1,
|
|
@@ -2788,6 +3615,7 @@ var DATA_ALLOW = {
|
|
|
2788
3615
|
"turn.started": { 0: ["turn_id"], 1: ["prompt_chars"] },
|
|
2789
3616
|
"turn.ended": { 0: ["turn_id", "reason", "ms"], 1: ["error"] },
|
|
2790
3617
|
"turn.transcript": { 2: ["turn", "user", "assistant", "at"] },
|
|
3618
|
+
"turn.progress": { 2: ["turn", "user", "assistant", "at"] },
|
|
2791
3619
|
"tool.started": { 1: ["tool", "call_id", "turn_id", "agent_id"], 3: ["input"] },
|
|
2792
3620
|
"tool.ended": { 1: ["tool", "call_id", "turn_id", "agent_id", "ms", "ok", "error"], 3: ["input", "output"] },
|
|
2793
3621
|
"files.changed": { 1: ["paths"] },
|
|
@@ -2828,10 +3656,12 @@ function cap(v, max) {
|
|
|
2828
3656
|
}
|
|
2829
3657
|
return v;
|
|
2830
3658
|
}
|
|
3659
|
+
function capTail(v, max) {
|
|
3660
|
+
return v.length > max ? `\u2026${v.slice(v.length - max + 1)}` : v;
|
|
3661
|
+
}
|
|
2831
3662
|
function filterEvent(event, tier, pii, opts = {}) {
|
|
2832
3663
|
const minTier = TYPE_TIER[event.type];
|
|
2833
3664
|
if (minTier !== void 0 && minTier > tier) return null;
|
|
2834
|
-
if (event.type === "native" && tier < 3) return null;
|
|
2835
3665
|
const session = {};
|
|
2836
3666
|
for (const k of SESSION_ALLOW)
|
|
2837
3667
|
if (event.session[k] !== void 0)
|
|
@@ -2841,7 +3671,8 @@ function filterEvent(event, tier, pii, opts = {}) {
|
|
|
2841
3671
|
if (keys === null) data = { ...event.data };
|
|
2842
3672
|
else for (const k of keys) if (event.data[k] !== void 0) data[k] = event.data[k];
|
|
2843
3673
|
if (typeof data.user === "string") data.user = cap(data.user, MAX_SIDE);
|
|
2844
|
-
if (typeof data.assistant === "string")
|
|
3674
|
+
if (typeof data.assistant === "string")
|
|
3675
|
+
data.assistant = event.type === "turn.progress" ? capTail(data.assistant, MAX_SIDE) : cap(data.assistant, MAX_SIDE);
|
|
2845
3676
|
if (data.input !== void 0) data.input = cap(data.input, MAX_IO);
|
|
2846
3677
|
if (data.output !== void 0) data.output = cap(data.output, MAX_IO);
|
|
2847
3678
|
if (typeof data.message === "string") data.message = cap(data.message, 200);
|
|
@@ -2862,9 +3693,15 @@ function filterEvent(event, tier, pii, opts = {}) {
|
|
|
2862
3693
|
return out;
|
|
2863
3694
|
}
|
|
2864
3695
|
|
|
3696
|
+
// src/short-id.ts
|
|
3697
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
3698
|
+
function shortId(harness, id) {
|
|
3699
|
+
return createHash4("sha256").update(`sessionpipe.session:${harness}:${id}`).digest("hex").slice(0, 8);
|
|
3700
|
+
}
|
|
3701
|
+
|
|
2865
3702
|
// src/sinks/file.ts
|
|
2866
3703
|
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync6 } from "node:fs";
|
|
2867
|
-
import
|
|
3704
|
+
import path15 from "node:path";
|
|
2868
3705
|
var FileSink = class {
|
|
2869
3706
|
constructor(name, file, tier, pii) {
|
|
2870
3707
|
this.name = name;
|
|
@@ -2878,7 +3715,7 @@ var FileSink = class {
|
|
|
2878
3715
|
pii;
|
|
2879
3716
|
async send(events) {
|
|
2880
3717
|
try {
|
|
2881
|
-
mkdirSync6(
|
|
3718
|
+
mkdirSync6(path15.dirname(this.file), { recursive: true });
|
|
2882
3719
|
appendFileSync2(this.file, `${events.map((e) => JSON.stringify(e)).join("\n")}
|
|
2883
3720
|
`);
|
|
2884
3721
|
return { ok: true, acknowledged: events.length, accepted: events.length, duplicates: 0, rejected: [] };
|
|
@@ -2902,8 +3739,12 @@ var StdoutSink = class {
|
|
|
2902
3739
|
pii;
|
|
2903
3740
|
write;
|
|
2904
3741
|
async send(events) {
|
|
2905
|
-
|
|
2906
|
-
|
|
3742
|
+
try {
|
|
3743
|
+
for (const e of events) this.write(JSON.stringify(e));
|
|
3744
|
+
return { ok: true, acknowledged: events.length, accepted: events.length, duplicates: 0, rejected: [] };
|
|
3745
|
+
} catch (e) {
|
|
3746
|
+
return { ok: false, acknowledged: 0, error: String(e.message ?? e), action: "retry" };
|
|
3747
|
+
}
|
|
2907
3748
|
}
|
|
2908
3749
|
};
|
|
2909
3750
|
|
|
@@ -2914,8 +3755,9 @@ export {
|
|
|
2914
3755
|
AG_STALE_PRETOOL,
|
|
2915
3756
|
Cursors,
|
|
2916
3757
|
DEFAULT_CONFIG,
|
|
3758
|
+
DEVIN_EVENTS,
|
|
2917
3759
|
FileSink,
|
|
2918
|
-
|
|
3760
|
+
JOB_TIMEOUT_MS,
|
|
2919
3761
|
Outbox,
|
|
2920
3762
|
PROTOCOL_VERSION,
|
|
2921
3763
|
REDACTED,
|
|
@@ -2926,35 +3768,52 @@ export {
|
|
|
2926
3768
|
adapterByName,
|
|
2927
3769
|
antigravity,
|
|
2928
3770
|
antigravityAnswer,
|
|
3771
|
+
antigravity_control_exports as antigravityControl,
|
|
3772
|
+
capTail,
|
|
3773
|
+
claudeAccounts,
|
|
2929
3774
|
claudeCode,
|
|
2930
3775
|
claude_code_control_exports as claudeControl,
|
|
2931
3776
|
claudeDirs,
|
|
2932
3777
|
codex,
|
|
3778
|
+
codex_control_exports as codexControl,
|
|
2933
3779
|
configFile,
|
|
3780
|
+
deniedNote,
|
|
3781
|
+
devin,
|
|
3782
|
+
devinConfigDir,
|
|
3783
|
+
devinDataDir,
|
|
3784
|
+
drivableHarnesses,
|
|
2934
3785
|
filterEvent,
|
|
2935
3786
|
geminiCli,
|
|
2936
3787
|
gitFacts,
|
|
2937
3788
|
hasSecret,
|
|
2938
3789
|
hashMachine,
|
|
3790
|
+
homeDir,
|
|
2939
3791
|
isInjected,
|
|
2940
3792
|
isSecretName,
|
|
2941
3793
|
isUlid,
|
|
3794
|
+
jobEnv,
|
|
2942
3795
|
machineName,
|
|
2943
3796
|
makeEvent,
|
|
3797
|
+
notClaudeCode,
|
|
2944
3798
|
pairTurns,
|
|
3799
|
+
processAlive,
|
|
2945
3800
|
readAntigravity,
|
|
2946
3801
|
readClaude,
|
|
2947
3802
|
readCodex,
|
|
2948
3803
|
readConfig,
|
|
3804
|
+
readDevinChain,
|
|
2949
3805
|
readGemini,
|
|
2950
3806
|
redactDeep,
|
|
2951
3807
|
redactSecrets,
|
|
2952
3808
|
reducePii,
|
|
2953
3809
|
reducePiiDeep,
|
|
3810
|
+
runHeadless,
|
|
2954
3811
|
safeId,
|
|
3812
|
+
shortId,
|
|
2955
3813
|
stateDir,
|
|
2956
3814
|
stripCredentials,
|
|
2957
3815
|
takeLock,
|
|
3816
|
+
takeNamedLock,
|
|
2958
3817
|
tilde,
|
|
2959
3818
|
ulid,
|
|
2960
3819
|
writeConfig
|