sessionpipe 0.10.0 → 0.11.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 +5085 -2360
- 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 +37 -1
- package/dist/hook.js.map +2 -2
- package/dist/index.js +1101 -875
- 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 +1096 -870
- 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.11.0 · Apache-2.0 · https://sessionpipe.org
|
|
2
2
|
|
|
3
3
|
// src/http-sink.ts
|
|
4
4
|
import { createHash, createHmac } from "node:crypto";
|
|
@@ -103,25 +103,12 @@ import {
|
|
|
103
103
|
readdirSync as readdirSync6,
|
|
104
104
|
readFileSync as readFileSync9,
|
|
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,816 +627,941 @@ 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
|
|
627
686
|
})
|
|
628
687
|
);
|
|
629
688
|
break;
|
|
630
|
-
case "
|
|
631
|
-
|
|
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
|
|
702
|
+
})
|
|
703
|
+
);
|
|
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;
|
|
724
|
+
case "Stop":
|
|
725
|
+
events.push(
|
|
726
|
+
ev("turn.ended", {
|
|
727
|
+
turn_id,
|
|
728
|
+
reason: "stop",
|
|
729
|
+
background: Array.isArray(s.background_tasks) ? s.background_tasks.length : void 0
|
|
730
|
+
})
|
|
731
|
+
);
|
|
732
|
+
break;
|
|
733
|
+
case "StopFailure":
|
|
734
|
+
events.push(
|
|
735
|
+
ev("turn.ended", {
|
|
736
|
+
turn_id,
|
|
737
|
+
reason: "error",
|
|
738
|
+
error: typeof s.error === "string" ? s.error.slice(0, 200) : void 0
|
|
739
|
+
})
|
|
740
|
+
);
|
|
741
|
+
break;
|
|
742
|
+
case "SubagentStart":
|
|
743
|
+
events.push(
|
|
744
|
+
ev("subagent.started", {
|
|
745
|
+
agent_id: String(s.agent_id ?? ""),
|
|
746
|
+
agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
|
|
747
|
+
})
|
|
748
|
+
);
|
|
749
|
+
break;
|
|
750
|
+
case "SubagentStop":
|
|
751
|
+
events.push(
|
|
752
|
+
ev("subagent.ended", {
|
|
753
|
+
agent_id: String(s.agent_id ?? ""),
|
|
754
|
+
agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
|
|
755
|
+
})
|
|
756
|
+
);
|
|
757
|
+
break;
|
|
758
|
+
case "PreCompact":
|
|
759
|
+
events.push(ev("context.compacted", { trigger: s.trigger === "manual" ? "manual" : "auto" }));
|
|
760
|
+
break;
|
|
761
|
+
case "PostCompact":
|
|
762
|
+
break;
|
|
763
|
+
// PreCompact already said it; a second event would double-count
|
|
764
|
+
case "SessionEnd":
|
|
765
|
+
events.push(ev("session.ended", { reason: endReason(s.reason) }));
|
|
766
|
+
break;
|
|
767
|
+
case "StatusLine": {
|
|
768
|
+
const limits = claudeLimits(s.rate_limits);
|
|
769
|
+
if (!limits) return null;
|
|
770
|
+
events.push(ev("limits.observed", limits));
|
|
632
771
|
break;
|
|
633
|
-
default:
|
|
634
|
-
return { session, events: [], stdout };
|
|
635
|
-
}
|
|
636
|
-
const r = { session, events, stdout, hints: { workspaces } };
|
|
637
|
-
if (typeof s.transcriptPath === "string") r.transcript = s.transcriptPath;
|
|
638
|
-
return r;
|
|
639
|
-
},
|
|
640
|
-
readTranscript(file, fromLine) {
|
|
641
|
-
let text = "";
|
|
642
|
-
try {
|
|
643
|
-
text = readFileSync2(file, "utf8");
|
|
644
|
-
} catch {
|
|
645
|
-
return { turns: [], line: fromLine };
|
|
646
|
-
}
|
|
647
|
-
return readAntigravity(text.split("\n"), fromLine);
|
|
648
|
-
},
|
|
649
|
-
facts(session, transcript, hints) {
|
|
650
|
-
if (!transcript) return {};
|
|
651
|
-
return antigravityFacts(transcript, {
|
|
652
|
-
conversation: session.id,
|
|
653
|
-
workspaces: hints.workspaces ?? (session.cwd ? [session.cwd] : []),
|
|
654
|
-
env: process.env
|
|
655
|
-
});
|
|
656
|
-
},
|
|
657
|
-
backfill(sinceMs, env = process.env) {
|
|
658
|
-
const rows2 = [];
|
|
659
|
-
for (const data of dataDirs(env)) {
|
|
660
|
-
let ids = [];
|
|
661
|
-
try {
|
|
662
|
-
ids = readdirSync2(path4.join(data, "brain")).filter((n) => /^[0-9a-f-]{36}$/i.test(n));
|
|
663
|
-
} catch {
|
|
664
|
-
}
|
|
665
|
-
for (const id of ids) {
|
|
666
|
-
const f = path4.join(data, "brain", id, ".system_generated", "logs", "transcript.jsonl");
|
|
667
|
-
try {
|
|
668
|
-
if (statSync2(f).mtimeMs < sinceMs) continue;
|
|
669
|
-
} catch {
|
|
670
|
-
continue;
|
|
671
|
-
}
|
|
672
|
-
const facts = antigravityFacts(f, { conversation: id, workspaces: [], env });
|
|
673
|
-
if (!facts.started_at || !facts.last_at) continue;
|
|
674
|
-
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
675
|
-
rows2.push({ session: { id, ...rest }, started_at, last_at });
|
|
676
772
|
}
|
|
773
|
+
default:
|
|
774
|
+
return null;
|
|
775
|
+
}
|
|
776
|
+
const hints = {};
|
|
777
|
+
if (event === "UserPromptSubmit" && typeof s.prompt === "string") hints.prompt = s.prompt;
|
|
778
|
+
const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
|
|
779
|
+
if (host && DESKTOP_ID.test(host)) hints.desktop_id = host;
|
|
780
|
+
const attended = input.env.CLAUDE_CODE_SESSION_ATTENDED;
|
|
781
|
+
if (attended === "1" || attended === "0") hints.attended = attended === "1" || !!input.env.SESSIONPIPE_CONTROL_JOB;
|
|
782
|
+
if (event !== "PreToolUse" && event !== "PostToolUse") hints.claude_pid = claudePid(input.env);
|
|
783
|
+
const r = { session, events, hints };
|
|
784
|
+
if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
|
|
785
|
+
return r;
|
|
786
|
+
},
|
|
787
|
+
readTranscript(file, fromLine) {
|
|
788
|
+
let text = "";
|
|
789
|
+
try {
|
|
790
|
+
text = readFileSync2(file, "utf8");
|
|
791
|
+
} catch {
|
|
792
|
+
return { turns: [], line: fromLine };
|
|
793
|
+
}
|
|
794
|
+
return readClaude(text.split("\n"), fromLine);
|
|
795
|
+
},
|
|
796
|
+
facts(session, transcript, hints, state) {
|
|
797
|
+
const out = {};
|
|
798
|
+
if (transcript) Object.assign(out, claudeFacts(transcript, session.id, hints, state));
|
|
799
|
+
const cwd = out.cwd ?? session.cwd;
|
|
800
|
+
Object.assign(out, gitFacts(cwd), out.branch ? { branch: out.branch } : {});
|
|
801
|
+
if (!out.url && typeof hints.desktop_id === "string") out.url = `claude://code/continue/${hints.desktop_id}`;
|
|
802
|
+
if (typeof hints.attended === "boolean") out.attended = hints.attended || !!out.url;
|
|
803
|
+
return out;
|
|
804
|
+
},
|
|
805
|
+
remoteFacts: (session, local, transcript, state, env = process.env) => claudeRemoteFacts(session.id, local, transcript, state, env),
|
|
806
|
+
backfill(sinceMs, env = process.env) {
|
|
807
|
+
const rows2 = [];
|
|
808
|
+
const files = [];
|
|
809
|
+
for (const d of claudeDirs(env))
|
|
810
|
+
files.push(...recentFiles(path4.join(d, "projects"), 1, sinceMs, (n) => /^[0-9a-f-]{36}\.jsonl$/.test(n)));
|
|
811
|
+
const noState = { get: () => ({}), save: () => {
|
|
812
|
+
} };
|
|
813
|
+
for (const f of files) {
|
|
814
|
+
const id = path4.basename(f, ".jsonl");
|
|
815
|
+
const facts = claudeFacts(f, id, {}, noState);
|
|
816
|
+
if (!facts.started_at || !facts.last_at) continue;
|
|
817
|
+
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
818
|
+
const cwd = rest.cwd;
|
|
819
|
+
rows2.push({
|
|
820
|
+
session: { id, ...rest, ...gitFacts(cwd), ...rest.branch ? { branch: rest.branch } : {} },
|
|
821
|
+
started_at,
|
|
822
|
+
last_at,
|
|
823
|
+
transcript: f
|
|
824
|
+
});
|
|
677
825
|
}
|
|
678
826
|
return rows2;
|
|
679
827
|
}
|
|
680
828
|
};
|
|
829
|
+
var LINE_REFRESH_S = 60;
|
|
830
|
+
function statusLine(file, want, cmd) {
|
|
831
|
+
const f = readJsonFile(file);
|
|
832
|
+
if (!f.ok) return null;
|
|
833
|
+
const cur = f.value.statusLine;
|
|
834
|
+
const ours = typeof cur?.command === "string" && OURS.test(cur.command);
|
|
835
|
+
const command = cmd?.([NAME, "StatusLine"]).command;
|
|
836
|
+
if (want && cur && !ours)
|
|
837
|
+
return { file, changed: false, skipped: true, note: "a status line of your own is set; limits are not read" };
|
|
838
|
+
if (want ? ours && cur?.command === command && cur?.refreshInterval === LINE_REFRESH_S : !ours) return null;
|
|
839
|
+
if (want) f.value.statusLine = { type: "command", command, refreshInterval: LINE_REFRESH_S };
|
|
840
|
+
else delete f.value.statusLine;
|
|
841
|
+
writeFileSync2(file, formatJson(f.value, f));
|
|
842
|
+
return { file, changed: true, note: want ? "status line written (account limits)" : "status line removed" };
|
|
843
|
+
}
|
|
844
|
+
var epochIso = (t) => typeof t === "number" && t > 0 && t < 1e11 ? new Date(t * 1e3).toISOString() : void 0;
|
|
845
|
+
var WINDOW_MINUTES = { five_hour: 300, seven_day: 10080 };
|
|
846
|
+
function claudeLimits(rl) {
|
|
847
|
+
if (!rl || typeof rl !== "object" || Array.isArray(rl)) return null;
|
|
848
|
+
const windows = [];
|
|
849
|
+
for (const [id, w] of Object.entries(rl)) {
|
|
850
|
+
if (!/^[a-z0-9_]{1,64}$/.test(id) || typeof w?.used_percentage !== "number" || w.used_percentage < 0) continue;
|
|
851
|
+
windows.push(
|
|
852
|
+
prune({
|
|
853
|
+
id,
|
|
854
|
+
used_percent: w.used_percentage,
|
|
855
|
+
window_minutes: WINDOW_MINUTES[id],
|
|
856
|
+
resets_at: epochIso(w.resets_at)
|
|
857
|
+
})
|
|
858
|
+
);
|
|
859
|
+
}
|
|
860
|
+
return windows.length ? { windows: windows.slice(0, 8) } : null;
|
|
861
|
+
}
|
|
681
862
|
function prune(o) {
|
|
682
863
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
683
864
|
return o;
|
|
684
865
|
}
|
|
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);
|
|
866
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
867
|
+
function notClaudeCode(id, s) {
|
|
868
|
+
return typeof s.transcript_path !== "string" && !UUID.test(id);
|
|
698
869
|
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
870
|
+
function pickSource(v) {
|
|
871
|
+
return typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
|
|
872
|
+
}
|
|
873
|
+
var harnessWord = (v) => typeof v === "string" && /^[A-Za-z0-9][A-Za-z0-9_.-]{0,39}$/.test(v) ? v : void 0;
|
|
874
|
+
var WRAPPER = /^<(?:task-notification|cross-session-message|system-reminder|local-command-stdout|command-[a-z-]+)[\s/>]/i;
|
|
875
|
+
var injectedBlock = (prompt) => WRAPPER.test(prompt.trim());
|
|
876
|
+
function errText(v) {
|
|
877
|
+
if (typeof v === "string") return v.slice(0, 2e3);
|
|
878
|
+
if (v && typeof v === "object") {
|
|
879
|
+
const e = v;
|
|
880
|
+
if (typeof e.error === "string") return e.error.slice(0, 2e3);
|
|
881
|
+
if (typeof e.message === "string") return e.message.slice(0, 2e3);
|
|
706
882
|
}
|
|
707
|
-
return
|
|
883
|
+
return void 0;
|
|
708
884
|
}
|
|
709
|
-
function
|
|
710
|
-
|
|
885
|
+
function describe(input) {
|
|
886
|
+
if (!input || typeof input !== "object") return "";
|
|
887
|
+
const i = input;
|
|
888
|
+
const v = i.command ?? i.file_path ?? i.path ?? i.url ?? i.pattern;
|
|
889
|
+
return typeof v === "string" ? ` ${v.slice(0, 120)}` : "";
|
|
890
|
+
}
|
|
891
|
+
var NAME_RANK = {
|
|
892
|
+
user: "custom",
|
|
893
|
+
auto: "harness",
|
|
894
|
+
hook: "harness",
|
|
895
|
+
peer: "harness"
|
|
896
|
+
};
|
|
897
|
+
function registryEntry(j) {
|
|
898
|
+
if (!j || typeof j !== "object") return null;
|
|
899
|
+
const r = j;
|
|
900
|
+
const source = r.nameSource ? NAME_RANK[r.nameSource] : void 0;
|
|
901
|
+
return {
|
|
902
|
+
sessionId: r.sessionId ? String(r.sessionId) : null,
|
|
903
|
+
name: source && typeof r.name === "string" && r.name.trim() ? r.name.trim() : null,
|
|
904
|
+
name_source: source ?? null,
|
|
905
|
+
bridge: typeof r.bridgeSessionId === "string" ? r.bridgeSessionId : null
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
var liveCache = /* @__PURE__ */ new Map();
|
|
909
|
+
function claudeLive(dir) {
|
|
910
|
+
const hit = liveCache.get(dir);
|
|
911
|
+
if (hit) return hit;
|
|
912
|
+
const map = /* @__PURE__ */ new Map();
|
|
711
913
|
let names = [];
|
|
712
914
|
try {
|
|
713
|
-
names = readdirSync2(dir).filter((n) => n.endsWith(".json"));
|
|
915
|
+
names = readdirSync2(path4.join(dir, "sessions")).filter((n) => n.endsWith(".json"));
|
|
714
916
|
} catch {
|
|
715
|
-
return out;
|
|
716
917
|
}
|
|
717
918
|
for (const n of names) {
|
|
718
|
-
const id = n.slice(0, -5);
|
|
719
|
-
if (!ID_RE.test(id)) continue;
|
|
720
|
-
let j;
|
|
721
919
|
try {
|
|
722
|
-
|
|
920
|
+
const e = registryEntry(JSON.parse(readFileSync2(path4.join(dir, "sessions", n), "utf8")));
|
|
921
|
+
if (e?.sessionId) map.set(e.sessionId, e);
|
|
723
922
|
} catch {
|
|
724
|
-
continue;
|
|
725
923
|
}
|
|
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
924
|
}
|
|
733
|
-
|
|
925
|
+
liveCache.set(dir, map);
|
|
926
|
+
return map;
|
|
734
927
|
}
|
|
735
|
-
function
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
928
|
+
function claudeLiveByPid(dir, pid) {
|
|
929
|
+
if (!pid) return null;
|
|
930
|
+
try {
|
|
931
|
+
return registryEntry(JSON.parse(readFileSync2(path4.join(dir, "sessions", `${pid}.json`), "utf8")));
|
|
932
|
+
} catch {
|
|
933
|
+
return null;
|
|
740
934
|
}
|
|
741
|
-
let best = null;
|
|
742
|
-
let bestLen = -1;
|
|
743
|
-
for (const pr of projects)
|
|
744
|
-
for (const f of pr.folders) {
|
|
745
|
-
const within = ws.some((w) => w === f || w.startsWith(f.endsWith(path4.sep) ? f : f + path4.sep));
|
|
746
|
-
if (within && f.length > bestLen) {
|
|
747
|
-
best = pr.id;
|
|
748
|
-
bestLen = f.length;
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
return best;
|
|
752
935
|
}
|
|
753
|
-
function
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
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;
|
|
767
|
-
} catch {
|
|
936
|
+
function claudePid(env = process.env) {
|
|
937
|
+
try {
|
|
938
|
+
const dirs = claudeDirs(env);
|
|
939
|
+
const has = (pid2) => dirs.some((d) => existsSync2(path4.join(d, "sessions", `${pid2}.json`)));
|
|
940
|
+
let pid = process.ppid;
|
|
941
|
+
if (has(pid)) return pid;
|
|
942
|
+
const parent = parentOf();
|
|
943
|
+
for (let hop = 0; hop < 6 && pid > 1; hop++) {
|
|
944
|
+
pid = parent(pid);
|
|
945
|
+
if (pid > 1 && has(pid)) return pid;
|
|
768
946
|
}
|
|
947
|
+
} catch {
|
|
769
948
|
}
|
|
770
949
|
return null;
|
|
771
950
|
}
|
|
772
|
-
function
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
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;
|
|
951
|
+
function parentOf() {
|
|
952
|
+
if (process.platform === "linux") {
|
|
953
|
+
return (pid) => {
|
|
954
|
+
try {
|
|
955
|
+
const st = readFileSync2(`/proc/${pid}/stat`, "utf8");
|
|
956
|
+
return Number(st.slice(st.lastIndexOf(")") + 2).split(" ")[1]) || 0;
|
|
957
|
+
} catch {
|
|
958
|
+
return 0;
|
|
959
|
+
}
|
|
960
|
+
};
|
|
788
961
|
}
|
|
789
|
-
const
|
|
790
|
-
|
|
791
|
-
out
|
|
792
|
-
|
|
962
|
+
const table = /* @__PURE__ */ new Map();
|
|
963
|
+
try {
|
|
964
|
+
const out = execFileSync2("ps", ["-axo", "pid=,ppid="], {
|
|
965
|
+
encoding: "utf8",
|
|
966
|
+
timeout: 2e3,
|
|
967
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
968
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
969
|
+
});
|
|
970
|
+
for (const l of out.split("\n")) {
|
|
971
|
+
const m = /^\s*(\d+)\s+(\d+)/.exec(l);
|
|
972
|
+
if (m) table.set(Number(m[1]), Number(m[2]));
|
|
973
|
+
}
|
|
974
|
+
} catch {
|
|
793
975
|
}
|
|
794
|
-
return
|
|
976
|
+
return (pid) => table.get(pid) || 0;
|
|
795
977
|
}
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
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) => {
|
|
978
|
+
var EMAIL_RE = /^[^@\s]{1,64}@[^@\s]{1,189}$/;
|
|
979
|
+
function claudeLogin(dir, home = homeDir()) {
|
|
980
|
+
const files = [path4.join(dir, ".claude.json")];
|
|
981
|
+
if (path4.resolve(dir) === path4.join(home, ".claude")) files.unshift(path4.join(home, ".claude.json"));
|
|
982
|
+
for (const f of files) {
|
|
811
983
|
try {
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
}
|
|
984
|
+
const a = JSON.parse(readFileSync2(f, "utf8")).oauthAccount;
|
|
985
|
+
if (typeof a?.accountUuid === "string" && a.accountUuid) {
|
|
986
|
+
const email = typeof a.emailAddress === "string" && EMAIL_RE.test(a.emailAddress) ? a.emailAddress : void 0;
|
|
987
|
+
return { id: a.accountUuid.slice(0, 128), ...email ? { email } : {} };
|
|
988
|
+
}
|
|
817
989
|
} catch {
|
|
818
|
-
return "";
|
|
819
990
|
}
|
|
820
|
-
}
|
|
821
|
-
|
|
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$/, "");
|
|
991
|
+
}
|
|
992
|
+
return apiAccount(dir);
|
|
833
993
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
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);
|
|
994
|
+
var ANTHROPIC_HOST = /(^|\.)anthropic\.com$/i;
|
|
995
|
+
function apiAccount(dir) {
|
|
996
|
+
const base = readJson(settingsIn(dir)).env?.ANTHROPIC_BASE_URL;
|
|
997
|
+
if (typeof base !== "string") return null;
|
|
998
|
+
let u;
|
|
872
999
|
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
|
-
}
|
|
1000
|
+
u = new URL(base.trim());
|
|
878
1001
|
} catch {
|
|
1002
|
+
return null;
|
|
879
1003
|
}
|
|
880
|
-
|
|
1004
|
+
if (u.protocol !== "https:" && u.protocol !== "http:" || !u.hostname || ANTHROPIC_HOST.test(u.hostname))
|
|
1005
|
+
return null;
|
|
1006
|
+
const origin = `${u.protocol}//${u.host}`;
|
|
1007
|
+
return { id: `api-${createHash2("sha256").update(origin).digest("hex").slice(0, 16)}`, name: u.host };
|
|
881
1008
|
}
|
|
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
|
-
});
|
|
1009
|
+
var accountCache = /* @__PURE__ */ new Map();
|
|
1010
|
+
function claudeAccount(dir) {
|
|
1011
|
+
const hit = accountCache.get(dir);
|
|
1012
|
+
if (hit !== void 0) return hit;
|
|
1013
|
+
const acct = claudeLogin(dir)?.id ?? null;
|
|
1014
|
+
accountCache.set(dir, acct);
|
|
1015
|
+
return acct;
|
|
1016
|
+
}
|
|
1017
|
+
var LINK_RE = /^https:\/\/claude\.ai\/code\/session_[A-Za-z0-9]+$/;
|
|
1018
|
+
function transcriptLink(records) {
|
|
1019
|
+
for (let i = records.length - 1; i >= 0; i--) {
|
|
1020
|
+
const j = records[i];
|
|
1021
|
+
if (j.type === "system" && j.subtype === "bridge_status" && typeof j.url === "string" && LINK_RE.test(j.url))
|
|
1022
|
+
return j.url;
|
|
1023
|
+
if (j.type === "bridge-session" && typeof j.bridgeSessionId === "string") {
|
|
1024
|
+
const m = /^(?:cse|session)_([A-Za-z0-9]+)$/.exec(j.bridgeSessionId);
|
|
1025
|
+
if (m) return `https://claude.ai/code/session_${m[1]}`;
|
|
1095
1026
|
}
|
|
1096
|
-
return rows2;
|
|
1097
1027
|
}
|
|
1098
|
-
|
|
1099
|
-
function prune2(o) {
|
|
1100
|
-
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
1101
|
-
return o;
|
|
1028
|
+
return null;
|
|
1102
1029
|
}
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
return
|
|
1030
|
+
function appTitle(text) {
|
|
1031
|
+
const t = String(text).replace(/\s+/g, " ").trim();
|
|
1032
|
+
return t.length > 50 ? `${t.slice(0, 50)}...` : t;
|
|
1106
1033
|
}
|
|
1107
|
-
function
|
|
1108
|
-
|
|
1034
|
+
function askText(j) {
|
|
1035
|
+
if (!j || j.type !== "user" || j.isMeta || j.isSidechain) return null;
|
|
1036
|
+
const origin = j.origin;
|
|
1037
|
+
if (origin?.kind && origin.kind !== "human") return null;
|
|
1038
|
+
const c = j.message?.content;
|
|
1039
|
+
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();
|
|
1040
|
+
if (!text || isInjected(text)) return null;
|
|
1041
|
+
return text;
|
|
1109
1042
|
}
|
|
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);
|
|
1043
|
+
function firstAsk(records) {
|
|
1044
|
+
for (const j of records) {
|
|
1045
|
+
const t = askText(j);
|
|
1046
|
+
if (t) return appTitle(t);
|
|
1119
1047
|
}
|
|
1120
|
-
return
|
|
1121
|
-
}
|
|
1122
|
-
function describe(input) {
|
|
1123
|
-
if (!input || typeof input !== "object") return "";
|
|
1124
|
-
const i = input;
|
|
1125
|
-
const v = i.command ?? i.file_path ?? i.path ?? i.url ?? i.pattern;
|
|
1126
|
-
return typeof v === "string" ? ` ${v.slice(0, 120)}` : "";
|
|
1048
|
+
return null;
|
|
1127
1049
|
}
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1050
|
+
function scanTitles(file, from, found) {
|
|
1051
|
+
const out = { custom: found.custom ?? null, ai: found.ai ?? null, upto: from };
|
|
1052
|
+
let size = 0;
|
|
1053
|
+
try {
|
|
1054
|
+
size = statSync2(file).size;
|
|
1055
|
+
} catch {
|
|
1056
|
+
return out;
|
|
1057
|
+
}
|
|
1058
|
+
if (size < from) {
|
|
1059
|
+
out.upto = 0;
|
|
1060
|
+
out.custom = out.ai = null;
|
|
1061
|
+
from = 0;
|
|
1062
|
+
}
|
|
1063
|
+
let pos = 0;
|
|
1064
|
+
scanLines(
|
|
1065
|
+
file,
|
|
1066
|
+
(l) => {
|
|
1067
|
+
pos += Buffer.byteLength(l, "utf8") + 1;
|
|
1068
|
+
if (pos <= from || l.indexOf('-title"') < 0) return null;
|
|
1069
|
+
try {
|
|
1070
|
+
const j = JSON.parse(l);
|
|
1071
|
+
if (j.type === "custom-title" && j.customTitle) out.custom = j.customTitle;
|
|
1072
|
+
if (j.type === "ai-title" && j.aiTitle) out.ai = j.aiTitle;
|
|
1073
|
+
} catch {
|
|
1074
|
+
}
|
|
1075
|
+
return null;
|
|
1076
|
+
},
|
|
1077
|
+
512 * 1024 * 1024
|
|
1078
|
+
);
|
|
1079
|
+
out.upto = size;
|
|
1080
|
+
return out;
|
|
1144
1081
|
}
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
const
|
|
1148
|
-
|
|
1149
|
-
const
|
|
1150
|
-
|
|
1082
|
+
function claudeFacts(file, sessionId, hints, state) {
|
|
1083
|
+
const { head, tail } = ends(file, 64 * 1024, 256 * 1024);
|
|
1084
|
+
const H = parseLines(head);
|
|
1085
|
+
const T = parseLines(tail);
|
|
1086
|
+
const out = {};
|
|
1087
|
+
const id = sessionId || path4.basename(file, ".jsonl");
|
|
1088
|
+
const mem = state.get(NAME, id);
|
|
1089
|
+
let custom = null;
|
|
1090
|
+
let ai = null;
|
|
1091
|
+
for (const j of H.concat(T)) {
|
|
1092
|
+
if (j.type === "custom-title" && typeof j.customTitle === "string") custom = j.customTitle;
|
|
1093
|
+
if (j.type === "ai-title" && typeof j.aiTitle === "string") ai = j.aiTitle;
|
|
1094
|
+
}
|
|
1095
|
+
let size = 0;
|
|
1151
1096
|
try {
|
|
1152
|
-
|
|
1097
|
+
size = statSync2(file).size;
|
|
1153
1098
|
} catch {
|
|
1154
1099
|
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
}
|
|
1100
|
+
if (!custom && !ai && size > 64 * 1024 + 256 * 1024) {
|
|
1101
|
+
const s = scanTitles(file, Number(mem.scan_upto) || 0, {
|
|
1102
|
+
custom: mem.scan_custom,
|
|
1103
|
+
ai: mem.scan_ai
|
|
1104
|
+
});
|
|
1105
|
+
custom = s.custom;
|
|
1106
|
+
ai = s.ai;
|
|
1107
|
+
state.save(NAME, id, { scan_upto: s.upto, scan_custom: s.custom, scan_ai: s.ai });
|
|
1108
|
+
}
|
|
1109
|
+
const dir = configDirOf(file);
|
|
1110
|
+
const live = claudeLive(dir).get(id) ?? claudeLiveByPid(dir, hints.claude_pid);
|
|
1111
|
+
if (live?.name) state.save(NAME, id, { name: live.name, name_source: live.name_source });
|
|
1112
|
+
const bridge = live?.bridge && /^session_[A-Za-z0-9]+$/.test(live.bridge) ? `https://claude.ai/code/${live.bridge}` : null;
|
|
1113
|
+
const url = bridge ?? transcriptLink(T) ?? transcriptLink(H) ?? (typeof mem.url === "string" ? mem.url : null);
|
|
1114
|
+
if (url) {
|
|
1115
|
+
out.url = url;
|
|
1116
|
+
state.save(NAME, id, { url });
|
|
1117
|
+
}
|
|
1118
|
+
const name = live?.name ? { title: live.name, source: live.name_source } : typeof mem.name === "string" ? { title: mem.name, source: mem.name_source } : null;
|
|
1119
|
+
const pick = name && name.source === "custom" && name || custom && { title: custom, source: "custom" } || name || ai && { title: ai, source: "harness" };
|
|
1120
|
+
let ask2 = typeof mem.first_ask === "string" ? mem.first_ask : firstAsk(H);
|
|
1121
|
+
if (!ask2 && size > 64 * 1024 && !mem.first_ask_none) {
|
|
1122
|
+
ask2 = scanLines(file, (l) => {
|
|
1123
|
+
try {
|
|
1124
|
+
const t = askText(JSON.parse(l));
|
|
1125
|
+
return t ? appTitle(t) : null;
|
|
1126
|
+
} catch {
|
|
1127
|
+
return null;
|
|
1128
|
+
}
|
|
1129
|
+
});
|
|
1130
|
+
if (!ask2 && size > 16 * 1024 * 1024) state.save(NAME, id, { first_ask_none: true });
|
|
1131
|
+
}
|
|
1132
|
+
if (ask2) {
|
|
1133
|
+
out.first_ask = ask2;
|
|
1134
|
+
if (!mem.first_ask) state.save(NAME, id, { first_ask: ask2 });
|
|
1135
|
+
}
|
|
1136
|
+
if (pick) {
|
|
1137
|
+
out.title = String(pick.title).slice(0, 120);
|
|
1138
|
+
out.title_source = pick.source;
|
|
1139
|
+
} else if (ask2) {
|
|
1140
|
+
out.title = ask2;
|
|
1141
|
+
out.title_source = "first-ask";
|
|
1142
|
+
}
|
|
1143
|
+
const acct = claudeAccount(dir);
|
|
1144
|
+
if (acct) out.account_id = acct;
|
|
1145
|
+
for (let i = T.length - 1; i >= 0; i--) {
|
|
1146
|
+
const m = T[i].message?.model;
|
|
1147
|
+
if (T[i]?.type === "assistant" && m && m !== "<synthetic>") {
|
|
1148
|
+
out.model = m;
|
|
1149
|
+
break;
|
|
1160
1150
|
}
|
|
1161
1151
|
}
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
return registryEntry(JSON.parse(readFileSync3(path5.join(dir, "sessions", `${pid}.json`), "utf8")));
|
|
1169
|
-
} catch {
|
|
1170
|
-
return null;
|
|
1152
|
+
const meta = [...T].reverse().find((j) => j.version) ?? H.find((j) => j.version);
|
|
1153
|
+
if (meta?.version) out.harness_version = String(meta.version);
|
|
1154
|
+
const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
|
|
1155
|
+
if (times.length) {
|
|
1156
|
+
out.started_at = Math.min(...times);
|
|
1157
|
+
out.last_at = Math.max(...times);
|
|
1171
1158
|
}
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
const has = (pid2) => dirs.some((d) => existsSync3(path5.join(d, "sessions", `${pid2}.json`)));
|
|
1177
|
-
let pid = process.ppid;
|
|
1178
|
-
if (has(pid)) return pid;
|
|
1179
|
-
const parent = parentOf();
|
|
1180
|
-
for (let hop = 0; hop < 6 && pid > 1; hop++) {
|
|
1181
|
-
pid = parent(pid);
|
|
1182
|
-
if (pid > 1 && has(pid)) return pid;
|
|
1183
|
-
}
|
|
1184
|
-
} catch {
|
|
1159
|
+
const withCwd = H.find((j) => typeof j.cwd === "string");
|
|
1160
|
+
if (withCwd) {
|
|
1161
|
+
out.cwd = withCwd.cwd;
|
|
1162
|
+
if (typeof withCwd.gitBranch === "string" && withCwd.gitBranch !== "HEAD") out.branch = withCwd.gitBranch;
|
|
1185
1163
|
}
|
|
1186
|
-
return
|
|
1164
|
+
return out;
|
|
1187
1165
|
}
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1166
|
+
var REMOTE_RETRY_MS = 6e4;
|
|
1167
|
+
var REMOTE_REFRESH_MS = 30 * 6e4;
|
|
1168
|
+
var CLAUDE_API = "https://api.anthropic.com";
|
|
1169
|
+
var KEYCHAIN = "Claude Code-credentials";
|
|
1170
|
+
var REMOTE_TIMEOUT_MS = 3e3;
|
|
1171
|
+
var KEYCHAIN_TIMEOUT_MS = 2e3;
|
|
1172
|
+
var REMOTE_MAX_BYTES = 1 << 20;
|
|
1173
|
+
var cleanName = (s) => s.replace(/[\p{Cc}\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]/gu, " ").replace(/\s+/g, " ").trim();
|
|
1174
|
+
async function claudeRemoteFacts(id, local, transcript, state, env, now = Date.now()) {
|
|
1175
|
+
if (!transcript || !local.url || !LINK_RE.test(local.url) || local.title_source === "custom") return {};
|
|
1176
|
+
const mem = state.get(NAME, id);
|
|
1177
|
+
let title = typeof mem.remote_title === "string" ? mem.remote_title : null;
|
|
1178
|
+
if (now - (Number(mem.remote_at) || 0) >= (title ? REMOTE_REFRESH_MS : REMOTE_RETRY_MS)) {
|
|
1179
|
+
state.save(NAME, id, { remote_at: now });
|
|
1180
|
+
const token = claudeAiToken(configDirOf(transcript), env, now);
|
|
1181
|
+
const got = token ? await claudeAiTitle(local.url.slice(local.url.lastIndexOf("/") + 1), token) : void 0;
|
|
1182
|
+
if (got !== void 0) {
|
|
1183
|
+
title = got?.slice(0, 200) ?? null;
|
|
1184
|
+
state.save(NAME, id, { remote_title: title });
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
return title ? { title: title.slice(0, 120), title_source: "harness" } : {};
|
|
1188
|
+
}
|
|
1189
|
+
function keychainServices(dir, env) {
|
|
1190
|
+
const named = (p) => `${KEYCHAIN}-${createHash2("sha256").update(p.normalize("NFC")).digest("hex").slice(0, 8)}`;
|
|
1191
|
+
const out = /* @__PURE__ */ new Set();
|
|
1192
|
+
if (env.CLAUDE_CONFIG_DIR && path4.resolve(env.CLAUDE_CONFIG_DIR) === dir) out.add(named(env.CLAUDE_CONFIG_DIR));
|
|
1193
|
+
if (dir === path4.join(env.HOME || homeDir(), ".claude")) out.add(KEYCHAIN);
|
|
1194
|
+
out.add(named(dir));
|
|
1195
|
+
return [...out];
|
|
1196
|
+
}
|
|
1197
|
+
var tokenCache = /* @__PURE__ */ new Map();
|
|
1198
|
+
function claudeAiToken(dir, env, now) {
|
|
1199
|
+
const hit = tokenCache.get(dir);
|
|
1200
|
+
if (hit && hit.expires > now + 6e4) return hit.token;
|
|
1201
|
+
const raw = [];
|
|
1202
|
+
if (process.platform === "darwin")
|
|
1203
|
+
for (const service of keychainServices(dir, env)) {
|
|
1191
1204
|
try {
|
|
1192
|
-
const
|
|
1193
|
-
|
|
1205
|
+
const user = env.USER || userInfo().username;
|
|
1206
|
+
const account = /^[\w.-]+$/.test(user) ? user : "claude-code-user";
|
|
1207
|
+
raw.push(
|
|
1208
|
+
execFileSync2("security", ["find-generic-password", "-a", account, "-w", "-s", service], {
|
|
1209
|
+
encoding: "utf8",
|
|
1210
|
+
timeout: KEYCHAIN_TIMEOUT_MS,
|
|
1211
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1212
|
+
})
|
|
1213
|
+
);
|
|
1214
|
+
break;
|
|
1194
1215
|
} catch {
|
|
1195
|
-
return 0;
|
|
1196
1216
|
}
|
|
1197
|
-
};
|
|
1198
|
-
}
|
|
1199
|
-
const table = /* @__PURE__ */ new Map();
|
|
1200
|
-
try {
|
|
1201
|
-
const out = execFileSync2("ps", ["-axo", "pid=,ppid="], {
|
|
1202
|
-
encoding: "utf8",
|
|
1203
|
-
timeout: 2e3,
|
|
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
1217
|
}
|
|
1218
|
+
try {
|
|
1219
|
+
raw.push(readFileSync2(path4.join(dir, ".credentials.json"), "utf8"));
|
|
1211
1220
|
} catch {
|
|
1212
1221
|
}
|
|
1213
|
-
|
|
1214
|
-
}
|
|
1215
|
-
var EMAIL_RE = /^[^@\s]{1,64}@[^@\s]{1,189}$/;
|
|
1216
|
-
function claudeLogin(dir, home = homeDir()) {
|
|
1217
|
-
const files = [path5.join(dir, ".claude.json")];
|
|
1218
|
-
if (path5.resolve(dir) === path5.join(home, ".claude")) files.unshift(path5.join(home, ".claude.json"));
|
|
1219
|
-
for (const f of files) {
|
|
1222
|
+
for (const r of raw) {
|
|
1220
1223
|
try {
|
|
1221
|
-
const
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1224
|
+
const t = r.trim();
|
|
1225
|
+
const o = JSON.parse(/^(?:[0-9a-f]{2})+$/i.test(t) ? Buffer.from(t, "hex").toString("utf8") : t).claudeAiOauth;
|
|
1226
|
+
if (typeof o?.accessToken !== "string" || !o.accessToken) continue;
|
|
1227
|
+
const expires = typeof o.expiresAt === "number" ? o.expiresAt : Number.POSITIVE_INFINITY;
|
|
1228
|
+
if (expires <= now + 6e4) continue;
|
|
1229
|
+
tokenCache.set(dir, { token: o.accessToken, expires });
|
|
1230
|
+
return o.accessToken;
|
|
1226
1231
|
} catch {
|
|
1227
1232
|
}
|
|
1228
1233
|
}
|
|
1229
|
-
return
|
|
1234
|
+
return null;
|
|
1230
1235
|
}
|
|
1231
|
-
|
|
1232
|
-
function apiAccount(dir) {
|
|
1233
|
-
let base;
|
|
1236
|
+
async function claudeAiTitle(bridge, token) {
|
|
1234
1237
|
try {
|
|
1235
|
-
|
|
1238
|
+
const r = await fetch(`${CLAUDE_API}/v1/code/sessions/${bridge}`, {
|
|
1239
|
+
headers: { Authorization: `Bearer ${token}`, "anthropic-version": "2023-06-01", "User-Agent": "sessionpipe" },
|
|
1240
|
+
redirect: "error",
|
|
1241
|
+
signal: AbortSignal.timeout(REMOTE_TIMEOUT_MS)
|
|
1242
|
+
});
|
|
1243
|
+
if (r.status !== 200 || Number(r.headers.get("content-length")) > REMOTE_MAX_BYTES) return void 0;
|
|
1244
|
+
const text = await r.text();
|
|
1245
|
+
if (text.length > REMOTE_MAX_BYTES) return void 0;
|
|
1246
|
+
const j = JSON.parse(text);
|
|
1247
|
+
const s = j?.response_shape ?? j?.session ?? j;
|
|
1248
|
+
const bare = (x) => x.replace(/^(?:session|cse)_/, "");
|
|
1249
|
+
if (typeof s?.id !== "string" || bare(s.id) !== bare(bridge)) return void 0;
|
|
1250
|
+
return typeof s.title === "string" && cleanName(s.title) || null;
|
|
1236
1251
|
} catch {
|
|
1237
|
-
return
|
|
1252
|
+
return void 0;
|
|
1238
1253
|
}
|
|
1239
|
-
|
|
1240
|
-
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
// ../core/src/adapters/headless.ts
|
|
1257
|
+
import { accessSync, constants, statSync as statSync3 } from "node:fs";
|
|
1258
|
+
import path5 from "node:path";
|
|
1259
|
+
var JOB_TIMEOUT_MS = 30 * 6e4;
|
|
1260
|
+
var OUT_CAP = 1024 * 1024;
|
|
1261
|
+
function isExe(p) {
|
|
1241
1262
|
try {
|
|
1242
|
-
|
|
1263
|
+
if (!statSync3(p).isFile()) return false;
|
|
1264
|
+
accessSync(p, constants.X_OK);
|
|
1265
|
+
return true;
|
|
1243
1266
|
} catch {
|
|
1244
|
-
return
|
|
1267
|
+
return false;
|
|
1245
1268
|
}
|
|
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
1269
|
}
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
const
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
return
|
|
1270
|
+
function findBinary(o) {
|
|
1271
|
+
if (o.override && isExe(o.override)) return o.override;
|
|
1272
|
+
for (const d of String(o.env?.PATH || "").split(path5.delimiter)) {
|
|
1273
|
+
if (!d || !path5.isAbsolute(d)) continue;
|
|
1274
|
+
for (const n of o.names) if (isExe(path5.join(d, n))) return path5.join(d, n);
|
|
1275
|
+
}
|
|
1276
|
+
for (const d of o.dirs) for (const n of o.names) if (isExe(path5.join(d, n))) return path5.join(d, n);
|
|
1277
|
+
return null;
|
|
1258
1278
|
}
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1279
|
+
|
|
1280
|
+
// ../core/src/adapters/antigravity.ts
|
|
1281
|
+
import {
|
|
1282
|
+
existsSync as existsSync3,
|
|
1283
|
+
mkdirSync as mkdirSync2,
|
|
1284
|
+
readdirSync as readdirSync3,
|
|
1285
|
+
readFileSync as readFileSync3,
|
|
1286
|
+
realpathSync,
|
|
1287
|
+
statSync as statSync4,
|
|
1288
|
+
unlinkSync as unlinkSync2,
|
|
1289
|
+
writeFileSync as writeFileSync3
|
|
1290
|
+
} from "node:fs";
|
|
1291
|
+
import path6 from "node:path";
|
|
1292
|
+
var NAME2 = "antigravity";
|
|
1293
|
+
var HOOK_NAME = "sessionpipe";
|
|
1294
|
+
var AG_EVENTS = ["PreInvocation", "PostInvocation", "PostToolUse", "Stop"];
|
|
1295
|
+
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';
|
|
1296
|
+
var antigravityAnswer = (event) => event === "PreToolUse" ? AG_STALE_PRETOOL : "{}\n";
|
|
1297
|
+
var gemini = (env) => env.GEMINI_CLI_HOME || path6.join(homeDir(), ".gemini");
|
|
1298
|
+
var hooksFile = (env) => path6.join(gemini(env), "config", "hooks.json");
|
|
1299
|
+
var projectsDir = (env) => path6.join(gemini(env), "config", "projects");
|
|
1300
|
+
var dataDirs = (env) => ["antigravity", "antigravity-cli", "antigravity-ide"].map((d) => path6.join(gemini(env), d));
|
|
1301
|
+
var ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
|
|
1302
|
+
var validTool2 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
1303
|
+
function entry(cmd) {
|
|
1304
|
+
const handler = (e) => ({ type: "command", command: cmd([NAME2, e]).command, timeout: 5 });
|
|
1305
|
+
const out = { enabled: true };
|
|
1306
|
+
for (const e of AG_EVENTS) out[e] = /Tool/.test(e) ? [{ matcher: "*", hooks: [handler(e)] }] : [handler(e)];
|
|
1307
|
+
return out;
|
|
1308
|
+
}
|
|
1309
|
+
var antigravity = {
|
|
1310
|
+
name: NAME2,
|
|
1311
|
+
events: AG_EVENTS,
|
|
1312
|
+
detect: (env = process.env) => existsSync3(path6.join(gemini(env), "config")) || dataDirs(env).some((d) => existsSync3(d)),
|
|
1313
|
+
configFiles: (env = process.env) => [hooksFile(env)],
|
|
1314
|
+
install(cmd, env = process.env) {
|
|
1315
|
+
const file = hooksFile(env);
|
|
1316
|
+
const f = readJsonFile(file);
|
|
1317
|
+
if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
|
|
1318
|
+
const hooks = f.value;
|
|
1319
|
+
const e = entry(cmd);
|
|
1320
|
+
if (JSON.stringify(hooks[HOOK_NAME]) === JSON.stringify(e)) return [{ file, changed: false }];
|
|
1321
|
+
hooks[HOOK_NAME] = e;
|
|
1322
|
+
mkdirSync2(path6.dirname(file), { recursive: true });
|
|
1323
|
+
writeFileSync3(file, formatJson(hooks, f));
|
|
1324
|
+
return [
|
|
1325
|
+
{
|
|
1326
|
+
file,
|
|
1327
|
+
changed: true,
|
|
1328
|
+
created: !f.existed,
|
|
1329
|
+
note: "hooks.json is read when a conversation starts; open ones report after a restart"
|
|
1330
|
+
}
|
|
1331
|
+
];
|
|
1332
|
+
},
|
|
1333
|
+
uninstall(env = process.env, opts = {}) {
|
|
1334
|
+
const file = hooksFile(env);
|
|
1335
|
+
if (!existsSync3(file)) return [{ file, changed: false }];
|
|
1336
|
+
const f = readJsonFile(file);
|
|
1337
|
+
if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
|
|
1338
|
+
const hooks = f.value;
|
|
1339
|
+
if (!(HOOK_NAME in hooks)) return [{ file, changed: false }];
|
|
1340
|
+
delete hooks[HOOK_NAME];
|
|
1341
|
+
if (opts.created?.includes(file) && !Object.keys(hooks).length) {
|
|
1342
|
+
unlinkSync2(file);
|
|
1343
|
+
return [{ file, changed: true, note: "removed (sessionpipe created it)" }];
|
|
1344
|
+
}
|
|
1345
|
+
writeFileSync3(file, formatJson(hooks, f));
|
|
1346
|
+
return [{ file, changed: true }];
|
|
1347
|
+
},
|
|
1348
|
+
installed(cmd, env = process.env) {
|
|
1349
|
+
const file = hooksFile(env);
|
|
1350
|
+
const f = readJsonFile(file);
|
|
1351
|
+
const have = f.ok ? f.value[HOOK_NAME] : void 0;
|
|
1352
|
+
return [
|
|
1353
|
+
{
|
|
1354
|
+
file,
|
|
1355
|
+
state: !have ? "missing" : JSON.stringify(have) === JSON.stringify(entry(cmd)) ? "current" : "stale"
|
|
1356
|
+
}
|
|
1357
|
+
];
|
|
1358
|
+
},
|
|
1359
|
+
fromHook(input) {
|
|
1360
|
+
const event = input.argv[1] ?? "";
|
|
1361
|
+
let s = {};
|
|
1362
|
+
try {
|
|
1363
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
1364
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1365
|
+
} catch {
|
|
1366
|
+
}
|
|
1367
|
+
const id = s.conversationId ?? s.session_id ?? s.sessionId;
|
|
1368
|
+
const stdout = antigravityAnswer(event);
|
|
1369
|
+
if (!id) return { session: { id: "" }, events: [], stdout };
|
|
1370
|
+
const workspaces = agWorkspaces(s.workspacePaths);
|
|
1371
|
+
const session = { id: String(id), cwd: workspaces[0] ?? input.cwd };
|
|
1372
|
+
if (typeof s.modelName === "string" && s.modelName !== "auto") session.model = s.modelName;
|
|
1373
|
+
const toolCall = s.toolCall;
|
|
1374
|
+
const tool = validTool2(toolCall?.name) ? toolCall?.name : validTool2(s.toolName) ? s.toolName : void 0;
|
|
1375
|
+
const ev = (type, data) => ({ type, data: prune2(data), harnessEvent: event });
|
|
1376
|
+
const events = [];
|
|
1377
|
+
switch (event) {
|
|
1378
|
+
case "PreInvocation":
|
|
1379
|
+
if (Number(s.invocationNum) === 0 || s.invocationNum === void 0) {
|
|
1380
|
+
events.push(ev("session.started", { source: "unknown" }));
|
|
1381
|
+
events.push(ev("turn.started", {}));
|
|
1382
|
+
} else events.push(ev("session.heartbeat", {}));
|
|
1383
|
+
break;
|
|
1384
|
+
case "PostInvocation":
|
|
1385
|
+
break;
|
|
1386
|
+
case "PreToolUse":
|
|
1387
|
+
if (tool) events.push(ev("tool.started", { tool, input: toolCall?.args }));
|
|
1388
|
+
break;
|
|
1389
|
+
case "PostToolUse":
|
|
1390
|
+
if (!tool) events.push(ev("session.heartbeat", {}));
|
|
1391
|
+
else
|
|
1392
|
+
events.push(
|
|
1393
|
+
ev("tool.ended", {
|
|
1394
|
+
tool,
|
|
1395
|
+
ok: !s.error,
|
|
1396
|
+
error: typeof s.error === "string" ? s.error.slice(0, 2e3) : void 0,
|
|
1397
|
+
input: toolCall?.args
|
|
1398
|
+
})
|
|
1399
|
+
);
|
|
1400
|
+
break;
|
|
1401
|
+
case "Stop":
|
|
1402
|
+
events.push(ev("turn.ended", { reason: s.error ? "error" : "stop" }));
|
|
1403
|
+
break;
|
|
1404
|
+
default:
|
|
1405
|
+
return { session, events: [], stdout };
|
|
1406
|
+
}
|
|
1407
|
+
const r = { session, events, stdout, hints: { workspaces } };
|
|
1408
|
+
if (typeof s.transcriptPath === "string") r.transcript = s.transcriptPath;
|
|
1409
|
+
return r;
|
|
1410
|
+
},
|
|
1411
|
+
readTranscript(file, fromLine) {
|
|
1412
|
+
let text = "";
|
|
1413
|
+
try {
|
|
1414
|
+
text = readFileSync3(file, "utf8");
|
|
1415
|
+
} catch {
|
|
1416
|
+
return { turns: [], line: fromLine };
|
|
1417
|
+
}
|
|
1418
|
+
return readAntigravity(text.split("\n"), fromLine);
|
|
1419
|
+
},
|
|
1420
|
+
facts(session, transcript, hints) {
|
|
1421
|
+
if (!transcript) return {};
|
|
1422
|
+
return antigravityFacts(transcript, {
|
|
1423
|
+
conversation: session.id,
|
|
1424
|
+
workspaces: hints.workspaces ?? (session.cwd ? [session.cwd] : []),
|
|
1425
|
+
env: process.env
|
|
1426
|
+
});
|
|
1427
|
+
},
|
|
1428
|
+
backfill(sinceMs, env = process.env) {
|
|
1429
|
+
const rows2 = [];
|
|
1430
|
+
for (const data of dataDirs(env)) {
|
|
1431
|
+
let ids = [];
|
|
1432
|
+
try {
|
|
1433
|
+
ids = readdirSync3(path6.join(data, "brain")).filter((n) => /^[0-9a-f-]{36}$/i.test(n));
|
|
1434
|
+
} catch {
|
|
1435
|
+
}
|
|
1436
|
+
for (const id of ids) {
|
|
1437
|
+
const f = path6.join(data, "brain", id, ".system_generated", "logs", "transcript.jsonl");
|
|
1438
|
+
try {
|
|
1439
|
+
if (statSync4(f).mtimeMs < sinceMs) continue;
|
|
1440
|
+
} catch {
|
|
1441
|
+
continue;
|
|
1442
|
+
}
|
|
1443
|
+
const facts = antigravityFacts(f, { conversation: id, workspaces: [], env });
|
|
1444
|
+
if (!facts.started_at || !facts.last_at) continue;
|
|
1445
|
+
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
1446
|
+
rows2.push({ session: { id, ...rest }, started_at, last_at });
|
|
1447
|
+
}
|
|
1268
1448
|
}
|
|
1449
|
+
return rows2;
|
|
1269
1450
|
}
|
|
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;
|
|
1451
|
+
};
|
|
1452
|
+
function prune2(o) {
|
|
1453
|
+
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
1454
|
+
return o;
|
|
1284
1455
|
}
|
|
1285
|
-
function
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1456
|
+
function agPath(p) {
|
|
1457
|
+
if (typeof p !== "string" || !p) return null;
|
|
1458
|
+
let s = p;
|
|
1459
|
+
if (/^file:/i.test(s)) {
|
|
1460
|
+
try {
|
|
1461
|
+
s = decodeURIComponent(new URL(s).pathname);
|
|
1462
|
+
} catch {
|
|
1463
|
+
return null;
|
|
1464
|
+
}
|
|
1465
|
+
if (process.platform === "win32" && /^\/[A-Za-z]:/.test(s)) s = s.slice(1);
|
|
1466
|
+
} else if (/^[a-z][a-z0-9+.-]*:\/\//i.test(s)) return null;
|
|
1467
|
+
if (!path6.isAbsolute(s)) return null;
|
|
1468
|
+
return path6.resolve(s);
|
|
1291
1469
|
}
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1470
|
+
var agWorkspaces = (paths) => (Array.isArray(paths) ? paths : []).map(agPath).filter((p) => !!p).filter((p, i, a) => a.indexOf(p) === i);
|
|
1471
|
+
function agVariants(p) {
|
|
1472
|
+
const out = [p];
|
|
1295
1473
|
try {
|
|
1296
|
-
|
|
1474
|
+
const r = realpathSync.native(p);
|
|
1475
|
+
if (r !== p) out.push(r);
|
|
1297
1476
|
} catch {
|
|
1298
|
-
return out;
|
|
1299
|
-
}
|
|
1300
|
-
if (size < from) {
|
|
1301
|
-
out.upto = 0;
|
|
1302
|
-
out.custom = out.ai = null;
|
|
1303
|
-
from = 0;
|
|
1304
1477
|
}
|
|
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
1478
|
return out;
|
|
1323
1479
|
}
|
|
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;
|
|
1480
|
+
function agProjects(dir) {
|
|
1481
|
+
const out = [];
|
|
1482
|
+
let names = [];
|
|
1338
1483
|
try {
|
|
1339
|
-
|
|
1484
|
+
names = readdirSync3(dir).filter((n) => n.endsWith(".json"));
|
|
1340
1485
|
} catch {
|
|
1486
|
+
return out;
|
|
1341
1487
|
}
|
|
1342
|
-
|
|
1343
|
-
const
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1488
|
+
for (const n of names) {
|
|
1489
|
+
const id = n.slice(0, -5);
|
|
1490
|
+
if (!ID_RE.test(id)) continue;
|
|
1491
|
+
let j;
|
|
1492
|
+
try {
|
|
1493
|
+
j = JSON.parse(readFileSync3(path6.join(dir, n), "utf8"));
|
|
1494
|
+
} catch {
|
|
1495
|
+
continue;
|
|
1496
|
+
}
|
|
1497
|
+
const folders = [];
|
|
1498
|
+
for (const r of j?.projectResources?.resources ?? []) {
|
|
1499
|
+
const f = agPath(r?.folderUri ?? r?.gitFolder?.folderUri);
|
|
1500
|
+
if (f) folders.push(...agVariants(f));
|
|
1501
|
+
}
|
|
1502
|
+
if (folders.length) out.push({ id, folders });
|
|
1350
1503
|
}
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
const
|
|
1355
|
-
const
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
state.save(NAME2, id, { url });
|
|
1504
|
+
return out;
|
|
1505
|
+
}
|
|
1506
|
+
function agMatchProject(projects, workspaces) {
|
|
1507
|
+
const ws = [];
|
|
1508
|
+
for (const w of workspaces) {
|
|
1509
|
+
const p = agPath(w);
|
|
1510
|
+
if (p) ws.push(...agVariants(p));
|
|
1359
1511
|
}
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
} catch {
|
|
1369
|
-
return null;
|
|
1512
|
+
let best = null;
|
|
1513
|
+
let bestLen = -1;
|
|
1514
|
+
for (const pr of projects)
|
|
1515
|
+
for (const f of pr.folders) {
|
|
1516
|
+
const within = ws.some((w) => w === f || w.startsWith(f.endsWith(path6.sep) ? f : f + path6.sep));
|
|
1517
|
+
if (within && f.length > bestLen) {
|
|
1518
|
+
best = pr.id;
|
|
1519
|
+
bestLen = f.length;
|
|
1370
1520
|
}
|
|
1371
|
-
}
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1521
|
+
}
|
|
1522
|
+
return best;
|
|
1523
|
+
}
|
|
1524
|
+
function antigravityRemoteUrl(transcript, opts) {
|
|
1525
|
+
const i = transcript.indexOf(`${path6.sep}brain${path6.sep}`);
|
|
1526
|
+
const dirs = (i > 0 ? [transcript.slice(0, i)] : []).concat(dataDirs(opts.env));
|
|
1527
|
+
for (const d of dirs) {
|
|
1528
|
+
try {
|
|
1529
|
+
const m = /installation_uuid:\s*"([0-9a-f-]{36})"/i.exec(
|
|
1530
|
+
readFileSync3(path6.join(d, "antigravity_state.pbtxt"), "utf8")
|
|
1531
|
+
);
|
|
1532
|
+
if (!m) continue;
|
|
1533
|
+
const base = `https://antigravity.google.com/r/${m[1]}-v2`;
|
|
1534
|
+
const conv = opts.conversation ?? "";
|
|
1535
|
+
if (!ID_RE.test(conv) || !opts.workspaces?.length) return base;
|
|
1536
|
+
const project = agMatchProject(agProjects(opts.projectsDir ?? projectsDir(opts.env)), opts.workspaces);
|
|
1537
|
+
return project ? `${base}?p=${encodeURIComponent(`c/${conv}?section=${project}`)}` : base;
|
|
1538
|
+
} catch {
|
|
1539
|
+
}
|
|
1377
1540
|
}
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1541
|
+
return null;
|
|
1542
|
+
}
|
|
1543
|
+
function antigravityFacts(file, opts) {
|
|
1544
|
+
const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
|
|
1545
|
+
const H = parseLines(head);
|
|
1546
|
+
const T = parseLines(tail);
|
|
1547
|
+
const out = {};
|
|
1548
|
+
const url = antigravityRemoteUrl(file, opts);
|
|
1549
|
+
if (url) out.url = url;
|
|
1550
|
+
for (const j of H) {
|
|
1551
|
+
if (j.type !== "USER_INPUT" || typeof j.content !== "string") continue;
|
|
1552
|
+
const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
|
|
1553
|
+
const text = (m ? m[1] : j.content).trim();
|
|
1554
|
+
const line = (text.split("\n").find((l) => l.trim()) ?? "").trim();
|
|
1555
|
+
if (!line || /^<[a-z_-]+>/i.test(line)) continue;
|
|
1556
|
+
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
1383
1557
|
out.title_source = "first-ask";
|
|
1558
|
+
break;
|
|
1384
1559
|
}
|
|
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);
|
|
1560
|
+
const times = H.concat(T).map((j) => Date.parse(String(j.created_at ?? j.timestamp ?? ""))).filter((n) => n > 0);
|
|
1397
1561
|
if (times.length) {
|
|
1398
1562
|
out.started_at = Math.min(...times);
|
|
1399
1563
|
out.last_at = Math.max(...times);
|
|
1400
1564
|
}
|
|
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
1565
|
return out;
|
|
1407
1566
|
}
|
|
1408
1567
|
|
|
@@ -1415,15 +1574,24 @@ var NONCE_TTL_MS = 25 * 36e5;
|
|
|
1415
1574
|
var MODEL_RE = /^[A-Za-z0-9][A-Za-z0-9._:/@[\]-]*$/;
|
|
1416
1575
|
var START_PERMISSIONS = ["read", "safe", "edits", "auto"];
|
|
1417
1576
|
var levelRank = (l) => START_PERMISSIONS.indexOf(l);
|
|
1577
|
+
var FIELDS = {
|
|
1578
|
+
prompt: ["text", "files"],
|
|
1579
|
+
"permission.answer": ["for", "decision"],
|
|
1580
|
+
cancel: [],
|
|
1581
|
+
start: ["text", "cwd", "files", "permission", "model"],
|
|
1582
|
+
"key.add": ["key", "name"],
|
|
1583
|
+
pool: ["pool_action", "pool_account", "pool_order", "pool_code"]
|
|
1584
|
+
};
|
|
1585
|
+
var POOL_FIELDS = FIELDS.pool;
|
|
1418
1586
|
var enc = new TextEncoder();
|
|
1419
1587
|
var dec = new TextDecoder();
|
|
1420
1588
|
|
|
1421
1589
|
// ../core/src/adapters/codex.ts
|
|
1422
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync4, writeFileSync as
|
|
1423
|
-
import
|
|
1590
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
1591
|
+
import path7 from "node:path";
|
|
1424
1592
|
|
|
1425
1593
|
// ../core/src/adapters/codex-config.ts
|
|
1426
|
-
var NOTIFY_LINE = /^[ \t]*notify[ \t]*=[ \t]*(\[.*\])[ \t]*(?:#.*)
|
|
1594
|
+
var NOTIFY_LINE = /^[ \t]*notify[ \t]*=[ \t]*(\[.*\])[ \t]*(?:#.*)?(?=\r?$)/m;
|
|
1427
1595
|
var NOTIFY_KEY = /^[ \t]*notify[ \t]*=/m;
|
|
1428
1596
|
var OURS2 = /(?:hook\.js|sessionpipe-hook)",\s*"codex",\s*"notify"/;
|
|
1429
1597
|
function scan(line, st) {
|
|
@@ -1513,10 +1681,11 @@ function dropLine(text, index, length) {
|
|
|
1513
1681
|
}
|
|
1514
1682
|
function insertTopLevel(text, line) {
|
|
1515
1683
|
const { head, tail } = split(text);
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1684
|
+
const body = head.replace(/\s*$/, "");
|
|
1685
|
+
const eol = (body.includes("\n") ? body : text).includes("\r\n") ? "\r\n" : "\n";
|
|
1686
|
+
const top = head.trim() ? head.replace(/\s*$/, eol) : "";
|
|
1687
|
+
if (!tail) return top + line + eol;
|
|
1688
|
+
return `${top + line}${eol}${eol}${tail}`;
|
|
1520
1689
|
}
|
|
1521
1690
|
function withNotify(text, line) {
|
|
1522
1691
|
const n = readNotify(text);
|
|
@@ -1565,10 +1734,10 @@ var CODEX_EVENTS = [
|
|
|
1565
1734
|
];
|
|
1566
1735
|
var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
1567
1736
|
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) =>
|
|
1737
|
+
var codexHome = (env) => env.CODEX_HOME || path7.join(homeDir(), ".codex");
|
|
1738
|
+
var hooksFile2 = (env) => path7.join(codexHome(env), "hooks.json");
|
|
1739
|
+
var configFile2 = (env) => path7.join(codexHome(env), "config.toml");
|
|
1740
|
+
var sessionsDir = (env) => path7.join(codexHome(env), "sessions");
|
|
1572
1741
|
var validTool3 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
1573
1742
|
var endReason2 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
|
|
1574
1743
|
function installNotify(cmd, env) {
|
|
@@ -1582,8 +1751,8 @@ function installNotify(cmd, env) {
|
|
|
1582
1751
|
const line = `notify = ${JSON.stringify(argv)}`;
|
|
1583
1752
|
const n = readNotify(text);
|
|
1584
1753
|
const write = (next) => {
|
|
1585
|
-
mkdirSync3(
|
|
1586
|
-
|
|
1754
|
+
mkdirSync3(path7.dirname(file), { recursive: true });
|
|
1755
|
+
writeFileSync4(file, next);
|
|
1587
1756
|
};
|
|
1588
1757
|
if (n && !n.other) {
|
|
1589
1758
|
if (!n.misplaced && JSON.stringify(n.argv) === JSON.stringify(argv))
|
|
@@ -1628,12 +1797,12 @@ function uninstallNotify(env) {
|
|
|
1628
1797
|
const n = readNotify(text);
|
|
1629
1798
|
if (!n) return { file, changed: false };
|
|
1630
1799
|
if (!n.other) {
|
|
1631
|
-
|
|
1800
|
+
writeFileSync4(file, withoutNotify(text));
|
|
1632
1801
|
return { file, changed: true };
|
|
1633
1802
|
}
|
|
1634
1803
|
const prev = previousNotify(n.argv);
|
|
1635
1804
|
if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev))) {
|
|
1636
|
-
|
|
1805
|
+
writeFileSync4(file, replaceNotify(text, n, `notify = ${stripPreviousRaw(n.raw)}`));
|
|
1637
1806
|
return { file, changed: true };
|
|
1638
1807
|
}
|
|
1639
1808
|
return { file, changed: false };
|
|
@@ -1649,7 +1818,7 @@ function findRollout(threadId, env = process.env) {
|
|
|
1649
1818
|
return;
|
|
1650
1819
|
}
|
|
1651
1820
|
for (const n of names.sort().reverse()) {
|
|
1652
|
-
const p =
|
|
1821
|
+
const p = path7.join(dir, n);
|
|
1653
1822
|
if (n.endsWith(`-${threadId}.jsonl`)) {
|
|
1654
1823
|
best = p;
|
|
1655
1824
|
return;
|
|
@@ -1834,7 +2003,7 @@ var codex = {
|
|
|
1834
2003
|
backfill(sinceMs, env = process.env) {
|
|
1835
2004
|
const rows2 = [];
|
|
1836
2005
|
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(
|
|
2006
|
+
const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(path7.basename(f));
|
|
1838
2007
|
if (!m?.[1]) continue;
|
|
1839
2008
|
if (codexSubagent(rolloutMeta(f))) continue;
|
|
1840
2009
|
const facts = codexFacts(f);
|
|
@@ -1843,12 +2012,40 @@ var codex = {
|
|
|
1843
2012
|
rows2.push({ session: { id: m[1], ...rest }, started_at, last_at });
|
|
1844
2013
|
}
|
|
1845
2014
|
return rows2;
|
|
1846
|
-
}
|
|
2015
|
+
},
|
|
2016
|
+
limits: (transcript) => codexLimits(transcript)
|
|
1847
2017
|
};
|
|
2018
|
+
var epochIso2 = (t) => typeof t === "number" && t > 0 && t < 1e11 ? new Date(t * 1e3).toISOString() : void 0;
|
|
1848
2019
|
function prune3(o) {
|
|
1849
2020
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
1850
2021
|
return o;
|
|
1851
2022
|
}
|
|
2023
|
+
function codexLimits(file) {
|
|
2024
|
+
const { tail } = ends(file, 0, 256 * 1024);
|
|
2025
|
+
for (const r of parseLines(tail).reverse()) {
|
|
2026
|
+
const p = r.payload;
|
|
2027
|
+
if (r.type !== "event_msg" || p?.type !== "token_count") continue;
|
|
2028
|
+
if (!p.rate_limits || typeof p.rate_limits !== "object") continue;
|
|
2029
|
+
const rl = p.rate_limits;
|
|
2030
|
+
if (rl.limit_id != null && rl.limit_id !== "codex") continue;
|
|
2031
|
+
const windows = [];
|
|
2032
|
+
for (const id of ["primary", "secondary"]) {
|
|
2033
|
+
const w = rl[id];
|
|
2034
|
+
if (typeof w?.used_percent !== "number" || w.used_percent < 0) continue;
|
|
2035
|
+
windows.push(
|
|
2036
|
+
prune3({
|
|
2037
|
+
id,
|
|
2038
|
+
used_percent: w.used_percent,
|
|
2039
|
+
window_minutes: Number.isInteger(w.window_minutes) && Number(w.window_minutes) > 0 ? Number(w.window_minutes) : void 0,
|
|
2040
|
+
resets_at: epochIso2(w.resets_at)
|
|
2041
|
+
})
|
|
2042
|
+
);
|
|
2043
|
+
}
|
|
2044
|
+
if (!windows.length) return null;
|
|
2045
|
+
return prune3({ windows, plan: typeof rl.plan_type === "string" ? rl.plan_type.slice(0, 64) : void 0 });
|
|
2046
|
+
}
|
|
2047
|
+
return null;
|
|
2048
|
+
}
|
|
1852
2049
|
function codexFacts(file) {
|
|
1853
2050
|
const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
|
|
1854
2051
|
let H = parseLines(head);
|
|
@@ -1893,7 +2090,7 @@ function codexFacts(file) {
|
|
|
1893
2090
|
// ../core/src/adapters/devin.ts
|
|
1894
2091
|
import { createHash as createHash3 } from "node:crypto";
|
|
1895
2092
|
import { existsSync as existsSync6, readlinkSync } from "node:fs";
|
|
1896
|
-
import
|
|
2093
|
+
import path8 from "node:path";
|
|
1897
2094
|
|
|
1898
2095
|
// ../core/src/readers/sqlite.ts
|
|
1899
2096
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
@@ -2077,11 +2274,11 @@ function foreignEvent(file) {
|
|
|
2077
2274
|
return Object.keys(hooks).find((k) => !ok.includes(k));
|
|
2078
2275
|
}
|
|
2079
2276
|
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) =>
|
|
2277
|
+
var appData = (env) => env.APPDATA || path8.join(homeOf(env), "AppData", "Roaming");
|
|
2278
|
+
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");
|
|
2279
|
+
var configFile3 = (env) => path8.join(devinConfigDir(env), "config.json");
|
|
2280
|
+
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");
|
|
2281
|
+
var sessionsDb = (env) => path8.join(devinDataDir(env), "sessions.db");
|
|
2085
2282
|
var SESSION_ID = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
2086
2283
|
var validTool4 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
2087
2284
|
var endReason3 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
|
|
@@ -2253,7 +2450,7 @@ var BACKFILL_SQL = `SELECT s.id, ${SESSION_FIELDS} FROM sessions s
|
|
|
2253
2450
|
function sessionFacts(row) {
|
|
2254
2451
|
const out = {};
|
|
2255
2452
|
if (typeof row.model === "string" && row.model) out.model = row.model;
|
|
2256
|
-
if (typeof row.working_directory === "string" &&
|
|
2453
|
+
if (typeof row.working_directory === "string" && path8.isAbsolute(row.working_directory))
|
|
2257
2454
|
out.cwd = row.working_directory;
|
|
2258
2455
|
const started = Number(row.created_at);
|
|
2259
2456
|
const last = Number(row.last_activity_at);
|
|
@@ -2298,8 +2495,8 @@ function readDevinChain(db, session, fromLine) {
|
|
|
2298
2495
|
}
|
|
2299
2496
|
function devinVersion(env = process.env) {
|
|
2300
2497
|
try {
|
|
2301
|
-
const target = readlinkSync(
|
|
2302
|
-
const name =
|
|
2498
|
+
const target = readlinkSync(path8.join(devinDataDir(env), "_versions", "current"));
|
|
2499
|
+
const name = path8.basename(target);
|
|
2303
2500
|
return /^[0-9][0-9A-Za-z.+-]{0,63}$/.test(name) ? name : void 0;
|
|
2304
2501
|
} catch {
|
|
2305
2502
|
return void 0;
|
|
@@ -2314,7 +2511,7 @@ function devinAccount(env = process.env) {
|
|
|
2314
2511
|
// ../core/src/adapters/gemini-cli.ts
|
|
2315
2512
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
2316
2513
|
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
|
|
2317
|
-
import
|
|
2514
|
+
import path9 from "node:path";
|
|
2318
2515
|
var NAME5 = "gemini-cli";
|
|
2319
2516
|
var GEMINI_EVENTS = [
|
|
2320
2517
|
"SessionStart",
|
|
@@ -2327,26 +2524,31 @@ var GEMINI_EVENTS = [
|
|
|
2327
2524
|
"SessionEnd"
|
|
2328
2525
|
];
|
|
2329
2526
|
var OPTS4 = { matcherFor: (e) => /Tool/.test(e) ? ".*" : void 0, timeout: 5e3 };
|
|
2330
|
-
var geminiDir = (env) => env.GEMINI_CLI_HOME ||
|
|
2331
|
-
var settings = (env) =>
|
|
2527
|
+
var geminiDir = (env) => env.GEMINI_CLI_HOME || path9.join(homeDir(), ".gemini");
|
|
2528
|
+
var settings = (env) => path9.join(geminiDir(env), "settings.json");
|
|
2332
2529
|
var validTool5 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
2333
2530
|
var eventsFor4 = (lean) => lean ? GEMINI_EVENTS.filter((e) => e !== "BeforeTool") : [...GEMINI_EVENTS];
|
|
2531
|
+
function geminiVersion(platform = process.platform, env = process.env) {
|
|
2532
|
+
const win = platform === "win32";
|
|
2533
|
+
const bin = win ? findBinary({ names: ["gemini.exe", "gemini.cmd"], dirs: [path9.dirname(process.execPath)], env }) : "gemini";
|
|
2534
|
+
if (!bin) throw new Error("no gemini on PATH");
|
|
2535
|
+
const o = {
|
|
2536
|
+
encoding: "utf8",
|
|
2537
|
+
timeout: 4e3,
|
|
2538
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2539
|
+
env
|
|
2540
|
+
};
|
|
2541
|
+
const out = /\.cmd$/i.test(bin) ? execFileSync4(`"${bin}" --version`, { ...o, shell: true }) : execFileSync4(bin, ["--version"], o);
|
|
2542
|
+
return out.trim();
|
|
2543
|
+
}
|
|
2334
2544
|
function geminiHooksActive(settingsFile) {
|
|
2335
|
-
|
|
2336
|
-
try {
|
|
2337
|
-
tools = JSON.parse(readFileSync5(settingsFile, "utf8")).tools;
|
|
2338
|
-
} catch {
|
|
2339
|
-
}
|
|
2545
|
+
const tools = readJson(settingsFile).tools;
|
|
2340
2546
|
if (tools?.enableHooks === true) return { active: true };
|
|
2341
2547
|
if (tools?.enableHooks === false)
|
|
2342
2548
|
return { active: false, note: "inactive: tools.enableHooks is false in settings.json" };
|
|
2343
2549
|
let version = "";
|
|
2344
2550
|
try {
|
|
2345
|
-
version =
|
|
2346
|
-
encoding: "utf8",
|
|
2347
|
-
timeout: 4e3,
|
|
2348
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
2349
|
-
}).trim();
|
|
2551
|
+
version = geminiVersion();
|
|
2350
2552
|
} catch {
|
|
2351
2553
|
return {
|
|
2352
2554
|
active: true,
|
|
@@ -2364,7 +2566,7 @@ var geminiCli = {
|
|
|
2364
2566
|
name: NAME5,
|
|
2365
2567
|
events: GEMINI_EVENTS,
|
|
2366
2568
|
// ~/.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(
|
|
2569
|
+
detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(path9.join(geminiDir(env), "tmp")),
|
|
2368
2570
|
configFiles: (env = process.env) => [settings(env)],
|
|
2369
2571
|
install: (cmd, env = process.env, opts = {}) => {
|
|
2370
2572
|
const r = installClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
|
|
@@ -2502,8 +2704,8 @@ var ADAPTERS = [claudeCode, codex, geminiCli, antigravity, devin];
|
|
|
2502
2704
|
var adapterByName = (name) => ADAPTERS.find((a) => a.name === name);
|
|
2503
2705
|
|
|
2504
2706
|
// ../core/src/config.ts
|
|
2505
|
-
import { chmodSync, existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as
|
|
2506
|
-
import
|
|
2707
|
+
import { chmodSync, existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
|
|
2708
|
+
import path10 from "node:path";
|
|
2507
2709
|
var DEFAULT_CONFIG = { sinks: [], harnesses: {}, update_check: true, keep_days: 30 };
|
|
2508
2710
|
function readConfig(file = configFile()) {
|
|
2509
2711
|
try {
|
|
@@ -2519,8 +2721,8 @@ function readConfig(file = configFile()) {
|
|
|
2519
2721
|
}
|
|
2520
2722
|
}
|
|
2521
2723
|
function writeConfig(c, file = configFile()) {
|
|
2522
|
-
mkdirSync4(
|
|
2523
|
-
|
|
2724
|
+
mkdirSync4(path10.dirname(file), { recursive: true, mode: 448 });
|
|
2725
|
+
writeFileSync5(file, `${JSON.stringify(c, null, 2)}
|
|
2524
2726
|
`, { mode: 384 });
|
|
2525
2727
|
if (process.platform !== "win32" && existsSync8(file)) {
|
|
2526
2728
|
try {
|
|
@@ -2610,11 +2812,11 @@ import {
|
|
|
2610
2812
|
readFileSync as readFileSync7,
|
|
2611
2813
|
readSync as readSync2,
|
|
2612
2814
|
renameSync,
|
|
2613
|
-
statSync as
|
|
2815
|
+
statSync as statSync5,
|
|
2614
2816
|
unlinkSync as unlinkSync3,
|
|
2615
|
-
writeFileSync as
|
|
2817
|
+
writeFileSync as writeFileSync6
|
|
2616
2818
|
} from "node:fs";
|
|
2617
|
-
import
|
|
2819
|
+
import path11 from "node:path";
|
|
2618
2820
|
var READ_CHUNK = 1024 * 1024;
|
|
2619
2821
|
var READ_MAX = 8 * 1024 * 1024;
|
|
2620
2822
|
var Outbox = class {
|
|
@@ -2623,13 +2825,13 @@ var Outbox = class {
|
|
|
2623
2825
|
}
|
|
2624
2826
|
root;
|
|
2625
2827
|
dir(harness) {
|
|
2626
|
-
return
|
|
2828
|
+
return path11.join(this.root, "outbox", safeId(harness));
|
|
2627
2829
|
}
|
|
2628
2830
|
file(ref) {
|
|
2629
|
-
return
|
|
2831
|
+
return path11.join(this.dir(ref.harness), `${safeId(ref.session)}.jsonl`);
|
|
2630
2832
|
}
|
|
2631
2833
|
seqFile(ref) {
|
|
2632
|
-
return
|
|
2834
|
+
return path11.join(this.dir(ref.harness), `${safeId(ref.session)}.seq`);
|
|
2633
2835
|
}
|
|
2634
2836
|
/** The next seq for a session, persisted (survives a hibernating machine and a
|
|
2635
2837
|
* restart; an in-memory counter would restart at 0 and collide). */
|
|
@@ -2642,8 +2844,8 @@ var Outbox = class {
|
|
|
2642
2844
|
} catch {
|
|
2643
2845
|
}
|
|
2644
2846
|
if (n < 0) n = this.lastSeqInFile(ref) + 1;
|
|
2645
|
-
mkdirSync5(
|
|
2646
|
-
|
|
2847
|
+
mkdirSync5(path11.dirname(f), { recursive: true, mode: 448 });
|
|
2848
|
+
writeFileSync6(f, String(n + 1), { mode: 384 });
|
|
2647
2849
|
return n;
|
|
2648
2850
|
}
|
|
2649
2851
|
lastSeqInFile(ref) {
|
|
@@ -2668,10 +2870,10 @@ var Outbox = class {
|
|
|
2668
2870
|
/** Every session file, newest first. */
|
|
2669
2871
|
sessions() {
|
|
2670
2872
|
const out = [];
|
|
2671
|
-
const base =
|
|
2873
|
+
const base = path11.join(this.root, "outbox");
|
|
2672
2874
|
if (!existsSync9(base)) return [];
|
|
2673
2875
|
for (const h of readdirSync5(base)) {
|
|
2674
|
-
const d =
|
|
2876
|
+
const d = path11.join(base, h);
|
|
2675
2877
|
let names = [];
|
|
2676
2878
|
try {
|
|
2677
2879
|
names = readdirSync5(d).filter((n) => n.endsWith(".jsonl"));
|
|
@@ -2680,7 +2882,7 @@ var Outbox = class {
|
|
|
2680
2882
|
}
|
|
2681
2883
|
for (const n of names) {
|
|
2682
2884
|
try {
|
|
2683
|
-
out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime:
|
|
2885
|
+
out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime: statSync5(path11.join(d, n)).mtimeMs });
|
|
2684
2886
|
} catch {
|
|
2685
2887
|
}
|
|
2686
2888
|
}
|
|
@@ -2696,7 +2898,7 @@ var Outbox = class {
|
|
|
2696
2898
|
read(ref, from, maxEvents = 500) {
|
|
2697
2899
|
const f = this.file(ref);
|
|
2698
2900
|
if (!existsSync9(f)) return { events: [], offset: from };
|
|
2699
|
-
const size =
|
|
2901
|
+
const size = statSync5(f).size;
|
|
2700
2902
|
if (from >= size) return { events: [], offset: from };
|
|
2701
2903
|
const fd = openSync2(f, "r");
|
|
2702
2904
|
try {
|
|
@@ -2738,7 +2940,7 @@ var Outbox = class {
|
|
|
2738
2940
|
for (const ref of this.sessions()) {
|
|
2739
2941
|
const f = this.file(ref);
|
|
2740
2942
|
try {
|
|
2741
|
-
if (
|
|
2943
|
+
if (statSync5(f).mtimeMs < cutoff) {
|
|
2742
2944
|
unlinkSync3(f);
|
|
2743
2945
|
try {
|
|
2744
2946
|
unlinkSync3(this.seqFile(ref));
|
|
@@ -2768,7 +2970,7 @@ var Cursors = class {
|
|
|
2768
2970
|
root;
|
|
2769
2971
|
sink;
|
|
2770
2972
|
file() {
|
|
2771
|
-
return
|
|
2973
|
+
return path11.join(this.root, "cursors", `${safeId(this.sink)}.json`);
|
|
2772
2974
|
}
|
|
2773
2975
|
read() {
|
|
2774
2976
|
try {
|
|
@@ -2779,9 +2981,9 @@ var Cursors = class {
|
|
|
2779
2981
|
}
|
|
2780
2982
|
write(c) {
|
|
2781
2983
|
const f = this.file();
|
|
2782
|
-
mkdirSync5(
|
|
2984
|
+
mkdirSync5(path11.dirname(f), { recursive: true, mode: 448 });
|
|
2783
2985
|
const tmp = `${f}.${process.pid}.tmp`;
|
|
2784
|
-
|
|
2986
|
+
writeFileSync6(tmp, JSON.stringify(c), { mode: 384 });
|
|
2785
2987
|
renameSync(tmp, f);
|
|
2786
2988
|
}
|
|
2787
2989
|
key(ref) {
|
|
@@ -2792,16 +2994,16 @@ function takeLock(root, ref, staleMs = 5 * 6e4) {
|
|
|
2792
2994
|
return takeNamedLock(root, `${safeId(ref.harness)}-${safeId(ref.session)}`, staleMs);
|
|
2793
2995
|
}
|
|
2794
2996
|
function takeNamedLock(root, name, staleMs = 5 * 6e4) {
|
|
2795
|
-
const dir =
|
|
2997
|
+
const dir = path11.join(root, "locks");
|
|
2796
2998
|
mkdirSync5(dir, { recursive: true, mode: 448 });
|
|
2797
|
-
const f =
|
|
2999
|
+
const f = path11.join(dir, `${name}.lock`);
|
|
2798
3000
|
try {
|
|
2799
|
-
if (Date.now() -
|
|
3001
|
+
if (Date.now() - statSync5(f).mtimeMs > staleMs || !processAlive(Number(readFileSync7(f, "utf8")))) unlinkSync3(f);
|
|
2800
3002
|
else return null;
|
|
2801
3003
|
} catch {
|
|
2802
3004
|
}
|
|
2803
3005
|
try {
|
|
2804
|
-
|
|
3006
|
+
writeFileSync6(f, String(process.pid), { flag: "wx" });
|
|
2805
3007
|
} catch {
|
|
2806
3008
|
return null;
|
|
2807
3009
|
}
|
|
@@ -3033,6 +3235,7 @@ var TYPE_TIER = {
|
|
|
3033
3235
|
"subagent.started": 1,
|
|
3034
3236
|
"subagent.ended": 1,
|
|
3035
3237
|
"context.compacted": 1,
|
|
3238
|
+
"limits.observed": 1,
|
|
3036
3239
|
native: 3
|
|
3037
3240
|
};
|
|
3038
3241
|
var DATA_ALLOW = {
|
|
@@ -3052,7 +3255,8 @@ var DATA_ALLOW = {
|
|
|
3052
3255
|
"attention.cleared": { 0: ["attention_id", "how"] },
|
|
3053
3256
|
"subagent.started": { 1: ["agent_id", "agent_type"] },
|
|
3054
3257
|
"subagent.ended": { 1: ["agent_id", "agent_type"] },
|
|
3055
|
-
"context.compacted": { 1: ["trigger"] }
|
|
3258
|
+
"context.compacted": { 1: ["trigger"] },
|
|
3259
|
+
"limits.observed": { 1: ["windows", "plan", "account_id"] }
|
|
3056
3260
|
};
|
|
3057
3261
|
var SESSION_ALLOW = [
|
|
3058
3262
|
"id",
|
|
@@ -3117,7 +3321,9 @@ function filterEvent(event, tier, pii, opts = {}) {
|
|
|
3117
3321
|
const s = reducePiiDeep(out.session, opts.home);
|
|
3118
3322
|
if (typeof s.machine === "string") s.machine = hashMachine(s.machine);
|
|
3119
3323
|
delete s.account_id;
|
|
3120
|
-
|
|
3324
|
+
const d = reducePiiDeep(out.data, opts.home);
|
|
3325
|
+
delete d.account_id;
|
|
3326
|
+
out = { ...out, session: s, data: d };
|
|
3121
3327
|
rulesets.push(RULESET_PII);
|
|
3122
3328
|
}
|
|
3123
3329
|
out.privacy = { rulesets, pii };
|
|
@@ -3132,7 +3338,7 @@ function shortId(harness, id) {
|
|
|
3132
3338
|
|
|
3133
3339
|
// ../core/src/sinks/file.ts
|
|
3134
3340
|
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync6 } from "node:fs";
|
|
3135
|
-
import
|
|
3341
|
+
import path12 from "node:path";
|
|
3136
3342
|
var FileSink = class {
|
|
3137
3343
|
constructor(name, file, tier, pii) {
|
|
3138
3344
|
this.name = name;
|
|
@@ -3146,7 +3352,7 @@ var FileSink = class {
|
|
|
3146
3352
|
pii;
|
|
3147
3353
|
async send(events) {
|
|
3148
3354
|
try {
|
|
3149
|
-
mkdirSync6(
|
|
3355
|
+
mkdirSync6(path12.dirname(this.file), { recursive: true });
|
|
3150
3356
|
appendFileSync2(this.file, `${events.map((e) => JSON.stringify(e)).join("\n")}
|
|
3151
3357
|
`);
|
|
3152
3358
|
return { ok: true, acknowledged: events.length, accepted: events.length, duplicates: 0, rejected: [] };
|
|
@@ -3180,8 +3386,8 @@ var StdoutSink = class {
|
|
|
3180
3386
|
};
|
|
3181
3387
|
|
|
3182
3388
|
// src/control/aliases.ts
|
|
3183
|
-
import { chmodSync as chmodSync2, mkdirSync as mkdirSync7, readFileSync as readFileSync8, renameSync as renameSync2, writeFileSync as
|
|
3184
|
-
import
|
|
3389
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync7, readFileSync as readFileSync8, renameSync as renameSync2, writeFileSync as writeFileSync7 } from "node:fs";
|
|
3390
|
+
import path13 from "node:path";
|
|
3185
3391
|
|
|
3186
3392
|
// src/secrets.ts
|
|
3187
3393
|
import { spawnSync } from "node:child_process";
|
|
@@ -3225,7 +3431,7 @@ function startOptionsOf(v) {
|
|
|
3225
3431
|
}
|
|
3226
3432
|
|
|
3227
3433
|
// src/control/aliases.ts
|
|
3228
|
-
var aliasFile = (controlDir) =>
|
|
3434
|
+
var aliasFile = (controlDir) => path13.join(controlDir, "aliases.json");
|
|
3229
3435
|
function readAliases(file) {
|
|
3230
3436
|
try {
|
|
3231
3437
|
const j = JSON.parse(readFileSync8(file, "utf8"));
|
|
@@ -3250,11 +3456,11 @@ function receiverIdOf(all, harness, id) {
|
|
|
3250
3456
|
// src/control/local.ts
|
|
3251
3457
|
import net from "node:net";
|
|
3252
3458
|
import os2 from "node:os";
|
|
3253
|
-
import
|
|
3459
|
+
import path14 from "node:path";
|
|
3254
3460
|
function socketPath(state, env = process.env) {
|
|
3255
3461
|
if (env.SESSIONPIPE_SOCKET) return env.SESSIONPIPE_SOCKET;
|
|
3256
3462
|
if (process.platform === "win32") return `\\\\.\\pipe\\sessionpipe-control-${os2.userInfo().username}`;
|
|
3257
|
-
return
|
|
3463
|
+
return path14.join(state, "control.sock");
|
|
3258
3464
|
}
|
|
3259
3465
|
function ask(sock, req, timeoutMs) {
|
|
3260
3466
|
return new Promise((resolve) => {
|
|
@@ -3305,8 +3511,8 @@ async function tellDaemon(state, job, session, cwd, transcript) {
|
|
|
3305
3511
|
function enqueueJob(state, job) {
|
|
3306
3512
|
const jobs = jobsDir(state);
|
|
3307
3513
|
mkdirSync8(jobs, { recursive: true, mode: 448 });
|
|
3308
|
-
const f =
|
|
3309
|
-
|
|
3514
|
+
const f = path15.join(jobs, `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.json`);
|
|
3515
|
+
writeFileSync8(f, JSON.stringify(job), { mode: 384 });
|
|
3310
3516
|
return f;
|
|
3311
3517
|
}
|
|
3312
3518
|
function releaseJob(claimed) {
|
|
@@ -3319,9 +3525,9 @@ function releaseJob(claimed) {
|
|
|
3319
3525
|
function log(state, line) {
|
|
3320
3526
|
try {
|
|
3321
3527
|
mkdirSync8(state, { recursive: true, mode: 448 });
|
|
3322
|
-
const f =
|
|
3528
|
+
const f = path15.join(state, "log");
|
|
3323
3529
|
try {
|
|
3324
|
-
if (
|
|
3530
|
+
if (statSync6(f).size > 1024 * 1024) renameSync3(f, `${f}.1`);
|
|
3325
3531
|
} catch {
|
|
3326
3532
|
}
|
|
3327
3533
|
appendFileSync3(f, `${(/* @__PURE__ */ new Date()).toISOString()} ${line}
|
|
@@ -3330,7 +3536,7 @@ function log(state, line) {
|
|
|
3330
3536
|
}
|
|
3331
3537
|
}
|
|
3332
3538
|
function factsState(state) {
|
|
3333
|
-
const file = (h, s) =>
|
|
3539
|
+
const file = (h, s) => path15.join(state, "facts", safeId(h), `${safeId(s)}.json`);
|
|
3334
3540
|
return {
|
|
3335
3541
|
get(h, s) {
|
|
3336
3542
|
try {
|
|
@@ -3343,8 +3549,8 @@ function factsState(state) {
|
|
|
3343
3549
|
const cur = this.get(h, s);
|
|
3344
3550
|
const next = { ...cur, ...patch };
|
|
3345
3551
|
if (JSON.stringify(next) === JSON.stringify(cur)) return;
|
|
3346
|
-
mkdirSync8(
|
|
3347
|
-
|
|
3552
|
+
mkdirSync8(path15.dirname(file(h, s)), { recursive: true, mode: 448 });
|
|
3553
|
+
writeFileSync8(file(h, s), JSON.stringify(next), { mode: 384 });
|
|
3348
3554
|
}
|
|
3349
3555
|
};
|
|
3350
3556
|
}
|
|
@@ -3416,7 +3622,7 @@ async function runJob(job, opts = {}) {
|
|
|
3416
3622
|
);
|
|
3417
3623
|
return { events: [], delivered: {} };
|
|
3418
3624
|
}
|
|
3419
|
-
const id = receiverIdOf(readAliases(aliasFile(
|
|
3625
|
+
const id = receiverIdOf(readAliases(aliasFile(path15.join(state, "control"))), job.harness, r.session.id) ?? r.session.id;
|
|
3420
3626
|
const claudeEnd = job.harness === "claude-code" && r.events.some((e) => e.type === "session.ended");
|
|
3421
3627
|
if (!claudeEnd) await tellDaemon(state, job, id, r.session.cwd, r.transcript);
|
|
3422
3628
|
const outbox = new Outbox(state);
|
|
@@ -3445,12 +3651,15 @@ async function runJob(job, opts = {}) {
|
|
|
3445
3651
|
const heavy = !/^(PreToolUse|PostToolUse|PostToolUseFailure|BeforeTool|AfterTool|PostInvocation)$/.test(job.event);
|
|
3446
3652
|
let learned = {};
|
|
3447
3653
|
if (heavy) {
|
|
3448
|
-
const
|
|
3654
|
+
const hookEnv = { ...process.env, ...job.env };
|
|
3655
|
+
const f = adapter.facts(r.session, r.transcript, r.hints ?? {}, facts, hookEnv);
|
|
3656
|
+
if (cfg.remote_titles && adapter.remoteFacts)
|
|
3657
|
+
Object.assign(f, await adapter.remoteFacts(r.session, f, r.transcript, facts, hookEnv));
|
|
3449
3658
|
const { started_at: _s, last_at: _l, first_ask: _fa, harness_version, ...rest } = f;
|
|
3450
3659
|
learned = rest;
|
|
3451
3660
|
if (harness_version) facts.save(job.harness, id, { harness_version });
|
|
3452
3661
|
facts.save(job.harness, id, { session: { ...mem.session, ...learned } });
|
|
3453
|
-
if (CWD_FROM_HOOKS.has(job.harness) && r.session.cwd &&
|
|
3662
|
+
if (CWD_FROM_HOOKS.has(job.harness) && r.session.cwd && path15.isAbsolute(r.session.cwd))
|
|
3454
3663
|
facts.save(job.harness, id, { cwd: r.session.cwd });
|
|
3455
3664
|
}
|
|
3456
3665
|
const known = mem.session ?? {};
|
|
@@ -3532,6 +3741,23 @@ async function runJob(job, opts = {}) {
|
|
|
3532
3741
|
}
|
|
3533
3742
|
if (read.turns.length) facts.save(job.harness, id, { transcript_line: read.line, transcript_turns: turnNo });
|
|
3534
3743
|
}
|
|
3744
|
+
if (adapter.limits && r.transcript && ending && cfg.sinks.some((s) => !s.paused && Math.min(s.tier, s.max_tier ?? 3) >= 1)) {
|
|
3745
|
+
const limits = adapter.limits(r.transcript);
|
|
3746
|
+
const sig = limits && JSON.stringify(limits);
|
|
3747
|
+
if (limits && sig !== facts.get(job.harness, id).limits_sig) {
|
|
3748
|
+
events.push(
|
|
3749
|
+
makeEvent(
|
|
3750
|
+
{ type: "limits.observed", harnessEvent: job.event, data: limits },
|
|
3751
|
+
{
|
|
3752
|
+
harness: { name: job.harness, ...version ? { version } : {} },
|
|
3753
|
+
session: { ...session, seq: outbox.nextSeq(ref) },
|
|
3754
|
+
now: opts.now ?? job.at
|
|
3755
|
+
}
|
|
3756
|
+
)
|
|
3757
|
+
);
|
|
3758
|
+
facts.save(job.harness, id, { limits_sig: sig });
|
|
3759
|
+
}
|
|
3760
|
+
}
|
|
3535
3761
|
outbox.append(
|
|
3536
3762
|
ref,
|
|
3537
3763
|
events.map((e) => ({ ...e, session: redactDeep(e.session), data: redactDeep(e.data) }))
|
|
@@ -3549,11 +3775,11 @@ async function runJob(job, opts = {}) {
|
|
|
3549
3775
|
return { events, delivered };
|
|
3550
3776
|
}
|
|
3551
3777
|
async function drain(state, sinkConfigs, outbox, first) {
|
|
3552
|
-
const more =
|
|
3778
|
+
const more = path15.join(state, "locks", "flush.more");
|
|
3553
3779
|
let release = takeNamedLock(state, "flush");
|
|
3554
3780
|
if (!release) {
|
|
3555
3781
|
try {
|
|
3556
|
-
|
|
3782
|
+
writeFileSync8(more, "", { mode: 384 });
|
|
3557
3783
|
} catch {
|
|
3558
3784
|
}
|
|
3559
3785
|
release = takeNamedLock(state, "flush");
|
|
@@ -3577,7 +3803,7 @@ async function drain(state, sinkConfigs, outbox, first) {
|
|
|
3577
3803
|
}
|
|
3578
3804
|
}
|
|
3579
3805
|
var BACKOFF_MS = [2e3, 1e4, 6e4, 5 * 6e4, 30 * 6e4];
|
|
3580
|
-
var backoffFile = (state, sink) =>
|
|
3806
|
+
var backoffFile = (state, sink) => path15.join(state, "backoff", `${safeId(sink)}.json`);
|
|
3581
3807
|
function readBackoff(state, sink) {
|
|
3582
3808
|
try {
|
|
3583
3809
|
const b = JSON.parse(readFileSync9(backoffFile(state, sink), "utf8"));
|
|
@@ -3593,8 +3819,8 @@ function noteFailure(state, sink, res, prev) {
|
|
|
3593
3819
|
const b = { until: Date.now() + wait, failures, why: `${res.status ?? ""} ${res.error ?? ""}`.trim() };
|
|
3594
3820
|
const f = backoffFile(state, sink);
|
|
3595
3821
|
try {
|
|
3596
|
-
mkdirSync8(
|
|
3597
|
-
|
|
3822
|
+
mkdirSync8(path15.dirname(f), { recursive: true, mode: 448 });
|
|
3823
|
+
writeFileSync8(f, JSON.stringify(b), { mode: 384 });
|
|
3598
3824
|
} catch {
|
|
3599
3825
|
}
|
|
3600
3826
|
return b;
|
|
@@ -3726,7 +3952,7 @@ function applySinkVerdict(name, res) {
|
|
|
3726
3952
|
} catch {
|
|
3727
3953
|
}
|
|
3728
3954
|
}
|
|
3729
|
-
var jobsDir = (state) =>
|
|
3955
|
+
var jobsDir = (state) => path15.join(state, "jobs");
|
|
3730
3956
|
export {
|
|
3731
3957
|
HttpSink,
|
|
3732
3958
|
buildSinks,
|