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.
Files changed (61) hide show
  1. package/README.md +1 -1
  2. package/dist/control.js +54 -4
  3. package/dist/control.js.map +2 -2
  4. package/dist/index.js +1690 -831
  5. package/dist/index.js.map +4 -4
  6. package/dist/schema/v1.d.ts +139 -71
  7. package/dist/schema/v1.d.ts.map +1 -1
  8. package/dist/schema.js +56 -5
  9. package/dist/schema.js.map +2 -2
  10. package/dist/src/adapters/antigravity-control.d.ts +58 -0
  11. package/dist/src/adapters/antigravity-control.d.ts.map +1 -0
  12. package/dist/src/adapters/antigravity.d.ts +1 -2
  13. package/dist/src/adapters/antigravity.d.ts.map +1 -1
  14. package/dist/src/adapters/claude-code-control.d.ts +7 -26
  15. package/dist/src/adapters/claude-code-control.d.ts.map +1 -1
  16. package/dist/src/adapters/claude-code.d.ts +10 -0
  17. package/dist/src/adapters/claude-code.d.ts.map +1 -1
  18. package/dist/src/adapters/claude-shaped.d.ts.map +1 -1
  19. package/dist/src/adapters/codex-config.d.ts +2 -2
  20. package/dist/src/adapters/codex-config.d.ts.map +1 -1
  21. package/dist/src/adapters/codex-control.d.ts +62 -0
  22. package/dist/src/adapters/codex-control.d.ts.map +1 -0
  23. package/dist/src/adapters/codex-rollout.d.ts +8 -0
  24. package/dist/src/adapters/codex-rollout.d.ts.map +1 -0
  25. package/dist/src/adapters/codex.d.ts +5 -1
  26. package/dist/src/adapters/codex.d.ts.map +1 -1
  27. package/dist/src/adapters/devin.d.ts +32 -0
  28. package/dist/src/adapters/devin.d.ts.map +1 -0
  29. package/dist/src/adapters/gemini-cli.d.ts.map +1 -1
  30. package/dist/src/adapters/headless.d.ts +70 -0
  31. package/dist/src/adapters/headless.d.ts.map +1 -0
  32. package/dist/src/adapters/index.d.ts +10 -2
  33. package/dist/src/adapters/index.d.ts.map +1 -1
  34. package/dist/src/adapters/types.d.ts +9 -1
  35. package/dist/src/adapters/types.d.ts.map +1 -1
  36. package/dist/src/control/verify.d.ts +27 -0
  37. package/dist/src/control/verify.d.ts.map +1 -1
  38. package/dist/src/index.d.ts +3 -2
  39. package/dist/src/index.d.ts.map +1 -1
  40. package/dist/src/outbox.d.ts +13 -2
  41. package/dist/src/outbox.d.ts.map +1 -1
  42. package/dist/src/paths.d.ts +3 -1
  43. package/dist/src/paths.d.ts.map +1 -1
  44. package/dist/src/privacy/filter.d.ts +2 -0
  45. package/dist/src/privacy/filter.d.ts.map +1 -1
  46. package/dist/src/privacy/index.d.ts +1 -1
  47. package/dist/src/privacy/index.d.ts.map +1 -1
  48. package/dist/src/readers/files.d.ts.map +1 -1
  49. package/dist/src/readers/git.d.ts.map +1 -1
  50. package/dist/src/readers/sqlite.d.ts +23 -0
  51. package/dist/src/readers/sqlite.d.ts.map +1 -0
  52. package/dist/src/readers/transcripts.d.ts +9 -2
  53. package/dist/src/readers/transcripts.d.ts.map +1 -1
  54. package/dist/src/short-id.d.ts +2 -0
  55. package/dist/src/short-id.d.ts.map +1 -0
  56. package/dist/src/sinks/file.d.ts.map +1 -1
  57. package/dist/src/sinks/stdout.d.ts.map +1 -1
  58. package/dist/src/types.d.ts +1 -0
  59. package/dist/src/types.d.ts.map +1 -1
  60. package/dist/src/ulid.d.ts.map +1 -1
  61. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- // sessionpipe-core 0.6.1 · Apache-2.0 · https://sessionpipe.org
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 HOME = os.homedir();
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(HOME, "AppData", "Roaming"), "sessionpipe", "config.json");
29
- return path.join(env.XDG_CONFIG_HOME || path.join(HOME, ".config"), "sessionpipe", "config.json");
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(HOME, "AppData", "Local"), "sessionpipe");
35
- return path.join(env.XDG_STATE_HOME || path.join(HOME, ".local", "state"), "sessionpipe");
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 = 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
- return { turns: turns.map((t) => ({ user: t.user, assistant: t.assistant, at: t.at, endLine: t.endLine })), line };
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_-]+>/i.test(text) && /<\/[a-z_-]+>\s*$/i.test(text) || /^<!--\s/.test(text) || /^<command-/.test(text);
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")) continue;
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
- for (let i = from; i < lines.length; i++) {
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 (/^<[a-z_-]+>/i.test(text)) continue;
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] ?? "" : j.content).trim();
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
- const next = formatJson(settings2, f);
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(HOME, ".gemini");
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
- s = input.stdin ? JSON.parse(input.stdin) : {};
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, state) {
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 rows = [];
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: [], state: noState, env });
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
- rows.push({ session: { id, ...rest }, started_at, last_at });
570
+ rows2.push({ session: { id, ...rest }, started_at, last_at });
531
571
  }
