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