sessionpipe 0.6.0 → 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.0 · 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
  }
@@ -381,20 +384,19 @@ function installedClaudeShaped(file, harness, events, cmd2, o) {
381
384
  // ../core/src/adapters/antigravity.ts
382
385
  var NAME = "antigravity";
383
386
  var HOOK_NAME = "sessionpipe";
384
- var AG_EVENTS = ["PreInvocation", "PostInvocation", "PreToolUse", "PostToolUse", "Stop"];
387
+ var AG_EVENTS = ["PreInvocation", "PostInvocation", "PostToolUse", "Stop"];
388
+ var AG_STALE_PRETOOL = '{"decision":"ask","reason":"sessionpipe: an outdated PreToolUse hook is still loaded. Restart this conversation (or run sessionpipe install) to drop it."}\n';
389
+ var antigravityAnswer = (event) => event === "PreToolUse" ? AG_STALE_PRETOOL : "{}\n";
385
390
  var gemini = (env) => env.GEMINI_CLI_HOME || path4.join(HOME, ".gemini");
386
391
  var hooksFile = (env) => path4.join(gemini(env), "config", "hooks.json");
387
392
  var projectsDir = (env) => path4.join(gemini(env), "config", "projects");
388
393
  var dataDirs = (env) => ["antigravity", "antigravity-cli", "antigravity-ide"].map((d) => path4.join(gemini(env), d));
389
394
  var ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
390
395
  var validTool = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
391
- function entry(cmd2, lean) {
396
+ function entry(cmd2) {
392
397
  const handler = (e) => ({ type: "command", command: cmd2([NAME, e]).command, timeout: 5 });
393
398
  const out2 = { enabled: true };
394
- for (const e of AG_EVENTS) {
395
- if (lean && e === "PreToolUse") continue;
396
- out2[e] = /Tool/.test(e) ? [{ matcher: "*", hooks: [handler(e)] }] : [handler(e)];
397
- }
399
+ for (const e of AG_EVENTS) out2[e] = /Tool/.test(e) ? [{ matcher: "*", hooks: [handler(e)] }] : [handler(e)];
398
400
  return out2;
399
401
  }
400
402
  var antigravity = {
@@ -402,12 +404,12 @@ var antigravity = {
402
404
  events: AG_EVENTS,
403
405
  detect: (env = process.env) => existsSync2(path4.join(gemini(env), "config")) || dataDirs(env).some((d) => existsSync2(d)),
404
406
  configFiles: (env = process.env) => [hooksFile(env)],
405
- install(cmd2, env = process.env, opts = {}) {
407
+ install(cmd2, env = process.env) {
406
408
  const file = hooksFile(env);
407
409
  const f = readJsonFile(file);
408
410
  if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
409
411
  const hooks = f.value;
410
- const e = entry(cmd2, opts.lean);
412
+ const e = entry(cmd2);
411
413
  if (JSON.stringify(hooks[HOOK_NAME]) === JSON.stringify(e)) return [{ file, changed: false }];
412
414
  hooks[HOOK_NAME] = e;
413
415
  mkdirSync2(path4.dirname(file), { recursive: true });
@@ -436,14 +438,14 @@ var antigravity = {
436
438
  writeFileSync2(file, formatJson(hooks, f));
437
439
  return [{ file, changed: true }];
438
440
  },
439
- installed(cmd2, env = process.env, opts = {}) {
441
+ installed(cmd2, env = process.env) {
440
442
  const file = hooksFile(env);
441
443
  const f = readJsonFile(file);
442
444
  const have = f.ok ? f.value[HOOK_NAME] : void 0;
443
445
  return [
444
446
  {
445
447
  file,
446
- state: !have ? "missing" : JSON.stringify(have) === JSON.stringify(entry(cmd2, opts.lean)) ? "current" : "stale"
448
+ state: !have ? "missing" : JSON.stringify(have) === JSON.stringify(entry(cmd2)) ? "current" : "stale"
447
449
  }
448
450
  ];
449
451
  },
@@ -455,7 +457,8 @@ var antigravity = {
455
457
  } catch {
456
458
  }
457
459
  const id = s.conversationId ?? s.session_id ?? s.sessionId;
458
- if (!id) return { session: { id: "" }, events: [], stdout: "{}\n" };
460
+ const stdout = antigravityAnswer(event);
461
+ if (!id) return { session: { id: "" }, events: [], stdout };
459
462
  const workspaces = agWorkspaces(s.workspacePaths);
460
463
  const session = { id: String(id), cwd: workspaces[0] ?? input.cwd };
461
464
  if (typeof s.modelName === "string" && s.modelName !== "auto") session.model = s.modelName;
@@ -476,7 +479,8 @@ var antigravity = {
476
479
  if (tool) events.push(ev("tool.started", { tool, input: toolCall?.args }));
477
480
  break;
478
481
  case "PostToolUse":
479
- if (tool)
482
+ if (!tool) events.push(ev("session.heartbeat", {}));
483
+ else
480
484
  events.push(
481
485
  ev("tool.ended", {
482
486
  tool,
@@ -490,9 +494,9 @@ var antigravity = {
490
494
  events.push(ev("turn.ended", { reason: s.error ? "error" : "stop" }));
491
495
  break;
492
496
  default:
493
- return { session, events: [], stdout: "{}\n" };
497
+ return { session, events: [], stdout };
494
498
  }
495
- const r = { session, events, stdout: "{}\n", hints: { workspaces } };
499
+ const r = { session, events, stdout, hints: { workspaces } };
496
500
  if (typeof s.transcriptPath === "string") r.transcript = s.transcriptPath;
497
501
  return r;
498
502
  },
@@ -656,11 +660,288 @@ function antigravityFacts(file, opts) {
656
660
  return out2;
657
661
  }
658
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
+
659
940
  // ../core/src/adapters/claude-code.ts
660
941
  import { execFileSync as execFileSync2 } from "node:child_process";
661
942
  import { createHash } from "node:crypto";
662
- import { existsSync as existsSync3, readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as statSync3 } from "node:fs";
663
- 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";
664
945
 
665
946
  // ../core/src/readers/git.ts
666
947
  import { execFileSync } from "node:child_process";
@@ -727,23 +1008,23 @@ function claudeDirs(env = process.env, home = env.HOME || HOME) {
727
1008
  const out2 = [];
728
1009
  const add = (d) => {
729
1010
  if (!d) return;
730
- const r = path5.resolve(d);
1011
+ const r = path7.resolve(d);
731
1012
  if (!out2.includes(r)) out2.push(r);
732
1013
  };
733
- add(path5.join(home, ".claude"));
1014
+ add(path7.join(home, ".claude"));
734
1015
  add(env.CLAUDE_CONFIG_DIR);
735
1016
  try {
736
1017
  for (const e of readdirSync3(home, { withFileTypes: true })) {
737
1018
  if (!e.isDirectory() || !/^\.claude[-_.]/.test(e.name)) continue;
738
- const d = path5.join(home, e.name);
739
- 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);
740
1021
  }
741
1022
  } catch {
742
1023
  }
743
1024
  return out2.filter((d, i) => i === 0 || existsSync3(d));
744
1025
  }
745
- var settingsIn = (dir) => path5.join(dir, "settings.json");
746
- 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)));
747
1028
  var validTool2 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
748
1029
  var endReason = (r) => r === "clear" || r === "logout" || r === "exit" ? r : r === "prompt_input_exit" ? "exit" : "other";
749
1030
  var notifKind = (t) => t === "permission_prompt" ? "permission" : t === "idle_prompt" ? "idle" : t === "elicitation_dialog" ? "elicitation" : "question";
@@ -915,11 +1196,11 @@ var claudeCode = {
915
1196
  const rows = [];
916
1197
  const files = [];
917
1198
  for (const d of claudeDirs(env))
918
- 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)));
919
1200
  const noState = { get: () => ({}), save: () => {
920
1201
  } };
921
1202
  for (const f of files) {
922
- const id = path5.basename(f, ".jsonl");
1203
+ const id = path7.basename(f, ".jsonl");
923
1204
  const facts = claudeFacts(f, id, {}, noState);
924
1205
  if (!facts.started_at || !facts.last_at) continue;
925
1206
  const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
@@ -979,12 +1260,12 @@ function claudeLive(dir) {
979
1260
  const map = /* @__PURE__ */ new Map();
980
1261
  let names = [];
981
1262
  try {
982
- names = readdirSync3(path5.join(dir, "sessions")).filter((n) => n.endsWith(".json"));
1263
+ names = readdirSync3(path7.join(dir, "sessions")).filter((n) => n.endsWith(".json"));
983
1264
  } catch {
984
1265
  }
985
1266
  for (const n of names) {
986
1267
  try {
987
- 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")));
988
1269
  if (e?.sessionId) map.set(e.sessionId, e);
989
1270
  } catch {
990
1271
  }
@@ -995,7 +1276,7 @@ function claudeLive(dir) {
995
1276
  function claudeLiveByPid(dir, pid) {
996
1277
  if (!pid) return null;
997
1278
  try {
998
- 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")));
999
1280
  } catch {
1000
1281
  return null;
1001
1282
  }
@@ -1003,7 +1284,7 @@ function claudeLiveByPid(dir, pid) {
1003
1284
  function claudePid(env = process.env) {
1004
1285
  try {
1005
1286
  const dirs = claudeDirs(env);
1006
- 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`)));
1007
1288
  let pid = process.ppid;
1008
1289
  if (has2(pid)) return pid;
1009
1290
  const parent = parentOf();
@@ -1046,8 +1327,8 @@ var accountCache = /* @__PURE__ */ new Map();
1046
1327
  function claudeAccount(dir) {
1047
1328
  const hit = accountCache.get(dir);
1048
1329
  if (hit !== void 0) return hit;
1049
- const files = [path5.join(dir, ".claude.json")];
1050
- 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"));
1051
1332
  let acct = null;
1052
1333
  for (const f of files) {
1053
1334
  try {
@@ -1099,7 +1380,7 @@ function scanTitles(file, from, found) {
1099
1380
  const out2 = { custom: found.custom ?? null, ai: found.ai ?? null, upto: from };
1100
1381
  let size = 0;
1101
1382
  try {
1102
- size = statSync3(file).size;
1383
+ size = statSync4(file).size;
1103
1384
  } catch {
1104
1385
  return out2;
1105
1386
  }
@@ -1132,7 +1413,7 @@ function claudeFacts(file, sessionId, hints, state2) {
1132
1413
  const H = parseLines(head);
1133
1414
  const T = parseLines(tail2);
1134
1415
  const out2 = {};
1135
- const id = sessionId || path5.basename(file, ".jsonl");
1416
+ const id = sessionId || path7.basename(file, ".jsonl");
1136
1417
  const mem = state2.get(NAME2, id);
1137
1418
  let custom = null;
1138
1419
  let ai = null;
@@ -1142,7 +1423,7 @@ function claudeFacts(file, sessionId, hints, state2) {
1142
1423
  }
1143
1424
  let size = 0;
1144
1425
  try {
1145
- size = statSync3(file).size;
1426
+ size = statSync4(file).size;
1146
1427
  } catch {
1147
1428
  }
1148
1429
  if (!custom && !ai && size > 64 * 1024 + 256 * 1024) {
@@ -1212,1088 +1493,1121 @@ function claudeFacts(file, sessionId, hints, state2) {
1212
1493
  return out2;
1213
1494
  }
1214
1495
 
1215
- // ../core/src/adapters/codex.ts
1216
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
1217
- import path6 from "node:path";
1218
-
1219
- // ../core/src/adapters/codex-config.ts
1220
- var NOTIFY_LINE = /^[ \t]*notify[ \t]*=[ \t]*(\[.*\])[ \t]*(?:#.*)?$/m;
1221
- var NOTIFY_KEY = /^[ \t]*notify[ \t]*=/m;
1222
- var OURS2 = /(?:hook\.js|sessionpipe-hook)",\s*"codex",\s*"notify"/;
1223
- function scan(line, st) {
1224
- let i = 0;
1225
- while (i < line.length) {
1226
- if (st.str) {
1227
- const j = line.indexOf(st.str, i);
1228
- if (j < 0) return;
1229
- i = j + 3;
1230
- st.str = null;
1231
- continue;
1232
- }
1233
- const ch = line[i];
1234
- if (ch === "#") return;
1235
- if (line.startsWith('"""', i) || line.startsWith("'''", i)) {
1236
- st.str = line.slice(i, i + 3);
1237
- i += 3;
1238
- continue;
1239
- }
1240
- if (ch === '"') {
1241
- i++;
1242
- while (i < line.length && line[i] !== '"') i += line[i] === "\\" ? 2 : 1;
1243
- i++;
1244
- continue;
1245
- }
1246
- if (ch === "'") {
1247
- const j = line.indexOf("'", i + 1);
1248
- i = j < 0 ? line.length : j + 1;
1249
- continue;
1250
- }
1251
- if (ch === "[") st.depth++;
1252
- else if (ch === "]") st.depth = Math.max(0, st.depth - 1);
1253
- 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);
1254
1536
  }
1255
- }
1256
- function firstTableAt(text) {
1257
- const st = { depth: 0, str: null };
1258
- let pos = 0;
1259
- for (const line of text.split("\n")) {
1260
- if (!st.str && st.depth === 0 && /^[ \t]*\[/.test(line)) return pos;
1261
- scan(line, st);
1262
- 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 {
1263
1549
  }
1264
- return -1;
1265
- }
1266
- function split(text) {
1267
- const at = firstTableAt(text);
1268
- return at < 0 ? { head: text, tail: "", at: text.length } : { head: text.slice(0, at), tail: text.slice(at), at };
1269
- }
1270
- function parseArgv(raw) {
1271
- try {
1272
- const a = JSON.parse(raw);
1273
- return Array.isArray(a) && a.every((x) => typeof x === "string") ? a : null;
1274
- } catch {
1275
- return null;
1276
- }
1277
- }
1278
- function readNotify(text) {
1279
- const { head, tail: tail2, at } = split(text);
1280
- const m = NOTIFY_LINE.exec(head);
1281
- if (m)
1282
- return {
1283
- raw: m[1] ?? "",
1284
- argv: parseArgv(m[1] ?? ""),
1285
- index: m.index,
1286
- length: m[0].length,
1287
- misplaced: false,
1288
- other: !OURS2.test(m[1] ?? "")
1289
- };
1290
- const k = NOTIFY_KEY.exec(head);
1291
- if (k) {
1292
- const rest = head.slice(k.index).split("\n")[0] ?? "";
1293
- return {
1294
- raw: rest.replace(NOTIFY_KEY, "").trim(),
1295
- argv: null,
1296
- index: k.index,
1297
- length: 0,
1298
- misplaced: false,
1299
- other: true
1300
- };
1550
+ for (const v of versions) {
1551
+ const p = path8.join(app, v, "claude.app", "Contents", "MacOS", "claude");
1552
+ if (isExe(p)) return p;
1301
1553
  }
1302
- const t = NOTIFY_LINE.exec(tail2);
1303
- if (t && OURS2.test(t[1] ?? ""))
1304
- return {
1305
- raw: t[1] ?? "",
1306
- argv: parseArgv(t[1] ?? ""),
1307
- index: at + t.index,
1308
- length: t[0].length,
1309
- misplaced: true,
1310
- other: false
1311
- };
1312
1554
  return null;
1313
1555
  }
1314
- function dropLine(text, index, length) {
1315
- return text.slice(0, index) + text.slice(index + length).replace(/^\r?\n/, "");
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(" ");
1316
1565
  }
1317
- function insertTopLevel(text, line) {
1318
- const { head, tail: tail2 } = split(text);
1319
- if (!tail2) return (head.trim() ? head.replace(/\s*$/, "\n") : "") + line + "\n";
1320
- return `${(head.trim() ? head.replace(/\s*$/, "\n") : "") + line}
1321
-
1322
- ${tail2}`;
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
+ };
1323
1576
  }
1324
- function withNotify(text, line) {
1325
- const n = readNotify(text);
1326
- if (n && !n.misplaced && !n.other) return text.slice(0, n.index) + line + text.slice(n.index + n.length);
1327
- if (n?.misplaced) return insertTopLevel(dropLine(text, n.index, n.length), line);
1328
- return insertTopLevel(text, line);
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"] };
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 };
1329
1592
  }
1330
- function replaceNotify(text, line) {
1331
- const n = readNotify(text);
1332
- if (!n || n.misplaced || !n.length) return null;
1333
- return text.slice(0, n.index) + line + text.slice(n.index + n.length);
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;
1608
+ }
1609
+ return e;
1334
1610
  }
1335
- function withoutNotify(text) {
1336
- const n = readNotify(text);
1337
- if (!n || n.other || !n.length) return text;
1338
- return dropLine(text, n.index, n.length);
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 };
1618
+ return {
1619
+ loggedIn: j.loggedIn,
1620
+ ...typeof j.authMethod === "string" ? { method: j.authMethod } : {},
1621
+ ...typeof j.subscriptionType === "string" ? { subscription: j.subscriptionType } : {}
1622
+ };
1623
+ } catch {
1624
+ return { loggedIn: null };
1625
+ }
1339
1626
  }
1340
- var PREVIOUS = "--previous-notify";
1341
- function previousNotify(argv) {
1342
- if (!Array.isArray(argv)) return null;
1343
- const i = argv.indexOf(PREVIOUS);
1344
- 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
+ });
1345
1637
  }
1346
- function stripPreviousRaw(raw) {
1347
- 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\``;
1348
1642
  }
1349
- function withPreviousRaw(raw, prev) {
1350
- const base = stripPreviousRaw(raw).replace(/\s*\]\s*$/, "");
1351
- 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.`;
1352
1649
  }
1353
- var isComputerUse = (argv) => Array.isArray(argv) && typeof argv[0] === "string" && /(^|\/)SkyComputerUseClient$/.test(argv[0]);
1354
-
1355
- // ../core/src/adapters/codex.ts
1356
- var NAME3 = "codex";
1357
- var CODEX_EVENTS = [
1358
- "SessionStart",
1359
- "UserPromptSubmit",
1360
- "PreToolUse",
1361
- "PostToolUse",
1362
- "PermissionRequest",
1363
- "Stop",
1364
- "Interrupt",
1365
- "SubagentStart",
1366
- "SubagentStop",
1367
- "PreCompact",
1368
- "PostCompact",
1369
- "SessionEnd"
1370
- ];
1371
- var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
1372
- var eventsFor2 = (lean) => lean ? CODEX_EVENTS.filter((e) => e !== "PreToolUse") : [...CODEX_EVENTS];
1373
- var codexHome = (env) => env.CODEX_HOME || path6.join(HOME, ".codex");
1374
- var hooksFile2 = (env) => path6.join(codexHome(env), "hooks.json");
1375
- var configFile2 = (env) => path6.join(codexHome(env), "config.toml");
1376
- var sessionsDir = (env) => path6.join(codexHome(env), "sessions");
1377
- var validTool3 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
1378
- var endReason2 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
1379
- function installNotify(cmd2, env) {
1380
- const file = configFile2(env);
1381
- let text = "";
1650
+ function detectCaps(bin) {
1651
+ let help2 = "";
1382
1652
  try {
1383
- text = readFileSync4(file, "utf8");
1384
- } catch {
1385
- }
1386
- const argv = cmd2([NAME3, "notify"]).argv;
1387
- const line = `notify = ${JSON.stringify(argv)}`;
1388
- const n = readNotify(text);
1389
- const write = (next) => {
1390
- mkdirSync3(path6.dirname(file), { recursive: true });
1391
- writeFileSync3(file, next);
1392
- };
1393
- if (n && !n.other) {
1394
- if (!n.misplaced && JSON.stringify(n.argv) === JSON.stringify(argv))
1395
- return { file, changed: false, note: "notify current" };
1396
- write(withNotify(text, line));
1397
- return {
1398
- file,
1399
- changed: true,
1400
- note: n.misplaced ? "notify moved to the top level (it sat inside a [table], where Codex never reads it)" : "notify updated"
1401
- };
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 || "");
1402
1662
  }
1403
- if (n?.other) {
1404
- const prev = previousNotify(n.argv);
1405
- if (isComputerUse(n.argv)) {
1406
- if (prev && OURS2.test(JSON.stringify(prev))) {
1407
- if (JSON.stringify(prev) === JSON.stringify(argv))
1408
- return { file, changed: false, note: "notify current (after Codex Computer Use)" };
1409
- }
1410
- if (!prev || OURS2.test(JSON.stringify(prev))) {
1411
- const next = replaceNotify(text, `notify = ${withPreviousRaw(n.raw, argv)}`);
1412
- if (next) {
1413
- write(next);
1414
- return { file, changed: true, note: "chained after Codex Computer Use (--previous-notify)" };
1415
- }
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 {
1416
1680
  }
1417
1681
  }
1418
- return {
1419
- file,
1420
- changed: false,
1421
- skipped: true,
1422
- 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`
1423
- };
1424
1682
  }
1425
- write(withNotify(text, line));
1426
- return { file, changed: true, note: "notify set at the top level" };
1683
+ return null;
1427
1684
  }
1428
- function uninstallNotify(env) {
1429
- const file = configFile2(env);
1430
- let text;
1431
- try {
1432
- text = readFileSync4(file, "utf8");
1433
- } catch {
1434
- return { file, changed: false };
1435
- }
1436
- const n = readNotify(text);
1437
- if (!n) return { file, changed: false };
1438
- if (!n.other) {
1439
- writeFileSync3(file, withoutNotify(text));
1440
- return { file, changed: true };
1441
- }
1442
- const prev = previousNotify(n.argv);
1443
- if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev))) {
1444
- const next = replaceNotify(text, `notify = ${stripPreviousRaw(n.raw)}`);
1445
- if (next) writeFileSync3(file, next);
1446
- return { file, changed: true };
1447
- }
1448
- return { file, changed: false };
1449
- }
1450
- function findRollout(threadId, env) {
1451
- let best = null;
1452
- const walk = (dir, depth) => {
1453
- 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;
1454
1693
  try {
1455
- names = readdirSync4(dir);
1694
+ j = JSON.parse(line.toString("utf8"));
1456
1695
  } catch {
1457
- return;
1458
- }
1459
- for (const n of names.sort().reverse()) {
1460
- const p = path6.join(dir, n);
1461
- if (n.endsWith(".jsonl") && n.includes(threadId)) {
1462
- best = p;
1463
- return;
1464
- }
1465
- if (depth < 3) {
1466
- walk(p, depth + 1);
1467
- if (best) return;
1468
- }
1696
+ return null;
1469
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;
1470
1703
  };
1471
- walk(sessionsDir(env), 0);
1472
- return best;
1473
- }
1474
- var codex = {
1475
- name: NAME3,
1476
- events: CODEX_EVENTS,
1477
- detect: (env = process.env) => existsSync4(codexHome(env)),
1478
- configFiles: (env = process.env) => [hooksFile2(env), configFile2(env)],
1479
- install: (cmd2, env = process.env, opts = {}) => [
1480
- installClaudeShaped(hooksFile2(env), NAME3, eventsFor2(opts.lean), cmd2, OPTS2),
1481
- installNotify(cmd2, env)
1482
- ],
1483
- uninstall: (env = process.env, opts = {}) => [
1484
- uninstallClaudeShaped(hooksFile2(env), CODEX_EVENTS, { created: opts.created?.includes(hooksFile2(env)) }),
1485
- uninstallNotify(env)
1486
- ],
1487
- installed: (cmd2, env = process.env, opts = {}) => {
1488
- const out2 = [
1489
- {
1490
- file: hooksFile2(env),
1491
- state: installedClaudeShaped(hooksFile2(env), NAME3, eventsFor2(opts.lean), cmd2, OPTS2),
1492
- 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;
1493
1718
  }
1494
- ];
1495
- let text = "";
1496
- try {
1497
- text = readFileSync4(configFile2(env), "utf8");
1498
- } 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);
1499
1736
  }
1500
- const n = readNotify(text);
1501
- const argv = cmd2([NAME3, "notify"]).argv;
1502
- const prev = n ? previousNotify(n.argv) : null;
1503
- const ours = n && (!n.other || prev && JSON.stringify(prev) === JSON.stringify(argv));
1504
- out2.push({
1505
- file: configFile2(env),
1506
- 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"
1507
- });
1508
- return out2;
1509
- },
1510
- fromHook(input) {
1511
- const event = input.argv[1] ?? "";
1512
- let s = {};
1513
- if (event === "notify") {
1514
- const raw = input.argv.find((a) => a.startsWith("{")) ?? input.stdin;
1737
+ } catch {
1738
+ } finally {
1739
+ if (fd !== void 0)
1515
1740
  try {
1516
- s = raw ? JSON.parse(raw) : {};
1741
+ closeSync2(fd);
1517
1742
  } catch {
1518
1743
  }
1519
- const id2 = s["thread-id"] ?? s.thread_id ?? s["session-id"] ?? s.session_id;
1520
- if (!id2) return null;
1521
- const r2 = {
1522
- session: { id: String(id2), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd },
1523
- events: [
1524
- {
1525
- type: "turn.ended",
1526
- data: { reason: "stop", ...typeof s["turn-id"] === "string" ? { turn_id: s["turn-id"] } : {} },
1527
- harnessEvent: "notify"
1528
- }
1529
- ],
1530
- hints: {}
1531
- };
1532
- const file = findRollout(String(id2), input.env);
1533
- if (file) r2.transcript = file;
1534
- return r2;
1535
- }
1536
- try {
1537
- s = input.stdin ? JSON.parse(input.stdin) : {};
1538
- } catch {
1539
- }
1540
- const id = s.session_id;
1541
- if (!id) return null;
1542
- const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
1543
- if (typeof s.model === "string") session.model = s.model;
1544
- const turn_id = typeof s.turn_id === "string" ? s.turn_id : void 0;
1545
- const tool = validTool3(s.tool_name) ? s.tool_name : void 0;
1546
- const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
1547
- const ev = (type, data) => ({ type, data: prune3(data), harnessEvent: event });
1548
- const events = [];
1549
- switch (event) {
1550
- case "SessionStart":
1551
- events.push(
1552
- ev("session.started", {
1553
- source: typeof s.source === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(s.source) ? s.source : "unknown"
1554
- })
1555
- );
1556
- break;
1557
- case "UserPromptSubmit":
1558
- events.push(
1559
- ev("turn.started", {
1560
- turn_id,
1561
- prompt_chars: typeof s.prompt === "string" ? [...s.prompt].length : void 0
1562
- })
1563
- );
1564
- break;
1565
- case "PreToolUse":
1566
- if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, input: s.tool_input }));
1567
- break;
1568
- case "PostToolUse":
1569
- if (tool)
1570
- events.push(
1571
- ev("tool.ended", { tool, call_id, turn_id, ok: true, input: s.tool_input, output: s.tool_response })
1572
- );
1573
- break;
1574
- case "PermissionRequest":
1575
- events.push(
1576
- ev("attention.needed", { attention_id: call_id ?? `perm-${Date.now()}`, kind: "permission", tool })
1577
- );
1578
- break;
1579
- case "Stop":
1580
- events.push(ev("turn.ended", { turn_id, reason: "stop" }));
1581
- break;
1582
- case "Interrupt":
1583
- events.push(ev("turn.ended", { turn_id, reason: "interrupt" }));
1584
- break;
1585
- case "SubagentStart":
1586
- events.push(
1587
- ev("subagent.started", {
1588
- agent_id: String(s.agent_id ?? s.subagent_id ?? ""),
1589
- agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
1590
- })
1591
- );
1592
- break;
1593
- case "SubagentStop":
1594
- events.push(
1595
- ev("subagent.ended", {
1596
- agent_id: String(s.agent_id ?? s.subagent_id ?? ""),
1597
- agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
1598
- })
1599
- );
1600
- break;
1601
- case "PreCompact":
1602
- events.push(ev("context.compacted", { trigger: s.trigger === "manual" ? "manual" : "auto" }));
1603
- break;
1604
- case "PostCompact":
1605
- break;
1606
- case "SessionEnd":
1607
- events.push(ev("session.ended", { reason: endReason2(s.reason) }));
1608
- break;
1609
- default:
1610
- return null;
1611
- }
1612
- const r = { session, events, hints: {} };
1613
- if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
1614
- return r;
1615
- },
1616
- readTranscript(file, fromLine) {
1617
- let text = "";
1618
- try {
1619
- text = readFileSync4(file, "utf8");
1620
- } catch {
1621
- return { turns: [], line: fromLine };
1622
- }
1623
- return readCodex(text.split("\n"), fromLine);
1624
- },
1625
- facts(session, transcript) {
1626
- const out2 = {};
1627
- if (transcript) Object.assign(out2, codexFacts(transcript));
1628
- return out2;
1629
- },
1630
- backfill(sinceMs, env = process.env) {
1631
- const rows = [];
1632
- for (const f of recentFiles(sessionsDir(env), 3, sinceMs, (n) => /^rollout-.*\.jsonl$/.test(n))) {
1633
- const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(path6.basename(f));
1634
- if (!m?.[1]) continue;
1635
- const facts = codexFacts(f);
1636
- if (!facts.started_at || !facts.last_at) continue;
1637
- const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
1638
- rows.push({ session: { id: m[1], ...rest }, started_at, last_at });
1639
- }
1640
- return rows;
1641
1744
  }
1642
- };
1643
- function prune3(o) {
1644
- for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
1645
- return o;
1745
+ return first;
1646
1746
  }
1647
- function codexFacts(file) {
1648
- const { head, tail: tail2 } = ends(file, 128 * 1024, 64 * 1024);
1649
- const H = parseLines(head);
1650
- const T = parseLines(tail2);
1651
- const out2 = {};
1652
- const meta = H.find((j) => j.type === "session_meta" && j.payload);
1653
- if (meta?.payload) {
1654
- const p = meta.payload;
1655
- if (p.cwd) out2.cwd = p.cwd;
1656
- if (p.cli_version) out2.harness_version = String(p.cli_version);
1657
- if (p.git?.branch) out2.branch = p.git.branch;
1658
- 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";
1659
1754
  }
1660
- const ctx = H.concat(T).reverse().find((j) => j.type === "turn_context" && j.payload?.model);
1661
- if (ctx?.payload?.model) out2.model = ctx.payload.model;
1662
- for (const j of H) {
1663
- const p = j.payload;
1664
- if (j.type !== "response_item" || !p || p.type !== "message" || p.role !== "user") continue;
1665
- const text = (p.content ?? []).filter((b) => b && b.type === "input_text" && typeof b.text === "string").map((b) => b.text).join("\n").trim();
1666
- if (!text || /^<[a-z_-]+>/i.test(text)) continue;
1667
- const line = text.split("\n").find((l) => l.trim()) ?? "";
1668
- out2.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
1669
- out2.title_source = "first-ask";
1670
- 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;
1671
1762
  }
1672
- const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
1673
- if (times.length) {
1674
- out2.started_at = Math.min(...times);
1675
- 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
+ }
1676
1770
  }
1677
- return out2;
1678
- }
1679
-
1680
- // ../core/src/adapters/gemini-cli.ts
1681
- import { execFileSync as execFileSync3 } from "node:child_process";
1682
- import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
1683
- import path7 from "node:path";
1684
- var NAME4 = "gemini-cli";
1685
- var GEMINI_EVENTS = [
1686
- "SessionStart",
1687
- "BeforeAgent",
1688
- "BeforeTool",
1689
- "AfterTool",
1690
- "Notification",
1691
- "AfterAgent",
1692
- "PreCompress",
1693
- "SessionEnd"
1694
- ];
1695
- var OPTS3 = { matcherFor: (e) => /Tool/.test(e) ? ".*" : void 0, timeout: 5e3 };
1696
- var geminiDir = (env) => env.GEMINI_CLI_HOME || path7.join(HOME, ".gemini");
1697
- var settings = (env) => path7.join(geminiDir(env), "settings.json");
1698
- var validTool4 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
1699
- var eventsFor3 = (lean) => lean ? GEMINI_EVENTS.filter((e) => e !== "BeforeTool") : [...GEMINI_EVENTS];
1700
- function geminiHooksActive(settingsFile) {
1701
- let tools;
1771
+ return false;
1772
+ }
1773
+ function recentlyWritten(file, ourLastEnd, now = Date.now()) {
1774
+ let m;
1702
1775
  try {
1703
- tools = JSON.parse(readFileSync5(settingsFile, "utf8")).tools;
1776
+ m = statSync5(file).mtimeMs;
1704
1777
  } catch {
1778
+ return false;
1705
1779
  }
1706
- if (tools?.enableHooks === true) return { active: true };
1707
- if (tools?.enableHooks === false)
1708
- return { active: false, note: "inactive: tools.enableHooks is false in settings.json" };
1709
- 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
+ };
1710
1796
  try {
1711
- version = execFileSync3("gemini", ["--version"], {
1712
- encoding: "utf8",
1713
- timeout: 4e3,
1714
- stdio: ["ignore", "pipe", "ignore"]
1715
- }).trim();
1797
+ return pick(JSON.parse(s));
1716
1798
  } catch {
1717
- return {
1718
- active: true,
1719
- note: "could not run `gemini --version`; hooks need tools.enableHooks on versions before 0.46"
1720
- };
1721
1799
  }
