sessionpipe-core 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/control.js +1 -2
- package/dist/control.js.map +2 -2
- package/dist/index.js +1313 -993
- package/dist/index.js.map +4 -4
- package/dist/schema.js +1 -1
- package/dist/src/adapters/antigravity-control.d.ts +58 -0
- package/dist/src/adapters/antigravity-control.d.ts.map +1 -0
- package/dist/src/adapters/claude-code-control.d.ts +5 -26
- package/dist/src/adapters/claude-code-control.d.ts.map +1 -1
- package/dist/src/adapters/codex-control.d.ts +62 -0
- package/dist/src/adapters/codex-control.d.ts.map +1 -0
- package/dist/src/adapters/codex.d.ts +4 -0
- package/dist/src/adapters/codex.d.ts.map +1 -1
- package/dist/src/adapters/headless.d.ts +70 -0
- package/dist/src/adapters/headless.d.ts.map +1 -0
- package/dist/src/adapters/index.d.ts +6 -0
- package/dist/src/adapters/index.d.ts.map +1 -1
- package/dist/src/control/verify.d.ts.map +1 -1
- package/dist/src/readers/transcripts.d.ts +4 -0
- package/dist/src/readers/transcripts.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// sessionpipe-core 0.
|
|
1
|
+
// sessionpipe-core 0.7.0 · Apache-2.0 · https://sessionpipe.org
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __export = (target, all) => {
|
|
4
4
|
for (var name in all)
|
|
@@ -191,6 +191,9 @@ function readClaude(lines, from) {
|
|
|
191
191
|
}
|
|
192
192
|
return pairTurns(msgs, endOf(lines));
|
|
193
193
|
}
|
|
194
|
+
function codexInjected(text) {
|
|
195
|
+
return /^<[a-z_-]+>/i.test(text) || /^# AGENTS\.md instructions\b/i.test(text);
|
|
196
|
+
}
|
|
194
197
|
function readCodex(lines, from) {
|
|
195
198
|
const msgs = [];
|
|
196
199
|
for (let i = from; i < lines.length; i++) {
|
|
@@ -202,7 +205,7 @@ function readCodex(lines, from) {
|
|
|
202
205
|
const text = textParts(p.content, ["input_text", "output_text"]).trim();
|
|
203
206
|
if (!text) continue;
|
|
204
207
|
if (p.role === "user") {
|
|
205
|
-
if (
|
|
208
|
+
if (codexInjected(text)) continue;
|
|
206
209
|
msgs.push({ role: "user", text, at, line: i });
|
|
207
210
|
} else if (p.role === "assistant") msgs.push({ role: "assistant", text, at, line: i });
|
|
208
211
|
}
|
|
@@ -649,11 +652,288 @@ function antigravityFacts(file, opts) {
|
|
|
649
652
|
return out;
|
|
650
653
|
}
|
|
651
654
|
|
|
655
|
+
// src/adapters/antigravity-control.ts
|
|
656
|
+
var antigravity_control_exports = {};
|
|
657
|
+
__export(antigravity_control_exports, {
|
|
658
|
+
AgyReading: () => AgyReading,
|
|
659
|
+
agyArgs: () => agyArgs,
|
|
660
|
+
agyEnv: () => agyEnv,
|
|
661
|
+
agyError: () => agyError,
|
|
662
|
+
agyLine: () => agyLine,
|
|
663
|
+
agyModeFlags: () => agyModeFlags,
|
|
664
|
+
findAgy: () => findAgy,
|
|
665
|
+
reading: () => reading
|
|
666
|
+
});
|
|
667
|
+
import os2 from "node:os";
|
|
668
|
+
import path6 from "node:path";
|
|
669
|
+
|
|
670
|
+
// src/adapters/headless.ts
|
|
671
|
+
import { spawn } from "node:child_process";
|
|
672
|
+
import { accessSync, constants, statSync as statSync3 } from "node:fs";
|
|
673
|
+
import path5 from "node:path";
|
|
674
|
+
var JOB_TIMEOUT_MS = 30 * 6e4;
|
|
675
|
+
var OUT_CAP = 1024 * 1024;
|
|
676
|
+
function isExe(p) {
|
|
677
|
+
try {
|
|
678
|
+
if (!statSync3(p).isFile()) return false;
|
|
679
|
+
accessSync(p, constants.X_OK);
|
|
680
|
+
return true;
|
|
681
|
+
} catch {
|
|
682
|
+
return false;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
function findBinary(o) {
|
|
686
|
+
if (o.override && isExe(o.override)) return o.override;
|
|
687
|
+
for (const d of String(o.env?.PATH || "").split(path5.delimiter)) {
|
|
688
|
+
if (!d || !path5.isAbsolute(d)) continue;
|
|
689
|
+
for (const n of o.names) if (isExe(path5.join(d, n))) return path5.join(d, n);
|
|
690
|
+
}
|
|
691
|
+
for (const d of o.dirs) for (const n of o.names) if (isExe(path5.join(d, n))) return path5.join(d, n);
|
|
692
|
+
return null;
|
|
693
|
+
}
|
|
694
|
+
function jobEnv(env = process.env) {
|
|
695
|
+
return { ...env, SESSIONPIPE_CONTROL_JOB: "1" };
|
|
696
|
+
}
|
|
697
|
+
function runHeadless(bin, args, o) {
|
|
698
|
+
return new Promise((resolve) => {
|
|
699
|
+
let out = [];
|
|
700
|
+
let outLen = 0;
|
|
701
|
+
let tooBig = false;
|
|
702
|
+
let errTail = "";
|
|
703
|
+
let timedOut = false;
|
|
704
|
+
let settled = false;
|
|
705
|
+
let child;
|
|
706
|
+
try {
|
|
707
|
+
child = spawn(bin, args, {
|
|
708
|
+
cwd: o.cwd,
|
|
709
|
+
env: o.env,
|
|
710
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
711
|
+
detached: process.platform !== "win32",
|
|
712
|
+
windowsHide: true
|
|
713
|
+
});
|
|
714
|
+
} catch (e) {
|
|
715
|
+
resolve({ error: e.message });
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
const kill = (sig) => {
|
|
719
|
+
try {
|
|
720
|
+
if (process.platform !== "win32" && child.pid) process.kill(-child.pid, sig);
|
|
721
|
+
else child.kill(sig);
|
|
722
|
+
} catch {
|
|
723
|
+
try {
|
|
724
|
+
child.kill(sig);
|
|
725
|
+
} catch {
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
};
|
|
729
|
+
const keep = (line) => {
|
|
730
|
+
if (!o.keep?.(line)) return;
|
|
731
|
+
out = [Buffer.from(line)];
|
|
732
|
+
outLen = line.length;
|
|
733
|
+
};
|
|
734
|
+
let partial = "";
|
|
735
|
+
child.stdout?.on("data", (b) => {
|
|
736
|
+
if (o.onLine) {
|
|
737
|
+
const chunk = partial + b.toString("utf8");
|
|
738
|
+
const lines = chunk.split("\n");
|
|
739
|
+
partial = lines.pop() ?? "";
|
|
740
|
+
for (const line of lines) {
|
|
741
|
+
if (!line.trim()) continue;
|
|
742
|
+
try {
|
|
743
|
+
o.onLine(line);
|
|
744
|
+
} catch {
|
|
745
|
+
}
|
|
746
|
+
keep(line);
|
|
747
|
+
}
|
|
748
|
+
if (partial.length > OUT_CAP) partial = "";
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
if (tooBig) return;
|
|
752
|
+
if (outLen + b.length > OUT_CAP) {
|
|
753
|
+
tooBig = true;
|
|
754
|
+
out = [];
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
out.push(b);
|
|
758
|
+
outLen += b.length;
|
|
759
|
+
});
|
|
760
|
+
child.stderr?.on("data", (b) => {
|
|
761
|
+
errTail = (errTail + b.toString("utf8")).slice(-4e3);
|
|
762
|
+
});
|
|
763
|
+
const timer = setTimeout(() => {
|
|
764
|
+
timedOut = true;
|
|
765
|
+
kill("SIGTERM");
|
|
766
|
+
setTimeout(() => kill("SIGKILL"), 1e4).unref();
|
|
767
|
+
}, o.timeoutMs ?? JOB_TIMEOUT_MS);
|
|
768
|
+
const result = (code, signal) => ({
|
|
769
|
+
code,
|
|
770
|
+
signal,
|
|
771
|
+
stdout: tooBig ? null : Buffer.concat(out).toString("utf8"),
|
|
772
|
+
tooBig,
|
|
773
|
+
stderr: errTail,
|
|
774
|
+
timedOut
|
|
775
|
+
});
|
|
776
|
+
const finish = (r) => {
|
|
777
|
+
if (settled) return;
|
|
778
|
+
settled = true;
|
|
779
|
+
clearTimeout(timer);
|
|
780
|
+
resolve(r);
|
|
781
|
+
};
|
|
782
|
+
child.on("error", (e) => finish({ error: e.message }));
|
|
783
|
+
child.on("exit", (code, signal) => setTimeout(() => finish(result(code, signal)), 2e3).unref());
|
|
784
|
+
child.on("close", (code, signal) => {
|
|
785
|
+
if (o.onLine && partial.trim()) {
|
|
786
|
+
try {
|
|
787
|
+
o.onLine(partial);
|
|
788
|
+
} catch {
|
|
789
|
+
}
|
|
790
|
+
keep(partial);
|
|
791
|
+
partial = "";
|
|
792
|
+
}
|
|
793
|
+
finish(result(code, signal));
|
|
794
|
+
});
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
function stderrTail(stderr, n = 3) {
|
|
798
|
+
return String(stderr || "").trim().split("\n").slice(-n).join(" ").trim();
|
|
799
|
+
}
|
|
800
|
+
function deniedNote(names, count) {
|
|
801
|
+
if (!count) return "";
|
|
802
|
+
return `
|
|
803
|
+
|
|
804
|
+
(Not allowed on this machine, since nobody was there to approve: ${names.join(", ") || `${count} action(s)`}.)`;
|
|
805
|
+
}
|
|
806
|
+
function runFailure(label, res) {
|
|
807
|
+
if (res.error) return `couldn't start ${label}: ${res.error}`;
|
|
808
|
+
if (res.timedOut) return `${label} was still working after 30 minutes, so it was stopped`;
|
|
809
|
+
return null;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// src/adapters/antigravity-control.ts
|
|
813
|
+
var LABEL = "Antigravity";
|
|
814
|
+
function findAgy(env = process.env, home = os2.homedir()) {
|
|
815
|
+
return findBinary({
|
|
816
|
+
override: env.SESSIONPIPE_AGY,
|
|
817
|
+
names: process.platform === "win32" ? ["agy.exe", "agy.cmd"] : ["agy"],
|
|
818
|
+
env,
|
|
819
|
+
dirs: [path6.join(home, ".local", "bin"), "/opt/homebrew/bin", "/usr/local/bin"]
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
function agyModeFlags(mode) {
|
|
823
|
+
return mode === "auto" ? ["--mode", "accept-edits"] : [];
|
|
824
|
+
}
|
|
825
|
+
function agyArgs(o) {
|
|
826
|
+
const prompt = o.prompt.startsWith("-") ? ` ${o.prompt}` : o.prompt;
|
|
827
|
+
return [
|
|
828
|
+
...o.resume ? ["--conversation", o.resume] : [],
|
|
829
|
+
"-p",
|
|
830
|
+
prompt,
|
|
831
|
+
"--output-format",
|
|
832
|
+
"stream-json",
|
|
833
|
+
...agyModeFlags(o.mode)
|
|
834
|
+
];
|
|
835
|
+
}
|
|
836
|
+
var agyEnv = (env = process.env) => jobEnv(env);
|
|
837
|
+
function agyLine(line) {
|
|
838
|
+
let j;
|
|
839
|
+
try {
|
|
840
|
+
j = JSON.parse(line);
|
|
841
|
+
} catch {
|
|
842
|
+
return null;
|
|
843
|
+
}
|
|
844
|
+
if (!j || typeof j !== "object") return null;
|
|
845
|
+
const str = (v) => typeof v === "string" ? v : "";
|
|
846
|
+
const out = {};
|
|
847
|
+
if (j.event === "init") {
|
|
848
|
+
if (str(j.conversation_id)) out.session = str(j.conversation_id);
|
|
849
|
+
} else if (j.event === "step_update") {
|
|
850
|
+
const s = j.step_update ?? {};
|
|
851
|
+
if (str(s.conversation_id)) out.session = str(s.conversation_id);
|
|
852
|
+
if (s.step_type === "agent_response" && str(s.text_delta))
|
|
853
|
+
out.delta = { step: Number(s.step_index ?? 0), text: str(s.text_delta) };
|
|
854
|
+
if (s.step_type === "error_message") {
|
|
855
|
+
const why = str(s.text_delta) || str(s.error) || str(s.message);
|
|
856
|
+
if (why) out.note = why;
|
|
857
|
+
}
|
|
858
|
+
} else if (j.event === "result") {
|
|
859
|
+
const r = j.result ?? {};
|
|
860
|
+
if (str(r.conversation_id)) out.session = str(r.conversation_id);
|
|
861
|
+
out.result = {
|
|
862
|
+
status: str(r.status),
|
|
863
|
+
response: str(r.response),
|
|
864
|
+
error: str(r.error),
|
|
865
|
+
denied: Array.isArray(r.denied_actions) ? r.denied_actions : []
|
|
866
|
+
};
|
|
867
|
+
} else return null;
|
|
868
|
+
return Object.keys(out).length ? out : null;
|
|
869
|
+
}
|
|
870
|
+
function deniedName(d) {
|
|
871
|
+
if (typeof d === "string") return d.slice(0, 80);
|
|
872
|
+
if (!d || typeof d !== "object") return null;
|
|
873
|
+
const o = d;
|
|
874
|
+
for (const k of ["tool_name", "toolName", "name", "tool", "action", "type"])
|
|
875
|
+
if (typeof o[k] === "string" && o[k]) return o[k].slice(0, 80);
|
|
876
|
+
return null;
|
|
877
|
+
}
|
|
878
|
+
function agyError(stderr) {
|
|
879
|
+
const s = String(stderr || "");
|
|
880
|
+
const i = s.lastIndexOf("AGY_ERROR:");
|
|
881
|
+
if (i < 0) return "";
|
|
882
|
+
const raw = s.slice(i + "AGY_ERROR:".length).trim();
|
|
883
|
+
try {
|
|
884
|
+
const j = JSON.parse(raw);
|
|
885
|
+
for (const k of ["message", "error", "detail"]) if (typeof j[k] === "string" && j[k]) return j[k];
|
|
886
|
+
} catch {
|
|
887
|
+
}
|
|
888
|
+
return raw.split("\n")[0]?.slice(0, 300) ?? "";
|
|
889
|
+
}
|
|
890
|
+
var AgyReading = class {
|
|
891
|
+
session = null;
|
|
892
|
+
lines = 0;
|
|
893
|
+
buf = "";
|
|
894
|
+
step = null;
|
|
895
|
+
result = null;
|
|
896
|
+
note = null;
|
|
897
|
+
get text() {
|
|
898
|
+
return this.buf;
|
|
899
|
+
}
|
|
900
|
+
feed(line) {
|
|
901
|
+
this.lines++;
|
|
902
|
+
const p = agyLine(line);
|
|
903
|
+
if (!p) return false;
|
|
904
|
+
if (p.session && !this.session) this.session = p.session;
|
|
905
|
+
if (p.note) this.note = p.note;
|
|
906
|
+
if (p.result) this.result = p.result;
|
|
907
|
+
if (!p.delta) return false;
|
|
908
|
+
if (this.step !== null && p.delta.step !== this.step && this.buf && !this.buf.endsWith("\n\n")) this.buf += "\n\n";
|
|
909
|
+
this.step = p.delta.step;
|
|
910
|
+
this.buf += p.delta.text;
|
|
911
|
+
return true;
|
|
912
|
+
}
|
|
913
|
+
outcome(res) {
|
|
914
|
+
const broke = runFailure(LABEL, res);
|
|
915
|
+
if (broke) return { failed: broke };
|
|
916
|
+
const r = this.result;
|
|
917
|
+
if (!r) {
|
|
918
|
+
const why = agyError(res.stderr) || this.note || "";
|
|
919
|
+
return {
|
|
920
|
+
failed: `Antigravity exited (${res.signal || `code ${res.code}`}) without an answer${why ? `: ${why}` : ""}`
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
if (r.status !== "SUCCESS")
|
|
924
|
+
return { failed: r.error || agyError(res.stderr) || this.note || "Antigravity reported an error" };
|
|
925
|
+
const names = [...new Set(r.denied.map(deniedName).filter((n) => !!n))];
|
|
926
|
+
const text = r.response.trim() ? r.response.trim() : this.buf.trim() || "(Antigravity finished without a written reply.)";
|
|
927
|
+
return { reply: text + deniedNote(names, r.denied.length) };
|
|
928
|
+
}
|
|
929
|
+
};
|
|
930
|
+
var reading = () => new AgyReading();
|
|
931
|
+
|
|
652
932
|
// src/adapters/claude-code.ts
|
|
653
933
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
654
934
|
import { createHash } from "node:crypto";
|
|
655
|
-
import { existsSync as existsSync3, readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as
|
|
656
|
-
import
|
|
935
|
+
import { existsSync as existsSync3, readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as statSync4 } from "node:fs";
|
|
936
|
+
import path7 from "node:path";
|
|
657
937
|
|
|
658
938
|
// src/readers/git.ts
|
|
659
939
|
import { execFileSync } from "node:child_process";
|
|
@@ -720,23 +1000,23 @@ function claudeDirs(env = process.env, home = env.HOME || HOME) {
|
|
|
720
1000
|
const out = [];
|
|
721
1001
|
const add = (d) => {
|
|
722
1002
|
if (!d) return;
|
|
723
|
-
const r =
|
|
1003
|
+
const r = path7.resolve(d);
|
|
724
1004
|
if (!out.includes(r)) out.push(r);
|
|
725
1005
|
};
|
|
726
|
-
add(
|
|
1006
|
+
add(path7.join(home, ".claude"));
|
|
727
1007
|
add(env.CLAUDE_CONFIG_DIR);
|
|
728
1008
|
try {
|
|
729
1009
|
for (const e of readdirSync3(home, { withFileTypes: true })) {
|
|
730
1010
|
if (!e.isDirectory() || !/^\.claude[-_.]/.test(e.name)) continue;
|
|
731
|
-
const d =
|
|
732
|
-
if (existsSync3(
|
|
1011
|
+
const d = path7.join(home, e.name);
|
|
1012
|
+
if (existsSync3(path7.join(d, "projects")) || existsSync3(path7.join(d, "settings.json"))) add(d);
|
|
733
1013
|
}
|
|
734
1014
|
} catch {
|
|
735
1015
|
}
|
|
736
1016
|
return out.filter((d, i) => i === 0 || existsSync3(d));
|
|
737
1017
|
}
|
|
738
|
-
var settingsIn = (dir) =>
|
|
739
|
-
var configDirOf = (transcript) =>
|
|
1018
|
+
var settingsIn = (dir) => path7.join(dir, "settings.json");
|
|
1019
|
+
var configDirOf = (transcript) => path7.dirname(path7.dirname(path7.dirname(transcript)));
|
|
740
1020
|
var validTool2 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
741
1021
|
var endReason = (r) => r === "clear" || r === "logout" || r === "exit" ? r : r === "prompt_input_exit" ? "exit" : "other";
|
|
742
1022
|
var notifKind = (t) => t === "permission_prompt" ? "permission" : t === "idle_prompt" ? "idle" : t === "elicitation_dialog" ? "elicitation" : "question";
|
|
@@ -908,11 +1188,11 @@ var claudeCode = {
|
|
|
908
1188
|
const rows = [];
|
|
909
1189
|
const files = [];
|
|
910
1190
|
for (const d of claudeDirs(env))
|
|
911
|
-
files.push(...recentFiles(
|
|
1191
|
+
files.push(...recentFiles(path7.join(d, "projects"), 1, sinceMs, (n) => /^[0-9a-f-]{36}\.jsonl$/.test(n)));
|
|
912
1192
|
const noState = { get: () => ({}), save: () => {
|
|
913
1193
|
} };
|
|
914
1194
|
for (const f of files) {
|
|
915
|
-
const id =
|
|
1195
|
+
const id = path7.basename(f, ".jsonl");
|
|
916
1196
|
const facts = claudeFacts(f, id, {}, noState);
|
|
917
1197
|
if (!facts.started_at || !facts.last_at) continue;
|
|
918
1198
|
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
@@ -972,12 +1252,12 @@ function claudeLive(dir) {
|
|
|
972
1252
|
const map = /* @__PURE__ */ new Map();
|
|
973
1253
|
let names = [];
|
|
974
1254
|
try {
|
|
975
|
-
names = readdirSync3(
|
|
1255
|
+
names = readdirSync3(path7.join(dir, "sessions")).filter((n) => n.endsWith(".json"));
|
|
976
1256
|
} catch {
|
|
977
1257
|
}
|
|
978
1258
|
for (const n of names) {
|
|
979
1259
|
try {
|
|
980
|
-
const e = registryEntry(JSON.parse(readFileSync3(
|
|
1260
|
+
const e = registryEntry(JSON.parse(readFileSync3(path7.join(dir, "sessions", n), "utf8")));
|
|
981
1261
|
if (e?.sessionId) map.set(e.sessionId, e);
|
|
982
1262
|
} catch {
|
|
983
1263
|
}
|
|
@@ -988,7 +1268,7 @@ function claudeLive(dir) {
|
|
|
988
1268
|
function claudeLiveByPid(dir, pid) {
|
|
989
1269
|
if (!pid) return null;
|
|
990
1270
|
try {
|
|
991
|
-
return registryEntry(JSON.parse(readFileSync3(
|
|
1271
|
+
return registryEntry(JSON.parse(readFileSync3(path7.join(dir, "sessions", `${pid}.json`), "utf8")));
|
|
992
1272
|
} catch {
|
|
993
1273
|
return null;
|
|
994
1274
|
}
|
|
@@ -996,7 +1276,7 @@ function claudeLiveByPid(dir, pid) {
|
|
|
996
1276
|
function claudePid(env = process.env) {
|
|
997
1277
|
try {
|
|
998
1278
|
const dirs = claudeDirs(env);
|
|
999
|
-
const has = (pid2) => dirs.some((d) => existsSync3(
|
|
1279
|
+
const has = (pid2) => dirs.some((d) => existsSync3(path7.join(d, "sessions", `${pid2}.json`)));
|
|
1000
1280
|
let pid = process.ppid;
|
|
1001
1281
|
if (has(pid)) return pid;
|
|
1002
1282
|
const parent = parentOf();
|
|
@@ -1039,8 +1319,8 @@ var accountCache = /* @__PURE__ */ new Map();
|
|
|
1039
1319
|
function claudeAccount(dir) {
|
|
1040
1320
|
const hit = accountCache.get(dir);
|
|
1041
1321
|
if (hit !== void 0) return hit;
|
|
1042
|
-
const files = [
|
|
1043
|
-
if (
|
|
1322
|
+
const files = [path7.join(dir, ".claude.json")];
|
|
1323
|
+
if (path7.resolve(dir) === path7.join(HOME, ".claude")) files.unshift(path7.join(HOME, ".claude.json"));
|
|
1044
1324
|
let acct = null;
|
|
1045
1325
|
for (const f of files) {
|
|
1046
1326
|
try {
|
|
@@ -1092,7 +1372,7 @@ function scanTitles(file, from, found) {
|
|
|
1092
1372
|
const out = { custom: found.custom ?? null, ai: found.ai ?? null, upto: from };
|
|
1093
1373
|
let size = 0;
|
|
1094
1374
|
try {
|
|
1095
|
-
size =
|
|
1375
|
+
size = statSync4(file).size;
|
|
1096
1376
|
} catch {
|
|
1097
1377
|
return out;
|
|
1098
1378
|
}
|
|
@@ -1125,7 +1405,7 @@ function claudeFacts(file, sessionId, hints, state) {
|
|
|
1125
1405
|
const H = parseLines(head);
|
|
1126
1406
|
const T = parseLines(tail);
|
|
1127
1407
|
const out = {};
|
|
1128
|
-
const id = sessionId ||
|
|
1408
|
+
const id = sessionId || path7.basename(file, ".jsonl");
|
|
1129
1409
|
const mem = state.get(NAME2, id);
|
|
1130
1410
|
let custom = null;
|
|
1131
1411
|
let ai = null;
|
|
@@ -1135,7 +1415,7 @@ function claudeFacts(file, sessionId, hints, state) {
|
|
|
1135
1415
|
}
|
|
1136
1416
|
let size = 0;
|
|
1137
1417
|
try {
|
|
1138
|
-
size =
|
|
1418
|
+
size = statSync4(file).size;
|
|
1139
1419
|
} catch {
|
|
1140
1420
|
}
|
|
1141
1421
|
if (!custom && !ai && size > 64 * 1024 + 256 * 1024) {
|
|
@@ -1205,548 +1485,644 @@ function claudeFacts(file, sessionId, hints, state) {
|
|
|
1205
1485
|
return out;
|
|
1206
1486
|
}
|
|
1207
1487
|
|
|
1208
|
-
// src/adapters/
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
if (
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
}
|
|
1249
|
-
function firstTableAt(text) {
|
|
1250
|
-
const st = { depth: 0, str: null };
|
|
1251
|
-
let pos = 0;
|
|
1252
|
-
for (const line of text.split("\n")) {
|
|
1253
|
-
if (!st.str && st.depth === 0 && /^[ \t]*\[/.test(line)) return pos;
|
|
1254
|
-
scan(line, st);
|
|
1255
|
-
pos += line.length + 1;
|
|
1488
|
+
// src/adapters/claude-code-control.ts
|
|
1489
|
+
var claude_code_control_exports = {};
|
|
1490
|
+
__export(claude_code_control_exports, {
|
|
1491
|
+
JOB_TIMEOUT_MS: () => JOB_TIMEOUT_MS,
|
|
1492
|
+
claudeEnv: () => claudeEnv,
|
|
1493
|
+
claudeLogin: () => claudeLogin,
|
|
1494
|
+
detectCaps: () => detectCaps,
|
|
1495
|
+
findClaude: () => findClaude,
|
|
1496
|
+
findTranscript: () => findTranscript,
|
|
1497
|
+
isUuid: () => isUuid,
|
|
1498
|
+
liveProcess: () => liveProcess,
|
|
1499
|
+
loginCommand: () => loginCommand,
|
|
1500
|
+
loginHint: () => loginHint,
|
|
1501
|
+
modeFlags: () => modeFlags,
|
|
1502
|
+
parseAuthStatus: () => parseAuthStatus,
|
|
1503
|
+
parseCaps: () => parseCaps,
|
|
1504
|
+
parseResult: () => parseResult,
|
|
1505
|
+
recentlyWritten: () => recentlyWritten,
|
|
1506
|
+
runClaude: () => runClaude,
|
|
1507
|
+
sessionFolder: () => sessionFolder
|
|
1508
|
+
});
|
|
1509
|
+
import { execFile, execFileSync as execFileSync3 } from "node:child_process";
|
|
1510
|
+
import { closeSync as closeSync2, openSync as openSync2, readdirSync as readdirSync4, readFileSync as readFileSync4, readSync as readSync2, statSync as statSync5 } from "node:fs";
|
|
1511
|
+
import os3 from "node:os";
|
|
1512
|
+
import path8 from "node:path";
|
|
1513
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1514
|
+
var LIVE_WRITE_MS = 9e4;
|
|
1515
|
+
var cmpVersion = (a, b) => {
|
|
1516
|
+
const pa = a.split(".").map(Number);
|
|
1517
|
+
const pb = b.split(".").map(Number);
|
|
1518
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++)
|
|
1519
|
+
if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
1520
|
+
return 0;
|
|
1521
|
+
};
|
|
1522
|
+
function findClaude(env = process.env, home = os3.homedir()) {
|
|
1523
|
+
if (env.SESSIONPIPE_CLAUDE && isExe(env.SESSIONPIPE_CLAUDE)) return env.SESSIONPIPE_CLAUDE;
|
|
1524
|
+
const names = process.platform === "win32" ? ["claude.exe", "claude.cmd"] : ["claude"];
|
|
1525
|
+
for (const d of String(env.PATH || "").split(path8.delimiter)) {
|
|
1526
|
+
if (!d || !path8.isAbsolute(d)) continue;
|
|
1527
|
+
for (const n of names) if (isExe(path8.join(d, n))) return path8.join(d, n);
|
|
1256
1528
|
}
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1529
|
+
for (const p of [
|
|
1530
|
+
path8.join(home, ".claude", "local", "claude"),
|
|
1531
|
+
path8.join(home, ".local", "bin", "claude"),
|
|
1532
|
+
"/opt/homebrew/bin/claude",
|
|
1533
|
+
"/usr/local/bin/claude"
|
|
1534
|
+
])
|
|
1535
|
+
if (isExe(p)) return p;
|
|
1536
|
+
const app = path8.join(home, "Library", "Application Support", "Claude", "claude-code");
|
|
1537
|
+
let versions = [];
|
|
1264
1538
|
try {
|
|
1265
|
-
|
|
1266
|
-
return Array.isArray(a) && a.every((x) => typeof x === "string") ? a : null;
|
|
1539
|
+
versions = readdirSync4(app).filter((n) => /^\d+(\.\d+)*$/.test(n)).sort(cmpVersion).reverse();
|
|
1267
1540
|
} catch {
|
|
1268
|
-
return null;
|
|
1269
1541
|
}
|
|
1542
|
+
for (const v of versions) {
|
|
1543
|
+
const p = path8.join(app, v, "claude.app", "Contents", "MacOS", "claude");
|
|
1544
|
+
if (isExe(p)) return p;
|
|
1545
|
+
}
|
|
1546
|
+
return null;
|
|
1270
1547
|
}
|
|
1271
|
-
function
|
|
1272
|
-
const
|
|
1273
|
-
const
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
misplaced: false,
|
|
1281
|
-
other: !OURS2.test(m[1] ?? "")
|
|
1282
|
-
};
|
|
1283
|
-
const k = NOTIFY_KEY.exec(head);
|
|
1284
|
-
if (k) {
|
|
1285
|
-
const rest = head.slice(k.index).split("\n")[0] ?? "";
|
|
1286
|
-
return {
|
|
1287
|
-
raw: rest.replace(NOTIFY_KEY, "").trim(),
|
|
1288
|
-
argv: null,
|
|
1289
|
-
index: k.index,
|
|
1290
|
-
length: 0,
|
|
1291
|
-
misplaced: false,
|
|
1292
|
-
other: true
|
|
1293
|
-
};
|
|
1294
|
-
}
|
|
1295
|
-
const t = NOTIFY_LINE.exec(tail);
|
|
1296
|
-
if (t && OURS2.test(t[1] ?? ""))
|
|
1297
|
-
return {
|
|
1298
|
-
raw: t[1] ?? "",
|
|
1299
|
-
argv: parseArgv(t[1] ?? ""),
|
|
1300
|
-
index: at + t.index,
|
|
1301
|
-
length: t[0].length,
|
|
1302
|
-
misplaced: true,
|
|
1303
|
-
other: false
|
|
1304
|
-
};
|
|
1305
|
-
return null;
|
|
1306
|
-
}
|
|
1307
|
-
function dropLine(text, index, length) {
|
|
1308
|
-
return text.slice(0, index) + text.slice(index + length).replace(/^\r?\n/, "");
|
|
1309
|
-
}
|
|
1310
|
-
function insertTopLevel(text, line) {
|
|
1311
|
-
const { head, tail } = split(text);
|
|
1312
|
-
if (!tail) return (head.trim() ? head.replace(/\s*$/, "\n") : "") + line + "\n";
|
|
1313
|
-
return `${(head.trim() ? head.replace(/\s*$/, "\n") : "") + line}
|
|
1314
|
-
|
|
1315
|
-
${tail}`;
|
|
1316
|
-
}
|
|
1317
|
-
function withNotify(text, line) {
|
|
1318
|
-
const n = readNotify(text);
|
|
1319
|
-
if (n && !n.misplaced && !n.other) return text.slice(0, n.index) + line + text.slice(n.index + n.length);
|
|
1320
|
-
if (n?.misplaced) return insertTopLevel(dropLine(text, n.index, n.length), line);
|
|
1321
|
-
return insertTopLevel(text, line);
|
|
1322
|
-
}
|
|
1323
|
-
function replaceNotify(text, line) {
|
|
1324
|
-
const n = readNotify(text);
|
|
1325
|
-
if (!n || n.misplaced || !n.length) return null;
|
|
1326
|
-
return text.slice(0, n.index) + line + text.slice(n.index + n.length);
|
|
1327
|
-
}
|
|
1328
|
-
function withoutNotify(text) {
|
|
1329
|
-
const n = readNotify(text);
|
|
1330
|
-
if (!n || n.other || !n.length) return text;
|
|
1331
|
-
return dropLine(text, n.index, n.length);
|
|
1548
|
+
function optionBlock(help, flag) {
|
|
1549
|
+
const lines = help.split("\n");
|
|
1550
|
+
const re = new RegExp(`^\\s{0,4}(?:-\\w, )?${flag.replace(/-/g, "\\-")}(?![\\w-])`);
|
|
1551
|
+
const start = lines.findIndex((l) => re.test(l));
|
|
1552
|
+
if (start < 0) return null;
|
|
1553
|
+
const out = [lines[start]];
|
|
1554
|
+
for (let i = start + 1; i < lines.length && lines[i]?.trim() && !/^\s{0,4}-/.test(lines[i]); i++)
|
|
1555
|
+
out.push(lines[i]);
|
|
1556
|
+
return out.join(" ");
|
|
1332
1557
|
}
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1558
|
+
function parseCaps(help) {
|
|
1559
|
+
const pm = optionBlock(help, "--permission-mode");
|
|
1560
|
+
const pp = optionBlock(help, "--permission-prompts");
|
|
1561
|
+
return {
|
|
1562
|
+
hasMode: !!pm,
|
|
1563
|
+
modes: pm ? [...pm.matchAll(/"([A-Za-z]+)"/g)].map((m) => m[1]) : [],
|
|
1564
|
+
promptsNone: !!pp && /"none"/.test(pp),
|
|
1565
|
+
fork: !!optionBlock(help, "--fork-session"),
|
|
1566
|
+
stream: !!optionBlock(help, "--include-partial-messages")
|
|
1567
|
+
};
|
|
1338
1568
|
}
|
|
1339
|
-
function
|
|
1340
|
-
|
|
1569
|
+
function modeFlags(mode, caps) {
|
|
1570
|
+
if (mode === "auto") {
|
|
1571
|
+
if (!caps.modes.includes("auto"))
|
|
1572
|
+
return { error: "this machine is in auto mode, but its Claude Code has no auto permission mode" };
|
|
1573
|
+
return { args: ["--permission-mode", "auto"] };
|
|
1574
|
+
}
|
|
1575
|
+
if (caps.modes.includes("dontAsk")) return { args: ["--permission-mode", "dontAsk"] };
|
|
1576
|
+
const args = [];
|
|
1577
|
+
if (caps.hasMode)
|
|
1578
|
+
args.push(
|
|
1579
|
+
"--permission-mode",
|
|
1580
|
+
caps.modes.includes("manual") && !caps.modes.includes("default") ? "manual" : "default"
|
|
1581
|
+
);
|
|
1582
|
+
if (caps.promptsNone) args.push("--permission-prompts", "none");
|
|
1583
|
+
return { args };
|
|
1341
1584
|
}
|
|
1342
|
-
function
|
|
1343
|
-
const
|
|
1344
|
-
|
|
1585
|
+
function claudeEnv(configDir, env = process.env) {
|
|
1586
|
+
const e = { ...env };
|
|
1587
|
+
for (const k of [
|
|
1588
|
+
"CLAUDECODE",
|
|
1589
|
+
"CLAUDE_CODE_SESSION_ID",
|
|
1590
|
+
"CLAUDE_CODE_HOST_SESSION_ID",
|
|
1591
|
+
"CLAUDE_CODE_ENTRYPOINT",
|
|
1592
|
+
"CLAUDE_CODE_SESSION_ATTENDED",
|
|
1593
|
+
"CLAUDE_CODE_SSE_PORT"
|
|
1594
|
+
])
|
|
1595
|
+
delete e[k];
|
|
1596
|
+
e.SESSIONPIPE_CONTROL_JOB = "1";
|
|
1597
|
+
if (configDir !== void 0) {
|
|
1598
|
+
if (path8.resolve(configDir) === path8.resolve(os3.homedir(), ".claude")) delete e.CLAUDE_CONFIG_DIR;
|
|
1599
|
+
else e.CLAUDE_CONFIG_DIR = configDir;
|
|
1600
|
+
}
|
|
1601
|
+
return e;
|
|
1345
1602
|
}
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
var CODEX_EVENTS = [
|
|
1351
|
-
"SessionStart",
|
|
1352
|
-
"UserPromptSubmit",
|
|
1353
|
-
"PreToolUse",
|
|
1354
|
-
"PostToolUse",
|
|
1355
|
-
"PermissionRequest",
|
|
1356
|
-
"Stop",
|
|
1357
|
-
"Interrupt",
|
|
1358
|
-
"SubagentStart",
|
|
1359
|
-
"SubagentStop",
|
|
1360
|
-
"PreCompact",
|
|
1361
|
-
"PostCompact",
|
|
1362
|
-
"SessionEnd"
|
|
1363
|
-
];
|
|
1364
|
-
var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
1365
|
-
var eventsFor2 = (lean) => lean ? CODEX_EVENTS.filter((e) => e !== "PreToolUse") : [...CODEX_EVENTS];
|
|
1366
|
-
var codexHome = (env) => env.CODEX_HOME || path6.join(HOME, ".codex");
|
|
1367
|
-
var hooksFile2 = (env) => path6.join(codexHome(env), "hooks.json");
|
|
1368
|
-
var configFile2 = (env) => path6.join(codexHome(env), "config.toml");
|
|
1369
|
-
var sessionsDir = (env) => path6.join(codexHome(env), "sessions");
|
|
1370
|
-
var validTool3 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
1371
|
-
var endReason2 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
|
|
1372
|
-
function installNotify(cmd, env) {
|
|
1373
|
-
const file = configFile2(env);
|
|
1374
|
-
let text = "";
|
|
1603
|
+
function parseAuthStatus(stdout) {
|
|
1604
|
+
const text = String(stdout || "").trim();
|
|
1605
|
+
const start = text.indexOf("{");
|
|
1606
|
+
if (start < 0) return { loggedIn: null };
|
|
1375
1607
|
try {
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
}
|
|
1379
|
-
const argv = cmd([NAME3, "notify"]).argv;
|
|
1380
|
-
const line = `notify = ${JSON.stringify(argv)}`;
|
|
1381
|
-
const n = readNotify(text);
|
|
1382
|
-
const write = (next) => {
|
|
1383
|
-
mkdirSync3(path6.dirname(file), { recursive: true });
|
|
1384
|
-
writeFileSync3(file, next);
|
|
1385
|
-
};
|
|
1386
|
-
if (n && !n.other) {
|
|
1387
|
-
if (!n.misplaced && JSON.stringify(n.argv) === JSON.stringify(argv))
|
|
1388
|
-
return { file, changed: false, note: "notify current" };
|
|
1389
|
-
write(withNotify(text, line));
|
|
1390
|
-
return {
|
|
1391
|
-
file,
|
|
1392
|
-
changed: true,
|
|
1393
|
-
note: n.misplaced ? "notify moved to the top level (it sat inside a [table], where Codex never reads it)" : "notify updated"
|
|
1394
|
-
};
|
|
1395
|
-
}
|
|
1396
|
-
if (n?.other) {
|
|
1397
|
-
const prev = previousNotify(n.argv);
|
|
1398
|
-
if (isComputerUse(n.argv)) {
|
|
1399
|
-
if (prev && OURS2.test(JSON.stringify(prev))) {
|
|
1400
|
-
if (JSON.stringify(prev) === JSON.stringify(argv))
|
|
1401
|
-
return { file, changed: false, note: "notify current (after Codex Computer Use)" };
|
|
1402
|
-
}
|
|
1403
|
-
if (!prev || OURS2.test(JSON.stringify(prev))) {
|
|
1404
|
-
const next = replaceNotify(text, `notify = ${withPreviousRaw(n.raw, argv)}`);
|
|
1405
|
-
if (next) {
|
|
1406
|
-
write(next);
|
|
1407
|
-
return { file, changed: true, note: "chained after Codex Computer Use (--previous-notify)" };
|
|
1408
|
-
}
|
|
1409
|
-
}
|
|
1410
|
-
}
|
|
1608
|
+
const j = JSON.parse(text.slice(start));
|
|
1609
|
+
if (typeof j.loggedIn !== "boolean") return { loggedIn: null };
|
|
1411
1610
|
return {
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
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`
|
|
1611
|
+
loggedIn: j.loggedIn,
|
|
1612
|
+
...typeof j.authMethod === "string" ? { method: j.authMethod } : {},
|
|
1613
|
+
...typeof j.subscriptionType === "string" ? { subscription: j.subscriptionType } : {}
|
|
1416
1614
|
};
|
|
1615
|
+
} catch {
|
|
1616
|
+
return { loggedIn: null };
|
|
1417
1617
|
}
|
|
1418
|
-
write(withNotify(text, line));
|
|
1419
|
-
return { file, changed: true, note: "notify set at the top level" };
|
|
1420
1618
|
}
|
|
1421
|
-
function
|
|
1422
|
-
|
|
1423
|
-
|
|
1619
|
+
function claudeLogin(bin, configDir, env = process.env) {
|
|
1620
|
+
return new Promise((resolve) => {
|
|
1621
|
+
execFile(
|
|
1622
|
+
bin,
|
|
1623
|
+
["auth", "status", "--json"],
|
|
1624
|
+
{ encoding: "utf8", timeout: 2e4, env: claudeEnv(configDir, env), maxBuffer: 256 * 1024 },
|
|
1625
|
+
// `auth status` may exit non-zero when signed out; its JSON still says so.
|
|
1626
|
+
(_err, stdout) => resolve(parseAuthStatus(stdout))
|
|
1627
|
+
);
|
|
1628
|
+
});
|
|
1629
|
+
}
|
|
1630
|
+
function loginCommand(configDir) {
|
|
1631
|
+
const home = os3.homedir();
|
|
1632
|
+
const isDefault = !configDir || path8.resolve(configDir) === path8.resolve(home, ".claude");
|
|
1633
|
+
return isDefault ? "`claude auth login`" : `\`CLAUDE_CONFIG_DIR=${configDir.replace(home, "~")} claude auth login\``;
|
|
1634
|
+
}
|
|
1635
|
+
function loginHint(configDir, platform = process.platform) {
|
|
1636
|
+
const shown = configDir ? configDir.replace(os3.homedir(), "~") : "~/.claude";
|
|
1637
|
+
const login = loginCommand(configDir);
|
|
1638
|
+
if (platform === "darwin")
|
|
1639
|
+
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.`;
|
|
1640
|
+
return `Claude Code (${shown}) isn't signed in on this machine: run ${login} there.`;
|
|
1641
|
+
}
|
|
1642
|
+
function detectCaps(bin) {
|
|
1643
|
+
let help = "";
|
|
1424
1644
|
try {
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
}
|
|
1435
|
-
const prev = previousNotify(n.argv);
|
|
1436
|
-
if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev))) {
|
|
1437
|
-
const next = replaceNotify(text, `notify = ${stripPreviousRaw(n.raw)}`);
|
|
1438
|
-
if (next) writeFileSync3(file, next);
|
|
1439
|
-
return { file, changed: true };
|
|
1645
|
+
help = execFileSync3(bin, ["--help"], {
|
|
1646
|
+
encoding: "utf8",
|
|
1647
|
+
timeout: 3e4,
|
|
1648
|
+
env: claudeEnv(),
|
|
1649
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1650
|
+
maxBuffer: 4 * 1024 * 1024
|
|
1651
|
+
});
|
|
1652
|
+
} catch (e) {
|
|
1653
|
+
help = String(e?.stdout || "");
|
|
1440
1654
|
}
|
|
1441
|
-
return
|
|
1655
|
+
return parseCaps(help);
|
|
1442
1656
|
}
|
|
1443
|
-
function
|
|
1444
|
-
|
|
1445
|
-
const
|
|
1446
|
-
let
|
|
1657
|
+
function findTranscript(session, dirs) {
|
|
1658
|
+
if (!UUID_RE.test(session)) return null;
|
|
1659
|
+
for (const dir of dirs) {
|
|
1660
|
+
let projects = [];
|
|
1447
1661
|
try {
|
|
1448
|
-
|
|
1662
|
+
projects = readdirSync4(path8.join(dir, "projects"), { withFileTypes: true });
|
|
1449
1663
|
} catch {
|
|
1450
|
-
|
|
1664
|
+
continue;
|
|
1451
1665
|
}
|
|
1452
|
-
for (const
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
return;
|
|
1457
|
-
}
|
|
1458
|
-
if (depth < 3) {
|
|
1459
|
-
walk(p, depth + 1);
|
|
1460
|
-
if (best) return;
|
|
1666
|
+
for (const p of projects) {
|
|
1667
|
+
if (!p.isDirectory()) continue;
|
|
1668
|
+
const file = path8.join(dir, "projects", p.name, `${session}.jsonl`);
|
|
1669
|
+
try {
|
|
1670
|
+
if (statSync5(file).isFile()) return { file, configDir: dir, project: p.name };
|
|
1671
|
+
} catch {
|
|
1461
1672
|
}
|
|
1462
1673
|
}
|
|
1463
|
-
}
|
|
1464
|
-
|
|
1465
|
-
return best;
|
|
1674
|
+
}
|
|
1675
|
+
return null;
|
|
1466
1676
|
}
|
|
1467
|
-
var
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
],
|
|
1476
|
-
uninstall: (env = process.env, opts = {}) => [
|
|
1477
|
-
uninstallClaudeShaped(hooksFile2(env), CODEX_EVENTS, { created: opts.created?.includes(hooksFile2(env)) }),
|
|
1478
|
-
uninstallNotify(env)
|
|
1479
|
-
],
|
|
1480
|
-
installed: (cmd, env = process.env, opts = {}) => {
|
|
1481
|
-
const out = [
|
|
1482
|
-
{
|
|
1483
|
-
file: hooksFile2(env),
|
|
1484
|
-
state: installedClaudeShaped(hooksFile2(env), NAME3, eventsFor2(opts.lean), cmd, OPTS2),
|
|
1485
|
-
note: "Codex runs non-managed hooks only after the review in `codex` (/hooks); until then nothing is reported unless the notify fallback is ours"
|
|
1486
|
-
}
|
|
1487
|
-
];
|
|
1488
|
-
let text = "";
|
|
1677
|
+
var projectName = (cwd) => cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
1678
|
+
function sessionFolder(t, cap2 = 8 * 1024 * 1024) {
|
|
1679
|
+
let first = null;
|
|
1680
|
+
let count = 0;
|
|
1681
|
+
let fd;
|
|
1682
|
+
const look = (line) => {
|
|
1683
|
+
if (line.indexOf('"cwd"') < 0) return null;
|
|
1684
|
+
let j;
|
|
1489
1685
|
try {
|
|
1490
|
-
|
|
1686
|
+
j = JSON.parse(line.toString("utf8"));
|
|
1491
1687
|
} catch {
|
|
1688
|
+
return null;
|
|
1492
1689
|
}
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1690
|
+
if (!j || typeof j.cwd !== "string" || !path8.isAbsolute(j.cwd)) return null;
|
|
1691
|
+
if (projectName(j.cwd) === t.project) return j.cwd;
|
|
1692
|
+
first ??= j.cwd;
|
|
1693
|
+
count++;
|
|
1694
|
+
return null;
|
|
1695
|
+
};
|
|
1696
|
+
try {
|
|
1697
|
+
fd = openSync2(t.file, "r");
|
|
1698
|
+
const buf = Buffer.alloc(256 * 1024);
|
|
1699
|
+
let pos = 0;
|
|
1700
|
+
let rest = Buffer.alloc(0);
|
|
1701
|
+
let skipping = false;
|
|
1702
|
+
while (pos < cap2 && count < 50) {
|
|
1703
|
+
const n = readSync2(fd, buf, 0, buf.length, pos);
|
|
1704
|
+
if (!n) {
|
|
1705
|
+
if (!skipping && rest.length) {
|
|
1706
|
+
const hit = look(rest);
|
|
1707
|
+
if (hit) return hit;
|
|
1708
|
+
}
|
|
1709
|
+
break;
|
|
1710
|
+
}
|
|
1711
|
+
pos += n;
|
|
1712
|
+
let chunk = rest.length ? Buffer.concat([rest, buf.subarray(0, n)]) : buf.subarray(0, n);
|
|
1713
|
+
let nl = chunk.indexOf(10);
|
|
1714
|
+
while (nl >= 0) {
|
|
1715
|
+
const line = chunk.subarray(0, nl);
|
|
1716
|
+
chunk = chunk.subarray(nl + 1);
|
|
1717
|
+
if (skipping) skipping = false;
|
|
1718
|
+
else {
|
|
1719
|
+
const hit = look(line);
|
|
1720
|
+
if (hit) return hit;
|
|
1721
|
+
}
|
|
1722
|
+
nl = chunk.indexOf(10);
|
|
1723
|
+
}
|
|
1724
|
+
if (chunk.length > 2 * 1024 * 1024) {
|
|
1725
|
+
skipping = true;
|
|
1726
|
+
rest = Buffer.alloc(0);
|
|
1727
|
+
} else rest = skipping ? Buffer.alloc(0) : Buffer.from(chunk);
|
|
1728
|
+
}
|
|
1729
|
+
} catch {
|
|
1730
|
+
} finally {
|
|
1731
|
+
if (fd !== void 0)
|
|
1508
1732
|
try {
|
|
1509
|
-
|
|
1733
|
+
closeSync2(fd);
|
|
1510
1734
|
} catch {
|
|
1511
1735
|
}
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1736
|
+
}
|
|
1737
|
+
return first;
|
|
1738
|
+
}
|
|
1739
|
+
function pidAlive(pid) {
|
|
1740
|
+
if (!Number.isSafeInteger(pid) || pid <= 1) return false;
|
|
1741
|
+
try {
|
|
1742
|
+
process.kill(pid, 0);
|
|
1743
|
+
return true;
|
|
1744
|
+
} catch (e) {
|
|
1745
|
+
return e.code === "EPERM";
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
function liveProcess(session, configDir) {
|
|
1749
|
+
let names = [];
|
|
1750
|
+
try {
|
|
1751
|
+
names = readdirSync4(path8.join(configDir, "sessions")).filter((n) => /^\d+\.json$/.test(n));
|
|
1752
|
+
} catch {
|
|
1753
|
+
return false;
|
|
1754
|
+
}
|
|
1755
|
+
for (const n of names) {
|
|
1529
1756
|
try {
|
|
1530
|
-
|
|
1757
|
+
const j = JSON.parse(readFileSync4(path8.join(configDir, "sessions", n), "utf8"));
|
|
1758
|
+
if (String(j.sessionId) !== session) continue;
|
|
1759
|
+
if (pidAlive(Number(j.pid ?? n.slice(0, -5)))) return true;
|
|
1531
1760
|
} catch {
|
|
1532
1761
|
}
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
);
|
|
1557
|
-
break;
|
|
1558
|
-
case "PreToolUse":
|
|
1559
|
-
if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, input: s.tool_input }));
|
|
1560
|
-
break;
|
|
1561
|
-
case "PostToolUse":
|
|
1562
|
-
if (tool)
|
|
1563
|
-
events.push(
|
|
1564
|
-
ev("tool.ended", { tool, call_id, turn_id, ok: true, input: s.tool_input, output: s.tool_response })
|
|
1565
|
-
);
|
|
1566
|
-
break;
|
|
1567
|
-
case "PermissionRequest":
|
|
1568
|
-
events.push(
|
|
1569
|
-
ev("attention.needed", { attention_id: call_id ?? `perm-${Date.now()}`, kind: "permission", tool })
|
|
1570
|
-
);
|
|
1571
|
-
break;
|
|
1572
|
-
case "Stop":
|
|
1573
|
-
events.push(ev("turn.ended", { turn_id, reason: "stop" }));
|
|
1574
|
-
break;
|
|
1575
|
-
case "Interrupt":
|
|
1576
|
-
events.push(ev("turn.ended", { turn_id, reason: "interrupt" }));
|
|
1577
|
-
break;
|
|
1578
|
-
case "SubagentStart":
|
|
1579
|
-
events.push(
|
|
1580
|
-
ev("subagent.started", {
|
|
1581
|
-
agent_id: String(s.agent_id ?? s.subagent_id ?? ""),
|
|
1582
|
-
agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
|
|
1583
|
-
})
|
|
1584
|
-
);
|
|
1585
|
-
break;
|
|
1586
|
-
case "SubagentStop":
|
|
1587
|
-
events.push(
|
|
1588
|
-
ev("subagent.ended", {
|
|
1589
|
-
agent_id: String(s.agent_id ?? s.subagent_id ?? ""),
|
|
1590
|
-
agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
|
|
1591
|
-
})
|
|
1592
|
-
);
|
|
1593
|
-
break;
|
|
1594
|
-
case "PreCompact":
|
|
1595
|
-
events.push(ev("context.compacted", { trigger: s.trigger === "manual" ? "manual" : "auto" }));
|
|
1596
|
-
break;
|
|
1597
|
-
case "PostCompact":
|
|
1598
|
-
break;
|
|
1599
|
-
case "SessionEnd":
|
|
1600
|
-
events.push(ev("session.ended", { reason: endReason2(s.reason) }));
|
|
1601
|
-
break;
|
|
1602
|
-
default:
|
|
1603
|
-
return null;
|
|
1762
|
+
}
|
|
1763
|
+
return false;
|
|
1764
|
+
}
|
|
1765
|
+
function recentlyWritten(file, ourLastEnd, now = Date.now()) {
|
|
1766
|
+
let m;
|
|
1767
|
+
try {
|
|
1768
|
+
m = statSync5(file).mtimeMs;
|
|
1769
|
+
} catch {
|
|
1770
|
+
return false;
|
|
1771
|
+
}
|
|
1772
|
+
if (now - m > LIVE_WRITE_MS) return false;
|
|
1773
|
+
return !(ourLastEnd && m <= ourLastEnd + 2e3);
|
|
1774
|
+
}
|
|
1775
|
+
function runClaude(bin, args, o) {
|
|
1776
|
+
return runHeadless(bin, args, { ...o, keep: o.keep ?? ((line) => line.includes('"type":"result"')) });
|
|
1777
|
+
}
|
|
1778
|
+
function parseResult(stdout) {
|
|
1779
|
+
const s = String(stdout || "").trim();
|
|
1780
|
+
if (!s) return null;
|
|
1781
|
+
const pick = (j) => {
|
|
1782
|
+
if (Array.isArray(j)) {
|
|
1783
|
+
for (let i = j.length - 1; i >= 0; i--) if (j[i]?.type === "result") return j[i];
|
|
1784
|
+
return null;
|
|
1604
1785
|
}
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1786
|
+
return j && typeof j === "object" ? j : null;
|
|
1787
|
+
};
|
|
1788
|
+
try {
|
|
1789
|
+
return pick(JSON.parse(s));
|
|
1790
|
+
} catch {
|
|
1791
|
+
}
|
|
1792
|
+
const lines = s.split("\n");
|
|
1793
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
1611
1794
|
try {
|
|
1612
|
-
|
|
1795
|
+
const j = pick(JSON.parse(lines[i]));
|
|
1796
|
+
if (j && (j.type === "result" || "result" in j)) return j;
|
|
1613
1797
|
} catch {
|
|
1614
|
-
return { turns: [], line: fromLine };
|
|
1615
1798
|
}
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1799
|
+
}
|
|
1800
|
+
return null;
|
|
1801
|
+
}
|
|
1802
|
+
var isUuid = (s) => typeof s === "string" && UUID_RE.test(s);
|
|
1803
|
+
|
|
1804
|
+
// src/adapters/codex.ts
|
|
1805
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1806
|
+
import path9 from "node:path";
|
|
1807
|
+
|
|
1808
|
+
// src/adapters/codex-config.ts
|
|
1809
|
+
var NOTIFY_LINE = /^[ \t]*notify[ \t]*=[ \t]*(\[.*\])[ \t]*(?:#.*)?$/m;
|
|
1810
|
+
var NOTIFY_KEY = /^[ \t]*notify[ \t]*=/m;
|
|
1811
|
+
var OURS2 = /(?:hook\.js|sessionpipe-hook)",\s*"codex",\s*"notify"/;
|
|
1812
|
+
function scan(line, st) {
|
|
1813
|
+
let i = 0;
|
|
1814
|
+
while (i < line.length) {
|
|
1815
|
+
if (st.str) {
|
|
1816
|
+
const j = line.indexOf(st.str, i);
|
|
1817
|
+
if (j < 0) return;
|
|
1818
|
+
i = j + 3;
|
|
1819
|
+
st.str = null;
|
|
1820
|
+
continue;
|
|
1632
1821
|
}
|
|
1633
|
-
|
|
1822
|
+
const ch = line[i];
|
|
1823
|
+
if (ch === "#") return;
|
|
1824
|
+
if (line.startsWith('"""', i) || line.startsWith("'''", i)) {
|
|
1825
|
+
st.str = line.slice(i, i + 3);
|
|
1826
|
+
i += 3;
|
|
1827
|
+
continue;
|
|
1828
|
+
}
|
|
1829
|
+
if (ch === '"') {
|
|
1830
|
+
i++;
|
|
1831
|
+
while (i < line.length && line[i] !== '"') i += line[i] === "\\" ? 2 : 1;
|
|
1832
|
+
i++;
|
|
1833
|
+
continue;
|
|
1834
|
+
}
|
|
1835
|
+
if (ch === "'") {
|
|
1836
|
+
const j = line.indexOf("'", i + 1);
|
|
1837
|
+
i = j < 0 ? line.length : j + 1;
|
|
1838
|
+
continue;
|
|
1839
|
+
}
|
|
1840
|
+
if (ch === "[") st.depth++;
|
|
1841
|
+
else if (ch === "]") st.depth = Math.max(0, st.depth - 1);
|
|
1842
|
+
i++;
|
|
1634
1843
|
}
|
|
1635
|
-
};
|
|
1636
|
-
function prune3(o) {
|
|
1637
|
-
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
1638
|
-
return o;
|
|
1639
1844
|
}
|
|
1640
|
-
function
|
|
1641
|
-
const
|
|
1642
|
-
|
|
1643
|
-
const
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
const p = meta.payload;
|
|
1648
|
-
if (p.cwd) out.cwd = p.cwd;
|
|
1649
|
-
if (p.cli_version) out.harness_version = String(p.cli_version);
|
|
1650
|
-
if (p.git?.branch) out.branch = p.git.branch;
|
|
1651
|
-
if (p.git?.repository_url) out.repo = String(p.git.repository_url).replace(/\/\/[^/@]*@/, "//");
|
|
1845
|
+
function firstTableAt(text) {
|
|
1846
|
+
const st = { depth: 0, str: null };
|
|
1847
|
+
let pos = 0;
|
|
1848
|
+
for (const line of text.split("\n")) {
|
|
1849
|
+
if (!st.str && st.depth === 0 && /^[ \t]*\[/.test(line)) return pos;
|
|
1850
|
+
scan(line, st);
|
|
1851
|
+
pos += line.length + 1;
|
|
1652
1852
|
}
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1853
|
+
return -1;
|
|
1854
|
+
}
|
|
1855
|
+
function split(text) {
|
|
1856
|
+
const at = firstTableAt(text);
|
|
1857
|
+
return at < 0 ? { head: text, tail: "", at: text.length } : { head: text.slice(0, at), tail: text.slice(at), at };
|
|
1858
|
+
}
|
|
1859
|
+
function parseArgv(raw) {
|
|
1860
|
+
try {
|
|
1861
|
+
const a = JSON.parse(raw);
|
|
1862
|
+
return Array.isArray(a) && a.every((x) => typeof x === "string") ? a : null;
|
|
1863
|
+
} catch {
|
|
1864
|
+
return null;
|
|
1664
1865
|
}
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1866
|
+
}
|
|
1867
|
+
function readNotify(text) {
|
|
1868
|
+
const { head, tail, at } = split(text);
|
|
1869
|
+
const m = NOTIFY_LINE.exec(head);
|
|
1870
|
+
if (m)
|
|
1871
|
+
return {
|
|
1872
|
+
raw: m[1] ?? "",
|
|
1873
|
+
argv: parseArgv(m[1] ?? ""),
|
|
1874
|
+
index: m.index,
|
|
1875
|
+
length: m[0].length,
|
|
1876
|
+
misplaced: false,
|
|
1877
|
+
other: !OURS2.test(m[1] ?? "")
|
|
1878
|
+
};
|
|
1879
|
+
const k = NOTIFY_KEY.exec(head);
|
|
1880
|
+
if (k) {
|
|
1881
|
+
const rest = head.slice(k.index).split("\n")[0] ?? "";
|
|
1882
|
+
return {
|
|
1883
|
+
raw: rest.replace(NOTIFY_KEY, "").trim(),
|
|
1884
|
+
argv: null,
|
|
1885
|
+
index: k.index,
|
|
1886
|
+
length: 0,
|
|
1887
|
+
misplaced: false,
|
|
1888
|
+
other: true
|
|
1889
|
+
};
|
|
1669
1890
|
}
|
|
1670
|
-
|
|
1891
|
+
const t = NOTIFY_LINE.exec(tail);
|
|
1892
|
+
if (t && OURS2.test(t[1] ?? ""))
|
|
1893
|
+
return {
|
|
1894
|
+
raw: t[1] ?? "",
|
|
1895
|
+
argv: parseArgv(t[1] ?? ""),
|
|
1896
|
+
index: at + t.index,
|
|
1897
|
+
length: t[0].length,
|
|
1898
|
+
misplaced: true,
|
|
1899
|
+
other: false
|
|
1900
|
+
};
|
|
1901
|
+
return null;
|
|
1902
|
+
}
|
|
1903
|
+
function dropLine(text, index, length) {
|
|
1904
|
+
return text.slice(0, index) + text.slice(index + length).replace(/^\r?\n/, "");
|
|
1671
1905
|
}
|
|
1906
|
+
function insertTopLevel(text, line) {
|
|
1907
|
+
const { head, tail } = split(text);
|
|
1908
|
+
if (!tail) return (head.trim() ? head.replace(/\s*$/, "\n") : "") + line + "\n";
|
|
1909
|
+
return `${(head.trim() ? head.replace(/\s*$/, "\n") : "") + line}
|
|
1672
1910
|
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1911
|
+
${tail}`;
|
|
1912
|
+
}
|
|
1913
|
+
function withNotify(text, line) {
|
|
1914
|
+
const n = readNotify(text);
|
|
1915
|
+
if (n && !n.misplaced && !n.other) return text.slice(0, n.index) + line + text.slice(n.index + n.length);
|
|
1916
|
+
if (n?.misplaced) return insertTopLevel(dropLine(text, n.index, n.length), line);
|
|
1917
|
+
return insertTopLevel(text, line);
|
|
1918
|
+
}
|
|
1919
|
+
function replaceNotify(text, line) {
|
|
1920
|
+
const n = readNotify(text);
|
|
1921
|
+
if (!n || n.misplaced || !n.length) return null;
|
|
1922
|
+
return text.slice(0, n.index) + line + text.slice(n.index + n.length);
|
|
1923
|
+
}
|
|
1924
|
+
function withoutNotify(text) {
|
|
1925
|
+
const n = readNotify(text);
|
|
1926
|
+
if (!n || n.other || !n.length) return text;
|
|
1927
|
+
return dropLine(text, n.index, n.length);
|
|
1928
|
+
}
|
|
1929
|
+
var PREVIOUS = "--previous-notify";
|
|
1930
|
+
function previousNotify(argv) {
|
|
1931
|
+
if (!Array.isArray(argv)) return null;
|
|
1932
|
+
const i = argv.indexOf(PREVIOUS);
|
|
1933
|
+
return i >= 0 && typeof argv[i + 1] === "string" ? parseArgv(argv[i + 1]) : null;
|
|
1934
|
+
}
|
|
1935
|
+
function stripPreviousRaw(raw) {
|
|
1936
|
+
return raw.replace(/,\s*"--previous-notify",\s*"(?:[^"\\]|\\.)*"\s*(?=\])/, "");
|
|
1937
|
+
}
|
|
1938
|
+
function withPreviousRaw(raw, prev) {
|
|
1939
|
+
const base = stripPreviousRaw(raw).replace(/\s*\]\s*$/, "");
|
|
1940
|
+
return `${base}, "--previous-notify", ${JSON.stringify(JSON.stringify(prev))}]`;
|
|
1941
|
+
}
|
|
1942
|
+
var isComputerUse = (argv) => Array.isArray(argv) && typeof argv[0] === "string" && /(^|\/)SkyComputerUseClient$/.test(argv[0]);
|
|
1943
|
+
|
|
1944
|
+
// src/adapters/codex.ts
|
|
1945
|
+
var NAME3 = "codex";
|
|
1946
|
+
var CODEX_EVENTS = [
|
|
1679
1947
|
"SessionStart",
|
|
1680
|
-
"
|
|
1681
|
-
"
|
|
1682
|
-
"
|
|
1683
|
-
"
|
|
1684
|
-
"
|
|
1685
|
-
"
|
|
1948
|
+
"UserPromptSubmit",
|
|
1949
|
+
"PreToolUse",
|
|
1950
|
+
"PostToolUse",
|
|
1951
|
+
"PermissionRequest",
|
|
1952
|
+
"Stop",
|
|
1953
|
+
"Interrupt",
|
|
1954
|
+
"SubagentStart",
|
|
1955
|
+
"SubagentStop",
|
|
1956
|
+
"PreCompact",
|
|
1957
|
+
"PostCompact",
|
|
1686
1958
|
"SessionEnd"
|
|
1687
1959
|
];
|
|
1688
|
-
var
|
|
1689
|
-
var
|
|
1690
|
-
var
|
|
1691
|
-
var
|
|
1692
|
-
var
|
|
1693
|
-
|
|
1694
|
-
|
|
1960
|
+
var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
1961
|
+
var eventsFor2 = (lean) => lean ? CODEX_EVENTS.filter((e) => e !== "PreToolUse") : [...CODEX_EVENTS];
|
|
1962
|
+
var codexHome = (env) => env.CODEX_HOME || path9.join(HOME, ".codex");
|
|
1963
|
+
var hooksFile2 = (env) => path9.join(codexHome(env), "hooks.json");
|
|
1964
|
+
var configFile2 = (env) => path9.join(codexHome(env), "config.toml");
|
|
1965
|
+
var sessionsDir = (env) => path9.join(codexHome(env), "sessions");
|
|
1966
|
+
var validTool3 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
1967
|
+
var endReason2 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
|
|
1968
|
+
function installNotify(cmd, env) {
|
|
1969
|
+
const file = configFile2(env);
|
|
1970
|
+
let text = "";
|
|
1695
1971
|
try {
|
|
1696
|
-
|
|
1972
|
+
text = readFileSync5(file, "utf8");
|
|
1697
1973
|
} catch {
|
|
1698
1974
|
}
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1975
|
+
const argv = cmd([NAME3, "notify"]).argv;
|
|
1976
|
+
const line = `notify = ${JSON.stringify(argv)}`;
|
|
1977
|
+
const n = readNotify(text);
|
|
1978
|
+
const write = (next) => {
|
|
1979
|
+
mkdirSync3(path9.dirname(file), { recursive: true });
|
|
1980
|
+
writeFileSync3(file, next);
|
|
1981
|
+
};
|
|
1982
|
+
if (n && !n.other) {
|
|
1983
|
+
if (!n.misplaced && JSON.stringify(n.argv) === JSON.stringify(argv))
|
|
1984
|
+
return { file, changed: false, note: "notify current" };
|
|
1985
|
+
write(withNotify(text, line));
|
|
1710
1986
|
return {
|
|
1711
|
-
|
|
1712
|
-
|
|
1987
|
+
file,
|
|
1988
|
+
changed: true,
|
|
1989
|
+
note: n.misplaced ? "notify moved to the top level (it sat inside a [table], where Codex never reads it)" : "notify updated"
|
|
1713
1990
|
};
|
|
1714
1991
|
}
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
}
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1992
|
+
if (n?.other) {
|
|
1993
|
+
const prev = previousNotify(n.argv);
|
|
1994
|
+
if (isComputerUse(n.argv)) {
|
|
1995
|
+
if (prev && OURS2.test(JSON.stringify(prev))) {
|
|
1996
|
+
if (JSON.stringify(prev) === JSON.stringify(argv))
|
|
1997
|
+
return { file, changed: false, note: "notify current (after Codex Computer Use)" };
|
|
1998
|
+
}
|
|
1999
|
+
if (!prev || OURS2.test(JSON.stringify(prev))) {
|
|
2000
|
+
const next = replaceNotify(text, `notify = ${withPreviousRaw(n.raw, argv)}`);
|
|
2001
|
+
if (next) {
|
|
2002
|
+
write(next);
|
|
2003
|
+
return { file, changed: true, note: "chained after Codex Computer Use (--previous-notify)" };
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
return {
|
|
2008
|
+
file,
|
|
2009
|
+
changed: false,
|
|
2010
|
+
skipped: true,
|
|
2011
|
+
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`
|
|
2012
|
+
};
|
|
2013
|
+
}
|
|
2014
|
+
write(withNotify(text, line));
|
|
2015
|
+
return { file, changed: true, note: "notify set at the top level" };
|
|
2016
|
+
}
|
|
2017
|
+
function uninstallNotify(env) {
|
|
2018
|
+
const file = configFile2(env);
|
|
2019
|
+
let text;
|
|
2020
|
+
try {
|
|
2021
|
+
text = readFileSync5(file, "utf8");
|
|
2022
|
+
} catch {
|
|
2023
|
+
return { file, changed: false };
|
|
2024
|
+
}
|
|
2025
|
+
const n = readNotify(text);
|
|
2026
|
+
if (!n) return { file, changed: false };
|
|
2027
|
+
if (!n.other) {
|
|
2028
|
+
writeFileSync3(file, withoutNotify(text));
|
|
2029
|
+
return { file, changed: true };
|
|
2030
|
+
}
|
|
2031
|
+
const prev = previousNotify(n.argv);
|
|
2032
|
+
if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev))) {
|
|
2033
|
+
const next = replaceNotify(text, `notify = ${stripPreviousRaw(n.raw)}`);
|
|
2034
|
+
if (next) writeFileSync3(file, next);
|
|
2035
|
+
return { file, changed: true };
|
|
2036
|
+
}
|
|
2037
|
+
return { file, changed: false };
|
|
2038
|
+
}
|
|
2039
|
+
function findRollout(threadId, env = process.env) {
|
|
2040
|
+
if (!/^[A-Za-z0-9-]{8,128}$/.test(threadId)) return null;
|
|
2041
|
+
let best = null;
|
|
2042
|
+
const walk = (dir, depth) => {
|
|
2043
|
+
let names = [];
|
|
2044
|
+
try {
|
|
2045
|
+
names = readdirSync5(dir);
|
|
2046
|
+
} catch {
|
|
2047
|
+
return;
|
|
2048
|
+
}
|
|
2049
|
+
for (const n of names.sort().reverse()) {
|
|
2050
|
+
const p = path9.join(dir, n);
|
|
2051
|
+
if (n.endsWith(`-${threadId}.jsonl`)) {
|
|
2052
|
+
best = p;
|
|
2053
|
+
return;
|
|
2054
|
+
}
|
|
2055
|
+
if (depth < 3) {
|
|
2056
|
+
walk(p, depth + 1);
|
|
2057
|
+
if (best) return;
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
};
|
|
2061
|
+
walk(sessionsDir(env), 0);
|
|
2062
|
+
return best;
|
|
2063
|
+
}
|
|
2064
|
+
var codex = {
|
|
2065
|
+
name: NAME3,
|
|
2066
|
+
events: CODEX_EVENTS,
|
|
2067
|
+
detect: (env = process.env) => existsSync4(codexHome(env)),
|
|
2068
|
+
configFiles: (env = process.env) => [hooksFile2(env), configFile2(env)],
|
|
2069
|
+
install: (cmd, env = process.env, opts = {}) => [
|
|
2070
|
+
installClaudeShaped(hooksFile2(env), NAME3, eventsFor2(opts.lean), cmd, OPTS2),
|
|
2071
|
+
installNotify(cmd, env)
|
|
2072
|
+
],
|
|
2073
|
+
uninstall: (env = process.env, opts = {}) => [
|
|
2074
|
+
uninstallClaudeShaped(hooksFile2(env), CODEX_EVENTS, { created: opts.created?.includes(hooksFile2(env)) }),
|
|
2075
|
+
uninstallNotify(env)
|
|
2076
|
+
],
|
|
2077
|
+
installed: (cmd, env = process.env, opts = {}) => {
|
|
2078
|
+
const out = [
|
|
1740
2079
|
{
|
|
1741
|
-
file:
|
|
1742
|
-
state:
|
|
1743
|
-
|
|
2080
|
+
file: hooksFile2(env),
|
|
2081
|
+
state: installedClaudeShaped(hooksFile2(env), NAME3, eventsFor2(opts.lean), cmd, OPTS2),
|
|
2082
|
+
note: "Codex runs non-managed hooks only after the review in `codex` (/hooks); until then nothing is reported unless the notify fallback is ours"
|
|
1744
2083
|
}
|
|
1745
2084
|
];
|
|
2085
|
+
let text = "";
|
|
2086
|
+
try {
|
|
2087
|
+
text = readFileSync5(configFile2(env), "utf8");
|
|
2088
|
+
} catch {
|
|
2089
|
+
}
|
|
2090
|
+
const n = readNotify(text);
|
|
2091
|
+
const argv = cmd([NAME3, "notify"]).argv;
|
|
2092
|
+
const prev = n ? previousNotify(n.argv) : null;
|
|
2093
|
+
const ours = n && (!n.other || prev && JSON.stringify(prev) === JSON.stringify(argv));
|
|
2094
|
+
out.push({
|
|
2095
|
+
file: configFile2(env),
|
|
2096
|
+
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"
|
|
2097
|
+
});
|
|
2098
|
+
return out;
|
|
1746
2099
|
},
|
|
1747
2100
|
fromHook(input) {
|
|
1748
2101
|
const event = input.argv[1] ?? "";
|
|
1749
2102
|
let s = {};
|
|
2103
|
+
if (event === "notify") {
|
|
2104
|
+
const raw = input.argv.find((a) => a.startsWith("{")) ?? input.stdin;
|
|
2105
|
+
try {
|
|
2106
|
+
s = raw ? JSON.parse(raw) : {};
|
|
2107
|
+
} catch {
|
|
2108
|
+
}
|
|
2109
|
+
const id2 = s["thread-id"] ?? s.thread_id ?? s["session-id"] ?? s.session_id;
|
|
2110
|
+
if (!id2) return null;
|
|
2111
|
+
const r2 = {
|
|
2112
|
+
session: { id: String(id2), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd },
|
|
2113
|
+
events: [
|
|
2114
|
+
{
|
|
2115
|
+
type: "turn.ended",
|
|
2116
|
+
data: { reason: "stop", ...typeof s["turn-id"] === "string" ? { turn_id: s["turn-id"] } : {} },
|
|
2117
|
+
harnessEvent: "notify"
|
|
2118
|
+
}
|
|
2119
|
+
],
|
|
2120
|
+
hints: {}
|
|
2121
|
+
};
|
|
2122
|
+
const file = findRollout(String(id2), input.env);
|
|
2123
|
+
if (file) r2.transcript = file;
|
|
2124
|
+
return r2;
|
|
2125
|
+
}
|
|
1750
2126
|
try {
|
|
1751
2127
|
s = input.stdin ? JSON.parse(input.stdin) : {};
|
|
1752
2128
|
} catch {
|
|
@@ -1754,62 +2130,72 @@ var geminiCli = {
|
|
|
1754
2130
|
const id = s.session_id;
|
|
1755
2131
|
if (!id) return null;
|
|
1756
2132
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
1757
|
-
|
|
1758
|
-
const
|
|
2133
|
+
if (typeof s.model === "string") session.model = s.model;
|
|
2134
|
+
const turn_id = typeof s.turn_id === "string" ? s.turn_id : void 0;
|
|
2135
|
+
const tool = validTool3(s.tool_name) ? s.tool_name : void 0;
|
|
2136
|
+
const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
|
|
2137
|
+
const ev = (type, data) => ({ type, data: prune3(data), harnessEvent: event });
|
|
1759
2138
|
const events = [];
|
|
1760
2139
|
switch (event) {
|
|
1761
2140
|
case "SessionStart":
|
|
1762
2141
|
events.push(
|
|
1763
2142
|
ev("session.started", {
|
|
1764
|
-
source: typeof s.source === "string" && ["startup", "resume", "clear"].includes(s.source) ? s.source : "unknown"
|
|
2143
|
+
source: typeof s.source === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(s.source) ? s.source : "unknown"
|
|
1765
2144
|
})
|
|
1766
2145
|
);
|
|
1767
2146
|
break;
|
|
1768
|
-
case "
|
|
2147
|
+
case "UserPromptSubmit":
|
|
1769
2148
|
events.push(
|
|
1770
|
-
ev("turn.started", {
|
|
2149
|
+
ev("turn.started", {
|
|
2150
|
+
turn_id,
|
|
2151
|
+
prompt_chars: typeof s.prompt === "string" ? [...s.prompt].length : void 0
|
|
2152
|
+
})
|
|
1771
2153
|
);
|
|
1772
2154
|
break;
|
|
1773
|
-
case "
|
|
1774
|
-
if (tool) events.push(ev("tool.started", { tool, input: s.tool_input }));
|
|
2155
|
+
case "PreToolUse":
|
|
2156
|
+
if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, input: s.tool_input }));
|
|
1775
2157
|
break;
|
|
1776
|
-
case "
|
|
1777
|
-
const resp = s.tool_response;
|
|
1778
|
-
const err = resp && typeof resp === "object" && resp.error ? String(typeof resp.error === "string" ? resp.error : JSON.stringify(resp.error)).slice(0, 2e3) : void 0;
|
|
2158
|
+
case "PostToolUse":
|
|
1779
2159
|
if (tool)
|
|
1780
2160
|
events.push(
|
|
1781
|
-
ev("tool.ended", {
|
|
1782
|
-
tool,
|
|
1783
|
-
ok: !err,
|
|
1784
|
-
error: err,
|
|
1785
|
-
input: s.tool_input,
|
|
1786
|
-
output: resp?.llmContent ?? s.tool_response
|
|
1787
|
-
})
|
|
2161
|
+
ev("tool.ended", { tool, call_id, turn_id, ok: true, input: s.tool_input, output: s.tool_response })
|
|
1788
2162
|
);
|
|
1789
2163
|
break;
|
|
1790
|
-
|
|
1791
|
-
case "Notification":
|
|
2164
|
+
case "PermissionRequest":
|
|
1792
2165
|
events.push(
|
|
1793
|
-
ev("attention.needed", {
|
|
1794
|
-
attention_id: `notif-${Date.now()}`,
|
|
1795
|
-
kind: s.notification_type === "ToolPermission" ? "permission" : "question",
|
|
1796
|
-
message: typeof s.message === "string" ? s.message.slice(0, 200) : void 0
|
|
1797
|
-
})
|
|
2166
|
+
ev("attention.needed", { attention_id: call_id ?? `perm-${Date.now()}`, kind: "permission", tool })
|
|
1798
2167
|
);
|
|
1799
2168
|
break;
|
|
1800
|
-
case "
|
|
1801
|
-
events.push(ev("turn.ended", { reason: "stop" }));
|
|
2169
|
+
case "Stop":
|
|
2170
|
+
events.push(ev("turn.ended", { turn_id, reason: "stop" }));
|
|
1802
2171
|
break;
|
|
1803
|
-
case "
|
|
1804
|
-
events.push(ev("
|
|
2172
|
+
case "Interrupt":
|
|
2173
|
+
events.push(ev("turn.ended", { turn_id, reason: "interrupt" }));
|
|
1805
2174
|
break;
|
|
1806
|
-
case "
|
|
2175
|
+
case "SubagentStart":
|
|
1807
2176
|
events.push(
|
|
1808
|
-
ev("
|
|
1809
|
-
|
|
2177
|
+
ev("subagent.started", {
|
|
2178
|
+
agent_id: String(s.agent_id ?? s.subagent_id ?? ""),
|
|
2179
|
+
agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
|
|
2180
|
+
})
|
|
2181
|
+
);
|
|
2182
|
+
break;
|
|
2183
|
+
case "SubagentStop":
|
|
2184
|
+
events.push(
|
|
2185
|
+
ev("subagent.ended", {
|
|
2186
|
+
agent_id: String(s.agent_id ?? s.subagent_id ?? ""),
|
|
2187
|
+
agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
|
|
1810
2188
|
})
|
|
1811
2189
|
);
|
|
1812
2190
|
break;
|
|
2191
|
+
case "PreCompact":
|
|
2192
|
+
events.push(ev("context.compacted", { trigger: s.trigger === "manual" ? "manual" : "auto" }));
|
|
2193
|
+
break;
|
|
2194
|
+
case "PostCompact":
|
|
2195
|
+
break;
|
|
2196
|
+
case "SessionEnd":
|
|
2197
|
+
events.push(ev("session.ended", { reason: endReason2(s.reason) }));
|
|
2198
|
+
break;
|
|
1813
2199
|
default:
|
|
1814
2200
|
return null;
|
|
1815
2201
|
}
|
|
@@ -1824,469 +2210,396 @@ var geminiCli = {
|
|
|
1824
2210
|
} catch {
|
|
1825
2211
|
return { turns: [], line: fromLine };
|
|
1826
2212
|
}
|
|
1827
|
-
return
|
|
2213
|
+
return readCodex(text.split("\n"), fromLine);
|
|
1828
2214
|
},
|
|
1829
|
-
facts(
|
|
2215
|
+
facts(session, transcript) {
|
|
1830
2216
|
const out = {};
|
|
1831
|
-
if (
|
|
1832
|
-
const { head, tail } = ends(transcript, 64 * 1024, 64 * 1024);
|
|
1833
|
-
const H = parseLines(head);
|
|
1834
|
-
const T = parseLines(tail);
|
|
1835
|
-
const first = H.find((j) => j.type === "user");
|
|
1836
|
-
const raw = first ? first.content ?? first.message ?? first.text : void 0;
|
|
1837
|
-
const text = typeof raw === "string" ? raw.trim() : "";
|
|
1838
|
-
if (text) {
|
|
1839
|
-
const line = text.split("\n").find((l) => l.trim()) ?? "";
|
|
1840
|
-
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
1841
|
-
out.title_source = "first-ask";
|
|
1842
|
-
}
|
|
1843
|
-
const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
|
|
1844
|
-
if (times.length) {
|
|
1845
|
-
out.started_at = Math.min(...times);
|
|
1846
|
-
out.last_at = Math.max(...times);
|
|
1847
|
-
}
|
|
2217
|
+
if (transcript) Object.assign(out, codexFacts(transcript));
|
|
1848
2218
|
return out;
|
|
1849
2219
|
},
|
|
1850
|
-
backfill
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
return
|
|
1938
|
-
}
|
|
1939
|
-
function
|
|
1940
|
-
const
|
|
1941
|
-
|
|
1942
|
-
return
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
const args = [];
|
|
1958
|
-
if (caps.hasMode)
|
|
1959
|
-
args.push(
|
|
1960
|
-
"--permission-mode",
|
|
1961
|
-
caps.modes.includes("manual") && !caps.modes.includes("default") ? "manual" : "default"
|
|
1962
|
-
);
|
|
1963
|
-
if (caps.promptsNone) args.push("--permission-prompts", "none");
|
|
1964
|
-
return { args };
|
|
1965
|
-
}
|
|
1966
|
-
function claudeEnv(configDir, env = process.env) {
|
|
1967
|
-
const e = { ...env };
|
|
1968
|
-
for (const k of [
|
|
1969
|
-
"CLAUDECODE",
|
|
1970
|
-
"CLAUDE_CODE_SESSION_ID",
|
|
1971
|
-
"CLAUDE_CODE_HOST_SESSION_ID",
|
|
1972
|
-
"CLAUDE_CODE_ENTRYPOINT",
|
|
1973
|
-
"CLAUDE_CODE_SESSION_ATTENDED",
|
|
1974
|
-
"CLAUDE_CODE_SSE_PORT"
|
|
1975
|
-
])
|
|
1976
|
-
delete e[k];
|
|
1977
|
-
e.SESSIONPIPE_CONTROL_JOB = "1";
|
|
1978
|
-
if (configDir !== void 0) {
|
|
1979
|
-
if (path8.resolve(configDir) === path8.resolve(os2.homedir(), ".claude")) delete e.CLAUDE_CONFIG_DIR;
|
|
1980
|
-
else e.CLAUDE_CONFIG_DIR = configDir;
|
|
1981
|
-
}
|
|
1982
|
-
return e;
|
|
1983
|
-
}
|
|
1984
|
-
function parseAuthStatus(stdout) {
|
|
1985
|
-
const text = String(stdout || "").trim();
|
|
1986
|
-
const start = text.indexOf("{");
|
|
1987
|
-
if (start < 0) return { loggedIn: null };
|
|
1988
|
-
try {
|
|
1989
|
-
const j = JSON.parse(text.slice(start));
|
|
1990
|
-
if (typeof j.loggedIn !== "boolean") return { loggedIn: null };
|
|
1991
|
-
return {
|
|
1992
|
-
loggedIn: j.loggedIn,
|
|
1993
|
-
...typeof j.authMethod === "string" ? { method: j.authMethod } : {},
|
|
1994
|
-
...typeof j.subscriptionType === "string" ? { subscription: j.subscriptionType } : {}
|
|
1995
|
-
};
|
|
1996
|
-
} catch {
|
|
1997
|
-
return { loggedIn: null };
|
|
1998
|
-
}
|
|
1999
|
-
}
|
|
2000
|
-
function claudeLogin(bin, configDir, env = process.env) {
|
|
2001
|
-
return new Promise((resolve) => {
|
|
2002
|
-
execFile(
|
|
2003
|
-
bin,
|
|
2004
|
-
["auth", "status", "--json"],
|
|
2005
|
-
{ encoding: "utf8", timeout: 2e4, env: claudeEnv(configDir, env), maxBuffer: 256 * 1024 },
|
|
2006
|
-
// `auth status` may exit non-zero when signed out; its JSON still says so.
|
|
2007
|
-
(_err, stdout) => resolve(parseAuthStatus(stdout))
|
|
2008
|
-
);
|
|
2009
|
-
});
|
|
2010
|
-
}
|
|
2011
|
-
function loginCommand(configDir) {
|
|
2012
|
-
const home = os2.homedir();
|
|
2013
|
-
const isDefault = !configDir || path8.resolve(configDir) === path8.resolve(home, ".claude");
|
|
2014
|
-
return isDefault ? "`claude auth login`" : `\`CLAUDE_CONFIG_DIR=${configDir.replace(home, "~")} claude auth login\``;
|
|
2015
|
-
}
|
|
2016
|
-
function loginHint(configDir, platform = process.platform) {
|
|
2017
|
-
const shown = configDir ? configDir.replace(os2.homedir(), "~") : "~/.claude";
|
|
2018
|
-
const login = loginCommand(configDir);
|
|
2019
|
-
if (platform === "darwin")
|
|
2020
|
-
return `Claude Code (${shown}) isn't signed in as this machine's background service sees it. On a Mac its login lives in the login keychain, which a background service can read only while someone is logged in at the screen: log in at the Mac, or run ${login} there.`;
|
|
2021
|
-
return `Claude Code (${shown}) isn't signed in on this machine: run ${login} there.`;
|
|
2022
|
-
}
|
|
2023
|
-
function detectCaps(bin) {
|
|
2024
|
-
let help = "";
|
|
2025
|
-
try {
|
|
2026
|
-
help = execFileSync4(bin, ["--help"], {
|
|
2027
|
-
encoding: "utf8",
|
|
2028
|
-
timeout: 3e4,
|
|
2029
|
-
env: claudeEnv(),
|
|
2030
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
2031
|
-
maxBuffer: 4 * 1024 * 1024
|
|
2032
|
-
});
|
|
2033
|
-
} catch (e) {
|
|
2034
|
-
help = String(e?.stdout || "");
|
|
2035
|
-
}
|
|
2036
|
-
return parseCaps(help);
|
|
2037
|
-
}
|
|
2038
|
-
function findTranscript(session, dirs) {
|
|
2039
|
-
if (!UUID_RE.test(session)) return null;
|
|
2040
|
-
for (const dir of dirs) {
|
|
2041
|
-
let projects = [];
|
|
2042
|
-
try {
|
|
2043
|
-
projects = readdirSync5(path8.join(dir, "projects"), { withFileTypes: true });
|
|
2044
|
-
} catch {
|
|
2045
|
-
continue;
|
|
2046
|
-
}
|
|
2047
|
-
for (const p of projects) {
|
|
2048
|
-
if (!p.isDirectory()) continue;
|
|
2049
|
-
const file = path8.join(dir, "projects", p.name, `${session}.jsonl`);
|
|
2050
|
-
try {
|
|
2051
|
-
if (statSync4(file).isFile()) return { file, configDir: dir, project: p.name };
|
|
2052
|
-
} catch {
|
|
2053
|
-
}
|
|
2054
|
-
}
|
|
2055
|
-
}
|
|
2056
|
-
return null;
|
|
2057
|
-
}
|
|
2058
|
-
var projectName = (cwd) => cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
2059
|
-
function sessionFolder(t, cap2 = 8 * 1024 * 1024) {
|
|
2060
|
-
let first = null;
|
|
2061
|
-
let count = 0;
|
|
2062
|
-
let fd;
|
|
2063
|
-
const look = (line) => {
|
|
2064
|
-
if (line.indexOf('"cwd"') < 0) return null;
|
|
2065
|
-
let j;
|
|
2066
|
-
try {
|
|
2067
|
-
j = JSON.parse(line.toString("utf8"));
|
|
2068
|
-
} catch {
|
|
2069
|
-
return null;
|
|
2070
|
-
}
|
|
2071
|
-
if (!j || typeof j.cwd !== "string" || !path8.isAbsolute(j.cwd)) return null;
|
|
2072
|
-
if (projectName(j.cwd) === t.project) return j.cwd;
|
|
2073
|
-
first ??= j.cwd;
|
|
2074
|
-
count++;
|
|
2075
|
-
return null;
|
|
2076
|
-
};
|
|
2077
|
-
try {
|
|
2078
|
-
fd = openSync2(t.file, "r");
|
|
2079
|
-
const buf = Buffer.alloc(256 * 1024);
|
|
2080
|
-
let pos = 0;
|
|
2081
|
-
let rest = Buffer.alloc(0);
|
|
2082
|
-
let skipping = false;
|
|
2083
|
-
while (pos < cap2 && count < 50) {
|
|
2084
|
-
const n = readSync2(fd, buf, 0, buf.length, pos);
|
|
2085
|
-
if (!n) {
|
|
2086
|
-
if (!skipping && rest.length) {
|
|
2087
|
-
const hit = look(rest);
|
|
2088
|
-
if (hit) return hit;
|
|
2089
|
-
}
|
|
2090
|
-
break;
|
|
2091
|
-
}
|
|
2092
|
-
pos += n;
|
|
2093
|
-
let chunk = rest.length ? Buffer.concat([rest, buf.subarray(0, n)]) : buf.subarray(0, n);
|
|
2094
|
-
let nl = chunk.indexOf(10);
|
|
2095
|
-
while (nl >= 0) {
|
|
2096
|
-
const line = chunk.subarray(0, nl);
|
|
2097
|
-
chunk = chunk.subarray(nl + 1);
|
|
2098
|
-
if (skipping) skipping = false;
|
|
2099
|
-
else {
|
|
2100
|
-
const hit = look(line);
|
|
2101
|
-
if (hit) return hit;
|
|
2102
|
-
}
|
|
2103
|
-
nl = chunk.indexOf(10);
|
|
2104
|
-
}
|
|
2105
|
-
if (chunk.length > 2 * 1024 * 1024) {
|
|
2106
|
-
skipping = true;
|
|
2107
|
-
rest = Buffer.alloc(0);
|
|
2108
|
-
} else rest = skipping ? Buffer.alloc(0) : Buffer.from(chunk);
|
|
2109
|
-
}
|
|
2110
|
-
} catch {
|
|
2111
|
-
} finally {
|
|
2112
|
-
if (fd !== void 0)
|
|
2113
|
-
try {
|
|
2114
|
-
closeSync2(fd);
|
|
2115
|
-
} catch {
|
|
2116
|
-
}
|
|
2117
|
-
}
|
|
2118
|
-
return first;
|
|
2119
|
-
}
|
|
2120
|
-
function pidAlive(pid) {
|
|
2121
|
-
if (!Number.isSafeInteger(pid) || pid <= 1) return false;
|
|
2122
|
-
try {
|
|
2123
|
-
process.kill(pid, 0);
|
|
2124
|
-
return true;
|
|
2125
|
-
} catch (e) {
|
|
2126
|
-
return e.code === "EPERM";
|
|
2127
|
-
}
|
|
2128
|
-
}
|
|
2129
|
-
function liveProcess(session, configDir) {
|
|
2130
|
-
let names = [];
|
|
2131
|
-
try {
|
|
2132
|
-
names = readdirSync5(path8.join(configDir, "sessions")).filter((n) => /^\d+\.json$/.test(n));
|
|
2133
|
-
} catch {
|
|
2134
|
-
return false;
|
|
2135
|
-
}
|
|
2136
|
-
for (const n of names) {
|
|
2137
|
-
try {
|
|
2138
|
-
const j = JSON.parse(readFileSync6(path8.join(configDir, "sessions", n), "utf8"));
|
|
2139
|
-
if (String(j.sessionId) !== session) continue;
|
|
2140
|
-
if (pidAlive(Number(j.pid ?? n.slice(0, -5)))) return true;
|
|
2141
|
-
} catch {
|
|
2142
|
-
}
|
|
2143
|
-
}
|
|
2144
|
-
return false;
|
|
2145
|
-
}
|
|
2146
|
-
function recentlyWritten(file, ourLastEnd, now = Date.now()) {
|
|
2147
|
-
let m;
|
|
2148
|
-
try {
|
|
2149
|
-
m = statSync4(file).mtimeMs;
|
|
2150
|
-
} catch {
|
|
2151
|
-
return false;
|
|
2152
|
-
}
|
|
2153
|
-
if (now - m > LIVE_WRITE_MS) return false;
|
|
2154
|
-
return !(ourLastEnd && m <= ourLastEnd + 2e3);
|
|
2155
|
-
}
|
|
2156
|
-
function runClaude(bin, args, o) {
|
|
2157
|
-
return new Promise((resolve) => {
|
|
2158
|
-
let out = [];
|
|
2159
|
-
let outLen = 0;
|
|
2160
|
-
let tooBig = false;
|
|
2161
|
-
let errTail = "";
|
|
2162
|
-
let timedOut = false;
|
|
2163
|
-
let settled = false;
|
|
2164
|
-
let child;
|
|
2165
|
-
try {
|
|
2166
|
-
child = spawn(bin, args, {
|
|
2167
|
-
cwd: o.cwd,
|
|
2168
|
-
env: o.env,
|
|
2169
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
2170
|
-
detached: process.platform !== "win32",
|
|
2171
|
-
windowsHide: true
|
|
2172
|
-
});
|
|
2173
|
-
} catch (e) {
|
|
2174
|
-
resolve({ error: e.message });
|
|
2175
|
-
return;
|
|
2176
|
-
}
|
|
2177
|
-
const kill = (sig) => {
|
|
2178
|
-
try {
|
|
2179
|
-
if (process.platform !== "win32" && child.pid) process.kill(-child.pid, sig);
|
|
2180
|
-
else child.kill(sig);
|
|
2181
|
-
} catch {
|
|
2182
|
-
try {
|
|
2183
|
-
child.kill(sig);
|
|
2184
|
-
} catch {
|
|
2185
|
-
}
|
|
2186
|
-
}
|
|
2187
|
-
};
|
|
2188
|
-
let partial = "";
|
|
2189
|
-
child.stdout?.on("data", (b) => {
|
|
2190
|
-
if (o.onLine) {
|
|
2191
|
-
const chunk = partial + b.toString("utf8");
|
|
2192
|
-
const lines = chunk.split("\n");
|
|
2193
|
-
partial = lines.pop() ?? "";
|
|
2194
|
-
for (const line of lines) {
|
|
2195
|
-
if (!line.trim()) continue;
|
|
2196
|
-
try {
|
|
2197
|
-
o.onLine(line);
|
|
2198
|
-
} catch {
|
|
2199
|
-
}
|
|
2200
|
-
if (line.includes('"type":"result"')) {
|
|
2201
|
-
out = [Buffer.from(line)];
|
|
2202
|
-
outLen = line.length;
|
|
2203
|
-
}
|
|
2204
|
-
}
|
|
2205
|
-
if (partial.length > OUT_CAP) partial = "";
|
|
2206
|
-
return;
|
|
2207
|
-
}
|
|
2208
|
-
if (tooBig) return;
|
|
2209
|
-
if (outLen + b.length > OUT_CAP) {
|
|
2210
|
-
tooBig = true;
|
|
2211
|
-
out = [];
|
|
2212
|
-
return;
|
|
2213
|
-
}
|
|
2214
|
-
out.push(b);
|
|
2215
|
-
outLen += b.length;
|
|
2216
|
-
});
|
|
2217
|
-
child.stderr?.on("data", (b) => {
|
|
2218
|
-
errTail = (errTail + b.toString("utf8")).slice(-4e3);
|
|
2219
|
-
});
|
|
2220
|
-
const timer = setTimeout(() => {
|
|
2221
|
-
timedOut = true;
|
|
2222
|
-
kill("SIGTERM");
|
|
2223
|
-
setTimeout(() => kill("SIGKILL"), 1e4).unref();
|
|
2224
|
-
}, o.timeoutMs ?? JOB_TIMEOUT_MS);
|
|
2225
|
-
const result = (code, signal) => ({
|
|
2226
|
-
code,
|
|
2227
|
-
signal,
|
|
2228
|
-
stdout: tooBig ? null : Buffer.concat(out).toString("utf8"),
|
|
2229
|
-
tooBig,
|
|
2230
|
-
stderr: errTail,
|
|
2231
|
-
timedOut
|
|
2232
|
-
});
|
|
2233
|
-
const finish = (r) => {
|
|
2234
|
-
if (settled) return;
|
|
2235
|
-
settled = true;
|
|
2236
|
-
clearTimeout(timer);
|
|
2237
|
-
resolve(r);
|
|
2238
|
-
};
|
|
2239
|
-
child.on("error", (e) => finish({ error: e.message }));
|
|
2240
|
-
child.on("exit", (code, signal) => setTimeout(() => finish(result(code, signal)), 2e3).unref());
|
|
2241
|
-
child.on("close", (code, signal) => {
|
|
2242
|
-
if (o.onLine && partial.trim()) {
|
|
2243
|
-
try {
|
|
2244
|
-
o.onLine(partial);
|
|
2245
|
-
} catch {
|
|
2246
|
-
}
|
|
2247
|
-
if (partial.includes('"type":"result"')) {
|
|
2248
|
-
out = [Buffer.from(partial)];
|
|
2249
|
-
outLen = partial.length;
|
|
2250
|
-
}
|
|
2251
|
-
partial = "";
|
|
2220
|
+
backfill(sinceMs, env = process.env) {
|
|
2221
|
+
const rows = [];
|
|
2222
|
+
for (const f of recentFiles(sessionsDir(env), 3, sinceMs, (n) => /^rollout-.*\.jsonl$/.test(n))) {
|
|
2223
|
+
const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(path9.basename(f));
|
|
2224
|
+
if (!m?.[1]) continue;
|
|
2225
|
+
const facts = codexFacts(f);
|
|
2226
|
+
if (!facts.started_at || !facts.last_at) continue;
|
|
2227
|
+
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
2228
|
+
rows.push({ session: { id: m[1], ...rest }, started_at, last_at });
|
|
2229
|
+
}
|
|
2230
|
+
return rows;
|
|
2231
|
+
}
|
|
2232
|
+
};
|
|
2233
|
+
function prune3(o) {
|
|
2234
|
+
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
2235
|
+
return o;
|
|
2236
|
+
}
|
|
2237
|
+
function codexFacts(file) {
|
|
2238
|
+
const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
|
|
2239
|
+
const H = parseLines(head);
|
|
2240
|
+
const T = parseLines(tail);
|
|
2241
|
+
const out = {};
|
|
2242
|
+
const meta = H.find((j) => j.type === "session_meta" && j.payload);
|
|
2243
|
+
if (meta?.payload) {
|
|
2244
|
+
const p = meta.payload;
|
|
2245
|
+
if (p.cwd) out.cwd = p.cwd;
|
|
2246
|
+
if (p.cli_version) out.harness_version = String(p.cli_version);
|
|
2247
|
+
if (p.git?.branch) out.branch = p.git.branch;
|
|
2248
|
+
if (p.git?.repository_url) out.repo = String(p.git.repository_url).replace(/\/\/[^/@]*@/, "//");
|
|
2249
|
+
}
|
|
2250
|
+
const ctx = H.concat(T).reverse().find((j) => j.type === "turn_context" && j.payload?.model);
|
|
2251
|
+
if (ctx?.payload?.model) out.model = ctx.payload.model;
|
|
2252
|
+
for (const j of H) {
|
|
2253
|
+
const p = j.payload;
|
|
2254
|
+
if (j.type !== "response_item" || !p || p.type !== "message" || p.role !== "user") continue;
|
|
2255
|
+
const text = (p.content ?? []).filter((b) => b && b.type === "input_text" && typeof b.text === "string").map((b) => b.text).join("\n").trim();
|
|
2256
|
+
if (!text || codexInjected(text)) continue;
|
|
2257
|
+
const line = text.split("\n").find((l) => l.trim()) ?? "";
|
|
2258
|
+
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
2259
|
+
out.title_source = "first-ask";
|
|
2260
|
+
break;
|
|
2261
|
+
}
|
|
2262
|
+
const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
|
|
2263
|
+
if (times.length) {
|
|
2264
|
+
out.started_at = Math.min(...times);
|
|
2265
|
+
out.last_at = Math.max(...times);
|
|
2266
|
+
}
|
|
2267
|
+
return out;
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
// src/adapters/codex-control.ts
|
|
2271
|
+
var codex_control_exports = {};
|
|
2272
|
+
__export(codex_control_exports, {
|
|
2273
|
+
CodexReading: () => CodexReading,
|
|
2274
|
+
LOGIN_HINT: () => LOGIN_HINT,
|
|
2275
|
+
codexArgs: () => codexArgs,
|
|
2276
|
+
codexEnv: () => codexEnv,
|
|
2277
|
+
codexFacts: () => codexFacts,
|
|
2278
|
+
codexLine: () => codexLine,
|
|
2279
|
+
codexLogin: () => codexLogin,
|
|
2280
|
+
codexModeFlags: () => codexModeFlags,
|
|
2281
|
+
findCodex: () => findCodex,
|
|
2282
|
+
findRollout: () => findRollout,
|
|
2283
|
+
reading: () => reading2
|
|
2284
|
+
});
|
|
2285
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
2286
|
+
import os4 from "node:os";
|
|
2287
|
+
import path10 from "node:path";
|
|
2288
|
+
var LABEL2 = "Codex";
|
|
2289
|
+
function findCodex(env = process.env, home = os4.homedir(), nodeDir = path10.dirname(process.execPath)) {
|
|
2290
|
+
return findBinary({
|
|
2291
|
+
override: env.SESSIONPIPE_CODEX,
|
|
2292
|
+
names: process.platform === "win32" ? ["codex.exe", "codex.cmd"] : ["codex"],
|
|
2293
|
+
env,
|
|
2294
|
+
dirs: [nodeDir, path10.join(home, ".local", "bin"), "/opt/homebrew/bin", "/usr/local/bin"]
|
|
2295
|
+
});
|
|
2296
|
+
}
|
|
2297
|
+
function codexModeFlags(mode, resume = false) {
|
|
2298
|
+
if (resume)
|
|
2299
|
+
return mode === "auto" ? [
|
|
2300
|
+
"-c",
|
|
2301
|
+
'sandbox_mode="workspace-write"',
|
|
2302
|
+
"-c",
|
|
2303
|
+
'approval_policy="on-request"',
|
|
2304
|
+
"-c",
|
|
2305
|
+
'approvals_reviewer="auto_review"'
|
|
2306
|
+
] : ["-c", 'sandbox_mode="workspace-write"'];
|
|
2307
|
+
return mode === "auto" ? ["--approve-for-me"] : ["-s", "workspace-write"];
|
|
2308
|
+
}
|
|
2309
|
+
function codexArgs(o) {
|
|
2310
|
+
const common = ["--json", "--skip-git-repo-check"];
|
|
2311
|
+
if (o.resume) return ["exec", "resume", ...common, ...codexModeFlags(o.mode, true), "--", o.resume, o.prompt];
|
|
2312
|
+
return ["exec", ...common, ...o.cwd ? ["-C", o.cwd] : [], ...codexModeFlags(o.mode), "--", o.prompt];
|
|
2313
|
+
}
|
|
2314
|
+
var codexEnv = (env = process.env) => jobEnv(env);
|
|
2315
|
+
var LOGIN_HINT = "Codex isn't signed in on this machine: run `codex login --device-auth` there.";
|
|
2316
|
+
function codexLogin(bin, env = process.env) {
|
|
2317
|
+
if (env.CODEX_API_KEY) return Promise.resolve({ loggedIn: true, method: "api key" });
|
|
2318
|
+
return new Promise((resolve) => {
|
|
2319
|
+
execFile2(
|
|
2320
|
+
bin,
|
|
2321
|
+
["login", "status"],
|
|
2322
|
+
{ encoding: "utf8", timeout: 2e4, env: codexEnv(env), maxBuffer: 256 * 1024 },
|
|
2323
|
+
(err) => {
|
|
2324
|
+
if (!err) return resolve({ loggedIn: true });
|
|
2325
|
+
const code = err.code;
|
|
2326
|
+
resolve(code === 1 ? { loggedIn: false } : { loggedIn: null });
|
|
2252
2327
|
}
|
|
2253
|
-
|
|
2254
|
-
});
|
|
2328
|
+
);
|
|
2255
2329
|
});
|
|
2256
2330
|
}
|
|
2257
|
-
function
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2331
|
+
function codexLine(line) {
|
|
2332
|
+
let j;
|
|
2333
|
+
try {
|
|
2334
|
+
j = JSON.parse(line);
|
|
2335
|
+
} catch {
|
|
2336
|
+
return null;
|
|
2337
|
+
}
|
|
2338
|
+
if (!j || typeof j !== "object") return null;
|
|
2339
|
+
const str = (v) => typeof v === "string" ? v : "";
|
|
2340
|
+
switch (j.type) {
|
|
2341
|
+
case "thread.started":
|
|
2342
|
+
return str(j.thread_id) ? { session: str(j.thread_id) } : null;
|
|
2343
|
+
case "item.completed": {
|
|
2344
|
+
const it = j.item;
|
|
2345
|
+
if (it?.type === "agent_message" && str(it.text)) return { message: str(it.text) };
|
|
2346
|
+
if (it?.type === "error" && str(it.message)) return { warning: str(it.message) };
|
|
2263
2347
|
return null;
|
|
2264
2348
|
}
|
|
2265
|
-
|
|
2266
|
-
|
|
2349
|
+
case "turn.completed":
|
|
2350
|
+
return { done: true };
|
|
2351
|
+
case "turn.failed":
|
|
2352
|
+
return { done: true, failed: str(j.error?.message) || "Codex reported an error" };
|
|
2353
|
+
case "error":
|
|
2354
|
+
return str(j.message) ? { note: str(j.message) } : null;
|
|
2355
|
+
default:
|
|
2356
|
+
return null;
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
var CodexReading = class {
|
|
2360
|
+
session = null;
|
|
2361
|
+
lines = 0;
|
|
2362
|
+
warnings = [];
|
|
2363
|
+
messages = [];
|
|
2364
|
+
done = false;
|
|
2365
|
+
failed = null;
|
|
2366
|
+
note = null;
|
|
2367
|
+
get text() {
|
|
2368
|
+
return this.messages.join("\n\n");
|
|
2369
|
+
}
|
|
2370
|
+
feed(line) {
|
|
2371
|
+
this.lines++;
|
|
2372
|
+
const p = codexLine(line);
|
|
2373
|
+
if (!p) return false;
|
|
2374
|
+
if (p.session && !this.session) this.session = p.session;
|
|
2375
|
+
if (p.note) this.note = p.note;
|
|
2376
|
+
if (p.warning && this.warnings.length < 20) this.warnings.push(p.warning);
|
|
2377
|
+
if (p.failed) this.failed = p.failed;
|
|
2378
|
+
if (p.done) this.done = true;
|
|
2379
|
+
if (p.message?.trim()) {
|
|
2380
|
+
this.messages.push(p.message.trim());
|
|
2381
|
+
return true;
|
|
2382
|
+
}
|
|
2383
|
+
return false;
|
|
2384
|
+
}
|
|
2385
|
+
/** A failure is `turn.failed`, or a run that exits non-zero having said nothing; an
|
|
2386
|
+
* `error` item before a turn that then succeeds is only a warning. */
|
|
2387
|
+
outcome(res) {
|
|
2388
|
+
const broke = runFailure(LABEL2, res);
|
|
2389
|
+
if (broke) return { failed: broke };
|
|
2390
|
+
if (this.failed) return { failed: this.failed };
|
|
2391
|
+
if (this.done || res.code === 0 || this.messages.length)
|
|
2392
|
+
return { reply: this.text || "(Codex finished without a written reply.)" };
|
|
2393
|
+
const why = this.note || codexStderr(res.stderr) || this.warnings.at(-1) || "";
|
|
2394
|
+
return { failed: `Codex exited (${res.signal || `code ${res.code}`}) without an answer${why ? `: ${why}` : ""}` };
|
|
2395
|
+
}
|
|
2396
|
+
};
|
|
2397
|
+
function codexStderr(stderr) {
|
|
2398
|
+
return stderrTail(
|
|
2399
|
+
String(stderr || "").split("\n").filter((l) => !/^Reading additional input from stdin/.test(l.trim())).join("\n")
|
|
2400
|
+
);
|
|
2401
|
+
}
|
|
2402
|
+
var reading2 = () => new CodexReading();
|
|
2403
|
+
|
|
2404
|
+
// src/adapters/gemini-cli.ts
|
|
2405
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
2406
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6 } from "node:fs";
|
|
2407
|
+
import path11 from "node:path";
|
|
2408
|
+
var NAME4 = "gemini-cli";
|
|
2409
|
+
var GEMINI_EVENTS = [
|
|
2410
|
+
"SessionStart",
|
|
2411
|
+
"BeforeAgent",
|
|
2412
|
+
"BeforeTool",
|
|
2413
|
+
"AfterTool",
|
|
2414
|
+
"Notification",
|
|
2415
|
+
"AfterAgent",
|
|
2416
|
+
"PreCompress",
|
|
2417
|
+
"SessionEnd"
|
|
2418
|
+
];
|
|
2419
|
+
var OPTS3 = { matcherFor: (e) => /Tool/.test(e) ? ".*" : void 0, timeout: 5e3 };
|
|
2420
|
+
var geminiDir = (env) => env.GEMINI_CLI_HOME || path11.join(HOME, ".gemini");
|
|
2421
|
+
var settings = (env) => path11.join(geminiDir(env), "settings.json");
|
|
2422
|
+
var validTool4 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
2423
|
+
var eventsFor3 = (lean) => lean ? GEMINI_EVENTS.filter((e) => e !== "BeforeTool") : [...GEMINI_EVENTS];
|
|
2424
|
+
function geminiHooksActive(settingsFile) {
|
|
2425
|
+
let tools;
|
|
2267
2426
|
try {
|
|
2268
|
-
|
|
2427
|
+
tools = JSON.parse(readFileSync6(settingsFile, "utf8")).tools;
|
|
2269
2428
|
} catch {
|
|
2270
2429
|
}
|
|
2271
|
-
|
|
2272
|
-
|
|
2430
|
+
if (tools?.enableHooks === true) return { active: true };
|
|
2431
|
+
if (tools?.enableHooks === false)
|
|
2432
|
+
return { active: false, note: "inactive: tools.enableHooks is false in settings.json" };
|
|
2433
|
+
let version = "";
|
|
2434
|
+
try {
|
|
2435
|
+
version = execFileSync4("gemini", ["--version"], {
|
|
2436
|
+
encoding: "utf8",
|
|
2437
|
+
timeout: 4e3,
|
|
2438
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
2439
|
+
}).trim();
|
|
2440
|
+
} catch {
|
|
2441
|
+
return {
|
|
2442
|
+
active: true,
|
|
2443
|
+
note: "could not run `gemini --version`; hooks need tools.enableHooks on versions before 0.46"
|
|
2444
|
+
};
|
|
2445
|
+
}
|
|
2446
|
+
const m = /(\d+)\.(\d+)/.exec(version);
|
|
2447
|
+
if (m && (Number(m[1]) > 0 || Number(m[2]) >= 46)) return { active: true };
|
|
2448
|
+
return {
|
|
2449
|
+
active: false,
|
|
2450
|
+
note: `inactive: Gemini CLI ${version || "?"} runs hooks only with "tools": {"enableHooks": true} in settings.json`
|
|
2451
|
+
};
|
|
2452
|
+
}
|
|
2453
|
+
var geminiCli = {
|
|
2454
|
+
name: NAME4,
|
|
2455
|
+
events: GEMINI_EVENTS,
|
|
2456
|
+
// ~/.gemini also belongs to Antigravity; Gemini CLI is here when its settings or its chat logs exist.
|
|
2457
|
+
detect: (env = process.env) => existsSync5(settings(env)) || existsSync5(path11.join(geminiDir(env), "tmp")),
|
|
2458
|
+
configFiles: (env = process.env) => [settings(env)],
|
|
2459
|
+
install: (cmd, env = process.env, opts = {}) => {
|
|
2460
|
+
const r = installClaudeShaped(settings(env), NAME4, eventsFor3(opts.lean), cmd, OPTS3);
|
|
2461
|
+
const a = geminiHooksActive(settings(env));
|
|
2462
|
+
return [a.note && !r.skipped ? { ...r, note: [r.note, a.note].filter(Boolean).join("; ") } : r];
|
|
2463
|
+
},
|
|
2464
|
+
uninstall: (env = process.env, opts = {}) => [
|
|
2465
|
+
uninstallClaudeShaped(settings(env), GEMINI_EVENTS, { created: opts.created?.includes(settings(env)) })
|
|
2466
|
+
],
|
|
2467
|
+
installed: (cmd, env = process.env, opts = {}) => {
|
|
2468
|
+
const state = installedClaudeShaped(settings(env), NAME4, eventsFor3(opts.lean), cmd, OPTS3);
|
|
2469
|
+
const a = geminiHooksActive(settings(env));
|
|
2470
|
+
return [
|
|
2471
|
+
{
|
|
2472
|
+
file: settings(env),
|
|
2473
|
+
state: state === "current" && !a.active ? "inactive" : state,
|
|
2474
|
+
...a.note ? { note: a.note } : {}
|
|
2475
|
+
}
|
|
2476
|
+
];
|
|
2477
|
+
},
|
|
2478
|
+
fromHook(input) {
|
|
2479
|
+
const event = input.argv[1] ?? "";
|
|
2480
|
+
let s = {};
|
|
2273
2481
|
try {
|
|
2274
|
-
|
|
2275
|
-
if (j && (j.type === "result" || "result" in j)) return j;
|
|
2482
|
+
s = input.stdin ? JSON.parse(input.stdin) : {};
|
|
2276
2483
|
} catch {
|
|
2277
2484
|
}
|
|
2278
|
-
|
|
2279
|
-
|
|
2485
|
+
const id = s.session_id;
|
|
2486
|
+
if (!id) return null;
|
|
2487
|
+
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
2488
|
+
const tool = validTool4(s.tool_name) ? s.tool_name : void 0;
|
|
2489
|
+
const ev = (type, data) => ({ type, data: prune4(data), harnessEvent: event });
|
|
2490
|
+
const events = [];
|
|
2491
|
+
switch (event) {
|
|
2492
|
+
case "SessionStart":
|
|
2493
|
+
events.push(
|
|
2494
|
+
ev("session.started", {
|
|
2495
|
+
source: typeof s.source === "string" && ["startup", "resume", "clear"].includes(s.source) ? s.source : "unknown"
|
|
2496
|
+
})
|
|
2497
|
+
);
|
|
2498
|
+
break;
|
|
2499
|
+
case "BeforeAgent":
|
|
2500
|
+
events.push(
|
|
2501
|
+
ev("turn.started", { prompt_chars: typeof s.prompt === "string" ? [...s.prompt].length : void 0 })
|
|
2502
|
+
);
|
|
2503
|
+
break;
|
|
2504
|
+
case "BeforeTool":
|
|
2505
|
+
if (tool) events.push(ev("tool.started", { tool, input: s.tool_input }));
|
|
2506
|
+
break;
|
|
2507
|
+
case "AfterTool": {
|
|
2508
|
+
const resp = s.tool_response;
|
|
2509
|
+
const err = resp && typeof resp === "object" && resp.error ? String(typeof resp.error === "string" ? resp.error : JSON.stringify(resp.error)).slice(0, 2e3) : void 0;
|
|
2510
|
+
if (tool)
|
|
2511
|
+
events.push(
|
|
2512
|
+
ev("tool.ended", {
|
|
2513
|
+
tool,
|
|
2514
|
+
ok: !err,
|
|
2515
|
+
error: err,
|
|
2516
|
+
input: s.tool_input,
|
|
2517
|
+
output: resp?.llmContent ?? s.tool_response
|
|
2518
|
+
})
|
|
2519
|
+
);
|
|
2520
|
+
break;
|
|
2521
|
+
}
|
|
2522
|
+
case "Notification":
|
|
2523
|
+
events.push(
|
|
2524
|
+
ev("attention.needed", {
|
|
2525
|
+
attention_id: `notif-${Date.now()}`,
|
|
2526
|
+
kind: s.notification_type === "ToolPermission" ? "permission" : "question",
|
|
2527
|
+
message: typeof s.message === "string" ? s.message.slice(0, 200) : void 0
|
|
2528
|
+
})
|
|
2529
|
+
);
|
|
2530
|
+
break;
|
|
2531
|
+
case "AfterAgent":
|
|
2532
|
+
events.push(ev("turn.ended", { reason: "stop" }));
|
|
2533
|
+
break;
|
|
2534
|
+
case "PreCompress":
|
|
2535
|
+
events.push(ev("context.compacted", { trigger: s.trigger === "manual" ? "manual" : "auto" }));
|
|
2536
|
+
break;
|
|
2537
|
+
case "SessionEnd":
|
|
2538
|
+
events.push(
|
|
2539
|
+
ev("session.ended", {
|
|
2540
|
+
reason: s.reason === "clear" || s.reason === "logout" || s.reason === "exit" ? s.reason : s.reason === "prompt_input_exit" ? "exit" : "other"
|
|
2541
|
+
})
|
|
2542
|
+
);
|
|
2543
|
+
break;
|
|
2544
|
+
default:
|
|
2545
|
+
return null;
|
|
2546
|
+
}
|
|
2547
|
+
const r = { session, events, hints: {} };
|
|
2548
|
+
if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
|
|
2549
|
+
return r;
|
|
2550
|
+
},
|
|
2551
|
+
readTranscript(file, fromLine) {
|
|
2552
|
+
let text = "";
|
|
2553
|
+
try {
|
|
2554
|
+
text = readFileSync6(file, "utf8");
|
|
2555
|
+
} catch {
|
|
2556
|
+
return { turns: [], line: fromLine };
|
|
2557
|
+
}
|
|
2558
|
+
return readGemini(text.split("\n"), fromLine);
|
|
2559
|
+
},
|
|
2560
|
+
facts(_session, transcript) {
|
|
2561
|
+
const out = {};
|
|
2562
|
+
if (!transcript) return out;
|
|
2563
|
+
const { head, tail } = ends(transcript, 64 * 1024, 64 * 1024);
|
|
2564
|
+
const H = parseLines(head);
|
|
2565
|
+
const T = parseLines(tail);
|
|
2566
|
+
const first = H.find((j) => j.type === "user");
|
|
2567
|
+
const raw = first ? first.content ?? first.message ?? first.text : void 0;
|
|
2568
|
+
const text = typeof raw === "string" ? raw.trim() : "";
|
|
2569
|
+
if (text) {
|
|
2570
|
+
const line = text.split("\n").find((l) => l.trim()) ?? "";
|
|
2571
|
+
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
2572
|
+
out.title_source = "first-ask";
|
|
2573
|
+
}
|
|
2574
|
+
const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
|
|
2575
|
+
if (times.length) {
|
|
2576
|
+
out.started_at = Math.min(...times);
|
|
2577
|
+
out.last_at = Math.max(...times);
|
|
2578
|
+
}
|
|
2579
|
+
return out;
|
|
2580
|
+
},
|
|
2581
|
+
backfill: () => []
|
|
2582
|
+
// chat logs are keyed by a project hash, not a session id we can name; the first hook of a resumed session reports it
|
|
2583
|
+
};
|
|
2584
|
+
function prune4(o) {
|
|
2585
|
+
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
2586
|
+
return o;
|
|
2280
2587
|
}
|
|
2281
|
-
var isUuid = (s) => typeof s === "string" && UUID_RE.test(s);
|
|
2282
2588
|
|
|
2283
2589
|
// src/adapters/index.ts
|
|
2284
2590
|
var ADAPTERS = [claudeCode, codex, geminiCli, antigravity];
|
|
2285
2591
|
var adapterByName = (name) => ADAPTERS.find((a) => a.name === name);
|
|
2592
|
+
function drivableHarnesses(env = process.env) {
|
|
2593
|
+
return [
|
|
2594
|
+
...findClaude(env) ? ["claude-code"] : [],
|
|
2595
|
+
...findCodex(env) ? ["codex"] : [],
|
|
2596
|
+
...findAgy(env) ? ["antigravity"] : []
|
|
2597
|
+
];
|
|
2598
|
+
}
|
|
2286
2599
|
|
|
2287
2600
|
// src/config.ts
|
|
2288
2601
|
import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
|
|
2289
|
-
import
|
|
2602
|
+
import path12 from "node:path";
|
|
2290
2603
|
var DEFAULT_CONFIG = { sinks: [], harnesses: {}, update_check: true, keep_days: 30 };
|
|
2291
2604
|
function readConfig(file = configFile()) {
|
|
2292
2605
|
try {
|
|
@@ -2302,7 +2615,7 @@ function readConfig(file = configFile()) {
|
|
|
2302
2615
|
}
|
|
2303
2616
|
}
|
|
2304
2617
|
function writeConfig(c, file = configFile()) {
|
|
2305
|
-
mkdirSync4(
|
|
2618
|
+
mkdirSync4(path12.dirname(file), { recursive: true, mode: 448 });
|
|
2306
2619
|
writeFileSync4(file, `${JSON.stringify(c, null, 2)}
|
|
2307
2620
|
`, { mode: 384 });
|
|
2308
2621
|
if (process.platform !== "win32" && existsSync6(file)) {
|
|
@@ -2379,24 +2692,24 @@ import {
|
|
|
2379
2692
|
readFileSync as readFileSync8,
|
|
2380
2693
|
readSync as readSync3,
|
|
2381
2694
|
renameSync,
|
|
2382
|
-
statSync as
|
|
2695
|
+
statSync as statSync6,
|
|
2383
2696
|
unlinkSync as unlinkSync3,
|
|
2384
2697
|
writeFileSync as writeFileSync5
|
|
2385
2698
|
} from "node:fs";
|
|
2386
|
-
import
|
|
2699
|
+
import path13 from "node:path";
|
|
2387
2700
|
var Outbox = class {
|
|
2388
2701
|
constructor(root) {
|
|
2389
2702
|
this.root = root;
|
|
2390
2703
|
}
|
|
2391
2704
|
root;
|
|
2392
2705
|
dir(harness) {
|
|
2393
|
-
return
|
|
2706
|
+
return path13.join(this.root, "outbox", safeId(harness));
|
|
2394
2707
|
}
|
|
2395
2708
|
file(ref) {
|
|
2396
|
-
return
|
|
2709
|
+
return path13.join(this.dir(ref.harness), `${safeId(ref.session)}.jsonl`);
|
|
2397
2710
|
}
|
|
2398
2711
|
seqFile(ref) {
|
|
2399
|
-
return
|
|
2712
|
+
return path13.join(this.dir(ref.harness), `${safeId(ref.session)}.seq`);
|
|
2400
2713
|
}
|
|
2401
2714
|
/** The next seq for a session, persisted (survives a hibernating machine and a
|
|
2402
2715
|
* restart; an in-memory counter would restart at 0 and collide). */
|
|
@@ -2408,7 +2721,7 @@ var Outbox = class {
|
|
|
2408
2721
|
} catch {
|
|
2409
2722
|
n = this.lastSeqInFile(ref) + 1;
|
|
2410
2723
|
}
|
|
2411
|
-
mkdirSync5(
|
|
2724
|
+
mkdirSync5(path13.dirname(f), { recursive: true, mode: 448 });
|
|
2412
2725
|
writeFileSync5(f, String(n + 1), { mode: 384 });
|
|
2413
2726
|
return n;
|
|
2414
2727
|
}
|
|
@@ -2434,10 +2747,10 @@ var Outbox = class {
|
|
|
2434
2747
|
/** Every session file, newest first. */
|
|
2435
2748
|
sessions() {
|
|
2436
2749
|
const out = [];
|
|
2437
|
-
const base =
|
|
2750
|
+
const base = path13.join(this.root, "outbox");
|
|
2438
2751
|
if (!existsSync7(base)) return [];
|
|
2439
2752
|
for (const h of readdirSync6(base)) {
|
|
2440
|
-
const d =
|
|
2753
|
+
const d = path13.join(base, h);
|
|
2441
2754
|
let names = [];
|
|
2442
2755
|
try {
|
|
2443
2756
|
names = readdirSync6(d).filter((n) => n.endsWith(".jsonl"));
|
|
@@ -2446,7 +2759,7 @@ var Outbox = class {
|
|
|
2446
2759
|
}
|
|
2447
2760
|
for (const n of names) {
|
|
2448
2761
|
try {
|
|
2449
|
-
out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime:
|
|
2762
|
+
out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime: statSync6(path13.join(d, n)).mtimeMs });
|
|
2450
2763
|
} catch {
|
|
2451
2764
|
}
|
|
2452
2765
|
}
|
|
@@ -2458,7 +2771,7 @@ var Outbox = class {
|
|
|
2458
2771
|
read(ref, from, maxEvents = 500) {
|
|
2459
2772
|
const f = this.file(ref);
|
|
2460
2773
|
if (!existsSync7(f)) return { events: [], offset: from };
|
|
2461
|
-
const size =
|
|
2774
|
+
const size = statSync6(f).size;
|
|
2462
2775
|
if (from >= size) return { events: [], offset: from };
|
|
2463
2776
|
const fd = openSync3(f, "r");
|
|
2464
2777
|
try {
|
|
@@ -2492,7 +2805,7 @@ var Outbox = class {
|
|
|
2492
2805
|
for (const ref of this.sessions()) {
|
|
2493
2806
|
const f = this.file(ref);
|
|
2494
2807
|
try {
|
|
2495
|
-
if (
|
|
2808
|
+
if (statSync6(f).mtimeMs < cutoff) {
|
|
2496
2809
|
unlinkSync3(f);
|
|
2497
2810
|
try {
|
|
2498
2811
|
unlinkSync3(this.seqFile(ref));
|
|
@@ -2522,7 +2835,7 @@ var Cursors = class {
|
|
|
2522
2835
|
root;
|
|
2523
2836
|
sink;
|
|
2524
2837
|
file() {
|
|
2525
|
-
return
|
|
2838
|
+
return path13.join(this.root, "cursors", `${safeId(this.sink)}.json`);
|
|
2526
2839
|
}
|
|
2527
2840
|
read() {
|
|
2528
2841
|
try {
|
|
@@ -2533,7 +2846,7 @@ var Cursors = class {
|
|
|
2533
2846
|
}
|
|
2534
2847
|
write(c) {
|
|
2535
2848
|
const f = this.file();
|
|
2536
|
-
mkdirSync5(
|
|
2849
|
+
mkdirSync5(path13.dirname(f), { recursive: true, mode: 448 });
|
|
2537
2850
|
const tmp = `${f}.${process.pid}.tmp`;
|
|
2538
2851
|
writeFileSync5(tmp, JSON.stringify(c), { mode: 384 });
|
|
2539
2852
|
renameSync(tmp, f);
|
|
@@ -2543,11 +2856,11 @@ var Cursors = class {
|
|
|
2543
2856
|
}
|
|
2544
2857
|
};
|
|
2545
2858
|
function takeLock(root, ref, staleMs = 5 * 6e4) {
|
|
2546
|
-
const dir =
|
|
2859
|
+
const dir = path13.join(root, "locks");
|
|
2547
2860
|
mkdirSync5(dir, { recursive: true, mode: 448 });
|
|
2548
|
-
const f =
|
|
2861
|
+
const f = path13.join(dir, `${safeId(ref.harness)}-${safeId(ref.session)}.lock`);
|
|
2549
2862
|
try {
|
|
2550
|
-
if (Date.now() -
|
|
2863
|
+
if (Date.now() - statSync6(f).mtimeMs > staleMs) unlinkSync3(f);
|
|
2551
2864
|
else return null;
|
|
2552
2865
|
} catch {
|
|
2553
2866
|
}
|
|
@@ -2864,7 +3177,7 @@ function filterEvent(event, tier, pii, opts = {}) {
|
|
|
2864
3177
|
|
|
2865
3178
|
// src/sinks/file.ts
|
|
2866
3179
|
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync6 } from "node:fs";
|
|
2867
|
-
import
|
|
3180
|
+
import path14 from "node:path";
|
|
2868
3181
|
var FileSink = class {
|
|
2869
3182
|
constructor(name, file, tier, pii) {
|
|
2870
3183
|
this.name = name;
|
|
@@ -2878,7 +3191,7 @@ var FileSink = class {
|
|
|
2878
3191
|
pii;
|
|
2879
3192
|
async send(events) {
|
|
2880
3193
|
try {
|
|
2881
|
-
mkdirSync6(
|
|
3194
|
+
mkdirSync6(path14.dirname(this.file), { recursive: true });
|
|
2882
3195
|
appendFileSync2(this.file, `${events.map((e) => JSON.stringify(e)).join("\n")}
|
|
2883
3196
|
`);
|
|
2884
3197
|
return { ok: true, acknowledged: events.length, accepted: events.length, duplicates: 0, rejected: [] };
|
|
@@ -2916,6 +3229,7 @@ export {
|
|
|
2916
3229
|
DEFAULT_CONFIG,
|
|
2917
3230
|
FileSink,
|
|
2918
3231
|
HOME,
|
|
3232
|
+
JOB_TIMEOUT_MS,
|
|
2919
3233
|
Outbox,
|
|
2920
3234
|
PROTOCOL_VERSION,
|
|
2921
3235
|
REDACTED,
|
|
@@ -2926,11 +3240,15 @@ export {
|
|
|
2926
3240
|
adapterByName,
|
|
2927
3241
|
antigravity,
|
|
2928
3242
|
antigravityAnswer,
|
|
3243
|
+
antigravity_control_exports as antigravityControl,
|
|
2929
3244
|
claudeCode,
|
|
2930
3245
|
claude_code_control_exports as claudeControl,
|
|
2931
3246
|
claudeDirs,
|
|
2932
3247
|
codex,
|
|
3248
|
+
codex_control_exports as codexControl,
|
|
2933
3249
|
configFile,
|
|
3250
|
+
deniedNote,
|
|
3251
|
+
drivableHarnesses,
|
|
2934
3252
|
filterEvent,
|
|
2935
3253
|
geminiCli,
|
|
2936
3254
|
gitFacts,
|
|
@@ -2939,6 +3257,7 @@ export {
|
|
|
2939
3257
|
isInjected,
|
|
2940
3258
|
isSecretName,
|
|
2941
3259
|
isUlid,
|
|
3260
|
+
jobEnv,
|
|
2942
3261
|
machineName,
|
|
2943
3262
|
makeEvent,
|
|
2944
3263
|
pairTurns,
|
|
@@ -2951,6 +3270,7 @@ export {
|
|
|
2951
3270
|
redactSecrets,
|
|
2952
3271
|
reducePii,
|
|
2953
3272
|
reducePiiDeep,
|
|
3273
|
+
runHeadless,
|
|
2954
3274
|
safeId,
|
|
2955
3275
|
stateDir,
|
|
2956
3276
|
stripCredentials,
|