532
572
  }
533
- return rows;
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] ?? "" : j.content).trim();
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 statSync3 } from "node:fs";
656
- import path5 from "node:path";
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
- let u = url.replace(/\/\/[^/@]*@/, "//");
686
- const ssh = /^(?:ssh:\/\/)?git@([^:/]+)[:/](.+?)(?:\.git)?$/.exec(u);
687
- if (ssh) u = `${ssh[1]}/${ssh[2]}`;
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 || 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 = path5.resolve(d);
1039
+ const r = path7.resolve(d);
724
1040
  if (!out.includes(r)) out.push(r);
725
1041
  };
726
- add(path5.join(home, ".claude"));
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 = path5.join(home, e.name);
732
- if (existsSync3(path5.join(d, "projects")) || existsSync3(path5.join(d, "settings.json"))) add(d);
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) => path5.join(dir, "settings.json");
739
- var configDirOf = (transcript) => path5.dirname(path5.dirname(path5.dirname(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
- s = input.stdin ? JSON.parse(input.stdin) : {};
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 rows = [];
1227
+ const rows2 = [];
909
1228
  const files = [];
910
1229
  for (const d of claudeDirs(env))
911
- files.push(...recentFiles(path5.join(d, "projects"), 1, sinceMs, (n) => /^[0-9a-f-]{36}\.jsonl$/.test(n)));
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 = path5.basename(f, ".jsonl");
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
- rows.push({
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 rows;
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(path5.join(dir, "sessions")).filter((n) => n.endsWith(".json"));
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(path5.join(dir, "sessions", n), "utf8")));
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(path5.join(dir, "sessions", `${pid}.json`), "utf8")));
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(path5.join(d, "sessions", `${pid2}.json`)));
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 accountCache = /* @__PURE__ */ new Map();
1039
- function claudeAccount(dir) {
1040
- const hit = accountCache.get(dir);
1041
- if (hit !== void 0) return hit;
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
- acct = String(a.accountUuid);
1050
- break;
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 = statSync3(file).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 || path5.basename(file, ".jsonl");
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 = statSync3(file).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/codex.ts
1209
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
1210
- import path6 from "node:path";
1211
-
1212
- // src/adapters/codex-config.ts
1213
- var NOTIFY_LINE = /^[ \t]*notify[ \t]*=[ \t]*(\[.*\])[ \t]*(?:#.*)?$/m;
1214
- var NOTIFY_KEY = /^[ \t]*notify[ \t]*=/m;
1215
- var OURS2 = /(?:hook\.js|sessionpipe-hook)",\s*"codex",\s*"notify"/;
1216
- function scan(line, st) {
1217
- let i = 0;
1218
- while (i < line.length) {
1219
- if (st.str) {
1220
- const j = line.indexOf(st.str, i);
1221
- if (j < 0) return;
1222
- i = j + 3;
1223
- st.str = null;
1224
- continue;
1225
- }
1226
- const ch = line[i];
1227
- if (ch === "#") return;
1228
- if (line.startsWith('"""', i) || line.startsWith("'''", i)) {
1229
- st.str = line.slice(i, i + 3);
1230
- i += 3;
1231
- continue;
1232
- }
1233
- if (ch === '"') {
1234
- i++;
1235
- while (i < line.length && line[i] !== '"') i += line[i] === "\\" ? 2 : 1;
1236
- i++;
1237
- continue;
1238
- }
1239
- if (ch === "'") {
1240
- const j = line.indexOf("'", i + 1);
1241
- i = j < 0 ? line.length : j + 1;
1242
- continue;
1243
- }
1244
- if (ch === "[") st.depth++;
1245
- else if (ch === "]") st.depth = Math.max(0, st.depth - 1);
1246
- i++;
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
- return -1;
1258
- }
1259
- function split(text) {
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
- return {
1276
- raw: m[1] ?? "",
1277
- argv: parseArgv(m[1] ?? ""),
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 && OURS2.test(t[1] ?? ""))
1297
- return {
1298
- raw: t[1] ?? "",
1299
- argv: parseArgv(t[1] ?? ""),
1300
- index: at + t.index,
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 || path6.join(HOME, ".codex");
1367
- var hooksFile2 = (env) => path6.join(codexHome(env), "hooks.json");
1368
- var configFile2 = (env) => path6.join(codexHome(env), "config.toml");
1369
- var sessionsDir = (env) => path6.join(codexHome(env), "sessions");
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 = readFileSync4(file, "utf8");
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(path6.dirname(file), { recursive: true });
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
- const next = replaceNotify(text, `notify = ${withPreviousRaw(n.raw, argv)}`);
1405
- if (next) {
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 = readFileSync4(file, "utf8");
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
- const next = replaceNotify(text, `notify = ${stripPreviousRaw(n.raw)}`);
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 = readdirSync4(dir);
2089
+ names = readdirSync5(dir);
1449
2090
  } catch {
1450
2091
  return;
1451
2092
  }
1452
2093
  for (const n of names.sort().reverse()) {
1453
- const p = path6.join(dir, n);
1454
- if (n.endsWith(".jsonl") && n.includes(threadId)) {
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 = readFileSync4(configFile2(env), "utf8");
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
- s = raw ? JSON.parse(raw) : {};
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
- s = input.stdin ? JSON.parse(input.stdin) : {};
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") r.transcript = s.transcript_path;
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 readGemini(text.split("\n"), fromLine);
2269
+ return readCodex(text.split("\n"), fromLine);
1828
2270
  },
1829
- facts(_session, transcript) {
2271
+ facts(session, transcript) {
1830
2272
  const out = {};
1831
- if (!transcript) return out;
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
- // chat logs are keyed by a project hash, not a session id we can name; the first hook of a resumed session reports it
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 prune4(o) {
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/claude-code-control.ts
1859
- var claude_code_control_exports = {};
1860
- __export(claude_code_control_exports, {
1861
- JOB_TIMEOUT_MS: () => JOB_TIMEOUT_MS,
1862
- claudeEnv: () => claudeEnv,
1863
- claudeLogin: () => claudeLogin,
1864
- detectCaps: () => detectCaps,
1865
- findClaude: () => findClaude,
1866
- findTranscript: () => findTranscript,
1867
- isUuid: () => isUuid,
1868
- liveProcess: () => liveProcess,
1869
- loginCommand: () => loginCommand,
1870
- loginHint: () => loginHint,
1871
- modeFlags: () => modeFlags,
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, execFileSync as execFileSync4, spawn } from "node:child_process";
1880
- import { accessSync, closeSync as closeSync2, constants, openSync as openSync2, readdirSync as readdirSync5, readFileSync as readFileSync6, readSync as readSync2, statSync as statSync4 } from "node:fs";
1881
- import os2 from "node:os";
1882
- import path8 from "node:path";
1883
- var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1884
- var JOB_TIMEOUT_MS = 30 * 6e4;
1885
- var OUT_CAP = 1024 * 1024;
1886
- var LIVE_WRITE_MS = 9e4;
1887
- function isExe(p) {
1888
- try {
1889
- if (!statSync4(p).isFile()) return false;
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
- var cmpVersion = (a, b) => {
1897
- const pa = a.split(".").map(Number);
1898
- const pb = b.split(".").map(Number);
1899
- for (let i = 0; i < Math.max(pa.length, pb.length); i++)
1900
- if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) - (pb[i] ?? 0);
1901
- return 0;
1902
- };
1903
- function findClaude(env = process.env, home = os2.homedir()) {
1904
- if (env.SESSIONPIPE_CLAUDE && isExe(env.SESSIONPIPE_CLAUDE)) return env.SESSIONPIPE_CLAUDE;
1905
- const names = process.platform === "win32" ? ["claude.exe", "claude.cmd"] : ["claude"];
1906
- for (const d of String(env.PATH || "").split(path8.delimiter)) {
1907
- if (!d || !path8.isAbsolute(d)) continue;
1908
- for (const n of names) if (isExe(path8.join(d, n))) return path8.join(d, n);
1909
- }
1910
- for (const p of [
1911
- path8.join(home, ".claude", "local", "claude"),
1912
- path8.join(home, ".local", "bin", "claude"),
1913
- "/opt/homebrew/bin/claude",
1914
- "/usr/local/bin/claude"
1915
- ])
1916
- if (isExe(p)) return p;
1917
- const app = path8.join(home, "Library", "Application Support", "Claude", "claude-code");
1918
- let versions = [];
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
- versions = readdirSync5(app).filter((n) => /^\d+(\.\d+)*$/.test(n)).sort(cmpVersion).reverse();
2399
+ j = JSON.parse(line);
1921
2400
  } catch {
2401
+ return null;
1922
2402
  }
1923
- for (const v of versions) {
1924
- const p = path8.join(app, v, "claude.app", "Contents", "MacOS", "claude");
1925
- if (isExe(p)) return p;
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
- function modeFlags(mode, caps) {
1951
- if (mode === "auto") {
1952
- if (!caps.modes.includes("auto"))
1953
- return { error: "this machine is in auto mode, but its Claude Code has no auto permission mode" };
1954
- return { args: ["--permission-mode", "auto"] };
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
- if (caps.modes.includes("dontAsk")) return { args: ["--permission-mode", "dontAsk"] };
1957
- const args = [];
1958
- if (caps.hasMode)
1959
- args.push(
1960
- "--permission-mode",
1961
- caps.modes.includes("manual") && !caps.modes.includes("default") ? "manual" : "default"
1962
- );
1963
- if (caps.promptsNone) args.push("--permission-prompts", "none");
1964
- return { args };
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
- function claudeEnv(configDir, env = process.env) {
1967
- const e = { ...env };
1968
- for (const k of [
1969
- "CLAUDECODE",
1970
- "CLAUDE_CODE_SESSION_ID",
1971
- "CLAUDE_CODE_HOST_SESSION_ID",
1972
- "CLAUDE_CODE_ENTRYPOINT",
1973
- "CLAUDE_CODE_SESSION_ATTENDED",
1974
- "CLAUDE_CODE_SSE_PORT"
1975
- ])
1976
- delete e[k];
1977
- e.SESSIONPIPE_CONTROL_JOB = "1";
1978
- if (configDir !== void 0) {
1979
- if (path8.resolve(configDir) === path8.resolve(os2.homedir(), ".claude")) delete e.CLAUDE_CONFIG_DIR;
1980
- else e.CLAUDE_CONFIG_DIR = configDir;
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
- function parseAuthStatus(stdout) {
1985
- const text = String(stdout || "").trim();
1986
- const start = text.indexOf("{");
1987
- if (start < 0) return { loggedIn: null };
2491
+ var ctorCache;
2492
+ function dbCtor() {
2493
+ if (ctorCache !== void 0) return ctorCache;
2494
+ ctorCache = null;
1988
2495
  try {
1989
- const j = JSON.parse(text.slice(start));
1990
- if (typeof j.loggedIn !== "boolean") return { loggedIn: null };
1991
- return {
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 claudeLogin(bin, configDir, env = process.env) {
2001
- return new Promise((resolve) => {
2002
- execFile(
2003
- bin,
2004
- ["auth", "status", "--json"],
2005
- { encoding: "utf8", timeout: 2e4, env: claudeEnv(configDir, env), maxBuffer: 256 * 1024 },
2006
- // `auth status` may exit non-zero when signed out; its JSON still says so.
2007
- (_err, stdout) => resolve(parseAuthStatus(stdout))
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
- function detectCaps(bin) {
2024
- let help = "";
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
- help = execFileSync4(bin, ["--help"], {
2027
- encoding: "utf8",
2028
- timeout: 3e4,
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
- return parseCaps(help);
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
- projects = readdirSync5(path8.join(dir, "projects"), { withFileTypes: true });
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
- return null;
2057
- }
2058
- var projectName = (cwd) => cwd.replace(/[^a-zA-Z0-9]/g, "-");
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
- j = JSON.parse(line.toString("utf8"));
2541
+ db.close();
2068
2542
  } catch {
2069
- return null;
2070
2543
  }
2071
- if (!j || typeof j.cwd !== "string" || !path8.isAbsolute(j.cwd)) return null;
2072
- if (projectName(j.cwd) === t.project) return j.cwd;
2073
- first ??= j.cwd;
2074
- count++;
2075
- return null;
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
- fd = openSync2(t.file, "r");
2079
- const buf = Buffer.alloc(256 * 1024);
2080
- let pos = 0;
2081
- let rest = Buffer.alloc(0);
2082
- let skipping = false;
2083
- while (pos < cap2 && count < 50) {
2084
- const n = readSync2(fd, buf, 0, buf.length, pos);
2085
- if (!n) {
2086
- if (!skipping && rest.length) {
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
- if (chunk.length > 2 * 1024 * 1024) {
2106
- skipping = true;
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
- } finally {
2112
- if (fd !== void 0)
2113
- try {
2114
- closeSync2(fd);
2115
- } catch {
2116
- }
2568
+ return null;
2117
2569
  }
2118
- return first;
2119
2570
  }
2120
- function pidAlive(pid) {
2121
- if (!Number.isSafeInteger(pid) || pid <= 1) return false;
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
- process.kill(pid, 0);
2124
- return true;
2125
- } catch (e) {
2126
- return e.code === "EPERM";
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 liveProcess(session, configDir) {
2130
- let names = [];
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
- names = readdirSync5(path8.join(configDir, "sessions")).filter((n) => /^\d+\.json$/.test(n));
2620
+ if (!existsSync5(file)) return null;
2133
2621
  } catch {
2134
- return false;
2622
+ return null;
2135
2623
  }
2136
- for (const n of names) {
2137
- try {
2138
- const j = JSON.parse(readFileSync6(path8.join(configDir, "sessions", n), "utf8"));
2139
- if (String(j.sessionId) !== session) continue;
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 false;
2629
+ return null;
2145
2630
  }
2146
- function recentlyWritten(file, ourLastEnd, now = Date.now()) {
2147
- let m;
2148
- try {
2149
- m = statSync4(file).mtimeMs;
2150
- } catch {
2151
- return false;
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
- if (now - m > LIVE_WRITE_MS) return false;
2154
- return !(ourLastEnd && m <= ourLastEnd + 2e3);
2672
+ return void 0;
2155
2673
  }
2156
- function runClaude(bin, args, o) {
2157
- return new Promise((resolve) => {
2158
- let out = [];
2159
- let outLen = 0;
2160
- let tooBig = false;
2161
- let errTail = "";
2162
- let timedOut = false;
2163
- let settled = false;
2164
- let child;
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
- child = spawn(bin, args, {
2167
- cwd: o.cwd,
2168
- env: o.env,
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 kill = (sig) => {
2178
- try {
2179
- if (process.platform !== "win32" && child.pid) process.kill(-child.pid, sig);
2180
- else child.kill(sig);
2181
- } catch {
2182
- try {
2183
- child.kill(sig);
2184
- } catch {
2185
- }
2186
- }
2187
- };
2188
- let partial = "";
2189
- child.stdout?.on("data", (b) => {
2190
- if (o.onLine) {
2191
- const chunk = partial + b.toString("utf8");
2192
- const lines = chunk.split("\n");
2193
- partial = lines.pop() ?? "";
2194
- for (const line of lines) {
2195
- if (!line.trim()) continue;
2196
- try {
2197
- o.onLine(line);
2198
- } catch {
2199
- }
2200
- if (line.includes('"type":"result"')) {
2201
- out = [Buffer.from(line)];
2202
- outLen = line.length;
2203
- }
2204
- }
2205
- if (partial.length > OUT_CAP) partial = "";
2206
- return;
2207
- }
2208
- if (tooBig) return;
2209
- if (outLen + b.length > OUT_CAP) {
2210
- tooBig = true;
2211
- out = [];
2212
- return;
2213
- }
2214
- out.push(b);
2215
- outLen += b.length;
2216
- });
2217
- child.stderr?.on("data", (b) => {
2218
- errTail = (errTail + b.toString("utf8")).slice(-4e3);
2219
- });
2220
- const timer = setTimeout(() => {
2221
- timedOut = true;
2222
- kill("SIGTERM");
2223
- setTimeout(() => kill("SIGKILL"), 1e4).unref();
2224
- }, o.timeoutMs ?? JOB_TIMEOUT_MS);
2225
- const result = (code, signal) => ({
2226
- code,
2227
- signal,
2228
- stdout: tooBig ? null : Buffer.concat(out).toString("utf8"),
2229
- tooBig,
2230
- stderr: errTail,
2231
- timedOut
2232
- });
2233
- const finish = (r) => {
2234
- if (settled) return;
2235
- settled = true;
2236
- clearTimeout(timer);
2237
- resolve(r);
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
- child.on("error", (e) => finish({ error: e.message }));
2240
- child.on("exit", (code, signal) => setTimeout(() => finish(result(code, signal)), 2e3).unref());
2241
- child.on("close", (code, signal) => {
2242
- if (o.onLine && partial.trim()) {
2243
- try {
2244
- o.onLine(partial);
2245
- } catch {
2246
- }
2247
- if (partial.includes('"type":"result"')) {
2248
- out = [Buffer.from(partial)];
2249
- outLen = partial.length;
2250
- }
2251
- partial = "";
2252
- }
2253
- finish(result(code, signal));
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
- function parseResult(stdout) {
2258
- const s = String(stdout || "").trim();
2259
- if (!s) return null;
2260
- const pick = (j) => {
2261
- if (Array.isArray(j)) {
2262
- for (let i = j.length - 1; i >= 0; i--) if (j[i]?.type === "result") return j[i];
2263
- return null;
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
- return j && typeof j === "object" ? j : null;
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
- return pick(JSON.parse(s));
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
- const lines = s.split("\n");
2272
- for (let i = lines.length - 1; i >= 0; i--) {
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 = pick(JSON.parse(lines[i]));
2275
- if (j && (j.type === "result" || "result" in j)) return 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
- return null;
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 existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
2289
- import path9 from "node:path";
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(path9.dirname(file), { recursive: true, mode: 448 });
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" && existsSync6(file)) {
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 = (lastRand[i] ?? 0) + 1;
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
- const b = randomBytes(16);
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 existsSync7,
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 statSync5,
3181
+ statSync as statSync6,
2383
3182
  unlinkSync as unlinkSync3,
2384
3183
  writeFileSync as writeFileSync5
2385
3184
  } from "node:fs";
2386
- import path10 from "node:path";
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 path10.join(this.root, "outbox", safeId(harness));
3194
+ return path14.join(this.root, "outbox", safeId(harness));
2394
3195
  }
2395
3196
  file(ref) {
2396
- return path10.join(this.dir(ref.harness), `${safeId(ref.session)}.jsonl`);
3197
+ return path14.join(this.dir(ref.harness), `${safeId(ref.session)}.jsonl`);
2397
3198
  }
2398
3199
  seqFile(ref) {
2399
- return path10.join(this.dir(ref.harness), `${safeId(ref.session)}.seq`);
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 = 0;
3206
+ let n = -1;
2406
3207
  try {
2407
- n = Number(readFileSync8(f, "utf8")) || 0;
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
- mkdirSync5(path10.dirname(f), { recursive: true, mode: 448 });
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 (!existsSync7(f)) return -1;
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 = path10.join(this.root, "outbox");
2438
- if (!existsSync7(base)) return [];
3239
+ const base = path14.join(this.root, "outbox");
3240
+ if (!existsSync9(base)) return [];
2439
3241
  for (const h of readdirSync6(base)) {
2440
- const d = path10.join(base, h);
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: statSync5(path10.join(d, n)).mtimeMs });
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 (!existsSync7(f)) return { events: [], offset: from };
2461
- const size = statSync5(f).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 pos = 0;
2471
- while (events.length < maxEvents) {
2472
- const nl = text.indexOf("\n", pos);
2473
- if (nl < 0) break;
2474
- const line = text.slice(pos, nl);
2475
- pos = nl + 1;
2476
- offset = from + Buffer.byteLength(text.slice(0, pos), "utf8");
2477
- if (!line.trim()) continue;
2478
- try {
2479
- events.push(JSON.parse(line));
2480
- } catch {
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 (statSync5(f).mtimeMs < cutoff) {
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 path10.join(this.root, "cursors", `${safeId(this.sink)}.json`);
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(path10.dirname(f), { recursive: true, mode: 448 });
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
- const dir = path10.join(root, "locks");
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 = path10.join(dir, `${safeId(ref.harness)}-${safeId(ref.session)}.lock`);
3365
+ const f = path14.join(dir, `${name}.lock`);
2549
3366
  try {
2550
- if (Date.now() - statSync5(f).mtimeMs > staleMs) unlinkSync3(f);
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 createHash2 } from "node:crypto";
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 createHash2("sha256").update(name).digest("hex").slice(0, 8);
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, (m, head, v) => v.includes(REDACTED) ? m : `${head}${REDACTED}`);
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") data.assistant = cap(data.assistant, MAX_SIDE);
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 path11 from "node:path";
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(path11.dirname(this.file), { recursive: true });
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
- for (const e of events) this.write(JSON.stringify(e));
2906
- return { ok: true, acknowledged: events.length, accepted: events.length, duplicates: 0, rejected: [] };
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
- HOME,
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