1722
- const m = /(\d+)\.(\d+)/.exec(version);
1723
- if (m && (Number(m[1]) > 0 || Number(m[2]) >= 46)) return { active: true };
1724
- return {
1725
- active: false,
1726
- note: `inactive: Gemini CLI ${version || "?"} runs hooks only with "tools": {"enableHooks": true} in settings.json`
1727
- };
1728
- }
1729
- var geminiCli = {
1730
- name: NAME4,
1731
- events: GEMINI_EVENTS,
1732
- // ~/.gemini also belongs to Antigravity; Gemini CLI is here when its settings or its chat logs exist.
1733
- detect: (env = process.env) => existsSync5(settings(env)) || existsSync5(path7.join(geminiDir(env), "tmp")),
1734
- configFiles: (env = process.env) => [settings(env)],
1735
- install: (cmd2, env = process.env, opts = {}) => {
1736
- const r = installClaudeShaped(settings(env), NAME4, eventsFor3(opts.lean), cmd2, OPTS3);
1737
- const a = geminiHooksActive(settings(env));
1738
- return [a.note && !r.skipped ? { ...r, note: [r.note, a.note].filter(Boolean).join("; ") } : r];
1739
- },
1740
- uninstall: (env = process.env, opts = {}) => [
1741
- uninstallClaudeShaped(settings(env), GEMINI_EVENTS, { created: opts.created?.includes(settings(env)) })
1742
- ],
1743
- installed: (cmd2, env = process.env, opts = {}) => {
1744
- const state2 = installedClaudeShaped(settings(env), NAME4, eventsFor3(opts.lean), cmd2, OPTS3);
1745
- const a = geminiHooksActive(settings(env));
1746
- return [
1747
- {
1748
- file: settings(env),
1749
- state: state2 === "current" && !a.active ? "inactive" : state2,
1750
- ...a.note ? { note: a.note } : {}
1751
- }
1752
- ];
1753
- },
1754
- fromHook(input) {
1755
- const event = input.argv[1] ?? "";
1756
- let s = {};
1800
+ const lines = s.split("\n");
1801
+ for (let i = lines.length - 1; i >= 0; i--) {
1757
1802
  try {
1758
- 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;
1759
1805
  } catch {
1760
1806
  }
1761
- const id = s.session_id;
1762
- if (!id) return null;
1763
- const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
1764
- const tool = validTool4(s.tool_name) ? s.tool_name : void 0;
1765
- const ev = (type, data) => ({ type, data: prune4(data), harnessEvent: event });
1766
- const events = [];
1767
- switch (event) {
1768
- case "SessionStart":
1769
- events.push(
1770
- ev("session.started", {
1771
- source: typeof s.source === "string" && ["startup", "resume", "clear"].includes(s.source) ? s.source : "unknown"
1772
- })
1773
- );
1774
- break;
1775
- case "BeforeAgent":
1776
- events.push(
1777
- ev("turn.started", { prompt_chars: typeof s.prompt === "string" ? [...s.prompt].length : void 0 })
1778
- );
1779
- break;
1780
- case "BeforeTool":
1781
- if (tool) events.push(ev("tool.started", { tool, input: s.tool_input }));
1782
- break;
1783
- case "AfterTool": {
1784
- const resp = s.tool_response;
1785
- const err = resp && typeof resp === "object" && resp.error ? String(typeof resp.error === "string" ? resp.error : JSON.stringify(resp.error)).slice(0, 2e3) : void 0;
1786
- if (tool)
1787
- events.push(
1788
- ev("tool.ended", {
1789
- tool,
1790
- ok: !err,
1791
- error: err,
1792
- input: s.tool_input,
1793
- output: resp?.llmContent ?? s.tool_response
1794
- })
1795
- );
1796
- break;
1797
- }
1798
- case "Notification":
1799
- events.push(
1800
- ev("attention.needed", {
1801
- attention_id: `notif-${Date.now()}`,
1802
- kind: s.notification_type === "ToolPermission" ? "permission" : "question",
1803
- message: typeof s.message === "string" ? s.message.slice(0, 200) : void 0
1804
- })
1805
- );
1806
- break;
1807
- case "AfterAgent":
1808
- events.push(ev("turn.ended", { reason: "stop" }));
1809
- break;
1810
- case "PreCompress":
1811
- events.push(ev("context.compacted", { trigger: s.trigger === "manual" ? "manual" : "auto" }));
1812
- break;
1813
- case "SessionEnd":
1814
- events.push(
1815
- ev("session.ended", {
1816
- reason: s.reason === "clear" || s.reason === "logout" || s.reason === "exit" ? s.reason : s.reason === "prompt_input_exit" ? "exit" : "other"
1817
- })
1818
- );
1819
- break;
1820
- default:
1821
- 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;
1822
1829
  }
1823
- const r = { session, events, hints: {} };
1824
- if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
1825
- return r;
1826
- },
1827
- readTranscript(file, fromLine) {
1828
- let text = "";
1829
- try {
1830
- text = readFileSync5(file, "utf8");
1831
- } catch {
1832
- 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;
1833
1836
  }
1834
- return readGemini(text.split("\n"), fromLine);
1835
- },
1836
- facts(_session, transcript) {
1837
- const out2 = {};
1838
- if (!transcript) return out2;
1839
- const { head, tail: tail2 } = ends(transcript, 64 * 1024, 64 * 1024);
1840
- const H = parseLines(head);
1841
- const T = parseLines(tail2);
1842
- const first = H.find((j) => j.type === "user");
1843
- const raw = first ? first.content ?? first.message ?? first.text : void 0;
1844
- const text = typeof raw === "string" ? raw.trim() : "";
1845
- if (text) {
1846
- const line = text.split("\n").find((l) => l.trim()) ?? "";
1847
- out2.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
1848
- 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;
1849
1842
  }
1850
- const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
1851
- if (times.length) {
1852
- out2.started_at = Math.min(...times);
1853
- 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;
1854
1847
  }
1855
- return out2;
1856
- },
1857
- backfill: () => []
1858
- // chat logs are keyed by a project hash, not a session id we can name; the first hook of a resumed session reports it
1859
- };
1860
- function prune4(o) {
1861
- for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
1862
- return o;
1863
- }
1864
-
1865
- // ../core/src/adapters/claude-code-control.ts
1866
- var claude_code_control_exports = {};
1867
- __export(claude_code_control_exports, {
1868
- JOB_TIMEOUT_MS: () => JOB_TIMEOUT_MS,
1869
- claudeEnv: () => claudeEnv,
1870
- claudeLogin: () => claudeLogin,
1871
- detectCaps: () => detectCaps,
1872
- findClaude: () => findClaude,
1873
- findTranscript: () => findTranscript,
1874
- isUuid: () => isUuid,
1875
- liveProcess: () => liveProcess,
1876
- loginCommand: () => loginCommand,
1877
- loginHint: () => loginHint,
1878
- modeFlags: () => modeFlags,
1879
- parseAuthStatus: () => parseAuthStatus,
1880
- parseCaps: () => parseCaps,
1881
- parseResult: () => parseResult,
1882
- recentlyWritten: () => recentlyWritten,
1883
- runClaude: () => runClaude,
1884
- sessionFolder: () => sessionFolder
1885
- });
1886
- import { execFile, execFileSync as execFileSync4, spawn } from "node:child_process";
1887
- import { accessSync, closeSync as closeSync2, constants, openSync as openSync2, readdirSync as readdirSync5, readFileSync as readFileSync6, readSync as readSync2, statSync as statSync4 } from "node:fs";
1888
- import os2 from "node:os";
1889
- import path8 from "node:path";
1890
- var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1891
- var JOB_TIMEOUT_MS = 30 * 6e4;
1892
- var OUT_CAP = 1024 * 1024;
1893
- var LIVE_WRITE_MS = 9e4;
1894
- function isExe(p) {
1895
- try {
1896
- if (!statSync4(p).isFile()) return false;
1897
- accessSync(p, constants.X_OK);
1898
- return true;
1899
- } catch {
1900
- return false;
1848
+ if (ch === "[") st.depth++;
1849
+ else if (ch === "]") st.depth = Math.max(0, st.depth - 1);
1850
+ i++;
1901
1851
  }
1902
1852
  }
1903
- var cmpVersion = (a, b) => {
1904
- const pa = a.split(".").map(Number);
1905
- const pb = b.split(".").map(Number);
1906
- for (let i = 0; i < Math.max(pa.length, pb.length); i++)
1907
- if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) - (pb[i] ?? 0);
1908
- return 0;
1909
- };
1910
- function findClaude(env = process.env, home = os2.homedir()) {
1911
- if (env.SESSIONPIPE_CLAUDE && isExe(env.SESSIONPIPE_CLAUDE)) return env.SESSIONPIPE_CLAUDE;
1912
- const names = process.platform === "win32" ? ["claude.exe", "claude.cmd"] : ["claude"];
1913
- for (const d of String(env.PATH || "").split(path8.delimiter)) {
1914
- if (!d || !path8.isAbsolute(d)) continue;
1915
- 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;
1916
1860
  }
1917
- for (const p of [
1918
- path8.join(home, ".claude", "local", "claude"),
1919
- path8.join(home, ".local", "bin", "claude"),
1920
- "/opt/homebrew/bin/claude",
1921
- "/usr/local/bin/claude"
1922
- ])
1923
- if (isExe(p)) return p;
1924
- const app = path8.join(home, "Library", "Application Support", "Claude", "claude-code");
1925
- 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) {
1926
1868
  try {
1927
- 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;
1928
1871
  } catch {
1872
+ return null;
1929
1873
  }
1930
- for (const v of versions) {
1931
- const p = path8.join(app, v, "claude.app", "Contents", "MacOS", "claude");
1932
- 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
+ };
1933
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
+ };
1934
1909
  return null;
