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