sessionpipe 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // sessionpipe 0.6.1 · Apache-2.0 · https://sessionpipe.org
2
+ // sessionpipe 0.7.0 · Apache-2.0 · https://sessionpipe.org
3
3
  var __defProp = Object.defineProperty;
4
4
  var __export = (target, all) => {
5
5
  for (var name in all)
@@ -8,9 +8,9 @@ var __export = (target, all) => {
8
8
 
9
9
  // src/cli.ts
10
10
  import { execFileSync as execFileSync7, spawnSync as spawnSync2 } from "node:child_process";
11
- import { mkdtempSync, readdirSync as readdirSync8, readFileSync as readFileSync13, realpathSync as realpathSync6, rmSync as rmSync2, statSync as statSync9, unlinkSync as unlinkSync7 } from "node:fs";
12
- import os9 from "node:os";
13
- import path23 from "node:path";
11
+ import { mkdtempSync, readdirSync as readdirSync8, readFileSync as readFileSync15, realpathSync as realpathSync6, rmSync as rmSync2, statSync as statSync10, unlinkSync as unlinkSync7 } from "node:fs";
12
+ import os11 from "node:os";
13
+ import path27 from "node:path";
14
14
  import { fileURLToPath } from "node:url";
15
15
 
16
16
  // ../core/src/adapters/antigravity.ts
@@ -199,6 +199,9 @@ function readClaude(lines, from) {
199
199
  }
200
200
  return pairTurns(msgs, endOf(lines));
201
201
  }
202
+ function codexInjected(text) {
203
+ return /^<[a-z_-]+>/i.test(text) || /^# AGENTS\.md instructions\b/i.test(text);
204
+ }
202
205
  function readCodex(lines, from) {
203
206
  const msgs = [];
204
207
  for (let i = from; i < lines.length; i++) {
@@ -210,7 +213,7 @@ function readCodex(lines, from) {
210
213
  const text = textParts(p.content, ["input_text", "output_text"]).trim();
211
214
  if (!text) continue;
212
215
  if (p.role === "user") {
213
- if (/^<[a-z_-]+>/i.test(text)) continue;
216
+ if (codexInjected(text)) continue;
214
217
  msgs.push({ role: "user", text, at, line: i });
215
218
  } else if (p.role === "assistant") msgs.push({ role: "assistant", text, at, line: i });
216
219
  }
@@ -657,11 +660,288 @@ function antigravityFacts(file, opts) {
657
660
  return out2;
658
661
  }
659
662
 
663
+ // ../core/src/adapters/antigravity-control.ts
664
+ var antigravity_control_exports = {};
665
+ __export(antigravity_control_exports, {
666
+ AgyReading: () => AgyReading,
667
+ agyArgs: () => agyArgs,
668
+ agyEnv: () => agyEnv,
669
+ agyError: () => agyError,
670
+ agyLine: () => agyLine,
671
+ agyModeFlags: () => agyModeFlags,
672
+ findAgy: () => findAgy,
673
+ reading: () => reading
674
+ });
675
+ import os2 from "node:os";
676
+ import path6 from "node:path";
677
+
678
+ // ../core/src/adapters/headless.ts
679
+ import { spawn } from "node:child_process";
680
+ import { accessSync, constants, statSync as statSync3 } from "node:fs";
681
+ import path5 from "node:path";
682
+ var JOB_TIMEOUT_MS = 30 * 6e4;
683
+ var OUT_CAP = 1024 * 1024;
684
+ function isExe(p) {
685
+ try {
686
+ if (!statSync3(p).isFile()) return false;
687
+ accessSync(p, constants.X_OK);
688
+ return true;
689
+ } catch {
690
+ return false;
691
+ }
692
+ }
693
+ function findBinary(o) {
694
+ if (o.override && isExe(o.override)) return o.override;
695
+ for (const d of String(o.env?.PATH || "").split(path5.delimiter)) {
696
+ if (!d || !path5.isAbsolute(d)) continue;
697
+ for (const n of o.names) if (isExe(path5.join(d, n))) return path5.join(d, n);
698
+ }
699
+ for (const d of o.dirs) for (const n of o.names) if (isExe(path5.join(d, n))) return path5.join(d, n);
700
+ return null;
701
+ }
702
+ function jobEnv(env = process.env) {
703
+ return { ...env, SESSIONPIPE_CONTROL_JOB: "1" };
704
+ }
705
+ function runHeadless(bin, args2, o) {
706
+ return new Promise((resolve) => {
707
+ let out2 = [];
708
+ let outLen = 0;
709
+ let tooBig = false;
710
+ let errTail = "";
711
+ let timedOut = false;
712
+ let settled = false;
713
+ let child;
714
+ try {
715
+ child = spawn(bin, args2, {
716
+ cwd: o.cwd,
717
+ env: o.env,
718
+ stdio: ["ignore", "pipe", "pipe"],
719
+ detached: process.platform !== "win32",
720
+ windowsHide: true
721
+ });
722
+ } catch (e) {
723
+ resolve({ error: e.message });
724
+ return;
725
+ }
726
+ const kill = (sig) => {
727
+ try {
728
+ if (process.platform !== "win32" && child.pid) process.kill(-child.pid, sig);
729
+ else child.kill(sig);
730
+ } catch {
731
+ try {
732
+ child.kill(sig);
733
+ } catch {
734
+ }
735
+ }
736
+ };
737
+ const keep = (line) => {
738
+ if (!o.keep?.(line)) return;
739
+ out2 = [Buffer.from(line)];
740
+ outLen = line.length;
741
+ };
742
+ let partial = "";
743
+ child.stdout?.on("data", (b) => {
744
+ if (o.onLine) {
745
+ const chunk = partial + b.toString("utf8");
746
+ const lines = chunk.split("\n");
747
+ partial = lines.pop() ?? "";
748
+ for (const line of lines) {
749
+ if (!line.trim()) continue;
750
+ try {
751
+ o.onLine(line);
752
+ } catch {
753
+ }
754
+ keep(line);
755
+ }
756
+ if (partial.length > OUT_CAP) partial = "";
757
+ return;
758
+ }
759
+ if (tooBig) return;
760
+ if (outLen + b.length > OUT_CAP) {
761
+ tooBig = true;
762
+ out2 = [];
763
+ return;
764
+ }
765
+ out2.push(b);
766
+ outLen += b.length;
767
+ });
768
+ child.stderr?.on("data", (b) => {
769
+ errTail = (errTail + b.toString("utf8")).slice(-4e3);
770
+ });
771
+ const timer = setTimeout(() => {
772
+ timedOut = true;
773
+ kill("SIGTERM");
774
+ setTimeout(() => kill("SIGKILL"), 1e4).unref();
775
+ }, o.timeoutMs ?? JOB_TIMEOUT_MS);
776
+ const result = (code, signal) => ({
777
+ code,
778
+ signal,
779
+ stdout: tooBig ? null : Buffer.concat(out2).toString("utf8"),
780
+ tooBig,
781
+ stderr: errTail,
782
+ timedOut
783
+ });
784
+ const finish = (r) => {
785
+ if (settled) return;
786
+ settled = true;
787
+ clearTimeout(timer);
788
+ resolve(r);
789
+ };
790
+ child.on("error", (e) => finish({ error: e.message }));
791
+ child.on("exit", (code, signal) => setTimeout(() => finish(result(code, signal)), 2e3).unref());
792
+ child.on("close", (code, signal) => {
793
+ if (o.onLine && partial.trim()) {
794
+ try {
795
+ o.onLine(partial);
796
+ } catch {
797
+ }
798
+ keep(partial);
799
+ partial = "";
800
+ }
801
+ finish(result(code, signal));
802
+ });
803
+ });
804
+ }
805
+ function stderrTail(stderr, n = 3) {
806
+ return String(stderr || "").trim().split("\n").slice(-n).join(" ").trim();
807
+ }
808
+ function deniedNote(names, count) {
809
+ if (!count) return "";
810
+ return `
811
+
812
+ (Not allowed on this machine, since nobody was there to approve: ${names.join(", ") || `${count} action(s)`}.)`;
813
+ }
814
+ function runFailure(label, res) {
815
+ if (res.error) return `couldn't start ${label}: ${res.error}`;
816
+ if (res.timedOut) return `${label} was still working after 30 minutes, so it was stopped`;
817
+ return null;
818
+ }
819
+
820
+ // ../core/src/adapters/antigravity-control.ts
821
+ var LABEL = "Antigravity";
822
+ function findAgy(env = process.env, home = os2.homedir()) {
823
+ return findBinary({
824
+ override: env.SESSIONPIPE_AGY,
825
+ names: process.platform === "win32" ? ["agy.exe", "agy.cmd"] : ["agy"],
826
+ env,
827
+ dirs: [path6.join(home, ".local", "bin"), "/opt/homebrew/bin", "/usr/local/bin"]
828
+ });
829
+ }
830
+ function agyModeFlags(mode) {
831
+ return mode === "auto" ? ["--mode", "accept-edits"] : [];
832
+ }
833
+ function agyArgs(o) {
834
+ const prompt = o.prompt.startsWith("-") ? ` ${o.prompt}` : o.prompt;
835
+ return [
836
+ ...o.resume ? ["--conversation", o.resume] : [],
837
+ "-p",
838
+ prompt,
839
+ "--output-format",
840
+ "stream-json",
841
+ ...agyModeFlags(o.mode)
842
+ ];
843
+ }
844
+ var agyEnv = (env = process.env) => jobEnv(env);
845
+ function agyLine(line) {
846
+ let j;
847
+ try {
848
+ j = JSON.parse(line);
849
+ } catch {
850
+ return null;
851
+ }
852
+ if (!j || typeof j !== "object") return null;
853
+ const str = (v) => typeof v === "string" ? v : "";
854
+ const out2 = {};
855
+ if (j.event === "init") {
856
+ if (str(j.conversation_id)) out2.session = str(j.conversation_id);
857
+ } else if (j.event === "step_update") {
858
+ const s = j.step_update ?? {};
859
+ if (str(s.conversation_id)) out2.session = str(s.conversation_id);
860
+ if (s.step_type === "agent_response" && str(s.text_delta))
861
+ out2.delta = { step: Number(s.step_index ?? 0), text: str(s.text_delta) };
862
+ if (s.step_type === "error_message") {
863
+ const why = str(s.text_delta) || str(s.error) || str(s.message);
864
+ if (why) out2.note = why;
865
+ }
866
+ } else if (j.event === "result") {
867
+ const r = j.result ?? {};
868
+ if (str(r.conversation_id)) out2.session = str(r.conversation_id);
869
+ out2.result = {
870
+ status: str(r.status),
871
+ response: str(r.response),
872
+ error: str(r.error),
873
+ denied: Array.isArray(r.denied_actions) ? r.denied_actions : []
874
+ };
875
+ } else return null;
876
+ return Object.keys(out2).length ? out2 : null;
877
+ }
878
+ function deniedName(d) {
879
+ if (typeof d === "string") return d.slice(0, 80);
880
+ if (!d || typeof d !== "object") return null;
881
+ const o = d;
882
+ for (const k of ["tool_name", "toolName", "name", "tool", "action", "type"])
883
+ if (typeof o[k] === "string" && o[k]) return o[k].slice(0, 80);
884
+ return null;
885
+ }
886
+ function agyError(stderr) {
887
+ const s = String(stderr || "");
888
+ const i = s.lastIndexOf("AGY_ERROR:");
889
+ if (i < 0) return "";
890
+ const raw = s.slice(i + "AGY_ERROR:".length).trim();
891
+ try {
892
+ const j = JSON.parse(raw);
893
+ for (const k of ["message", "error", "detail"]) if (typeof j[k] === "string" && j[k]) return j[k];
894
+ } catch {
895
+ }
896
+ return raw.split("\n")[0]?.slice(0, 300) ?? "";
897
+ }
898
+ var AgyReading = class {
899
+ session = null;
900
+ lines = 0;
901
+ buf = "";
902
+ step = null;
903
+ result = null;
904
+ note = null;
905
+ get text() {
906
+ return this.buf;
907
+ }
908
+ feed(line) {
909
+ this.lines++;
910
+ const p = agyLine(line);
911
+ if (!p) return false;
912
+ if (p.session && !this.session) this.session = p.session;
913
+ if (p.note) this.note = p.note;
914
+ if (p.result) this.result = p.result;
915
+ if (!p.delta) return false;
916
+ if (this.step !== null && p.delta.step !== this.step && this.buf && !this.buf.endsWith("\n\n")) this.buf += "\n\n";
917
+ this.step = p.delta.step;
918
+ this.buf += p.delta.text;
919
+ return true;
920
+ }
921
+ outcome(res) {
922
+ const broke = runFailure(LABEL, res);
923
+ if (broke) return { failed: broke };
924
+ const r = this.result;
925
+ if (!r) {
926
+ const why = agyError(res.stderr) || this.note || "";
927
+ return {
928
+ failed: `Antigravity exited (${res.signal || `code ${res.code}`}) without an answer${why ? `: ${why}` : ""}`
929
+ };
930
+ }
931
+ if (r.status !== "SUCCESS")
932
+ return { failed: r.error || agyError(res.stderr) || this.note || "Antigravity reported an error" };
933
+ const names = [...new Set(r.denied.map(deniedName).filter((n) => !!n))];
934
+ const text = r.response.trim() ? r.response.trim() : this.buf.trim() || "(Antigravity finished without a written reply.)";
935
+ return { reply: text + deniedNote(names, r.denied.length) };
936
+ }
937
+ };
938
+ var reading = () => new AgyReading();
939
+
660
940
  // ../core/src/adapters/claude-code.ts
661
941
  import { execFileSync as execFileSync2 } from "node:child_process";
662
942
  import { createHash } from "node:crypto";
663
- import { existsSync as existsSync3, readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as statSync3 } from "node:fs";
664
- import path5 from "node:path";
943
+ import { existsSync as existsSync3, readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as statSync4 } from "node:fs";
944
+ import path7 from "node:path";
665
945
 
666
946
  // ../core/src/readers/git.ts
667
947
  import { execFileSync } from "node:child_process";
@@ -728,23 +1008,23 @@ function claudeDirs(env = process.env, home = env.HOME || HOME) {
728
1008
  const out2 = [];
729
1009
  const add = (d) => {
730
1010
  if (!d) return;
731
- const r = path5.resolve(d);
1011
+ const r = path7.resolve(d);
732
1012
  if (!out2.includes(r)) out2.push(r);
733
1013
  };
734
- add(path5.join(home, ".claude"));
1014
+ add(path7.join(home, ".claude"));
735
1015
  add(env.CLAUDE_CONFIG_DIR);
736
1016
  try {
737
1017
  for (const e of readdirSync3(home, { withFileTypes: true })) {
738
1018
  if (!e.isDirectory() || !/^\.claude[-_.]/.test(e.name)) continue;
739
- const d = path5.join(home, e.name);
740
- if (existsSync3(path5.join(d, "projects")) || existsSync3(path5.join(d, "settings.json"))) add(d);
1019
+ const d = path7.join(home, e.name);
1020
+ if (existsSync3(path7.join(d, "projects")) || existsSync3(path7.join(d, "settings.json"))) add(d);
741
1021
  }
742
1022
  } catch {
743
1023
  }
744
1024
  return out2.filter((d, i) => i === 0 || existsSync3(d));
745
1025
  }
746
- var settingsIn = (dir) => path5.join(dir, "settings.json");
747
- var configDirOf = (transcript) => path5.dirname(path5.dirname(path5.dirname(transcript)));
1026
+ var settingsIn = (dir) => path7.join(dir, "settings.json");
1027
+ var configDirOf = (transcript) => path7.dirname(path7.dirname(path7.dirname(transcript)));
748
1028
  var validTool2 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
749
1029
  var endReason = (r) => r === "clear" || r === "logout" || r === "exit" ? r : r === "prompt_input_exit" ? "exit" : "other";
750
1030
  var notifKind = (t) => t === "permission_prompt" ? "permission" : t === "idle_prompt" ? "idle" : t === "elicitation_dialog" ? "elicitation" : "question";
@@ -916,11 +1196,11 @@ var claudeCode = {
916
1196
  const rows = [];
917
1197
  const files = [];
918
1198
  for (const d of claudeDirs(env))
919
- files.push(...recentFiles(path5.join(d, "projects"), 1, sinceMs, (n) => /^[0-9a-f-]{36}\.jsonl$/.test(n)));
1199
+ files.push(...recentFiles(path7.join(d, "projects"), 1, sinceMs, (n) => /^[0-9a-f-]{36}\.jsonl$/.test(n)));
920
1200
  const noState = { get: () => ({}), save: () => {
921
1201
  } };
922
1202
  for (const f of files) {
923
- const id = path5.basename(f, ".jsonl");
1203
+ const id = path7.basename(f, ".jsonl");
924
1204
  const facts = claudeFacts(f, id, {}, noState);
925
1205
  if (!facts.started_at || !facts.last_at) continue;
926
1206
  const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
@@ -980,12 +1260,12 @@ function claudeLive(dir) {
980
1260
  const map = /* @__PURE__ */ new Map();
981
1261
  let names = [];
982
1262
  try {
983
- names = readdirSync3(path5.join(dir, "sessions")).filter((n) => n.endsWith(".json"));
1263
+ names = readdirSync3(path7.join(dir, "sessions")).filter((n) => n.endsWith(".json"));
984
1264
  } catch {
985
1265
  }
986
1266
  for (const n of names) {
987
1267
  try {
988
- const e = registryEntry(JSON.parse(readFileSync3(path5.join(dir, "sessions", n), "utf8")));
1268
+ const e = registryEntry(JSON.parse(readFileSync3(path7.join(dir, "sessions", n), "utf8")));
989
1269
  if (e?.sessionId) map.set(e.sessionId, e);
990
1270
  } catch {
991
1271
  }
@@ -996,7 +1276,7 @@ function claudeLive(dir) {
996
1276
  function claudeLiveByPid(dir, pid) {
997
1277
  if (!pid) return null;
998
1278
  try {
999
- return registryEntry(JSON.parse(readFileSync3(path5.join(dir, "sessions", `${pid}.json`), "utf8")));
1279
+ return registryEntry(JSON.parse(readFileSync3(path7.join(dir, "sessions", `${pid}.json`), "utf8")));
1000
1280
  } catch {
1001
1281
  return null;
1002
1282
  }
@@ -1004,7 +1284,7 @@ function claudeLiveByPid(dir, pid) {
1004
1284
  function claudePid(env = process.env) {
1005
1285
  try {
1006
1286
  const dirs = claudeDirs(env);
1007
- const has2 = (pid2) => dirs.some((d) => existsSync3(path5.join(d, "sessions", `${pid2}.json`)));
1287
+ const has2 = (pid2) => dirs.some((d) => existsSync3(path7.join(d, "sessions", `${pid2}.json`)));
1008
1288
  let pid = process.ppid;
1009
1289
  if (has2(pid)) return pid;
1010
1290
  const parent = parentOf();
@@ -1047,8 +1327,8 @@ var accountCache = /* @__PURE__ */ new Map();
1047
1327
  function claudeAccount(dir) {
1048
1328
  const hit = accountCache.get(dir);
1049
1329
  if (hit !== void 0) return hit;
1050
- const files = [path5.join(dir, ".claude.json")];
1051
- if (path5.resolve(dir) === path5.join(HOME, ".claude")) files.unshift(path5.join(HOME, ".claude.json"));
1330
+ const files = [path7.join(dir, ".claude.json")];
1331
+ if (path7.resolve(dir) === path7.join(HOME, ".claude")) files.unshift(path7.join(HOME, ".claude.json"));
1052
1332
  let acct = null;
1053
1333
  for (const f of files) {
1054
1334
  try {
@@ -1100,7 +1380,7 @@ function scanTitles(file, from, found) {
1100
1380
  const out2 = { custom: found.custom ?? null, ai: found.ai ?? null, upto: from };
1101
1381
  let size = 0;
1102
1382
  try {
1103
- size = statSync3(file).size;
1383
+ size = statSync4(file).size;
1104
1384
  } catch {
1105
1385
  return out2;
1106
1386
  }
@@ -1133,7 +1413,7 @@ function claudeFacts(file, sessionId, hints, state2) {
1133
1413
  const H = parseLines(head);
1134
1414
  const T = parseLines(tail2);
1135
1415
  const out2 = {};
1136
- const id = sessionId || path5.basename(file, ".jsonl");
1416
+ const id = sessionId || path7.basename(file, ".jsonl");
1137
1417
  const mem = state2.get(NAME2, id);
1138
1418
  let custom = null;
1139
1419
  let ai = null;
@@ -1143,7 +1423,7 @@ function claudeFacts(file, sessionId, hints, state2) {
1143
1423
  }
1144
1424
  let size = 0;
1145
1425
  try {
1146
- size = statSync3(file).size;
1426
+ size = statSync4(file).size;
1147
1427
  } catch {
1148
1428
  }
1149
1429
  if (!custom && !ai && size > 64 * 1024 + 256 * 1024) {
@@ -1213,1088 +1493,1121 @@ function claudeFacts(file, sessionId, hints, state2) {
1213
1493
  return out2;
1214
1494
  }
1215
1495
 
1216
- // ../core/src/adapters/codex.ts
1217
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
1218
- import path6 from "node:path";
1219
-
1220
- // ../core/src/adapters/codex-config.ts
1221
- var NOTIFY_LINE = /^[ \t]*notify[ \t]*=[ \t]*(\[.*\])[ \t]*(?:#.*)?$/m;
1222
- var NOTIFY_KEY = /^[ \t]*notify[ \t]*=/m;
1223
- var OURS2 = /(?:hook\.js|sessionpipe-hook)",\s*"codex",\s*"notify"/;
1224
- function scan(line, st) {
1225
- let i = 0;
1226
- while (i < line.length) {
1227
- if (st.str) {
1228
- const j = line.indexOf(st.str, i);
1229
- if (j < 0) return;
1230
- i = j + 3;
1231
- st.str = null;
1232
- continue;
1233
- }
1234
- const ch = line[i];
1235
- if (ch === "#") return;
1236
- if (line.startsWith('"""', i) || line.startsWith("'''", i)) {
1237
- st.str = line.slice(i, i + 3);
1238
- i += 3;
1239
- continue;
1240
- }
1241
- if (ch === '"') {
1242
- i++;
1243
- while (i < line.length && line[i] !== '"') i += line[i] === "\\" ? 2 : 1;
1244
- i++;
1245
- continue;
1246
- }
1247
- if (ch === "'") {
1248
- const j = line.indexOf("'", i + 1);
1249
- i = j < 0 ? line.length : j + 1;
1250
- continue;
1251
- }
1252
- if (ch === "[") st.depth++;
1253
- else if (ch === "]") st.depth = Math.max(0, st.depth - 1);
1254
- i++;
1496
+ // ../core/src/adapters/claude-code-control.ts
1497
+ var claude_code_control_exports = {};
1498
+ __export(claude_code_control_exports, {
1499
+ JOB_TIMEOUT_MS: () => JOB_TIMEOUT_MS,
1500
+ claudeEnv: () => claudeEnv,
1501
+ claudeLogin: () => claudeLogin,
1502
+ detectCaps: () => detectCaps,
1503
+ findClaude: () => findClaude,
1504
+ findTranscript: () => findTranscript,
1505
+ isUuid: () => isUuid,
1506
+ liveProcess: () => liveProcess,
1507
+ loginCommand: () => loginCommand,
1508
+ loginHint: () => loginHint,
1509
+ modeFlags: () => modeFlags,
1510
+ parseAuthStatus: () => parseAuthStatus,
1511
+ parseCaps: () => parseCaps,
1512
+ parseResult: () => parseResult,
1513
+ recentlyWritten: () => recentlyWritten,
1514
+ runClaude: () => runClaude,
1515
+ sessionFolder: () => sessionFolder
1516
+ });
1517
+ import { execFile, execFileSync as execFileSync3 } from "node:child_process";
1518
+ import { closeSync as closeSync2, openSync as openSync2, readdirSync as readdirSync4, readFileSync as readFileSync4, readSync as readSync2, statSync as statSync5 } from "node:fs";
1519
+ import os3 from "node:os";
1520
+ import path8 from "node:path";
1521
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1522
+ var LIVE_WRITE_MS = 9e4;
1523
+ var cmpVersion = (a, b) => {
1524
+ const pa = a.split(".").map(Number);
1525
+ const pb = b.split(".").map(Number);
1526
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++)
1527
+ if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) - (pb[i] ?? 0);
1528
+ return 0;
1529
+ };
1530
+ function findClaude(env = process.env, home = os3.homedir()) {
1531
+ if (env.SESSIONPIPE_CLAUDE && isExe(env.SESSIONPIPE_CLAUDE)) return env.SESSIONPIPE_CLAUDE;
1532
+ const names = process.platform === "win32" ? ["claude.exe", "claude.cmd"] : ["claude"];
1533
+ for (const d of String(env.PATH || "").split(path8.delimiter)) {
1534
+ if (!d || !path8.isAbsolute(d)) continue;
1535
+ for (const n of names) if (isExe(path8.join(d, n))) return path8.join(d, n);
1255
1536
  }
1256
- }
1257
- function firstTableAt(text) {
1258
- const st = { depth: 0, str: null };
1259
- let pos = 0;
1260
- for (const line of text.split("\n")) {
1261
- if (!st.str && st.depth === 0 && /^[ \t]*\[/.test(line)) return pos;
1262
- scan(line, st);
1263
- pos += line.length + 1;
1537
+ for (const p of [
1538
+ path8.join(home, ".claude", "local", "claude"),
1539
+ path8.join(home, ".local", "bin", "claude"),
1540
+ "/opt/homebrew/bin/claude",
1541
+ "/usr/local/bin/claude"
1542
+ ])
1543
+ if (isExe(p)) return p;
1544
+ const app = path8.join(home, "Library", "Application Support", "Claude", "claude-code");
1545
+ let versions = [];
1546
+ try {
1547
+ versions = readdirSync4(app).filter((n) => /^\d+(\.\d+)*$/.test(n)).sort(cmpVersion).reverse();
1548
+ } catch {
1264
1549
  }
1265
- return -1;
1550
+ for (const v of versions) {
1551
+ const p = path8.join(app, v, "claude.app", "Contents", "MacOS", "claude");
1552
+ if (isExe(p)) return p;
1553
+ }
1554
+ return null;
1266
1555
  }
1267
- function split(text) {
1268
- const at = firstTableAt(text);
1269
- return at < 0 ? { head: text, tail: "", at: text.length } : { head: text.slice(0, at), tail: text.slice(at), at };
1556
+ function optionBlock(help2, flag2) {
1557
+ const lines = help2.split("\n");
1558
+ const re = new RegExp(`^\\s{0,4}(?:-\\w, )?${flag2.replace(/-/g, "\\-")}(?![\\w-])`);
1559
+ const start = lines.findIndex((l) => re.test(l));
1560
+ if (start < 0) return null;
1561
+ const out2 = [lines[start]];
1562
+ for (let i = start + 1; i < lines.length && lines[i]?.trim() && !/^\s{0,4}-/.test(lines[i]); i++)
1563
+ out2.push(lines[i]);
1564
+ return out2.join(" ");
1270
1565
  }
1271
- function parseArgv(raw) {
1272
- try {
1273
- const a = JSON.parse(raw);
1274
- return Array.isArray(a) && a.every((x) => typeof x === "string") ? a : null;
1275
- } catch {
1276
- return null;
1566
+ function parseCaps(help2) {
1567
+ const pm = optionBlock(help2, "--permission-mode");
1568
+ const pp = optionBlock(help2, "--permission-prompts");
1569
+ return {
1570
+ hasMode: !!pm,
1571
+ modes: pm ? [...pm.matchAll(/"([A-Za-z]+)"/g)].map((m) => m[1]) : [],
1572
+ promptsNone: !!pp && /"none"/.test(pp),
1573
+ fork: !!optionBlock(help2, "--fork-session"),
1574
+ stream: !!optionBlock(help2, "--include-partial-messages")
1575
+ };
1576
+ }
1577
+ function modeFlags(mode, caps) {
1578
+ if (mode === "auto") {
1579
+ if (!caps.modes.includes("auto"))
1580
+ return { error: "this machine is in auto mode, but its Claude Code has no auto permission mode" };
1581
+ return { args: ["--permission-mode", "auto"] };
1277
1582
  }
1583
+ if (caps.modes.includes("dontAsk")) return { args: ["--permission-mode", "dontAsk"] };
1584
+ const args2 = [];
1585
+ if (caps.hasMode)
1586
+ args2.push(
1587
+ "--permission-mode",
1588
+ caps.modes.includes("manual") && !caps.modes.includes("default") ? "manual" : "default"
1589
+ );
1590
+ if (caps.promptsNone) args2.push("--permission-prompts", "none");
1591
+ return { args: args2 };
1278
1592
  }
1279
- function readNotify(text) {
1280
- const { head, tail: tail2, at } = split(text);
1281
- const m = NOTIFY_LINE.exec(head);
1282
- if (m)
1283
- return {
1284
- raw: m[1] ?? "",
1285
- argv: parseArgv(m[1] ?? ""),
1286
- index: m.index,
1287
- length: m[0].length,
1288
- misplaced: false,
1289
- other: !OURS2.test(m[1] ?? "")
1290
- };
1291
- const k = NOTIFY_KEY.exec(head);
1292
- if (k) {
1293
- const rest = head.slice(k.index).split("\n")[0] ?? "";
1294
- return {
1295
- raw: rest.replace(NOTIFY_KEY, "").trim(),
1296
- argv: null,
1297
- index: k.index,
1298
- length: 0,
1299
- misplaced: false,
1300
- other: true
1301
- };
1593
+ function claudeEnv(configDir, env = process.env) {
1594
+ const e = { ...env };
1595
+ for (const k of [
1596
+ "CLAUDECODE",
1597
+ "CLAUDE_CODE_SESSION_ID",
1598
+ "CLAUDE_CODE_HOST_SESSION_ID",
1599
+ "CLAUDE_CODE_ENTRYPOINT",
1600
+ "CLAUDE_CODE_SESSION_ATTENDED",
1601
+ "CLAUDE_CODE_SSE_PORT"
1602
+ ])
1603
+ delete e[k];
1604
+ e.SESSIONPIPE_CONTROL_JOB = "1";
1605
+ if (configDir !== void 0) {
1606
+ if (path8.resolve(configDir) === path8.resolve(os3.homedir(), ".claude")) delete e.CLAUDE_CONFIG_DIR;
1607
+ else e.CLAUDE_CONFIG_DIR = configDir;
1302
1608
  }
1303
- const t = NOTIFY_LINE.exec(tail2);
1304
- if (t && OURS2.test(t[1] ?? ""))
1609
+ return e;
1610
+ }
1611
+ function parseAuthStatus(stdout) {
1612
+ const text = String(stdout || "").trim();
1613
+ const start = text.indexOf("{");
1614
+ if (start < 0) return { loggedIn: null };
1615
+ try {
1616
+ const j = JSON.parse(text.slice(start));
1617
+ if (typeof j.loggedIn !== "boolean") return { loggedIn: null };
1305
1618
  return {
1306
- raw: t[1] ?? "",
1307
- argv: parseArgv(t[1] ?? ""),
1308
- index: at + t.index,
1309
- length: t[0].length,
1310
- misplaced: true,
1311
- other: false
1619
+ loggedIn: j.loggedIn,
1620
+ ...typeof j.authMethod === "string" ? { method: j.authMethod } : {},
1621
+ ...typeof j.subscriptionType === "string" ? { subscription: j.subscriptionType } : {}
1312
1622
  };
1313
- return null;
1314
- }
1315
- function dropLine(text, index, length) {
1316
- return text.slice(0, index) + text.slice(index + length).replace(/^\r?\n/, "");
1317
- }
1318
- function insertTopLevel(text, line) {
1319
- const { head, tail: tail2 } = split(text);
1320
- if (!tail2) return (head.trim() ? head.replace(/\s*$/, "\n") : "") + line + "\n";
1321
- return `${(head.trim() ? head.replace(/\s*$/, "\n") : "") + line}
1322
-
1323
- ${tail2}`;
1324
- }
1325
- function withNotify(text, line) {
1326
- const n = readNotify(text);
1327
- if (n && !n.misplaced && !n.other) return text.slice(0, n.index) + line + text.slice(n.index + n.length);
1328
- if (n?.misplaced) return insertTopLevel(dropLine(text, n.index, n.length), line);
1329
- return insertTopLevel(text, line);
1330
- }
1331
- function replaceNotify(text, line) {
1332
- const n = readNotify(text);
1333
- if (!n || n.misplaced || !n.length) return null;
1334
- return text.slice(0, n.index) + line + text.slice(n.index + n.length);
1335
- }
1336
- function withoutNotify(text) {
1337
- const n = readNotify(text);
1338
- if (!n || n.other || !n.length) return text;
1339
- return dropLine(text, n.index, n.length);
1623
+ } catch {
1624
+ return { loggedIn: null };
1625
+ }
1340
1626
  }
1341
- var PREVIOUS = "--previous-notify";
1342
- function previousNotify(argv) {
1343
- if (!Array.isArray(argv)) return null;
1344
- const i = argv.indexOf(PREVIOUS);
1345
- return i >= 0 && typeof argv[i + 1] === "string" ? parseArgv(argv[i + 1]) : null;
1627
+ function claudeLogin(bin, configDir, env = process.env) {
1628
+ return new Promise((resolve) => {
1629
+ execFile(
1630
+ bin,
1631
+ ["auth", "status", "--json"],
1632
+ { encoding: "utf8", timeout: 2e4, env: claudeEnv(configDir, env), maxBuffer: 256 * 1024 },
1633
+ // `auth status` may exit non-zero when signed out; its JSON still says so.
1634
+ (_err, stdout) => resolve(parseAuthStatus(stdout))
1635
+ );
1636
+ });
1346
1637
  }
1347
- function stripPreviousRaw(raw) {
1348
- return raw.replace(/,\s*"--previous-notify",\s*"(?:[^"\\]|\\.)*"\s*(?=\])/, "");
1638
+ function loginCommand(configDir) {
1639
+ const home = os3.homedir();
1640
+ const isDefault = !configDir || path8.resolve(configDir) === path8.resolve(home, ".claude");
1641
+ return isDefault ? "`claude auth login`" : `\`CLAUDE_CONFIG_DIR=${configDir.replace(home, "~")} claude auth login\``;
1349
1642
  }
1350
- function withPreviousRaw(raw, prev) {
1351
- const base = stripPreviousRaw(raw).replace(/\s*\]\s*$/, "");
1352
- return `${base}, "--previous-notify", ${JSON.stringify(JSON.stringify(prev))}]`;
1643
+ function loginHint(configDir, platform = process.platform) {
1644
+ const shown = configDir ? configDir.replace(os3.homedir(), "~") : "~/.claude";
1645
+ const login = loginCommand(configDir);
1646
+ if (platform === "darwin")
1647
+ 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.`;
1648
+ return `Claude Code (${shown}) isn't signed in on this machine: run ${login} there.`;
1353
1649
  }
1354
- var isComputerUse = (argv) => Array.isArray(argv) && typeof argv[0] === "string" && /(^|\/)SkyComputerUseClient$/.test(argv[0]);
1355
-
1356
- // ../core/src/adapters/codex.ts
1357
- var NAME3 = "codex";
1358
- var CODEX_EVENTS = [
1359
- "SessionStart",
1360
- "UserPromptSubmit",
1361
- "PreToolUse",
1362
- "PostToolUse",
1363
- "PermissionRequest",
1364
- "Stop",
1365
- "Interrupt",
1366
- "SubagentStart",
1367
- "SubagentStop",
1368
- "PreCompact",
1369
- "PostCompact",
1370
- "SessionEnd"
1371
- ];
1372
- var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
1373
- var eventsFor2 = (lean) => lean ? CODEX_EVENTS.filter((e) => e !== "PreToolUse") : [...CODEX_EVENTS];
1374
- var codexHome = (env) => env.CODEX_HOME || path6.join(HOME, ".codex");
1375
- var hooksFile2 = (env) => path6.join(codexHome(env), "hooks.json");
1376
- var configFile2 = (env) => path6.join(codexHome(env), "config.toml");
1377
- var sessionsDir = (env) => path6.join(codexHome(env), "sessions");
1378
- var validTool3 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
1379
- var endReason2 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
1380
- function installNotify(cmd2, env) {
1381
- const file = configFile2(env);
1382
- let text = "";
1650
+ function detectCaps(bin) {
1651
+ let help2 = "";
1383
1652
  try {
1384
- text = readFileSync4(file, "utf8");
1385
- } catch {
1386
- }
1387
- const argv = cmd2([NAME3, "notify"]).argv;
1388
- const line = `notify = ${JSON.stringify(argv)}`;
1389
- const n = readNotify(text);
1390
- const write = (next) => {
1391
- mkdirSync3(path6.dirname(file), { recursive: true });
1392
- writeFileSync3(file, next);
1393
- };
1394
- if (n && !n.other) {
1395
- if (!n.misplaced && JSON.stringify(n.argv) === JSON.stringify(argv))
1396
- return { file, changed: false, note: "notify current" };
1397
- write(withNotify(text, line));
1398
- return {
1399
- file,
1400
- changed: true,
1401
- note: n.misplaced ? "notify moved to the top level (it sat inside a [table], where Codex never reads it)" : "notify updated"
1402
- };
1653
+ help2 = execFileSync3(bin, ["--help"], {
1654
+ encoding: "utf8",
1655
+ timeout: 3e4,
1656
+ env: claudeEnv(),
1657
+ stdio: ["ignore", "pipe", "pipe"],
1658
+ maxBuffer: 4 * 1024 * 1024
1659
+ });
1660
+ } catch (e) {
1661
+ help2 = String(e?.stdout || "");
1403
1662
  }
1404
- if (n?.other) {
1405
- const prev = previousNotify(n.argv);
1406
- if (isComputerUse(n.argv)) {
1407
- if (prev && OURS2.test(JSON.stringify(prev))) {
1408
- if (JSON.stringify(prev) === JSON.stringify(argv))
1409
- return { file, changed: false, note: "notify current (after Codex Computer Use)" };
1410
- }
1411
- if (!prev || OURS2.test(JSON.stringify(prev))) {
1412
- const next = replaceNotify(text, `notify = ${withPreviousRaw(n.raw, argv)}`);
1413
- if (next) {
1414
- write(next);
1415
- return { file, changed: true, note: "chained after Codex Computer Use (--previous-notify)" };
1416
- }
1663
+ return parseCaps(help2);
1664
+ }
1665
+ function findTranscript(session, dirs) {
1666
+ if (!UUID_RE.test(session)) return null;
1667
+ for (const dir of dirs) {
1668
+ let projects = [];
1669
+ try {
1670
+ projects = readdirSync4(path8.join(dir, "projects"), { withFileTypes: true });
1671
+ } catch {
1672
+ continue;
1673
+ }
1674
+ for (const p of projects) {
1675
+ if (!p.isDirectory()) continue;
1676
+ const file = path8.join(dir, "projects", p.name, `${session}.jsonl`);
1677
+ try {
1678
+ if (statSync5(file).isFile()) return { file, configDir: dir, project: p.name };
1679
+ } catch {
1417
1680
  }
1418
1681
  }
1419
- return {
1420
- file,
1421
- changed: false,
1422
- skipped: true,
1423
- note: `notify belongs to ${(n.argv?.[0] ?? n.raw).replace(/^.*\//, "").slice(0, 40)}; left alone. Codex will report nothing until you approve the hooks: start \`codex\` and accept the hook review`
1424
- };
1425
1682
  }
1426
- write(withNotify(text, line));
1427
- return { file, changed: true, note: "notify set at the top level" };
1683
+ return null;
1428
1684
  }
1429
- function uninstallNotify(env) {
1430
- const file = configFile2(env);
1431
- let text;
1432
- try {
1433
- text = readFileSync4(file, "utf8");
1434
- } catch {
1435
- return { file, changed: false };
1436
- }
1437
- const n = readNotify(text);
1438
- if (!n) return { file, changed: false };
1439
- if (!n.other) {
1440
- writeFileSync3(file, withoutNotify(text));
1441
- return { file, changed: true };
1442
- }
1443
- const prev = previousNotify(n.argv);
1444
- if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev))) {
1445
- const next = replaceNotify(text, `notify = ${stripPreviousRaw(n.raw)}`);
1446
- if (next) writeFileSync3(file, next);
1447
- return { file, changed: true };
1448
- }
1449
- return { file, changed: false };
1450
- }
1451
- function findRollout(threadId, env) {
1452
- let best = null;
1453
- const walk = (dir, depth) => {
1454
- let names = [];
1685
+ var projectName = (cwd) => cwd.replace(/[^a-zA-Z0-9]/g, "-");
1686
+ function sessionFolder(t, cap2 = 8 * 1024 * 1024) {
1687
+ let first = null;
1688
+ let count = 0;
1689
+ let fd;
1690
+ const look = (line) => {
1691
+ if (line.indexOf('"cwd"') < 0) return null;
1692
+ let j;
1455
1693
  try {
1456
- names = readdirSync4(dir);
1694
+ j = JSON.parse(line.toString("utf8"));
1457
1695
  } catch {
1458
- return;
1459
- }
1460
- for (const n of names.sort().reverse()) {
1461
- const p = path6.join(dir, n);
1462
- if (n.endsWith(".jsonl") && n.includes(threadId)) {
1463
- best = p;
1464
- return;
1465
- }
1466
- if (depth < 3) {
1467
- walk(p, depth + 1);
1468
- if (best) return;
1469
- }
1696
+ return null;
1470
1697
  }
1698
+ if (!j || typeof j.cwd !== "string" || !path8.isAbsolute(j.cwd)) return null;
1699
+ if (projectName(j.cwd) === t.project) return j.cwd;
1700
+ first ??= j.cwd;
1701
+ count++;
1702
+ return null;
1471
1703
  };
1472
- walk(sessionsDir(env), 0);
1473
- return best;
1474
- }
1475
- var codex = {
1476
- name: NAME3,
1477
- events: CODEX_EVENTS,
1478
- detect: (env = process.env) => existsSync4(codexHome(env)),
1479
- configFiles: (env = process.env) => [hooksFile2(env), configFile2(env)],
1480
- install: (cmd2, env = process.env, opts = {}) => [
1481
- installClaudeShaped(hooksFile2(env), NAME3, eventsFor2(opts.lean), cmd2, OPTS2),
1482
- installNotify(cmd2, env)
1483
- ],
1484
- uninstall: (env = process.env, opts = {}) => [
1485
- uninstallClaudeShaped(hooksFile2(env), CODEX_EVENTS, { created: opts.created?.includes(hooksFile2(env)) }),
1486
- uninstallNotify(env)
1487
- ],
1488
- installed: (cmd2, env = process.env, opts = {}) => {
1489
- const out2 = [
1490
- {
1491
- file: hooksFile2(env),
1492
- state: installedClaudeShaped(hooksFile2(env), NAME3, eventsFor2(opts.lean), cmd2, OPTS2),
1493
- note: "Codex runs non-managed hooks only after the review in `codex` (/hooks); until then nothing is reported unless the notify fallback is ours"
1704
+ try {
1705
+ fd = openSync2(t.file, "r");
1706
+ const buf = Buffer.alloc(256 * 1024);
1707
+ let pos = 0;
1708
+ let rest = Buffer.alloc(0);
1709
+ let skipping = false;
1710
+ while (pos < cap2 && count < 50) {
1711
+ const n = readSync2(fd, buf, 0, buf.length, pos);
1712
+ if (!n) {
1713
+ if (!skipping && rest.length) {
1714
+ const hit = look(rest);
1715
+ if (hit) return hit;
1716
+ }
1717
+ break;
1494
1718
  }
1495
- ];
1496
- let text = "";
1497
- try {
1498
- text = readFileSync4(configFile2(env), "utf8");
1499
- } catch {
1719
+ pos += n;
1720
+ let chunk = rest.length ? Buffer.concat([rest, buf.subarray(0, n)]) : buf.subarray(0, n);
1721
+ let nl = chunk.indexOf(10);
1722
+ while (nl >= 0) {
1723
+ const line = chunk.subarray(0, nl);
1724
+ chunk = chunk.subarray(nl + 1);
1725
+ if (skipping) skipping = false;
1726
+ else {
1727
+ const hit = look(line);
1728
+ if (hit) return hit;
1729
+ }
1730
+ nl = chunk.indexOf(10);
1731
+ }
1732
+ if (chunk.length > 2 * 1024 * 1024) {
1733
+ skipping = true;
1734
+ rest = Buffer.alloc(0);
1735
+ } else rest = skipping ? Buffer.alloc(0) : Buffer.from(chunk);
1500
1736
  }
1501
- const n = readNotify(text);
1502
- const argv = cmd2([NAME3, "notify"]).argv;
1503
- const prev = n ? previousNotify(n.argv) : null;
1504
- const ours = n && (!n.other || prev && JSON.stringify(prev) === JSON.stringify(argv));
1505
- out2.push({
1506
- file: configFile2(env),
1507
- state: !n || n.other && !ours ? "missing" : n.misplaced ? "misplaced" : JSON.stringify(n.argv) === JSON.stringify(argv) || prev && JSON.stringify(prev) === JSON.stringify(argv) ? "current" : "stale"
1508
- });
1509
- return out2;
1510
- },
1511
- fromHook(input) {
1512
- const event = input.argv[1] ?? "";
1513
- let s = {};
1514
- if (event === "notify") {
1515
- const raw = input.argv.find((a) => a.startsWith("{")) ?? input.stdin;
1737
+ } catch {
1738
+ } finally {
1739
+ if (fd !== void 0)
1516
1740
  try {
1517
- s = raw ? JSON.parse(raw) : {};
1741
+ closeSync2(fd);
1518
1742
  } catch {
1519
1743
  }
1520
- const id2 = s["thread-id"] ?? s.thread_id ?? s["session-id"] ?? s.session_id;
1521
- if (!id2) return null;
1522
- const r2 = {
1523
- session: { id: String(id2), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd },
1524
- events: [
1525
- {
1526
- type: "turn.ended",
1527
- data: { reason: "stop", ...typeof s["turn-id"] === "string" ? { turn_id: s["turn-id"] } : {} },
1528
- harnessEvent: "notify"
1529
- }
1530
- ],
1531
- hints: {}
1532
- };
1533
- const file = findRollout(String(id2), input.env);
1534
- if (file) r2.transcript = file;
1535
- return r2;
1536
- }
1537
- try {
1538
- s = input.stdin ? JSON.parse(input.stdin) : {};
1539
- } catch {
1540
- }
1541
- const id = s.session_id;
1542
- if (!id) return null;
1543
- const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
1544
- if (typeof s.model === "string") session.model = s.model;
1545
- const turn_id = typeof s.turn_id === "string" ? s.turn_id : void 0;
1546
- const tool = validTool3(s.tool_name) ? s.tool_name : void 0;
1547
- const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
1548
- const ev = (type, data) => ({ type, data: prune3(data), harnessEvent: event });
1549
- const events = [];
1550
- switch (event) {
1551
- case "SessionStart":
1552
- events.push(
1553
- ev("session.started", {
1554
- source: typeof s.source === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(s.source) ? s.source : "unknown"
1555
- })
1556
- );
1557
- break;
1558
- case "UserPromptSubmit":
1559
- events.push(
1560
- ev("turn.started", {
1561
- turn_id,
1562
- prompt_chars: typeof s.prompt === "string" ? [...s.prompt].length : void 0
1563
- })
1564
- );
1565
- break;
1566
- case "PreToolUse":
1567
- if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, input: s.tool_input }));
1568
- break;
1569
- case "PostToolUse":
1570
- if (tool)
1571
- events.push(
1572
- ev("tool.ended", { tool, call_id, turn_id, ok: true, input: s.tool_input, output: s.tool_response })
1573
- );
1574
- break;
1575
- case "PermissionRequest":
1576
- events.push(
1577
- ev("attention.needed", { attention_id: call_id ?? `perm-${Date.now()}`, kind: "permission", tool })
1578
- );
1579
- break;
1580
- case "Stop":
1581
- events.push(ev("turn.ended", { turn_id, reason: "stop" }));
1582
- break;
1583
- case "Interrupt":
1584
- events.push(ev("turn.ended", { turn_id, reason: "interrupt" }));
1585
- break;
1586
- case "SubagentStart":
1587
- events.push(
1588
- ev("subagent.started", {
1589
- agent_id: String(s.agent_id ?? s.subagent_id ?? ""),
1590
- agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
1591
- })
1592
- );
1593
- break;
1594
- case "SubagentStop":
1595
- events.push(
1596
- ev("subagent.ended", {
1597
- agent_id: String(s.agent_id ?? s.subagent_id ?? ""),
1598
- agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
1599
- })
1600
- );
1601
- break;
1602
- case "PreCompact":
1603
- events.push(ev("context.compacted", { trigger: s.trigger === "manual" ? "manual" : "auto" }));
1604
- break;
1605
- case "PostCompact":
1606
- break;
1607
- case "SessionEnd":
1608
- events.push(ev("session.ended", { reason: endReason2(s.reason) }));
1609
- break;
1610
- default:
1611
- return null;
1612
- }
1613
- const r = { session, events, hints: {} };
1614
- if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
1615
- return r;
1616
- },
1617
- readTranscript(file, fromLine) {
1618
- let text = "";
1619
- try {
1620
- text = readFileSync4(file, "utf8");
1621
- } catch {
1622
- return { turns: [], line: fromLine };
1623
- }
1624
- return readCodex(text.split("\n"), fromLine);
1625
- },
1626
- facts(session, transcript) {
1627
- const out2 = {};
1628
- if (transcript) Object.assign(out2, codexFacts(transcript));
1629
- return out2;
1630
- },
1631
- backfill(sinceMs, env = process.env) {
1632
- const rows = [];
1633
- for (const f of recentFiles(sessionsDir(env), 3, sinceMs, (n) => /^rollout-.*\.jsonl$/.test(n))) {
1634
- const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(path6.basename(f));
1635
- if (!m?.[1]) continue;
1636
- const facts = codexFacts(f);
1637
- if (!facts.started_at || !facts.last_at) continue;
1638
- const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
1639
- rows.push({ session: { id: m[1], ...rest }, started_at, last_at });
1640
- }
1641
- return rows;
1642
1744
  }
1643
- };
1644
- function prune3(o) {
1645
- for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
1646
- return o;
1745
+ return first;
1647
1746
  }
1648
- function codexFacts(file) {
1649
- const { head, tail: tail2 } = ends(file, 128 * 1024, 64 * 1024);
1650
- const H = parseLines(head);
1651
- const T = parseLines(tail2);
1652
- const out2 = {};
1653
- const meta = H.find((j) => j.type === "session_meta" && j.payload);
1654
- if (meta?.payload) {
1655
- const p = meta.payload;
1656
- if (p.cwd) out2.cwd = p.cwd;
1657
- if (p.cli_version) out2.harness_version = String(p.cli_version);
1658
- if (p.git?.branch) out2.branch = p.git.branch;
1659
- if (p.git?.repository_url) out2.repo = String(p.git.repository_url).replace(/\/\/[^/@]*@/, "//");
1747
+ function pidAlive(pid) {
1748
+ if (!Number.isSafeInteger(pid) || pid <= 1) return false;
1749
+ try {
1750
+ process.kill(pid, 0);
1751
+ return true;
1752
+ } catch (e) {
1753
+ return e.code === "EPERM";
1660
1754
  }
1661
- const ctx = H.concat(T).reverse().find((j) => j.type === "turn_context" && j.payload?.model);
1662
- if (ctx?.payload?.model) out2.model = ctx.payload.model;
1663
- for (const j of H) {
1664
- const p = j.payload;
1665
- if (j.type !== "response_item" || !p || p.type !== "message" || p.role !== "user") continue;
1666
- const text = (p.content ?? []).filter((b) => b && b.type === "input_text" && typeof b.text === "string").map((b) => b.text).join("\n").trim();
1667
- if (!text || /^<[a-z_-]+>/i.test(text)) continue;
1668
- const line = text.split("\n").find((l) => l.trim()) ?? "";
1669
- out2.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
1670
- out2.title_source = "first-ask";
1671
- break;
1755
+ }
1756
+ function liveProcess(session, configDir) {
1757
+ let names = [];
1758
+ try {
1759
+ names = readdirSync4(path8.join(configDir, "sessions")).filter((n) => /^\d+\.json$/.test(n));
1760
+ } catch {
1761
+ return false;
1672
1762
  }
1673
- const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
1674
- if (times.length) {
1675
- out2.started_at = Math.min(...times);
1676
- out2.last_at = Math.max(...times);
1763
+ for (const n of names) {
1764
+ try {
1765
+ const j = JSON.parse(readFileSync4(path8.join(configDir, "sessions", n), "utf8"));
1766
+ if (String(j.sessionId) !== session) continue;
1767
+ if (pidAlive(Number(j.pid ?? n.slice(0, -5)))) return true;
1768
+ } catch {
1769
+ }
1677
1770
  }
1678
- return out2;
1679
- }
1680
-
1681
- // ../core/src/adapters/gemini-cli.ts
1682
- import { execFileSync as execFileSync3 } from "node:child_process";
1683
- import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
1684
- import path7 from "node:path";
1685
- var NAME4 = "gemini-cli";
1686
- var GEMINI_EVENTS = [
1687
- "SessionStart",
1688
- "BeforeAgent",
1689
- "BeforeTool",
1690
- "AfterTool",
1691
- "Notification",
1692
- "AfterAgent",
1693
- "PreCompress",
1694
- "SessionEnd"
1695
- ];
1696
- var OPTS3 = { matcherFor: (e) => /Tool/.test(e) ? ".*" : void 0, timeout: 5e3 };
1697
- var geminiDir = (env) => env.GEMINI_CLI_HOME || path7.join(HOME, ".gemini");
1698
- var settings = (env) => path7.join(geminiDir(env), "settings.json");
1699
- var validTool4 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
1700
- var eventsFor3 = (lean) => lean ? GEMINI_EVENTS.filter((e) => e !== "BeforeTool") : [...GEMINI_EVENTS];
1701
- function geminiHooksActive(settingsFile) {
1702
- let tools;
1771
+ return false;
1772
+ }
1773
+ function recentlyWritten(file, ourLastEnd, now = Date.now()) {
1774
+ let m;
1703
1775
  try {
1704
- tools = JSON.parse(readFileSync5(settingsFile, "utf8")).tools;
1776
+ m = statSync5(file).mtimeMs;
1705
1777
  } catch {
1778
+ return false;
1706
1779
  }
1707
- if (tools?.enableHooks === true) return { active: true };
1708
- if (tools?.enableHooks === false)
1709
- return { active: false, note: "inactive: tools.enableHooks is false in settings.json" };
1710
- let version = "";
1780
+ if (now - m > LIVE_WRITE_MS) return false;
1781
+ return !(ourLastEnd && m <= ourLastEnd + 2e3);
1782
+ }
1783
+ function runClaude(bin, args2, o) {
1784
+ return runHeadless(bin, args2, { ...o, keep: o.keep ?? ((line) => line.includes('"type":"result"')) });
1785
+ }
1786
+ function parseResult(stdout) {
1787
+ const s = String(stdout || "").trim();
1788
+ if (!s) return null;
1789
+ const pick = (j) => {
1790
+ if (Array.isArray(j)) {
1791
+ for (let i = j.length - 1; i >= 0; i--) if (j[i]?.type === "result") return j[i];
1792
+ return null;
1793
+ }
1794
+ return j && typeof j === "object" ? j : null;
1795
+ };
1711
1796
  try {
1712
- version = execFileSync3("gemini", ["--version"], {
1713
- encoding: "utf8",
1714
- timeout: 4e3,
1715
- stdio: ["ignore", "pipe", "ignore"]
1716
- }).trim();
1797
+ return pick(JSON.parse(s));
1717
1798
  } catch {
1718
- return {
1719
- active: true,
1720
- note: "could not run `gemini --version`; hooks need tools.enableHooks on versions before 0.46"
1721
- };
1722
1799
  }
1723
- const m = /(\d+)\.(\d+)/.exec(version);
1724
- if (m && (Number(m[1]) > 0 || Number(m[2]) >= 46)) return { active: true };
1725
- return {
1726
- active: false,
1727
- note: `inactive: Gemini CLI ${version || "?"} runs hooks only with "tools": {"enableHooks": true} in settings.json`
1728
- };
1729
- }
1730
- var geminiCli = {
1731
- name: NAME4,
1732
- events: GEMINI_EVENTS,
1733
- // ~/.gemini also belongs to Antigravity; Gemini CLI is here when its settings or its chat logs exist.
1734
- detect: (env = process.env) => existsSync5(settings(env)) || existsSync5(path7.join(geminiDir(env), "tmp")),
1735
- configFiles: (env = process.env) => [settings(env)],
1736
- install: (cmd2, env = process.env, opts = {}) => {
1737
- const r = installClaudeShaped(settings(env), NAME4, eventsFor3(opts.lean), cmd2, OPTS3);
1738
- const a = geminiHooksActive(settings(env));
1739
- return [a.note && !r.skipped ? { ...r, note: [r.note, a.note].filter(Boolean).join("; ") } : r];
1740
- },
1741
- uninstall: (env = process.env, opts = {}) => [
1742
- uninstallClaudeShaped(settings(env), GEMINI_EVENTS, { created: opts.created?.includes(settings(env)) })
1743
- ],
1744
- installed: (cmd2, env = process.env, opts = {}) => {
1745
- const state2 = installedClaudeShaped(settings(env), NAME4, eventsFor3(opts.lean), cmd2, OPTS3);
1746
- const a = geminiHooksActive(settings(env));
1747
- return [
1748
- {
1749
- file: settings(env),
1750
- state: state2 === "current" && !a.active ? "inactive" : state2,
1751
- ...a.note ? { note: a.note } : {}
1752
- }
1753
- ];
1754
- },
1755
- fromHook(input) {
1756
- const event = input.argv[1] ?? "";
1757
- let s = {};
1800
+ const lines = s.split("\n");
1801
+ for (let i = lines.length - 1; i >= 0; i--) {
1758
1802
  try {
1759
- s = input.stdin ? JSON.parse(input.stdin) : {};
1803
+ const j = pick(JSON.parse(lines[i]));
1804
+ if (j && (j.type === "result" || "result" in j)) return j;
1760
1805
  } catch {
1761
1806
  }
1762
- const id = s.session_id;
1763
- if (!id) return null;
1764
- const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
1765
- const tool = validTool4(s.tool_name) ? s.tool_name : void 0;
1766
- const ev = (type, data) => ({ type, data: prune4(data), harnessEvent: event });
1767
- const events = [];
1768
- switch (event) {
1769
- case "SessionStart":
1770
- events.push(
1771
- ev("session.started", {
1772
- source: typeof s.source === "string" && ["startup", "resume", "clear"].includes(s.source) ? s.source : "unknown"
1773
- })
1774
- );
1775
- break;
1776
- case "BeforeAgent":
1777
- events.push(
1778
- ev("turn.started", { prompt_chars: typeof s.prompt === "string" ? [...s.prompt].length : void 0 })
1779
- );
1780
- break;
1781
- case "BeforeTool":
1782
- if (tool) events.push(ev("tool.started", { tool, input: s.tool_input }));
1783
- break;
1784
- case "AfterTool": {
1785
- const resp = s.tool_response;
1786
- const err = resp && typeof resp === "object" && resp.error ? String(typeof resp.error === "string" ? resp.error : JSON.stringify(resp.error)).slice(0, 2e3) : void 0;
1787
- if (tool)
1788
- events.push(
1789
- ev("tool.ended", {
1790
- tool,
1791
- ok: !err,
1792
- error: err,
1793
- input: s.tool_input,
1794
- output: resp?.llmContent ?? s.tool_response
1795
- })
1796
- );
1797
- break;
1798
- }
1799
- case "Notification":
1800
- events.push(
1801
- ev("attention.needed", {
1802
- attention_id: `notif-${Date.now()}`,
1803
- kind: s.notification_type === "ToolPermission" ? "permission" : "question",
1804
- message: typeof s.message === "string" ? s.message.slice(0, 200) : void 0
1805
- })
1806
- );
1807
- break;
1808
- case "AfterAgent":
1809
- events.push(ev("turn.ended", { reason: "stop" }));
1810
- break;
1811
- case "PreCompress":
1812
- events.push(ev("context.compacted", { trigger: s.trigger === "manual" ? "manual" : "auto" }));
1813
- break;
1814
- case "SessionEnd":
1815
- events.push(
1816
- ev("session.ended", {
1817
- reason: s.reason === "clear" || s.reason === "logout" || s.reason === "exit" ? s.reason : s.reason === "prompt_input_exit" ? "exit" : "other"
1818
- })
1819
- );
1820
- break;
1821
- default:
1822
- return null;
1807
+ }
1808
+ return null;
1809
+ }
1810
+ var isUuid = (s) => typeof s === "string" && UUID_RE.test(s);
1811
+
1812
+ // ../core/src/adapters/codex.ts
1813
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
1814
+ import path9 from "node:path";
1815
+
1816
+ // ../core/src/adapters/codex-config.ts
1817
+ var NOTIFY_LINE = /^[ \t]*notify[ \t]*=[ \t]*(\[.*\])[ \t]*(?:#.*)?$/m;
1818
+ var NOTIFY_KEY = /^[ \t]*notify[ \t]*=/m;
1819
+ var OURS2 = /(?:hook\.js|sessionpipe-hook)",\s*"codex",\s*"notify"/;
1820
+ function scan(line, st) {
1821
+ let i = 0;
1822
+ while (i < line.length) {
1823
+ if (st.str) {
1824
+ const j = line.indexOf(st.str, i);
1825
+ if (j < 0) return;
1826
+ i = j + 3;
1827
+ st.str = null;
1828
+ continue;
1823
1829
  }
1824
- const r = { session, events, hints: {} };
1825
- if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
1826
- return r;
1827
- },
1828
- readTranscript(file, fromLine) {
1829
- let text = "";
1830
- try {
1831
- text = readFileSync5(file, "utf8");
1832
- } catch {
1833
- return { turns: [], line: fromLine };
1830
+ const ch = line[i];
1831
+ if (ch === "#") return;
1832
+ if (line.startsWith('"""', i) || line.startsWith("'''", i)) {
1833
+ st.str = line.slice(i, i + 3);
1834
+ i += 3;
1835
+ continue;
1834
1836
  }
1835
- return readGemini(text.split("\n"), fromLine);
1836
- },
1837
- facts(_session, transcript) {
1838
- const out2 = {};
1839
- if (!transcript) return out2;
1840
- const { head, tail: tail2 } = ends(transcript, 64 * 1024, 64 * 1024);
1841
- const H = parseLines(head);
1842
- const T = parseLines(tail2);
1843
- const first = H.find((j) => j.type === "user");
1844
- const raw = first ? first.content ?? first.message ?? first.text : void 0;
1845
- const text = typeof raw === "string" ? raw.trim() : "";
1846
- if (text) {
1847
- const line = text.split("\n").find((l) => l.trim()) ?? "";
1848
- out2.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
1849
- out2.title_source = "first-ask";
1837
+ if (ch === '"') {
1838
+ i++;
1839
+ while (i < line.length && line[i] !== '"') i += line[i] === "\\" ? 2 : 1;
1840
+ i++;
1841
+ continue;
1850
1842
  }
1851
- const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
1852
- if (times.length) {
1853
- out2.started_at = Math.min(...times);
1854
- out2.last_at = Math.max(...times);
1843
+ if (ch === "'") {
1844
+ const j = line.indexOf("'", i + 1);
1845
+ i = j < 0 ? line.length : j + 1;
1846
+ continue;
1855
1847
  }
1856
- return out2;
1857
- },
1858
- backfill: () => []
1859
- // chat logs are keyed by a project hash, not a session id we can name; the first hook of a resumed session reports it
1860
- };
1861
- function prune4(o) {
1862
- for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
1863
- return o;
1864
- }
1865
-
1866
- // ../core/src/adapters/claude-code-control.ts
1867
- var claude_code_control_exports = {};
1868
- __export(claude_code_control_exports, {
1869
- JOB_TIMEOUT_MS: () => JOB_TIMEOUT_MS,
1870
- claudeEnv: () => claudeEnv,
1871
- claudeLogin: () => claudeLogin,
1872
- detectCaps: () => detectCaps,
1873
- findClaude: () => findClaude,
1874
- findTranscript: () => findTranscript,
1875
- isUuid: () => isUuid,
1876
- liveProcess: () => liveProcess,
1877
- loginCommand: () => loginCommand,
1878
- loginHint: () => loginHint,
1879
- modeFlags: () => modeFlags,
1880
- parseAuthStatus: () => parseAuthStatus,
1881
- parseCaps: () => parseCaps,
1882
- parseResult: () => parseResult,
1883
- recentlyWritten: () => recentlyWritten,
1884
- runClaude: () => runClaude,
1885
- sessionFolder: () => sessionFolder
1886
- });
1887
- import { execFile, execFileSync as execFileSync4, spawn } from "node:child_process";
1888
- import { accessSync, closeSync as closeSync2, constants, openSync as openSync2, readdirSync as readdirSync5, readFileSync as readFileSync6, readSync as readSync2, statSync as statSync4 } from "node:fs";
1889
- import os2 from "node:os";
1890
- import path8 from "node:path";
1891
- var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1892
- var JOB_TIMEOUT_MS = 30 * 6e4;
1893
- var OUT_CAP = 1024 * 1024;
1894
- var LIVE_WRITE_MS = 9e4;
1895
- function isExe(p) {
1896
- try {
1897
- if (!statSync4(p).isFile()) return false;
1898
- accessSync(p, constants.X_OK);
1899
- return true;
1900
- } catch {
1901
- return false;
1848
+ if (ch === "[") st.depth++;
1849
+ else if (ch === "]") st.depth = Math.max(0, st.depth - 1);
1850
+ i++;
1902
1851
  }
1903
1852
  }
1904
- var cmpVersion = (a, b) => {
1905
- const pa = a.split(".").map(Number);
1906
- const pb = b.split(".").map(Number);
1907
- for (let i = 0; i < Math.max(pa.length, pb.length); i++)
1908
- if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) - (pb[i] ?? 0);
1909
- return 0;
1910
- };
1911
- function findClaude(env = process.env, home = os2.homedir()) {
1912
- if (env.SESSIONPIPE_CLAUDE && isExe(env.SESSIONPIPE_CLAUDE)) return env.SESSIONPIPE_CLAUDE;
1913
- const names = process.platform === "win32" ? ["claude.exe", "claude.cmd"] : ["claude"];
1914
- for (const d of String(env.PATH || "").split(path8.delimiter)) {
1915
- if (!d || !path8.isAbsolute(d)) continue;
1916
- for (const n of names) if (isExe(path8.join(d, n))) return path8.join(d, n);
1853
+ function firstTableAt(text) {
1854
+ const st = { depth: 0, str: null };
1855
+ let pos = 0;
1856
+ for (const line of text.split("\n")) {
1857
+ if (!st.str && st.depth === 0 && /^[ \t]*\[/.test(line)) return pos;
1858
+ scan(line, st);
1859
+ pos += line.length + 1;
1917
1860
  }
1918
- for (const p of [
1919
- path8.join(home, ".claude", "local", "claude"),
1920
- path8.join(home, ".local", "bin", "claude"),
1921
- "/opt/homebrew/bin/claude",
1922
- "/usr/local/bin/claude"
1923
- ])
1924
- if (isExe(p)) return p;
1925
- const app = path8.join(home, "Library", "Application Support", "Claude", "claude-code");
1926
- let versions = [];
1861
+ return -1;
1862
+ }
1863
+ function split(text) {
1864
+ const at = firstTableAt(text);
1865
+ return at < 0 ? { head: text, tail: "", at: text.length } : { head: text.slice(0, at), tail: text.slice(at), at };
1866
+ }
1867
+ function parseArgv(raw) {
1927
1868
  try {
1928
- versions = readdirSync5(app).filter((n) => /^\d+(\.\d+)*$/.test(n)).sort(cmpVersion).reverse();
1869
+ const a = JSON.parse(raw);
1870
+ return Array.isArray(a) && a.every((x) => typeof x === "string") ? a : null;
1929
1871
  } catch {
1872
+ return null;
1930
1873
  }
1931
- for (const v of versions) {
1932
- const p = path8.join(app, v, "claude.app", "Contents", "MacOS", "claude");
1933
- if (isExe(p)) return p;
1874
+ }
1875
+ function readNotify(text) {
1876
+ const { head, tail: tail2, at } = split(text);
1877
+ const m = NOTIFY_LINE.exec(head);
1878
+ if (m)
1879
+ return {
1880
+ raw: m[1] ?? "",
1881
+ argv: parseArgv(m[1] ?? ""),
1882
+ index: m.index,
1883
+ length: m[0].length,
1884
+ misplaced: false,
1885
+ other: !OURS2.test(m[1] ?? "")
1886
+ };
1887
+ const k = NOTIFY_KEY.exec(head);
1888
+ if (k) {
1889
+ const rest = head.slice(k.index).split("\n")[0] ?? "";
1890
+ return {
1891
+ raw: rest.replace(NOTIFY_KEY, "").trim(),
1892
+ argv: null,
1893
+ index: k.index,
1894
+ length: 0,
1895
+ misplaced: false,
1896
+ other: true
1897
+ };
1934
1898
  }
1899
+ const t = NOTIFY_LINE.exec(tail2);
1900
+ if (t && OURS2.test(t[1] ?? ""))
1901
+ return {
1902
+ raw: t[1] ?? "",
1903
+ argv: parseArgv(t[1] ?? ""),
1904
+ index: at + t.index,
1905
+ length: t[0].length,
1906
+ misplaced: true,
1907
+ other: false
1908
+ };
1935
1909
  return null;
1936
1910
  }
1937
- function optionBlock(help2, flag2) {
1938
- const lines = help2.split("\n");
1939
- const re = new RegExp(`^\\s{0,4}(?:-\\w, )?${flag2.replace(/-/g, "\\-")}(?![\\w-])`);
1940
- const start = lines.findIndex((l) => re.test(l));
1941
- if (start < 0) return null;
1942
- const out2 = [lines[start]];
1943
- for (let i = start + 1; i < lines.length && lines[i]?.trim() && !/^\s{0,4}-/.test(lines[i]); i++)
1944
- out2.push(lines[i]);
1945
- return out2.join(" ");
1911
+ function dropLine(text, index, length) {
1912
+ return text.slice(0, index) + text.slice(index + length).replace(/^\r?\n/, "");
1946
1913
  }
1947
- function parseCaps(help2) {
1948
- const pm = optionBlock(help2, "--permission-mode");
1949
- const pp = optionBlock(help2, "--permission-prompts");
1950
- return {
1951
- hasMode: !!pm,
1952
- modes: pm ? [...pm.matchAll(/"([A-Za-z]+)"/g)].map((m) => m[1]) : [],
1953
- promptsNone: !!pp && /"none"/.test(pp),
1954
- fork: !!optionBlock(help2, "--fork-session"),
1955
- stream: !!optionBlock(help2, "--include-partial-messages")
1956
- };
1914
+ function insertTopLevel(text, line) {
1915
+ const { head, tail: tail2 } = split(text);
1916
+ if (!tail2) return (head.trim() ? head.replace(/\s*$/, "\n") : "") + line + "\n";
1917
+ return `${(head.trim() ? head.replace(/\s*$/, "\n") : "") + line}
1918
+
1919
+ ${tail2}`;
1957
1920
  }
1958
- function modeFlags(mode, caps) {
1959
- if (mode === "auto") {
1960
- if (!caps.modes.includes("auto"))
1961
- return { error: "this machine is in auto mode, but its Claude Code has no auto permission mode" };
1962
- return { args: ["--permission-mode", "auto"] };
1963
- }
1964
- if (caps.modes.includes("dontAsk")) return { args: ["--permission-mode", "dontAsk"] };
1965
- const args2 = [];
1966
- if (caps.hasMode)
1967
- args2.push(
1968
- "--permission-mode",
1969
- caps.modes.includes("manual") && !caps.modes.includes("default") ? "manual" : "default"
1970
- );
1971
- if (caps.promptsNone) args2.push("--permission-prompts", "none");
1972
- return { args: args2 };
1921
+ function withNotify(text, line) {
1922
+ const n = readNotify(text);
1923
+ if (n && !n.misplaced && !n.other) return text.slice(0, n.index) + line + text.slice(n.index + n.length);
1924
+ if (n?.misplaced) return insertTopLevel(dropLine(text, n.index, n.length), line);
1925
+ return insertTopLevel(text, line);
1973
1926
  }
1974
- function claudeEnv(configDir, env = process.env) {
1975
- const e = { ...env };
1976
- for (const k of [
1977
- "CLAUDECODE",
1978
- "CLAUDE_CODE_SESSION_ID",
1979
- "CLAUDE_CODE_HOST_SESSION_ID",
1980
- "CLAUDE_CODE_ENTRYPOINT",
1981
- "CLAUDE_CODE_SESSION_ATTENDED",
1982
- "CLAUDE_CODE_SSE_PORT"
1983
- ])
1984
- delete e[k];
1985
- e.SESSIONPIPE_CONTROL_JOB = "1";
1986
- if (configDir !== void 0) {
1987
- if (path8.resolve(configDir) === path8.resolve(os2.homedir(), ".claude")) delete e.CLAUDE_CONFIG_DIR;
1988
- else e.CLAUDE_CONFIG_DIR = configDir;
1989
- }
1990
- return e;
1927
+ function replaceNotify(text, line) {
1928
+ const n = readNotify(text);
1929
+ if (!n || n.misplaced || !n.length) return null;
1930
+ return text.slice(0, n.index) + line + text.slice(n.index + n.length);
1991
1931
  }
1992
- function parseAuthStatus(stdout) {
1993
- const text = String(stdout || "").trim();
1994
- const start = text.indexOf("{");
1995
- if (start < 0) return { loggedIn: null };
1932
+ function withoutNotify(text) {
1933
+ const n = readNotify(text);
1934
+ if (!n || n.other || !n.length) return text;
1935
+ return dropLine(text, n.index, n.length);
1936
+ }
1937
+ var PREVIOUS = "--previous-notify";
1938
+ function previousNotify(argv) {
1939
+ if (!Array.isArray(argv)) return null;
1940
+ const i = argv.indexOf(PREVIOUS);
1941
+ return i >= 0 && typeof argv[i + 1] === "string" ? parseArgv(argv[i + 1]) : null;
1942
+ }
1943
+ function stripPreviousRaw(raw) {
1944
+ return raw.replace(/,\s*"--previous-notify",\s*"(?:[^"\\]|\\.)*"\s*(?=\])/, "");
1945
+ }
1946
+ function withPreviousRaw(raw, prev) {
1947
+ const base = stripPreviousRaw(raw).replace(/\s*\]\s*$/, "");
1948
+ return `${base}, "--previous-notify", ${JSON.stringify(JSON.stringify(prev))}]`;
1949
+ }
1950
+ var isComputerUse = (argv) => Array.isArray(argv) && typeof argv[0] === "string" && /(^|\/)SkyComputerUseClient$/.test(argv[0]);
1951
+
1952
+ // ../core/src/adapters/codex.ts
1953
+ var NAME3 = "codex";
1954
+ var CODEX_EVENTS = [
1955
+ "SessionStart",
1956
+ "UserPromptSubmit",
1957
+ "PreToolUse",
1958
+ "PostToolUse",
1959
+ "PermissionRequest",
1960
+ "Stop",
1961
+ "Interrupt",
1962
+ "SubagentStart",
1963
+ "SubagentStop",
1964
+ "PreCompact",
1965
+ "PostCompact",
1966
+ "SessionEnd"
1967
+ ];
1968
+ var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
1969
+ var eventsFor2 = (lean) => lean ? CODEX_EVENTS.filter((e) => e !== "PreToolUse") : [...CODEX_EVENTS];
1970
+ var codexHome = (env) => env.CODEX_HOME || path9.join(HOME, ".codex");
1971
+ var hooksFile2 = (env) => path9.join(codexHome(env), "hooks.json");
1972
+ var configFile2 = (env) => path9.join(codexHome(env), "config.toml");
1973
+ var sessionsDir = (env) => path9.join(codexHome(env), "sessions");
1974
+ var validTool3 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
1975
+ var endReason2 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
1976
+ function installNotify(cmd2, env) {
1977
+ const file = configFile2(env);
1978
+ let text = "";
1996
1979
  try {
1997
- const j = JSON.parse(text.slice(start));
1998
- if (typeof j.loggedIn !== "boolean") return { loggedIn: null };
1980
+ text = readFileSync5(file, "utf8");
1981
+ } catch {
1982
+ }
1983
+ const argv = cmd2([NAME3, "notify"]).argv;
1984
+ const line = `notify = ${JSON.stringify(argv)}`;
1985
+ const n = readNotify(text);
1986
+ const write = (next) => {
1987
+ mkdirSync3(path9.dirname(file), { recursive: true });
1988
+ writeFileSync3(file, next);
1989
+ };
1990
+ if (n && !n.other) {
1991
+ if (!n.misplaced && JSON.stringify(n.argv) === JSON.stringify(argv))
1992
+ return { file, changed: false, note: "notify current" };
1993
+ write(withNotify(text, line));
1999
1994
  return {
2000
- loggedIn: j.loggedIn,
2001
- ...typeof j.authMethod === "string" ? { method: j.authMethod } : {},
2002
- ...typeof j.subscriptionType === "string" ? { subscription: j.subscriptionType } : {}
1995
+ file,
1996
+ changed: true,
1997
+ note: n.misplaced ? "notify moved to the top level (it sat inside a [table], where Codex never reads it)" : "notify updated"
2003
1998
  };
2004
- } catch {
2005
- return { loggedIn: null };
2006
1999
  }
2000
+ if (n?.other) {
2001
+ const prev = previousNotify(n.argv);
2002
+ if (isComputerUse(n.argv)) {
2003
+ if (prev && OURS2.test(JSON.stringify(prev))) {
2004
+ if (JSON.stringify(prev) === JSON.stringify(argv))
2005
+ return { file, changed: false, note: "notify current (after Codex Computer Use)" };
2006
+ }
2007
+ if (!prev || OURS2.test(JSON.stringify(prev))) {
2008
+ const next = replaceNotify(text, `notify = ${withPreviousRaw(n.raw, argv)}`);
2009
+ if (next) {
2010
+ write(next);
2011
+ return { file, changed: true, note: "chained after Codex Computer Use (--previous-notify)" };
2012
+ }
2013
+ }
2014
+ }
2015
+ return {
2016
+ file,
2017
+ changed: false,
2018
+ skipped: true,
2019
+ note: `notify belongs to ${(n.argv?.[0] ?? n.raw).replace(/^.*\//, "").slice(0, 40)}; left alone. Codex will report nothing until you approve the hooks: start \`codex\` and accept the hook review`
2020
+ };
2021
+ }
2022
+ write(withNotify(text, line));
2023
+ return { file, changed: true, note: "notify set at the top level" };
2007
2024
  }
2008
- function claudeLogin(bin, configDir, env = process.env) {
2009
- return new Promise((resolve) => {
2010
- execFile(
2011
- bin,
2012
- ["auth", "status", "--json"],
2013
- { encoding: "utf8", timeout: 2e4, env: claudeEnv(configDir, env), maxBuffer: 256 * 1024 },
2014
- // `auth status` may exit non-zero when signed out; its JSON still says so.
2015
- (_err, stdout) => resolve(parseAuthStatus(stdout))
2016
- );
2017
- });
2018
- }
2019
- function loginCommand(configDir) {
2020
- const home = os2.homedir();
2021
- const isDefault = !configDir || path8.resolve(configDir) === path8.resolve(home, ".claude");
2022
- return isDefault ? "`claude auth login`" : `\`CLAUDE_CONFIG_DIR=${configDir.replace(home, "~")} claude auth login\``;
2023
- }
2024
- function loginHint(configDir, platform = process.platform) {
2025
- const shown = configDir ? configDir.replace(os2.homedir(), "~") : "~/.claude";
2026
- const login = loginCommand(configDir);
2027
- if (platform === "darwin")
2028
- 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.`;
2029
- return `Claude Code (${shown}) isn't signed in on this machine: run ${login} there.`;
2030
- }
2031
- function detectCaps(bin) {
2032
- let help2 = "";
2025
+ function uninstallNotify(env) {
2026
+ const file = configFile2(env);
2027
+ let text;
2033
2028
  try {
2034
- help2 = execFileSync4(bin, ["--help"], {
2035
- encoding: "utf8",
2036
- timeout: 3e4,
2037
- env: claudeEnv(),
2038
- stdio: ["ignore", "pipe", "pipe"],
2039
- maxBuffer: 4 * 1024 * 1024
2040
- });
2041
- } catch (e) {
2042
- help2 = String(e?.stdout || "");
2029
+ text = readFileSync5(file, "utf8");
2030
+ } catch {
2031
+ return { file, changed: false };
2043
2032
  }
2044
- return parseCaps(help2);
2033
+ const n = readNotify(text);
2034
+ if (!n) return { file, changed: false };
2035
+ if (!n.other) {
2036
+ writeFileSync3(file, withoutNotify(text));
2037
+ return { file, changed: true };
2038
+ }
2039
+ const prev = previousNotify(n.argv);
2040
+ if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev))) {
2041
+ const next = replaceNotify(text, `notify = ${stripPreviousRaw(n.raw)}`);
2042
+ if (next) writeFileSync3(file, next);
2043
+ return { file, changed: true };
2044
+ }
2045
+ return { file, changed: false };
2045
2046
  }
2046
- function findTranscript(session, dirs) {
2047
- if (!UUID_RE.test(session)) return null;
2048
- for (const dir of dirs) {
2049
- let projects = [];
2047
+ function findRollout(threadId, env = process.env) {
2048
+ if (!/^[A-Za-z0-9-]{8,128}$/.test(threadId)) return null;
2049
+ let best = null;
2050
+ const walk = (dir, depth) => {
2051
+ let names = [];
2050
2052
  try {
2051
- projects = readdirSync5(path8.join(dir, "projects"), { withFileTypes: true });
2053
+ names = readdirSync5(dir);
2052
2054
  } catch {
2053
- continue;
2055
+ return;
2054
2056
  }
2055
- for (const p of projects) {
2056
- if (!p.isDirectory()) continue;
2057
- const file = path8.join(dir, "projects", p.name, `${session}.jsonl`);
2058
- try {
2059
- if (statSync4(file).isFile()) return { file, configDir: dir, project: p.name };
2060
- } catch {
2057
+ for (const n of names.sort().reverse()) {
2058
+ const p = path9.join(dir, n);
2059
+ if (n.endsWith(`-${threadId}.jsonl`)) {
2060
+ best = p;
2061
+ return;
2062
+ }
2063
+ if (depth < 3) {
2064
+ walk(p, depth + 1);
2065
+ if (best) return;
2061
2066
  }
2062
2067
  }
2063
- }
2064
- return null;
2068
+ };
2069
+ walk(sessionsDir(env), 0);
2070
+ return best;
2065
2071
  }
2066
- var projectName = (cwd) => cwd.replace(/[^a-zA-Z0-9]/g, "-");
2067
- function sessionFolder(t, cap2 = 8 * 1024 * 1024) {
2068
- let first = null;
2069
- let count = 0;
2070
- let fd;
2071
- const look = (line) => {
2072
- if (line.indexOf('"cwd"') < 0) return null;
2073
- let j;
2072
+ var codex = {
2073
+ name: NAME3,
2074
+ events: CODEX_EVENTS,
2075
+ detect: (env = process.env) => existsSync4(codexHome(env)),
2076
+ configFiles: (env = process.env) => [hooksFile2(env), configFile2(env)],
2077
+ install: (cmd2, env = process.env, opts = {}) => [
2078
+ installClaudeShaped(hooksFile2(env), NAME3, eventsFor2(opts.lean), cmd2, OPTS2),
2079
+ installNotify(cmd2, env)
2080
+ ],
2081
+ uninstall: (env = process.env, opts = {}) => [
2082
+ uninstallClaudeShaped(hooksFile2(env), CODEX_EVENTS, { created: opts.created?.includes(hooksFile2(env)) }),
2083
+ uninstallNotify(env)
2084
+ ],
2085
+ installed: (cmd2, env = process.env, opts = {}) => {
2086
+ const out2 = [
2087
+ {
2088
+ file: hooksFile2(env),
2089
+ state: installedClaudeShaped(hooksFile2(env), NAME3, eventsFor2(opts.lean), cmd2, OPTS2),
2090
+ note: "Codex runs non-managed hooks only after the review in `codex` (/hooks); until then nothing is reported unless the notify fallback is ours"
2091
+ }
2092
+ ];
2093
+ let text = "";
2074
2094
  try {
2075
- j = JSON.parse(line.toString("utf8"));
2095
+ text = readFileSync5(configFile2(env), "utf8");
2076
2096
  } catch {
2077
- return null;
2078
- }
2079
- if (!j || typeof j.cwd !== "string" || !path8.isAbsolute(j.cwd)) return null;
2080
- if (projectName(j.cwd) === t.project) return j.cwd;
2081
- first ??= j.cwd;
2082
- count++;
2083
- return null;
2084
- };
2085
- try {
2086
- fd = openSync2(t.file, "r");
2087
- const buf = Buffer.alloc(256 * 1024);
2088
- let pos = 0;
2089
- let rest = Buffer.alloc(0);
2090
- let skipping = false;
2091
- while (pos < cap2 && count < 50) {
2092
- const n = readSync2(fd, buf, 0, buf.length, pos);
2093
- if (!n) {
2094
- if (!skipping && rest.length) {
2095
- const hit = look(rest);
2096
- if (hit) return hit;
2097
- }
2098
- break;
2099
- }
2100
- pos += n;
2101
- let chunk = rest.length ? Buffer.concat([rest, buf.subarray(0, n)]) : buf.subarray(0, n);
2102
- let nl = chunk.indexOf(10);
2103
- while (nl >= 0) {
2104
- const line = chunk.subarray(0, nl);
2105
- chunk = chunk.subarray(nl + 1);
2106
- if (skipping) skipping = false;
2107
- else {
2108
- const hit = look(line);
2109
- if (hit) return hit;
2110
- }
2111
- nl = chunk.indexOf(10);
2112
- }
2113
- if (chunk.length > 2 * 1024 * 1024) {
2114
- skipping = true;
2115
- rest = Buffer.alloc(0);
2116
- } else rest = skipping ? Buffer.alloc(0) : Buffer.from(chunk);
2117
2097
  }
2118
- } catch {
2119
- } finally {
2120
- if (fd !== void 0)
2098
+ const n = readNotify(text);
2099
+ const argv = cmd2([NAME3, "notify"]).argv;
2100
+ const prev = n ? previousNotify(n.argv) : null;
2101
+ const ours = n && (!n.other || prev && JSON.stringify(prev) === JSON.stringify(argv));
2102
+ out2.push({
2103
+ file: configFile2(env),
2104
+ state: !n || n.other && !ours ? "missing" : n.misplaced ? "misplaced" : JSON.stringify(n.argv) === JSON.stringify(argv) || prev && JSON.stringify(prev) === JSON.stringify(argv) ? "current" : "stale"
2105
+ });
2106
+ return out2;
2107
+ },
2108
+ fromHook(input) {
2109
+ const event = input.argv[1] ?? "";
2110
+ let s = {};
2111
+ if (event === "notify") {
2112
+ const raw = input.argv.find((a) => a.startsWith("{")) ?? input.stdin;
2121
2113
  try {
2122
- closeSync2(fd);
2114
+ s = raw ? JSON.parse(raw) : {};
2123
2115
  } catch {
2124
2116
  }
2117
+ const id2 = s["thread-id"] ?? s.thread_id ?? s["session-id"] ?? s.session_id;
2118
+ if (!id2) return null;
2119
+ const r2 = {
2120
+ session: { id: String(id2), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd },
2121
+ events: [
2122
+ {
2123
+ type: "turn.ended",
2124
+ data: { reason: "stop", ...typeof s["turn-id"] === "string" ? { turn_id: s["turn-id"] } : {} },
2125
+ harnessEvent: "notify"
2126
+ }
2127
+ ],
2128
+ hints: {}
2129
+ };
2130
+ const file = findRollout(String(id2), input.env);
2131
+ if (file) r2.transcript = file;
2132
+ return r2;
2133
+ }
2134
+ try {
2135
+ s = input.stdin ? JSON.parse(input.stdin) : {};
2136
+ } catch {
2137
+ }
2138
+ const id = s.session_id;
2139
+ if (!id) return null;
2140
+ const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
2141
+ if (typeof s.model === "string") session.model = s.model;
2142
+ const turn_id = typeof s.turn_id === "string" ? s.turn_id : void 0;
2143
+ const tool = validTool3(s.tool_name) ? s.tool_name : void 0;
2144
+ const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
2145
+ const ev = (type, data) => ({ type, data: prune3(data), harnessEvent: event });
2146
+ const events = [];
2147
+ switch (event) {
2148
+ case "SessionStart":
2149
+ events.push(
2150
+ ev("session.started", {
2151
+ source: typeof s.source === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(s.source) ? s.source : "unknown"
2152
+ })
2153
+ );
2154
+ break;
2155
+ case "UserPromptSubmit":
2156
+ events.push(
2157
+ ev("turn.started", {
2158
+ turn_id,
2159
+ prompt_chars: typeof s.prompt === "string" ? [...s.prompt].length : void 0
2160
+ })
2161
+ );
2162
+ break;
2163
+ case "PreToolUse":
2164
+ if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, input: s.tool_input }));
2165
+ break;
2166
+ case "PostToolUse":
2167
+ if (tool)
2168
+ events.push(
2169
+ ev("tool.ended", { tool, call_id, turn_id, ok: true, input: s.tool_input, output: s.tool_response })
2170
+ );
2171
+ break;
2172
+ case "PermissionRequest":
2173
+ events.push(
2174
+ ev("attention.needed", { attention_id: call_id ?? `perm-${Date.now()}`, kind: "permission", tool })
2175
+ );
2176
+ break;
2177
+ case "Stop":
2178
+ events.push(ev("turn.ended", { turn_id, reason: "stop" }));
2179
+ break;
2180
+ case "Interrupt":
2181
+ events.push(ev("turn.ended", { turn_id, reason: "interrupt" }));
2182
+ break;
2183
+ case "SubagentStart":
2184
+ events.push(
2185
+ ev("subagent.started", {
2186
+ agent_id: String(s.agent_id ?? s.subagent_id ?? ""),
2187
+ agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
2188
+ })
2189
+ );
2190
+ break;
2191
+ case "SubagentStop":
2192
+ events.push(
2193
+ ev("subagent.ended", {
2194
+ agent_id: String(s.agent_id ?? s.subagent_id ?? ""),
2195
+ agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
2196
+ })
2197
+ );
2198
+ break;
2199
+ case "PreCompact":
2200
+ events.push(ev("context.compacted", { trigger: s.trigger === "manual" ? "manual" : "auto" }));
2201
+ break;
2202
+ case "PostCompact":
2203
+ break;
2204
+ case "SessionEnd":
2205
+ events.push(ev("session.ended", { reason: endReason2(s.reason) }));
2206
+ break;
2207
+ default:
2208
+ return null;
2209
+ }
2210
+ const r = { session, events, hints: {} };
2211
+ if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
2212
+ return r;
2213
+ },
2214
+ readTranscript(file, fromLine) {
2215
+ let text = "";
2216
+ try {
2217
+ text = readFileSync5(file, "utf8");
2218
+ } catch {
2219
+ return { turns: [], line: fromLine };
2220
+ }
2221
+ return readCodex(text.split("\n"), fromLine);
2222
+ },
2223
+ facts(session, transcript) {
2224
+ const out2 = {};
2225
+ if (transcript) Object.assign(out2, codexFacts(transcript));
2226
+ return out2;
2227
+ },
2228
+ backfill(sinceMs, env = process.env) {
2229
+ const rows = [];
2230
+ for (const f of recentFiles(sessionsDir(env), 3, sinceMs, (n) => /^rollout-.*\.jsonl$/.test(n))) {
2231
+ const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(path9.basename(f));
2232
+ if (!m?.[1]) continue;
2233
+ const facts = codexFacts(f);
2234
+ if (!facts.started_at || !facts.last_at) continue;
2235
+ const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
2236
+ rows.push({ session: { id: m[1], ...rest }, started_at, last_at });
2237
+ }
2238
+ return rows;
2125
2239
  }
2126
- return first;
2240
+ };
2241
+ function prune3(o) {
2242
+ for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
2243
+ return o;
2127
2244
  }
2128
- function pidAlive(pid) {
2129
- if (!Number.isSafeInteger(pid) || pid <= 1) return false;
2130
- try {
2131
- process.kill(pid, 0);
2132
- return true;
2133
- } catch (e) {
2134
- return e.code === "EPERM";
2245
+ function codexFacts(file) {
2246
+ const { head, tail: tail2 } = ends(file, 128 * 1024, 64 * 1024);
2247
+ const H = parseLines(head);
2248
+ const T = parseLines(tail2);
2249
+ const out2 = {};
2250
+ const meta = H.find((j) => j.type === "session_meta" && j.payload);
2251
+ if (meta?.payload) {
2252
+ const p = meta.payload;
2253
+ if (p.cwd) out2.cwd = p.cwd;
2254
+ if (p.cli_version) out2.harness_version = String(p.cli_version);
2255
+ if (p.git?.branch) out2.branch = p.git.branch;
2256
+ if (p.git?.repository_url) out2.repo = String(p.git.repository_url).replace(/\/\/[^/@]*@/, "//");
2257
+ }
2258
+ const ctx = H.concat(T).reverse().find((j) => j.type === "turn_context" && j.payload?.model);
2259
+ if (ctx?.payload?.model) out2.model = ctx.payload.model;
2260
+ for (const j of H) {
2261
+ const p = j.payload;
2262
+ if (j.type !== "response_item" || !p || p.type !== "message" || p.role !== "user") continue;
2263
+ const text = (p.content ?? []).filter((b) => b && b.type === "input_text" && typeof b.text === "string").map((b) => b.text).join("\n").trim();
2264
+ if (!text || codexInjected(text)) continue;
2265
+ const line = text.split("\n").find((l) => l.trim()) ?? "";
2266
+ out2.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
2267
+ out2.title_source = "first-ask";
2268
+ break;
2269
+ }
2270
+ const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
2271
+ if (times.length) {
2272
+ out2.started_at = Math.min(...times);
2273
+ out2.last_at = Math.max(...times);
2135
2274
  }
2275
+ return out2;
2136
2276
  }
2137
- function liveProcess(session, configDir) {
2138
- let names = [];
2277
+
2278
+ // ../core/src/adapters/codex-control.ts
2279
+ var codex_control_exports = {};
2280
+ __export(codex_control_exports, {
2281
+ CodexReading: () => CodexReading,
2282
+ LOGIN_HINT: () => LOGIN_HINT,
2283
+ codexArgs: () => codexArgs,
2284
+ codexEnv: () => codexEnv,
2285
+ codexFacts: () => codexFacts,
2286
+ codexLine: () => codexLine,
2287
+ codexLogin: () => codexLogin,
2288
+ codexModeFlags: () => codexModeFlags,
2289
+ findCodex: () => findCodex,
2290
+ findRollout: () => findRollout,
2291
+ reading: () => reading2
2292
+ });
2293
+ import { execFile as execFile2 } from "node:child_process";
2294
+ import os4 from "node:os";
2295
+ import path10 from "node:path";
2296
+ var LABEL2 = "Codex";
2297
+ function findCodex(env = process.env, home = os4.homedir(), nodeDir = path10.dirname(process.execPath)) {
2298
+ return findBinary({
2299
+ override: env.SESSIONPIPE_CODEX,
2300
+ names: process.platform === "win32" ? ["codex.exe", "codex.cmd"] : ["codex"],
2301
+ env,
2302
+ dirs: [nodeDir, path10.join(home, ".local", "bin"), "/opt/homebrew/bin", "/usr/local/bin"]
2303
+ });
2304
+ }
2305
+ function codexModeFlags(mode, resume = false) {
2306
+ if (resume)
2307
+ return mode === "auto" ? [
2308
+ "-c",
2309
+ 'sandbox_mode="workspace-write"',
2310
+ "-c",
2311
+ 'approval_policy="on-request"',
2312
+ "-c",
2313
+ 'approvals_reviewer="auto_review"'
2314
+ ] : ["-c", 'sandbox_mode="workspace-write"'];
2315
+ return mode === "auto" ? ["--approve-for-me"] : ["-s", "workspace-write"];
2316
+ }
2317
+ function codexArgs(o) {
2318
+ const common = ["--json", "--skip-git-repo-check"];
2319
+ if (o.resume) return ["exec", "resume", ...common, ...codexModeFlags(o.mode, true), "--", o.resume, o.prompt];
2320
+ return ["exec", ...common, ...o.cwd ? ["-C", o.cwd] : [], ...codexModeFlags(o.mode), "--", o.prompt];
2321
+ }
2322
+ var codexEnv = (env = process.env) => jobEnv(env);
2323
+ var LOGIN_HINT = "Codex isn't signed in on this machine: run `codex login --device-auth` there.";
2324
+ function codexLogin(bin, env = process.env) {
2325
+ if (env.CODEX_API_KEY) return Promise.resolve({ loggedIn: true, method: "api key" });
2326
+ return new Promise((resolve) => {
2327
+ execFile2(
2328
+ bin,
2329
+ ["login", "status"],
2330
+ { encoding: "utf8", timeout: 2e4, env: codexEnv(env), maxBuffer: 256 * 1024 },
2331
+ (err) => {
2332
+ if (!err) return resolve({ loggedIn: true });
2333
+ const code = err.code;
2334
+ resolve(code === 1 ? { loggedIn: false } : { loggedIn: null });
2335
+ }
2336
+ );
2337
+ });
2338
+ }
2339
+ function codexLine(line) {
2340
+ let j;
2139
2341
  try {
2140
- names = readdirSync5(path8.join(configDir, "sessions")).filter((n) => /^\d+\.json$/.test(n));
2342
+ j = JSON.parse(line);
2141
2343
  } catch {
2142
- return false;
2344
+ return null;
2143
2345
  }
2144
- for (const n of names) {
2145
- try {
2146
- const j = JSON.parse(readFileSync6(path8.join(configDir, "sessions", n), "utf8"));
2147
- if (String(j.sessionId) !== session) continue;
2148
- if (pidAlive(Number(j.pid ?? n.slice(0, -5)))) return true;
2149
- } catch {
2346
+ if (!j || typeof j !== "object") return null;
2347
+ const str = (v) => typeof v === "string" ? v : "";
2348
+ switch (j.type) {
2349
+ case "thread.started":
2350
+ return str(j.thread_id) ? { session: str(j.thread_id) } : null;
2351
+ case "item.completed": {
2352
+ const it = j.item;
2353
+ if (it?.type === "agent_message" && str(it.text)) return { message: str(it.text) };
2354
+ if (it?.type === "error" && str(it.message)) return { warning: str(it.message) };
2355
+ return null;
2150
2356
  }
2357
+ case "turn.completed":
2358
+ return { done: true };
2359
+ case "turn.failed":
2360
+ return { done: true, failed: str(j.error?.message) || "Codex reported an error" };
2361
+ case "error":
2362
+ return str(j.message) ? { note: str(j.message) } : null;
2363
+ default:
2364
+ return null;
2151
2365
  }
2152
- return false;
2153
2366
  }
2154
- function recentlyWritten(file, ourLastEnd, now = Date.now()) {
2155
- let m;
2367
+ var CodexReading = class {
2368
+ session = null;
2369
+ lines = 0;
2370
+ warnings = [];
2371
+ messages = [];
2372
+ done = false;
2373
+ failed = null;
2374
+ note = null;
2375
+ get text() {
2376
+ return this.messages.join("\n\n");
2377
+ }
2378
+ feed(line) {
2379
+ this.lines++;
2380
+ const p = codexLine(line);
2381
+ if (!p) return false;
2382
+ if (p.session && !this.session) this.session = p.session;
2383
+ if (p.note) this.note = p.note;
2384
+ if (p.warning && this.warnings.length < 20) this.warnings.push(p.warning);
2385
+ if (p.failed) this.failed = p.failed;
2386
+ if (p.done) this.done = true;
2387
+ if (p.message?.trim()) {
2388
+ this.messages.push(p.message.trim());
2389
+ return true;
2390
+ }
2391
+ return false;
2392
+ }
2393
+ /** A failure is `turn.failed`, or a run that exits non-zero having said nothing; an
2394
+ * `error` item before a turn that then succeeds is only a warning. */
2395
+ outcome(res) {
2396
+ const broke = runFailure(LABEL2, res);
2397
+ if (broke) return { failed: broke };
2398
+ if (this.failed) return { failed: this.failed };
2399
+ if (this.done || res.code === 0 || this.messages.length)
2400
+ return { reply: this.text || "(Codex finished without a written reply.)" };
2401
+ const why = this.note || codexStderr(res.stderr) || this.warnings.at(-1) || "";
2402
+ return { failed: `Codex exited (${res.signal || `code ${res.code}`}) without an answer${why ? `: ${why}` : ""}` };
2403
+ }
2404
+ };
2405
+ function codexStderr(stderr) {
2406
+ return stderrTail(
2407
+ String(stderr || "").split("\n").filter((l) => !/^Reading additional input from stdin/.test(l.trim())).join("\n")
2408
+ );
2409
+ }
2410
+ var reading2 = () => new CodexReading();
2411
+
2412
+ // ../core/src/adapters/gemini-cli.ts
2413
+ import { execFileSync as execFileSync4 } from "node:child_process";
2414
+ import { existsSync as existsSync5, readFileSync as readFileSync6 } from "node:fs";
2415
+ import path11 from "node:path";
2416
+ var NAME4 = "gemini-cli";
2417
+ var GEMINI_EVENTS = [
2418
+ "SessionStart",
2419
+ "BeforeAgent",
2420
+ "BeforeTool",
2421
+ "AfterTool",
2422
+ "Notification",
2423
+ "AfterAgent",
2424
+ "PreCompress",
2425
+ "SessionEnd"
2426
+ ];
2427
+ var OPTS3 = { matcherFor: (e) => /Tool/.test(e) ? ".*" : void 0, timeout: 5e3 };
2428
+ var geminiDir = (env) => env.GEMINI_CLI_HOME || path11.join(HOME, ".gemini");
2429
+ var settings = (env) => path11.join(geminiDir(env), "settings.json");
2430
+ var validTool4 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
2431
+ var eventsFor3 = (lean) => lean ? GEMINI_EVENTS.filter((e) => e !== "BeforeTool") : [...GEMINI_EVENTS];
2432
+ function geminiHooksActive(settingsFile) {
2433
+ let tools;
2156
2434
  try {
2157
- m = statSync4(file).mtimeMs;
2435
+ tools = JSON.parse(readFileSync6(settingsFile, "utf8")).tools;
2158
2436
  } catch {
2159
- return false;
2160
2437
  }
2161
- if (now - m > LIVE_WRITE_MS) return false;
2162
- return !(ourLastEnd && m <= ourLastEnd + 2e3);
2438
+ if (tools?.enableHooks === true) return { active: true };
2439
+ if (tools?.enableHooks === false)
2440
+ return { active: false, note: "inactive: tools.enableHooks is false in settings.json" };
2441
+ let version = "";
2442
+ try {
2443
+ version = execFileSync4("gemini", ["--version"], {
2444
+ encoding: "utf8",
2445
+ timeout: 4e3,
2446
+ stdio: ["ignore", "pipe", "ignore"]
2447
+ }).trim();
2448
+ } catch {
2449
+ return {
2450
+ active: true,
2451
+ note: "could not run `gemini --version`; hooks need tools.enableHooks on versions before 0.46"
2452
+ };
2453
+ }
2454
+ const m = /(\d+)\.(\d+)/.exec(version);
2455
+ if (m && (Number(m[1]) > 0 || Number(m[2]) >= 46)) return { active: true };
2456
+ return {
2457
+ active: false,
2458
+ note: `inactive: Gemini CLI ${version || "?"} runs hooks only with "tools": {"enableHooks": true} in settings.json`
2459
+ };
2163
2460
  }
2164
- function runClaude(bin, args2, o) {
2165
- return new Promise((resolve) => {
2166
- let out2 = [];
2167
- let outLen = 0;
2168
- let tooBig = false;
2169
- let errTail = "";
2170
- let timedOut = false;
2171
- let settled = false;
2172
- let child;
2461
+ var geminiCli = {
2462
+ name: NAME4,
2463
+ events: GEMINI_EVENTS,
2464
+ // ~/.gemini also belongs to Antigravity; Gemini CLI is here when its settings or its chat logs exist.
2465
+ detect: (env = process.env) => existsSync5(settings(env)) || existsSync5(path11.join(geminiDir(env), "tmp")),
2466
+ configFiles: (env = process.env) => [settings(env)],
2467
+ install: (cmd2, env = process.env, opts = {}) => {
2468
+ const r = installClaudeShaped(settings(env), NAME4, eventsFor3(opts.lean), cmd2, OPTS3);
2469
+ const a = geminiHooksActive(settings(env));
2470
+ return [a.note && !r.skipped ? { ...r, note: [r.note, a.note].filter(Boolean).join("; ") } : r];
2471
+ },
2472
+ uninstall: (env = process.env, opts = {}) => [
2473
+ uninstallClaudeShaped(settings(env), GEMINI_EVENTS, { created: opts.created?.includes(settings(env)) })
2474
+ ],
2475
+ installed: (cmd2, env = process.env, opts = {}) => {
2476
+ const state2 = installedClaudeShaped(settings(env), NAME4, eventsFor3(opts.lean), cmd2, OPTS3);
2477
+ const a = geminiHooksActive(settings(env));
2478
+ return [
2479
+ {
2480
+ file: settings(env),
2481
+ state: state2 === "current" && !a.active ? "inactive" : state2,
2482
+ ...a.note ? { note: a.note } : {}
2483
+ }
2484
+ ];
2485
+ },
2486
+ fromHook(input) {
2487
+ const event = input.argv[1] ?? "";
2488
+ let s = {};
2173
2489
  try {
2174
- child = spawn(bin, args2, {
2175
- cwd: o.cwd,
2176
- env: o.env,
2177
- stdio: ["ignore", "pipe", "pipe"],
2178
- detached: process.platform !== "win32",
2179
- windowsHide: true
2180
- });
2181
- } catch (e) {
2182
- resolve({ error: e.message });
2183
- return;
2490
+ s = input.stdin ? JSON.parse(input.stdin) : {};
2491
+ } catch {
2184
2492
  }
2185
- const kill = (sig) => {
2186
- try {
2187
- if (process.platform !== "win32" && child.pid) process.kill(-child.pid, sig);
2188
- else child.kill(sig);
2189
- } catch {
2190
- try {
2191
- child.kill(sig);
2192
- } catch {
2193
- }
2194
- }
2195
- };
2196
- let partial = "";
2197
- child.stdout?.on("data", (b) => {
2198
- if (o.onLine) {
2199
- const chunk = partial + b.toString("utf8");
2200
- const lines = chunk.split("\n");
2201
- partial = lines.pop() ?? "";
2202
- for (const line of lines) {
2203
- if (!line.trim()) continue;
2204
- try {
2205
- o.onLine(line);
2206
- } catch {
2207
- }
2208
- if (line.includes('"type":"result"')) {
2209
- out2 = [Buffer.from(line)];
2210
- outLen = line.length;
2211
- }
2212
- }
2213
- if (partial.length > OUT_CAP) partial = "";
2214
- return;
2215
- }
2216
- if (tooBig) return;
2217
- if (outLen + b.length > OUT_CAP) {
2218
- tooBig = true;
2219
- out2 = [];
2220
- return;
2221
- }
2222
- out2.push(b);
2223
- outLen += b.length;
2224
- });
2225
- child.stderr?.on("data", (b) => {
2226
- errTail = (errTail + b.toString("utf8")).slice(-4e3);
2227
- });
2228
- const timer = setTimeout(() => {
2229
- timedOut = true;
2230
- kill("SIGTERM");
2231
- setTimeout(() => kill("SIGKILL"), 1e4).unref();
2232
- }, o.timeoutMs ?? JOB_TIMEOUT_MS);
2233
- const result = (code, signal) => ({
2234
- code,
2235
- signal,
2236
- stdout: tooBig ? null : Buffer.concat(out2).toString("utf8"),
2237
- tooBig,
2238
- stderr: errTail,
2239
- timedOut
2240
- });
2241
- const finish = (r) => {
2242
- if (settled) return;
2243
- settled = true;
2244
- clearTimeout(timer);
2245
- resolve(r);
2246
- };
2247
- child.on("error", (e) => finish({ error: e.message }));
2248
- child.on("exit", (code, signal) => setTimeout(() => finish(result(code, signal)), 2e3).unref());
2249
- child.on("close", (code, signal) => {
2250
- if (o.onLine && partial.trim()) {
2251
- try {
2252
- o.onLine(partial);
2253
- } catch {
2254
- }
2255
- if (partial.includes('"type":"result"')) {
2256
- out2 = [Buffer.from(partial)];
2257
- outLen = partial.length;
2258
- }
2259
- partial = "";
2493
+ const id = s.session_id;
2494
+ if (!id) return null;
2495
+ const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
2496
+ const tool = validTool4(s.tool_name) ? s.tool_name : void 0;
2497
+ const ev = (type, data) => ({ type, data: prune4(data), harnessEvent: event });
2498
+ const events = [];
2499
+ switch (event) {
2500
+ case "SessionStart":
2501
+ events.push(
2502
+ ev("session.started", {
2503
+ source: typeof s.source === "string" && ["startup", "resume", "clear"].includes(s.source) ? s.source : "unknown"
2504
+ })
2505
+ );
2506
+ break;
2507
+ case "BeforeAgent":
2508
+ events.push(
2509
+ ev("turn.started", { prompt_chars: typeof s.prompt === "string" ? [...s.prompt].length : void 0 })
2510
+ );
2511
+ break;
2512
+ case "BeforeTool":
2513
+ if (tool) events.push(ev("tool.started", { tool, input: s.tool_input }));
2514
+ break;
2515
+ case "AfterTool": {
2516
+ const resp = s.tool_response;
2517
+ const err = resp && typeof resp === "object" && resp.error ? String(typeof resp.error === "string" ? resp.error : JSON.stringify(resp.error)).slice(0, 2e3) : void 0;
2518
+ if (tool)
2519
+ events.push(
2520
+ ev("tool.ended", {
2521
+ tool,
2522
+ ok: !err,
2523
+ error: err,
2524
+ input: s.tool_input,
2525
+ output: resp?.llmContent ?? s.tool_response
2526
+ })
2527
+ );
2528
+ break;
2260
2529
  }
2261
- finish(result(code, signal));
2262
- });
2263
- });
2264
- }
2265
- function parseResult(stdout) {
2266
- const s = String(stdout || "").trim();
2267
- if (!s) return null;
2268
- const pick = (j) => {
2269
- if (Array.isArray(j)) {
2270
- for (let i = j.length - 1; i >= 0; i--) if (j[i]?.type === "result") return j[i];
2271
- return null;
2530
+ case "Notification":
2531
+ events.push(
2532
+ ev("attention.needed", {
2533
+ attention_id: `notif-${Date.now()}`,
2534
+ kind: s.notification_type === "ToolPermission" ? "permission" : "question",
2535
+ message: typeof s.message === "string" ? s.message.slice(0, 200) : void 0
2536
+ })
2537
+ );
2538
+ break;
2539
+ case "AfterAgent":
2540
+ events.push(ev("turn.ended", { reason: "stop" }));
2541
+ break;
2542
+ case "PreCompress":
2543
+ events.push(ev("context.compacted", { trigger: s.trigger === "manual" ? "manual" : "auto" }));
2544
+ break;
2545
+ case "SessionEnd":
2546
+ events.push(
2547
+ ev("session.ended", {
2548
+ reason: s.reason === "clear" || s.reason === "logout" || s.reason === "exit" ? s.reason : s.reason === "prompt_input_exit" ? "exit" : "other"
2549
+ })
2550
+ );
2551
+ break;
2552
+ default:
2553
+ return null;
2272
2554
  }
2273
- return j && typeof j === "object" ? j : null;
2274
- };
2275
- try {
2276
- return pick(JSON.parse(s));
2277
- } catch {
2278
- }
2279
- const lines = s.split("\n");
2280
- for (let i = lines.length - 1; i >= 0; i--) {
2555
+ const r = { session, events, hints: {} };
2556
+ if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
2557
+ return r;
2558
+ },
2559
+ readTranscript(file, fromLine) {
2560
+ let text = "";
2281
2561
  try {
2282
- const j = pick(JSON.parse(lines[i]));
2283
- if (j && (j.type === "result" || "result" in j)) return j;
2562
+ text = readFileSync6(file, "utf8");
2284
2563
  } catch {
2564
+ return { turns: [], line: fromLine };
2285
2565
  }
2286
- }
2287
- return null;
2566
+ return readGemini(text.split("\n"), fromLine);
2567
+ },
2568
+ facts(_session, transcript) {
2569
+ const out2 = {};
2570
+ if (!transcript) return out2;
2571
+ const { head, tail: tail2 } = ends(transcript, 64 * 1024, 64 * 1024);
2572
+ const H = parseLines(head);
2573
+ const T = parseLines(tail2);
2574
+ const first = H.find((j) => j.type === "user");
2575
+ const raw = first ? first.content ?? first.message ?? first.text : void 0;
2576
+ const text = typeof raw === "string" ? raw.trim() : "";
2577
+ if (text) {
2578
+ const line = text.split("\n").find((l) => l.trim()) ?? "";
2579
+ out2.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
2580
+ out2.title_source = "first-ask";
2581
+ }
2582
+ const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
2583
+ if (times.length) {
2584
+ out2.started_at = Math.min(...times);
2585
+ out2.last_at = Math.max(...times);
2586
+ }
2587
+ return out2;
2588
+ },
2589
+ backfill: () => []
2590
+ // chat logs are keyed by a project hash, not a session id we can name; the first hook of a resumed session reports it
2591
+ };
2592
+ function prune4(o) {
2593
+ for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
2594
+ return o;
2288
2595
  }
2289
- var isUuid = (s) => typeof s === "string" && UUID_RE.test(s);
2290
2596
 
2291
2597
  // ../core/src/adapters/index.ts
2292
2598
  var ADAPTERS = [claudeCode, codex, geminiCli, antigravity];
2293
2599
  var adapterByName = (name) => ADAPTERS.find((a) => a.name === name);
2600
+ function drivableHarnesses(env = process.env) {
2601
+ return [
2602
+ ...findClaude(env) ? ["claude-code"] : [],
2603
+ ...findCodex(env) ? ["codex"] : [],
2604
+ ...findAgy(env) ? ["antigravity"] : []
2605
+ ];
2606
+ }
2294
2607
 
2295
2608
  // ../core/src/config.ts
2296
2609
  import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
2297
- import path9 from "node:path";
2610
+ import path12 from "node:path";
2298
2611
  var DEFAULT_CONFIG = { sinks: [], harnesses: {}, update_check: true, keep_days: 30 };
2299
2612
  function readConfig(file = configFile()) {
2300
2613
  try {
@@ -2310,7 +2623,7 @@ function readConfig(file = configFile()) {
2310
2623
  }
2311
2624
  }
2312
2625
  function writeConfig(c, file = configFile()) {
2313
- mkdirSync4(path9.dirname(file), { recursive: true, mode: 448 });
2626
+ mkdirSync4(path12.dirname(file), { recursive: true, mode: 448 });
2314
2627
  writeFileSync4(file, `${JSON.stringify(c, null, 2)}
2315
2628
  `, { mode: 384 });
2316
2629
  if (process.platform !== "win32" && existsSync6(file)) {
@@ -2386,24 +2699,24 @@ import {
2386
2699
  readFileSync as readFileSync8,
2387
2700
  readSync as readSync3,
2388
2701
  renameSync,
2389
- statSync as statSync5,
2702
+ statSync as statSync6,
2390
2703
  unlinkSync as unlinkSync3,
2391
2704
  writeFileSync as writeFileSync5
2392
2705
  } from "node:fs";
2393
- import path10 from "node:path";
2706
+ import path13 from "node:path";
2394
2707
  var Outbox = class {
2395
2708
  constructor(root) {
2396
2709
  this.root = root;
2397
2710
  }
2398
2711
  root;
2399
2712
  dir(harness) {
2400
- return path10.join(this.root, "outbox", safeId(harness));
2713
+ return path13.join(this.root, "outbox", safeId(harness));
2401
2714
  }
2402
2715
  file(ref) {
2403
- return path10.join(this.dir(ref.harness), `${safeId(ref.session)}.jsonl`);
2716
+ return path13.join(this.dir(ref.harness), `${safeId(ref.session)}.jsonl`);
2404
2717
  }
2405
2718
  seqFile(ref) {
2406
- return path10.join(this.dir(ref.harness), `${safeId(ref.session)}.seq`);
2719
+ return path13.join(this.dir(ref.harness), `${safeId(ref.session)}.seq`);
2407
2720
  }
2408
2721
  /** The next seq for a session, persisted (survives a hibernating machine and a
2409
2722
  * restart; an in-memory counter would restart at 0 and collide). */
@@ -2415,7 +2728,7 @@ var Outbox = class {
2415
2728
  } catch {
2416
2729
  n = this.lastSeqInFile(ref) + 1;
2417
2730
  }
2418
- mkdirSync5(path10.dirname(f), { recursive: true, mode: 448 });
2731
+ mkdirSync5(path13.dirname(f), { recursive: true, mode: 448 });
2419
2732
  writeFileSync5(f, String(n + 1), { mode: 384 });
2420
2733
  return n;
2421
2734
  }
@@ -2441,10 +2754,10 @@ var Outbox = class {
2441
2754
  /** Every session file, newest first. */
2442
2755
  sessions() {
2443
2756
  const out2 = [];
2444
- const base = path10.join(this.root, "outbox");
2757
+ const base = path13.join(this.root, "outbox");
2445
2758
  if (!existsSync7(base)) return [];
2446
2759
  for (const h of readdirSync6(base)) {
2447
- const d = path10.join(base, h);
2760
+ const d = path13.join(base, h);
2448
2761
  let names = [];
2449
2762
  try {
2450
2763
  names = readdirSync6(d).filter((n) => n.endsWith(".jsonl"));
@@ -2453,7 +2766,7 @@ var Outbox = class {
2453
2766
  }
2454
2767
  for (const n of names) {
2455
2768
  try {
2456
- out2.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime: statSync5(path10.join(d, n)).mtimeMs });
2769
+ out2.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime: statSync6(path13.join(d, n)).mtimeMs });
2457
2770
  } catch {
2458
2771
  }
2459
2772
  }
@@ -2465,7 +2778,7 @@ var Outbox = class {
2465
2778
  read(ref, from, maxEvents = 500) {
2466
2779
  const f = this.file(ref);
2467
2780
  if (!existsSync7(f)) return { events: [], offset: from };
2468
- const size = statSync5(f).size;
2781
+ const size = statSync6(f).size;
2469
2782
  if (from >= size) return { events: [], offset: from };
2470
2783
  const fd = openSync3(f, "r");
2471
2784
  try {
@@ -2499,7 +2812,7 @@ var Outbox = class {
2499
2812
  for (const ref of this.sessions()) {
2500
2813
  const f = this.file(ref);
2501
2814
  try {
2502
- if (statSync5(f).mtimeMs < cutoff) {
2815
+ if (statSync6(f).mtimeMs < cutoff) {
2503
2816
  unlinkSync3(f);
2504
2817
  try {
2505
2818
  unlinkSync3(this.seqFile(ref));
@@ -2529,7 +2842,7 @@ var Cursors = class {
2529
2842
  root;
2530
2843
  sink;
2531
2844
  file() {
2532
- return path10.join(this.root, "cursors", `${safeId(this.sink)}.json`);
2845
+ return path13.join(this.root, "cursors", `${safeId(this.sink)}.json`);
2533
2846
  }
2534
2847
  read() {
2535
2848
  try {
@@ -2540,7 +2853,7 @@ var Cursors = class {
2540
2853
  }
2541
2854
  write(c) {
2542
2855
  const f = this.file();
2543
- mkdirSync5(path10.dirname(f), { recursive: true, mode: 448 });
2856
+ mkdirSync5(path13.dirname(f), { recursive: true, mode: 448 });
2544
2857
  const tmp = `${f}.${process.pid}.tmp`;
2545
2858
  writeFileSync5(tmp, JSON.stringify(c), { mode: 384 });
2546
2859
  renameSync(tmp, f);
@@ -2550,11 +2863,11 @@ var Cursors = class {
2550
2863
  }
2551
2864
  };
2552
2865
  function takeLock(root, ref, staleMs = 5 * 6e4) {
2553
- const dir = path10.join(root, "locks");
2866
+ const dir = path13.join(root, "locks");
2554
2867
  mkdirSync5(dir, { recursive: true, mode: 448 });
2555
- const f = path10.join(dir, `${safeId(ref.harness)}-${safeId(ref.session)}.lock`);
2868
+ const f = path13.join(dir, `${safeId(ref.harness)}-${safeId(ref.session)}.lock`);
2556
2869
  try {
2557
- if (Date.now() - statSync5(f).mtimeMs > staleMs) unlinkSync3(f);
2870
+ if (Date.now() - statSync6(f).mtimeMs > staleMs) unlinkSync3(f);
2558
2871
  else return null;
2559
2872
  } catch {
2560
2873
  }
@@ -2868,7 +3181,7 @@ function filterEvent(event, tier, pii, opts = {}) {
2868
3181
 
2869
3182
  // ../core/src/sinks/file.ts
2870
3183
  import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync6 } from "node:fs";
2871
- import path11 from "node:path";
3184
+ import path14 from "node:path";
2872
3185
  var FileSink = class {
2873
3186
  constructor(name, file, tier, pii) {
2874
3187
  this.name = name;
@@ -2882,7 +3195,7 @@ var FileSink = class {
2882
3195
  pii;
2883
3196
  async send(events) {
2884
3197
  try {
2885
- mkdirSync6(path11.dirname(this.file), { recursive: true });
3198
+ mkdirSync6(path14.dirname(this.file), { recursive: true });
2886
3199
  appendFileSync2(this.file, `${events.map((e) => JSON.stringify(e)).join("\n")}
2887
3200
  `);
2888
3201
  return { ok: true, acknowledged: events.length, accepted: events.length, duplicates: 0, rejected: [] };
@@ -2912,23 +3225,23 @@ var StdoutSink = class {
2912
3225
  };
2913
3226
 
2914
3227
  // src/connect.ts
2915
- import path12 from "node:path";
3228
+ import path15 from "node:path";
2916
3229
  function isBroad(dir, home) {
2917
- const d = path12.resolve(dir);
2918
- const h = path12.resolve(home);
2919
- if (d === path12.parse(d).root) return true;
2920
- return d === h || h.startsWith(d.endsWith(path12.sep) ? d : d + path12.sep);
3230
+ const d = path15.resolve(dir);
3231
+ const h = path15.resolve(home);
3232
+ if (d === path15.parse(d).root) return true;
3233
+ return d === h || h.startsWith(d.endsWith(path15.sep) ? d : d + path15.sep);
2921
3234
  }
2922
- var inside = (child, parent) => child !== parent && child.startsWith(parent.endsWith(path12.sep) ? parent : parent + path12.sep);
3235
+ var inside = (child, parent) => child !== parent && child.startsWith(parent.endsWith(path15.sep) ? parent : parent + path15.sep);
2923
3236
  function connectFolders(o) {
2924
- if (o.flags.length) return { folders: o.flags.map((d) => path12.resolve(d)), from: "flags" };
3237
+ if (o.flags.length) return { folders: o.flags.map((d) => path15.resolve(d)), from: "flags" };
2925
3238
  if (o.existing.length) return { folders: [], from: "existing" };
2926
- if (!isBroad(o.cwd, o.home)) return { folders: [path12.resolve(o.cwd)], from: "here" };
3239
+ if (!isBroad(o.cwd, o.home)) return { folders: [path15.resolve(o.cwd)], from: "here" };
2927
3240
  const exists = o.exists ?? (() => true);
2928
3241
  const picked = [];
2929
3242
  for (const raw of o.recent) {
2930
- if (!raw || !path12.isAbsolute(raw)) continue;
2931
- const d = path12.resolve(raw);
3243
+ if (!raw || !path15.isAbsolute(raw)) continue;
3244
+ const d = path15.resolve(raw);
2932
3245
  if (isBroad(d, o.home) || picked.includes(d) || !exists(d)) continue;
2933
3246
  if (picked.some((p) => inside(d, p))) continue;
2934
3247
  for (let i = picked.length - 1; i >= 0; i--) if (inside(picked[i], d)) picked.splice(i, 1);
@@ -2945,43 +3258,79 @@ function connectTier(flag2, current) {
2945
3258
 
2946
3259
  // src/control/autoupdate.ts
2947
3260
  import { spawn as spawn3 } from "node:child_process";
2948
- import { readFileSync as readFileSync11 } from "node:fs";
2949
- import path18 from "node:path";
3261
+ import { readFileSync as readFileSync12 } from "node:fs";
3262
+ import path22 from "node:path";
2950
3263
 
2951
3264
  // src/update.ts
2952
3265
  import { spawn as spawn2 } from "node:child_process";
2953
3266
  import { accessSync as accessSync2, constants as constants2, existsSync as existsSync9, realpathSync as realpathSync3 } from "node:fs";
2954
- import path16 from "node:path";
3267
+ import path20 from "node:path";
2955
3268
 
2956
3269
  // src/runtime.ts
2957
3270
  import { execFileSync as execFileSync5 } from "node:child_process";
2958
3271
  import { realpathSync as realpathSync2 } from "node:fs";
2959
- import os5 from "node:os";
2960
- import path15 from "node:path";
3272
+ import os7 from "node:os";
3273
+ import path19 from "node:path";
2961
3274
 
2962
3275
  // src/run.ts
2963
3276
  import {
2964
3277
  appendFileSync as appendFileSync3,
2965
3278
  existsSync as existsSync8,
2966
- mkdirSync as mkdirSync7,
3279
+ mkdirSync as mkdirSync8,
2967
3280
  readdirSync as readdirSync7,
2968
- readFileSync as readFileSync9,
2969
- renameSync as renameSync2,
2970
- statSync as statSync6,
3281
+ readFileSync as readFileSync10,
3282
+ renameSync as renameSync3,
3283
+ statSync as statSync7,
2971
3284
  unlinkSync as unlinkSync4,
2972
- writeFileSync as writeFileSync6
3285
+ writeFileSync as writeFileSync7
2973
3286
  } from "node:fs";
2974
- import os4 from "node:os";
2975
- import path14 from "node:path";
3287
+ import os6 from "node:os";
3288
+ import path18 from "node:path";
3289
+
3290
+ // src/control/aliases.ts
3291
+ import { chmodSync as chmodSync2, mkdirSync as mkdirSync7, readFileSync as readFileSync9, renameSync as renameSync2, writeFileSync as writeFileSync6 } from "node:fs";
3292
+ import path16 from "node:path";
3293
+ var aliasFile = (controlDir) => path16.join(controlDir, "aliases.json");
3294
+ function readAliases(file) {
3295
+ try {
3296
+ const j = JSON.parse(readFileSync9(file, "utf8"));
3297
+ if (!j || typeof j !== "object" || Array.isArray(j)) return {};
3298
+ const out2 = {};
3299
+ for (const [k, v] of Object.entries(j)) {
3300
+ const a = v;
3301
+ if (a && typeof a.id === "string" && a.id && typeof a.cwd === "string")
3302
+ out2[k] = { id: a.id, cwd: a.cwd, at: typeof a.at === "string" ? a.at : "" };
3303
+ }
3304
+ return out2;
3305
+ } catch {
3306
+ return {};
3307
+ }
3308
+ }
3309
+ function writeAliases(file, all) {
3310
+ mkdirSync7(path16.dirname(file), { recursive: true, mode: 448 });
3311
+ const tmp = `${file}.${process.pid}.tmp`;
3312
+ writeFileSync6(tmp, `${JSON.stringify(all, null, 2)}
3313
+ `, { mode: 384 });
3314
+ renameSync2(tmp, file);
3315
+ try {
3316
+ chmodSync2(file, 384);
3317
+ } catch {
3318
+ }
3319
+ }
3320
+ function receiverIdOf(all, harness, id) {
3321
+ const prefix = `${harness}:`;
3322
+ for (const [k, a] of Object.entries(all)) if (a.id === id && k.startsWith(prefix)) return k.slice(prefix.length);
3323
+ return null;
3324
+ }
2976
3325
 
2977
3326
  // src/control/local.ts
2978
3327
  import net from "node:net";
2979
- import os3 from "node:os";
2980
- import path13 from "node:path";
3328
+ import os5 from "node:os";
3329
+ import path17 from "node:path";
2981
3330
  function socketPath(state2, env = process.env) {
2982
3331
  if (env.SESSIONPIPE_SOCKET) return env.SESSIONPIPE_SOCKET;
2983
- if (process.platform === "win32") return `\\\\.\\pipe\\sessionpipe-control-${os3.userInfo().username}`;
2984
- return path13.join(state2, "control.sock");
3332
+ if (process.platform === "win32") return `\\\\.\\pipe\\sessionpipe-control-${os5.userInfo().username}`;
3333
+ return path17.join(state2, "control.sock");
2985
3334
  }
2986
3335
  function ask(sock, req, timeoutMs) {
2987
3336
  return new Promise((resolve) => {
@@ -3215,35 +3564,35 @@ function dropSinkSecrets(s, run = defaultRunner) {
3215
3564
  }
3216
3565
 
3217
3566
  // src/run.ts
3218
- async function tellDaemon(state2, job, session, transcript) {
3567
+ async function tellDaemon(state2, job, session, cwd, transcript) {
3219
3568
  const sock = socketPath(state2);
3220
3569
  if (process.platform !== "win32" && !existsSync8(sock)) return;
3221
3570
  await ask(
3222
3571
  sock,
3223
3572
  {
3224
3573
  op: "event",
3225
- session: `claude-code:${session}`,
3574
+ session: `${job.harness}:${session}`,
3226
3575
  event: job.event,
3227
- cwd: job.cwd,
3576
+ ...cwd ? { cwd } : {},
3228
3577
  ...transcript ? { transcript } : {}
3229
3578
  },
3230
3579
  300
3231
3580
  );
3232
3581
  }
3233
- var VERSION = true ? "0.6.1" : "0.0.0";
3582
+ var VERSION = true ? "0.7.0" : "0.0.0";
3234
3583
  function enqueueJob(state2, job) {
3235
3584
  const jobs = jobsDir(state2);
3236
- mkdirSync7(jobs, { recursive: true, mode: 448 });
3237
- const f = path14.join(jobs, `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.json`);
3238
- writeFileSync6(f, JSON.stringify(job), { mode: 384 });
3585
+ mkdirSync8(jobs, { recursive: true, mode: 448 });
3586
+ const f = path18.join(jobs, `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.json`);
3587
+ writeFileSync7(f, JSON.stringify(job), { mode: 384 });
3239
3588
  return f;
3240
3589
  }
3241
3590
  function log(state2, line) {
3242
3591
  try {
3243
- mkdirSync7(state2, { recursive: true, mode: 448 });
3244
- const f = path14.join(state2, "log");
3592
+ mkdirSync8(state2, { recursive: true, mode: 448 });
3593
+ const f = path18.join(state2, "log");
3245
3594
  try {
3246
- if (statSync6(f).size > 1024 * 1024) renameSync2(f, `${f}.1`);
3595
+ if (statSync7(f).size > 1024 * 1024) renameSync3(f, `${f}.1`);
3247
3596
  } catch {
3248
3597
  }
3249
3598
  appendFileSync3(f, `${(/* @__PURE__ */ new Date()).toISOString()} ${line}
@@ -3252,11 +3601,11 @@ function log(state2, line) {
3252
3601
  }
3253
3602
  }
3254
3603
  function factsState(state2) {
3255
- const file = (h, s) => path14.join(state2, "facts", safeId(h), `${safeId(s)}.json`);
3604
+ const file = (h, s) => path18.join(state2, "facts", safeId(h), `${safeId(s)}.json`);
3256
3605
  return {
3257
3606
  get(h, s) {
3258
3607
  try {
3259
- return JSON.parse(readFileSync9(file(h, s), "utf8"));
3608
+ return JSON.parse(readFileSync10(file(h, s), "utf8"));
3260
3609
  } catch {
3261
3610
  return {};
3262
3611
  }
@@ -3265,8 +3614,8 @@ function factsState(state2) {
3265
3614
  const cur = this.get(h, s);
3266
3615
  const next = { ...cur, ...patch };
3267
3616
  if (JSON.stringify(next) === JSON.stringify(cur)) return;
3268
- mkdirSync7(path14.dirname(file(h, s)), { recursive: true, mode: 448 });
3269
- writeFileSync6(file(h, s), JSON.stringify(next), { mode: 384 });
3617
+ mkdirSync8(path18.dirname(file(h, s)), { recursive: true, mode: 448 });
3618
+ writeFileSync7(file(h, s), JSON.stringify(next), { mode: 384 });
3270
3619
  }
3271
3620
  };
3272
3621
  }
@@ -3282,7 +3631,7 @@ function buildSinks(sinks, o = {}) {
3282
3631
  const tier = Math.min(s.tier, s.max_tier ?? 3);
3283
3632
  if (s.url === "stdout") out2.push(new StdoutSink(s.name, tier, !!s.pii));
3284
3633
  else if (s.url.startsWith("file:"))
3285
- out2.push(new FileSink(s.name, s.url.slice(5).replace(/^~(?=\/|$)/, os4.homedir()), tier, !!s.pii));
3634
+ out2.push(new FileSink(s.name, s.url.slice(5).replace(/^~(?=\/|$)/, os6.homedir()), tier, !!s.pii));
3286
3635
  else if (/^https?:\/\//.test(s.url)) out2.push(new HttpSink(s, tier));
3287
3636
  }
3288
3637
  return out2;
@@ -3294,30 +3643,33 @@ async function runJob(job, opts = {}) {
3294
3643
  if (!adapter) return { events: [], delivered: {} };
3295
3644
  const r = adapter.fromHook({ argv: job.argv, stdin: job.stdin, env: { ...process.env, ...job.env }, cwd: job.cwd });
3296
3645
  if (!r || !r.session.id) return { events: [], delivered: {} };
3297
- if (job.harness === "claude-code") await tellDaemon(state2, job, r.session.id, r.transcript);
3646
+ const id = receiverIdOf(readAliases(aliasFile(path18.join(state2, "control"))), job.harness, r.session.id) ?? r.session.id;
3647
+ await tellDaemon(state2, job, id, r.session.cwd, r.transcript);
3298
3648
  const outbox = new Outbox(state2);
3299
- const ref = { harness: job.harness, session: r.session.id };
3649
+ const ref = { harness: job.harness, session: id };
3300
3650
  let release = takeLock(state2, ref);
3301
3651
  for (let wait = 50; !release && wait <= 3200; wait *= 2) {
3302
3652
  await new Promise((res) => setTimeout(res, wait));
3303
3653
  release = takeLock(state2, ref);
3304
3654
  }
3305
3655
  if (!release) {
3306
- log(state2, `${job.harness} ${job.event}: session ${r.session.id} locked for 6 s, re-queued`);
3656
+ log(state2, `${job.harness} ${job.event}: session ${id} locked for 6 s, re-queued`);
3307
3657
  enqueueJob(state2, job);
3308
3658
  return { events: [], delivered: {} };
3309
3659
  }
3310
3660
  try {
3311
3661
  const facts = factsState(state2);
3312
- const mem = facts.get(job.harness, r.session.id);
3662
+ const mem = facts.get(job.harness, id);
3313
3663
  const heavy = !/^(PreToolUse|PostToolUse|PostToolUseFailure|BeforeTool|AfterTool|PostInvocation)$/.test(job.event);
3314
3664
  let learned = {};
3315
3665
  if (heavy) {
3316
3666
  const f = adapter.facts(r.session, r.transcript, r.hints ?? {}, facts);
3317
3667
  const { started_at: _s, last_at: _l, first_ask: _fa, harness_version, ...rest } = f;
3318
3668
  learned = rest;
3319
- if (harness_version) facts.save(job.harness, r.session.id, { harness_version });
3320
- facts.save(job.harness, r.session.id, { session: { ...mem.session, ...learned } });
3669
+ if (harness_version) facts.save(job.harness, id, { harness_version });
3670
+ facts.save(job.harness, id, { session: { ...mem.session, ...learned } });
3671
+ if (job.harness === "antigravity" && r.session.cwd && path18.isAbsolute(r.session.cwd))
3672
+ facts.save(job.harness, id, { cwd: r.session.cwd });
3321
3673
  }
3322
3674
  const known = mem.session ?? {};
3323
3675
  const cwdRaw = learned.cwd ?? r.session.cwd ?? known.cwd;
@@ -3327,13 +3679,13 @@ async function runJob(job, opts = {}) {
3327
3679
  ...learned,
3328
3680
  ...git,
3329
3681
  ...learned.branch ? { branch: learned.branch } : {},
3330
- id: r.session.id,
3682
+ id,
3331
3683
  seq: 0,
3332
- machine: machineName(cfg, os4.hostname()),
3684
+ machine: machineName(cfg, os6.hostname()),
3333
3685
  ...cwdRaw ? { cwd: tilde(cwdRaw) } : {},
3334
3686
  ...r.session.model ? { model: r.session.model } : {}
3335
3687
  };
3336
- const version = facts.get(job.harness, r.session.id).harness_version ?? void 0;
3688
+ const version = facts.get(job.harness, id).harness_version ?? void 0;
3337
3689
  const events = [];
3338
3690
  for (const a of r.events) {
3339
3691
  const e = makeEvent(a, {
@@ -3345,7 +3697,7 @@ async function runJob(job, opts = {}) {
3345
3697
  }
3346
3698
  const wantsTranscript = cfg.sinks.some((s) => !s.paused && Math.min(s.tier, s.max_tier ?? 3) >= 2);
3347
3699
  if (wantsTranscript && r.transcript && r.events.some((e) => e.type === "turn.ended" || e.type === "session.ended")) {
3348
- const cur = facts.get(job.harness, r.session.id);
3700
+ const cur = facts.get(job.harness, id);
3349
3701
  const fromLine = Number(cur.transcript_line) || 0;
3350
3702
  let turnNo = Number(cur.transcript_turns) || 0;
3351
3703
  const read = adapter.readTranscript(r.transcript, fromLine);
@@ -3365,17 +3717,13 @@ async function runJob(job, opts = {}) {
3365
3717
  )
3366
3718
  );
3367
3719
  }
3368
- if (read.turns.length)
3369
- facts.save(job.harness, r.session.id, { transcript_line: read.line, transcript_turns: turnNo });
3720
+ if (read.turns.length) facts.save(job.harness, id, { transcript_line: read.line, transcript_turns: turnNo });
3370
3721
  }
3371
3722
  outbox.append(
3372
3723
  ref,
3373
3724
  events.map((e) => ({ ...e, session: redactDeep(e.session), data: redactDeep(e.data) }))
3374
3725
  );
3375
- log(
3376
- state2,
3377
- `${job.harness} ${job.event}: ${events.map((e) => e.type).join(",") || "nothing"} (${r.session.id.slice(0, 8)})`
3378
- );
3726
+ log(state2, `${job.harness} ${job.event}: ${events.map((e) => e.type).join(",") || "nothing"} (${id.slice(0, 8)})`);
3379
3727
  const delivered = await flush(state2, cfg.sinks, outbox, ref);
3380
3728
  if (Math.random() < 0.02) outbox.prune(cfg.keep_days ?? 30);
3381
3729
  return { events, delivered };
@@ -3434,7 +3782,7 @@ async function flush(state2, sinkConfigs, outbox, first) {
3434
3782
  const { events, offset: next } = outbox.read(ref, offset, room);
3435
3783
  if (!events.length) break;
3436
3784
  for (const e of events) {
3437
- const f = filterEvent(e, sink2.tier, sink2.pii, { home: os4.homedir() });
3785
+ const f = filterEvent(e, sink2.tier, sink2.pii, { home: os6.homedir() });
3438
3786
  if (f) batch.push(f);
3439
3787
  }
3440
3788
  offset = next;
@@ -3466,7 +3814,7 @@ function applySinkVerdict(name, res) {
3466
3814
  } catch {
3467
3815
  }
3468
3816
  }
3469
- var jobsDir = (state2) => path14.join(state2, "jobs");
3817
+ var jobsDir = (state2) => path18.join(state2, "jobs");
3470
3818
 
3471
3819
  // src/runtime.ts
3472
3820
  function stableNode(exe) {
@@ -3502,13 +3850,13 @@ function rerunGlobally(argv, out2) {
3502
3850
  }).trim();
3503
3851
  } catch {
3504
3852
  }
3505
- const target = /\/Cellar\/node(?:@\d+)?\/[^/]+/.test(prefix) ? path15.join(os5.homedir(), ".local") : prefix;
3853
+ const target = /\/Cellar\/node(?:@\d+)?\/[^/]+/.test(prefix) ? path19.join(os7.homedir(), ".local") : prefix;
3506
3854
  execFileSync5("npm", ["install", "-g", `sessionpipe@${VERSION}`, ...target ? ["--prefix", target] : []], {
3507
3855
  stdio: ["ignore", "ignore", "inherit"]
3508
3856
  });
3509
- bin = process.platform === "win32" ? path15.join(target, "sessionpipe.cmd") : target ? path15.join(target, "bin", "sessionpipe") : "sessionpipe";
3857
+ bin = process.platform === "win32" ? path19.join(target, "sessionpipe.cmd") : target ? path19.join(target, "bin", "sessionpipe") : "sessionpipe";
3510
3858
  if (target && target !== prefix)
3511
- out2(` Installed into ${target}; make sure ${path15.join(target, "bin")} is on your PATH.`);
3859
+ out2(` Installed into ${target}; make sure ${path19.join(target, "bin")} is on your PATH.`);
3512
3860
  } catch {
3513
3861
  out2(
3514
3862
  " ! couldn't install globally (`npm install -g sessionpipe` failed). Run it yourself, then retry this command."
@@ -3564,11 +3912,11 @@ function globalPrefix(distDir2, platform = process.platform) {
3564
3912
  if (viaNpx(distDir2)) return null;
3565
3913
  const prefix = prefixOf(distDir2, platform);
3566
3914
  if (!prefix) return null;
3567
- const bin = platform === "win32" ? path16.join(prefix, "sessionpipe.cmd") : path16.join(prefix, "bin", "sessionpipe");
3915
+ const bin = platform === "win32" ? path20.join(prefix, "sessionpipe.cmd") : path20.join(prefix, "bin", "sessionpipe");
3568
3916
  return existsSync9(bin) ? prefix : null;
3569
3917
  }
3570
3918
  function unwritable(prefix, platform = process.platform) {
3571
- const dir = platform === "win32" ? path16.join(prefix, "node_modules") : path16.join(prefix, "lib", "node_modules");
3919
+ const dir = platform === "win32" ? path20.join(prefix, "node_modules") : path20.join(prefix, "lib", "node_modules");
3572
3920
  try {
3573
3921
  accessSync2(dir, constants2.W_OK);
3574
3922
  return null;
@@ -3577,8 +3925,8 @@ function unwritable(prefix, platform = process.platform) {
3577
3925
  }
3578
3926
  }
3579
3927
  function npmArgv(node, platform = process.platform) {
3580
- const dir = path16.dirname(node);
3581
- const candidates = platform === "win32" ? [path16.join(dir, "node_modules", "npm", "bin", "npm-cli.js")] : [path16.join(dir, "npm"), path16.join(dir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js")];
3928
+ const dir = path20.dirname(node);
3929
+ const candidates = platform === "win32" ? [path20.join(dir, "node_modules", "npm", "bin", "npm-cli.js")] : [path20.join(dir, "npm"), path20.join(dir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js")];
3582
3930
  for (const c of candidates) {
3583
3931
  try {
3584
3932
  const real = realpathSync3(c);
@@ -3623,7 +3971,7 @@ var execCapture = (argv, o) => new Promise((resolve) => {
3623
3971
  });
3624
3972
  var lastLine = (r) => (r.error || r.stderr.trim().split("\n").filter(Boolean).slice(-2).join(" ") || `exit ${r.code}`).slice(0, 300);
3625
3973
  async function installedVersion(o) {
3626
- const r = await (o.exec ?? execCapture)([o.node, path16.join(o.distDir, "cli.js"), "--version"], {
3974
+ const r = await (o.exec ?? execCapture)([o.node, path20.join(o.distDir, "cli.js"), "--version"], {
3627
3975
  env: o.env ?? process.env,
3628
3976
  timeoutMs: 3e4
3629
3977
  });
@@ -3635,7 +3983,7 @@ async function installVersion(version, o) {
3635
3983
  const platform = o.platform ?? process.platform;
3636
3984
  const exec = o.exec ?? execCapture;
3637
3985
  const base = o.env ?? process.env;
3638
- const env = { ...base, PATH: [path16.dirname(o.node), base.PATH].filter(Boolean).join(path16.delimiter) };
3986
+ const env = { ...base, PATH: [path20.dirname(o.node), base.PATH].filter(Boolean).join(path20.delimiter) };
3639
3987
  const prefix = globalPrefix(o.distDir, platform);
3640
3988
  if (!prefix)
3641
3989
  return { ok: false, ms: Date.now() - t0, stage: "prefix", error: "this copy isn't a global npm install" };
@@ -3666,7 +4014,7 @@ async function installVersion(version, o) {
3666
4014
  let hooks = "no harness set up here; nothing to re-arm";
3667
4015
  if (o.harnesses.length) {
3668
4016
  const h = await exec(
3669
- [o.node, path16.join(o.distDir, "cli.js"), "install", "--backfill", "0", ...o.harnesses.map((n) => `--${n}`)],
4017
+ [o.node, path20.join(o.distDir, "cli.js"), "install", "--backfill", "0", ...o.harnesses.map((n) => `--${n}`)],
3670
4018
  { env, timeoutMs: 2 * 6e4 }
3671
4019
  );
3672
4020
  hooks = h.code === 0 ? `hooks re-armed for ${o.harnesses.join(", ")}` : `re-arming the hooks failed (${lastLine(h)}); \`sessionpipe install\` does it`;
@@ -3677,17 +4025,17 @@ async function installVersion(version, o) {
3677
4025
  // src/control/store.ts
3678
4026
  import {
3679
4027
  appendFileSync as appendFileSync4,
3680
- chmodSync as chmodSync2,
4028
+ chmodSync as chmodSync3,
3681
4029
  closeSync as closeSync4,
3682
4030
  existsSync as existsSync10,
3683
4031
  fsyncSync,
3684
- mkdirSync as mkdirSync8,
4032
+ mkdirSync as mkdirSync9,
3685
4033
  openSync as openSync4,
3686
- readFileSync as readFileSync10,
3687
- renameSync as renameSync3,
3688
- writeFileSync as writeFileSync7
4034
+ readFileSync as readFileSync11,
4035
+ renameSync as renameSync4,
4036
+ writeFileSync as writeFileSync8
3689
4037
  } from "node:fs";
3690
- import path17 from "node:path";
4038
+ import path21 from "node:path";
3691
4039
 
3692
4040
  // ../core/src/control/verify.ts
3693
4041
  var GRANT_MAX_MS = 24 * 36e5;
@@ -3898,7 +4246,6 @@ function parseCommand(str) {
3898
4246
  break;
3899
4247
  case "start":
3900
4248
  if (!text()) return bad("bad_fields", `a new session needs text, at most ${TEXT_MAX} characters`);
3901
- if (ref.harness !== "claude-code") return bad("bad_fields", "only Claude Code sessions can be started");
3902
4249
  if (typeof c.cwd !== "string" || !isAbsolute(c.cwd) || c.cwd.length > 1024)
3903
4250
  return bad("bad_fields", "a new session needs an absolute folder");
3904
4251
  if (has2("for") || has2("decision")) return bad("bad_fields", "a start carries text and cwd");
@@ -4024,22 +4371,22 @@ async function checkGrantPath(s, c, o) {
4024
4371
  }
4025
4372
 
4026
4373
  // src/control/store.ts
4027
- var controlFile = (env = process.env) => path17.join(path17.dirname(configFile(env)), "control.json");
4028
- var controlState = (env = process.env) => path17.join(stateDir(env), "control");
4374
+ var controlFile = (env = process.env) => path21.join(path21.dirname(configFile(env)), "control.json");
4375
+ var controlState = (env = process.env) => path21.join(stateDir(env), "control");
4029
4376
  function writePrivate(file, value) {
4030
- mkdirSync8(path17.dirname(file), { recursive: true, mode: 448 });
4377
+ mkdirSync9(path21.dirname(file), { recursive: true, mode: 448 });
4031
4378
  const tmp = `${file}.${process.pid}.tmp`;
4032
- writeFileSync7(tmp, `${JSON.stringify(value, null, 2)}
4379
+ writeFileSync8(tmp, `${JSON.stringify(value, null, 2)}
4033
4380
  `, { mode: 384 });
4034
- renameSync3(tmp, file);
4381
+ renameSync4(tmp, file);
4035
4382
  try {
4036
- chmodSync2(file, 384);
4383
+ chmodSync3(file, 384);
4037
4384
  } catch {
4038
4385
  }
4039
4386
  }
4040
4387
  function readControl(env = process.env, run = defaultRunner) {
4041
4388
  try {
4042
- const j = JSON.parse(readFileSync10(controlFile(env), "utf8"));
4389
+ const j = JSON.parse(readFileSync11(controlFile(env), "utf8"));
4043
4390
  if (!j || !Array.isArray(j.receivers)) return null;
4044
4391
  return {
4045
4392
  name: j.name || "machine",
@@ -4056,7 +4403,7 @@ function readControl(env = process.env, run = defaultRunner) {
4056
4403
  }
4057
4404
  function pairedCount(env = process.env) {
4058
4405
  try {
4059
- const j = JSON.parse(readFileSync10(controlFile(env), "utf8"));
4406
+ const j = JSON.parse(readFileSync11(controlFile(env), "utf8"));
4060
4407
  return Array.isArray(j.receivers) ? j.receivers.length : 0;
4061
4408
  } catch {
4062
4409
  return 0;
@@ -4106,7 +4453,7 @@ var NonceStore = class {
4106
4453
  this.now = now;
4107
4454
  let raw = "";
4108
4455
  try {
4109
- raw = readFileSync10(file, "utf8");
4456
+ raw = readFileSync11(file, "utf8");
4110
4457
  } catch {
4111
4458
  }
4112
4459
  const cut = this.now() - NONCE_TTL_MS;
@@ -4127,7 +4474,7 @@ var NonceStore = class {
4127
4474
  const t = this.seenAt.get(nonce);
4128
4475
  if (t !== void 0 && t > this.now() - NONCE_TTL_MS) return true;
4129
4476
  const at = this.now();
4130
- mkdirSync8(path17.dirname(this.file), { recursive: true, mode: 448 });
4477
+ mkdirSync9(path21.dirname(this.file), { recursive: true, mode: 448 });
4131
4478
  const fd = openSync4(this.file, "a", 384);
4132
4479
  try {
4133
4480
  appendFileSync4(fd, `${JSON.stringify({ n: nonce, t: at })}
@@ -4145,10 +4492,10 @@ var NonceStore = class {
4145
4492
  for (const [n, t] of this.seenAt) if (t <= cut) this.seenAt.delete(n);
4146
4493
  if (!existsSync10(this.file) && !this.seenAt.size) return;
4147
4494
  const body = [...this.seenAt].map(([n, t]) => JSON.stringify({ n, t })).join("\n");
4148
- mkdirSync8(path17.dirname(this.file), { recursive: true, mode: 448 });
4149
- writeFileSync7(`${this.file}.tmp`, body ? `${body}
4495
+ mkdirSync9(path21.dirname(this.file), { recursive: true, mode: 448 });
4496
+ writeFileSync8(`${this.file}.tmp`, body ? `${body}
4150
4497
  ` : "", { mode: 384 });
4151
- renameSync3(`${this.file}.tmp`, this.file);
4498
+ renameSync4(`${this.file}.tmp`, this.file);
4152
4499
  }
4153
4500
  get size() {
4154
4501
  return this.seenAt.size;
@@ -4161,27 +4508,27 @@ var AckOutbox = class {
4161
4508
  file;
4162
4509
  all() {
4163
4510
  try {
4164
- return readFileSync10(this.file, "utf8").split("\n").filter(Boolean).map((l) => JSON.parse(l));
4511
+ return readFileSync11(this.file, "utf8").split("\n").filter(Boolean).map((l) => JSON.parse(l));
4165
4512
  } catch {
4166
4513
  return [];
4167
4514
  }
4168
4515
  }
4169
4516
  add(a) {
4170
- mkdirSync8(path17.dirname(this.file), { recursive: true, mode: 448 });
4517
+ mkdirSync9(path21.dirname(this.file), { recursive: true, mode: 448 });
4171
4518
  appendFileSync4(this.file, `${JSON.stringify(a)}
4172
4519
  `, { mode: 384 });
4173
4520
  }
4174
4521
  keep(left) {
4175
- mkdirSync8(path17.dirname(this.file), { recursive: true, mode: 448 });
4176
- writeFileSync7(`${this.file}.tmp`, left.map((a) => JSON.stringify(a)).join("\n") + (left.length ? "\n" : ""), {
4522
+ mkdirSync9(path21.dirname(this.file), { recursive: true, mode: 448 });
4523
+ writeFileSync8(`${this.file}.tmp`, left.map((a) => JSON.stringify(a)).join("\n") + (left.length ? "\n" : ""), {
4177
4524
  mode: 384
4178
4525
  });
4179
- renameSync3(`${this.file}.tmp`, this.file);
4526
+ renameSync4(`${this.file}.tmp`, this.file);
4180
4527
  }
4181
4528
  };
4182
4529
  function readState(file) {
4183
4530
  try {
4184
- const j = JSON.parse(readFileSync10(file, "utf8"));
4531
+ const j = JSON.parse(readFileSync11(file, "utf8"));
4185
4532
  return { copies: j.copies && typeof j.copies === "object" ? j.copies : {} };
4186
4533
  } catch {
4187
4534
  return { copies: {} };
@@ -4192,12 +4539,12 @@ var writeState = (file, s) => writePrivate(file, s);
4192
4539
  // src/control/autoupdate.ts
4193
4540
  var UPDATE_EXIT = 75;
4194
4541
  var DAY_MS = 24 * 36e5;
4195
- var LABEL = "org.sessionpipe.control";
4542
+ var LABEL3 = "org.sessionpipe.control";
4196
4543
  var UNIT = "sessionpipe-control.service";
4197
- var updateFile = (env = process.env) => path18.join(controlState(env), "update.json");
4544
+ var updateFile = (env = process.env) => path22.join(controlState(env), "update.json");
4198
4545
  function readUpdate(file) {
4199
4546
  try {
4200
- const j = JSON.parse(readFileSync11(file, "utf8"));
4547
+ const j = JSON.parse(readFileSync12(file, "utf8"));
4201
4548
  return j && typeof j === "object" ? j : {};
4202
4549
  } catch {
4203
4550
  return {};
@@ -4255,12 +4602,12 @@ function updateLine(s, now) {
4255
4602
  function restartVia(o) {
4256
4603
  const s = o.env.SESSIONPIPE_SERVICE ?? "";
4257
4604
  if (s.startsWith("supervisor:") && s === `supervisor:${o.ppid}`) return "supervisor";
4258
- if (o.platform === "darwin" && o.ppid === 1 && (s === "launchd" || o.env.XPC_SERVICE_NAME === LABEL))
4605
+ if (o.platform === "darwin" && o.ppid === 1 && (s === "launchd" || o.env.XPC_SERVICE_NAME === LABEL3))
4259
4606
  return "launchd";
4260
4607
  if (o.platform === "linux") {
4261
4608
  let cg = "";
4262
4609
  try {
4263
- cg = (o.cgroup ?? (() => readFileSync11("/proc/self/cgroup", "utf8")))();
4610
+ cg = (o.cgroup ?? (() => readFileSync12("/proc/self/cgroup", "utf8")))();
4264
4611
  } catch {
4265
4612
  }
4266
4613
  const inUnit = cg.split("\n").some((l) => l.endsWith(`/${UNIT}`) || l.includes(`/${UNIT}/`));
@@ -4491,19 +4838,19 @@ function supervise(o) {
4491
4838
 
4492
4839
  // src/control/cli.ts
4493
4840
  import { existsSync as existsSync13, realpathSync as realpathSync5, unwatchFile, watchFile } from "node:fs";
4494
- import os7 from "node:os";
4495
- import path21 from "node:path";
4841
+ import os9 from "node:os";
4842
+ import path25 from "node:path";
4496
4843
 
4497
4844
  // src/control/daemon.ts
4498
- import { existsSync as existsSync11, mkdirSync as mkdirSync9, realpathSync as realpathSync4, statSync as statSync8, unlinkSync as unlinkSync5 } from "node:fs";
4845
+ import { existsSync as existsSync11, mkdirSync as mkdirSync10, readFileSync as readFileSync13, realpathSync as realpathSync4, statSync as statSync9, unlinkSync as unlinkSync5 } from "node:fs";
4499
4846
  import net2 from "node:net";
4500
- import path19 from "node:path";
4847
+ import path23 from "node:path";
4501
4848
 
4502
4849
  // src/control/reply.ts
4503
- import { closeSync as closeSync5, openSync as openSync5, readSync as readSync4, statSync as statSync7 } from "node:fs";
4850
+ import { closeSync as closeSync5, openSync as openSync5, readSync as readSync4, statSync as statSync8 } from "node:fs";
4504
4851
  function transcriptEnd(file) {
4505
4852
  try {
4506
- return statSync7(file).size;
4853
+ return statSync8(file).size;
4507
4854
  } catch {
4508
4855
  return 0;
4509
4856
  }
@@ -4560,16 +4907,19 @@ async function awaitAnswer(file, from, o) {
4560
4907
  }
4561
4908
 
4562
4909
  // src/control/route.ts
4910
+ var HEADLESS = /* @__PURE__ */ new Set(["claude-code", "codex", "antigravity"]);
4563
4911
  function routePrompt(s) {
4564
4912
  if (s.sdk) return { mode: "sdk" };
4565
4913
  if (s.waiter) return { mode: "waiter" };
4566
4914
  if (s.midTurn) return { mode: "turn" };
4567
4915
  if (s.api) return { mode: "api" };
4568
- if (s.harness !== "claude-code") return { unsupported: `no idle delivery for ${s.harness} yet` };
4916
+ if (!HEADLESS.has(s.harness)) return { unsupported: `no idle delivery for ${s.harness} yet` };
4569
4917
  if (!s.known) return { refused: "no_session" };
4570
4918
  if (!s.live) return { mode: "resume" };
4571
4919
  if (s.canFork) return { mode: "fork" };
4572
- return { unsupported: "the session is open and this Claude Code can't copy it (no --fork-session)" };
4920
+ return {
4921
+ unsupported: s.harness === "claude-code" ? "the session is open and this Claude Code can't copy it (no --fork-session)" : `the session is open in ${s.harness} right now, and a ${s.harness} session can't be copied: send it again once it has been idle a couple of minutes`
4922
+ };
4573
4923
  }
4574
4924
  function kindPath(kind, harness) {
4575
4925
  switch (kind) {
@@ -4580,7 +4930,7 @@ function kindPath(kind, harness) {
4580
4930
  case "cancel":
4581
4931
  return "cancel";
4582
4932
  case "start":
4583
- return harness === "claude-code" ? "start" : { unsupported: `can't start a ${harness} session` };
4933
+ return HEADLESS.has(harness) ? "start" : { unsupported: `can't start a ${harness} session` };
4584
4934
  default:
4585
4935
  return { unsupported: `unknown kind ${String(kind)}` };
4586
4936
  }
@@ -4689,7 +5039,6 @@ var {
4689
5039
  claudeEnv: claudeEnv2,
4690
5040
  findTranscript: findTranscript2,
4691
5041
  isUuid: isUuid2,
4692
- JOB_TIMEOUT_MS: JOB_TIMEOUT_MS2,
4693
5042
  liveProcess: liveProcess2,
4694
5043
  loginHint: loginHint2,
4695
5044
  modeFlags: modeFlags2,
@@ -4697,7 +5046,16 @@ var {
4697
5046
  recentlyWritten: recentlyWritten2,
4698
5047
  sessionFolder: sessionFolder2
4699
5048
  } = claude_code_control_exports;
4700
- var VERSION2 = true ? "0.6.1" : "0.0.0";
5049
+ function startNamed(cmd2) {
5050
+ if (typeof cmd2 !== "string" || cmd2.length > 64 * 1024 || !cmd2.includes('"start"')) return null;
5051
+ try {
5052
+ const c = JSON.parse(cmd2);
5053
+ return c?.kind === "start" && typeof c.session === "string" ? c.session : null;
5054
+ } catch {
5055
+ return null;
5056
+ }
5057
+ }
5058
+ var VERSION2 = true ? "0.7.0" : "0.0.0";
4701
5059
  var MAX_REPLY = 2e4;
4702
5060
  var HELLO_EVERY_MS = 10 * 6e4;
4703
5061
  var TURN_FALLBACK_MS = 30 * 6e4;
@@ -4733,9 +5091,10 @@ var ControlDaemon = class {
4733
5091
  this.dir = dir;
4734
5092
  this.sock = sock;
4735
5093
  this.deps = deps;
4736
- this.nonces = new NonceStore(path19.join(dir, "nonces.jsonl"), deps.now);
4737
- this.acks = new AckOutbox(path19.join(dir, "acks.jsonl"));
4738
- this.state = readState(path19.join(dir, "state.json"));
5094
+ this.nonces = new NonceStore(path23.join(dir, "nonces.jsonl"), deps.now);
5095
+ this.acks = new AckOutbox(path23.join(dir, "acks.jsonl"));
5096
+ this.state = readState(path23.join(dir, "state.json"));
5097
+ this.aliases = readAliases(aliasFile(dir));
4739
5098
  }
4740
5099
  cfg;
4741
5100
  dir;
@@ -4781,10 +5140,16 @@ var ControlDaemon = class {
4781
5140
  /** Every open local connection: stop() ends them, or the server's close would wait
4782
5141
  * on a client that never says anything. */
4783
5142
  conns = /* @__PURE__ */ new Set();
5143
+ /** Codex and Antigravity sessions the daemon started, under the start's id (aliases.ts). */
5144
+ aliases;
5145
+ /** Starts in hand, by the session they name, from the moment they arrive: a prompt
5146
+ * for that session that came in the same poll waits for its start rather than
5147
+ * finding no session (2026-10-04: a start and a prompt queued back to back). */
5148
+ startsPending = /* @__PURE__ */ new Map();
4784
5149
  // --- lifecycle ------------------------------------------------------------------
4785
5150
  async start() {
4786
5151
  this.running = true;
4787
- mkdirSync9(this.dir, { recursive: true, mode: 448 });
5152
+ mkdirSync10(this.dir, { recursive: true, mode: 448 });
4788
5153
  await this.listen();
4789
5154
  for (const r of this.cfg.receivers) void this.pollLoop(r);
4790
5155
  this.helloTimer = setInterval(() => void this.helloAll(), HELLO_EVERY_MS);
@@ -4837,7 +5202,7 @@ var ControlDaemon = class {
4837
5202
  * for ten minutes: a session killed mid-turn never sends its Stop. */
4838
5203
  busy() {
4839
5204
  if (this.inflight.size) return `${this.inflight.size} message(s) in hand`;
4840
- if (this.procs || this.ownRuns.size) return "a headless Claude Code run";
5205
+ if (this.procs || this.ownRuns.size) return "a headless agent run";
4841
5206
  if (this.startsRunning) return "a session starting";
4842
5207
  for (const s of this.sdkSessions.values()) if (s.busy) return "an Agent SDK session mid-turn";
4843
5208
  for (const l of this.live.values()) if (l.attentions.size) return "a permission prompt waiting for an answer";
@@ -5050,7 +5415,7 @@ ${IN_PLACE_ANSWER}`, id: p.msgId });
5050
5415
  return null;
5051
5416
  }
5052
5417
  onEvent(e) {
5053
- const l = this.liveOf(e.session);
5418
+ const l = this.liveOf(this.named(e.session));
5054
5419
  l.lastEvent = this.deps.now();
5055
5420
  if (e.cwd) l.cwd = e.cwd;
5056
5421
  if (e.transcript) l.transcript = e.transcript;
@@ -5195,10 +5560,11 @@ ${IN_PLACE_ANSWER}`, id: p.msgId });
5195
5560
  void this.refreshSessionTools();
5196
5561
  const waiting = [...this.live].filter(([, l]) => l.waiter).map(([k]) => k);
5197
5562
  const modes = ["waiter", "turn", "resume", "fork", ...this.deps.sdk ? ["sdk"] : []];
5563
+ const harnesses = this.harnesses();
5198
5564
  for (const r of this.cfg.receivers) {
5199
5565
  const body = {
5200
5566
  name: this.cfg.name,
5201
- harnesses: ["claude-code"],
5567
+ harnesses,
5202
5568
  modes,
5203
5569
  keys: r.keys.map((k) => k.id),
5204
5570
  version: VERSION2,
@@ -5277,6 +5643,27 @@ ${IN_PLACE_ANSWER}`, id: p.msgId });
5277
5643
  return;
5278
5644
  }
5279
5645
  this.inflight.add(m.id);
5646
+ const starting = startNamed(m.cmd);
5647
+ let done = () => {
5648
+ };
5649
+ if (starting) {
5650
+ const mine = new Promise((res) => {
5651
+ done = res;
5652
+ });
5653
+ const before = this.startsPending.get(starting);
5654
+ const all = before ? Promise.all([before, mine]).then(() => void 0) : mine;
5655
+ this.startsPending.set(starting, all);
5656
+ void all.then(() => {
5657
+ if (this.startsPending.get(starting) === all) this.startsPending.delete(starting);
5658
+ });
5659
+ }
5660
+ try {
5661
+ await this.handleVerified(r, m);
5662
+ } finally {
5663
+ done();
5664
+ }
5665
+ }
5666
+ async handleVerified(r, m) {
5280
5667
  const sent = Date.parse(m.at) || this.deps.now();
5281
5668
  if (Date.parse(m.expires_at) <= this.deps.now()) return this.finish(r, m.id, { outcome: "expired" });
5282
5669
  const v = await verifyCommand(
@@ -5295,8 +5682,10 @@ ${IN_PLACE_ANSWER}`, id: p.msgId });
5295
5682
  try {
5296
5683
  if (kp === "start") return await this.startSession(r, m.id, cmd2, sent);
5297
5684
  let place = this.place(cmd2.session);
5298
- if ("refused" in place && this.ownRuns.has(ref.id)) {
5685
+ const starting = kp === "prompt" ? this.startsPending.get(cmd2.session) : void 0;
5686
+ if ("refused" in place && (starting || this.ownRuns.has(ref.id))) {
5299
5687
  this.finish(r, m.id, { outcome: "taken" });
5688
+ await starting;
5300
5689
  await this.serial(ref.id, async () => {
5301
5690
  });
5302
5691
  place = this.place(cmd2.session);
@@ -5315,9 +5704,11 @@ ${IN_PLACE_ANSWER}`, id: p.msgId });
5315
5704
  }
5316
5705
  }
5317
5706
  /** Where the session lives, and whether its folder is allowed — read from the
5318
- * session's own transcript (Claude Code) or the hooks' cwd, never the command. */
5707
+ * session's own transcript (Claude Code, Codex), the folder its start recorded, or
5708
+ * the hooks' cwd, never the command. */
5319
5709
  place(session) {
5320
5710
  const ref = parseSessionRef(session);
5711
+ if (ref.harness === "codex" || ref.harness === "antigravity") return this.placeDriven(session, ref);
5321
5712
  if (ref.harness === "claude-code") {
5322
5713
  const target = this.state.copies[ref.id] ?? ref.id;
5323
5714
  const t = findTranscript2(target, this.deps.claudeDirs()) ?? findTranscript2(ref.id, this.deps.claudeDirs());
@@ -5332,12 +5723,100 @@ ${IN_PLACE_ANSWER}`, id: p.msgId });
5332
5723
  if (!ok) return { refused: cwd ? "folder" : "no_session", why: "no allowed folder known for that session" };
5333
5724
  return { folder: ok, transcript: null };
5334
5725
  }
5726
+ /** A Codex or Antigravity session: one the daemon started is its alias (the harness's
5727
+ * id and the start's folder); one the hooks reported is Codex's rollout (its
5728
+ * `session_meta` cwd) or, for Antigravity, the folder its hooks gave. */
5729
+ placeDriven(session, ref) {
5730
+ const a = this.aliasOf(ref.harness, ref.id);
5731
+ const target = a?.id ?? ref.id;
5732
+ let cwd = a?.cwd || void 0;
5733
+ let file = null;
5734
+ if (ref.harness === "codex") {
5735
+ file = isUuid2(target) ? codex_control_exports.findRollout(target, this.deps.env ?? process.env) : null;
5736
+ if (!a && !file) return { refused: "no_session", why: `no Codex session ${ref.id.slice(0, 8)} on this machine` };
5737
+ cwd ||= file ? codex_control_exports.codexFacts(file).cwd : void 0;
5738
+ } else cwd ||= this.live.get(session)?.cwd || this.factsCwd(ref.harness, ref.id);
5739
+ if (!cwd)
5740
+ return {
5741
+ refused: "no_session",
5742
+ why: `no folder known for ${ref.harness === "codex" ? "Codex" : "Antigravity"} session ${ref.id.slice(0, 8)}`
5743
+ };
5744
+ const ok = this.allowed(cwd);
5745
+ if (!ok) return { refused: "folder", why: "that session's folder isn't one this machine allows" };
5746
+ return { folder: ok, transcript: null, target, file };
5747
+ }
5748
+ /** The folder the worker recorded for a session from its hooks (run.ts, facts). */
5749
+ factsCwd(harness, id) {
5750
+ try {
5751
+ const f = path23.join(path23.dirname(this.dir), "facts", safeId(harness), `${safeId(id)}.json`);
5752
+ const cwd = JSON.parse(readFileSync13(f, "utf8")).cwd;
5753
+ return typeof cwd === "string" && path23.isAbsolute(cwd) ? cwd : void 0;
5754
+ } catch {
5755
+ return void 0;
5756
+ }
5757
+ }
5758
+ aliasOf(harness, id) {
5759
+ return this.aliases[`${harness}:${id}`] ?? null;
5760
+ }
5761
+ /** The harness revealed its own id for a session the command named: from now on that
5762
+ * is where the command's id leads (aliases.ts). Written before anything else reads it. */
5763
+ setAlias(harness, id, real, cwd) {
5764
+ this.aliases = {
5765
+ ...this.aliases,
5766
+ [`${harness}:${id}`]: { id: real, cwd, at: new Date(this.deps.now()).toISOString() }
5767
+ };
5768
+ try {
5769
+ writeAliases(aliasFile(this.dir), this.aliases);
5770
+ } catch (e) {
5771
+ this.deps.log(
5772
+ `couldn't record ${harness}:${id.slice(0, 8)} \u2192 ${real.slice(0, 8)}: ${String(e?.message || e)}`
5773
+ );
5774
+ }
5775
+ }
5776
+ /** `<harness>:<harness's id>` → `<harness>:<the start's id>` for a session the daemon named. */
5777
+ named(session) {
5778
+ const i = session.indexOf(":");
5779
+ if (i <= 0) return session;
5780
+ const harness = session.slice(0, i);
5781
+ const as = receiverIdOf(this.aliases, harness, session.slice(i + 1));
5782
+ return as ? `${harness}:${as}` : session;
5783
+ }
5784
+ driver(harness) {
5785
+ if (harness === "codex")
5786
+ return {
5787
+ label: "Codex",
5788
+ cmd: "codex",
5789
+ bin: () => this.deps.codex?.() ?? null,
5790
+ args: ({ mode, prompt, cwd, resume }) => resume ? codex_control_exports.codexArgs({ mode, prompt, resume }) : codex_control_exports.codexArgs({ mode, prompt, cwd }),
5791
+ reading: () => codex_control_exports.reading(),
5792
+ ...this.deps.codexLogin ? { login: this.deps.codexLogin } : {},
5793
+ loginHint: codex_control_exports.LOGIN_HINT
5794
+ };
5795
+ if (harness === "antigravity")
5796
+ return {
5797
+ label: "Antigravity",
5798
+ cmd: "agy",
5799
+ bin: () => this.deps.agy?.() ?? null,
5800
+ args: ({ mode, prompt, resume }) => resume ? antigravity_control_exports.agyArgs({ mode, prompt, resume }) : antigravity_control_exports.agyArgs({ mode, prompt }),
5801
+ reading: () => antigravity_control_exports.reading()
5802
+ };
5803
+ return null;
5804
+ }
5805
+ /** The harnesses this machine can run a message in, for hello. */
5806
+ harnesses() {
5807
+ if (this.deps.harnesses) return this.deps.harnesses();
5808
+ return [
5809
+ ...this.deps.claude() ? ["claude-code"] : [],
5810
+ ...this.deps.codex?.() ? ["codex"] : [],
5811
+ ...this.deps.agy?.() ? ["antigravity"] : []
5812
+ ];
5813
+ }
5335
5814
  allowed(dir) {
5336
- if (!path19.isAbsolute(dir)) return null;
5815
+ if (!path23.isAbsolute(dir)) return null;
5337
5816
  let r;
5338
5817
  try {
5339
5818
  r = realpathSync4.native(dir);
5340
- if (!statSync8(r).isDirectory()) return null;
5819
+ if (!statSync9(r).isDirectory()) return null;
5341
5820
  } catch {
5342
5821
  return null;
5343
5822
  }
@@ -5347,7 +5826,7 @@ ${IN_PLACE_ANSWER}`, id: p.msgId });
5347
5826
  rf = realpathSync4.native(f);
5348
5827
  } catch {
5349
5828
  }
5350
- if (r === rf || r.startsWith(rf.endsWith(path19.sep) ? rf : rf + path19.sep)) return r;
5829
+ if (r === rf || r.startsWith(rf.endsWith(path23.sep) ? rf : rf + path23.sep)) return r;
5351
5830
  }
5352
5831
  return null;
5353
5832
  }
@@ -5384,9 +5863,23 @@ ${IN_PLACE_ANSWER}`, id: p.msgId });
5384
5863
  await held.interrupt();
5385
5864
  this.finish(r, id, { outcome: "delivered", mode: "sdk" }, sent);
5386
5865
  }
5387
- view(session, transcript) {
5866
+ view(session, place) {
5388
5867
  const ref = parseSessionRef(session);
5389
5868
  const l = this.live.get(session);
5869
+ if (ref.harness !== "claude-code") {
5870
+ const live2 = !this.ownRuns.has(ref.id) && !!place.file && recentlyWritten2(place.file, this.lastEnd.get(ref.id), this.deps.now());
5871
+ return {
5872
+ harness: ref.harness,
5873
+ sdk: false,
5874
+ waiter: !!l?.waiter && !l.waiter.destroyed,
5875
+ midTurn: !!l?.midTurn,
5876
+ live: live2,
5877
+ known: true,
5878
+ canFork: false,
5879
+ api: false
5880
+ };
5881
+ }
5882
+ const transcript = place.transcript;
5390
5883
  const claude = ref.harness === "claude-code" ? this.deps.claude() : null;
5391
5884
  const target = this.state.copies[ref.id] ?? ref.id;
5392
5885
  const t = transcript;
@@ -5403,7 +5896,7 @@ ${IN_PLACE_ANSWER}`, id: p.msgId });
5403
5896
  };
5404
5897
  }
5405
5898
  async prompt(r, id, cmd2, place, sent) {
5406
- const route = routePrompt(this.view(cmd2.session, place.transcript));
5899
+ const route = routePrompt(this.view(cmd2.session, place));
5407
5900
  if ("unsupported" in route) return this.finish(r, id, { outcome: "unsupported", detail: route.unsupported });
5408
5901
  if ("refused" in route) return this.finish(r, id, { outcome: "refused", code: route.refused });
5409
5902
  const ref = parseSessionRef(cmd2.session);
@@ -5477,20 +5970,100 @@ ${IN_PLACE_ANSWER}`, id: p.msgId });
5477
5970
  /** Null when Claude Code is signed in for this account (or can't say); otherwise what
5478
5971
  * to tell the person. */
5479
5972
  async loginProblem(configDir) {
5480
- if (!this.deps.login) return null;
5481
- const key = configDir ?? "";
5973
+ const login = this.deps.login;
5974
+ if (!login) return null;
5975
+ if (await this.signedIn(configDir ?? "", () => login(configDir))) return null;
5976
+ this.deps.log(`claude isn't signed in for ${configDir ?? "the default account"} as the daemon sees it`);
5977
+ return loginHint2(configDir, this.deps.platform ?? process.platform);
5978
+ }
5979
+ /** One harness account's login, asked at most every 10 minutes (30 s after a no). */
5980
+ async signedIn(key, ask2) {
5482
5981
  const now = this.deps.now();
5483
5982
  const hit = this.logins.get(key);
5484
5983
  let login = hit && now - hit.at < (hit.login.loggedIn === false ? 3e4 : 6e5) ? hit.login : null;
5485
5984
  if (!login) {
5486
- login = await this.deps.login(configDir).catch(() => ({ loggedIn: null }));
5985
+ login = await ask2().catch(() => ({ loggedIn: null }));
5487
5986
  this.logins.set(key, { at: now, login });
5488
5987
  }
5489
- if (login.loggedIn !== false) return null;
5490
- this.deps.log(`claude isn't signed in for ${configDir ?? "the default account"} as the daemon sees it`);
5491
- return loginHint2(configDir, this.deps.platform ?? process.platform);
5988
+ return login.loggedIn !== false;
5989
+ }
5990
+ /** Null when a Codex or Antigravity run may go ahead; otherwise what to tell the person. */
5991
+ async drivenProblem(drv) {
5992
+ const bin = drv.bin();
5993
+ if (!bin) return { failed: `${drv.label} isn't installed on this machine` };
5994
+ const login = drv.login;
5995
+ if (login && !await this.signedIn(`${drv.cmd}:`, () => login(bin))) {
5996
+ this.deps.log(`${drv.cmd} isn't signed in as the daemon sees it`);
5997
+ return { failed: drv.loginHint ?? `${drv.label} isn't signed in on this machine` };
5998
+ }
5999
+ return { bin };
6000
+ }
6001
+ /** A Codex or Antigravity session's next message: a headless turn resumed under the
6002
+ * harness's own id, after any run of the daemon's own that holds it. */
6003
+ async resumeDriven(r, id, cmd2, place, sent) {
6004
+ const ref = parseSessionRef(cmd2.session);
6005
+ const drv = this.driver(ref.harness);
6006
+ const ready = await this.drivenProblem(drv);
6007
+ if ("failed" in ready) return this.finish(r, id, { outcome: "failed", detail: ready.failed });
6008
+ this.finish(r, id, { outcome: "taken" });
6009
+ await this.serial(ref.id, async () => {
6010
+ const target = this.aliasOf(ref.harness, ref.id)?.id ?? place.target ?? ref.id;
6011
+ const args2 = drv.args({
6012
+ mode: this.cfg.mode,
6013
+ prompt: frame(cmd2.text, r.url),
6014
+ cwd: place.folder,
6015
+ resume: target
6016
+ });
6017
+ this.deps.log(`${id.slice(-8)}: ${drv.cmd} resume ${target.slice(0, 8)} (${this.cfg.mode}) in ${place.folder}`);
6018
+ await this.drive(r, id, ref, drv, ready.bin, args2, place.folder, sent, target);
6019
+ });
6020
+ }
6021
+ /** Run one Codex or Antigravity turn and ack it: `progress` as its answer grows, then
6022
+ * `delivered` (mode `resume`) with the reply. The moment the harness names the session
6023
+ * — a start's new one, or a fresh one for an id it didn't know — the command's id
6024
+ * becomes its alias, so the next message resumes it. */
6025
+ async drive(r, id, ref, drv, bin, args2, cwd, sent, asked) {
6026
+ const stream = this.answerStream(r, id);
6027
+ const reading3 = drv.reading();
6028
+ let named = null;
6029
+ const name = () => {
6030
+ const s = reading3.session;
6031
+ if (!s || s === named) return;
6032
+ named = s;
6033
+ if (s !== asked) this.setAlias(ref.harness, ref.id, s, cwd);
6034
+ };
6035
+ const onLine = (l) => {
6036
+ if (reading3.feed(l)) stream.set(reading3.text);
6037
+ name();
6038
+ };
6039
+ const res = await this.owning(
6040
+ ref.id,
6041
+ () => this.slot(
6042
+ () => this.deps.run(bin, args2, {
6043
+ cwd,
6044
+ env: jobEnv(this.deps.env),
6045
+ timeoutMs: JOB_TIMEOUT_MS,
6046
+ onLine,
6047
+ keep: () => false
6048
+ })
6049
+ )
6050
+ );
6051
+ if (!reading3.lines && res.stdout) {
6052
+ for (const l of res.stdout.split("\n")) if (l.trim()) onLine(l);
6053
+ }
6054
+ stream.close();
6055
+ this.lastEnd.set(ref.id, this.deps.now());
6056
+ if (reading3.warnings?.length)
6057
+ this.deps.log(`${id.slice(-8)}: ${drv.cmd} warned: ${reading3.warnings.join(" \xB7 ").slice(0, 400)}`);
6058
+ const out2 = reading3.outcome(res);
6059
+ if ("failed" in out2) return this.finish(r, id, { outcome: "failed", detail: out2.failed });
6060
+ const moved = asked && named && named !== asked ? `
6061
+
6062
+ (${drv.label} didn't find that session on this machine, so this ran in a new one; later messages continue there.)` : "";
6063
+ this.finish(r, id, { outcome: "delivered", mode: "resume", reply: out2.reply + moved }, sent);
5492
6064
  }
5493
6065
  async headless(r, id, cmd2, place, mode, sent) {
6066
+ if (parseSessionRef(cmd2.session)?.harness !== "claude-code") return this.resumeDriven(r, id, cmd2, place, sent);
5494
6067
  const claude = this.deps.claude();
5495
6068
  if (!claude)
5496
6069
  return this.finish(r, id, { outcome: "failed", detail: "Claude Code isn't installed on this machine" });
@@ -5520,7 +6093,7 @@ ${IN_PLACE_ANSWER}`, id: p.msgId });
5520
6093
  () => this.deps.run(claude.bin, args2, {
5521
6094
  cwd: place.folder,
5522
6095
  env: claudeEnv2(place.transcript?.configDir, this.deps.env),
5523
- timeoutMs: JOB_TIMEOUT_MS2,
6096
+ timeoutMs: JOB_TIMEOUT_MS,
5524
6097
  ...claude.caps.stream ? { onLine: (l) => stream.feed(l) } : {}
5525
6098
  })
5526
6099
  )
@@ -5532,7 +6105,7 @@ ${IN_PLACE_ANSWER}`, id: p.msgId });
5532
6105
  const ran = isUuid2(out2.session) ? out2.session : target;
5533
6106
  if (mode === "fork" && ran !== target) {
5534
6107
  this.state.copies[ref.id] = ran;
5535
- writeState(path19.join(this.dir, "state.json"), this.state);
6108
+ writeState(path23.join(this.dir, "state.json"), this.state);
5536
6109
  this.lastEnd.set(ran, this.deps.now());
5537
6110
  }
5538
6111
  this.finish(
@@ -5557,9 +6130,7 @@ ${IN_PLACE_ANSWER}`, id: p.msgId });
5557
6130
  if (j.is_error) return { failed: String(j.result || j.subtype || "Claude Code reported an error") };
5558
6131
  const denials = Array.isArray(j.permission_denials) ? j.permission_denials : [];
5559
6132
  const names = [...new Set(denials.map((d) => d?.tool_name).filter((n) => typeof n === "string"))];
5560
- const tail2 = denials.length ? `
5561
-
5562
- (Not allowed on this machine, since nobody was there to approve: ${names.join(", ") || `${denials.length} action(s)`}.)` : "";
6133
+ const tail2 = deniedNote(names, denials.length);
5563
6134
  const text = typeof j.result === "string" && j.result.trim() ? j.result : "(Claude Code finished without a written reply.)";
5564
6135
  return { reply: text + tail2, ...typeof j.session_id === "string" ? { session: j.session_id } : {} };
5565
6136
  }
@@ -5694,6 +6265,7 @@ ${IN_PLACE_ANSWER}`, id: p.msgId });
5694
6265
  code: "folder",
5695
6266
  detail: "that folder isn't one this machine allows"
5696
6267
  });
6268
+ if (ref.harness !== "claude-code") return this.startDriven(r, id, cmd2, ref, cwd, sent);
5697
6269
  if (findTranscript2(ref.id, this.deps.claudeDirs()) || this.sdkSessions.has(ref.id))
5698
6270
  return this.finish(r, id, {
5699
6271
  outcome: "refused",
@@ -5738,7 +6310,7 @@ ${IN_PLACE_ANSWER}`, id: p.msgId });
5738
6310
  {
5739
6311
  cwd,
5740
6312
  env: claudeEnv2(void 0, this.deps.env),
5741
- timeoutMs: JOB_TIMEOUT_MS2,
6313
+ timeoutMs: JOB_TIMEOUT_MS,
5742
6314
  ...claude.caps.stream ? { onLine: (l) => stream.feed(l) } : {}
5743
6315
  }
5744
6316
  )
@@ -5749,6 +6321,24 @@ ${IN_PLACE_ANSWER}`, id: p.msgId });
5749
6321
  if ("failed" in out2) return this.finish(r, id, { outcome: "failed", detail: out2.failed });
5750
6322
  this.finish(r, id, { outcome: "delivered", mode: "resume", reply: out2.reply }, sent);
5751
6323
  }
6324
+ /** A new Codex or Antigravity session: the harness picks its id, and the start's id
6325
+ * becomes its name (aliases.ts) as soon as the run says what it picked. */
6326
+ async startDriven(r, id, cmd2, ref, cwd, sent) {
6327
+ const drv = this.driver(ref.harness);
6328
+ const env = this.deps.env ?? process.env;
6329
+ if (this.aliasOf(ref.harness, ref.id) || this.live.get(cmd2.session)?.cwd || ref.harness === "codex" && isUuid2(ref.id) && codex_control_exports.findRollout(ref.id, env))
6330
+ return this.finish(r, id, {
6331
+ outcome: "refused",
6332
+ code: "bad_session",
6333
+ detail: "that new session's id is already taken"
6334
+ });
6335
+ const ready = await this.drivenProblem(drv);
6336
+ if ("failed" in ready) return this.finish(r, id, { outcome: "failed", detail: ready.failed });
6337
+ this.finish(r, id, { outcome: "taken" });
6338
+ const args2 = drv.args({ mode: this.cfg.mode, prompt: frameStart(cmd2.text, r.url), cwd });
6339
+ this.deps.log(`${id.slice(-8)}: ${drv.cmd} new session for ${ref.id.slice(0, 8)} (${this.cfg.mode}) in ${cwd}`);
6340
+ await this.drive(r, id, ref, drv, ready.bin, args2, cwd, sent, null);
6341
+ }
5752
6342
  closeIdleSdk() {
5753
6343
  const idle = this.deps.sdkIdleMs ?? 10 * 6e4;
5754
6344
  for (const [id, s] of this.sdkSessions)
@@ -5764,7 +6354,7 @@ ${IN_PLACE_ANSWER}`, id: p.msgId });
5764
6354
  var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms).unref?.());
5765
6355
  function socketId(p) {
5766
6356
  try {
5767
- const st = statSync8(p);
6357
+ const st = statSync9(p);
5768
6358
  return `${st.dev}:${st.ino}`;
5769
6359
  } catch {
5770
6360
  return null;
@@ -5774,10 +6364,10 @@ function socketId(p) {
5774
6364
  // src/control/pair.ts
5775
6365
  import { execFileSync as execFileSync6 } from "node:child_process";
5776
6366
  import { randomBytes as randomBytes3 } from "node:crypto";
5777
- import { existsSync as existsSync12, mkdirSync as mkdirSync10, unlinkSync as unlinkSync6, writeFileSync as writeFileSync8 } from "node:fs";
5778
- import os6 from "node:os";
5779
- import path20 from "node:path";
5780
- var VERSION3 = true ? "0.6.1" : "0.0.0";
6367
+ import { existsSync as existsSync12, mkdirSync as mkdirSync11, unlinkSync as unlinkSync6, writeFileSync as writeFileSync9 } from "node:fs";
6368
+ import os8 from "node:os";
6369
+ import path24 from "node:path";
6370
+ var VERSION3 = true ? "0.7.0" : "0.0.0";
5781
6371
  async function json(r) {
5782
6372
  try {
5783
6373
  return await r.json();
@@ -5812,7 +6402,7 @@ async function pair(o) {
5812
6402
  const cfg = readControl(o.env) ?? { name: o.name, folders: [], mode: o.mode, receivers: [] };
5813
6403
  cfg.name = o.name || cfg.name;
5814
6404
  cfg.mode = o.mode;
5815
- cfg.folders = [.../* @__PURE__ */ new Set([...cfg.folders, ...o.folders.map((d) => path20.resolve(d))])];
6405
+ cfg.folders = [.../* @__PURE__ */ new Set([...cfg.folders, ...o.folders.map((d) => path24.resolve(d))])];
5816
6406
  const existing = cfg.receivers.find((r) => r.control === control);
5817
6407
  const auth = { authorization: `Bearer ${o.token ?? pollKey}`, "content-type": "application/json" };
5818
6408
  const started = await f(`${control}/pair`, {
@@ -5822,7 +6412,7 @@ async function pair(o) {
5822
6412
  ...pollKey ? { poll_key: pollKey } : {},
5823
6413
  ...existing ? { machine: existing.machine } : {},
5824
6414
  name: cfg.name,
5825
- harnesses: ["claude-code"],
6415
+ harnesses: o.harnesses ?? drivableHarnesses(o.env ?? process.env),
5826
6416
  modes: ["waiter", "turn", "resume", "fork"],
5827
6417
  version: VERSION3,
5828
6418
  folders: cfg.folders,
@@ -5912,24 +6502,24 @@ function removeKey(id, env) {
5912
6502
  writeControl(cfg, env);
5913
6503
  return n;
5914
6504
  }
5915
- var LABEL2 = "org.sessionpipe.control";
6505
+ var LABEL4 = "org.sessionpipe.control";
5916
6506
  var UNIT2 = "sessionpipe-control.service";
5917
6507
  function serviceFiles(o) {
5918
- const home = o.home ?? os6.homedir();
6508
+ const home = o.home ?? os8.homedir();
5919
6509
  const xml = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
5920
6510
  const plist = `<?xml version="1.0" encoding="UTF-8"?>
5921
6511
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
5922
6512
  <plist version="1.0">
5923
6513
  <dict>
5924
- <key>Label</key><string>${LABEL2}</string>
6514
+ <key>Label</key><string>${LABEL4}</string>
5925
6515
  <key>ProgramArguments</key>
5926
6516
  <array><string>${xml(o.node)}</string><string>${xml(o.cli)}</string><string>control</string><string>run</string></array>
5927
6517
  <key>EnvironmentVariables</key><dict><key>SESSIONPIPE_SERVICE</key><string>launchd</string></dict>
5928
6518
  <key>RunAtLoad</key><true/>
5929
6519
  <key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict>
5930
6520
  <key>ProcessType</key><string>Background</string>
5931
- <key>StandardOutPath</key><string>${xml(path20.join(o.logDir, "daemon.log"))}</string>
5932
- <key>StandardErrorPath</key><string>${xml(path20.join(o.logDir, "daemon.log"))}</string>
6521
+ <key>StandardOutPath</key><string>${xml(path24.join(o.logDir, "daemon.log"))}</string>
6522
+ <key>StandardErrorPath</key><string>${xml(path24.join(o.logDir, "daemon.log"))}</string>
5933
6523
  </dict>
5934
6524
  </plist>
5935
6525
  `;
@@ -5949,8 +6539,8 @@ RestartSec=5
5949
6539
  WantedBy=default.target
5950
6540
  `;
5951
6541
  return {
5952
- plist: { file: path20.join(home, "Library", "LaunchAgents", `${LABEL2}.plist`), body: plist },
5953
- unit: { file: path20.join(home, ".config", "systemd", "user", UNIT2), body: unit }
6542
+ plist: { file: path24.join(home, "Library", "LaunchAgents", `${LABEL4}.plist`), body: plist },
6543
+ unit: { file: path24.join(home, ".config", "systemd", "user", UNIT2), body: unit }
5954
6544
  };
5955
6545
  }
5956
6546
  var execQuiet = (cmd2, args2) => execFileSync6(cmd2, args2, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 15e3 });
@@ -5975,37 +6565,37 @@ function ensureLinger(user, exec = execQuiet) {
5975
6565
  }
5976
6566
  function installService(o) {
5977
6567
  const logDir = controlState(o.env);
5978
- mkdirSync10(logDir, { recursive: true, mode: 448 });
6568
+ mkdirSync11(logDir, { recursive: true, mode: 448 });
5979
6569
  const f = serviceFiles({ node: o.node, cli: o.cli, logDir });
5980
6570
  const run = o.exec ?? execQuiet;
5981
6571
  const platform = o.platform ?? process.platform;
5982
6572
  if (platform === "darwin") {
5983
- mkdirSync10(path20.dirname(f.plist.file), { recursive: true });
5984
- writeFileSync8(f.plist.file, f.plist.body);
6573
+ mkdirSync11(path24.dirname(f.plist.file), { recursive: true });
6574
+ writeFileSync9(f.plist.file, f.plist.body);
5985
6575
  const uid = String(process.getuid?.() ?? "");
5986
6576
  for (const d of ["gui", "user"])
5987
6577
  try {
5988
- run("launchctl", ["bootout", `${d}/${uid}/${LABEL2}`]);
6578
+ run("launchctl", ["bootout", `${d}/${uid}/${LABEL4}`]);
5989
6579
  } catch {
5990
6580
  }
5991
6581
  try {
5992
6582
  run("launchctl", ["bootstrap", `gui/${uid}`, f.plist.file]);
5993
- return `launchd agent ${LABEL2} (log: ${path20.join(logDir, "daemon.log")})`;
6583
+ return `launchd agent ${LABEL4} (log: ${path24.join(logDir, "daemon.log")})`;
5994
6584
  } catch {
5995
6585
  }
5996
6586
  run("launchctl", ["bootstrap", `user/${uid}`, f.plist.file]);
5997
- return `launchd agent ${LABEL2} in the background domain, since nobody is logged in at this Mac's screen; after a restart it starts again when someone logs in there (log: ${path20.join(logDir, "daemon.log")})`;
6587
+ return `launchd agent ${LABEL4} in the background domain, since nobody is logged in at this Mac's screen; after a restart it starts again when someone logs in there (log: ${path24.join(logDir, "daemon.log")})`;
5998
6588
  }
5999
6589
  if (platform === "linux") {
6000
- mkdirSync10(path20.dirname(f.unit.file), { recursive: true });
6001
- writeFileSync8(f.unit.file, f.unit.body);
6590
+ mkdirSync11(path24.dirname(f.unit.file), { recursive: true });
6591
+ writeFileSync9(f.unit.file, f.unit.body);
6002
6592
  try {
6003
6593
  run("systemctl", ["--user", "daemon-reload"]);
6004
6594
  run("systemctl", ["--user", "enable", "--now", UNIT2]);
6005
6595
  } catch {
6006
6596
  return `wrote ${f.unit.file}, but systemd --user isn't reachable here; run \`sessionpipe control run\` yourself (tmux, screen)`;
6007
6597
  }
6008
- const linger = ensureLinger(os6.userInfo().username, run);
6598
+ const linger = ensureLinger(os8.userInfo().username, run);
6009
6599
  return `systemd user unit ${UNIT2}; ${linger.note} (journalctl --user -u ${UNIT2})`;
6010
6600
  }
6011
6601
  return "no service manager for this platform yet; run `sessionpipe control run` yourself";
@@ -6019,8 +6609,8 @@ function uninstallService(env) {
6019
6609
  }
6020
6610
  };
6021
6611
  if (process.platform === "darwin" && existsSync12(f.plist.file)) {
6022
- run("launchctl", ["bootout", `gui/${process.getuid?.() ?? ""}/${LABEL2}`]);
6023
- run("launchctl", ["bootout", `user/${process.getuid?.() ?? ""}/${LABEL2}`]);
6612
+ run("launchctl", ["bootout", `gui/${process.getuid?.() ?? ""}/${LABEL4}`]);
6613
+ run("launchctl", ["bootout", `user/${process.getuid?.() ?? ""}/${LABEL4}`]);
6024
6614
  unlinkSync6(f.plist.file);
6025
6615
  }
6026
6616
  if (process.platform === "linux" && existsSync12(f.unit.file)) {
@@ -6150,9 +6740,11 @@ var CONTROL_HELP = ` sessionpipe control pair <receiver> [--folder DIR]\u2026 [
6150
6740
  sessionpipe wait [--session <harness>:<id>] (a session runs this in the background)`;
6151
6741
  var MODE_TEXT = ` auto Claude Code's auto mode: the session reads, edits and runs commands, and Claude Code's
6152
6742
  own safety check stops what looks dangerous. Pick this to get work done from your phone.
6743
+ (Codex runs with --approve-for-me, Antigravity with --mode accept-edits.)
6153
6744
  safe Only what each folder's Claude Code settings already allow: with no allow rules, a
6154
6745
  session can't even read a file. Pick this if this machine holds things no agent
6155
- should touch unattended.`;
6746
+ should touch unattended. (Codex runs in its workspace-write sandbox and never
6747
+ escalates; Antigravity is denied every tool call that would need approval.)`;
6156
6748
  async function askMode(out2) {
6157
6749
  if (!process.stdin.isTTY) {
6158
6750
  out2(" Mode: safe for now. Run `sessionpipe control mode auto` to let sessions you message work unattended.");
@@ -6197,7 +6789,7 @@ ${CONTROL_HELP}`);
6197
6789
  const folders = flags("--folder");
6198
6790
  const existing = readControl();
6199
6791
  if (!folders.length && !existing?.folders.length) {
6200
- if (!isBroad(process.cwd(), os7.homedir())) folders.push(process.cwd());
6792
+ if (!isBroad(process.cwd(), os9.homedir())) folders.push(process.cwd());
6201
6793
  else
6202
6794
  out2(
6203
6795
  ` ! Not allowing all of ${process.cwd()}: sessions you message may run only in folders you name. Pass --folder <project>, or run this from inside one.`
@@ -6219,7 +6811,7 @@ ${CONTROL_HELP}`);
6219
6811
  },
6220
6812
  folders,
6221
6813
  mode,
6222
- name: flag2("--name") ?? existing?.name ?? cfg.machine ?? os7.hostname(),
6814
+ name: flag2("--name") ?? existing?.name ?? cfg.machine ?? os9.hostname(),
6223
6815
  out: out2
6224
6816
  });
6225
6817
  const c = readControl();
@@ -6227,7 +6819,7 @@ ${CONTROL_HELP}`);
6227
6819
  out2(
6228
6820
  ` Mode: ${c.mode} (${c.mode === "safe" ? "nothing that needs approval runs unattended" : "Claude Code's auto mode"})`
6229
6821
  );
6230
- let cli = path21.join(distDir2, "cli.js");
6822
+ let cli = path25.join(distDir2, "cli.js");
6231
6823
  try {
6232
6824
  cli = realpathSync5(cli);
6233
6825
  } catch {
@@ -6310,6 +6902,10 @@ async function runDaemon(out2, distDir2) {
6310
6902
  log: log2,
6311
6903
  claudeDirs: () => claudeDirs(),
6312
6904
  claude: claudeNow,
6905
+ codex: () => codex_control_exports.findCodex(),
6906
+ agy: () => antigravity_control_exports.findAgy(),
6907
+ codexLogin: (bin) => codex_control_exports.codexLogin(bin),
6908
+ harnesses: () => drivableHarnesses(),
6313
6909
  run: runClaude2,
6314
6910
  sdk,
6315
6911
  login: async (configDir) => {
@@ -6361,7 +6957,7 @@ async function runDaemon(out2, distDir2) {
6361
6957
  log2(`exiting ${UPDATE_EXIT} so ${via === "supervisor" ? "the supervising sessionpipe" : via} starts ${to}`);
6362
6958
  process.exit(UPDATE_EXIT);
6363
6959
  }
6364
- let cli = path21.join(distDir2, "cli.js");
6960
+ let cli = path25.join(distDir2, "cli.js");
6365
6961
  try {
6366
6962
  cli = realpathSync5(cli);
6367
6963
  } catch {
@@ -6406,16 +7002,16 @@ function adoptSinkToken(url, token, tier, file) {
6406
7002
  }
6407
7003
 
6408
7004
  // src/launcher.ts
6409
- import { accessSync as accessSync3, chmodSync as chmodSync3, constants as constants3, mkdirSync as mkdirSync11, readFileSync as readFileSync12, renameSync as renameSync4, rmSync, writeFileSync as writeFileSync9 } from "node:fs";
6410
- import os8 from "node:os";
6411
- import path22 from "node:path";
7005
+ import { accessSync as accessSync3, chmodSync as chmodSync4, constants as constants3, mkdirSync as mkdirSync12, readFileSync as readFileSync14, renameSync as renameSync5, rmSync, writeFileSync as writeFileSync10 } from "node:fs";
7006
+ import os10 from "node:os";
7007
+ import path26 from "node:path";
6412
7008
  var LAUNCHER = "sessionpipe-hook";
6413
7009
  var COPIED = ["worker.js", "hook.js"];
6414
7010
  function dataDir(env = process.env) {
6415
- if (env.SESSIONPIPE_DATA) return path22.resolve(env.SESSIONPIPE_DATA);
6416
- return path22.join(env.XDG_DATA_HOME || path22.join(os8.homedir(), ".local", "share"), "sessionpipe");
7011
+ if (env.SESSIONPIPE_DATA) return path26.resolve(env.SESSIONPIPE_DATA);
7012
+ return path26.join(env.XDG_DATA_HOME || path26.join(os10.homedir(), ".local", "share"), "sessionpipe");
6417
7013
  }
6418
- var launcherPath = (env = process.env) => path22.join(dataDir(env), LAUNCHER);
7014
+ var launcherPath = (env = process.env) => path26.join(dataDir(env), LAUNCHER);
6419
7015
  var usesLauncher = (platform = process.platform) => platform !== "win32";
6420
7016
  var sq = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
6421
7017
  function launcherScript(node, hook, version) {
@@ -6430,37 +7026,37 @@ exec "$n" ${sq(hook)} "$@"
6430
7026
  }
6431
7027
  function writeIfChanged(file, body, mode) {
6432
7028
  try {
6433
- const cur = readFileSync12(file);
7029
+ const cur = readFileSync14(file);
6434
7030
  if (cur.equals(Buffer.isBuffer(body) ? body : Buffer.from(body))) {
6435
- chmodSync3(file, mode);
7031
+ chmodSync4(file, mode);
6436
7032
  return false;
6437
7033
  }
6438
7034
  } catch {
6439
7035
  }
6440
7036
  const tmp = `${file}.${process.pid}.tmp`;
6441
- writeFileSync9(tmp, body, { mode });
6442
- renameSync4(tmp, file);
6443
- chmodSync3(file, mode);
7037
+ writeFileSync10(tmp, body, { mode });
7038
+ renameSync5(tmp, file);
7039
+ chmodSync4(file, mode);
6444
7040
  return true;
6445
7041
  }
6446
7042
  function installLauncher(o) {
6447
7043
  const dir = dataDir(o.env);
6448
- mkdirSync11(dir, { recursive: true, mode: 493 });
7044
+ mkdirSync12(dir, { recursive: true, mode: 493 });
6449
7045
  let changed = false;
6450
7046
  for (const f of COPIED)
6451
- changed = writeIfChanged(path22.join(dir, f), readFileSync12(path22.join(o.distDir, f)), 420) || changed;
6452
- const launcher = path22.join(dir, LAUNCHER);
6453
- changed = writeIfChanged(launcher, launcherScript(o.node, path22.join(dir, "hook.js"), o.version), 493) || changed;
7047
+ changed = writeIfChanged(path26.join(dir, f), readFileSync14(path26.join(o.distDir, f)), 420) || changed;
7048
+ const launcher = path26.join(dir, LAUNCHER);
7049
+ changed = writeIfChanged(launcher, launcherScript(o.node, path26.join(dir, "hook.js"), o.version), 493) || changed;
6454
7050
  return { launcher, changed };
6455
7051
  }
6456
7052
  function removeLauncher(env = process.env) {
6457
- for (const f of [LAUNCHER, ...COPIED]) rmSync(path22.join(dataDir(env), f), { force: true });
7053
+ for (const f of [LAUNCHER, ...COPIED]) rmSync(path26.join(dataDir(env), f), { force: true });
6458
7054
  }
6459
7055
  function launcherState(version, env = process.env) {
6460
7056
  const file = launcherPath(env);
6461
7057
  let text = "";
6462
7058
  try {
6463
- text = readFileSync12(file, "utf8");
7059
+ text = readFileSync14(file, "utf8");
6464
7060
  } catch {
6465
7061
  return {
6466
7062
  file,
@@ -6488,7 +7084,7 @@ function launcherState(version, env = process.env) {
6488
7084
  );
6489
7085
  for (const f of COPIED)
6490
7086
  try {
6491
- accessSync3(path22.join(path22.dirname(file), f), constants3.R_OK);
7087
+ accessSync3(path26.join(path26.dirname(file), f), constants3.R_OK);
6492
7088
  } catch {
6493
7089
  problems.push(`${f} is missing beside the hook launcher; run \`sessionpipe install\` again`);
6494
7090
  }
@@ -6515,14 +7111,14 @@ var out = (s = "") => {
6515
7111
  `);
6516
7112
  };
6517
7113
  var state = stateDir();
6518
- var distDir = path23.dirname(fileURLToPath(import.meta.url));
7114
+ var distDir = path27.dirname(fileURLToPath(import.meta.url));
6519
7115
  function hookCommand() {
6520
7116
  if (usesLauncher()) {
6521
7117
  const l = launcherPath();
6522
7118
  return (a) => ({ argv: [l, ...a], command: `${JSON.stringify(l)} ${a.join(" ")}` });
6523
7119
  }
6524
7120
  const node = stableNode(process.execPath);
6525
- let script = path23.join(distDir, "hook.js");
7121
+ let script = path27.join(distDir, "hook.js");
6526
7122
  try {
6527
7123
  script = realpathSync6(script);
6528
7124
  } catch {
@@ -6613,7 +7209,7 @@ async function install() {
6613
7209
  );
6614
7210
  return;
6615
7211
  }
6616
- out(` Machine: ${machineName(cfg, os9.hostname())}`);
7212
+ out(` Machine: ${machineName(cfg, os11.hostname())}`);
6617
7213
  const lean = isLean(cfg);
6618
7214
  if (lean)
6619
7215
  out(
@@ -6675,7 +7271,7 @@ async function connect() {
6675
7271
  const cfg = readConfig();
6676
7272
  const machine = flag("--machine");
6677
7273
  if (machine) cfg.machine = machine.slice(0, 80);
6678
- const name = machineName(cfg, os9.hostname());
7274
+ const name = machineName(cfg, os11.hostname());
6679
7275
  const before = cfg.sinks.find((x) => x.url.replace(/\/$/, "") === url);
6680
7276
  const tier = connectTier(flag("--tier"), before?.tier);
6681
7277
  out(` Connecting ${name} to ${host}.`);
@@ -6697,13 +7293,13 @@ async function connect() {
6697
7293
  const recent = rows.slice().sort((a, b) => b.row.last_at - a.row.last_at).map((r) => r.row.session.cwd ?? "");
6698
7294
  const choice = connectFolders({
6699
7295
  cwd: process.cwd(),
6700
- home: os9.homedir(),
7296
+ home: os11.homedir(),
6701
7297
  flags: argsAll("--folder"),
6702
7298
  existing: control?.folders ?? [],
6703
7299
  recent,
6704
7300
  exists: (d) => {
6705
7301
  try {
6706
- return statSync9(d).isDirectory();
7302
+ return statSync10(d).isDirectory();
6707
7303
  } catch {
6708
7304
  return false;
6709
7305
  }
@@ -6756,7 +7352,7 @@ async function connect() {
6756
7352
  out(` \u2713 Keys: in ${storeLabel(store)}${kept.length ? ` (left in the file: ${kept.join(", ")})` : ""}`);
6757
7353
  } else
6758
7354
  out(
6759
- ` Keys: in ${tilde(path23.dirname(configFile()))} at 0600 \u2014 no unlocked keychain in this session${process.env.SSH_CONNECTION ? " (an ssh shell)" : ""}`
7355
+ ` Keys: in ${tilde(path27.dirname(configFile()))} at 0600 \u2014 no unlocked keychain in this session${process.env.SSH_CONNECTION ? " (an ssh shell)" : ""}`
6760
7356
  );
6761
7357
  if (rows.length) queueBackfill(rows, 30, readConfig());
6762
7358
  out("");
@@ -6854,7 +7450,7 @@ async function sink() {
6854
7450
  if (!s) return out(` no sink named ${args[2]}`);
6855
7451
  const [sk] = buildSinks([s]);
6856
7452
  if (!sk) return out(" sink type not supported yet");
6857
- const session = { id: `test-${Date.now()}`, machine: machineName(cfg, os9.hostname()) };
7453
+ const session = { id: `test-${Date.now()}`, machine: machineName(cfg, os11.hostname()) };
6858
7454
  const mk = (type, seq) => makeEvent(
6859
7455
  { type, harnessEvent: "test", data: {} },
6860
7456
  { harness: { name: "sessionpipe" }, session: { ...session, seq } }
@@ -6937,7 +7533,7 @@ function backfillRows(adapters, days, cfg) {
6937
7533
  function queueBackfill(rows, days, cfg) {
6938
7534
  const outbox = new Outbox(state);
6939
7535
  let n = 0;
6940
- const machine = machineName(cfg, os9.hostname());
7536
+ const machine = machineName(cfg, os11.hostname());
6941
7537
  for (const { harness, row } of rows) {
6942
7538
  const ref = { harness, session: row.session.id };
6943
7539
  const facts = factsState(state).get(harness, row.session.id);
@@ -6979,7 +7575,7 @@ async function forget() {
6979
7575
  { type: "session.forgotten", harnessEvent: "forget", data: {} },
6980
7576
  {
6981
7577
  harness: { name: harness },
6982
- session: { id: session, seq: outbox.nextSeq(ref), machine: machineName(cfg, os9.hostname()) }
7578
+ session: { id: session, seq: outbox.nextSeq(ref), machine: machineName(cfg, os11.hostname()) }
6983
7579
  }
6984
7580
  );
6985
7581
  const sinks = buildSinks(cfg.sinks);
@@ -7004,7 +7600,7 @@ async function replay() {
7004
7600
  function status() {
7005
7601
  const cfg = readConfig();
7006
7602
  const hc = hookCommand();
7007
- out(` machine: ${machineName(cfg, os9.hostname())} sessionpipe ${VERSION} state ${tilde(state)}`);
7603
+ out(` machine: ${machineName(cfg, os11.hostname())} sessionpipe ${VERSION} state ${tilde(state)}`);
7008
7604
  for (const a of ADAPTERS) {
7009
7605
  if (!a.detect() && !cfg.harnesses[a.name]) continue;
7010
7606
  for (const r of a.installed(hc, process.env, { lean: isLean(cfg) }))
@@ -7020,7 +7616,7 @@ function status() {
7020
7616
  for (const ref of outbox.sessions()) {
7021
7617
  const f = outbox.file(ref);
7022
7618
  try {
7023
- pending += Math.max(0, statSync9(f).size - (c[`${ref.harness}/${ref.session}`] ?? 0)) > 0 ? 1 : 0;
7619
+ pending += Math.max(0, statSync10(f).size - (c[`${ref.harness}/${ref.session}`] ?? 0)) > 0 ? 1 : 0;
7024
7620
  } catch {
7025
7621
  }
7026
7622
  }
@@ -7042,12 +7638,12 @@ function status() {
7042
7638
  }
7043
7639
  }
7044
7640
  function hookWall(runs = 8) {
7045
- const hook = path23.join(distDir, "hook.js");
7046
- const tmp = mkdtempSync(path23.join(os9.tmpdir(), "sessionpipe-doctor-"));
7641
+ const hook = path27.join(distDir, "hook.js");
7642
+ const tmp = mkdtempSync(path27.join(os11.tmpdir(), "sessionpipe-doctor-"));
7047
7643
  const env = {
7048
7644
  ...process.env,
7049
7645
  SESSIONPIPE_STATE: tmp,
7050
- SESSIONPIPE_CONFIG: path23.join(tmp, "config.json"),
7646
+ SESSIONPIPE_CONFIG: path27.join(tmp, "config.json"),
7051
7647
  SESSIONPIPE_NO_WORKER: "1"
7052
7648
  };
7053
7649
  const input = JSON.stringify({
@@ -7075,7 +7671,7 @@ function hookWall(runs = 8) {
7075
7671
  }
7076
7672
  function timing() {
7077
7673
  try {
7078
- const lines = readFileSync13(path23.join(state, "timing.jsonl"), "utf8").trim().split("\n").slice(-100);
7674
+ const lines = readFileSync15(path27.join(state, "timing.jsonl"), "utf8").trim().split("\n").slice(-100);
7079
7675
  const ms = lines.map((l) => JSON.parse(l).ms).filter((x) => x >= 0).sort((a, b) => a - b);
7080
7676
  if (!ms.length) return null;
7081
7677
  const q = (p) => Math.round(ms[Math.min(ms.length - 1, Math.floor(p * ms.length))] ?? 0);
@@ -7091,7 +7687,7 @@ async function doctor() {
7091
7687
  version: VERSION,
7092
7688
  node: process.version,
7093
7689
  node_path: stableNode(process.execPath),
7094
- machine: machineName(cfg, os9.hostname()),
7690
+ machine: machineName(cfg, os11.hostname()),
7095
7691
  state,
7096
7692
  config: configFile(),
7097
7693
  harnesses: {},
@@ -7139,7 +7735,7 @@ async function doctor() {
7139
7735
  let linger = null;
7140
7736
  try {
7141
7737
  linger = /^Linger=yes/m.test(
7142
- execFileSync7("loginctl", ["show-user", os9.userInfo().username, "--property=Linger"], {
7738
+ execFileSync7("loginctl", ["show-user", os11.userInfo().username, "--property=Linger"], {
7143
7739
  encoding: "utf8",
7144
7740
  timeout: 5e3,
7145
7741
  stdio: ["ignore", "pipe", "ignore"]
@@ -7150,7 +7746,7 @@ async function doctor() {
7150
7746
  report.linger = linger;
7151
7747
  if (linger === false)
7152
7748
  warnings.push(
7153
- `the control daemon stops when you log out: \`loginctl enable-linger ${os9.userInfo().username}\` (with sudo if refused) keeps it running`
7749
+ `the control daemon stops when you log out: \`loginctl enable-linger ${os11.userInfo().username}\` (with sudo if refused) keeps it running`
7154
7750
  );
7155
7751
  }
7156
7752
  if (/\/Cellar\/node(?:@\d+)?\/[^/]+\//.test(distDir))
@@ -7273,7 +7869,7 @@ async function tail() {
7273
7869
  out(`sessionpipe tail \xB7 tier ${tier} \xB7 ${tilde(state)}/outbox (Ctrl-C to stop)`);
7274
7870
  for (const ref of outbox.sessions()) {
7275
7871
  try {
7276
- offsets.set(`${ref.harness}/${ref.session}`, has("--all") ? 0 : statSync9(outbox.file(ref)).size);
7872
+ offsets.set(`${ref.harness}/${ref.session}`, has("--all") ? 0 : statSync10(outbox.file(ref)).size);
7277
7873
  } catch {
7278
7874
  }
7279
7875
  }
@@ -7296,7 +7892,7 @@ async function tail() {
7296
7892
  async function workerFromArgv() {
7297
7893
  const f = args[1];
7298
7894
  if (!f) return;
7299
- const job = JSON.parse(readFileSync13(f, "utf8"));
7895
+ const job = JSON.parse(readFileSync15(f, "utf8"));
7300
7896
  try {
7301
7897
  unlinkSync7(f);
7302
7898
  } catch {