1935
1910
  }
1936
- function optionBlock(help2, flag2) {
1937
- const lines = help2.split("\n");
1938
- const re = new RegExp(`^\\s{0,4}(?:-\\w, )?${flag2.replace(/-/g, "\\-")}(?![\\w-])`);
1939
- const start = lines.findIndex((l) => re.test(l));
1940
- if (start < 0) return null;
1941
- const out2 = [lines[start]];
1942
- for (let i = start + 1; i < lines.length && lines[i]?.trim() && !/^\s{0,4}-/.test(lines[i]); i++)
1943
- out2.push(lines[i]);
1944
- return out2.join(" ");
1911
+ function dropLine(text, index, length) {
1912
+ return text.slice(0, index) + text.slice(index + length).replace(/^\r?\n/, "");
1945
1913
  }
1946
- function parseCaps(help2) {
1947
- const pm = optionBlock(help2, "--permission-mode");
1948
- const pp = optionBlock(help2, "--permission-prompts");
1949
- return {
1950
- hasMode: !!pm,
1951
- modes: pm ? [...pm.matchAll(/"([A-Za-z]+)"/g)].map((m) => m[1]) : [],
1952
- promptsNone: !!pp && /"none"/.test(pp),
1953
- fork: !!optionBlock(help2, "--fork-session"),
1954
- stream: !!optionBlock(help2, "--include-partial-messages")
1955
- };
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}`;
1956
1920
  }
1957
- function modeFlags(mode, caps) {
1958
- if (mode === "auto") {
1959
- if (!caps.modes.includes("auto"))
1960
- return { error: "this machine is in auto mode, but its Claude Code has no auto permission mode" };
1961
- return { args: ["--permission-mode", "auto"] };
1962
- }
1963
- if (caps.modes.includes("dontAsk")) return { args: ["--permission-mode", "dontAsk"] };
1964
- const args2 = [];
1965
- if (caps.hasMode)
1966
- args2.push(
1967
- "--permission-mode",
1968
- caps.modes.includes("manual") && !caps.modes.includes("default") ? "manual" : "default"
1969
- );
1970
- if (caps.promptsNone) args2.push("--permission-prompts", "none");
1971
- 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);
1972
1926
  }
1973
- function claudeEnv(configDir, env = process.env) {
1974
- const e = { ...env };
1975
- for (const k of [
1976
- "CLAUDECODE",
1977
- "CLAUDE_CODE_SESSION_ID",
1978
- "CLAUDE_CODE_HOST_SESSION_ID",
1979
- "CLAUDE_CODE_ENTRYPOINT",
1980
- "CLAUDE_CODE_SESSION_ATTENDED",
1981
- "CLAUDE_CODE_SSE_PORT"
1982
- ])
1983
- delete e[k];
1984
- e.SESSIONPIPE_CONTROL_JOB = "1";
1985
- if (configDir !== void 0) {
1986
- if (path8.resolve(configDir) === path8.resolve(os2.homedir(), ".claude")) delete e.CLAUDE_CONFIG_DIR;
1987
- else e.CLAUDE_CONFIG_DIR = configDir;
1988
- }
1989
- 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);
1990
1931
  }
1991
- function parseAuthStatus(stdout) {
1992
- const text = String(stdout || "").trim();
1993
- const start = text.indexOf("{");
1994
- 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 = "";
1995
1979
  try {
1996
- const j = JSON.parse(text.slice(start));
1997
- 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));
1998
1994
  return {
1999
- loggedIn: j.loggedIn,
2000
- ...typeof j.authMethod === "string" ? { method: j.authMethod } : {},
2001
- ...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"
2002
1998
  };
2003
- } catch {
2004
- return { loggedIn: null };
2005
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" };
2006
2024
  }
2007
- function claudeLogin(bin, configDir, env = process.env) {
2008
- return new Promise((resolve) => {
2009
- execFile(
2010
- bin,
2011
- ["auth", "status", "--json"],
2012
- { encoding: "utf8", timeout: 2e4, env: claudeEnv(configDir, env), maxBuffer: 256 * 1024 },
2013
- // `auth status` may exit non-zero when signed out; its JSON still says so.
2014
- (_err, stdout) => resolve(parseAuthStatus(stdout))
2015
- );
2016
- });
2017
- }
2018
- function loginCommand(configDir) {
2019
- const home = os2.homedir();
2020
- const isDefault = !configDir || path8.resolve(configDir) === path8.resolve(home, ".claude");
2021
- return isDefault ? "`claude auth login`" : `\`CLAUDE_CONFIG_DIR=${configDir.replace(home, "~")} claude auth login\``;
2022
- }
2023
- function loginHint(configDir, platform = process.platform) {
2024
- const shown = configDir ? configDir.replace(os2.homedir(), "~") : "~/.claude";
2025
- const login = loginCommand(configDir);
2026
- if (platform === "darwin")
2027
- 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.`;
2028
- return `Claude Code (${shown}) isn't signed in on this machine: run ${login} there.`;
2029
- }
2030
- function detectCaps(bin) {
2031
- let help2 = "";
2025
+ function uninstallNotify(env) {
2026
+ const file = configFile2(env);
2027
+ let text;
2032
2028
  try {
2033
- help2 = execFileSync4(bin, ["--help"], {
2034
- encoding: "utf8",
2035
- timeout: 3e4,
2036
- env: claudeEnv(),
2037
- stdio: ["ignore", "pipe", "pipe"],
2038
- maxBuffer: 4 * 1024 * 1024
2039
- });
2040
- } catch (e) {
2041
- help2 = String(e?.stdout || "");
2029
+ text = readFileSync5(file, "utf8");
2030
+ } catch {
2031
+ return { file, changed: false };
2042
2032
  }
2043
- 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 };
2044
2046
  }
