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