sessionpipe 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +5165 -2399
- package/dist/cli.js.map +4 -4
- package/dist/control/cli.d.ts +4 -0
- package/dist/control/cli.d.ts.map +1 -1
- package/dist/control/daemon.d.ts +24 -0
- package/dist/control/daemon.d.ts.map +1 -1
- package/dist/control/local.d.ts +24 -0
- package/dist/control/local.d.ts.map +1 -1
- package/dist/control/pool.d.ts +49 -0
- package/dist/control/pool.d.ts.map +1 -0
- package/dist/control/route.d.ts +1 -1
- package/dist/control/route.d.ts.map +1 -1
- package/dist/help.d.ts.map +1 -1
- package/dist/hook.d.ts.map +1 -1
- package/dist/hook.js +38 -2
- package/dist/hook.js.map +2 -2
- package/dist/index.js +1135 -880
- package/dist/index.js.map +4 -4
- package/dist/pool/cli.d.ts +39 -0
- package/dist/pool/cli.d.ts.map +1 -0
- package/dist/pool/credentials.d.ts +44 -0
- package/dist/pool/credentials.d.ts.map +1 -0
- package/dist/pool/import.d.ts +23 -0
- package/dist/pool/import.d.ts.map +1 -0
- package/dist/pool/index.d.ts +138 -0
- package/dist/pool/index.d.ts.map +1 -0
- package/dist/pool/install.d.ts +46 -0
- package/dist/pool/install.d.ts.map +1 -0
- package/dist/pool/login.d.ts +27 -0
- package/dist/pool/login.d.ts.map +1 -0
- package/dist/pool/policy.d.ts +100 -0
- package/dist/pool/policy.d.ts.map +1 -0
- package/dist/pool/server.d.ts +76 -0
- package/dist/pool/server.d.ts.map +1 -0
- package/dist/pool/store.d.ts +89 -0
- package/dist/pool/store.d.ts.map +1 -0
- package/dist/pool/types.d.ts +92 -0
- package/dist/pool/types.d.ts.map +1 -0
- package/dist/run.d.ts +14 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/worker.js +1143 -888
- package/dist/worker.js.map +4 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// sessionpipe 0.
|
|
1
|
+
// sessionpipe 0.12.0 · Apache-2.0 · https://sessionpipe.org
|
|
2
2
|
|
|
3
3
|
// src/http-sink.ts
|
|
4
4
|
import { createHash, createHmac } from "node:crypto";
|
|
@@ -101,27 +101,14 @@ import {
|
|
|
101
101
|
existsSync as existsSync10,
|
|
102
102
|
mkdirSync as mkdirSync8,
|
|
103
103
|
readdirSync as readdirSync6,
|
|
104
|
-
readFileSync as
|
|
104
|
+
readFileSync as readFileSync10,
|
|
105
105
|
renameSync as renameSync3,
|
|
106
|
-
statSync as
|
|
106
|
+
statSync as statSync6,
|
|
107
107
|
unlinkSync as unlinkSync4,
|
|
108
|
-
writeFileSync as
|
|
108
|
+
writeFileSync as writeFileSync8
|
|
109
109
|
} from "node:fs";
|
|
110
110
|
import os3 from "node:os";
|
|
111
|
-
import
|
|
112
|
-
|
|
113
|
-
// ../core/src/adapters/antigravity.ts
|
|
114
|
-
import {
|
|
115
|
-
existsSync as existsSync2,
|
|
116
|
-
mkdirSync as mkdirSync2,
|
|
117
|
-
readdirSync as readdirSync2,
|
|
118
|
-
readFileSync as readFileSync2,
|
|
119
|
-
realpathSync,
|
|
120
|
-
statSync as statSync2,
|
|
121
|
-
unlinkSync as unlinkSync2,
|
|
122
|
-
writeFileSync as writeFileSync2
|
|
123
|
-
} from "node:fs";
|
|
124
|
-
import path4 from "node:path";
|
|
111
|
+
import path15 from "node:path";
|
|
125
112
|
|
|
126
113
|
// ../core/src/paths.ts
|
|
127
114
|
import os from "node:os";
|
|
@@ -146,6 +133,13 @@ function tilde(p, home = homeDir()) {
|
|
|
146
133
|
}
|
|
147
134
|
var safeId = (s) => String(s).replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 160);
|
|
148
135
|
|
|
136
|
+
// ../core/src/adapters/claude-code.ts
|
|
137
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
138
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
139
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
140
|
+
import { userInfo } from "node:os";
|
|
141
|
+
import path4 from "node:path";
|
|
142
|
+
|
|
149
143
|
// ../core/src/readers/files.ts
|
|
150
144
|
import { closeSync, fstatSync, openSync, readdirSync, readSync, statSync } from "node:fs";
|
|
151
145
|
import path2 from "node:path";
|
|
@@ -236,6 +230,38 @@ function recentFiles(root, depth, sinceMs, match) {
|
|
|
236
230
|
return out;
|
|
237
231
|
}
|
|
238
232
|
|
|
233
|
+
// ../core/src/readers/git.ts
|
|
234
|
+
import { execFileSync } from "node:child_process";
|
|
235
|
+
var cache = /* @__PURE__ */ new Map();
|
|
236
|
+
function gitFacts(cwd) {
|
|
237
|
+
if (!cwd) return {};
|
|
238
|
+
const hit = cache.get(cwd);
|
|
239
|
+
if (hit) return hit;
|
|
240
|
+
const run = (args) => {
|
|
241
|
+
try {
|
|
242
|
+
return execFileSync("git", ["-C", cwd, ...args], {
|
|
243
|
+
encoding: "utf8",
|
|
244
|
+
timeout: 1500,
|
|
245
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
246
|
+
}).trim();
|
|
247
|
+
} catch {
|
|
248
|
+
return "";
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
const out = {};
|
|
252
|
+
const repo = run(["remote", "get-url", "origin"]);
|
|
253
|
+
if (repo) out.repo = stripCredentials(repo);
|
|
254
|
+
const branch = run(["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
255
|
+
if (branch && branch !== "HEAD") out.branch = branch;
|
|
256
|
+
cache.set(cwd, out);
|
|
257
|
+
return out;
|
|
258
|
+
}
|
|
259
|
+
function stripCredentials(url) {
|
|
260
|
+
const ssh = /^(?:ssh:\/\/)?git@([^:/]+)[:/](.+?)(?:\.git)?$/.exec(url);
|
|
261
|
+
if (ssh) return `${ssh[1]}/${ssh[2]}`;
|
|
262
|
+
return url.replace(/\/\/[^/@]*@/, "//").replace(/^https?:\/\//, "").replace(/\.git$/, "");
|
|
263
|
+
}
|
|
264
|
+
|
|
239
265
|
// ../core/src/adapters/codex-rollout.ts
|
|
240
266
|
function codexSubagent(meta) {
|
|
241
267
|
if (meta?.type !== "session_meta") return false;
|
|
@@ -395,8 +421,10 @@ function readJsonFile(file) {
|
|
|
395
421
|
try {
|
|
396
422
|
raw = readFileSync(file, "utf8");
|
|
397
423
|
} catch {
|
|
398
|
-
return { ok: true, existed: false, value: {}, raw: "", indent: " ", eol: "\n", finalNewline: true };
|
|
424
|
+
return { ok: true, existed: false, value: {}, raw: "", indent: " ", eol: "\n", finalNewline: true, bom: false };
|
|
399
425
|
}
|
|
426
|
+
const bom = raw.charCodeAt(0) === 65279;
|
|
427
|
+
if (bom) raw = raw.slice(1);
|
|
400
428
|
let value;
|
|
401
429
|
try {
|
|
402
430
|
value = JSON.parse(raw);
|
|
@@ -416,12 +444,13 @@ function readJsonFile(file) {
|
|
|
416
444
|
raw,
|
|
417
445
|
indent,
|
|
418
446
|
eol,
|
|
419
|
-
finalNewline: /\r?\n$/.test(raw)
|
|
447
|
+
finalNewline: /\r?\n$/.test(raw),
|
|
448
|
+
bom
|
|
420
449
|
};
|
|
421
450
|
}
|
|
422
451
|
function formatJson(value, f) {
|
|
423
452
|
const text = JSON.stringify(value, null, f.indent).replace(/\n/g, f.eol);
|
|
424
|
-
return f.finalNewline ? text + f.eol : text;
|
|
453
|
+
return (f.bom ? "\uFEFF" : "") + (f.finalNewline ? text + f.eol : text);
|
|
425
454
|
}
|
|
426
455
|
function readJson(file) {
|
|
427
456
|
const f = readJsonFile(file);
|
|
@@ -517,74 +546,79 @@ function installedClaudeShaped(file, harness, events, cmd, o) {
|
|
|
517
546
|
return current === events.length ? "current" : "stale";
|
|
518
547
|
}
|
|
519
548
|
|
|
520
|
-
// ../core/src/adapters/
|
|
521
|
-
var NAME = "
|
|
522
|
-
var
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
549
|
+
// ../core/src/adapters/claude-code.ts
|
|
550
|
+
var NAME = "claude-code";
|
|
551
|
+
var CLAUDE_EVENTS = [
|
|
552
|
+
"SessionStart",
|
|
553
|
+
"UserPromptSubmit",
|
|
554
|
+
"PreToolUse",
|
|
555
|
+
"PostToolUse",
|
|
556
|
+
"PostToolUseFailure",
|
|
557
|
+
"PermissionRequest",
|
|
558
|
+
"Notification",
|
|
559
|
+
"Stop",
|
|
560
|
+
"SubagentStart",
|
|
561
|
+
"SubagentStop",
|
|
562
|
+
"PreCompact",
|
|
563
|
+
"PostCompact",
|
|
564
|
+
"SessionEnd",
|
|
565
|
+
"StopFailure"
|
|
566
|
+
];
|
|
567
|
+
var LEAN_SKIP = /* @__PURE__ */ new Set(["PreToolUse", "PostToolUseFailure"]);
|
|
568
|
+
var eventsFor = (lean) => lean ? CLAUDE_EVENTS.filter((e) => !LEAN_SKIP.has(e)) : [...CLAUDE_EVENTS];
|
|
569
|
+
var OPTS = {
|
|
570
|
+
matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0,
|
|
571
|
+
timeout: 5,
|
|
572
|
+
// The hook waits up to 125 s for a verified control answer, and only when control
|
|
573
|
+
// is paired on this machine; everywhere else it exits in milliseconds as before.
|
|
574
|
+
timeoutFor: (e) => e === "PermissionRequest" ? 130 : void 0
|
|
575
|
+
};
|
|
576
|
+
var DESKTOP_ID = /^local_[A-Za-z0-9-]{1,64}$/;
|
|
577
|
+
function claudeDirs(env = process.env, home = env.HOME || homeDir()) {
|
|
578
|
+
const out = [];
|
|
579
|
+
const add = (d) => {
|
|
580
|
+
if (!d) return;
|
|
581
|
+
const r = path4.resolve(d);
|
|
582
|
+
if (!out.includes(r)) out.push(r);
|
|
583
|
+
};
|
|
584
|
+
add(path4.join(home, ".claude"));
|
|
585
|
+
add(env.CLAUDE_CONFIG_DIR);
|
|
586
|
+
try {
|
|
587
|
+
for (const e of readdirSync2(home, { withFileTypes: true })) {
|
|
588
|
+
if (!e.isDirectory() || !/^\.claude[-_.]/.test(e.name)) continue;
|
|
589
|
+
const d = path4.join(home, e.name);
|
|
590
|
+
if (existsSync2(path4.join(d, "projects")) || existsSync2(path4.join(d, "settings.json"))) add(d);
|
|
591
|
+
}
|
|
592
|
+
} catch {
|
|
593
|
+
}
|
|
594
|
+
return out.filter((d, i) => i === 0 || existsSync2(d));
|
|
537
595
|
}
|
|
538
|
-
var
|
|
596
|
+
var settingsIn = (dir) => path4.join(dir, "settings.json");
|
|
597
|
+
var configDirOf = (transcript) => path4.dirname(path4.dirname(path4.dirname(transcript)));
|
|
598
|
+
var validTool = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
599
|
+
var endReason = (r) => r === "clear" || r === "logout" || r === "exit" ? r : r === "prompt_input_exit" ? "exit" : "other";
|
|
600
|
+
var notifKind = (t) => t === "permission_prompt" ? "permission" : t === "idle_prompt" ? "idle" : t === "elicitation_dialog" ? "elicitation" : "question";
|
|
601
|
+
var claudeCode = {
|
|
539
602
|
name: NAME,
|
|
540
|
-
events:
|
|
541
|
-
detect: (env = process.env) =>
|
|
542
|
-
configFiles: (env = process.env) =>
|
|
543
|
-
install(cmd, env = process.env) {
|
|
544
|
-
const
|
|
545
|
-
const
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
hooks
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
return
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
}
|
|
560
|
-
];
|
|
561
|
-
},
|
|
562
|
-
uninstall(env = process.env, opts = {}) {
|
|
563
|
-
const file = hooksFile(env);
|
|
564
|
-
if (!existsSync2(file)) return [{ file, changed: false }];
|
|
565
|
-
const f = readJsonFile(file);
|
|
566
|
-
if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
|
|
567
|
-
const hooks = f.value;
|
|
568
|
-
if (!(HOOK_NAME in hooks)) return [{ file, changed: false }];
|
|
569
|
-
delete hooks[HOOK_NAME];
|
|
570
|
-
if (opts.created?.includes(file) && !Object.keys(hooks).length) {
|
|
571
|
-
unlinkSync2(file);
|
|
572
|
-
return [{ file, changed: true, note: "removed (sessionpipe created it)" }];
|
|
573
|
-
}
|
|
574
|
-
writeFileSync2(file, formatJson(hooks, f));
|
|
575
|
-
return [{ file, changed: true }];
|
|
576
|
-
},
|
|
577
|
-
installed(cmd, env = process.env) {
|
|
578
|
-
const file = hooksFile(env);
|
|
579
|
-
const f = readJsonFile(file);
|
|
580
|
-
const have = f.ok ? f.value[HOOK_NAME] : void 0;
|
|
581
|
-
return [
|
|
582
|
-
{
|
|
583
|
-
file,
|
|
584
|
-
state: !have ? "missing" : JSON.stringify(have) === JSON.stringify(entry(cmd)) ? "current" : "stale"
|
|
585
|
-
}
|
|
586
|
-
];
|
|
587
|
-
},
|
|
603
|
+
events: CLAUDE_EVENTS,
|
|
604
|
+
detect: (env = process.env) => claudeDirs(env).some((d) => existsSync2(d)),
|
|
605
|
+
configFiles: (env = process.env) => claudeDirs(env).map(settingsIn),
|
|
606
|
+
install: (cmd, env = process.env, opts = {}) => claudeDirs(env).flatMap((d) => {
|
|
607
|
+
const hooks = installClaudeShaped(settingsIn(d), NAME, eventsFor(opts.lean), cmd, OPTS);
|
|
608
|
+
const line = opts.statusLine === void 0 ? null : statusLine(settingsIn(d), opts.statusLine, cmd);
|
|
609
|
+
return line ? [hooks, line] : [hooks];
|
|
610
|
+
}),
|
|
611
|
+
uninstall: (env = process.env, opts = {}) => claudeDirs(env).map((d) => {
|
|
612
|
+
const line = statusLine(settingsIn(d), false);
|
|
613
|
+
const hooks = uninstallClaudeShaped(settingsIn(d), CLAUDE_EVENTS, {
|
|
614
|
+
created: opts.created?.includes(settingsIn(d))
|
|
615
|
+
});
|
|
616
|
+
return line && !hooks.changed ? line : hooks;
|
|
617
|
+
}),
|
|
618
|
+
installed: (cmd, env = process.env, opts = {}) => claudeDirs(env).map((d) => ({
|
|
619
|
+
file: settingsIn(d),
|
|
620
|
+
state: installedClaudeShaped(settingsIn(d), NAME, eventsFor(opts.lean), cmd, OPTS)
|
|
621
|
+
})),
|
|
588
622
|
fromHook(input) {
|
|
589
623
|
const event = input.argv[1] ?? "";
|
|
590
624
|
let s = {};
|
|
@@ -593,49 +627,157 @@ var antigravity = {
|
|
|
593
627
|
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
594
628
|
} catch {
|
|
595
629
|
}
|
|
596
|
-
const id = s.
|
|
597
|
-
|
|
598
|
-
if (
|
|
599
|
-
const
|
|
600
|
-
|
|
601
|
-
if (typeof s.
|
|
602
|
-
|
|
603
|
-
const
|
|
630
|
+
const id = s.session_id ?? s.sessionId;
|
|
631
|
+
if (!id) return null;
|
|
632
|
+
if (notClaudeCode(String(id), s)) return null;
|
|
633
|
+
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
634
|
+
if (typeof s.model === "string") session.model = s.model;
|
|
635
|
+
if (typeof s.agent_id === "string" && event.startsWith("Subagent") === false && s.agent_id)
|
|
636
|
+
session.parent_id = null;
|
|
637
|
+
const turn_id = typeof s.prompt_id === "string" ? s.prompt_id : void 0;
|
|
638
|
+
const tool = validTool(s.tool_name) ? s.tool_name : void 0;
|
|
639
|
+
const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
|
|
640
|
+
const ms = typeof s.duration_ms === "number" ? Math.round(s.duration_ms) : void 0;
|
|
641
|
+
const agent_id = typeof s.agent_id === "string" ? s.agent_id : void 0;
|
|
642
|
+
const permissionId = () => `perm-${createHash2("sha256").update(`${turn_id ?? ""}|${tool ?? ""}|${JSON.stringify(s.tool_input ?? null)}`).digest("hex").slice(0, 16)}`;
|
|
604
643
|
const ev = (type, data) => ({ type, data: prune(data), harnessEvent: event });
|
|
605
644
|
const events = [];
|
|
606
645
|
switch (event) {
|
|
607
|
-
case "
|
|
608
|
-
|
|
609
|
-
events.push(ev("session.started", { source: "unknown" }));
|
|
610
|
-
events.push(ev("turn.started", {}));
|
|
611
|
-
} else events.push(ev("session.heartbeat", {}));
|
|
646
|
+
case "SessionStart":
|
|
647
|
+
events.push(ev("session.started", { source: pickSource(s.source ?? s.trigger) }));
|
|
612
648
|
break;
|
|
613
|
-
case "
|
|
649
|
+
case "UserPromptSubmit": {
|
|
650
|
+
const prompt = typeof s.prompt === "string" ? s.prompt : void 0;
|
|
651
|
+
events.push(
|
|
652
|
+
ev("turn.started", {
|
|
653
|
+
turn_id,
|
|
654
|
+
prompt_chars: prompt === void 0 ? void 0 : [...prompt].length,
|
|
655
|
+
// Claude Code's hook schema documents `source`
|
|
656
|
+
// (user | sdk | system | loop_wakeup | schedule_wakeup | poll_event) and says a
|
|
657
|
+
// payload may omit it while the field rolls out; 2.1.304's payload has no such
|
|
658
|
+
// key at all. Forwarded verbatim, shape-guarded only: the list is the harness's
|
|
659
|
+
// to extend.
|
|
660
|
+
submitted_by: harnessWord(s.source),
|
|
661
|
+
// One of Claude Code's own prompt wrappers: the prompt is a block the harness
|
|
662
|
+
// wrote, so it is not the person's words. Absent is unknown, never "the person" —
|
|
663
|
+
// and `true` is never "no person caused this turn", because a Talk or
|
|
664
|
+
// `sessionpipe wait` delivery hands the person's own message over inside a
|
|
665
|
+
// <task-notification> (PROTOCOL.md §4).
|
|
666
|
+
injected: prompt !== void 0 && injectedBlock(prompt) ? true : void 0
|
|
667
|
+
})
|
|
668
|
+
);
|
|
614
669
|
break;
|
|
670
|
+
}
|
|
615
671
|
case "PreToolUse":
|
|
616
|
-
if (tool) events.push(ev("tool.started", { tool, input:
|
|
672
|
+
if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, agent_id, input: s.tool_input }));
|
|
617
673
|
break;
|
|
618
674
|
case "PostToolUse":
|
|
619
|
-
if (
|
|
620
|
-
else
|
|
675
|
+
if (tool)
|
|
621
676
|
events.push(
|
|
622
677
|
ev("tool.ended", {
|
|
623
678
|
tool,
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
679
|
+
call_id,
|
|
680
|
+
turn_id,
|
|
681
|
+
agent_id,
|
|
682
|
+
ms,
|
|
683
|
+
ok: true,
|
|
684
|
+
input: s.tool_input,
|
|
685
|
+
output: s.tool_response
|
|
686
|
+
})
|
|
687
|
+
);
|
|
688
|
+
break;
|
|
689
|
+
case "PostToolUseFailure":
|
|
690
|
+
if (tool)
|
|
691
|
+
events.push(
|
|
692
|
+
ev("tool.ended", {
|
|
693
|
+
tool,
|
|
694
|
+
call_id,
|
|
695
|
+
turn_id,
|
|
696
|
+
agent_id,
|
|
697
|
+
ms,
|
|
698
|
+
ok: false,
|
|
699
|
+
error: errText(s.error ?? s.tool_response),
|
|
700
|
+
input: s.tool_input,
|
|
701
|
+
output: s.tool_response
|
|
627
702
|
})
|
|
628
703
|
);
|
|
629
704
|
break;
|
|
705
|
+
case "PermissionRequest":
|
|
706
|
+
events.push(
|
|
707
|
+
ev("attention.needed", {
|
|
708
|
+
attention_id: call_id ?? permissionId(),
|
|
709
|
+
kind: "permission",
|
|
710
|
+
tool,
|
|
711
|
+
message: tool ? `${tool}${describe(s.tool_input)}` : void 0
|
|
712
|
+
})
|
|
713
|
+
);
|
|
714
|
+
break;
|
|
715
|
+
case "Notification":
|
|
716
|
+
events.push(
|
|
717
|
+
ev("attention.needed", {
|
|
718
|
+
attention_id: `notif-${Date.now()}`,
|
|
719
|
+
kind: notifKind(s.notification_type),
|
|
720
|
+
message: typeof s.message === "string" ? s.message.slice(0, 200) : void 0
|
|
721
|
+
})
|
|
722
|
+
);
|
|
723
|
+
break;
|
|
630
724
|
case "Stop":
|
|
631
|
-
events.push(ev("turn.ended", { reason:
|
|
725
|
+
events.push(ev("turn.ended", { turn_id, reason: "stop", background: backgroundCount(s.background_tasks) }));
|
|
632
726
|
break;
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
727
|
+
case "StopFailure":
|
|
728
|
+
events.push(
|
|
729
|
+
ev("turn.ended", {
|
|
730
|
+
turn_id,
|
|
731
|
+
reason: "error",
|
|
732
|
+
error: typeof s.error === "string" ? s.error.slice(0, 200) : void 0
|
|
733
|
+
})
|
|
734
|
+
);
|
|
735
|
+
break;
|
|
736
|
+
case "SubagentStart":
|
|
737
|
+
events.push(
|
|
738
|
+
ev("subagent.started", {
|
|
739
|
+
agent_id: String(s.agent_id ?? ""),
|
|
740
|
+
agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
|
|
741
|
+
})
|
|
742
|
+
);
|
|
743
|
+
break;
|
|
744
|
+
case "SubagentStop":
|
|
745
|
+
events.push(
|
|
746
|
+
ev("subagent.ended", {
|
|
747
|
+
agent_id: String(s.agent_id ?? ""),
|
|
748
|
+
agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0,
|
|
749
|
+
background: backgroundCount(s.background_tasks, String(s.agent_id ?? ""))
|
|
750
|
+
})
|
|
751
|
+
);
|
|
752
|
+
break;
|
|
753
|
+
case "PreCompact":
|
|
754
|
+
events.push(ev("context.compacted", { trigger: s.trigger === "manual" ? "manual" : "auto" }));
|
|
755
|
+
break;
|
|
756
|
+
case "PostCompact":
|
|
757
|
+
break;
|
|
758
|
+
// PreCompact already said it; a second event would double-count
|
|
759
|
+
case "SessionEnd":
|
|
760
|
+
events.push(ev("session.ended", { reason: endReason(s.reason) }));
|
|
761
|
+
break;
|
|
762
|
+
case "StatusLine": {
|
|
763
|
+
const limits = claudeLimits(s.rate_limits);
|
|
764
|
+
if (!limits) return null;
|
|
765
|
+
events.push(ev("limits.observed", limits));
|
|
766
|
+
break;
|
|
767
|
+
}
|
|
768
|
+
default:
|
|
769
|
+
return null;
|
|
770
|
+
}
|
|
771
|
+
const hints = {};
|
|
772
|
+
if (event === "UserPromptSubmit" && typeof s.prompt === "string") hints.prompt = s.prompt;
|
|
773
|
+
const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
|
|
774
|
+
if (host && DESKTOP_ID.test(host)) hints.desktop_id = host;
|
|
775
|
+
const attended = input.env.CLAUDE_CODE_SESSION_ATTENDED;
|
|
776
|
+
if (attended === "1" || attended === "0") hints.attended = attended === "1" || !!input.env.SESSIONPIPE_CONTROL_JOB;
|
|
777
|
+
if (event !== "PreToolUse" && event !== "PostToolUse") hints.claude_pid = claudePid(input.env);
|
|
778
|
+
const r = { session, events, hints };
|
|
779
|
+
if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
|
|
780
|
+
return r;
|
|
639
781
|
},
|
|
640
782
|
readTranscript(file, fromLine) {
|
|
641
783
|
let text = "";
|
|
@@ -644,765 +786,811 @@ var antigravity = {
|
|
|
644
786
|
} catch {
|
|
645
787
|
return { turns: [], line: fromLine };
|
|
646
788
|
}
|
|
647
|
-
return
|
|
789
|
+
return readClaude(text.split("\n"), fromLine);
|
|
648
790
|
},
|
|
649
|
-
facts(session, transcript, hints) {
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
791
|
+
facts(session, transcript, hints, state) {
|
|
792
|
+
const out = {};
|
|
793
|
+
if (transcript) Object.assign(out, claudeFacts(transcript, session.id, hints, state));
|
|
794
|
+
const cwd = out.cwd ?? session.cwd;
|
|
795
|
+
Object.assign(out, gitFacts(cwd), out.branch ? { branch: out.branch } : {});
|
|
796
|
+
if (!out.url && typeof hints.desktop_id === "string") out.url = `claude://code/continue/${hints.desktop_id}`;
|
|
797
|
+
if (typeof hints.attended === "boolean") out.attended = hints.attended || !!out.url;
|
|
798
|
+
return out;
|
|
656
799
|
},
|
|
800
|
+
remoteFacts: (session, local, transcript, state, env = process.env) => claudeRemoteFacts(session.id, local, transcript, state, env),
|
|
657
801
|
backfill(sinceMs, env = process.env) {
|
|
658
802
|
const rows2 = [];
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
}
|
|
803
|
+
const files = [];
|
|
804
|
+
for (const d of claudeDirs(env))
|
|
805
|
+
files.push(...recentFiles(path4.join(d, "projects"), 1, sinceMs, (n) => /^[0-9a-f-]{36}\.jsonl$/.test(n)));
|
|
806
|
+
const noState = { get: () => ({}), save: () => {
|
|
807
|
+
} };
|
|
808
|
+
for (const f of files) {
|
|
809
|
+
const id = path4.basename(f, ".jsonl");
|
|
810
|
+
const facts = claudeFacts(f, id, {}, noState);
|
|
811
|
+
if (!facts.started_at || !facts.last_at) continue;
|
|
812
|
+
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
813
|
+
const cwd = rest.cwd;
|
|
814
|
+
rows2.push({
|
|
815
|
+
session: { id, ...rest, ...gitFacts(cwd), ...rest.branch ? { branch: rest.branch } : {} },
|
|
816
|
+
started_at,
|
|
817
|
+
last_at,
|
|
818
|
+
transcript: f
|
|
819
|
+
});
|
|
677
820
|
}
|
|
678
821
|
return rows2;
|
|
679
822
|
}
|
|
680
823
|
};
|
|
824
|
+
var WAITER_RE = /(?:^|[\s"'`/\\])sessionpipe(?:\.\w+)?["']?\s+wait(?:\s|$)/;
|
|
825
|
+
function backgroundCount(tasks, ending) {
|
|
826
|
+
if (!Array.isArray(tasks)) return void 0;
|
|
827
|
+
return tasks.filter((t) => {
|
|
828
|
+
const task = t;
|
|
829
|
+
if (ending !== void 0 && task?.id === ending) return false;
|
|
830
|
+
return !(typeof task?.command === "string" && WAITER_RE.test(task.command));
|
|
831
|
+
}).length;
|
|
832
|
+
}
|
|
833
|
+
var LINE_REFRESH_S = 60;
|
|
834
|
+
function statusLine(file, want, cmd) {
|
|
835
|
+
const f = readJsonFile(file);
|
|
836
|
+
if (!f.ok) return null;
|
|
837
|
+
const cur = f.value.statusLine;
|
|
838
|
+
const ours = typeof cur?.command === "string" && OURS.test(cur.command);
|
|
839
|
+
const command = cmd?.([NAME, "StatusLine"]).command;
|
|
840
|
+
if (want && cur && !ours)
|
|
841
|
+
return { file, changed: false, skipped: true, note: "a status line of your own is set; limits are not read" };
|
|
842
|
+
if (want ? ours && cur?.command === command && cur?.refreshInterval === LINE_REFRESH_S : !ours) return null;
|
|
843
|
+
if (want) f.value.statusLine = { type: "command", command, refreshInterval: LINE_REFRESH_S };
|
|
844
|
+
else delete f.value.statusLine;
|
|
845
|
+
writeFileSync2(file, formatJson(f.value, f));
|
|
846
|
+
return { file, changed: true, note: want ? "status line written (account limits)" : "status line removed" };
|
|
847
|
+
}
|
|
848
|
+
var epochIso = (t) => typeof t === "number" && t > 0 && t < 1e11 ? new Date(t * 1e3).toISOString() : void 0;
|
|
849
|
+
var WINDOW_MINUTES = { five_hour: 300, seven_day: 10080 };
|
|
850
|
+
function claudeLimits(rl) {
|
|
851
|
+
if (!rl || typeof rl !== "object" || Array.isArray(rl)) return null;
|
|
852
|
+
const windows = [];
|
|
853
|
+
for (const [id, w] of Object.entries(rl)) {
|
|
854
|
+
if (!/^[a-z0-9_]{1,64}$/.test(id) || typeof w?.used_percentage !== "number" || w.used_percentage < 0) continue;
|
|
855
|
+
windows.push(
|
|
856
|
+
prune({
|
|
857
|
+
id,
|
|
858
|
+
used_percent: w.used_percentage,
|
|
859
|
+
window_minutes: WINDOW_MINUTES[id],
|
|
860
|
+
resets_at: epochIso(w.resets_at)
|
|
861
|
+
})
|
|
862
|
+
);
|
|
863
|
+
}
|
|
864
|
+
return windows.length ? { windows: windows.slice(0, 8) } : null;
|
|
865
|
+
}
|
|
681
866
|
function prune(o) {
|
|
682
867
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
683
868
|
return o;
|
|
684
869
|
}
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
if (/^file:/i.test(s)) {
|
|
689
|
-
try {
|
|
690
|
-
s = decodeURIComponent(new URL(s).pathname);
|
|
691
|
-
} catch {
|
|
692
|
-
return null;
|
|
693
|
-
}
|
|
694
|
-
if (process.platform === "win32" && /^\/[A-Za-z]:/.test(s)) s = s.slice(1);
|
|
695
|
-
} else if (/^[a-z][a-z0-9+.-]*:\/\//i.test(s)) return null;
|
|
696
|
-
if (!path4.isAbsolute(s)) return null;
|
|
697
|
-
return path4.resolve(s);
|
|
870
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
871
|
+
function notClaudeCode(id, s) {
|
|
872
|
+
return typeof s.transcript_path !== "string" && !UUID.test(id);
|
|
698
873
|
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
874
|
+
function pickSource(v) {
|
|
875
|
+
return typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
|
|
876
|
+
}
|
|
877
|
+
var harnessWord = (v) => typeof v === "string" && /^[A-Za-z0-9][A-Za-z0-9_.-]{0,39}$/.test(v) ? v : void 0;
|
|
878
|
+
var WRAPPER = /^<(?:task-notification|cross-session-message|system-reminder|local-command-stdout|command-[a-z-]+)[\s/>]/i;
|
|
879
|
+
var injectedBlock = (prompt) => WRAPPER.test(prompt.trim());
|
|
880
|
+
function errText(v) {
|
|
881
|
+
if (typeof v === "string") return v.slice(0, 2e3);
|
|
882
|
+
if (v && typeof v === "object") {
|
|
883
|
+
const e = v;
|
|
884
|
+
if (typeof e.error === "string") return e.error.slice(0, 2e3);
|
|
885
|
+
if (typeof e.message === "string") return e.message.slice(0, 2e3);
|
|
706
886
|
}
|
|
707
|
-
return
|
|
887
|
+
return void 0;
|
|
708
888
|
}
|
|
709
|
-
function
|
|
710
|
-
|
|
889
|
+
function describe(input) {
|
|
890
|
+
if (!input || typeof input !== "object") return "";
|
|
891
|
+
const i = input;
|
|
892
|
+
const v = i.command ?? i.file_path ?? i.path ?? i.url ?? i.pattern;
|
|
893
|
+
return typeof v === "string" ? ` ${v.slice(0, 120)}` : "";
|
|
894
|
+
}
|
|
895
|
+
var NAME_RANK = {
|
|
896
|
+
user: "custom",
|
|
897
|
+
auto: "harness",
|
|
898
|
+
hook: "harness",
|
|
899
|
+
peer: "harness"
|
|
900
|
+
};
|
|
901
|
+
function registryEntry(j) {
|
|
902
|
+
if (!j || typeof j !== "object") return null;
|
|
903
|
+
const r = j;
|
|
904
|
+
const source = r.nameSource ? NAME_RANK[r.nameSource] : void 0;
|
|
905
|
+
return {
|
|
906
|
+
sessionId: r.sessionId ? String(r.sessionId) : null,
|
|
907
|
+
name: source && typeof r.name === "string" && r.name.trim() ? r.name.trim() : null,
|
|
908
|
+
name_source: source ?? null,
|
|
909
|
+
bridge: typeof r.bridgeSessionId === "string" ? r.bridgeSessionId : null
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
var liveCache = /* @__PURE__ */ new Map();
|
|
913
|
+
function claudeLive(dir) {
|
|
914
|
+
const hit = liveCache.get(dir);
|
|
915
|
+
if (hit) return hit;
|
|
916
|
+
const map = /* @__PURE__ */ new Map();
|
|
711
917
|
let names = [];
|
|
712
918
|
try {
|
|
713
|
-
names = readdirSync2(dir).filter((n) => n.endsWith(".json"));
|
|
919
|
+
names = readdirSync2(path4.join(dir, "sessions")).filter((n) => n.endsWith(".json"));
|
|
714
920
|
} catch {
|
|
715
|
-
return out;
|
|
716
921
|
}
|
|
717
922
|
for (const n of names) {
|
|
718
|
-
const id = n.slice(0, -5);
|
|
719
|
-
if (!ID_RE.test(id)) continue;
|
|
720
|
-
let j;
|
|
721
923
|
try {
|
|
722
|
-
|
|
924
|
+
const e = registryEntry(JSON.parse(readFileSync2(path4.join(dir, "sessions", n), "utf8")));
|
|
925
|
+
if (e?.sessionId) map.set(e.sessionId, e);
|
|
723
926
|
} catch {
|
|
724
|
-
continue;
|
|
725
927
|
}
|
|
726
|
-
const folders = [];
|
|
727
|
-
for (const r of j?.projectResources?.resources ?? []) {
|
|
728
|
-
const f = agPath(r?.folderUri ?? r?.gitFolder?.folderUri);
|
|
729
|
-
if (f) folders.push(...agVariants(f));
|
|
730
|
-
}
|
|
731
|
-
if (folders.length) out.push({ id, folders });
|
|
732
928
|
}
|
|
733
|
-
|
|
929
|
+
liveCache.set(dir, map);
|
|
930
|
+
return map;
|
|
734
931
|
}
|
|
735
|
-
function
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
932
|
+
function claudeLiveByPid(dir, pid) {
|
|
933
|
+
if (!pid) return null;
|
|
934
|
+
try {
|
|
935
|
+
return registryEntry(JSON.parse(readFileSync2(path4.join(dir, "sessions", `${pid}.json`), "utf8")));
|
|
936
|
+
} catch {
|
|
937
|
+
return null;
|
|
740
938
|
}
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
939
|
+
}
|
|
940
|
+
function claudePid(env = process.env) {
|
|
941
|
+
try {
|
|
942
|
+
const dirs = claudeDirs(env);
|
|
943
|
+
const has = (pid2) => dirs.some((d) => existsSync2(path4.join(d, "sessions", `${pid2}.json`)));
|
|
944
|
+
let pid = process.ppid;
|
|
945
|
+
if (has(pid)) return pid;
|
|
946
|
+
const parent = parentOf();
|
|
947
|
+
for (let hop = 0; hop < 6 && pid > 1; hop++) {
|
|
948
|
+
pid = parent(pid);
|
|
949
|
+
if (pid > 1 && has(pid)) return pid;
|
|
950
|
+
}
|
|
951
|
+
} catch {
|
|
952
|
+
}
|
|
953
|
+
return null;
|
|
954
|
+
}
|
|
955
|
+
function parentOf() {
|
|
956
|
+
if (process.platform === "linux") {
|
|
957
|
+
return (pid) => {
|
|
958
|
+
try {
|
|
959
|
+
const st = readFileSync2(`/proc/${pid}/stat`, "utf8");
|
|
960
|
+
return Number(st.slice(st.lastIndexOf(")") + 2).split(" ")[1]) || 0;
|
|
961
|
+
} catch {
|
|
962
|
+
return 0;
|
|
749
963
|
}
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
const table = /* @__PURE__ */ new Map();
|
|
967
|
+
try {
|
|
968
|
+
const out = execFileSync2("ps", ["-axo", "pid=,ppid="], {
|
|
969
|
+
encoding: "utf8",
|
|
970
|
+
timeout: 2e3,
|
|
971
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
972
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
973
|
+
});
|
|
974
|
+
for (const l of out.split("\n")) {
|
|
975
|
+
const m = /^\s*(\d+)\s+(\d+)/.exec(l);
|
|
976
|
+
if (m) table.set(Number(m[1]), Number(m[2]));
|
|
750
977
|
}
|
|
751
|
-
|
|
978
|
+
} catch {
|
|
979
|
+
}
|
|
980
|
+
return (pid) => table.get(pid) || 0;
|
|
752
981
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
const
|
|
756
|
-
|
|
982
|
+
var EMAIL_RE = /^[^@\s]{1,64}@[^@\s]{1,189}$/;
|
|
983
|
+
function claudeLogin(dir, home = homeDir()) {
|
|
984
|
+
const files = [path4.join(dir, ".claude.json")];
|
|
985
|
+
if (path4.resolve(dir) === path4.join(home, ".claude")) files.unshift(path4.join(home, ".claude.json"));
|
|
986
|
+
for (const f of files) {
|
|
757
987
|
try {
|
|
758
|
-
const
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
const conv = opts.conversation ?? "";
|
|
764
|
-
if (!ID_RE.test(conv) || !opts.workspaces?.length) return base;
|
|
765
|
-
const project = agMatchProject(agProjects(opts.projectsDir ?? projectsDir(opts.env)), opts.workspaces);
|
|
766
|
-
return project ? `${base}?p=${encodeURIComponent(`c/${conv}?section=${project}`)}` : base;
|
|
988
|
+
const a = JSON.parse(readFileSync2(f, "utf8")).oauthAccount;
|
|
989
|
+
if (typeof a?.accountUuid === "string" && a.accountUuid) {
|
|
990
|
+
const email = typeof a.emailAddress === "string" && EMAIL_RE.test(a.emailAddress) ? a.emailAddress : void 0;
|
|
991
|
+
return { id: a.accountUuid.slice(0, 128), ...email ? { email } : {} };
|
|
992
|
+
}
|
|
767
993
|
} catch {
|
|
768
994
|
}
|
|
769
995
|
}
|
|
770
|
-
return
|
|
996
|
+
return apiAccount(dir);
|
|
771
997
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
const
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
const url = antigravityRemoteUrl(file, opts);
|
|
778
|
-
if (url) out.url = url;
|
|
779
|
-
for (const j of H) {
|
|
780
|
-
if (j.type !== "USER_INPUT" || typeof j.content !== "string") continue;
|
|
781
|
-
const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
|
|
782
|
-
const text = (m ? m[1] : j.content).trim();
|
|
783
|
-
const line = (text.split("\n").find((l) => l.trim()) ?? "").trim();
|
|
784
|
-
if (!line || /^<[a-z_-]+>/i.test(line)) continue;
|
|
785
|
-
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
786
|
-
out.title_source = "first-ask";
|
|
787
|
-
break;
|
|
788
|
-
}
|
|
789
|
-
const times = H.concat(T).map((j) => Date.parse(String(j.created_at ?? j.timestamp ?? ""))).filter((n) => n > 0);
|
|
790
|
-
if (times.length) {
|
|
791
|
-
out.started_at = Math.min(...times);
|
|
792
|
-
out.last_at = Math.max(...times);
|
|
793
|
-
}
|
|
794
|
-
return out;
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
// ../core/src/adapters/claude-code.ts
|
|
798
|
-
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
799
|
-
import { createHash as createHash2 } from "node:crypto";
|
|
800
|
-
import { existsSync as existsSync3, readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as statSync3 } from "node:fs";
|
|
801
|
-
import path5 from "node:path";
|
|
802
|
-
|
|
803
|
-
// ../core/src/readers/git.ts
|
|
804
|
-
import { execFileSync } from "node:child_process";
|
|
805
|
-
var cache = /* @__PURE__ */ new Map();
|
|
806
|
-
function gitFacts(cwd) {
|
|
807
|
-
if (!cwd) return {};
|
|
808
|
-
const hit = cache.get(cwd);
|
|
809
|
-
if (hit) return hit;
|
|
810
|
-
const run = (args) => {
|
|
811
|
-
try {
|
|
812
|
-
return execFileSync("git", ["-C", cwd, ...args], {
|
|
813
|
-
encoding: "utf8",
|
|
814
|
-
timeout: 1500,
|
|
815
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
816
|
-
}).trim();
|
|
817
|
-
} catch {
|
|
818
|
-
return "";
|
|
819
|
-
}
|
|
820
|
-
};
|
|
821
|
-
const out = {};
|
|
822
|
-
const repo = run(["remote", "get-url", "origin"]);
|
|
823
|
-
if (repo) out.repo = stripCredentials(repo);
|
|
824
|
-
const branch = run(["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
825
|
-
if (branch && branch !== "HEAD") out.branch = branch;
|
|
826
|
-
cache.set(cwd, out);
|
|
827
|
-
return out;
|
|
828
|
-
}
|
|
829
|
-
function stripCredentials(url) {
|
|
830
|
-
const ssh = /^(?:ssh:\/\/)?git@([^:/]+)[:/](.+?)(?:\.git)?$/.exec(url);
|
|
831
|
-
if (ssh) return `${ssh[1]}/${ssh[2]}`;
|
|
832
|
-
return url.replace(/\/\/[^/@]*@/, "//").replace(/^https?:\/\//, "").replace(/\.git$/, "");
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
// ../core/src/adapters/claude-code.ts
|
|
836
|
-
var NAME2 = "claude-code";
|
|
837
|
-
var CLAUDE_EVENTS = [
|
|
838
|
-
"SessionStart",
|
|
839
|
-
"UserPromptSubmit",
|
|
840
|
-
"PreToolUse",
|
|
841
|
-
"PostToolUse",
|
|
842
|
-
"PostToolUseFailure",
|
|
843
|
-
"PermissionRequest",
|
|
844
|
-
"Notification",
|
|
845
|
-
"Stop",
|
|
846
|
-
"SubagentStart",
|
|
847
|
-
"SubagentStop",
|
|
848
|
-
"PreCompact",
|
|
849
|
-
"PostCompact",
|
|
850
|
-
"SessionEnd",
|
|
851
|
-
"StopFailure"
|
|
852
|
-
];
|
|
853
|
-
var LEAN_SKIP = /* @__PURE__ */ new Set(["PreToolUse", "PostToolUseFailure"]);
|
|
854
|
-
var eventsFor = (lean) => lean ? CLAUDE_EVENTS.filter((e) => !LEAN_SKIP.has(e)) : [...CLAUDE_EVENTS];
|
|
855
|
-
var OPTS = {
|
|
856
|
-
matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0,
|
|
857
|
-
timeout: 5,
|
|
858
|
-
// The hook waits up to 125 s for a verified control answer, and only when control
|
|
859
|
-
// is paired on this machine; everywhere else it exits in milliseconds as before.
|
|
860
|
-
timeoutFor: (e) => e === "PermissionRequest" ? 130 : void 0
|
|
861
|
-
};
|
|
862
|
-
var DESKTOP_ID = /^local_[A-Za-z0-9-]{1,64}$/;
|
|
863
|
-
function claudeDirs(env = process.env, home = env.HOME || homeDir()) {
|
|
864
|
-
const out = [];
|
|
865
|
-
const add = (d) => {
|
|
866
|
-
if (!d) return;
|
|
867
|
-
const r = path5.resolve(d);
|
|
868
|
-
if (!out.includes(r)) out.push(r);
|
|
869
|
-
};
|
|
870
|
-
add(path5.join(home, ".claude"));
|
|
871
|
-
add(env.CLAUDE_CONFIG_DIR);
|
|
998
|
+
var ANTHROPIC_HOST = /(^|\.)anthropic\.com$/i;
|
|
999
|
+
function apiAccount(dir) {
|
|
1000
|
+
const base = readJson(settingsIn(dir)).env?.ANTHROPIC_BASE_URL;
|
|
1001
|
+
if (typeof base !== "string") return null;
|
|
1002
|
+
let u;
|
|
872
1003
|
try {
|
|
873
|
-
|
|
874
|
-
if (!e.isDirectory() || !/^\.claude[-_.]/.test(e.name)) continue;
|
|
875
|
-
const d = path5.join(home, e.name);
|
|
876
|
-
if (existsSync3(path5.join(d, "projects")) || existsSync3(path5.join(d, "settings.json"))) add(d);
|
|
877
|
-
}
|
|
1004
|
+
u = new URL(base.trim());
|
|
878
1005
|
} catch {
|
|
1006
|
+
return null;
|
|
879
1007
|
}
|
|
880
|
-
|
|
1008
|
+
if (u.protocol !== "https:" && u.protocol !== "http:" || !u.hostname || ANTHROPIC_HOST.test(u.hostname))
|
|
1009
|
+
return null;
|
|
1010
|
+
const origin = `${u.protocol}//${u.host}`;
|
|
1011
|
+
return { id: `api-${createHash2("sha256").update(origin).digest("hex").slice(0, 16)}`, name: u.host };
|
|
881
1012
|
}
|
|
882
|
-
var
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
(
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
})),
|
|
900
|
-
fromHook(input) {
|
|
901
|
-
const event = input.argv[1] ?? "";
|
|
902
|
-
let s = {};
|
|
903
|
-
try {
|
|
904
|
-
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
905
|
-
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
906
|
-
} catch {
|
|
907
|
-
}
|
|
908
|
-
const id = s.session_id ?? s.sessionId;
|
|
909
|
-
if (!id) return null;
|
|
910
|
-
if (notClaudeCode(String(id), s)) return null;
|
|
911
|
-
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
912
|
-
if (typeof s.model === "string") session.model = s.model;
|
|
913
|
-
if (typeof s.agent_id === "string" && event.startsWith("Subagent") === false && s.agent_id)
|
|
914
|
-
session.parent_id = null;
|
|
915
|
-
const turn_id = typeof s.prompt_id === "string" ? s.prompt_id : void 0;
|
|
916
|
-
const tool = validTool2(s.tool_name) ? s.tool_name : void 0;
|
|
917
|
-
const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
|
|
918
|
-
const ms = typeof s.duration_ms === "number" ? Math.round(s.duration_ms) : void 0;
|
|
919
|
-
const agent_id = typeof s.agent_id === "string" ? s.agent_id : void 0;
|
|
920
|
-
const permissionId = () => `perm-${createHash2("sha256").update(`${turn_id ?? ""}|${tool ?? ""}|${JSON.stringify(s.tool_input ?? null)}`).digest("hex").slice(0, 16)}`;
|
|
921
|
-
const ev = (type, data) => ({ type, data: prune2(data), harnessEvent: event });
|
|
922
|
-
const events = [];
|
|
923
|
-
switch (event) {
|
|
924
|
-
case "SessionStart":
|
|
925
|
-
events.push(ev("session.started", { source: pickSource(s.source ?? s.trigger) }));
|
|
926
|
-
break;
|
|
927
|
-
case "UserPromptSubmit": {
|
|
928
|
-
const prompt = typeof s.prompt === "string" ? s.prompt : void 0;
|
|
929
|
-
events.push(
|
|
930
|
-
ev("turn.started", {
|
|
931
|
-
turn_id,
|
|
932
|
-
prompt_chars: prompt === void 0 ? void 0 : [...prompt].length,
|
|
933
|
-
// Claude Code's hook schema documents `source`
|
|
934
|
-
// (user | sdk | system | loop_wakeup | schedule_wakeup | poll_event) and says a
|
|
935
|
-
// payload may omit it while the field rolls out; 2.1.304's payload has no such
|
|
936
|
-
// key at all. Forwarded verbatim, shape-guarded only: the list is the harness's
|
|
937
|
-
// to extend.
|
|
938
|
-
submitted_by: harnessWord(s.source),
|
|
939
|
-
// One of Claude Code's own prompt wrappers: the prompt is a block the harness
|
|
940
|
-
// wrote, so it is not the person's words. Absent is unknown, never "the person" —
|
|
941
|
-
// and `true` is never "no person caused this turn", because a Talk or
|
|
942
|
-
// `sessionpipe wait` delivery hands the person's own message over inside a
|
|
943
|
-
// <task-notification> (PROTOCOL.md §4).
|
|
944
|
-
injected: prompt !== void 0 && injectedBlock(prompt) ? true : void 0
|
|
945
|
-
})
|
|
946
|
-
);
|
|
947
|
-
break;
|
|
948
|
-
}
|
|
949
|
-
case "PreToolUse":
|
|
950
|
-
if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, agent_id, input: s.tool_input }));
|
|
951
|
-
break;
|
|
952
|
-
case "PostToolUse":
|
|
953
|
-
if (tool)
|
|
954
|
-
events.push(
|
|
955
|
-
ev("tool.ended", {
|
|
956
|
-
tool,
|
|
957
|
-
call_id,
|
|
958
|
-
turn_id,
|
|
959
|
-
agent_id,
|
|
960
|
-
ms,
|
|
961
|
-
ok: true,
|
|
962
|
-
input: s.tool_input,
|
|
963
|
-
output: s.tool_response
|
|
964
|
-
})
|
|
965
|
-
);
|
|
966
|
-
break;
|
|
967
|
-
case "PostToolUseFailure":
|
|
968
|
-
if (tool)
|
|
969
|
-
events.push(
|
|
970
|
-
ev("tool.ended", {
|
|
971
|
-
tool,
|
|
972
|
-
call_id,
|
|
973
|
-
turn_id,
|
|
974
|
-
agent_id,
|
|
975
|
-
ms,
|
|
976
|
-
ok: false,
|
|
977
|
-
error: errText(s.error ?? s.tool_response),
|
|
978
|
-
input: s.tool_input,
|
|
979
|
-
output: s.tool_response
|
|
980
|
-
})
|
|
981
|
-
);
|
|
982
|
-
break;
|
|
983
|
-
case "PermissionRequest":
|
|
984
|
-
events.push(
|
|
985
|
-
ev("attention.needed", {
|
|
986
|
-
attention_id: call_id ?? permissionId(),
|
|
987
|
-
kind: "permission",
|
|
988
|
-
tool,
|
|
989
|
-
message: tool ? `${tool}${describe(s.tool_input)}` : void 0
|
|
990
|
-
})
|
|
991
|
-
);
|
|
992
|
-
break;
|
|
993
|
-
case "Notification":
|
|
994
|
-
events.push(
|
|
995
|
-
ev("attention.needed", {
|
|
996
|
-
attention_id: `notif-${Date.now()}`,
|
|
997
|
-
kind: notifKind(s.notification_type),
|
|
998
|
-
message: typeof s.message === "string" ? s.message.slice(0, 200) : void 0
|
|
999
|
-
})
|
|
1000
|
-
);
|
|
1001
|
-
break;
|
|
1002
|
-
case "Stop":
|
|
1003
|
-
events.push(
|
|
1004
|
-
ev("turn.ended", {
|
|
1005
|
-
turn_id,
|
|
1006
|
-
reason: "stop",
|
|
1007
|
-
background: Array.isArray(s.background_tasks) ? s.background_tasks.length : void 0
|
|
1008
|
-
})
|
|
1009
|
-
);
|
|
1010
|
-
break;
|
|
1011
|
-
case "StopFailure":
|
|
1012
|
-
events.push(
|
|
1013
|
-
ev("turn.ended", {
|
|
1014
|
-
turn_id,
|
|
1015
|
-
reason: "error",
|
|
1016
|
-
error: typeof s.error === "string" ? s.error.slice(0, 200) : void 0
|
|
1017
|
-
})
|
|
1018
|
-
);
|
|
1019
|
-
break;
|
|
1020
|
-
case "SubagentStart":
|
|
1021
|
-
events.push(
|
|
1022
|
-
ev("subagent.started", {
|
|
1023
|
-
agent_id: String(s.agent_id ?? ""),
|
|
1024
|
-
agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
|
|
1025
|
-
})
|
|
1026
|
-
);
|
|
1027
|
-
break;
|
|
1028
|
-
case "SubagentStop":
|
|
1029
|
-
events.push(
|
|
1030
|
-
ev("subagent.ended", {
|
|
1031
|
-
agent_id: String(s.agent_id ?? ""),
|
|
1032
|
-
agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
|
|
1033
|
-
})
|
|
1034
|
-
);
|
|
1035
|
-
break;
|
|
1036
|
-
case "PreCompact":
|
|
1037
|
-
events.push(ev("context.compacted", { trigger: s.trigger === "manual" ? "manual" : "auto" }));
|
|
1038
|
-
break;
|
|
1039
|
-
case "PostCompact":
|
|
1040
|
-
break;
|
|
1041
|
-
// PreCompact already said it; a second event would double-count
|
|
1042
|
-
case "SessionEnd":
|
|
1043
|
-
events.push(ev("session.ended", { reason: endReason(s.reason) }));
|
|
1044
|
-
break;
|
|
1045
|
-
default:
|
|
1046
|
-
return null;
|
|
1047
|
-
}
|
|
1048
|
-
const hints = {};
|
|
1049
|
-
if (event === "UserPromptSubmit" && typeof s.prompt === "string") hints.prompt = s.prompt;
|
|
1050
|
-
const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
|
|
1051
|
-
if (host && DESKTOP_ID.test(host)) hints.desktop_id = host;
|
|
1052
|
-
const attended = input.env.CLAUDE_CODE_SESSION_ATTENDED;
|
|
1053
|
-
if (attended === "1" || attended === "0") hints.attended = attended === "1" || !!input.env.SESSIONPIPE_CONTROL_JOB;
|
|
1054
|
-
if (event !== "PreToolUse" && event !== "PostToolUse") hints.claude_pid = claudePid(input.env);
|
|
1055
|
-
const r = { session, events, hints };
|
|
1056
|
-
if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
|
|
1057
|
-
return r;
|
|
1058
|
-
},
|
|
1059
|
-
readTranscript(file, fromLine) {
|
|
1060
|
-
let text = "";
|
|
1061
|
-
try {
|
|
1062
|
-
text = readFileSync3(file, "utf8");
|
|
1063
|
-
} catch {
|
|
1064
|
-
return { turns: [], line: fromLine };
|
|
1065
|
-
}
|
|
1066
|
-
return readClaude(text.split("\n"), fromLine);
|
|
1067
|
-
},
|
|
1068
|
-
facts(session, transcript, hints, state) {
|
|
1069
|
-
const out = {};
|
|
1070
|
-
if (transcript) Object.assign(out, claudeFacts(transcript, session.id, hints, state));
|
|
1071
|
-
const cwd = out.cwd ?? session.cwd;
|
|
1072
|
-
Object.assign(out, gitFacts(cwd), out.branch ? { branch: out.branch } : {});
|
|
1073
|
-
if (!out.url && typeof hints.desktop_id === "string") out.url = `claude://code/continue/${hints.desktop_id}`;
|
|
1074
|
-
if (typeof hints.attended === "boolean") out.attended = hints.attended || !!out.url;
|
|
1075
|
-
return out;
|
|
1076
|
-
},
|
|
1077
|
-
backfill(sinceMs, env = process.env) {
|
|
1078
|
-
const rows2 = [];
|
|
1079
|
-
const files = [];
|
|
1080
|
-
for (const d of claudeDirs(env))
|
|
1081
|
-
files.push(...recentFiles(path5.join(d, "projects"), 1, sinceMs, (n) => /^[0-9a-f-]{36}\.jsonl$/.test(n)));
|
|
1082
|
-
const noState = { get: () => ({}), save: () => {
|
|
1083
|
-
} };
|
|
1084
|
-
for (const f of files) {
|
|
1085
|
-
const id = path5.basename(f, ".jsonl");
|
|
1086
|
-
const facts = claudeFacts(f, id, {}, noState);
|
|
1087
|
-
if (!facts.started_at || !facts.last_at) continue;
|
|
1088
|
-
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
1089
|
-
const cwd = rest.cwd;
|
|
1090
|
-
rows2.push({
|
|
1091
|
-
session: { id, ...rest, ...gitFacts(cwd), ...rest.branch ? { branch: rest.branch } : {} },
|
|
1092
|
-
started_at,
|
|
1093
|
-
last_at
|
|
1094
|
-
});
|
|
1013
|
+
var accountCache = /* @__PURE__ */ new Map();
|
|
1014
|
+
function claudeAccount(dir) {
|
|
1015
|
+
const hit = accountCache.get(dir);
|
|
1016
|
+
if (hit !== void 0) return hit;
|
|
1017
|
+
const acct = claudeLogin(dir)?.id ?? null;
|
|
1018
|
+
accountCache.set(dir, acct);
|
|
1019
|
+
return acct;
|
|
1020
|
+
}
|
|
1021
|
+
var LINK_RE = /^https:\/\/claude\.ai\/code\/session_[A-Za-z0-9]+$/;
|
|
1022
|
+
function transcriptLink(records) {
|
|
1023
|
+
for (let i = records.length - 1; i >= 0; i--) {
|
|
1024
|
+
const j = records[i];
|
|
1025
|
+
if (j.type === "system" && j.subtype === "bridge_status" && typeof j.url === "string" && LINK_RE.test(j.url))
|
|
1026
|
+
return j.url;
|
|
1027
|
+
if (j.type === "bridge-session" && typeof j.bridgeSessionId === "string") {
|
|
1028
|
+
const m = /^(?:cse|session)_([A-Za-z0-9]+)$/.exec(j.bridgeSessionId);
|
|
1029
|
+
if (m) return `https://claude.ai/code/session_${m[1]}`;
|
|
1095
1030
|
}
|
|
1096
|
-
return rows2;
|
|
1097
1031
|
}
|
|
1098
|
-
|
|
1099
|
-
function prune2(o) {
|
|
1100
|
-
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
1101
|
-
return o;
|
|
1032
|
+
return null;
|
|
1102
1033
|
}
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
return
|
|
1034
|
+
function appTitle(text) {
|
|
1035
|
+
const t = String(text).replace(/\s+/g, " ").trim();
|
|
1036
|
+
return t.length > 50 ? `${t.slice(0, 50)}...` : t;
|
|
1106
1037
|
}
|
|
1107
|
-
function
|
|
1108
|
-
|
|
1038
|
+
function askText(j) {
|
|
1039
|
+
if (!j || j.type !== "user" || j.isMeta || j.isSidechain) return null;
|
|
1040
|
+
const origin = j.origin;
|
|
1041
|
+
if (origin?.kind && origin.kind !== "human") return null;
|
|
1042
|
+
const c = j.message?.content;
|
|
1043
|
+
const text = (typeof c === "string" ? c : Array.isArray(c) ? c.filter((b) => b && b.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n") : "").trim();
|
|
1044
|
+
if (!text || isInjected(text)) return null;
|
|
1045
|
+
return text;
|
|
1109
1046
|
}
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
if (typeof v === "string") return v.slice(0, 2e3);
|
|
1115
|
-
if (v && typeof v === "object") {
|
|
1116
|
-
const e = v;
|
|
1117
|
-
if (typeof e.error === "string") return e.error.slice(0, 2e3);
|
|
1118
|
-
if (typeof e.message === "string") return e.message.slice(0, 2e3);
|
|
1047
|
+
function firstAsk(records) {
|
|
1048
|
+
for (const j of records) {
|
|
1049
|
+
const t = askText(j);
|
|
1050
|
+
if (t) return appTitle(t);
|
|
1119
1051
|
}
|
|
1120
|
-
return
|
|
1052
|
+
return null;
|
|
1121
1053
|
}
|
|
1122
|
-
function
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1054
|
+
function scanTitles(file, from, found) {
|
|
1055
|
+
const out = { custom: found.custom ?? null, ai: found.ai ?? null, upto: from };
|
|
1056
|
+
let size = 0;
|
|
1057
|
+
try {
|
|
1058
|
+
size = statSync2(file).size;
|
|
1059
|
+
} catch {
|
|
1060
|
+
return out;
|
|
1061
|
+
}
|
|
1062
|
+
if (size < from) {
|
|
1063
|
+
out.upto = 0;
|
|
1064
|
+
out.custom = out.ai = null;
|
|
1065
|
+
from = 0;
|
|
1066
|
+
}
|
|
1067
|
+
let pos = 0;
|
|
1068
|
+
scanLines(
|
|
1069
|
+
file,
|
|
1070
|
+
(l) => {
|
|
1071
|
+
pos += Buffer.byteLength(l, "utf8") + 1;
|
|
1072
|
+
if (pos <= from || l.indexOf('-title"') < 0) return null;
|
|
1073
|
+
try {
|
|
1074
|
+
const j = JSON.parse(l);
|
|
1075
|
+
if (j.type === "custom-title" && j.customTitle) out.custom = j.customTitle;
|
|
1076
|
+
if (j.type === "ai-title" && j.aiTitle) out.ai = j.aiTitle;
|
|
1077
|
+
} catch {
|
|
1078
|
+
}
|
|
1079
|
+
return null;
|
|
1080
|
+
},
|
|
1081
|
+
512 * 1024 * 1024
|
|
1082
|
+
);
|
|
1083
|
+
out.upto = size;
|
|
1084
|
+
return out;
|
|
1127
1085
|
}
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
const
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1086
|
+
function claudeFacts(file, sessionId, hints, state) {
|
|
1087
|
+
const { head, tail } = ends(file, 64 * 1024, 256 * 1024);
|
|
1088
|
+
const H = parseLines(head);
|
|
1089
|
+
const T = parseLines(tail);
|
|
1090
|
+
const out = {};
|
|
1091
|
+
const id = sessionId || path4.basename(file, ".jsonl");
|
|
1092
|
+
const mem = state.get(NAME, id);
|
|
1093
|
+
let custom = null;
|
|
1094
|
+
let ai = null;
|
|
1095
|
+
for (const j of H.concat(T)) {
|
|
1096
|
+
if (j.type === "custom-title" && typeof j.customTitle === "string") custom = j.customTitle;
|
|
1097
|
+
if (j.type === "ai-title" && typeof j.aiTitle === "string") ai = j.aiTitle;
|
|
1098
|
+
}
|
|
1099
|
+
let size = 0;
|
|
1100
|
+
try {
|
|
1101
|
+
size = statSync2(file).size;
|
|
1102
|
+
} catch {
|
|
1103
|
+
}
|
|
1104
|
+
if (!custom && !ai && size > 64 * 1024 + 256 * 1024) {
|
|
1105
|
+
const s = scanTitles(file, Number(mem.scan_upto) || 0, {
|
|
1106
|
+
custom: mem.scan_custom,
|
|
1107
|
+
ai: mem.scan_ai
|
|
1108
|
+
});
|
|
1109
|
+
custom = s.custom;
|
|
1110
|
+
ai = s.ai;
|
|
1111
|
+
state.save(NAME, id, { scan_upto: s.upto, scan_custom: s.custom, scan_ai: s.ai });
|
|
1112
|
+
}
|
|
1113
|
+
const dir = configDirOf(file);
|
|
1114
|
+
const live = claudeLive(dir).get(id) ?? claudeLiveByPid(dir, hints.claude_pid);
|
|
1115
|
+
if (live?.name) state.save(NAME, id, { name: live.name, name_source: live.name_source });
|
|
1116
|
+
const bridge = live?.bridge && /^session_[A-Za-z0-9]+$/.test(live.bridge) ? `https://claude.ai/code/${live.bridge}` : null;
|
|
1117
|
+
const url = bridge ?? transcriptLink(T) ?? transcriptLink(H) ?? (typeof mem.url === "string" ? mem.url : null);
|
|
1118
|
+
if (url) {
|
|
1119
|
+
out.url = url;
|
|
1120
|
+
state.save(NAME, id, { url });
|
|
1121
|
+
}
|
|
1122
|
+
const name = live?.name ? { title: live.name, source: live.name_source } : typeof mem.name === "string" ? { title: mem.name, source: mem.name_source } : null;
|
|
1123
|
+
const pick = name && name.source === "custom" && name || custom && { title: custom, source: "custom" } || name || ai && { title: ai, source: "harness" };
|
|
1124
|
+
let ask2 = typeof mem.first_ask === "string" ? mem.first_ask : firstAsk(H);
|
|
1125
|
+
if (!ask2 && size > 64 * 1024 && !mem.first_ask_none) {
|
|
1126
|
+
ask2 = scanLines(file, (l) => {
|
|
1127
|
+
try {
|
|
1128
|
+
const t = askText(JSON.parse(l));
|
|
1129
|
+
return t ? appTitle(t) : null;
|
|
1130
|
+
} catch {
|
|
1131
|
+
return null;
|
|
1132
|
+
}
|
|
1133
|
+
});
|
|
1134
|
+
if (!ask2 && size > 16 * 1024 * 1024) state.save(NAME, id, { first_ask_none: true });
|
|
1135
|
+
}
|
|
1136
|
+
if (ask2) {
|
|
1137
|
+
out.first_ask = ask2;
|
|
1138
|
+
if (!mem.first_ask) state.save(NAME, id, { first_ask: ask2 });
|
|
1139
|
+
}
|
|
1140
|
+
if (pick) {
|
|
1141
|
+
out.title = String(pick.title).slice(0, 120);
|
|
1142
|
+
out.title_source = pick.source;
|
|
1143
|
+
} else if (ask2) {
|
|
1144
|
+
out.title = ask2;
|
|
1145
|
+
out.title_source = "first-ask";
|
|
1146
|
+
}
|
|
1147
|
+
const acct = claudeAccount(dir);
|
|
1148
|
+
if (acct) out.account_id = acct;
|
|
1149
|
+
for (let i = T.length - 1; i >= 0; i--) {
|
|
1150
|
+
const m = T[i].message?.model;
|
|
1151
|
+
if (T[i]?.type === "assistant" && m && m !== "<synthetic>") {
|
|
1152
|
+
out.model = m;
|
|
1153
|
+
break;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
const meta = [...T].reverse().find((j) => j.version) ?? H.find((j) => j.version);
|
|
1157
|
+
if (meta?.version) out.harness_version = String(meta.version);
|
|
1158
|
+
const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
|
|
1159
|
+
if (times.length) {
|
|
1160
|
+
out.started_at = Math.min(...times);
|
|
1161
|
+
out.last_at = Math.max(...times);
|
|
1162
|
+
}
|
|
1163
|
+
const withCwd = H.find((j) => typeof j.cwd === "string");
|
|
1164
|
+
if (withCwd) {
|
|
1165
|
+
out.cwd = withCwd.cwd;
|
|
1166
|
+
if (typeof withCwd.gitBranch === "string" && withCwd.gitBranch !== "HEAD") out.branch = withCwd.gitBranch;
|
|
1167
|
+
}
|
|
1168
|
+
return out;
|
|
1144
1169
|
}
|
|
1145
|
-
var
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1170
|
+
var REMOTE_RETRY_MS = 6e4;
|
|
1171
|
+
var REMOTE_REFRESH_MS = 30 * 6e4;
|
|
1172
|
+
var CLAUDE_API = "https://api.anthropic.com";
|
|
1173
|
+
var KEYCHAIN = "Claude Code-credentials";
|
|
1174
|
+
var REMOTE_TIMEOUT_MS = 3e3;
|
|
1175
|
+
var KEYCHAIN_TIMEOUT_MS = 2e3;
|
|
1176
|
+
var REMOTE_MAX_BYTES = 1 << 20;
|
|
1177
|
+
var cleanName = (s) => s.replace(/[\p{Cc}\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]/gu, " ").replace(/\s+/g, " ").trim();
|
|
1178
|
+
async function claudeRemoteFacts(id, local, transcript, state, env, now = Date.now()) {
|
|
1179
|
+
if (!transcript || !local.url || !LINK_RE.test(local.url) || local.title_source === "custom") return {};
|
|
1180
|
+
const mem = state.get(NAME, id);
|
|
1181
|
+
let title = typeof mem.remote_title === "string" ? mem.remote_title : null;
|
|
1182
|
+
if (now - (Number(mem.remote_at) || 0) >= (title ? REMOTE_REFRESH_MS : REMOTE_RETRY_MS)) {
|
|
1183
|
+
state.save(NAME, id, { remote_at: now });
|
|
1184
|
+
const token = claudeAiToken(configDirOf(transcript), env, now);
|
|
1185
|
+
const got = token ? await claudeAiTitle(local.url.slice(local.url.lastIndexOf("/") + 1), token) : void 0;
|
|
1186
|
+
if (got !== void 0) {
|
|
1187
|
+
title = got?.slice(0, 200) ?? null;
|
|
1188
|
+
state.save(NAME, id, { remote_title: title });
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
return title ? { title: title.slice(0, 120), title_source: "harness" } : {};
|
|
1192
|
+
}
|
|
1193
|
+
function keychainServices(dir, env) {
|
|
1194
|
+
const named = (p) => `${KEYCHAIN}-${createHash2("sha256").update(p.normalize("NFC")).digest("hex").slice(0, 8)}`;
|
|
1195
|
+
const out = /* @__PURE__ */ new Set();
|
|
1196
|
+
if (env.CLAUDE_CONFIG_DIR && path4.resolve(env.CLAUDE_CONFIG_DIR) === dir) out.add(named(env.CLAUDE_CONFIG_DIR));
|
|
1197
|
+
if (dir === path4.join(env.HOME || homeDir(), ".claude")) out.add(KEYCHAIN);
|
|
1198
|
+
out.add(named(dir));
|
|
1199
|
+
return [...out];
|
|
1200
|
+
}
|
|
1201
|
+
var tokenCache = /* @__PURE__ */ new Map();
|
|
1202
|
+
function claudeAiToken(dir, env, now) {
|
|
1203
|
+
const hit = tokenCache.get(dir);
|
|
1204
|
+
if (hit && hit.expires > now + 6e4) return hit.token;
|
|
1205
|
+
const raw = [];
|
|
1206
|
+
if (process.platform === "darwin")
|
|
1207
|
+
for (const service of keychainServices(dir, env)) {
|
|
1208
|
+
try {
|
|
1209
|
+
const user = env.USER || userInfo().username;
|
|
1210
|
+
const account = /^[\w.-]+$/.test(user) ? user : "claude-code-user";
|
|
1211
|
+
raw.push(
|
|
1212
|
+
execFileSync2("security", ["find-generic-password", "-a", account, "-w", "-s", service], {
|
|
1213
|
+
encoding: "utf8",
|
|
1214
|
+
timeout: KEYCHAIN_TIMEOUT_MS,
|
|
1215
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1216
|
+
})
|
|
1217
|
+
);
|
|
1218
|
+
break;
|
|
1219
|
+
} catch {
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1151
1222
|
try {
|
|
1152
|
-
|
|
1223
|
+
raw.push(readFileSync2(path4.join(dir, ".credentials.json"), "utf8"));
|
|
1153
1224
|
} catch {
|
|
1154
1225
|
}
|
|
1155
|
-
for (const
|
|
1226
|
+
for (const r of raw) {
|
|
1156
1227
|
try {
|
|
1157
|
-
const
|
|
1158
|
-
|
|
1228
|
+
const t = r.trim();
|
|
1229
|
+
const o = JSON.parse(/^(?:[0-9a-f]{2})+$/i.test(t) ? Buffer.from(t, "hex").toString("utf8") : t).claudeAiOauth;
|
|
1230
|
+
if (typeof o?.accessToken !== "string" || !o.accessToken) continue;
|
|
1231
|
+
const expires = typeof o.expiresAt === "number" ? o.expiresAt : Number.POSITIVE_INFINITY;
|
|
1232
|
+
if (expires <= now + 6e4) continue;
|
|
1233
|
+
tokenCache.set(dir, { token: o.accessToken, expires });
|
|
1234
|
+
return o.accessToken;
|
|
1159
1235
|
} catch {
|
|
1160
1236
|
}
|
|
1161
1237
|
}
|
|
1162
|
-
|
|
1163
|
-
return map;
|
|
1164
|
-
}
|
|
1165
|
-
function claudeLiveByPid(dir, pid) {
|
|
1166
|
-
if (!pid) return null;
|
|
1167
|
-
try {
|
|
1168
|
-
return registryEntry(JSON.parse(readFileSync3(path5.join(dir, "sessions", `${pid}.json`), "utf8")));
|
|
1169
|
-
} catch {
|
|
1170
|
-
return null;
|
|
1171
|
-
}
|
|
1238
|
+
return null;
|
|
1172
1239
|
}
|
|
1173
|
-
function
|
|
1240
|
+
async function claudeAiTitle(bridge, token) {
|
|
1174
1241
|
try {
|
|
1175
|
-
const
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1242
|
+
const r = await fetch(`${CLAUDE_API}/v1/code/sessions/${bridge}`, {
|
|
1243
|
+
headers: { Authorization: `Bearer ${token}`, "anthropic-version": "2023-06-01", "User-Agent": "sessionpipe" },
|
|
1244
|
+
redirect: "error",
|
|
1245
|
+
signal: AbortSignal.timeout(REMOTE_TIMEOUT_MS)
|
|
1246
|
+
});
|
|
1247
|
+
if (r.status !== 200 || Number(r.headers.get("content-length")) > REMOTE_MAX_BYTES) return void 0;
|
|
1248
|
+
const text = await r.text();
|
|
1249
|
+
if (text.length > REMOTE_MAX_BYTES) return void 0;
|
|
1250
|
+
const j = JSON.parse(text);
|
|
1251
|
+
const s = j?.response_shape ?? j?.session ?? j;
|
|
1252
|
+
const bare = (x) => x.replace(/^(?:session|cse)_/, "");
|
|
1253
|
+
if (typeof s?.id !== "string" || bare(s.id) !== bare(bridge)) return void 0;
|
|
1254
|
+
return typeof s.title === "string" && cleanName(s.title) || null;
|
|
1184
1255
|
} catch {
|
|
1256
|
+
return void 0;
|
|
1185
1257
|
}
|
|
1186
|
-
return null;
|
|
1187
1258
|
}
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
return 0;
|
|
1196
|
-
}
|
|
1197
|
-
};
|
|
1198
|
-
}
|
|
1199
|
-
const table = /* @__PURE__ */ new Map();
|
|
1259
|
+
|
|
1260
|
+
// ../core/src/adapters/headless.ts
|
|
1261
|
+
import { accessSync, constants, readFileSync as readFileSync3, statSync as statSync3 } from "node:fs";
|
|
1262
|
+
import path5 from "node:path";
|
|
1263
|
+
var JOB_TIMEOUT_MS = 30 * 6e4;
|
|
1264
|
+
var OUT_CAP = 1024 * 1024;
|
|
1265
|
+
function isExe(p) {
|
|
1200
1266
|
try {
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
maxBuffer: 8 * 1024 * 1024,
|
|
1205
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
1206
|
-
});
|
|
1207
|
-
for (const l of out.split("\n")) {
|
|
1208
|
-
const m = /^\s*(\d+)\s+(\d+)/.exec(l);
|
|
1209
|
-
if (m) table.set(Number(m[1]), Number(m[2]));
|
|
1210
|
-
}
|
|
1267
|
+
if (!statSync3(p).isFile()) return false;
|
|
1268
|
+
accessSync(p, constants.X_OK);
|
|
1269
|
+
return true;
|
|
1211
1270
|
} catch {
|
|
1271
|
+
return false;
|
|
1212
1272
|
}
|
|
1213
|
-
return (pid) => table.get(pid) || 0;
|
|
1214
1273
|
}
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
const
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
try {
|
|
1221
|
-
const a = JSON.parse(readFileSync3(f, "utf8")).oauthAccount;
|
|
1222
|
-
if (typeof a?.accountUuid === "string" && a.accountUuid) {
|
|
1223
|
-
const email = typeof a.emailAddress === "string" && EMAIL_RE.test(a.emailAddress) ? a.emailAddress : void 0;
|
|
1224
|
-
return { id: a.accountUuid.slice(0, 128), ...email ? { email } : {} };
|
|
1225
|
-
}
|
|
1226
|
-
} catch {
|
|
1227
|
-
}
|
|
1274
|
+
function findBinary(o) {
|
|
1275
|
+
if (o.override && isExe(o.override)) return o.override;
|
|
1276
|
+
for (const d of String(o.env?.PATH || "").split(path5.delimiter)) {
|
|
1277
|
+
if (!d || !path5.isAbsolute(d)) continue;
|
|
1278
|
+
for (const n of o.names) if (isExe(path5.join(d, n))) return path5.join(d, n);
|
|
1228
1279
|
}
|
|
1229
|
-
return
|
|
1280
|
+
for (const d of o.dirs) for (const n of o.names) if (isExe(path5.join(d, n))) return path5.join(d, n);
|
|
1281
|
+
return null;
|
|
1230
1282
|
}
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
let base;
|
|
1283
|
+
function shimTarget(shim) {
|
|
1284
|
+
let text = "";
|
|
1234
1285
|
try {
|
|
1235
|
-
|
|
1286
|
+
text = readFileSync3(shim, "utf8");
|
|
1236
1287
|
} catch {
|
|
1237
1288
|
return null;
|
|
1238
1289
|
}
|
|
1239
|
-
|
|
1240
|
-
|
|
1290
|
+
const m = /"%(?:~dp0|dp0%)\\([^"]+)"\s+%\*/.exec(text);
|
|
1291
|
+
if (!m) return null;
|
|
1292
|
+
const target = path5.resolve(path5.dirname(shim), m[1].split("\\").join(path5.sep));
|
|
1241
1293
|
try {
|
|
1242
|
-
|
|
1294
|
+
return statSync3(target).isFile() ? target : null;
|
|
1243
1295
|
} catch {
|
|
1244
1296
|
return null;
|
|
1245
1297
|
}
|
|
1246
|
-
if (u.protocol !== "https:" && u.protocol !== "http:" || !u.hostname || ANTHROPIC_HOST.test(u.hostname))
|
|
1247
|
-
return null;
|
|
1248
|
-
const origin = `${u.protocol}//${u.host}`;
|
|
1249
|
-
return { id: `api-${createHash2("sha256").update(origin).digest("hex").slice(0, 16)}`, name: u.host };
|
|
1250
1298
|
}
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
const
|
|
1254
|
-
|
|
1255
|
-
const acct = claudeLogin(dir)?.id ?? null;
|
|
1256
|
-
accountCache.set(dir, acct);
|
|
1257
|
-
return acct;
|
|
1299
|
+
function spawnable(bin, args) {
|
|
1300
|
+
if (!/\.(cmd|bat)$/i.test(bin)) return { file: bin, args };
|
|
1301
|
+
const target = shimTarget(bin);
|
|
1302
|
+
return target ? { file: process.execPath, args: [target, ...args] } : null;
|
|
1258
1303
|
}
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1304
|
+
function probe(bin, args) {
|
|
1305
|
+
const s = spawnable(bin, args);
|
|
1306
|
+
return s ? { ...s, shell: false } : { file: [`"${bin}"`, ...args].join(" "), args: [], shell: true };
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
// ../core/src/adapters/antigravity.ts
|
|
1310
|
+
import {
|
|
1311
|
+
existsSync as existsSync3,
|
|
1312
|
+
mkdirSync as mkdirSync2,
|
|
1313
|
+
readdirSync as readdirSync3,
|
|
1314
|
+
readFileSync as readFileSync4,
|
|
1315
|
+
realpathSync,
|
|
1316
|
+
statSync as statSync4,
|
|
1317
|
+
unlinkSync as unlinkSync2,
|
|
1318
|
+
writeFileSync as writeFileSync3
|
|
1319
|
+
} from "node:fs";
|
|
1320
|
+
import path6 from "node:path";
|
|
1321
|
+
var NAME2 = "antigravity";
|
|
1322
|
+
var HOOK_NAME = "sessionpipe";
|
|
1323
|
+
var AG_EVENTS = ["PreInvocation", "PostInvocation", "PostToolUse", "Stop"];
|
|
1324
|
+
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';
|
|
1325
|
+
var antigravityAnswer = (event) => event === "PreToolUse" ? AG_STALE_PRETOOL : "{}\n";
|
|
1326
|
+
var gemini = (env) => env.GEMINI_CLI_HOME || path6.join(homeDir(), ".gemini");
|
|
1327
|
+
var hooksFile = (env) => path6.join(gemini(env), "config", "hooks.json");
|
|
1328
|
+
var projectsDir = (env) => path6.join(gemini(env), "config", "projects");
|
|
1329
|
+
var dataDirs = (env) => ["antigravity", "antigravity-cli", "antigravity-ide"].map((d) => path6.join(gemini(env), d));
|
|
1330
|
+
var ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
|
|
1331
|
+
var validTool2 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
1332
|
+
function entry(cmd) {
|
|
1333
|
+
const handler = (e) => ({ type: "command", command: cmd([NAME2, e]).command, timeout: 5 });
|
|
1334
|
+
const out = { enabled: true };
|
|
1335
|
+
for (const e of AG_EVENTS) out[e] = /Tool/.test(e) ? [{ matcher: "*", hooks: [handler(e)] }] : [handler(e)];
|
|
1336
|
+
return out;
|
|
1337
|
+
}
|
|
1338
|
+
var antigravity = {
|
|
1339
|
+
name: NAME2,
|
|
1340
|
+
events: AG_EVENTS,
|
|
1341
|
+
detect: (env = process.env) => existsSync3(path6.join(gemini(env), "config")) || dataDirs(env).some((d) => existsSync3(d)),
|
|
1342
|
+
configFiles: (env = process.env) => [hooksFile(env)],
|
|
1343
|
+
install(cmd, env = process.env) {
|
|
1344
|
+
const file = hooksFile(env);
|
|
1345
|
+
const f = readJsonFile(file);
|
|
1346
|
+
if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
|
|
1347
|
+
const hooks = f.value;
|
|
1348
|
+
const e = entry(cmd);
|
|
1349
|
+
if (JSON.stringify(hooks[HOOK_NAME]) === JSON.stringify(e)) return [{ file, changed: false }];
|
|
1350
|
+
hooks[HOOK_NAME] = e;
|
|
1351
|
+
mkdirSync2(path6.dirname(file), { recursive: true });
|
|
1352
|
+
writeFileSync3(file, formatJson(hooks, f));
|
|
1353
|
+
return [
|
|
1354
|
+
{
|
|
1355
|
+
file,
|
|
1356
|
+
changed: true,
|
|
1357
|
+
created: !f.existed,
|
|
1358
|
+
note: "hooks.json is read when a conversation starts; open ones report after a restart"
|
|
1359
|
+
}
|
|
1360
|
+
];
|
|
1361
|
+
},
|
|
1362
|
+
uninstall(env = process.env, opts = {}) {
|
|
1363
|
+
const file = hooksFile(env);
|
|
1364
|
+
if (!existsSync3(file)) return [{ file, changed: false }];
|
|
1365
|
+
const f = readJsonFile(file);
|
|
1366
|
+
if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
|
|
1367
|
+
const hooks = f.value;
|
|
1368
|
+
if (!(HOOK_NAME in hooks)) return [{ file, changed: false }];
|
|
1369
|
+
delete hooks[HOOK_NAME];
|
|
1370
|
+
if (opts.created?.includes(file) && !Object.keys(hooks).length) {
|
|
1371
|
+
unlinkSync2(file);
|
|
1372
|
+
return [{ file, changed: true, note: "removed (sessionpipe created it)" }];
|
|
1373
|
+
}
|
|
1374
|
+
writeFileSync3(file, formatJson(hooks, f));
|
|
1375
|
+
return [{ file, changed: true }];
|
|
1376
|
+
},
|
|
1377
|
+
installed(cmd, env = process.env) {
|
|
1378
|
+
const file = hooksFile(env);
|
|
1379
|
+
const f = readJsonFile(file);
|
|
1380
|
+
const have = f.ok ? f.value[HOOK_NAME] : void 0;
|
|
1381
|
+
return [
|
|
1382
|
+
{
|
|
1383
|
+
file,
|
|
1384
|
+
state: !have ? "missing" : JSON.stringify(have) === JSON.stringify(entry(cmd)) ? "current" : "stale"
|
|
1385
|
+
}
|
|
1386
|
+
];
|
|
1387
|
+
},
|
|
1388
|
+
fromHook(input) {
|
|
1389
|
+
const event = input.argv[1] ?? "";
|
|
1390
|
+
let s = {};
|
|
1391
|
+
try {
|
|
1392
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
1393
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1394
|
+
} catch {
|
|
1395
|
+
}
|
|
1396
|
+
const id = s.conversationId ?? s.session_id ?? s.sessionId;
|
|
1397
|
+
const stdout = antigravityAnswer(event);
|
|
1398
|
+
if (!id) return { session: { id: "" }, events: [], stdout };
|
|
1399
|
+
const workspaces = agWorkspaces(s.workspacePaths);
|
|
1400
|
+
const session = { id: String(id), cwd: workspaces[0] ?? input.cwd };
|
|
1401
|
+
if (typeof s.modelName === "string" && s.modelName !== "auto") session.model = s.modelName;
|
|
1402
|
+
const toolCall = s.toolCall;
|
|
1403
|
+
const tool = validTool2(toolCall?.name) ? toolCall?.name : validTool2(s.toolName) ? s.toolName : void 0;
|
|
1404
|
+
const ev = (type, data) => ({ type, data: prune2(data), harnessEvent: event });
|
|
1405
|
+
const events = [];
|
|
1406
|
+
switch (event) {
|
|
1407
|
+
case "PreInvocation":
|
|
1408
|
+
if (Number(s.invocationNum) === 0 || s.invocationNum === void 0) {
|
|
1409
|
+
events.push(ev("session.started", { source: "unknown" }));
|
|
1410
|
+
events.push(ev("turn.started", {}));
|
|
1411
|
+
} else events.push(ev("session.heartbeat", {}));
|
|
1412
|
+
break;
|
|
1413
|
+
case "PostInvocation":
|
|
1414
|
+
break;
|
|
1415
|
+
case "PreToolUse":
|
|
1416
|
+
if (tool) events.push(ev("tool.started", { tool, input: toolCall?.args }));
|
|
1417
|
+
break;
|
|
1418
|
+
case "PostToolUse":
|
|
1419
|
+
if (!tool) events.push(ev("session.heartbeat", {}));
|
|
1420
|
+
else
|
|
1421
|
+
events.push(
|
|
1422
|
+
ev("tool.ended", {
|
|
1423
|
+
tool,
|
|
1424
|
+
ok: !s.error,
|
|
1425
|
+
error: typeof s.error === "string" ? s.error.slice(0, 2e3) : void 0,
|
|
1426
|
+
input: toolCall?.args
|
|
1427
|
+
})
|
|
1428
|
+
);
|
|
1429
|
+
break;
|
|
1430
|
+
case "Stop":
|
|
1431
|
+
events.push(ev("turn.ended", { reason: s.error ? "error" : "stop" }));
|
|
1432
|
+
break;
|
|
1433
|
+
default:
|
|
1434
|
+
return { session, events: [], stdout };
|
|
1435
|
+
}
|
|
1436
|
+
const r = { session, events, stdout, hints: { workspaces } };
|
|
1437
|
+
if (typeof s.transcriptPath === "string") r.transcript = s.transcriptPath;
|
|
1438
|
+
return r;
|
|
1439
|
+
},
|
|
1440
|
+
readTranscript(file, fromLine) {
|
|
1441
|
+
let text = "";
|
|
1442
|
+
try {
|
|
1443
|
+
text = readFileSync4(file, "utf8");
|
|
1444
|
+
} catch {
|
|
1445
|
+
return { turns: [], line: fromLine };
|
|
1446
|
+
}
|
|
1447
|
+
return readAntigravity(text.split("\n"), fromLine);
|
|
1448
|
+
},
|
|
1449
|
+
facts(session, transcript, hints) {
|
|
1450
|
+
if (!transcript) return {};
|
|
1451
|
+
return antigravityFacts(transcript, {
|
|
1452
|
+
conversation: session.id,
|
|
1453
|
+
workspaces: hints.workspaces ?? (session.cwd ? [session.cwd] : []),
|
|
1454
|
+
env: process.env
|
|
1455
|
+
});
|
|
1456
|
+
},
|
|
1457
|
+
backfill(sinceMs, env = process.env) {
|
|
1458
|
+
const rows2 = [];
|
|
1459
|
+
for (const data of dataDirs(env)) {
|
|
1460
|
+
let ids = [];
|
|
1461
|
+
try {
|
|
1462
|
+
ids = readdirSync3(path6.join(data, "brain")).filter((n) => /^[0-9a-f-]{36}$/i.test(n));
|
|
1463
|
+
} catch {
|
|
1464
|
+
}
|
|
1465
|
+
for (const id of ids) {
|
|
1466
|
+
const f = path6.join(data, "brain", id, ".system_generated", "logs", "transcript.jsonl");
|
|
1467
|
+
try {
|
|
1468
|
+
if (statSync4(f).mtimeMs < sinceMs) continue;
|
|
1469
|
+
} catch {
|
|
1470
|
+
continue;
|
|
1471
|
+
}
|
|
1472
|
+
const facts = antigravityFacts(f, { conversation: id, workspaces: [], env });
|
|
1473
|
+
if (!facts.started_at || !facts.last_at) continue;
|
|
1474
|
+
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
1475
|
+
rows2.push({ session: { id, ...rest }, started_at, last_at });
|
|
1476
|
+
}
|
|
1268
1477
|
}
|
|
1478
|
+
return rows2;
|
|
1269
1479
|
}
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
return t.length > 50 ? `${t.slice(0, 50)}...` : t;
|
|
1275
|
-
}
|
|
1276
|
-
function askText(j) {
|
|
1277
|
-
if (!j || j.type !== "user" || j.isMeta || j.isSidechain) return null;
|
|
1278
|
-
const origin = j.origin;
|
|
1279
|
-
if (origin?.kind && origin.kind !== "human") return null;
|
|
1280
|
-
const c = j.message?.content;
|
|
1281
|
-
const text = (typeof c === "string" ? c : Array.isArray(c) ? c.filter((b) => b && b.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n") : "").trim();
|
|
1282
|
-
if (!text || isInjected(text)) return null;
|
|
1283
|
-
return text;
|
|
1480
|
+
};
|
|
1481
|
+
function prune2(o) {
|
|
1482
|
+
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
1483
|
+
return o;
|
|
1284
1484
|
}
|
|
1285
|
-
function
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1485
|
+
function agPath(p) {
|
|
1486
|
+
if (typeof p !== "string" || !p) return null;
|
|
1487
|
+
let s = p;
|
|
1488
|
+
if (/^file:/i.test(s)) {
|
|
1489
|
+
try {
|
|
1490
|
+
s = decodeURIComponent(new URL(s).pathname);
|
|
1491
|
+
} catch {
|
|
1492
|
+
return null;
|
|
1493
|
+
}
|
|
1494
|
+
if (process.platform === "win32" && /^\/[A-Za-z]:/.test(s)) s = s.slice(1);
|
|
1495
|
+
} else if (/^[a-z][a-z0-9+.-]*:\/\//i.test(s)) return null;
|
|
1496
|
+
if (!path6.isAbsolute(s)) return null;
|
|
1497
|
+
return path6.resolve(s);
|
|
1291
1498
|
}
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1499
|
+
var agWorkspaces = (paths) => (Array.isArray(paths) ? paths : []).map(agPath).filter((p) => !!p).filter((p, i, a) => a.indexOf(p) === i);
|
|
1500
|
+
function agVariants(p) {
|
|
1501
|
+
const out = [p];
|
|
1295
1502
|
try {
|
|
1296
|
-
|
|
1503
|
+
const r = realpathSync.native(p);
|
|
1504
|
+
if (r !== p) out.push(r);
|
|
1297
1505
|
} catch {
|
|
1298
|
-
return out;
|
|
1299
|
-
}
|
|
1300
|
-
if (size < from) {
|
|
1301
|
-
out.upto = 0;
|
|
1302
|
-
out.custom = out.ai = null;
|
|
1303
|
-
from = 0;
|
|
1304
1506
|
}
|
|
1305
|
-
let pos = 0;
|
|
1306
|
-
scanLines(
|
|
1307
|
-
file,
|
|
1308
|
-
(l) => {
|
|
1309
|
-
pos += Buffer.byteLength(l, "utf8") + 1;
|
|
1310
|
-
if (pos <= from || l.indexOf('-title"') < 0) return null;
|
|
1311
|
-
try {
|
|
1312
|
-
const j = JSON.parse(l);
|
|
1313
|
-
if (j.type === "custom-title" && j.customTitle) out.custom = j.customTitle;
|
|
1314
|
-
if (j.type === "ai-title" && j.aiTitle) out.ai = j.aiTitle;
|
|
1315
|
-
} catch {
|
|
1316
|
-
}
|
|
1317
|
-
return null;
|
|
1318
|
-
},
|
|
1319
|
-
512 * 1024 * 1024
|
|
1320
|
-
);
|
|
1321
|
-
out.upto = size;
|
|
1322
1507
|
return out;
|
|
1323
1508
|
}
|
|
1324
|
-
function
|
|
1325
|
-
const
|
|
1326
|
-
|
|
1327
|
-
const T = parseLines(tail);
|
|
1328
|
-
const out = {};
|
|
1329
|
-
const id = sessionId || path5.basename(file, ".jsonl");
|
|
1330
|
-
const mem = state.get(NAME2, id);
|
|
1331
|
-
let custom = null;
|
|
1332
|
-
let ai = null;
|
|
1333
|
-
for (const j of H.concat(T)) {
|
|
1334
|
-
if (j.type === "custom-title" && typeof j.customTitle === "string") custom = j.customTitle;
|
|
1335
|
-
if (j.type === "ai-title" && typeof j.aiTitle === "string") ai = j.aiTitle;
|
|
1336
|
-
}
|
|
1337
|
-
let size = 0;
|
|
1509
|
+
function agProjects(dir) {
|
|
1510
|
+
const out = [];
|
|
1511
|
+
let names = [];
|
|
1338
1512
|
try {
|
|
1339
|
-
|
|
1513
|
+
names = readdirSync3(dir).filter((n) => n.endsWith(".json"));
|
|
1340
1514
|
} catch {
|
|
1515
|
+
return out;
|
|
1341
1516
|
}
|
|
1342
|
-
|
|
1343
|
-
const
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1517
|
+
for (const n of names) {
|
|
1518
|
+
const id = n.slice(0, -5);
|
|
1519
|
+
if (!ID_RE.test(id)) continue;
|
|
1520
|
+
let j;
|
|
1521
|
+
try {
|
|
1522
|
+
j = JSON.parse(readFileSync4(path6.join(dir, n), "utf8"));
|
|
1523
|
+
} catch {
|
|
1524
|
+
continue;
|
|
1525
|
+
}
|
|
1526
|
+
const folders = [];
|
|
1527
|
+
for (const r of j?.projectResources?.resources ?? []) {
|
|
1528
|
+
const f = agPath(r?.folderUri ?? r?.gitFolder?.folderUri);
|
|
1529
|
+
if (f) folders.push(...agVariants(f));
|
|
1530
|
+
}
|
|
1531
|
+
if (folders.length) out.push({ id, folders });
|
|
1350
1532
|
}
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
const
|
|
1355
|
-
const
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
state.save(NAME2, id, { url });
|
|
1533
|
+
return out;
|
|
1534
|
+
}
|
|
1535
|
+
function agMatchProject(projects, workspaces) {
|
|
1536
|
+
const ws = [];
|
|
1537
|
+
for (const w of workspaces) {
|
|
1538
|
+
const p = agPath(w);
|
|
1539
|
+
if (p) ws.push(...agVariants(p));
|
|
1359
1540
|
}
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
} catch {
|
|
1369
|
-
return null;
|
|
1541
|
+
let best = null;
|
|
1542
|
+
let bestLen = -1;
|
|
1543
|
+
for (const pr of projects)
|
|
1544
|
+
for (const f of pr.folders) {
|
|
1545
|
+
const within = ws.some((w) => w === f || w.startsWith(f.endsWith(path6.sep) ? f : f + path6.sep));
|
|
1546
|
+
if (within && f.length > bestLen) {
|
|
1547
|
+
best = pr.id;
|
|
1548
|
+
bestLen = f.length;
|
|
1370
1549
|
}
|
|
1371
|
-
}
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1550
|
+
}
|
|
1551
|
+
return best;
|
|
1552
|
+
}
|
|
1553
|
+
function antigravityRemoteUrl(transcript, opts) {
|
|
1554
|
+
const i = transcript.indexOf(`${path6.sep}brain${path6.sep}`);
|
|
1555
|
+
const dirs = (i > 0 ? [transcript.slice(0, i)] : []).concat(dataDirs(opts.env));
|
|
1556
|
+
for (const d of dirs) {
|
|
1557
|
+
try {
|
|
1558
|
+
const m = /installation_uuid:\s*"([0-9a-f-]{36})"/i.exec(
|
|
1559
|
+
readFileSync4(path6.join(d, "antigravity_state.pbtxt"), "utf8")
|
|
1560
|
+
);
|
|
1561
|
+
if (!m) continue;
|
|
1562
|
+
const base = `https://antigravity.google.com/r/${m[1]}-v2`;
|
|
1563
|
+
const conv = opts.conversation ?? "";
|
|
1564
|
+
if (!ID_RE.test(conv) || !opts.workspaces?.length) return base;
|
|
1565
|
+
const project = agMatchProject(agProjects(opts.projectsDir ?? projectsDir(opts.env)), opts.workspaces);
|
|
1566
|
+
return project ? `${base}?p=${encodeURIComponent(`c/${conv}?section=${project}`)}` : base;
|
|
1567
|
+
} catch {
|
|
1568
|
+
}
|
|
1377
1569
|
}
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1570
|
+
return null;
|
|
1571
|
+
}
|
|
1572
|
+
function antigravityFacts(file, opts) {
|
|
1573
|
+
const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
|
|
1574
|
+
const H = parseLines(head);
|
|
1575
|
+
const T = parseLines(tail);
|
|
1576
|
+
const out = {};
|
|
1577
|
+
const url = antigravityRemoteUrl(file, opts);
|
|
1578
|
+
if (url) out.url = url;
|
|
1579
|
+
for (const j of H) {
|
|
1580
|
+
if (j.type !== "USER_INPUT" || typeof j.content !== "string") continue;
|
|
1581
|
+
const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
|
|
1582
|
+
const text = (m ? m[1] : j.content).trim();
|
|
1583
|
+
const line = (text.split("\n").find((l) => l.trim()) ?? "").trim();
|
|
1584
|
+
if (!line || /^<[a-z_-]+>/i.test(line)) continue;
|
|
1585
|
+
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
1383
1586
|
out.title_source = "first-ask";
|
|
1587
|
+
break;
|
|
1384
1588
|
}
|
|
1385
|
-
const
|
|
1386
|
-
if (acct) out.account_id = acct;
|
|
1387
|
-
for (let i = T.length - 1; i >= 0; i--) {
|
|
1388
|
-
const m = T[i].message?.model;
|
|
1389
|
-
if (T[i]?.type === "assistant" && m && m !== "<synthetic>") {
|
|
1390
|
-
out.model = m;
|
|
1391
|
-
break;
|
|
1392
|
-
}
|
|
1393
|
-
}
|
|
1394
|
-
const meta = [...T].reverse().find((j) => j.version) ?? H.find((j) => j.version);
|
|
1395
|
-
if (meta?.version) out.harness_version = String(meta.version);
|
|
1396
|
-
const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
|
|
1589
|
+
const times = H.concat(T).map((j) => Date.parse(String(j.created_at ?? j.timestamp ?? ""))).filter((n) => n > 0);
|
|
1397
1590
|
if (times.length) {
|
|
1398
1591
|
out.started_at = Math.min(...times);
|
|
1399
1592
|
out.last_at = Math.max(...times);
|
|
1400
1593
|
}
|
|
1401
|
-
const withCwd = H.find((j) => typeof j.cwd === "string");
|
|
1402
|
-
if (withCwd) {
|
|
1403
|
-
out.cwd = withCwd.cwd;
|
|
1404
|
-
if (typeof withCwd.gitBranch === "string" && withCwd.gitBranch !== "HEAD") out.branch = withCwd.gitBranch;
|
|
1405
|
-
}
|
|
1406
1594
|
return out;
|
|
1407
1595
|
}
|
|
1408
1596
|
|
|
@@ -1415,15 +1603,24 @@ var NONCE_TTL_MS = 25 * 36e5;
|
|
|
1415
1603
|
var MODEL_RE = /^[A-Za-z0-9][A-Za-z0-9._:/@[\]-]*$/;
|
|
1416
1604
|
var START_PERMISSIONS = ["read", "safe", "edits", "auto"];
|
|
1417
1605
|
var levelRank = (l) => START_PERMISSIONS.indexOf(l);
|
|
1606
|
+
var FIELDS = {
|
|
1607
|
+
prompt: ["text", "files"],
|
|
1608
|
+
"permission.answer": ["for", "decision"],
|
|
1609
|
+
cancel: [],
|
|
1610
|
+
start: ["text", "cwd", "files", "permission", "model"],
|
|
1611
|
+
"key.add": ["key", "name"],
|
|
1612
|
+
pool: ["pool_action", "pool_account", "pool_order", "pool_code"]
|
|
1613
|
+
};
|
|
1614
|
+
var POOL_FIELDS = FIELDS.pool;
|
|
1418
1615
|
var enc = new TextEncoder();
|
|
1419
1616
|
var dec = new TextDecoder();
|
|
1420
1617
|
|
|
1421
1618
|
// ../core/src/adapters/codex.ts
|
|
1422
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as
|
|
1423
|
-
import
|
|
1619
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
1620
|
+
import path7 from "node:path";
|
|
1424
1621
|
|
|
1425
1622
|
// ../core/src/adapters/codex-config.ts
|
|
1426
|
-
var NOTIFY_LINE = /^[ \t]*notify[ \t]*=[ \t]*(\[.*\])[ \t]*(?:#.*)
|
|
1623
|
+
var NOTIFY_LINE = /^[ \t]*notify[ \t]*=[ \t]*(\[.*\])[ \t]*(?:#.*)?(?=\r?$)/m;
|
|
1427
1624
|
var NOTIFY_KEY = /^[ \t]*notify[ \t]*=/m;
|
|
1428
1625
|
var OURS2 = /(?:hook\.js|sessionpipe-hook)",\s*"codex",\s*"notify"/;
|
|
1429
1626
|
function scan(line, st) {
|
|
@@ -1513,10 +1710,11 @@ function dropLine(text, index, length) {
|
|
|
1513
1710
|
}
|
|
1514
1711
|
function insertTopLevel(text, line) {
|
|
1515
1712
|
const { head, tail } = split(text);
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1713
|
+
const body = head.replace(/\s*$/, "");
|
|
1714
|
+
const eol = (body.includes("\n") ? body : text).includes("\r\n") ? "\r\n" : "\n";
|
|
1715
|
+
const top = head.trim() ? head.replace(/\s*$/, eol) : "";
|
|
1716
|
+
if (!tail) return top + line + eol;
|
|
1717
|
+
return `${top + line}${eol}${eol}${tail}`;
|
|
1520
1718
|
}
|
|
1521
1719
|
function withNotify(text, line) {
|
|
1522
1720
|
const n = readNotify(text);
|
|
@@ -1565,25 +1763,25 @@ var CODEX_EVENTS = [
|
|
|
1565
1763
|
];
|
|
1566
1764
|
var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
1567
1765
|
var eventsFor2 = (lean) => lean ? CODEX_EVENTS.filter((e) => e !== "PreToolUse") : [...CODEX_EVENTS];
|
|
1568
|
-
var codexHome = (env) => env.CODEX_HOME ||
|
|
1569
|
-
var hooksFile2 = (env) =>
|
|
1570
|
-
var configFile2 = (env) =>
|
|
1571
|
-
var sessionsDir = (env) =>
|
|
1766
|
+
var codexHome = (env) => env.CODEX_HOME || path7.join(homeDir(), ".codex");
|
|
1767
|
+
var hooksFile2 = (env) => path7.join(codexHome(env), "hooks.json");
|
|
1768
|
+
var configFile2 = (env) => path7.join(codexHome(env), "config.toml");
|
|
1769
|
+
var sessionsDir = (env) => path7.join(codexHome(env), "sessions");
|
|
1572
1770
|
var validTool3 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
1573
1771
|
var endReason2 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
|
|
1574
1772
|
function installNotify(cmd, env) {
|
|
1575
1773
|
const file = configFile2(env);
|
|
1576
1774
|
let text = "";
|
|
1577
1775
|
try {
|
|
1578
|
-
text =
|
|
1776
|
+
text = readFileSync5(file, "utf8");
|
|
1579
1777
|
} catch {
|
|
1580
1778
|
}
|
|
1581
1779
|
const argv = cmd([NAME3, "notify"]).argv;
|
|
1582
1780
|
const line = `notify = ${JSON.stringify(argv)}`;
|
|
1583
1781
|
const n = readNotify(text);
|
|
1584
1782
|
const write = (next) => {
|
|
1585
|
-
mkdirSync3(
|
|
1586
|
-
|
|
1783
|
+
mkdirSync3(path7.dirname(file), { recursive: true });
|
|
1784
|
+
writeFileSync4(file, next);
|
|
1587
1785
|
};
|
|
1588
1786
|
if (n && !n.other) {
|
|
1589
1787
|
if (!n.misplaced && JSON.stringify(n.argv) === JSON.stringify(argv))
|
|
@@ -1621,19 +1819,19 @@ function uninstallNotify(env) {
|
|
|
1621
1819
|
const file = configFile2(env);
|
|
1622
1820
|
let text;
|
|
1623
1821
|
try {
|
|
1624
|
-
text =
|
|
1822
|
+
text = readFileSync5(file, "utf8");
|
|
1625
1823
|
} catch {
|
|
1626
1824
|
return { file, changed: false };
|
|
1627
1825
|
}
|
|
1628
1826
|
const n = readNotify(text);
|
|
1629
1827
|
if (!n) return { file, changed: false };
|
|
1630
1828
|
if (!n.other) {
|
|
1631
|
-
|
|
1829
|
+
writeFileSync4(file, withoutNotify(text));
|
|
1632
1830
|
return { file, changed: true };
|
|
1633
1831
|
}
|
|
1634
1832
|
const prev = previousNotify(n.argv);
|
|
1635
1833
|
if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev))) {
|
|
1636
|
-
|
|
1834
|
+
writeFileSync4(file, replaceNotify(text, n, `notify = ${stripPreviousRaw(n.raw)}`));
|
|
1637
1835
|
return { file, changed: true };
|
|
1638
1836
|
}
|
|
1639
1837
|
return { file, changed: false };
|
|
@@ -1649,7 +1847,7 @@ function findRollout(threadId, env = process.env) {
|
|
|
1649
1847
|
return;
|
|
1650
1848
|
}
|
|
1651
1849
|
for (const n of names.sort().reverse()) {
|
|
1652
|
-
const p =
|
|
1850
|
+
const p = path7.join(dir, n);
|
|
1653
1851
|
if (n.endsWith(`-${threadId}.jsonl`)) {
|
|
1654
1852
|
best = p;
|
|
1655
1853
|
return;
|
|
@@ -1693,7 +1891,7 @@ var codex = {
|
|
|
1693
1891
|
];
|
|
1694
1892
|
let text = "";
|
|
1695
1893
|
try {
|
|
1696
|
-
text =
|
|
1894
|
+
text = readFileSync5(configFile2(env), "utf8");
|
|
1697
1895
|
} catch {
|
|
1698
1896
|
}
|
|
1699
1897
|
const n = readNotify(text);
|
|
@@ -1820,7 +2018,7 @@ var codex = {
|
|
|
1820
2018
|
readTranscript(file, fromLine) {
|
|
1821
2019
|
let text = "";
|
|
1822
2020
|
try {
|
|
1823
|
-
text =
|
|
2021
|
+
text = readFileSync5(file, "utf8");
|
|
1824
2022
|
} catch {
|
|
1825
2023
|
return { turns: [], line: fromLine };
|
|
1826
2024
|
}
|
|
@@ -1834,7 +2032,7 @@ var codex = {
|
|
|
1834
2032
|
backfill(sinceMs, env = process.env) {
|
|
1835
2033
|
const rows2 = [];
|
|
1836
2034
|
for (const f of recentFiles(sessionsDir(env), 3, sinceMs, (n) => /^rollout-.*\.jsonl$/.test(n))) {
|
|
1837
|
-
const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(
|
|
2035
|
+
const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(path7.basename(f));
|
|
1838
2036
|
if (!m?.[1]) continue;
|
|
1839
2037
|
if (codexSubagent(rolloutMeta(f))) continue;
|
|
1840
2038
|
const facts = codexFacts(f);
|
|
@@ -1843,12 +2041,40 @@ var codex = {
|
|
|
1843
2041
|
rows2.push({ session: { id: m[1], ...rest }, started_at, last_at });
|
|
1844
2042
|
}
|
|
1845
2043
|
return rows2;
|
|
1846
|
-
}
|
|
2044
|
+
},
|
|
2045
|
+
limits: (transcript) => codexLimits(transcript)
|
|
1847
2046
|
};
|
|
2047
|
+
var epochIso2 = (t) => typeof t === "number" && t > 0 && t < 1e11 ? new Date(t * 1e3).toISOString() : void 0;
|
|
1848
2048
|
function prune3(o) {
|
|
1849
2049
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
1850
2050
|
return o;
|
|
1851
2051
|
}
|
|
2052
|
+
function codexLimits(file) {
|
|
2053
|
+
const { tail } = ends(file, 0, 256 * 1024);
|
|
2054
|
+
for (const r of parseLines(tail).reverse()) {
|
|
2055
|
+
const p = r.payload;
|
|
2056
|
+
if (r.type !== "event_msg" || p?.type !== "token_count") continue;
|
|
2057
|
+
if (!p.rate_limits || typeof p.rate_limits !== "object") continue;
|
|
2058
|
+
const rl = p.rate_limits;
|
|
2059
|
+
if (rl.limit_id != null && rl.limit_id !== "codex") continue;
|
|
2060
|
+
const windows = [];
|
|
2061
|
+
for (const id of ["primary", "secondary"]) {
|
|
2062
|
+
const w = rl[id];
|
|
2063
|
+
if (typeof w?.used_percent !== "number" || w.used_percent < 0) continue;
|
|
2064
|
+
windows.push(
|
|
2065
|
+
prune3({
|
|
2066
|
+
id,
|
|
2067
|
+
used_percent: w.used_percent,
|
|
2068
|
+
window_minutes: Number.isInteger(w.window_minutes) && Number(w.window_minutes) > 0 ? Number(w.window_minutes) : void 0,
|
|
2069
|
+
resets_at: epochIso2(w.resets_at)
|
|
2070
|
+
})
|
|
2071
|
+
);
|
|
2072
|
+
}
|
|
2073
|
+
if (!windows.length) return null;
|
|
2074
|
+
return prune3({ windows, plan: typeof rl.plan_type === "string" ? rl.plan_type.slice(0, 64) : void 0 });
|
|
2075
|
+
}
|
|
2076
|
+
return null;
|
|
2077
|
+
}
|
|
1852
2078
|
function codexFacts(file) {
|
|
1853
2079
|
const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
|
|
1854
2080
|
let H = parseLines(head);
|
|
@@ -1893,7 +2119,7 @@ function codexFacts(file) {
|
|
|
1893
2119
|
// ../core/src/adapters/devin.ts
|
|
1894
2120
|
import { createHash as createHash3 } from "node:crypto";
|
|
1895
2121
|
import { existsSync as existsSync6, readlinkSync } from "node:fs";
|
|
1896
|
-
import
|
|
2122
|
+
import path8 from "node:path";
|
|
1897
2123
|
|
|
1898
2124
|
// ../core/src/readers/sqlite.ts
|
|
1899
2125
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
@@ -2077,11 +2303,11 @@ function foreignEvent(file) {
|
|
|
2077
2303
|
return Object.keys(hooks).find((k) => !ok.includes(k));
|
|
2078
2304
|
}
|
|
2079
2305
|
var homeOf = (env) => env.HOME || homeDir();
|
|
2080
|
-
var appData = (env) => env.APPDATA ||
|
|
2081
|
-
var devinConfigDir = (env = process.env) => process.platform === "win32" ?
|
|
2082
|
-
var configFile3 = (env) =>
|
|
2083
|
-
var devinDataDir = (env = process.env) => process.platform === "win32" ?
|
|
2084
|
-
var sessionsDb = (env) =>
|
|
2306
|
+
var appData = (env) => env.APPDATA || path8.join(homeOf(env), "AppData", "Roaming");
|
|
2307
|
+
var devinConfigDir = (env = process.env) => process.platform === "win32" ? path8.join(appData(env), "devin") : path8.join(env.XDG_CONFIG_HOME || path8.join(homeOf(env), ".config"), "devin");
|
|
2308
|
+
var configFile3 = (env) => path8.join(devinConfigDir(env), "config.json");
|
|
2309
|
+
var devinDataDir = (env = process.env) => process.platform === "win32" ? path8.join(appData(env), "devin", "cli") : path8.join(env.XDG_DATA_HOME || path8.join(homeOf(env), ".local", "share"), "devin", "cli");
|
|
2310
|
+
var sessionsDb = (env) => path8.join(devinDataDir(env), "sessions.db");
|
|
2085
2311
|
var SESSION_ID = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
2086
2312
|
var validTool4 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
2087
2313
|
var endReason3 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
|
|
@@ -2253,7 +2479,7 @@ var BACKFILL_SQL = `SELECT s.id, ${SESSION_FIELDS} FROM sessions s
|
|
|
2253
2479
|
function sessionFacts(row) {
|
|
2254
2480
|
const out = {};
|
|
2255
2481
|
if (typeof row.model === "string" && row.model) out.model = row.model;
|
|
2256
|
-
if (typeof row.working_directory === "string" &&
|
|
2482
|
+
if (typeof row.working_directory === "string" && path8.isAbsolute(row.working_directory))
|
|
2257
2483
|
out.cwd = row.working_directory;
|
|
2258
2484
|
const started = Number(row.created_at);
|
|
2259
2485
|
const last = Number(row.last_activity_at);
|
|
@@ -2298,8 +2524,8 @@ function readDevinChain(db, session, fromLine) {
|
|
|
2298
2524
|
}
|
|
2299
2525
|
function devinVersion(env = process.env) {
|
|
2300
2526
|
try {
|
|
2301
|
-
const target = readlinkSync(
|
|
2302
|
-
const name =
|
|
2527
|
+
const target = readlinkSync(path8.join(devinDataDir(env), "_versions", "current"));
|
|
2528
|
+
const name = path8.basename(target);
|
|
2303
2529
|
return /^[0-9][0-9A-Za-z.+-]{0,63}$/.test(name) ? name : void 0;
|
|
2304
2530
|
} catch {
|
|
2305
2531
|
return void 0;
|
|
@@ -2313,8 +2539,8 @@ function devinAccount(env = process.env) {
|
|
|
2313
2539
|
|
|
2314
2540
|
// ../core/src/adapters/gemini-cli.ts
|
|
2315
2541
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
2316
|
-
import { existsSync as existsSync7, readFileSync as
|
|
2317
|
-
import
|
|
2542
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
|
|
2543
|
+
import path9 from "node:path";
|
|
2318
2544
|
var NAME5 = "gemini-cli";
|
|
2319
2545
|
var GEMINI_EVENTS = [
|
|
2320
2546
|
"SessionStart",
|
|
@@ -2327,26 +2553,31 @@ var GEMINI_EVENTS = [
|
|
|
2327
2553
|
"SessionEnd"
|
|
2328
2554
|
];
|
|
2329
2555
|
var OPTS4 = { matcherFor: (e) => /Tool/.test(e) ? ".*" : void 0, timeout: 5e3 };
|
|
2330
|
-
var geminiDir = (env) => env.GEMINI_CLI_HOME ||
|
|
2331
|
-
var settings = (env) =>
|
|
2556
|
+
var geminiDir = (env) => env.GEMINI_CLI_HOME || path9.join(homeDir(), ".gemini");
|
|
2557
|
+
var settings = (env) => path9.join(geminiDir(env), "settings.json");
|
|
2332
2558
|
var validTool5 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
2333
2559
|
var eventsFor4 = (lean) => lean ? GEMINI_EVENTS.filter((e) => e !== "BeforeTool") : [...GEMINI_EVENTS];
|
|
2560
|
+
function geminiVersion(platform = process.platform, env = process.env) {
|
|
2561
|
+
const win = platform === "win32";
|
|
2562
|
+
const bin = win ? findBinary({ names: ["gemini.exe", "gemini.cmd"], dirs: [path9.dirname(process.execPath)], env }) : "gemini";
|
|
2563
|
+
if (!bin) throw new Error("no gemini on PATH");
|
|
2564
|
+
const c = probe(bin, ["--version"]);
|
|
2565
|
+
return execFileSync4(c.file, c.args, {
|
|
2566
|
+
encoding: "utf8",
|
|
2567
|
+
timeout: 4e3,
|
|
2568
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2569
|
+
env,
|
|
2570
|
+
shell: c.shell
|
|
2571
|
+
}).trim();
|
|
2572
|
+
}
|
|
2334
2573
|
function geminiHooksActive(settingsFile) {
|
|
2335
|
-
|
|
2336
|
-
try {
|
|
2337
|
-
tools = JSON.parse(readFileSync5(settingsFile, "utf8")).tools;
|
|
2338
|
-
} catch {
|
|
2339
|
-
}
|
|
2574
|
+
const tools = readJson(settingsFile).tools;
|
|
2340
2575
|
if (tools?.enableHooks === true) return { active: true };
|
|
2341
2576
|
if (tools?.enableHooks === false)
|
|
2342
2577
|
return { active: false, note: "inactive: tools.enableHooks is false in settings.json" };
|
|
2343
2578
|
let version = "";
|
|
2344
2579
|
try {
|
|
2345
|
-
version =
|
|
2346
|
-
encoding: "utf8",
|
|
2347
|
-
timeout: 4e3,
|
|
2348
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
2349
|
-
}).trim();
|
|
2580
|
+
version = geminiVersion();
|
|
2350
2581
|
} catch {
|
|
2351
2582
|
return {
|
|
2352
2583
|
active: true,
|
|
@@ -2364,7 +2595,7 @@ var geminiCli = {
|
|
|
2364
2595
|
name: NAME5,
|
|
2365
2596
|
events: GEMINI_EVENTS,
|
|
2366
2597
|
// ~/.gemini also belongs to Antigravity; Gemini CLI is here when its settings or its chat logs exist.
|
|
2367
|
-
detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(
|
|
2598
|
+
detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(path9.join(geminiDir(env), "tmp")),
|
|
2368
2599
|
configFiles: (env = process.env) => [settings(env)],
|
|
2369
2600
|
install: (cmd, env = process.env, opts = {}) => {
|
|
2370
2601
|
const r = installClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
|
|
@@ -2462,7 +2693,7 @@ var geminiCli = {
|
|
|
2462
2693
|
readTranscript(file, fromLine) {
|
|
2463
2694
|
let text = "";
|
|
2464
2695
|
try {
|
|
2465
|
-
text =
|
|
2696
|
+
text = readFileSync6(file, "utf8");
|
|
2466
2697
|
} catch {
|
|
2467
2698
|
return { turns: [], line: fromLine };
|
|
2468
2699
|
}
|
|
@@ -2502,12 +2733,12 @@ var ADAPTERS = [claudeCode, codex, geminiCli, antigravity, devin];
|
|
|
2502
2733
|
var adapterByName = (name) => ADAPTERS.find((a) => a.name === name);
|
|
2503
2734
|
|
|
2504
2735
|
// ../core/src/config.ts
|
|
2505
|
-
import { chmodSync, existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as
|
|
2506
|
-
import
|
|
2736
|
+
import { chmodSync, existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "node:fs";
|
|
2737
|
+
import path10 from "node:path";
|
|
2507
2738
|
var DEFAULT_CONFIG = { sinks: [], harnesses: {}, update_check: true, keep_days: 30 };
|
|
2508
2739
|
function readConfig(file = configFile()) {
|
|
2509
2740
|
try {
|
|
2510
|
-
const j = JSON.parse(
|
|
2741
|
+
const j = JSON.parse(readFileSync7(file, "utf8"));
|
|
2511
2742
|
return {
|
|
2512
2743
|
...DEFAULT_CONFIG,
|
|
2513
2744
|
...j,
|
|
@@ -2519,8 +2750,8 @@ function readConfig(file = configFile()) {
|
|
|
2519
2750
|
}
|
|
2520
2751
|
}
|
|
2521
2752
|
function writeConfig(c, file = configFile()) {
|
|
2522
|
-
mkdirSync4(
|
|
2523
|
-
|
|
2753
|
+
mkdirSync4(path10.dirname(file), { recursive: true, mode: 448 });
|
|
2754
|
+
writeFileSync5(file, `${JSON.stringify(c, null, 2)}
|
|
2524
2755
|
`, { mode: 384 });
|
|
2525
2756
|
if (process.platform !== "win32" && existsSync8(file)) {
|
|
2526
2757
|
try {
|
|
@@ -2607,14 +2838,14 @@ import {
|
|
|
2607
2838
|
mkdirSync as mkdirSync5,
|
|
2608
2839
|
openSync as openSync2,
|
|
2609
2840
|
readdirSync as readdirSync5,
|
|
2610
|
-
readFileSync as
|
|
2841
|
+
readFileSync as readFileSync8,
|
|
2611
2842
|
readSync as readSync2,
|
|
2612
2843
|
renameSync,
|
|
2613
|
-
statSync as
|
|
2844
|
+
statSync as statSync5,
|
|
2614
2845
|
unlinkSync as unlinkSync3,
|
|
2615
|
-
writeFileSync as
|
|
2846
|
+
writeFileSync as writeFileSync6
|
|
2616
2847
|
} from "node:fs";
|
|
2617
|
-
import
|
|
2848
|
+
import path11 from "node:path";
|
|
2618
2849
|
var READ_CHUNK = 1024 * 1024;
|
|
2619
2850
|
var READ_MAX = 8 * 1024 * 1024;
|
|
2620
2851
|
var Outbox = class {
|
|
@@ -2623,13 +2854,13 @@ var Outbox = class {
|
|
|
2623
2854
|
}
|
|
2624
2855
|
root;
|
|
2625
2856
|
dir(harness) {
|
|
2626
|
-
return
|
|
2857
|
+
return path11.join(this.root, "outbox", safeId(harness));
|
|
2627
2858
|
}
|
|
2628
2859
|
file(ref) {
|
|
2629
|
-
return
|
|
2860
|
+
return path11.join(this.dir(ref.harness), `${safeId(ref.session)}.jsonl`);
|
|
2630
2861
|
}
|
|
2631
2862
|
seqFile(ref) {
|
|
2632
|
-
return
|
|
2863
|
+
return path11.join(this.dir(ref.harness), `${safeId(ref.session)}.seq`);
|
|
2633
2864
|
}
|
|
2634
2865
|
/** The next seq for a session, persisted (survives a hibernating machine and a
|
|
2635
2866
|
* restart; an in-memory counter would restart at 0 and collide). */
|
|
@@ -2637,19 +2868,19 @@ var Outbox = class {
|
|
|
2637
2868
|
const f = this.seqFile(ref);
|
|
2638
2869
|
let n = -1;
|
|
2639
2870
|
try {
|
|
2640
|
-
const t =
|
|
2871
|
+
const t = readFileSync8(f, "utf8").trim();
|
|
2641
2872
|
if (/^\d+$/.test(t)) n = Number(t);
|
|
2642
2873
|
} catch {
|
|
2643
2874
|
}
|
|
2644
2875
|
if (n < 0) n = this.lastSeqInFile(ref) + 1;
|
|
2645
|
-
mkdirSync5(
|
|
2646
|
-
|
|
2876
|
+
mkdirSync5(path11.dirname(f), { recursive: true, mode: 448 });
|
|
2877
|
+
writeFileSync6(f, String(n + 1), { mode: 384 });
|
|
2647
2878
|
return n;
|
|
2648
2879
|
}
|
|
2649
2880
|
lastSeqInFile(ref) {
|
|
2650
2881
|
const f = this.file(ref);
|
|
2651
2882
|
if (!existsSync9(f)) return -1;
|
|
2652
|
-
const lines =
|
|
2883
|
+
const lines = readFileSync8(f, "utf8").trim().split("\n");
|
|
2653
2884
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
2654
2885
|
try {
|
|
2655
2886
|
const j = JSON.parse(lines[i]);
|
|
@@ -2668,10 +2899,10 @@ var Outbox = class {
|
|
|
2668
2899
|
/** Every session file, newest first. */
|
|
2669
2900
|
sessions() {
|
|
2670
2901
|
const out = [];
|
|
2671
|
-
const base =
|
|
2902
|
+
const base = path11.join(this.root, "outbox");
|
|
2672
2903
|
if (!existsSync9(base)) return [];
|
|
2673
2904
|
for (const h of readdirSync5(base)) {
|
|
2674
|
-
const d =
|
|
2905
|
+
const d = path11.join(base, h);
|
|
2675
2906
|
let names = [];
|
|
2676
2907
|
try {
|
|
2677
2908
|
names = readdirSync5(d).filter((n) => n.endsWith(".jsonl"));
|
|
@@ -2680,7 +2911,7 @@ var Outbox = class {
|
|
|
2680
2911
|
}
|
|
2681
2912
|
for (const n of names) {
|
|
2682
2913
|
try {
|
|
2683
|
-
out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime:
|
|
2914
|
+
out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime: statSync5(path11.join(d, n)).mtimeMs });
|
|
2684
2915
|
} catch {
|
|
2685
2916
|
}
|
|
2686
2917
|
}
|
|
@@ -2696,7 +2927,7 @@ var Outbox = class {
|
|
|
2696
2927
|
read(ref, from, maxEvents = 500) {
|
|
2697
2928
|
const f = this.file(ref);
|
|
2698
2929
|
if (!existsSync9(f)) return { events: [], offset: from };
|
|
2699
|
-
const size =
|
|
2930
|
+
const size = statSync5(f).size;
|
|
2700
2931
|
if (from >= size) return { events: [], offset: from };
|
|
2701
2932
|
const fd = openSync2(f, "r");
|
|
2702
2933
|
try {
|
|
@@ -2738,7 +2969,7 @@ var Outbox = class {
|
|
|
2738
2969
|
for (const ref of this.sessions()) {
|
|
2739
2970
|
const f = this.file(ref);
|
|
2740
2971
|
try {
|
|
2741
|
-
if (
|
|
2972
|
+
if (statSync5(f).mtimeMs < cutoff) {
|
|
2742
2973
|
unlinkSync3(f);
|
|
2743
2974
|
try {
|
|
2744
2975
|
unlinkSync3(this.seqFile(ref));
|
|
@@ -2768,20 +2999,20 @@ var Cursors = class {
|
|
|
2768
2999
|
root;
|
|
2769
3000
|
sink;
|
|
2770
3001
|
file() {
|
|
2771
|
-
return
|
|
3002
|
+
return path11.join(this.root, "cursors", `${safeId(this.sink)}.json`);
|
|
2772
3003
|
}
|
|
2773
3004
|
read() {
|
|
2774
3005
|
try {
|
|
2775
|
-
return JSON.parse(
|
|
3006
|
+
return JSON.parse(readFileSync8(this.file(), "utf8"));
|
|
2776
3007
|
} catch {
|
|
2777
3008
|
return {};
|
|
2778
3009
|
}
|
|
2779
3010
|
}
|
|
2780
3011
|
write(c) {
|
|
2781
3012
|
const f = this.file();
|
|
2782
|
-
mkdirSync5(
|
|
3013
|
+
mkdirSync5(path11.dirname(f), { recursive: true, mode: 448 });
|
|
2783
3014
|
const tmp = `${f}.${process.pid}.tmp`;
|
|
2784
|
-
|
|
3015
|
+
writeFileSync6(tmp, JSON.stringify(c), { mode: 384 });
|
|
2785
3016
|
renameSync(tmp, f);
|
|
2786
3017
|
}
|
|
2787
3018
|
key(ref) {
|
|
@@ -2792,16 +3023,16 @@ function takeLock(root, ref, staleMs = 5 * 6e4) {
|
|
|
2792
3023
|
return takeNamedLock(root, `${safeId(ref.harness)}-${safeId(ref.session)}`, staleMs);
|
|
2793
3024
|
}
|
|
2794
3025
|
function takeNamedLock(root, name, staleMs = 5 * 6e4) {
|
|
2795
|
-
const dir =
|
|
3026
|
+
const dir = path11.join(root, "locks");
|
|
2796
3027
|
mkdirSync5(dir, { recursive: true, mode: 448 });
|
|
2797
|
-
const f =
|
|
3028
|
+
const f = path11.join(dir, `${name}.lock`);
|
|
2798
3029
|
try {
|
|
2799
|
-
if (Date.now() -
|
|
3030
|
+
if (Date.now() - statSync5(f).mtimeMs > staleMs || !processAlive(Number(readFileSync8(f, "utf8")))) unlinkSync3(f);
|
|
2800
3031
|
else return null;
|
|
2801
3032
|
} catch {
|
|
2802
3033
|
}
|
|
2803
3034
|
try {
|
|
2804
|
-
|
|
3035
|
+
writeFileSync6(f, String(process.pid), { flag: "wx" });
|
|
2805
3036
|
} catch {
|
|
2806
3037
|
return null;
|
|
2807
3038
|
}
|
|
@@ -3033,6 +3264,7 @@ var TYPE_TIER = {
|
|
|
3033
3264
|
"subagent.started": 1,
|
|
3034
3265
|
"subagent.ended": 1,
|
|
3035
3266
|
"context.compacted": 1,
|
|
3267
|
+
"limits.observed": 1,
|
|
3036
3268
|
native: 3
|
|
3037
3269
|
};
|
|
3038
3270
|
var DATA_ALLOW = {
|
|
@@ -3052,7 +3284,8 @@ var DATA_ALLOW = {
|
|
|
3052
3284
|
"attention.cleared": { 0: ["attention_id", "how"] },
|
|
3053
3285
|
"subagent.started": { 1: ["agent_id", "agent_type"] },
|
|
3054
3286
|
"subagent.ended": { 1: ["agent_id", "agent_type"] },
|
|
3055
|
-
"context.compacted": { 1: ["trigger"] }
|
|
3287
|
+
"context.compacted": { 1: ["trigger"] },
|
|
3288
|
+
"limits.observed": { 1: ["windows", "plan", "account_id"] }
|
|
3056
3289
|
};
|
|
3057
3290
|
var SESSION_ALLOW = [
|
|
3058
3291
|
"id",
|
|
@@ -3117,7 +3350,9 @@ function filterEvent(event, tier, pii, opts = {}) {
|
|
|
3117
3350
|
const s = reducePiiDeep(out.session, opts.home);
|
|
3118
3351
|
if (typeof s.machine === "string") s.machine = hashMachine(s.machine);
|
|
3119
3352
|
delete s.account_id;
|
|
3120
|
-
|
|
3353
|
+
const d = reducePiiDeep(out.data, opts.home);
|
|
3354
|
+
delete d.account_id;
|
|
3355
|
+
out = { ...out, session: s, data: d };
|
|
3121
3356
|
rulesets.push(RULESET_PII);
|
|
3122
3357
|
}
|
|
3123
3358
|
out.privacy = { rulesets, pii };
|
|
@@ -3132,7 +3367,7 @@ function shortId(harness, id) {
|
|
|
3132
3367
|
|
|
3133
3368
|
// ../core/src/sinks/file.ts
|
|
3134
3369
|
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync6 } from "node:fs";
|
|
3135
|
-
import
|
|
3370
|
+
import path12 from "node:path";
|
|
3136
3371
|
var FileSink = class {
|
|
3137
3372
|
constructor(name, file, tier, pii) {
|
|
3138
3373
|
this.name = name;
|
|
@@ -3146,7 +3381,7 @@ var FileSink = class {
|
|
|
3146
3381
|
pii;
|
|
3147
3382
|
async send(events) {
|
|
3148
3383
|
try {
|
|
3149
|
-
mkdirSync6(
|
|
3384
|
+
mkdirSync6(path12.dirname(this.file), { recursive: true });
|
|
3150
3385
|
appendFileSync2(this.file, `${events.map((e) => JSON.stringify(e)).join("\n")}
|
|
3151
3386
|
`);
|
|
3152
3387
|
return { ok: true, acknowledged: events.length, accepted: events.length, duplicates: 0, rejected: [] };
|
|
@@ -3180,8 +3415,8 @@ var StdoutSink = class {
|
|
|
3180
3415
|
};
|
|
3181
3416
|
|
|
3182
3417
|
// src/control/aliases.ts
|
|
3183
|
-
import { chmodSync as chmodSync2, mkdirSync as mkdirSync7, readFileSync as
|
|
3184
|
-
import
|
|
3418
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync7, readFileSync as readFileSync9, renameSync as renameSync2, writeFileSync as writeFileSync7 } from "node:fs";
|
|
3419
|
+
import path13 from "node:path";
|
|
3185
3420
|
|
|
3186
3421
|
// src/secrets.ts
|
|
3187
3422
|
import { spawnSync } from "node:child_process";
|
|
@@ -3225,10 +3460,10 @@ function startOptionsOf(v) {
|
|
|
3225
3460
|
}
|
|
3226
3461
|
|
|
3227
3462
|
// src/control/aliases.ts
|
|
3228
|
-
var aliasFile = (controlDir) =>
|
|
3463
|
+
var aliasFile = (controlDir) => path13.join(controlDir, "aliases.json");
|
|
3229
3464
|
function readAliases(file) {
|
|
3230
3465
|
try {
|
|
3231
|
-
const j = JSON.parse(
|
|
3466
|
+
const j = JSON.parse(readFileSync9(file, "utf8"));
|
|
3232
3467
|
if (!j || typeof j !== "object" || Array.isArray(j)) return {};
|
|
3233
3468
|
const out = {};
|
|
3234
3469
|
for (const [k, v] of Object.entries(j)) {
|
|
@@ -3250,11 +3485,11 @@ function receiverIdOf(all, harness, id) {
|
|
|
3250
3485
|
// src/control/local.ts
|
|
3251
3486
|
import net from "node:net";
|
|
3252
3487
|
import os2 from "node:os";
|
|
3253
|
-
import
|
|
3488
|
+
import path14 from "node:path";
|
|
3254
3489
|
function socketPath(state, env = process.env) {
|
|
3255
3490
|
if (env.SESSIONPIPE_SOCKET) return env.SESSIONPIPE_SOCKET;
|
|
3256
3491
|
if (process.platform === "win32") return `\\\\.\\pipe\\sessionpipe-control-${os2.userInfo().username}`;
|
|
3257
|
-
return
|
|
3492
|
+
return path14.join(state, "control.sock");
|
|
3258
3493
|
}
|
|
3259
3494
|
function ask(sock, req, timeoutMs) {
|
|
3260
3495
|
return new Promise((resolve) => {
|
|
@@ -3305,8 +3540,8 @@ async function tellDaemon(state, job, session, cwd, transcript) {
|
|
|
3305
3540
|
function enqueueJob(state, job) {
|
|
3306
3541
|
const jobs = jobsDir(state);
|
|
3307
3542
|
mkdirSync8(jobs, { recursive: true, mode: 448 });
|
|
3308
|
-
const f =
|
|
3309
|
-
|
|
3543
|
+
const f = path15.join(jobs, `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.json`);
|
|
3544
|
+
writeFileSync8(f, JSON.stringify(job), { mode: 384 });
|
|
3310
3545
|
return f;
|
|
3311
3546
|
}
|
|
3312
3547
|
function releaseJob(claimed) {
|
|
@@ -3319,9 +3554,9 @@ function releaseJob(claimed) {
|
|
|
3319
3554
|
function log(state, line) {
|
|
3320
3555
|
try {
|
|
3321
3556
|
mkdirSync8(state, { recursive: true, mode: 448 });
|
|
3322
|
-
const f =
|
|
3557
|
+
const f = path15.join(state, "log");
|
|
3323
3558
|
try {
|
|
3324
|
-
if (
|
|
3559
|
+
if (statSync6(f).size > 1024 * 1024) renameSync3(f, `${f}.1`);
|
|
3325
3560
|
} catch {
|
|
3326
3561
|
}
|
|
3327
3562
|
appendFileSync3(f, `${(/* @__PURE__ */ new Date()).toISOString()} ${line}
|
|
@@ -3330,11 +3565,11 @@ function log(state, line) {
|
|
|
3330
3565
|
}
|
|
3331
3566
|
}
|
|
3332
3567
|
function factsState(state) {
|
|
3333
|
-
const file = (h, s) =>
|
|
3568
|
+
const file = (h, s) => path15.join(state, "facts", safeId(h), `${safeId(s)}.json`);
|
|
3334
3569
|
return {
|
|
3335
3570
|
get(h, s) {
|
|
3336
3571
|
try {
|
|
3337
|
-
return JSON.parse(
|
|
3572
|
+
return JSON.parse(readFileSync10(file(h, s), "utf8"));
|
|
3338
3573
|
} catch {
|
|
3339
3574
|
return {};
|
|
3340
3575
|
}
|
|
@@ -3343,8 +3578,8 @@ function factsState(state) {
|
|
|
3343
3578
|
const cur = this.get(h, s);
|
|
3344
3579
|
const next = { ...cur, ...patch };
|
|
3345
3580
|
if (JSON.stringify(next) === JSON.stringify(cur)) return;
|
|
3346
|
-
mkdirSync8(
|
|
3347
|
-
|
|
3581
|
+
mkdirSync8(path15.dirname(file(h, s)), { recursive: true, mode: 448 });
|
|
3582
|
+
writeFileSync8(file(h, s), JSON.stringify(next), { mode: 384 });
|
|
3348
3583
|
}
|
|
3349
3584
|
};
|
|
3350
3585
|
}
|
|
@@ -3416,7 +3651,7 @@ async function runJob(job, opts = {}) {
|
|
|
3416
3651
|
);
|
|
3417
3652
|
return { events: [], delivered: {} };
|
|
3418
3653
|
}
|
|
3419
|
-
const id = receiverIdOf(readAliases(aliasFile(
|
|
3654
|
+
const id = receiverIdOf(readAliases(aliasFile(path15.join(state, "control"))), job.harness, r.session.id) ?? r.session.id;
|
|
3420
3655
|
const claudeEnd = job.harness === "claude-code" && r.events.some((e) => e.type === "session.ended");
|
|
3421
3656
|
if (!claudeEnd) await tellDaemon(state, job, id, r.session.cwd, r.transcript);
|
|
3422
3657
|
const outbox = new Outbox(state);
|
|
@@ -3445,12 +3680,15 @@ async function runJob(job, opts = {}) {
|
|
|
3445
3680
|
const heavy = !/^(PreToolUse|PostToolUse|PostToolUseFailure|BeforeTool|AfterTool|PostInvocation)$/.test(job.event);
|
|
3446
3681
|
let learned = {};
|
|
3447
3682
|
if (heavy) {
|
|
3448
|
-
const
|
|
3683
|
+
const hookEnv = { ...process.env, ...job.env };
|
|
3684
|
+
const f = adapter.facts(r.session, r.transcript, r.hints ?? {}, facts, hookEnv);
|
|
3685
|
+
if (cfg.remote_titles && adapter.remoteFacts)
|
|
3686
|
+
Object.assign(f, await adapter.remoteFacts(r.session, f, r.transcript, facts, hookEnv));
|
|
3449
3687
|
const { started_at: _s, last_at: _l, first_ask: _fa, harness_version, ...rest } = f;
|
|
3450
3688
|
learned = rest;
|
|
3451
3689
|
if (harness_version) facts.save(job.harness, id, { harness_version });
|
|
3452
3690
|
facts.save(job.harness, id, { session: { ...mem.session, ...learned } });
|
|
3453
|
-
if (CWD_FROM_HOOKS.has(job.harness) && r.session.cwd &&
|
|
3691
|
+
if (CWD_FROM_HOOKS.has(job.harness) && r.session.cwd && path15.isAbsolute(r.session.cwd))
|
|
3454
3692
|
facts.save(job.harness, id, { cwd: r.session.cwd });
|
|
3455
3693
|
}
|
|
3456
3694
|
const known = mem.session ?? {};
|
|
@@ -3532,6 +3770,23 @@ async function runJob(job, opts = {}) {
|
|
|
3532
3770
|
}
|
|
3533
3771
|
if (read.turns.length) facts.save(job.harness, id, { transcript_line: read.line, transcript_turns: turnNo });
|
|
3534
3772
|
}
|
|
3773
|
+
if (adapter.limits && r.transcript && ending && cfg.sinks.some((s) => !s.paused && Math.min(s.tier, s.max_tier ?? 3) >= 1)) {
|
|
3774
|
+
const limits = adapter.limits(r.transcript);
|
|
3775
|
+
const sig = limits && JSON.stringify(limits);
|
|
3776
|
+
if (limits && sig !== facts.get(job.harness, id).limits_sig) {
|
|
3777
|
+
events.push(
|
|
3778
|
+
makeEvent(
|
|
3779
|
+
{ type: "limits.observed", harnessEvent: job.event, data: limits },
|
|
3780
|
+
{
|
|
3781
|
+
harness: { name: job.harness, ...version ? { version } : {} },
|
|
3782
|
+
session: { ...session, seq: outbox.nextSeq(ref) },
|
|
3783
|
+
now: opts.now ?? job.at
|
|
3784
|
+
}
|
|
3785
|
+
)
|
|
3786
|
+
);
|
|
3787
|
+
facts.save(job.harness, id, { limits_sig: sig });
|
|
3788
|
+
}
|
|
3789
|
+
}
|
|
3535
3790
|
outbox.append(
|
|
3536
3791
|
ref,
|
|
3537
3792
|
events.map((e) => ({ ...e, session: redactDeep(e.session), data: redactDeep(e.data) }))
|
|
@@ -3549,11 +3804,11 @@ async function runJob(job, opts = {}) {
|
|
|
3549
3804
|
return { events, delivered };
|
|
3550
3805
|
}
|
|
3551
3806
|
async function drain(state, sinkConfigs, outbox, first) {
|
|
3552
|
-
const more =
|
|
3807
|
+
const more = path15.join(state, "locks", "flush.more");
|
|
3553
3808
|
let release = takeNamedLock(state, "flush");
|
|
3554
3809
|
if (!release) {
|
|
3555
3810
|
try {
|
|
3556
|
-
|
|
3811
|
+
writeFileSync8(more, "", { mode: 384 });
|
|
3557
3812
|
} catch {
|
|
3558
3813
|
}
|
|
3559
3814
|
release = takeNamedLock(state, "flush");
|
|
@@ -3577,10 +3832,10 @@ async function drain(state, sinkConfigs, outbox, first) {
|
|
|
3577
3832
|
}
|
|
3578
3833
|
}
|
|
3579
3834
|
var BACKOFF_MS = [2e3, 1e4, 6e4, 5 * 6e4, 30 * 6e4];
|
|
3580
|
-
var backoffFile = (state, sink) =>
|
|
3835
|
+
var backoffFile = (state, sink) => path15.join(state, "backoff", `${safeId(sink)}.json`);
|
|
3581
3836
|
function readBackoff(state, sink) {
|
|
3582
3837
|
try {
|
|
3583
|
-
const b = JSON.parse(
|
|
3838
|
+
const b = JSON.parse(readFileSync10(backoffFile(state, sink), "utf8"));
|
|
3584
3839
|
return typeof b.until === "number" ? b : null;
|
|
3585
3840
|
} catch {
|
|
3586
3841
|
return null;
|
|
@@ -3593,8 +3848,8 @@ function noteFailure(state, sink, res, prev) {
|
|
|
3593
3848
|
const b = { until: Date.now() + wait, failures, why: `${res.status ?? ""} ${res.error ?? ""}`.trim() };
|
|
3594
3849
|
const f = backoffFile(state, sink);
|
|
3595
3850
|
try {
|
|
3596
|
-
mkdirSync8(
|
|
3597
|
-
|
|
3851
|
+
mkdirSync8(path15.dirname(f), { recursive: true, mode: 448 });
|
|
3852
|
+
writeFileSync8(f, JSON.stringify(b), { mode: 384 });
|
|
3598
3853
|
} catch {
|
|
3599
3854
|
}
|
|
3600
3855
|
return b;
|
|
@@ -3726,7 +3981,7 @@ function applySinkVerdict(name, res) {
|
|
|
3726
3981
|
} catch {
|
|
3727
3982
|
}
|
|
3728
3983
|
}
|
|
3729
|
-
var jobsDir = (state) =>
|
|
3984
|
+
var jobsDir = (state) => path15.join(state, "jobs");
|
|
3730
3985
|
export {
|
|
3731
3986
|
HttpSink,
|
|
3732
3987
|
buildSinks,
|