2045
- function findTranscript(session, dirs) {
2046
- if (!UUID_RE.test(session)) return null;
2047
- for (const dir of dirs) {
2048
- 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 = [];
2049
2052
  try {
2050
- projects = readdirSync5(path8.join(dir, "projects"), { withFileTypes: true });
2053
+ names = readdirSync5(dir);
2051
2054
  } catch {
2052
- continue;
2055
+ return;
2053
2056
  }
2054
- for (const p of projects) {
2055
- if (!p.isDirectory()) continue;
2056
- const file = path8.join(dir, "projects", p.name, `${session}.jsonl`);
2057
- try {
2058
- if (statSync4(file).isFile()) return { file, configDir: dir, project: p.name };
2059
- } 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;
2060
2066
  }
2061
2067
  }
2062
- }
2063
- return null;
2068
+ };
2069
+ walk(sessionsDir(env), 0);
2070
+ return best;
2064
2071
  }
2065
- var projectName = (cwd) => cwd.replace(/[^a-zA-Z0-9]/g, "-");
2066
- function sessionFolder(t, cap2 = 8 * 1024 * 1024) {
2067
- let first = null;
2068
- let count = 0;
2069
- let fd;
2070
- const look = (line) => {
2071
- if (line.indexOf('"cwd"') < 0) return null;
2072
- 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 = "";
2073
2094
  try {
2074
- j = JSON.parse(line.toString("utf8"));
2095
+ text = readFileSync5(configFile2(env), "utf8");
2075
2096
  } catch {
2076
- return null;
2077
- }
2078
- if (!j || typeof j.cwd !== "string" || !path8.isAbsolute(j.cwd)) return null;
2079
- if (projectName(j.cwd) === t.project) return j.cwd;
2080
- first ??= j.cwd;
2081
- count++;
2082
- return null;
2083
- };
2084
- try {
2085
- fd = openSync2(t.file, "r");
2086
- const buf = Buffer.alloc(256 * 1024);
2087
- let pos = 0;
2088
- let rest = Buffer.alloc(0);
2089
- let skipping = false;
2090
- while (pos < cap2 && count < 50) {
2091
- const n = readSync2(fd, buf, 0, buf.length, pos);
2092
- if (!n) {
2093
- if (!skipping && rest.length) {
2094
- const hit = look(rest);
2095
- if (hit) return hit;
2096
- }
2097
- break;
2098
- }
2099
- pos += n;
2100
- let chunk = rest.length ? Buffer.concat([rest, buf.subarray(0, n)]) : buf.subarray(0, n);
2101
- let nl = chunk.indexOf(10);
2102
- while (nl >= 0) {
2103
- const line = chunk.subarray(0, nl);
2104
- chunk = chunk.subarray(nl + 1);
2105
- if (skipping) skipping = false;
2106
- else {
2107
- const hit = look(line);
2108
- if (hit) return hit;
2109
- }
2110
- nl = chunk.indexOf(10);
2111
- }
2112
- if (chunk.length > 2 * 1024 * 1024) {
2113
- skipping = true;
2114
- rest = Buffer.alloc(0);
2115
- } else rest = skipping ? Buffer.alloc(0) : Buffer.from(chunk);
2116
2097
  }
2117
- } catch {
2118
- } finally {
2119
- 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;
2120
2113
  try {
2121
- closeSync2(fd);
2114
+ s = raw ? JSON.parse(raw) : {};
2122
2115
  } catch {
2123
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;
2124
2239
  }
2125
- 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;
2126
2244
  }
2127
- function pidAlive(pid) {
2128
- if (!Number.isSafeInteger(pid) || pid <= 1) return false;
2129
- try {
2130
- process.kill(pid, 0);
2131
- return true;
2132
- } catch (e) {
2133
- 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);
2134
2274
  }
2275
+ return out2;
2135
2276
  }
2136
- function liveProcess(session, configDir) {
2137
- 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;
2138
2341
  try {
2139
- names = readdirSync5(path8.join(configDir, "sessions")).filter((n) => /^\d+\.json$/.test(n));
2342
+ j = JSON.parse(line);
2140
2343
  } catch {
2141
- return false;
2344
+ return null;
2142
2345
  }
2143
- for (const n of names) {
2144
- try {
2145
- const j = JSON.parse(readFileSync6(path8.join(configDir, "sessions", n), "utf8"));
2146
- if (String(j.sessionId) !== session) continue;
2147
- if (pidAlive(Number(j.pid ?? n.slice(0, -5)))) return true;
2148
- } 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;
2149
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;
2150
2365
  }
2151
- return false;
2152
2366
  }
2153
- function recentlyWritten(file, ourLastEnd, now = Date.now()) {
2154
- 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;
2155
2434
  try {
2156
- m = statSync4(file).mtimeMs;
2435
+ tools = JSON.parse(readFileSync6(settingsFile, "utf8")).tools;
2157
2436
  } catch {
2158
- return false;
2159
2437
  }
2160
- if (now - m > LIVE_WRITE_MS) return false;
2161
- 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
+ };
2162
2460
  }
2163
- function runClaude(bin, args2, o) {
2164
- return new Promise((resolve) => {
2165
- let out2 = [];
2166
- let outLen = 0;
2167
- let tooBig = false;
2168
- let errTail = "";
2169
- let timedOut = false;
2170
- let settled = false;
2171
- 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 = {};
2172
2489
  try {
2173
- child = spawn(bin, args2, {
2174
- cwd: o.cwd,
2175
- env: o.env,
2176
- stdio: ["ignore", "pipe", "pipe"],
2177
- detached: process.platform !== "win32",
2178
- windowsHide: true
2179
- });
2180
- } catch (e) {
2181
- resolve({ error: e.message });
2182
- return;
2490
+ s = input.stdin ? JSON.parse(input.stdin) : {};
2491
+ } catch {
2183
2492
  }
2184
- const kill = (sig) => {
2185
- try {
2186
- if (process.platform !== "win32" && child.pid) process.kill(-child.pid, sig);
2187
- else child.kill(sig);
2188
- } catch {
2189
- try {
2190
- child.kill(sig);
2191
- } catch {
2192
- }
2193
- }
2194
- };
2195
- let partial = "";
2196
- child.stdout?.on("data", (b) => {
2197
- if (o.onLine) {
2198
- const chunk = partial + b.toString("utf8");
2199
- const lines = chunk.split("\n");
2200
- partial = lines.pop() ?? "";
2201
- for (const line of lines) {
2202
- if (!line.trim()) continue;
2203
- try {
2204
- o.onLine(line);
2205
- } catch {
2206
- }
2207
- if (line.includes('"type":"result"')) {
2208
- out2 = [Buffer.from(line)];
2209
- outLen = line.length;
2210
- }
2211
- }
2212
- if (partial.length > OUT_CAP) partial = "";
2213
- return;
2214
- }
2215
- if (tooBig) return;
2216
- if (outLen + b.length > OUT_CAP) {
2217
- tooBig = true;
2218
- out2 = [];
2219
- return;
2220
- }
2221
- out2.push(b);
2222
- outLen += b.length;
2223
- });
2224
- child.stderr?.on("data", (b) => {
2225
- errTail = (errTail + b.toString("utf8")).slice(-4e3);
2226
- });
2227
- const timer = setTimeout(() => {
2228
- timedOut = true;
2229
- kill("SIGTERM");
2230
- setTimeout(() => kill("SIGKILL"), 1e4).unref();
2231
- }, o.timeoutMs ?? JOB_TIMEOUT_MS);
2232
- const result = (code, signal) => ({
2233
- code,
2234
- signal,
2235
- stdout: tooBig ? null : Buffer.concat(out2).toString("utf8"),
2236
- tooBig,
2237
- stderr: errTail,
2238
- timedOut
2239
- });
2240
- const finish = (r) => {
2241
- if (settled) return;
2242
- settled = true;
2243
- clearTimeout(timer);
2244
- resolve(r);
2245
- };
2246
- child.on("error", (e) => finish({ error: e.message }));
2247
- child.on("exit", (code, signal) => setTimeout(() => finish(result(code, signal)), 2e3).unref());
2248
- child.on("close", (code, signal) => {
2249
- if (o.onLine && partial.trim()) {
2250
- try {
2251
- o.onLine(partial);
2252
- } catch {
2253
- }
2254
- if (partial.includes('"type":"result"')) {
2255
- out2 = [Buffer.from(partial)];
2256
- outLen = partial.length;
2257
- }
2258
- 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;
2259
2529
  }
2260
- finish(result(code, signal));
2261
- });
2262
- });
2263
- }
2264
- function parseResult(stdout) {
2265
- const s = String(stdout || "").trim();
2266
- if (!s) return null;
2267
- const pick = (j) => {
2268
- if (Array.isArray(j)) {
2269
- for (let i = j.length - 1; i >= 0; i--) if (j[i]?.type === "result") return j[i];
2270
- 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;
2271
2554
  }
2272
- return j && typeof j === "object" ? j : null;
2273
- };
2274
- try {
2275
- return pick(JSON.parse(s));
2276
- } catch {
2277
- }
2278
- const lines = s.split("\n");
2279
- 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 = "";
2280
2561
  try {
2281
- const j = pick(JSON.parse(lines[i]));
2282
- if (j && (j.type === "result" || "result" in j)) return j;
2562
+ text = readFileSync6(file, "utf8");
2283
2563
  } catch {
2564
+ return { turns: [], line: fromLine };
2284
2565
  }
2285
- }
2286
- 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;
2287
2595
  }
2288
- var isUuid = (s) => typeof s === "string" && UUID_RE.test(s);
2289
2596
 
2290
2597
  // ../core/src/adapters/index.ts
2291
2598
  var ADAPTERS = [claudeCode, codex, geminiCli, antigravity];
2292
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
+ }
2293
2607
 
2294
2608
  // ../core/src/config.ts
2295
2609
  import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
2296
- import path9 from "node:path";
2610
+ import path12 from "node:path";
2297
2611
  var DEFAULT_CONFIG = { sinks: [], harnesses: {}, update_check: true, keep_days: 30 };
2298
2612
  function readConfig(file = configFile()) {
2299
2613
  try {
@@ -2309,7 +2623,7 @@ function readConfig(file = configFile()) {
2309
2623
  }
2310
2624
  }
2311
2625
  function writeConfig(c, file = configFile()) {
2312
- mkdirSync4(path9.dirname(file), { recursive: true, mode: 448 });
2626
+ mkdirSync4(path12.dirname(file), { recursive: true, mode: 448 });
2313
2627
  writeFileSync4(file, `${JSON.stringify(c, null, 2)}
2314
2628
  `, { mode: 384 });
2315
2629
  if (process.platform !== "win32" && existsSync6(file)) {
@@ -2385,24 +2699,24 @@ import {
2385
2699
  readFileSync as readFileSync8,
2386
2700
  readSync as readSync3,
2387
2701
  renameSync,
2388
- statSync as statSync5,
2702
+ statSync as statSync6,
2389
2703
  unlinkSync as unlinkSync3,
2390
2704
  writeFileSync as writeFileSync5
2391
2705
  } from "node:fs";
2392
- import path10 from "node:path";
2706
+ import path13 from "node:path";
2393
2707
  var Outbox = class {
2394
2708
  constructor(root) {
2395
2709
  this.root = root;
2396
2710
  }
2397
2711
  root;
2398
2712
  dir(harness) {
2399
- return path10.join(this.root, "outbox", safeId(harness));
2713
+ return path13.join(this.root, "outbox", safeId(harness));
2400
2714
  }
2401
2715
  file(ref) {
2402
- return path10.join(this.dir(ref.harness), `${safeId(ref.session)}.jsonl`);
2716
+ return path13.join(this.dir(ref.harness), `${safeId(ref.session)}.jsonl`);
2403
2717
  }
2404
2718
  seqFile(ref) {
2405
- return path10.join(this.dir(ref.harness), `${safeId(ref.session)}.seq`);
2719
+ return path13.join(this.dir(ref.harness), `${safeId(ref.session)}.seq`);
2406
2720
  }
2407
2721
  /** The next seq for a session, persisted (survives a hibernating machine and a
2408
2722
  * restart; an in-memory counter would restart at 0 and collide). */
@@ -2414,7 +2728,7 @@ var Outbox = class {
2414
2728
  } catch {
2415
2729
  n = this.lastSeqInFile(ref) + 1;
2416
2730
  }
2417
- mkdirSync5(path10.dirname(f), { recursive: true, mode: 448 });
2731
+ mkdirSync5(path13.dirname(f), { recursive: true, mode: 448 });
2418
2732
  writeFileSync5(f, String(n + 1), { mode: 384 });
2419
2733
  return n;
2420
2734
  }
@@ -2440,10 +2754,10 @@ var Outbox = class {
2440
2754
  /** Every session file, newest first. */
2441
2755
  sessions() {
2442
2756
  const out2 = [];
2443
- const base = path10.join(this.root, "outbox");
2757
+ const base = path13.join(this.root, "outbox");
2444
2758
  if (!existsSync7(base)) return [];
2445
2759
  for (const h of readdirSync6(base)) {
2446
- const d = path10.join(base, h);
2760
+ const d = path13.join(base, h);
2447
2761
  let names = [];
2448
2762
  try {
2449
2763
  names = readdirSync6(d).filter((n) => n.endsWith(".jsonl"));
@@ -2452,7 +2766,7 @@ var Outbox = class {
2452
2766
  }
2453
2767
  for (const n of names) {
2454
2768
  try {
2455
- 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 });
2456
2770
  } catch {
2457
2771
  }
2458
2772
  }
@@ -2464,7 +2778,7 @@ var Outbox = class {
2464
2778
  read(ref, from, maxEvents = 500) {
2465
2779
  const f = this.file(ref);
2466
2780
  if (!existsSync7(f)) return { events: [], offset: from };
2467
- const size = statSync5(f).size;
2781
+ const size = statSync6(f).size;
2468
2782
  if (from >= size) return { events: [], offset: from };
2469
2783
  const fd = openSync3(f, "r");
2470
2784
  try {
@@ -2498,7 +2812,7 @@ var Outbox = class {
2498
2812
  for (const ref of this.sessions()) {
2499
2813
  const f = this.file(ref);
2500
2814
  try {
2501
- if (statSync5(f).mtimeMs < cutoff) {
2815
+ if (statSync6(f).mtimeMs < cutoff) {
2502
2816
  unlinkSync3(f);
2503
2817
  try {
2504
2818
  unlinkSync3(this.seqFile(ref));
@@ -2528,7 +2842,7 @@ var Cursors = class {
2528
2842
  root;
2529
2843
  sink;
2530
2844
  file() {
2531
- return path10.join(this.root, "cursors", `${safeId(this.sink)}.json`);
2845
+ return path13.join(this.root, "cursors", `${safeId(this.sink)}.json`);
2532
2846
  }
2533
2847
  read() {
2534
2848
  try {
@@ -2539,7 +2853,7 @@ var Cursors = class {
2539
2853
  }
2540
2854
  write(c) {
2541
2855
  const f = this.file();
2542
- mkdirSync5(path10.dirname(f), { recursive: true, mode: 448 });
2856
+ mkdirSync5(path13.dirname(f), { recursive: true, mode: 448 });
2543
2857
  const tmp = `${f}.${process.pid}.tmp`;
2544
2858
  writeFileSync5(tmp, JSON.stringify(c), { mode: 384 });
2545
2859
  renameSync(tmp, f);
@@ -2549,11 +2863,11 @@ var Cursors = class {
2549
2863
  }
2550
2864
  };
2551
2865
  function takeLock(root, ref, staleMs = 5 * 6e4) {
2552
- const dir = path10.join(root, "locks");
2866
+ const dir = path13.join(root, "locks");
2553
2867
  mkdirSync5(dir, { recursive: true, mode: 448 });
2554
- 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`);
2555
2869
  try {
2556
- if (Date.now() - statSync5(f).mtimeMs > staleMs) unlinkSync3(f);
2870
+ if (Date.now() - statSync6(f).mtimeMs > staleMs) unlinkSync3(f);
2557
2871
  else return null;
2558
2872
  } catch {
2559
2873
  }
@@ -2867,7 +3181,7 @@ function filterEvent(event, tier, pii, opts = {}) {
2867
3181
 
2868
3182
  // ../core/src/sinks/file.ts
2869
3183
  import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync6 } from "node:fs";
2870
- import path11 from "node:path";
3184
+ import path14 from "node:path";
2871
3185
  var FileSink = class {
2872
3186
  constructor(name, file, tier, pii) {
2873
3187
  this.name = name;
@@ -2881,7 +3195,7 @@ var FileSink = class {
2881
3195
  pii;
2882
3196
  async send(events) {
2883
3197
  try {
2884
- mkdirSync6(path11.dirname(this.file), { recursive: true });
3198
+ mkdirSync6(path14.dirname(this.file), { recursive: true });
2885
3199
  appendFileSync2(this.file, `${events.map((e) => JSON.stringify(e)).join("\n")}
2886
3200
  `);
2887
3201
  return { ok: true, acknowledged: events.length, accepted: events.length, duplicates: 0, rejected: [] };
@@ -2911,23 +3225,23 @@ var StdoutSink = class {
2911
3225
  };
2912
3226
 
2913
3227
  // src/connect.ts
2914
- import path12 from "node:path";
3228
+ import path15 from "node:path";
2915
3229
  function isBroad(dir, home) {
2916
- const d = path12.resolve(dir);
2917
- const h = path12.resolve(home);
2918
- if (d === path12.parse(d).root) return true;
2919
- 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);
2920
3234
  }
2921
- 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);
2922
3236
  function connectFolders(o) {
2923
- 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" };
2924
3238
  if (o.existing.length) return { folders: [], from: "existing" };
2925
- 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" };
2926
3240
  const exists = o.exists ?? (() => true);
2927
3241
  const picked = [];
2928
3242
  for (const raw of o.recent) {
2929
- if (!raw || !path12.isAbsolute(raw)) continue;
2930
- const d = path12.resolve(raw);
3243
+ if (!raw || !path15.isAbsolute(raw)) continue;
3244
+ const d = path15.resolve(raw);
2931
3245
  if (isBroad(d, o.home) || picked.includes(d) || !exists(d)) continue;
2932
3246
  if (picked.some((p) => inside(d, p))) continue;
2933
3247
  for (let i = picked.length - 1; i >= 0; i--) if (inside(picked[i], d)) picked.splice(i, 1);
@@ -2944,43 +3258,79 @@ function connectTier(flag2, current) {
2944
3258
 
2945
3259
  // src/control/autoupdate.ts
2946
3260
  import { spawn as spawn3 } from "node:child_process";
2947
- import { readFileSync as readFileSync11 } from "node:fs";
2948
- import path18 from "node:path";
3261
+ import { readFileSync as readFileSync12 } from "node:fs";
3262
+ import path22 from "node:path";
2949
3263
 
2950
3264
  // src/update.ts
2951
3265
  import { spawn as spawn2 } from "node:child_process";
2952
3266
  import { accessSync as accessSync2, constants as constants2, existsSync as existsSync9, realpathSync as realpathSync3 } from "node:fs";
2953
- import path16 from "node:path";
3267
+ import path20 from "node:path";
2954
3268
 
2955
3269
  // src/runtime.ts
2956
3270
  import { execFileSync as execFileSync5 } from "node:child_process";
2957
3271
  import { realpathSync as realpathSync2 } from "node:fs";
2958
- import os5 from "node:os";
2959
- import path15 from "node:path";
3272
+ import os7 from "node:os";
3273
+ import path19 from "node:path";
2960
3274
 
2961
3275
  // src/run.ts
2962
3276
  import {
2963
3277
  appendFileSync as appendFileSync3,
2964
3278
  existsSync as existsSync8,
2965
- mkdirSync as mkdirSync7,
3279
+ mkdirSync as mkdirSync8,
2966
3280
  readdirSync as readdirSync7,
2967
- readFileSync as readFileSync9,
2968
- renameSync as renameSync2,
2969
- statSync as statSync6,
3281
+ readFileSync as readFileSync10,
3282
+ renameSync as renameSync3,
3283
+ statSync as statSync7,
2970
3284
  unlinkSync as unlinkSync4,
2971
- writeFileSync as writeFileSync6
3285
+ writeFileSync as writeFileSync7
2972
3286
  } from "node:fs";
2973
- import os4 from "node:os";
2974
- 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
+ }
2975
3325
 
2976
3326
  // src/control/local.ts
2977
3327
  import net from "node:net";
2978
- import os3 from "node:os";
2979
- import path13 from "node:path";
3328
+ import os5 from "node:os";
3329
+ import path17 from "node:path";
2980
3330
  function socketPath(state2, env = process.env) {
2981
3331
  if (env.SESSIONPIPE_SOCKET) return env.SESSIONPIPE_SOCKET;
2982
- if (process.platform === "win32") return `\\\\.\\pipe\\sessionpipe-control-${os3.userInfo().username}`;
2983
- 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");
2984
3334
  }
2985
3335
  function ask(sock, req, timeoutMs) {
2986
3336
  return new Promise((resolve) => {
@@ -3214,35 +3564,35 @@ function dropSinkSecrets(s, run = defaultRunner) {
3214
3564
  }
3215
3565
 
3216
3566
  // src/run.ts
3217
- async function tellDaemon(state2, job, session, transcript) {
3567
+ async function tellDaemon(state2, job, session, cwd, transcript) {
3218
3568
  const sock = socketPath(state2);
3219
3569
  if (process.platform !== "win32" && !existsSync8(sock)) return;
3220
3570
  await ask(
3221
3571
  sock,
3222
3572
  {
3223
3573
  op: "event",
3224
- session: `claude-code:${session}`,
3574
+ session: `${job.harness}:${session}`,
3225
3575
  event: job.event,
3226
- cwd: job.cwd,
3576
+ ...cwd ? { cwd } : {},
3227
3577
  ...transcript ? { transcript } : {}
3228
3578
  },
3229
3579
  300
3230
3580
  );
3231
3581
  }
3232
- var VERSION = true ? "0.6.0" : "0.0.0";
3582
+ var VERSION = true ? "0.7.0" : "0.0.0";
3233
3583
  function enqueueJob(state2, job) {
3234
3584
  const jobs = jobsDir(state2);
3235
- mkdirSync7(jobs, { recursive: true, mode: 448 });
3236
- const f = path14.join(jobs, `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.json`);
3237
- 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 });
3238
3588
  return f;
3239
3589
  }
3240
3590
  function log(state2, line) {
3241
3591
  try {
3242
- mkdirSync7(state2, { recursive: true, mode: 448 });
3243
- const f = path14.join(state2, "log");
3592
+ mkdirSync8(state2, { recursive: true, mode: 448 });
3593
+ const f = path18.join(state2, "log");
3244
3594
  try {
3245
- if (statSync6(f).size > 1024 * 1024) renameSync2(f, `${f}.1`);
3595
+ if (statSync7(f).size > 1024 * 1024) renameSync3(f, `${f}.1`);
3246
3596
  } catch {
3247
3597
  }
3248
3598
  appendFileSync3(f, `${(/* @__PURE__ */ new Date()).toISOString()} ${line}
@@ -3251,11 +3601,11 @@ function log(state2, line) {
3251
3601
  }
3252
3602
  }
3253
3603
  function factsState(state2) {
3254
- 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`);
3255
3605
  return {
3256
3606
  get(h, s) {
3257
3607
  try {
3258
- return JSON.parse(readFileSync9(file(h, s), "utf8"));
3608
+ return JSON.parse(readFileSync10(file(h, s), "utf8"));
3259
3609
  } catch {
3260
3610
  return {};
3261
3611
  }
@@ -3264,8 +3614,8 @@ function factsState(state2) {
3264
3614
  const cur = this.get(h, s);
3265
3615
  const next = { ...cur, ...patch };
3266
3616
  if (JSON.stringify(next) === JSON.stringify(cur)) return;
3267
- mkdirSync7(path14.dirname(file(h, s)), { recursive: true, mode: 448 });
3268
- 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 });
3269
3619
  }
3270
3620
  };
3271
3621
  }
@@ -3281,7 +3631,7 @@ function buildSinks(sinks, o = {}) {
3281
3631
  const tier = Math.min(s.tier, s.max_tier ?? 3);
3282
3632
  if (s.url === "stdout") out2.push(new StdoutSink(s.name, tier, !!s.pii));
3283
3633
  else if (s.url.startsWith("file:"))
3284
- 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));
3285
3635
  else if (/^https?:\/\//.test(s.url)) out2.push(new HttpSink(s, tier));
3286
3636
  }
3287
3637
  return out2;
@@ -3293,30 +3643,33 @@ async function runJob(job, opts = {}) {
3293
3643
  if (!adapter) return { events: [], delivered: {} };
3294
3644
  const r = adapter.fromHook({ argv: job.argv, stdin: job.stdin, env: { ...process.env, ...job.env }, cwd: job.cwd });
3295
3645
  if (!r || !r.session.id) return { events: [], delivered: {} };
3296
- 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);
3297
3648
  const outbox = new Outbox(state2);
3298
- const ref = { harness: job.harness, session: r.session.id };
3649
+ const ref = { harness: job.harness, session: id };
3299
3650
  let release = takeLock(state2, ref);
3300
3651
  for (let wait = 50; !release && wait <= 3200; wait *= 2) {
3301
3652
  await new Promise((res) => setTimeout(res, wait));
3302
3653
  release = takeLock(state2, ref);
3303
3654
  }
3304
3655
  if (!release) {
3305
- 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`);
3306
3657
  enqueueJob(state2, job);
3307
3658
  return { events: [], delivered: {} };
3308
3659
  }
3309
3660
  try {
3310
3661
  const facts = factsState(state2);
3311
- const mem = facts.get(job.harness, r.session.id);
3662
+ const mem = facts.get(job.harness, id);
3312
3663
  const heavy = !/^(PreToolUse|PostToolUse|PostToolUseFailure|BeforeTool|AfterTool|PostInvocation)$/.test(job.event);
3313
3664
  let learned = {};
3314
3665
  if (heavy) {
3315
3666
  const f = adapter.facts(r.session, r.transcript, r.hints ?? {}, facts);
3316
3667
  const { started_at: _s, last_at: _l, first_ask: _fa, harness_version, ...rest } = f;
3317
3668
  learned = rest;
3318
- if (harness_version) facts.save(job.harness, r.session.id, { harness_version });
3319
- 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 });
3320
3673
  }
3321
3674
  const known = mem.session ?? {};
3322
3675
  const cwdRaw = learned.cwd ?? r.session.cwd ?? known.cwd;
@@ -3326,13 +3679,13 @@ async function runJob(job, opts = {}) {
3326
3679
  ...learned,
3327
3680
  ...git,
3328
3681
  ...learned.branch ? { branch: learned.branch } : {},
3329
- id: r.session.id,
3682
+ id,
3330
3683
  seq: 0,
3331
- machine: machineName(cfg, os4.hostname()),
3684
+ machine: machineName(cfg, os6.hostname()),
3332
3685
  ...cwdRaw ? { cwd: tilde(cwdRaw) } : {},
3333
3686
  ...r.session.model ? { model: r.session.model } : {}
3334
3687
  };
3335
- 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;
3336
3689
  const events = [];
3337
3690
  for (const a of r.events) {
3338
3691
  const e = makeEvent(a, {
@@ -3344,7 +3697,7 @@ async function runJob(job, opts = {}) {
3344
3697
  }
3345
3698
  const wantsTranscript = cfg.sinks.some((s) => !s.paused && Math.min(s.tier, s.max_tier ?? 3) >= 2);
3346
3699
  if (wantsTranscript && r.transcript && r.events.some((e) => e.type === "turn.ended" || e.type === "session.ended")) {
3347
- const cur = facts.get(job.harness, r.session.id);
3700
+ const cur = facts.get(job.harness, id);
3348
3701
  const fromLine = Number(cur.transcript_line) || 0;
3349
3702
  let turnNo = Number(cur.transcript_turns) || 0;
3350
3703
  const read = adapter.readTranscript(r.transcript, fromLine);
@@ -3364,17 +3717,13 @@ async function runJob(job, opts = {}) {
3364
3717
  )
3365
3718
  );
3366
3719
  }
3367
- if (read.turns.length)
3368
- 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 });
3369
3721
  }
3370
3722
  outbox.append(
3371
3723
  ref,
3372
3724
  events.map((e) => ({ ...e, session: redactDeep(e.session), data: redactDeep(e.data) }))
3373
3725
  );
3374
- log(
3375
- state2,
3376
- `${job.harness} ${job.event}: ${events.map((e) => e.type).join(",") || "nothing"} (${r.session.id.slice(0, 8)})`
3377
- );
3726
+ log(state2, `${job.harness} ${job.event}: ${events.map((e) => e.type).join(",") || "nothing"} (${id.slice(0, 8)})`);
3378
3727
  const delivered = await flush(state2, cfg.sinks, outbox, ref);
3379
3728
  if (Math.random() < 0.02) outbox.prune(cfg.keep_days ?? 30);
3380
3729
  return { events, delivered };
@@ -3433,7 +3782,7 @@ async function flush(state2, sinkConfigs, outbox, first) {
3433
3782
  const { events, offset: next } = outbox.read(ref, offset, room);
3434
3783
  if (!events.length) break;
3435
3784
  for (const e of events) {
3436
- const f = filterEvent(e, sink2.tier, sink2.pii, { home: os4.homedir() });
3785
+ const f = filterEvent(e, sink2.tier, sink2.pii, { home: os6.homedir() });
3437
3786
  if (f) batch.push(f);
3438
3787
  }
3439
3788
  offset = next;
@@ -3465,7 +3814,7 @@ function applySinkVerdict(name, res) {
3465
3814
  } catch {
3466
3815
  }
3467
3816
  }
3468
- var jobsDir = (state2) => path14.join(state2, "jobs");
3817
+ var jobsDir = (state2) => path18.join(state2, "jobs");
3469
3818
 
3470
3819
  // src/runtime.ts
3471
3820
  function stableNode(exe) {
@@ -3501,13 +3850,13 @@ function rerunGlobally(argv, out2) {
3501
3850
  }).trim();
3502
3851
  } catch {
3503
3852
  }
3504
- 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;
3505
3854
  execFileSync5("npm", ["install", "-g", `sessionpipe@${VERSION}`, ...target ? ["--prefix", target] : []], {
3506
3855
  stdio: ["ignore", "ignore", "inherit"]
3507
3856
  });
3508
- 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";
3509
3858
  if (target && target !== prefix)
3510
- 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.`);
3511
3860
  } catch {
3512
3861
  out2(
3513
3862
  " ! couldn't install globally (`npm install -g sessionpipe` failed). Run it yourself, then retry this command."
@@ -3563,11 +3912,11 @@ function globalPrefix(distDir2, platform = process.platform) {
3563
3912
  if (viaNpx(distDir2)) return null;
3564
3913
  const prefix = prefixOf(distDir2, platform);
3565
3914
  if (!prefix) return null;
3566
- 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");
3567
3916
  return existsSync9(bin) ? prefix : null;
3568
3917
  }
3569
3918
  function unwritable(prefix, platform = process.platform) {
3570
- 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");
3571
3920
  try {
3572
3921
  accessSync2(dir, constants2.W_OK);
3573
3922
  return null;
@@ -3576,8 +3925,8 @@ function unwritable(prefix, platform = process.platform) {
3576
3925
  }
3577
3926
  }
3578
3927
  function npmArgv(node, platform = process.platform) {
3579
- const dir = path16.dirname(node);
3580
- 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")];
3581
3930
  for (const c of candidates) {
3582
3931
  try {
3583
3932
  const real = realpathSync3(c);
@@ -3622,7 +3971,7 @@ var execCapture = (argv, o) => new Promise((resolve) => {
3622
3971
  });
3623
3972
  var lastLine = (r) => (r.error || r.stderr.trim().split("\n").filter(Boolean).slice(-2).join(" ") || `exit ${r.code}`).slice(0, 300);
3624
3973
  async function installedVersion(o) {
3625
- 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"], {
3626
3975
  env: o.env ?? process.env,
3627
3976
  timeoutMs: 3e4
3628
3977
  });
@@ -3634,7 +3983,7 @@ async function installVersion(version, o) {
3634
3983
  const platform = o.platform ?? process.platform;
3635
3984
  const exec = o.exec ?? execCapture;
3636
3985
  const base = o.env ?? process.env;
3637
- 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) };
3638
3987
  const prefix = globalPrefix(o.distDir, platform);
3639
3988
  if (!prefix)
3640
3989
  return { ok: false, ms: Date.now() - t0, stage: "prefix", error: "this copy isn't a global npm install" };
@@ -3665,7 +4014,7 @@ async function installVersion(version, o) {
3665
4014
  let hooks = "no harness set up here; nothing to re-arm";
3666
4015
  if (o.harnesses.length) {
3667
4016
  const h = await exec(
3668
- [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}`)],
3669
4018
  { env, timeoutMs: 2 * 6e4 }
3670
4019
  );
3671
4020
  hooks = h.code === 0 ? `hooks re-armed for ${o.harnesses.join(", ")}` : `re-arming the hooks failed (${lastLine(h)}); \`sessionpipe install\` does it`;
@@ -3676,17 +4025,17 @@ async function installVersion(version, o) {
3676
4025
  // src/control/store.ts
3677
4026
  import {
3678
4027
  appendFileSync as appendFileSync4,
3679
- chmodSync as chmodSync2,
4028
+ chmodSync as chmodSync3,
3680
4029
  closeSync as closeSync4,
3681
4030
  existsSync as existsSync10,
3682
4031
  fsyncSync,
3683
- mkdirSync as mkdirSync8,
4032
+ mkdirSync as mkdirSync9,
3684
4033
  openSync as openSync4,
3685
- readFileSync as readFileSync10,
3686
- renameSync as renameSync3,
3687
- writeFileSync as writeFileSync7
4034
+ readFileSync as readFileSync11,
4035
+ renameSync as renameSync4,
4036
+ writeFileSync as writeFileSync8
3688
4037
  } from "node:fs";
3689
- import path17 from "node:path";
4038
+ import path21 from "node:path";
3690
4039
 
3691
4040
  // ../core/src/control/verify.ts
3692
4041
  var GRANT_MAX_MS = 24 * 36e5;
@@ -3897,7 +4246,6 @@ function parseCommand(str) {
3897
4246
  break;
3898
4247
  case "start":
3899
4248
  if (!text()) return bad("bad_fields", `a new session needs text, at most ${TEXT_MAX} characters`);
3900
- if (ref.harness !== "claude-code") return bad("bad_fields", "only Claude Code sessions can be started");
3901
4249
  if (typeof c.cwd !== "string" || !isAbsolute(c.cwd) || c.cwd.length > 1024)
3902
4250
  return bad("bad_fields", "a new session needs an absolute folder");
3903
4251
  if (has2("for") || has2("decision")) return bad("bad_fields", "a start carries text and cwd");
@@ -4023,22 +4371,22 @@ async function checkGrantPath(s, c, o) {
4023
4371
  }
4024
4372
 
4025
4373
  // src/control/store.ts
4026
- var controlFile = (env = process.env) => path17.join(path17.dirname(configFile(env)), "control.json");
4027
- 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");
4028
4376
  function writePrivate(file, value) {
4029
- mkdirSync8(path17.dirname(file), { recursive: true, mode: 448 });
4377
+ mkdirSync9(path21.dirname(file), { recursive: true, mode: 448 });
4030
4378
  const tmp = `${file}.${process.pid}.tmp`;
4031
- writeFileSync7(tmp, `${JSON.stringify(value, null, 2)}
4379
+ writeFileSync8(tmp, `${JSON.stringify(value, null, 2)}
4032
4380
  `, { mode: 384 });
4033
- renameSync3(tmp, file);
4381
+ renameSync4(tmp, file);
4034
4382
  try {
4035
- chmodSync2(file, 384);
4383
+ chmodSync3(file, 384);
4036
4384
  } catch {
4037
4385
  }
4038
4386
  }
4039
4387
  function readControl(env = process.env, run = defaultRunner) {
4040
4388
  try {
4041
- const j = JSON.parse(readFileSync10(controlFile(env), "utf8"));
4389
+ const j = JSON.parse(readFileSync11(controlFile(env), "utf8"));
4042
4390
  if (!j || !Array.isArray(j.receivers)) return null;
4043
4391
  return {
4044
4392
  name: j.name || "machine",
@@ -4055,7 +4403,7 @@ function readControl(env = process.env, run = defaultRunner) {
4055
4403
  }
4056
4404
  function pairedCount(env = process.env) {
4057
4405
  try {
4058
- const j = JSON.parse(readFileSync10(controlFile(env), "utf8"));
4406
+ const j = JSON.parse(readFileSync11(controlFile(env), "utf8"));
4059
4407
  return Array.isArray(j.receivers) ? j.receivers.length : 0;
4060
4408
  } catch {
4061
4409
  return 0;
@@ -4105,7 +4453,7 @@ var NonceStore = class {
4105
4453
  this.now = now;
4106
4454
  let raw = "";
4107
4455
  try {
4108
- raw = readFileSync10(file, "utf8");
4456
+ raw = readFileSync11(file, "utf8");
4109
4457
  } catch {
4110
4458
  }
4111
4459
  const cut = this.now() - NONCE_TTL_MS;
@@ -4126,7 +4474,7 @@ var NonceStore = class {
4126
4474
  const t = this.seenAt.get(nonce);
4127
4475
  if (t !== void 0 && t > this.now() - NONCE_TTL_MS) return true;
4128
4476
  const at = this.now();
4129
- mkdirSync8(path17.dirname(this.file), { recursive: true, mode: 448 });
4477
+ mkdirSync9(path21.dirname(this.file), { recursive: true, mode: 448 });
4130
4478
  const fd = openSync4(this.file, "a", 384);
4131
4479
  try {
4132
4480
  appendFileSync4(fd, `${JSON.stringify({ n: nonce, t: at })}
@@ -4144,10 +4492,10 @@ var NonceStore = class {
4144
4492
  for (const [n, t] of this.seenAt) if (t <= cut) this.seenAt.delete(n);
4145
4493
  if (!existsSync10(this.file) && !this.seenAt.size) return;
4146
4494
  const body = [...this.seenAt].map(([n, t]) => JSON.stringify({ n, t })).join("\n");
4147
- mkdirSync8(path17.dirname(this.file), { recursive: true, mode: 448 });
4148
- writeFileSync7(`${this.file}.tmp`, body ? `${body}
4495
+ mkdirSync9(path21.dirname(this.file), { recursive: true, mode: 448 });
4496
+ writeFileSync8(`${this.file}.tmp`, body ? `${body}
4149
4497
  ` : "", { mode: 384 });
4150
- renameSync3(`${this.file}.tmp`, this.file);
4498
+ renameSync4(`${this.file}.tmp`, this.file);
4151
4499
  }
4152
4500
  get size() {
4153
4501
  return this.seenAt.size;
@@ -4160,27 +4508,27 @@ var AckOutbox = class {
4160
4508
  file;
4161
4509
  all() {
4162
4510
  try {
4163
- 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));
4164
4512
  } catch {
4165
4513
  return [];
4166
4514
  }
4167
4515
  }
4168
4516
  add(a) {
4169
- mkdirSync8(path17.dirname(this.file), { recursive: true, mode: 448 });
4517
+ mkdirSync9(path21.dirname(this.file), { recursive: true, mode: 448 });
4170
4518
  appendFileSync4(this.file, `${JSON.stringify(a)}
4171
4519
  `, { mode: 384 });
4172
4520
  }
4173
4521
  keep(left) {
4174
- mkdirSync8(path17.dirname(this.file), { recursive: true, mode: 448 });
4175
- 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" : ""), {
4176
4524
  mode: 384
4177
4525
  });
4178
- renameSync3(`${this.file}.tmp`, this.file);
4526
+ renameSync4(`${this.file}.tmp`, this.file);
4179
4527
  }
4180
4528
  };
4181
4529
  function readState(file) {
4182
4530
  try {
4183
- const j = JSON.parse(readFileSync10(file, "utf8"));
4531
+ const j = JSON.parse(readFileSync11(file, "utf8"));
4184
4532
  return { copies: j.copies && typeof j.copies === "object" ? j.copies : {} };
4185
4533
  } catch {
4186
4534
  return { copies: {} };
@@ -4191,12 +4539,12 @@ var writeState = (file, s) => writePrivate(file, s);
4191
4539
  // src/control/autoupdate.ts
4192
4540
  var UPDATE_EXIT = 75;
4193
4541
  var DAY_MS = 24 * 36e5;
4194
- var LABEL = "org.sessionpipe.control";
4542
+ var LABEL3 = "org.sessionpipe.control";
4195
4543
  var UNIT = "sessionpipe-control.service";
4196
- var updateFile = (env = process.env) => path18.join(controlState(env), "update.json");
4544
+ var updateFile = (env = process.env) => path22.join(controlState(env), "update.json");
4197
4545
  function readUpdate(file) {
4198
4546
  try {
4199
- const j = JSON.parse(readFileSync11(file, "utf8"));
4547
+ const j = JSON.parse(readFileSync12(file, "utf8"));
4200
4548
  return j && typeof j === "object" ? j : {};
4201
4549
  } catch {
4202
4550
  return {};
@@ -4254,12 +4602,12 @@ function updateLine(s, now) {
4254
4602
  function restartVia(o) {
4255
4603
  const s = o.env.SESSIONPIPE_SERVICE ?? "";
4256
4604
  if (s.startsWith("supervisor:") && s === `supervisor:${o.ppid}`) return "supervisor";
4257
- 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))
4258
4606
  return "launchd";
4259
4607
  if (o.platform === "linux") {
4260
4608
  let cg = "";
4261
4609
  try {
4262
- cg = (o.cgroup ?? (() => readFileSync11("/proc/self/cgroup", "utf8")))();
4610
+ cg = (o.cgroup ?? (() => readFileSync12("/proc/self/cgroup", "utf8")))();
4263
4611
  } catch {
4264
4612
  }
4265
4613
  const inUnit = cg.split("\n").some((l) => l.endsWith(`/${UNIT}`) || l.includes(`/${UNIT}/`));
@@ -4490,19 +4838,19 @@ function supervise(o) {
4490
4838
 
4491
4839
  // src/control/cli.ts
4492
4840
  import { existsSync as existsSync13, realpathSync as realpathSync5, unwatchFile, watchFile } from "node:fs";
4493
- import os7 from "node:os";
4494
- import path21 from "node:path";
4841
+ import os9 from "node:os";
4842
+ import path25 from "node:path";
4495
4843
 
4496
4844
  // src/control/daemon.ts
4497
- 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";
4498
4846
  import net2 from "node:net";
4499
- import path19 from "node:path";
4847
+ import path23 from "node:path";
4500
4848
 
4501
4849
  // src/control/reply.ts
4502
- 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";
4503
4851
  function transcriptEnd(file) {
4504
4852
  try {
4505
- return statSync7(file).size;
4853
+ return statSync8(file).size;
4506
4854
  } catch {
4507
4855
  return 0;
4508
4856
  }
@@ -4559,16 +4907,19 @@ async function awaitAnswer(file, from, o) {
4559
4907
  }
4560
4908
 
4561
4909
  // src/control/route.ts
4910
+ var HEADLESS = /* @__PURE__ */ new Set(["claude-code", "codex", "antigravity"]);
4562
4911
  function routePrompt(s) {
4563
4912
  if (s.sdk) return { mode: "sdk" };
4564
4913
  if (s.waiter) return { mode: "waiter" };
4565
4914
  if (s.midTurn) return { mode: "turn" };
4566
4915
  if (s.api) return { mode: "api" };
4567
- 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` };
4568
4917
  if (!s.known) return { refused: "no_session" };
4569
4918
  if (!s.live) return { mode: "resume" };
4570
4919
  if (s.canFork) return { mode: "fork" };
4571
- 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
+ };
4572
4923
  }
4573
4924
  function kindPath(kind, harness) {
4574
4925
  switch (kind) {
@@ -4579,7 +4930,7 @@ function kindPath(kind, harness) {
4579
4930
  case "cancel":
4580
4931
  return "cancel";
4581
4932
  case "start":
4582
- 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` };
4583
4934
  default:
4584
4935
  return { unsupported: `unknown kind ${String(kind)}` };
4585
4936
  }
@@ -4688,7 +5039,6 @@ var {
4688
5039
  claudeEnv: claudeEnv2,
4689
5040
  findTranscript: findTranscript2,
4690
5041
  isUuid: isUuid2,
4691
- JOB_TIMEOUT_MS: JOB_TIMEOUT_MS2,
4692
5042
  liveProcess: liveProcess2,
4693
5043
  loginHint: loginHint2,
4694
5044
  modeFlags: modeFlags2,
@@ -4696,7 +5046,16 @@ var {
4696
5046
  recentlyWritten: recentlyWritten2,
4697
5047
  sessionFolder: sessionFolder2
4698
5048
  } = claude_code_control_exports;
4699
- var VERSION2 = true ? "0.6.0" : "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";
4700
5059
  var MAX_REPLY = 2e4;
4701
5060
  var HELLO_EVERY_MS = 10 * 6e4;
4702
5061
  var TURN_FALLBACK_MS = 30 * 6e4;
@@ -4732,9 +5091,10 @@ var ControlDaemon = class {
4732
5091
  this.dir = dir;
4733
5092
  this.sock = sock;
4734
5093
  this.deps = deps;
4735
- this.nonces = new NonceStore(path19.join(dir, "nonces.jsonl"), deps.now);
4736
- this.acks = new AckOutbox(path19.join(dir, "acks.jsonl"));
4737
- 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));
4738
5098
  }
4739
5099
  cfg;
4740
5100
  dir;
@@ -4780,10 +5140,16 @@ var ControlDaemon = class {
4780
5140
  /** Every open local connection: stop() ends them, or the server's close would wait
4781
5141
  * on a client that never says anything. */
4782
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();
4783
5149
  // --- lifecycle ------------------------------------------------------------------
4784
5150
  async start() {
4785
5151
  this.running = true;
4786
- mkdirSync9(this.dir, { recursive: true, mode: 448 });
5152
+ mkdirSync10(this.dir, { recursive: true, mode: 448 });
4787
5153
  await this.listen();
4788
5154
  for (const r of this.cfg.receivers) void this.pollLoop(r);
4789
5155
  this.helloTimer = setInterval(() => void this.helloAll(), HELLO_EVERY_MS);
@@ -4836,7 +5202,7 @@ var ControlDaemon = class {
4836
5202
  * for ten minutes: a session killed mid-turn never sends its Stop. */
4837
5203
  busy() {
4838
5204
  if (this.inflight.size) return `${this.inflight.size} message(s) in hand`;
4839
- if (this.procs || this.ownRuns.size) return "a headless Claude Code run";
5205
+ if (this.procs || this.ownRuns.size) return "a headless agent run";
4840
5206
  if (this.startsRunning) return "a session starting";
4841
5207
  for (const s of this.sdkSessions.values()) if (s.busy) return "an Agent SDK session mid-turn";
4842
5208
  for (const l of this.live.values()) if (l.attentions.size) return "a permission prompt waiting for an answer";
@@ -5049,7 +5415,7 @@ ${IN_PLACE_ANSWER}`, id: p.msgId });
5049
5415
  return null;
5050
5416
  }
5051
5417
  onEvent(e) {
5052
- const l = this.liveOf(e.session);
5418
+ const l = this.liveOf(this.named(e.session));
5053
5419
  l.lastEvent = this.deps.now();
5054
5420
  if (e.cwd) l.cwd = e.cwd;
5055
5421
  if (e.transcript) l.transcript = e.transcript;
@@ -5194,10 +5560,11 @@ ${IN_PLACE_ANSWER}`, id: p.msgId });
5194
5560
  void this.refreshSessionTools();
5195
5561
  const waiting = [...this.live].filter(([, l]) => l.waiter).map(([k]) => k);
5196
5562
  const modes = ["waiter", "turn", "resume", "fork", ...this.deps.sdk ? ["sdk"] : []];
5563
+ const harnesses = this.harnesses();
5197
5564
  for (const r of this.cfg.receivers) {
5198
5565
  const body = {
5199
5566
  name: this.cfg.name,
5200
- harnesses: ["claude-code"],
5567
+ harnesses,
5201
5568
  modes,
5202
5569
  keys: r.keys.map((k) => k.id),
5203
5570
  version: VERSION2,
@@ -5276,6 +5643,27 @@ ${IN_PLACE_ANSWER}`, id: p.msgId });
5276
5643
  return;
5277
5644
  }
5278
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) {
5279
5667
  const sent = Date.parse(m.at) || this.deps.now();
5280
5668
  if (Date.parse(m.expires_at) <= this.deps.now()) return this.finish(r, m.id, { outcome: "expired" });
5281
5669
  const v = await verifyCommand(
@@ -5294,8 +5682,10 @@ ${IN_PLACE_ANSWER}`, id: p.msgId });
5294
5682
  try {
5295
5683
  if (kp === "start") return await this.startSession(r, m.id, cmd2, sent);
5296
5684
  let place = this.place(cmd2.session);
5297
- 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))) {
5298
5687
  this.finish(r, m.id, { outcome: "taken" });
5688
+ await starting;
5299
5689
  await this.serial(ref.id, async () => {
5300
5690
  });
5301
5691
  place = this.place(cmd2.session);
@@ -5314,9 +5704,11 @@ ${IN_PLACE_ANSWER}`, id: p.msgId });
5314
5704
  }
5315
5705
  }
5316
5706
  /** Where the session lives, and whether its folder is allowed — read from the
5317
- * 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. */
5318
5709
  place(session) {
5319
5710
  const ref = parseSessionRef(session);
5711
+ if (ref.harness === "codex" || ref.harness === "antigravity") return this.placeDriven(session, ref);
5320
5712
  if (ref.harness === "claude-code") {
5321
5713
  const target = this.state.copies[ref.id] ?? ref.id;
5322
5714
  const t = findTranscript2(target, this.deps.claudeDirs()) ?? findTranscript2(ref.id, this.deps.claudeDirs());
@@ -5331,12 +5723,100 @@ ${IN_PLACE_ANSWER}`, id: p.msgId });
5331
5723
  if (!ok) return { refused: cwd ? "folder" : "no_session", why: "no allowed folder known for that session" };
5332
5724
  return { folder: ok, transcript: null };
5333
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
+ }
5334
5814
  allowed(dir) {
5335
- if (!path19.isAbsolute(dir)) return null;
5815
+ if (!path23.isAbsolute(dir)) return null;
5336
5816
  let r;
5337
5817
  try {
5338
5818
  r = realpathSync4.native(dir);
5339
- if (!statSync8(r).isDirectory()) return null;
5819
+ if (!statSync9(r).isDirectory()) return null;
5340
5820
  } catch {
5341
5821
  return null;
5342
5822
  }
@@ -5346,7 +5826,7 @@ ${IN_PLACE_ANSWER}`, id: p.msgId });
5346
5826
  rf = realpathSync4.native(f);
5347
5827
  } catch {
5348
5828
  }
5349
- 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;
5350
5830
  }
5351
5831
  return null;
5352
5832
  }
@@ -5383,9 +5863,23 @@ ${IN_PLACE_ANSWER}`, id: p.msgId });
5383
5863
  await held.interrupt();
5384
5864
  this.finish(r, id, { outcome: "delivered", mode: "sdk" }, sent);
5385
5865
  }
5386
- view(session, transcript) {
5866
+ view(session, place) {
5387
5867
  const ref = parseSessionRef(session);
5388
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;
5389
5883
  const claude = ref.harness === "claude-code" ? this.deps.claude() : null;
5390
5884
  const target = this.state.copies[ref.id] ?? ref.id;
5391
5885
  const t = transcript;
@@ -5402,7 +5896,7 @@ ${IN_PLACE_ANSWER}`, id: p.msgId });
5402
5896
  };
5403
5897
  }
5404
5898
  async prompt(r, id, cmd2, place, sent) {
5405
- const route = routePrompt(this.view(cmd2.session, place.transcript));
5899
+ const route = routePrompt(this.view(cmd2.session, place));
5406
5900
  if ("unsupported" in route) return this.finish(r, id, { outcome: "unsupported", detail: route.unsupported });
5407
5901
  if ("refused" in route) return this.finish(r, id, { outcome: "refused", code: route.refused });
5408
5902
  const ref = parseSessionRef(cmd2.session);
@@ -5476,20 +5970,100 @@ ${IN_PLACE_ANSWER}`, id: p.msgId });
5476
5970
  /** Null when Claude Code is signed in for this account (or can't say); otherwise what
5477
5971
  * to tell the person. */
5478
5972
  async loginProblem(configDir) {
5479
- if (!this.deps.login) return null;
5480
- 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) {
5481
5981
  const now = this.deps.now();
5482
5982
  const hit = this.logins.get(key);
5483
5983
  let login = hit && now - hit.at < (hit.login.loggedIn === false ? 3e4 : 6e5) ? hit.login : null;
5484
5984
  if (!login) {
5485
- login = await this.deps.login(configDir).catch(() => ({ loggedIn: null }));
5985
+ login = await ask2().catch(() => ({ loggedIn: null }));
5486
5986
  this.logins.set(key, { at: now, login });
5487
5987
  }
5488
- if (login.loggedIn !== false) return null;
5489
- this.deps.log(`claude isn't signed in for ${configDir ?? "the default account"} as the daemon sees it`);
5490
- 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);
5491
6064
  }
5492
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);
5493
6067
  const claude = this.deps.claude();
5494
6068
  if (!claude)
5495
6069
  return this.finish(r, id, { outcome: "failed", detail: "Claude Code isn't installed on this machine" });
@@ -5519,7 +6093,7 @@ ${IN_PLACE_ANSWER}`, id: p.msgId });
5519
6093
  () => this.deps.run(claude.bin, args2, {
5520
6094
  cwd: place.folder,
5521
6095
  env: claudeEnv2(place.transcript?.configDir, this.deps.env),
5522
- timeoutMs: JOB_TIMEOUT_MS2,
6096
+ timeoutMs: JOB_TIMEOUT_MS,
5523
6097
  ...claude.caps.stream ? { onLine: (l) => stream.feed(l) } : {}
5524
6098
  })
5525
6099
  )
@@ -5531,7 +6105,7 @@ ${IN_PLACE_ANSWER}`, id: p.msgId });
5531
6105
  const ran = isUuid2(out2.session) ? out2.session : target;
5532
6106
  if (mode === "fork" && ran !== target) {
5533
6107
  this.state.copies[ref.id] = ran;
5534
- writeState(path19.join(this.dir, "state.json"), this.state);
6108
+ writeState(path23.join(this.dir, "state.json"), this.state);
5535
6109
  this.lastEnd.set(ran, this.deps.now());
5536
6110
  }
5537
6111
  this.finish(
@@ -5556,9 +6130,7 @@ ${IN_PLACE_ANSWER}`, id: p.msgId });
5556
6130
  if (j.is_error) return { failed: String(j.result || j.subtype || "Claude Code reported an error") };
5557
6131
  const denials = Array.isArray(j.permission_denials) ? j.permission_denials : [];
5558
6132
  const names = [...new Set(denials.map((d) => d?.tool_name).filter((n) => typeof n === "string"))];
5559
- const tail2 = denials.length ? `
5560
-
5561
- (Not allowed on this machine, since nobody was there to approve: ${names.join(", ") || `${denials.length} action(s)`}.)` : "";
6133
+ const tail2 = deniedNote(names, denials.length);
5562
6134
  const text = typeof j.result === "string" && j.result.trim() ? j.result : "(Claude Code finished without a written reply.)";
5563
6135
  return { reply: text + tail2, ...typeof j.session_id === "string" ? { session: j.session_id } : {} };
5564
6136
  }
@@ -5693,6 +6265,7 @@ ${IN_PLACE_ANSWER}`, id: p.msgId });
5693
6265
  code: "folder",
5694
6266
  detail: "that folder isn't one this machine allows"
5695
6267
  });
6268
+ if (ref.harness !== "claude-code") return this.startDriven(r, id, cmd2, ref, cwd, sent);
5696
6269
  if (findTranscript2(ref.id, this.deps.claudeDirs()) || this.sdkSessions.has(ref.id))
5697
6270
  return this.finish(r, id, {
5698
6271
  outcome: "refused",
@@ -5737,7 +6310,7 @@ ${IN_PLACE_ANSWER}`, id: p.msgId });
5737
6310
  {
5738
6311
  cwd,
5739
6312
  env: claudeEnv2(void 0, this.deps.env),
5740
- timeoutMs: JOB_TIMEOUT_MS2,
6313
+ timeoutMs: JOB_TIMEOUT_MS,
5741
6314
  ...claude.caps.stream ? { onLine: (l) => stream.feed(l) } : {}
5742
6315
  }
5743
6316
  )
@@ -5748,6 +6321,24 @@ ${IN_PLACE_ANSWER}`, id: p.msgId });
5748
6321
  if ("failed" in out2) return this.finish(r, id, { outcome: "failed", detail: out2.failed });
5749
6322
  this.finish(r, id, { outcome: "delivered", mode: "resume", reply: out2.reply }, sent);
5750
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
+ }
5751
6342
  closeIdleSdk() {
5752
6343
  const idle = this.deps.sdkIdleMs ?? 10 * 6e4;
5753
6344
  for (const [id, s] of this.sdkSessions)
@@ -5763,7 +6354,7 @@ ${IN_PLACE_ANSWER}`, id: p.msgId });
5763
6354
  var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms).unref?.());
5764
6355
  function socketId(p) {
5765
6356
  try {
5766
- const st = statSync8(p);
6357
+ const st = statSync9(p);
5767
6358
  return `${st.dev}:${st.ino}`;
5768
6359
  } catch {
5769
6360
  return null;
@@ -5773,10 +6364,10 @@ function socketId(p) {
5773
6364
  // src/control/pair.ts
5774
6365
  import { execFileSync as execFileSync6 } from "node:child_process";
5775
6366
  import { randomBytes as randomBytes3 } from "node:crypto";
5776
- import { existsSync as existsSync12, mkdirSync as mkdirSync10, unlinkSync as unlinkSync6, writeFileSync as writeFileSync8 } from "node:fs";
5777
- import os6 from "node:os";
5778
- import path20 from "node:path";
5779
- var VERSION3 = true ? "0.6.0" : "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";
5780
6371
  async function json(r) {
5781
6372
  try {
5782
6373
  return await r.json();
@@ -5811,7 +6402,7 @@ async function pair(o) {
5811
6402
  const cfg = readControl(o.env) ?? { name: o.name, folders: [], mode: o.mode, receivers: [] };
5812
6403
  cfg.name = o.name || cfg.name;
5813
6404
  cfg.mode = o.mode;
5814
- 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))])];
5815
6406
  const existing = cfg.receivers.find((r) => r.control === control);
5816
6407
  const auth = { authorization: `Bearer ${o.token ?? pollKey}`, "content-type": "application/json" };
5817
6408
  const started = await f(`${control}/pair`, {
@@ -5821,7 +6412,7 @@ async function pair(o) {
5821
6412
  ...pollKey ? { poll_key: pollKey } : {},
5822
6413
  ...existing ? { machine: existing.machine } : {},
5823
6414
  name: cfg.name,
5824
- harnesses: ["claude-code"],
6415
+ harnesses: o.harnesses ?? drivableHarnesses(o.env ?? process.env),
5825
6416
  modes: ["waiter", "turn", "resume", "fork"],
5826
6417
  version: VERSION3,
5827
6418
  folders: cfg.folders,
@@ -5911,24 +6502,24 @@ function removeKey(id, env) {
5911
6502
  writeControl(cfg, env);
5912
6503
  return n;
5913
6504
  }
5914
- var LABEL2 = "org.sessionpipe.control";
6505
+ var LABEL4 = "org.sessionpipe.control";
5915
6506
  var UNIT2 = "sessionpipe-control.service";
5916
6507
  function serviceFiles(o) {
5917
- const home = o.home ?? os6.homedir();
6508
+ const home = o.home ?? os8.homedir();
5918
6509
  const xml = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
5919
6510
  const plist = `<?xml version="1.0" encoding="UTF-8"?>
5920
6511
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
5921
6512
  <plist version="1.0">
5922
6513
  <dict>
5923
- <key>Label</key><string>${LABEL2}</string>
6514
+ <key>Label</key><string>${LABEL4}</string>
5924
6515
  <key>ProgramArguments</key>
5925
6516
  <array><string>${xml(o.node)}</string><string>${xml(o.cli)}</string><string>control</string><string>run</string></array>
5926
6517
  <key>EnvironmentVariables</key><dict><key>SESSIONPIPE_SERVICE</key><string>launchd</string></dict>
5927
6518
  <key>RunAtLoad</key><true/>
5928
6519
  <key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict>
5929
6520
  <key>ProcessType</key><string>Background</string>
5930
- <key>StandardOutPath</key><string>${xml(path20.join(o.logDir, "daemon.log"))}</string>
5931
- <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>
5932
6523
  </dict>
5933
6524
  </plist>
5934
6525
  `;
@@ -5948,8 +6539,8 @@ RestartSec=5
5948
6539
  WantedBy=default.target
5949
6540
  `;
5950
6541
  return {
5951
- plist: { file: path20.join(home, "Library", "LaunchAgents", `${LABEL2}.plist`), body: plist },
5952
- 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 }
5953
6544
  };
5954
6545
  }
5955
6546
  var execQuiet = (cmd2, args2) => execFileSync6(cmd2, args2, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 15e3 });
@@ -5974,37 +6565,37 @@ function ensureLinger(user, exec = execQuiet) {
5974
6565
  }
5975
6566
  function installService(o) {
5976
6567
  const logDir = controlState(o.env);
5977
- mkdirSync10(logDir, { recursive: true, mode: 448 });
6568
+ mkdirSync11(logDir, { recursive: true, mode: 448 });
5978
6569
  const f = serviceFiles({ node: o.node, cli: o.cli, logDir });
5979
6570
  const run = o.exec ?? execQuiet;
5980
6571
  const platform = o.platform ?? process.platform;
5981
6572
  if (platform === "darwin") {
5982
- mkdirSync10(path20.dirname(f.plist.file), { recursive: true });
5983
- writeFileSync8(f.plist.file, f.plist.body);
6573
+ mkdirSync11(path24.dirname(f.plist.file), { recursive: true });
6574
+ writeFileSync9(f.plist.file, f.plist.body);
5984
6575
  const uid = String(process.getuid?.() ?? "");
5985
6576
  for (const d of ["gui", "user"])
5986
6577
  try {
5987
- run("launchctl", ["bootout", `${d}/${uid}/${LABEL2}`]);
6578
+ run("launchctl", ["bootout", `${d}/${uid}/${LABEL4}`]);
5988
6579
  } catch {
5989
6580
  }
5990
6581
  try {
5991
6582
  run("launchctl", ["bootstrap", `gui/${uid}`, f.plist.file]);
5992
- return `launchd agent ${LABEL2} (log: ${path20.join(logDir, "daemon.log")})`;
6583
+ return `launchd agent ${LABEL4} (log: ${path24.join(logDir, "daemon.log")})`;
5993
6584
  } catch {
5994
6585
  }
5995
6586
  run("launchctl", ["bootstrap", `user/${uid}`, f.plist.file]);
5996
- 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")})`;
5997
6588
  }
5998
6589
  if (platform === "linux") {
5999
- mkdirSync10(path20.dirname(f.unit.file), { recursive: true });
6000
- writeFileSync8(f.unit.file, f.unit.body);
6590
+ mkdirSync11(path24.dirname(f.unit.file), { recursive: true });
6591
+ writeFileSync9(f.unit.file, f.unit.body);
6001
6592
  try {
6002
6593
  run("systemctl", ["--user", "daemon-reload"]);
6003
6594
  run("systemctl", ["--user", "enable", "--now", UNIT2]);
6004
6595
  } catch {
6005
6596
  return `wrote ${f.unit.file}, but systemd --user isn't reachable here; run \`sessionpipe control run\` yourself (tmux, screen)`;
6006
6597
  }
6007
- const linger = ensureLinger(os6.userInfo().username, run);
6598
+ const linger = ensureLinger(os8.userInfo().username, run);
6008
6599
  return `systemd user unit ${UNIT2}; ${linger.note} (journalctl --user -u ${UNIT2})`;
6009
6600
  }
6010
6601
  return "no service manager for this platform yet; run `sessionpipe control run` yourself";
@@ -6018,8 +6609,8 @@ function uninstallService(env) {
6018
6609
  }
6019
6610
  };
6020
6611
  if (process.platform === "darwin" && existsSync12(f.plist.file)) {
6021
- run("launchctl", ["bootout", `gui/${process.getuid?.() ?? ""}/${LABEL2}`]);
6022
- run("launchctl", ["bootout", `user/${process.getuid?.() ?? ""}/${LABEL2}`]);
6612
+ run("launchctl", ["bootout", `gui/${process.getuid?.() ?? ""}/${LABEL4}`]);
6613
+ run("launchctl", ["bootout", `user/${process.getuid?.() ?? ""}/${LABEL4}`]);
6023
6614
  unlinkSync6(f.plist.file);
6024
6615
  }
6025
6616
  if (process.platform === "linux" && existsSync12(f.unit.file)) {
@@ -6149,9 +6740,11 @@ var CONTROL_HELP = ` sessionpipe control pair <receiver> [--folder DIR]\u2026 [
6149
6740
  sessionpipe wait [--session <harness>:<id>] (a session runs this in the background)`;
6150
6741
  var MODE_TEXT = ` auto Claude Code's auto mode: the session reads, edits and runs commands, and Claude Code's
6151
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.)
6152
6744
  safe Only what each folder's Claude Code settings already allow: with no allow rules, a
6153
6745
  session can't even read a file. Pick this if this machine holds things no agent
6154
- 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.)`;
6155
6748
  async function askMode(out2) {
6156
6749
  if (!process.stdin.isTTY) {
6157
6750
  out2(" Mode: safe for now. Run `sessionpipe control mode auto` to let sessions you message work unattended.");
@@ -6196,7 +6789,7 @@ ${CONTROL_HELP}`);
6196
6789
  const folders = flags("--folder");
6197
6790
  const existing = readControl();
6198
6791
  if (!folders.length && !existing?.folders.length) {
6199
- if (!isBroad(process.cwd(), os7.homedir())) folders.push(process.cwd());
6792
+ if (!isBroad(process.cwd(), os9.homedir())) folders.push(process.cwd());
6200
6793
  else
6201
6794
  out2(
6202
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.`
@@ -6218,7 +6811,7 @@ ${CONTROL_HELP}`);
6218
6811
  },
6219
6812
  folders,
6220
6813
  mode,
6221
- name: flag2("--name") ?? existing?.name ?? cfg.machine ?? os7.hostname(),
6814
+ name: flag2("--name") ?? existing?.name ?? cfg.machine ?? os9.hostname(),
6222
6815
  out: out2
6223
6816
  });
6224
6817
  const c = readControl();
@@ -6226,7 +6819,7 @@ ${CONTROL_HELP}`);
6226
6819
  out2(
6227
6820
  ` Mode: ${c.mode} (${c.mode === "safe" ? "nothing that needs approval runs unattended" : "Claude Code's auto mode"})`
6228
6821
  );
6229
- let cli = path21.join(distDir2, "cli.js");
6822
+ let cli = path25.join(distDir2, "cli.js");
6230
6823
  try {
6231
6824
  cli = realpathSync5(cli);
6232
6825
  } catch {
@@ -6309,6 +6902,10 @@ async function runDaemon(out2, distDir2) {
6309
6902
  log: log2,
6310
6903
  claudeDirs: () => claudeDirs(),
6311
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(),
6312
6909
  run: runClaude2,
6313
6910
  sdk,
6314
6911
  login: async (configDir) => {
@@ -6360,7 +6957,7 @@ async function runDaemon(out2, distDir2) {
6360
6957
  log2(`exiting ${UPDATE_EXIT} so ${via === "supervisor" ? "the supervising sessionpipe" : via} starts ${to}`);
6361
6958
  process.exit(UPDATE_EXIT);
6362
6959
  }
6363
- let cli = path21.join(distDir2, "cli.js");
6960
+ let cli = path25.join(distDir2, "cli.js");
6364
6961
  try {
6365
6962
  cli = realpathSync5(cli);
6366
6963
  } catch {
@@ -6405,16 +7002,16 @@ function adoptSinkToken(url, token, tier, file) {
6405
7002
  }
6406
7003
 
6407
7004
  // src/launcher.ts
6408
- 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";
6409
- import os8 from "node:os";
6410
- 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";
6411
7008
  var LAUNCHER = "sessionpipe-hook";
6412
7009
  var COPIED = ["worker.js", "hook.js"];
6413
7010
  function dataDir(env = process.env) {
6414
- if (env.SESSIONPIPE_DATA) return path22.resolve(env.SESSIONPIPE_DATA);
6415
- 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");
6416
7013
  }
6417
- var launcherPath = (env = process.env) => path22.join(dataDir(env), LAUNCHER);
7014
+ var launcherPath = (env = process.env) => path26.join(dataDir(env), LAUNCHER);
6418
7015
  var usesLauncher = (platform = process.platform) => platform !== "win32";
6419
7016
  var sq = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
6420
7017
  function launcherScript(node, hook, version) {
@@ -6423,43 +7020,43 @@ function launcherScript(node, hook, version) {
6423
7020
  # this; it runs hook.js beside it with the node sessionpipe was installed with, or with
6424
7021
  # any node on PATH once that one is gone (an nvm uninstall, a Homebrew upgrade).
6425
7022
  n=${sq(node)}
6426
- [ -x "$n" ] || n=$(command -v node) || { [ "$1" = antigravity ] && echo '{}'; exit 0; }
7023
+ [ -x "$n" ] || n=$(command -v node) || { [ "$1" = antigravity ] && if [ "$2" = PreToolUse ]; then echo ${sq(AG_STALE_PRETOOL.trim())}; else echo '{}'; fi; exit 0; }
6427
7024
  exec "$n" ${sq(hook)} "$@"
6428
7025
  `;
6429
7026
  }
6430
7027
  function writeIfChanged(file, body, mode) {
6431
7028
  try {
6432
- const cur = readFileSync12(file);
7029
+ const cur = readFileSync14(file);
6433
7030
  if (cur.equals(Buffer.isBuffer(body) ? body : Buffer.from(body))) {
6434
- chmodSync3(file, mode);
7031
+ chmodSync4(file, mode);
6435
7032
  return false;
6436
7033
  }
6437
7034
  } catch {
6438
7035
  }
6439
7036
  const tmp = `${file}.${process.pid}.tmp`;
6440
- writeFileSync9(tmp, body, { mode });
6441
- renameSync4(tmp, file);
6442
- chmodSync3(file, mode);
7037
+ writeFileSync10(tmp, body, { mode });
7038
+ renameSync5(tmp, file);
7039
+ chmodSync4(file, mode);
6443
7040
  return true;
6444
7041
  }
6445
7042
  function installLauncher(o) {
6446
7043
  const dir = dataDir(o.env);
6447
- mkdirSync11(dir, { recursive: true, mode: 493 });
7044
+ mkdirSync12(dir, { recursive: true, mode: 493 });
6448
7045
  let changed = false;
6449
7046
  for (const f of COPIED)
6450
- changed = writeIfChanged(path22.join(dir, f), readFileSync12(path22.join(o.distDir, f)), 420) || changed;
6451
- const launcher = path22.join(dir, LAUNCHER);
6452
- 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;
6453
7050
  return { launcher, changed };
6454
7051
  }
6455
7052
  function removeLauncher(env = process.env) {
6456
- 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 });
6457
7054
  }
6458
7055
  function launcherState(version, env = process.env) {
6459
7056
  const file = launcherPath(env);
6460
7057
  let text = "";
6461
7058
  try {
6462
- text = readFileSync12(file, "utf8");
7059
+ text = readFileSync14(file, "utf8");
6463
7060
  } catch {
6464
7061
  return {
6465
7062
  file,
@@ -6487,7 +7084,7 @@ function launcherState(version, env = process.env) {
6487
7084
  );
6488
7085
  for (const f of COPIED)
6489
7086
  try {
6490
- accessSync3(path22.join(path22.dirname(file), f), constants3.R_OK);
7087
+ accessSync3(path26.join(path26.dirname(file), f), constants3.R_OK);
6491
7088
  } catch {
6492
7089
  problems.push(`${f} is missing beside the hook launcher; run \`sessionpipe install\` again`);
6493
7090
  }
@@ -6514,14 +7111,14 @@ var out = (s = "") => {
6514
7111
  `);
6515
7112
  };
6516
7113
  var state = stateDir();
6517
- var distDir = path23.dirname(fileURLToPath(import.meta.url));
7114
+ var distDir = path27.dirname(fileURLToPath(import.meta.url));
6518
7115
  function hookCommand() {
6519
7116
  if (usesLauncher()) {
6520
7117
  const l = launcherPath();
6521
7118
  return (a) => ({ argv: [l, ...a], command: `${JSON.stringify(l)} ${a.join(" ")}` });
6522
7119
  }
6523
7120
  const node = stableNode(process.execPath);
6524
- let script = path23.join(distDir, "hook.js");
7121
+ let script = path27.join(distDir, "hook.js");
6525
7122
  try {
6526
7123
  script = realpathSync6(script);
6527
7124
  } catch {
@@ -6612,7 +7209,7 @@ async function install() {
6612
7209
  );
6613
7210
  return;
6614
7211
  }
6615
- out(` Machine: ${machineName(cfg, os9.hostname())}`);
7212
+ out(` Machine: ${machineName(cfg, os11.hostname())}`);
6616
7213
  const lean = isLean(cfg);
6617
7214
  if (lean)
6618
7215
  out(
@@ -6674,7 +7271,7 @@ async function connect() {
6674
7271
  const cfg = readConfig();
6675
7272
  const machine = flag("--machine");
6676
7273
  if (machine) cfg.machine = machine.slice(0, 80);
6677
- const name = machineName(cfg, os9.hostname());
7274
+ const name = machineName(cfg, os11.hostname());
6678
7275
  const before = cfg.sinks.find((x) => x.url.replace(/\/$/, "") === url);
6679
7276
  const tier = connectTier(flag("--tier"), before?.tier);
6680
7277
  out(` Connecting ${name} to ${host}.`);
@@ -6696,13 +7293,13 @@ async function connect() {
6696
7293
  const recent = rows.slice().sort((a, b) => b.row.last_at - a.row.last_at).map((r) => r.row.session.cwd ?? "");
6697
7294
  const choice = connectFolders({
6698
7295
  cwd: process.cwd(),
6699
- home: os9.homedir(),
7296
+ home: os11.homedir(),
6700
7297
  flags: argsAll("--folder"),
6701
7298
  existing: control?.folders ?? [],
6702
7299
  recent,
6703
7300
  exists: (d) => {
6704
7301
  try {
6705
- return statSync9(d).isDirectory();
7302
+ return statSync10(d).isDirectory();
6706
7303
  } catch {
6707
7304
  return false;
6708
7305
  }
@@ -6755,7 +7352,7 @@ async function connect() {
6755
7352
  out(` \u2713 Keys: in ${storeLabel(store)}${kept.length ? ` (left in the file: ${kept.join(", ")})` : ""}`);
6756
7353
  } else
6757
7354
  out(
6758
- ` 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)" : ""}`
6759
7356
  );
6760
7357
  if (rows.length) queueBackfill(rows, 30, readConfig());
6761
7358
  out("");
@@ -6853,7 +7450,7 @@ async function sink() {
6853
7450
  if (!s) return out(` no sink named ${args[2]}`);
6854
7451
  const [sk] = buildSinks([s]);
6855
7452
  if (!sk) return out(" sink type not supported yet");
6856
- const session = { id: `test-${Date.now()}`, machine: machineName(cfg, os9.hostname()) };
7453
+ const session = { id: `test-${Date.now()}`, machine: machineName(cfg, os11.hostname()) };
6857
7454
  const mk = (type, seq) => makeEvent(
6858
7455
  { type, harnessEvent: "test", data: {} },
6859
7456
  { harness: { name: "sessionpipe" }, session: { ...session, seq } }
@@ -6936,7 +7533,7 @@ function backfillRows(adapters, days, cfg) {
6936
7533
  function queueBackfill(rows, days, cfg) {
6937
7534
  const outbox = new Outbox(state);
6938
7535
  let n = 0;
6939
- const machine = machineName(cfg, os9.hostname());
7536
+ const machine = machineName(cfg, os11.hostname());
6940
7537
  for (const { harness, row } of rows) {
6941
7538
  const ref = { harness, session: row.session.id };
6942
7539
  const facts = factsState(state).get(harness, row.session.id);
@@ -6978,7 +7575,7 @@ async function forget() {
6978
7575
  { type: "session.forgotten", harnessEvent: "forget", data: {} },
6979
7576
  {
6980
7577
  harness: { name: harness },
6981
- 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()) }
6982
7579
  }
6983
7580
  );
6984
7581
  const sinks = buildSinks(cfg.sinks);
@@ -7003,7 +7600,7 @@ async function replay() {
7003
7600
  function status() {
7004
7601
  const cfg = readConfig();
7005
7602
  const hc = hookCommand();
7006
- out(` machine: ${machineName(cfg, os9.hostname())} sessionpipe ${VERSION} state ${tilde(state)}`);
7603
+ out(` machine: ${machineName(cfg, os11.hostname())} sessionpipe ${VERSION} state ${tilde(state)}`);
7007
7604
  for (const a of ADAPTERS) {
7008
7605
  if (!a.detect() && !cfg.harnesses[a.name]) continue;
7009
7606
  for (const r of a.installed(hc, process.env, { lean: isLean(cfg) }))
@@ -7019,7 +7616,7 @@ function status() {
7019
7616
  for (const ref of outbox.sessions()) {
7020
7617
  const f = outbox.file(ref);
7021
7618
  try {
7022
- 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;
7023
7620
  } catch {
7024
7621
  }
7025
7622
  }
@@ -7041,12 +7638,12 @@ function status() {
7041
7638
  }
7042
7639
  }
7043
7640
  function hookWall(runs = 8) {
7044
- const hook = path23.join(distDir, "hook.js");
7045
- 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-"));
7046
7643
  const env = {
7047
7644
  ...process.env,
7048
7645
  SESSIONPIPE_STATE: tmp,
7049
- SESSIONPIPE_CONFIG: path23.join(tmp, "config.json"),
7646
+ SESSIONPIPE_CONFIG: path27.join(tmp, "config.json"),
7050
7647
  SESSIONPIPE_NO_WORKER: "1"
7051
7648
  };
7052
7649
  const input = JSON.stringify({
@@ -7074,7 +7671,7 @@ function hookWall(runs = 8) {
7074
7671
  }
7075
7672
  function timing() {
7076
7673
  try {
7077
- 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);
7078
7675
  const ms = lines.map((l) => JSON.parse(l).ms).filter((x) => x >= 0).sort((a, b) => a - b);
7079
7676
  if (!ms.length) return null;
7080
7677
  const q = (p) => Math.round(ms[Math.min(ms.length - 1, Math.floor(p * ms.length))] ?? 0);
@@ -7090,7 +7687,7 @@ async function doctor() {
7090
7687
  version: VERSION,
7091
7688
  node: process.version,
7092
7689
  node_path: stableNode(process.execPath),
7093
- machine: machineName(cfg, os9.hostname()),
7690
+ machine: machineName(cfg, os11.hostname()),
7094
7691
  state,
7095
7692
  config: configFile(),
7096
7693
  harnesses: {},
@@ -7138,7 +7735,7 @@ async function doctor() {
7138
7735
  let linger = null;
7139
7736
  try {
7140
7737
  linger = /^Linger=yes/m.test(
7141
- execFileSync7("loginctl", ["show-user", os9.userInfo().username, "--property=Linger"], {
7738
+ execFileSync7("loginctl", ["show-user", os11.userInfo().username, "--property=Linger"], {
7142
7739
  encoding: "utf8",
7143
7740
  timeout: 5e3,
7144
7741
  stdio: ["ignore", "pipe", "ignore"]
@@ -7149,7 +7746,7 @@ async function doctor() {
7149
7746
  report.linger = linger;
7150
7747
  if (linger === false)
7151
7748
  warnings.push(
7152
- `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`
7153
7750
  );
7154
7751
  }
7155
7752
  if (/\/Cellar\/node(?:@\d+)?\/[^/]+\//.test(distDir))
@@ -7272,7 +7869,7 @@ async function tail() {
7272
7869
  out(`sessionpipe tail \xB7 tier ${tier} \xB7 ${tilde(state)}/outbox (Ctrl-C to stop)`);
7273
7870
  for (const ref of outbox.sessions()) {
7274
7871
  try {
7275
- 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);
7276
7873
  } catch {
7277
7874
  }
7278
7875
  }
@@ -7295,7 +7892,7 @@ async function tail() {
7295
7892
  async function workerFromArgv() {
7296
7893
  const f = args[1];
7297
7894
  if (!f) return;
7298
- const job = JSON.parse(readFileSync13(f, "utf8"));
7895
+ const job = JSON.parse(readFileSync15(f, "utf8"));
7299
7896
  try {
7300
7897
  unlinkSync7(f);
7301
7898
  } catch {