sessionpipe 0.9.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 +99 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +8737 -4942
- package/dist/cli.js.map +4 -4
- package/dist/control/aliases.d.ts +5 -0
- package/dist/control/aliases.d.ts.map +1 -1
- package/dist/control/autoupdate.d.ts +1 -18
- package/dist/control/autoupdate.d.ts.map +1 -1
- package/dist/control/cli.d.ts +5 -1
- package/dist/control/cli.d.ts.map +1 -1
- package/dist/control/daemon.d.ts +107 -12
- package/dist/control/daemon.d.ts.map +1 -1
- package/dist/control/files.d.ts +6 -4
- package/dist/control/files.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/pair.d.ts +15 -23
- package/dist/control/pair.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 +2 -2
- package/dist/control/route.d.ts.map +1 -1
- package/dist/control/sdk.d.ts.map +1 -1
- package/dist/control/store.d.ts +21 -2
- package/dist/control/store.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 +43 -1
- package/dist/hook.js.map +2 -2
- package/dist/index.js +1179 -847
- 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/update.d.ts +1 -7
- package/dist/update.d.ts.map +1 -1
- package/dist/worker.js +1182 -850
- 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,310 +627,735 @@ var antigravity = {
|
|
|
593
627
|
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
594
628
|
} catch {
|
|
595
629
|
}
|
|
596
|
-
const id = s.
|
|
597
|
-
|
|
598
|
-
if (
|
|
599
|
-
const
|
|
600
|
-
|
|
601
|
-
if (typeof s.
|
|
602
|
-
|
|
603
|
-
const
|
|
630
|
+
const id = s.session_id ?? s.sessionId;
|
|
631
|
+
if (!id) return null;
|
|
632
|
+
if (notClaudeCode(String(id), s)) return null;
|
|
633
|
+
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
634
|
+
if (typeof s.model === "string") session.model = s.model;
|
|
635
|
+
if (typeof s.agent_id === "string" && event.startsWith("Subagent") === false && s.agent_id)
|
|
636
|
+
session.parent_id = null;
|
|
637
|
+
const turn_id = typeof s.prompt_id === "string" ? s.prompt_id : void 0;
|
|
638
|
+
const tool = validTool(s.tool_name) ? s.tool_name : void 0;
|
|
639
|
+
const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
|
|
640
|
+
const ms = typeof s.duration_ms === "number" ? Math.round(s.duration_ms) : void 0;
|
|
641
|
+
const agent_id = typeof s.agent_id === "string" ? s.agent_id : void 0;
|
|
642
|
+
const permissionId = () => `perm-${createHash2("sha256").update(`${turn_id ?? ""}|${tool ?? ""}|${JSON.stringify(s.tool_input ?? null)}`).digest("hex").slice(0, 16)}`;
|
|
604
643
|
const ev = (type, data) => ({ type, data: prune(data), harnessEvent: event });
|
|
605
644
|
const events = [];
|
|
606
645
|
switch (event) {
|
|
607
|
-
case "
|
|
608
|
-
|
|
609
|
-
events.push(ev("session.started", { source: "unknown" }));
|
|
610
|
-
events.push(ev("turn.started", {}));
|
|
611
|
-
} else events.push(ev("session.heartbeat", {}));
|
|
646
|
+
case "SessionStart":
|
|
647
|
+
events.push(ev("session.started", { source: pickSource(s.source ?? s.trigger) }));
|
|
612
648
|
break;
|
|
613
|
-
case "
|
|
649
|
+
case "UserPromptSubmit": {
|
|
650
|
+
const prompt = typeof s.prompt === "string" ? s.prompt : void 0;
|
|
651
|
+
events.push(
|
|
652
|
+
ev("turn.started", {
|
|
653
|
+
turn_id,
|
|
654
|
+
prompt_chars: prompt === void 0 ? void 0 : [...prompt].length,
|
|
655
|
+
// Claude Code's hook schema documents `source`
|
|
656
|
+
// (user | sdk | system | loop_wakeup | schedule_wakeup | poll_event) and says a
|
|
657
|
+
// payload may omit it while the field rolls out; 2.1.304's payload has no such
|
|
658
|
+
// key at all. Forwarded verbatim, shape-guarded only: the list is the harness's
|
|
659
|
+
// to extend.
|
|
660
|
+
submitted_by: harnessWord(s.source),
|
|
661
|
+
// One of Claude Code's own prompt wrappers: the prompt is a block the harness
|
|
662
|
+
// wrote, so it is not the person's words. Absent is unknown, never "the person" —
|
|
663
|
+
// and `true` is never "no person caused this turn", because a Talk or
|
|
664
|
+
// `sessionpipe wait` delivery hands the person's own message over inside a
|
|
665
|
+
// <task-notification> (PROTOCOL.md §4).
|
|
666
|
+
injected: prompt !== void 0 && injectedBlock(prompt) ? true : void 0
|
|
667
|
+
})
|
|
668
|
+
);
|
|
614
669
|
break;
|
|
670
|
+
}
|
|
615
671
|
case "PreToolUse":
|
|
616
|
-
if (tool) events.push(ev("tool.started", { tool, input:
|
|
672
|
+
if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, agent_id, input: s.tool_input }));
|
|
617
673
|
break;
|
|
618
674
|
case "PostToolUse":
|
|
619
|
-
if (
|
|
620
|
-
else
|
|
675
|
+
if (tool)
|
|
621
676
|
events.push(
|
|
622
677
|
ev("tool.ended", {
|
|
623
678
|
tool,
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
679
|
+
call_id,
|
|
680
|
+
turn_id,
|
|
681
|
+
agent_id,
|
|
682
|
+
ms,
|
|
683
|
+
ok: true,
|
|
684
|
+
input: s.tool_input,
|
|
685
|
+
output: s.tool_response
|
|
686
|
+
})
|
|
687
|
+
);
|
|
688
|
+
break;
|
|
689
|
+
case "PostToolUseFailure":
|
|
690
|
+
if (tool)
|
|
691
|
+
events.push(
|
|
692
|
+
ev("tool.ended", {
|
|
693
|
+
tool,
|
|
694
|
+
call_id,
|
|
695
|
+
turn_id,
|
|
696
|
+
agent_id,
|
|
697
|
+
ms,
|
|
698
|
+
ok: false,
|
|
699
|
+
error: errText(s.error ?? s.tool_response),
|
|
700
|
+
input: s.tool_input,
|
|
701
|
+
output: s.tool_response
|
|
627
702
|
})
|
|
628
703
|
);
|
|
629
704
|
break;
|
|
705
|
+
case "PermissionRequest":
|
|
706
|
+
events.push(
|
|
707
|
+
ev("attention.needed", {
|
|
708
|
+
attention_id: call_id ?? permissionId(),
|
|
709
|
+
kind: "permission",
|
|
710
|
+
tool,
|
|
711
|
+
message: tool ? `${tool}${describe(s.tool_input)}` : void 0
|
|
712
|
+
})
|
|
713
|
+
);
|
|
714
|
+
break;
|
|
715
|
+
case "Notification":
|
|
716
|
+
events.push(
|
|
717
|
+
ev("attention.needed", {
|
|
718
|
+
attention_id: `notif-${Date.now()}`,
|
|
719
|
+
kind: notifKind(s.notification_type),
|
|
720
|
+
message: typeof s.message === "string" ? s.message.slice(0, 200) : void 0
|
|
721
|
+
})
|
|
722
|
+
);
|
|
723
|
+
break;
|
|
630
724
|
case "Stop":
|
|
631
|
-
events.push(
|
|
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
|
+
);
|
|
632
732
|
break;
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
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));
|
|
771
|
+
break;
|
|
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;
|
|
656
804
|
},
|
|
805
|
+
remoteFacts: (session, local, transcript, state, env = process.env) => claudeRemoteFacts(session.id, local, transcript, state, env),
|
|
657
806
|
backfill(sinceMs, env = process.env) {
|
|
658
807
|
const rows2 = [];
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
}
|
|
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
|
-
}
|
|
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
923
|
}
|
|
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
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
935
|
+
}
|
|
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;
|
|
946
|
+
}
|
|
947
|
+
} catch {
|
|
948
|
+
}
|
|
949
|
+
return null;
|
|
950
|
+
}
|
|
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;
|
|
749
959
|
}
|
|
960
|
+
};
|
|
961
|
+
}
|
|
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]));
|
|
750
973
|
}
|
|
751
|
-
|
|
974
|
+
} catch {
|
|
975
|
+
}
|
|
976
|
+
return (pid) => table.get(pid) || 0;
|
|
752
977
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
const
|
|
756
|
-
|
|
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) {
|
|
757
983
|
try {
|
|
758
|
-
const
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
const conv = opts.conversation ?? "";
|
|
764
|
-
if (!ID_RE.test(conv) || !opts.workspaces?.length) return base;
|
|
765
|
-
const project = agMatchProject(agProjects(opts.projectsDir ?? projectsDir(opts.env)), opts.workspaces);
|
|
766
|
-
return project ? `${base}?p=${encodeURIComponent(`c/${conv}?section=${project}`)}` : base;
|
|
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
|
+
}
|
|
767
989
|
} catch {
|
|
768
990
|
}
|
|
769
991
|
}
|
|
770
|
-
return
|
|
992
|
+
return apiAccount(dir);
|
|
771
993
|
}
|
|
772
|
-
|
|
773
|
-
|
|
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;
|
|
999
|
+
try {
|
|
1000
|
+
u = new URL(base.trim());
|
|
1001
|
+
} catch {
|
|
1002
|
+
return null;
|
|
1003
|
+
}
|
|
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 };
|
|
1008
|
+
}
|
|
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]}`;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
return null;
|
|
1029
|
+
}
|
|
1030
|
+
function appTitle(text) {
|
|
1031
|
+
const t = String(text).replace(/\s+/g, " ").trim();
|
|
1032
|
+
return t.length > 50 ? `${t.slice(0, 50)}...` : t;
|
|
1033
|
+
}
|
|
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;
|
|
1042
|
+
}
|
|
1043
|
+
function firstAsk(records) {
|
|
1044
|
+
for (const j of records) {
|
|
1045
|
+
const t = askText(j);
|
|
1046
|
+
if (t) return appTitle(t);
|
|
1047
|
+
}
|
|
1048
|
+
return null;
|
|
1049
|
+
}
|
|
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;
|
|
1081
|
+
}
|
|
1082
|
+
function claudeFacts(file, sessionId, hints, state) {
|
|
1083
|
+
const { head, tail } = ends(file, 64 * 1024, 256 * 1024);
|
|
774
1084
|
const H = parseLines(head);
|
|
775
1085
|
const T = parseLines(tail);
|
|
776
1086
|
const out = {};
|
|
777
|
-
const
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
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;
|
|
1096
|
+
try {
|
|
1097
|
+
size = statSync2(file).size;
|
|
1098
|
+
} catch {
|
|
1099
|
+
}
|
|
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;
|
|
786
1141
|
out.title_source = "first-ask";
|
|
787
|
-
break;
|
|
788
1142
|
}
|
|
789
|
-
const
|
|
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;
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
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);
|
|
790
1155
|
if (times.length) {
|
|
791
1156
|
out.started_at = Math.min(...times);
|
|
792
1157
|
out.last_at = Math.max(...times);
|
|
793
1158
|
}
|
|
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;
|
|
1163
|
+
}
|
|
794
1164
|
return out;
|
|
795
1165
|
}
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
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)) {
|
|
1204
|
+
try {
|
|
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;
|
|
1215
|
+
} catch {
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
try {
|
|
1219
|
+
raw.push(readFileSync2(path4.join(dir, ".credentials.json"), "utf8"));
|
|
1220
|
+
} catch {
|
|
1221
|
+
}
|
|
1222
|
+
for (const r of raw) {
|
|
811
1223
|
try {
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
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;
|
|
817
1231
|
} catch {
|
|
818
|
-
return "";
|
|
819
1232
|
}
|
|
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;
|
|
1233
|
+
}
|
|
1234
|
+
return null;
|
|
828
1235
|
}
|
|
829
|
-
function
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
1236
|
+
async function claudeAiTitle(bridge, token) {
|
|
1237
|
+
try {
|
|
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;
|
|
1251
|
+
} catch {
|
|
1252
|
+
return void 0;
|
|
1253
|
+
}
|
|
833
1254
|
}
|
|
834
1255
|
|
|
835
|
-
// ../core/src/adapters/
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
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);
|
|
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) {
|
|
872
1262
|
try {
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
if (existsSync3(path5.join(d, "projects")) || existsSync3(path5.join(d, "settings.json"))) add(d);
|
|
877
|
-
}
|
|
1263
|
+
if (!statSync3(p).isFile()) return false;
|
|
1264
|
+
accessSync(p, constants.X_OK);
|
|
1265
|
+
return true;
|
|
878
1266
|
} catch {
|
|
1267
|
+
return false;
|
|
879
1268
|
}
|
|
880
|
-
return out.filter((d, i) => i === 0 || existsSync3(d));
|
|
881
1269
|
}
|
|
882
|
-
|
|
883
|
-
|
|
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;
|
|
1278
|
+
}
|
|
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}$/;
|
|
884
1302
|
var validTool2 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
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 = {
|
|
888
1310
|
name: NAME2,
|
|
889
|
-
events:
|
|
890
|
-
detect: (env = process.env) =>
|
|
891
|
-
configFiles: (env = process.env) =>
|
|
892
|
-
install
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
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
|
+
},
|
|
900
1359
|
fromHook(input) {
|
|
901
1360
|
const event = input.argv[1] ?? "";
|
|
902
1361
|
let s = {};
|
|
@@ -905,133 +1364,48 @@ var claudeCode = {
|
|
|
905
1364
|
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
906
1365
|
} catch {
|
|
907
1366
|
}
|
|
908
|
-
const id = s.session_id ?? s.sessionId;
|
|
909
|
-
|
|
910
|
-
if (
|
|
911
|
-
const
|
|
912
|
-
|
|
913
|
-
if (typeof s.
|
|
914
|
-
|
|
915
|
-
const
|
|
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)}`;
|
|
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;
|
|
921
1375
|
const ev = (type, data) => ({ type, data: prune2(data), harnessEvent: event });
|
|
922
1376
|
const events = [];
|
|
923
1377
|
switch (event) {
|
|
924
|
-
case "
|
|
925
|
-
|
|
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", {}));
|
|
926
1383
|
break;
|
|
927
|
-
case "
|
|
928
|
-
events.push(
|
|
929
|
-
ev("turn.started", {
|
|
930
|
-
turn_id,
|
|
931
|
-
prompt_chars: typeof s.prompt === "string" ? [...s.prompt].length : void 0
|
|
932
|
-
})
|
|
933
|
-
);
|
|
1384
|
+
case "PostInvocation":
|
|
934
1385
|
break;
|
|
935
1386
|
case "PreToolUse":
|
|
936
|
-
if (tool) events.push(ev("tool.started", { tool,
|
|
1387
|
+
if (tool) events.push(ev("tool.started", { tool, input: toolCall?.args }));
|
|
937
1388
|
break;
|
|
938
1389
|
case "PostToolUse":
|
|
939
|
-
if (tool)
|
|
940
|
-
|
|
941
|
-
ev("tool.ended", {
|
|
942
|
-
tool,
|
|
943
|
-
call_id,
|
|
944
|
-
turn_id,
|
|
945
|
-
agent_id,
|
|
946
|
-
ms,
|
|
947
|
-
ok: true,
|
|
948
|
-
input: s.tool_input,
|
|
949
|
-
output: s.tool_response
|
|
950
|
-
})
|
|
951
|
-
);
|
|
952
|
-
break;
|
|
953
|
-
case "PostToolUseFailure":
|
|
954
|
-
if (tool)
|
|
1390
|
+
if (!tool) events.push(ev("session.heartbeat", {}));
|
|
1391
|
+
else
|
|
955
1392
|
events.push(
|
|
956
1393
|
ev("tool.ended", {
|
|
957
1394
|
tool,
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
ms,
|
|
962
|
-
ok: false,
|
|
963
|
-
error: errText(s.error ?? s.tool_response),
|
|
964
|
-
input: s.tool_input,
|
|
965
|
-
output: s.tool_response
|
|
1395
|
+
ok: !s.error,
|
|
1396
|
+
error: typeof s.error === "string" ? s.error.slice(0, 2e3) : void 0,
|
|
1397
|
+
input: toolCall?.args
|
|
966
1398
|
})
|
|
967
1399
|
);
|
|
968
1400
|
break;
|
|
969
|
-
case "
|
|
970
|
-
events.push(
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
);
|
|
978
|
-
break;
|
|
979
|
-
case "Notification":
|
|
980
|
-
events.push(
|
|
981
|
-
ev("attention.needed", {
|
|
982
|
-
attention_id: `notif-${Date.now()}`,
|
|
983
|
-
kind: notifKind(s.notification_type),
|
|
984
|
-
message: typeof s.message === "string" ? s.message.slice(0, 200) : void 0
|
|
985
|
-
})
|
|
986
|
-
);
|
|
987
|
-
break;
|
|
988
|
-
case "Stop":
|
|
989
|
-
events.push(ev("turn.ended", { turn_id, reason: "stop" }));
|
|
990
|
-
break;
|
|
991
|
-
case "StopFailure":
|
|
992
|
-
events.push(
|
|
993
|
-
ev("turn.ended", {
|
|
994
|
-
turn_id,
|
|
995
|
-
reason: "error",
|
|
996
|
-
error: typeof s.error === "string" ? s.error.slice(0, 200) : void 0
|
|
997
|
-
})
|
|
998
|
-
);
|
|
999
|
-
break;
|
|
1000
|
-
case "SubagentStart":
|
|
1001
|
-
events.push(
|
|
1002
|
-
ev("subagent.started", {
|
|
1003
|
-
agent_id: String(s.agent_id ?? ""),
|
|
1004
|
-
agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
|
|
1005
|
-
})
|
|
1006
|
-
);
|
|
1007
|
-
break;
|
|
1008
|
-
case "SubagentStop":
|
|
1009
|
-
events.push(
|
|
1010
|
-
ev("subagent.ended", {
|
|
1011
|
-
agent_id: String(s.agent_id ?? ""),
|
|
1012
|
-
agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
|
|
1013
|
-
})
|
|
1014
|
-
);
|
|
1015
|
-
break;
|
|
1016
|
-
case "PreCompact":
|
|
1017
|
-
events.push(ev("context.compacted", { trigger: s.trigger === "manual" ? "manual" : "auto" }));
|
|
1018
|
-
break;
|
|
1019
|
-
case "PostCompact":
|
|
1020
|
-
break;
|
|
1021
|
-
// PreCompact already said it; a second event would double-count
|
|
1022
|
-
case "SessionEnd":
|
|
1023
|
-
events.push(ev("session.ended", { reason: endReason(s.reason) }));
|
|
1024
|
-
break;
|
|
1025
|
-
default:
|
|
1026
|
-
return null;
|
|
1027
|
-
}
|
|
1028
|
-
const hints = {};
|
|
1029
|
-
if (event === "UserPromptSubmit" && typeof s.prompt === "string") hints.prompt = s.prompt;
|
|
1030
|
-
const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
|
|
1031
|
-
if (host && DESKTOP_ID.test(host)) hints.desktop_id = host;
|
|
1032
|
-
if (event !== "PreToolUse" && event !== "PostToolUse") hints.claude_pid = claudePid(input.env);
|
|
1033
|
-
const r = { session, events, hints };
|
|
1034
|
-
if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
|
|
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;
|
|
1035
1409
|
return r;
|
|
1036
1410
|
},
|
|
1037
1411
|
readTranscript(file, fromLine) {
|
|
@@ -1041,34 +1415,36 @@ var claudeCode = {
|
|
|
1041
1415
|
} catch {
|
|
1042
1416
|
return { turns: [], line: fromLine };
|
|
1043
1417
|
}
|
|
1044
|
-
return
|
|
1418
|
+
return readAntigravity(text.split("\n"), fromLine);
|
|
1045
1419
|
},
|
|
1046
|
-
facts(session, transcript, hints
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
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
|
+
});
|
|
1053
1427
|
},
|
|
1054
1428
|
backfill(sinceMs, env = process.env) {
|
|
1055
1429
|
const rows2 = [];
|
|
1056
|
-
const
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
const id
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
last_at
|
|
1071
|
-
|
|
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
|
+
}
|
|
1072
1448
|
}
|
|
1073
1449
|
return rows2;
|
|
1074
1450
|
}
|
|
@@ -1077,295 +1453,145 @@ function prune2(o) {
|
|
|
1077
1453
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
1078
1454
|
return o;
|
|
1079
1455
|
}
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
function pickSource(v) {
|
|
1085
|
-
return typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
|
|
1086
|
-
}
|
|
1087
|
-
function errText(v) {
|
|
1088
|
-
if (typeof v === "string") return v.slice(0, 2e3);
|
|
1089
|
-
if (v && typeof v === "object") {
|
|
1090
|
-
const e = v;
|
|
1091
|
-
if (typeof e.error === "string") return e.error.slice(0, 2e3);
|
|
1092
|
-
if (typeof e.message === "string") return e.message.slice(0, 2e3);
|
|
1093
|
-
}
|
|
1094
|
-
return void 0;
|
|
1095
|
-
}
|
|
1096
|
-
function describe(input) {
|
|
1097
|
-
if (!input || typeof input !== "object") return "";
|
|
1098
|
-
const i = input;
|
|
1099
|
-
const v = i.command ?? i.file_path ?? i.path ?? i.url ?? i.pattern;
|
|
1100
|
-
return typeof v === "string" ? ` ${v.slice(0, 120)}` : "";
|
|
1101
|
-
}
|
|
1102
|
-
var NAME_RANK = {
|
|
1103
|
-
user: "custom",
|
|
1104
|
-
auto: "harness",
|
|
1105
|
-
hook: "harness",
|
|
1106
|
-
peer: "harness"
|
|
1107
|
-
};
|
|
1108
|
-
function registryEntry(j) {
|
|
1109
|
-
if (!j || typeof j !== "object") return null;
|
|
1110
|
-
const r = j;
|
|
1111
|
-
const source = r.nameSource ? NAME_RANK[r.nameSource] : void 0;
|
|
1112
|
-
return {
|
|
1113
|
-
sessionId: r.sessionId ? String(r.sessionId) : null,
|
|
1114
|
-
name: source && typeof r.name === "string" && r.name.trim() ? r.name.trim() : null,
|
|
1115
|
-
name_source: source ?? null,
|
|
1116
|
-
bridge: typeof r.bridgeSessionId === "string" ? r.bridgeSessionId : null
|
|
1117
|
-
};
|
|
1118
|
-
}
|
|
1119
|
-
var liveCache = /* @__PURE__ */ new Map();
|
|
1120
|
-
function claudeLive(dir) {
|
|
1121
|
-
const hit = liveCache.get(dir);
|
|
1122
|
-
if (hit) return hit;
|
|
1123
|
-
const map = /* @__PURE__ */ new Map();
|
|
1124
|
-
let names = [];
|
|
1125
|
-
try {
|
|
1126
|
-
names = readdirSync3(path5.join(dir, "sessions")).filter((n) => n.endsWith(".json"));
|
|
1127
|
-
} catch {
|
|
1128
|
-
}
|
|
1129
|
-
for (const n of names) {
|
|
1130
|
-
try {
|
|
1131
|
-
const e = registryEntry(JSON.parse(readFileSync3(path5.join(dir, "sessions", n), "utf8")));
|
|
1132
|
-
if (e?.sessionId) map.set(e.sessionId, e);
|
|
1133
|
-
} catch {
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
liveCache.set(dir, map);
|
|
1137
|
-
return map;
|
|
1138
|
-
}
|
|
1139
|
-
function claudeLiveByPid(dir, pid) {
|
|
1140
|
-
if (!pid) return null;
|
|
1141
|
-
try {
|
|
1142
|
-
return registryEntry(JSON.parse(readFileSync3(path5.join(dir, "sessions", `${pid}.json`), "utf8")));
|
|
1143
|
-
} catch {
|
|
1144
|
-
return null;
|
|
1145
|
-
}
|
|
1146
|
-
}
|
|
1147
|
-
function claudePid(env = process.env) {
|
|
1148
|
-
try {
|
|
1149
|
-
const dirs = claudeDirs(env);
|
|
1150
|
-
const has = (pid2) => dirs.some((d) => existsSync3(path5.join(d, "sessions", `${pid2}.json`)));
|
|
1151
|
-
let pid = process.ppid;
|
|
1152
|
-
if (has(pid)) return pid;
|
|
1153
|
-
const parent = parentOf();
|
|
1154
|
-
for (let hop = 0; hop < 6 && pid > 1; hop++) {
|
|
1155
|
-
pid = parent(pid);
|
|
1156
|
-
if (pid > 1 && has(pid)) return pid;
|
|
1157
|
-
}
|
|
1158
|
-
} catch {
|
|
1159
|
-
}
|
|
1160
|
-
return null;
|
|
1161
|
-
}
|
|
1162
|
-
function parentOf() {
|
|
1163
|
-
if (process.platform === "linux") {
|
|
1164
|
-
return (pid) => {
|
|
1165
|
-
try {
|
|
1166
|
-
const st = readFileSync3(`/proc/${pid}/stat`, "utf8");
|
|
1167
|
-
return Number(st.slice(st.lastIndexOf(")") + 2).split(" ")[1]) || 0;
|
|
1168
|
-
} catch {
|
|
1169
|
-
return 0;
|
|
1170
|
-
}
|
|
1171
|
-
};
|
|
1172
|
-
}
|
|
1173
|
-
const table = /* @__PURE__ */ new Map();
|
|
1174
|
-
try {
|
|
1175
|
-
const out = execFileSync2("ps", ["-axo", "pid=,ppid="], {
|
|
1176
|
-
encoding: "utf8",
|
|
1177
|
-
timeout: 2e3,
|
|
1178
|
-
maxBuffer: 8 * 1024 * 1024,
|
|
1179
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
1180
|
-
});
|
|
1181
|
-
for (const l of out.split("\n")) {
|
|
1182
|
-
const m = /^\s*(\d+)\s+(\d+)/.exec(l);
|
|
1183
|
-
if (m) table.set(Number(m[1]), Number(m[2]));
|
|
1184
|
-
}
|
|
1185
|
-
} catch {
|
|
1186
|
-
}
|
|
1187
|
-
return (pid) => table.get(pid) || 0;
|
|
1188
|
-
}
|
|
1189
|
-
var EMAIL_RE = /^[^@\s]{1,64}@[^@\s]{1,189}$/;
|
|
1190
|
-
function claudeLogin(dir, home = homeDir()) {
|
|
1191
|
-
const files = [path5.join(dir, ".claude.json")];
|
|
1192
|
-
if (path5.resolve(dir) === path5.join(home, ".claude")) files.unshift(path5.join(home, ".claude.json"));
|
|
1193
|
-
for (const f of files) {
|
|
1456
|
+
function agPath(p) {
|
|
1457
|
+
if (typeof p !== "string" || !p) return null;
|
|
1458
|
+
let s = p;
|
|
1459
|
+
if (/^file:/i.test(s)) {
|
|
1194
1460
|
try {
|
|
1195
|
-
|
|
1196
|
-
if (typeof a?.accountUuid === "string" && a.accountUuid) {
|
|
1197
|
-
const email = typeof a.emailAddress === "string" && EMAIL_RE.test(a.emailAddress) ? a.emailAddress : void 0;
|
|
1198
|
-
return { id: a.accountUuid.slice(0, 128), ...email ? { email } : {} };
|
|
1199
|
-
}
|
|
1461
|
+
s = decodeURIComponent(new URL(s).pathname);
|
|
1200
1462
|
} catch {
|
|
1201
|
-
}
|
|
1202
|
-
}
|
|
1203
|
-
return null;
|
|
1204
|
-
}
|
|
1205
|
-
var accountCache = /* @__PURE__ */ new Map();
|
|
1206
|
-
function claudeAccount(dir) {
|
|
1207
|
-
const hit = accountCache.get(dir);
|
|
1208
|
-
if (hit !== void 0) return hit;
|
|
1209
|
-
const acct = claudeLogin(dir)?.id ?? null;
|
|
1210
|
-
accountCache.set(dir, acct);
|
|
1211
|
-
return acct;
|
|
1212
|
-
}
|
|
1213
|
-
var LINK_RE = /^https:\/\/claude\.ai\/code\/session_[A-Za-z0-9]+$/;
|
|
1214
|
-
function transcriptLink(records) {
|
|
1215
|
-
for (let i = records.length - 1; i >= 0; i--) {
|
|
1216
|
-
const j = records[i];
|
|
1217
|
-
if (j.type === "system" && j.subtype === "bridge_status" && typeof j.url === "string" && LINK_RE.test(j.url))
|
|
1218
|
-
return j.url;
|
|
1219
|
-
if (j.type === "bridge-session" && typeof j.bridgeSessionId === "string") {
|
|
1220
|
-
const m = /^(?:cse|session)_([A-Za-z0-9]+)$/.exec(j.bridgeSessionId);
|
|
1221
|
-
if (m) return `https://claude.ai/code/session_${m[1]}`;
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
return null;
|
|
1225
|
-
}
|
|
1226
|
-
function appTitle(text) {
|
|
1227
|
-
const t = String(text).replace(/\s+/g, " ").trim();
|
|
1228
|
-
return t.length > 50 ? `${t.slice(0, 50)}...` : t;
|
|
1229
|
-
}
|
|
1230
|
-
function askText(j) {
|
|
1231
|
-
if (!j || j.type !== "user" || j.isMeta || j.isSidechain) return null;
|
|
1232
|
-
const origin = j.origin;
|
|
1233
|
-
if (origin?.kind && origin.kind !== "human") return null;
|
|
1234
|
-
const c = j.message?.content;
|
|
1235
|
-
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();
|
|
1236
|
-
if (!text || isInjected(text)) return null;
|
|
1237
|
-
return text;
|
|
1238
|
-
}
|
|
1239
|
-
function firstAsk(records) {
|
|
1240
|
-
for (const j of records) {
|
|
1241
|
-
const t = askText(j);
|
|
1242
|
-
if (t) return appTitle(t);
|
|
1243
|
-
}
|
|
1244
|
-
return null;
|
|
1245
|
-
}
|
|
1246
|
-
function scanTitles(file, from, found) {
|
|
1247
|
-
const out = { custom: found.custom ?? null, ai: found.ai ?? null, upto: from };
|
|
1248
|
-
let size = 0;
|
|
1249
|
-
try {
|
|
1250
|
-
size = statSync3(file).size;
|
|
1251
|
-
} catch {
|
|
1252
|
-
return out;
|
|
1253
|
-
}
|
|
1254
|
-
if (size < from) {
|
|
1255
|
-
out.upto = 0;
|
|
1256
|
-
out.custom = out.ai = null;
|
|
1257
|
-
from = 0;
|
|
1258
|
-
}
|
|
1259
|
-
let pos = 0;
|
|
1260
|
-
scanLines(
|
|
1261
|
-
file,
|
|
1262
|
-
(l) => {
|
|
1263
|
-
pos += Buffer.byteLength(l, "utf8") + 1;
|
|
1264
|
-
if (pos <= from || l.indexOf('-title"') < 0) return null;
|
|
1265
|
-
try {
|
|
1266
|
-
const j = JSON.parse(l);
|
|
1267
|
-
if (j.type === "custom-title" && j.customTitle) out.custom = j.customTitle;
|
|
1268
|
-
if (j.type === "ai-title" && j.aiTitle) out.ai = j.aiTitle;
|
|
1269
|
-
} catch {
|
|
1270
|
-
}
|
|
1271
1463
|
return null;
|
|
1272
|
-
}
|
|
1273
|
-
|
|
1274
|
-
);
|
|
1275
|
-
|
|
1276
|
-
return
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
const
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
let custom = null;
|
|
1286
|
-
let ai = null;
|
|
1287
|
-
for (const j of H.concat(T)) {
|
|
1288
|
-
if (j.type === "custom-title" && typeof j.customTitle === "string") custom = j.customTitle;
|
|
1289
|
-
if (j.type === "ai-title" && typeof j.aiTitle === "string") ai = j.aiTitle;
|
|
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);
|
|
1469
|
+
}
|
|
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];
|
|
1473
|
+
try {
|
|
1474
|
+
const r = realpathSync.native(p);
|
|
1475
|
+
if (r !== p) out.push(r);
|
|
1476
|
+
} catch {
|
|
1290
1477
|
}
|
|
1291
|
-
|
|
1478
|
+
return out;
|
|
1479
|
+
}
|
|
1480
|
+
function agProjects(dir) {
|
|
1481
|
+
const out = [];
|
|
1482
|
+
let names = [];
|
|
1292
1483
|
try {
|
|
1293
|
-
|
|
1484
|
+
names = readdirSync3(dir).filter((n) => n.endsWith(".json"));
|
|
1294
1485
|
} catch {
|
|
1486
|
+
return out;
|
|
1295
1487
|
}
|
|
1296
|
-
|
|
1297
|
-
const
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
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 });
|
|
1304
1503
|
}
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
const
|
|
1309
|
-
const
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
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));
|
|
1313
1511
|
}
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
} catch {
|
|
1323
|
-
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;
|
|
1324
1520
|
}
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
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
|
+
}
|
|
1331
1540
|
}
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
}
|
|
1336
|
-
|
|
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;
|
|
1337
1557
|
out.title_source = "first-ask";
|
|
1558
|
+
break;
|
|
1338
1559
|
}
|
|
1339
|
-
const
|
|
1340
|
-
if (acct) out.account_id = acct;
|
|
1341
|
-
for (let i = T.length - 1; i >= 0; i--) {
|
|
1342
|
-
const m = T[i].message?.model;
|
|
1343
|
-
if (T[i]?.type === "assistant" && m && m !== "<synthetic>") {
|
|
1344
|
-
out.model = m;
|
|
1345
|
-
break;
|
|
1346
|
-
}
|
|
1347
|
-
}
|
|
1348
|
-
const meta = [...T].reverse().find((j) => j.version) ?? H.find((j) => j.version);
|
|
1349
|
-
if (meta?.version) out.harness_version = String(meta.version);
|
|
1350
|
-
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);
|
|
1351
1561
|
if (times.length) {
|
|
1352
1562
|
out.started_at = Math.min(...times);
|
|
1353
1563
|
out.last_at = Math.max(...times);
|
|
1354
1564
|
}
|
|
1355
|
-
const withCwd = H.find((j) => typeof j.cwd === "string");
|
|
1356
|
-
if (withCwd) {
|
|
1357
|
-
out.cwd = withCwd.cwd;
|
|
1358
|
-
if (typeof withCwd.gitBranch === "string" && withCwd.gitBranch !== "HEAD") out.branch = withCwd.gitBranch;
|
|
1359
|
-
}
|
|
1360
1565
|
return out;
|
|
1361
1566
|
}
|
|
1362
1567
|
|
|
1568
|
+
// ../core/src/control/verify.ts
|
|
1569
|
+
var GRANT_MAX_MS = 24 * 36e5;
|
|
1570
|
+
var SKEW_MS = 5 * 6e4;
|
|
1571
|
+
var MACHINE_MAX_AGE_MS = 24 * 36e5;
|
|
1572
|
+
var RECEIVER_MAX_AGE_MS = 10 * 6e4;
|
|
1573
|
+
var NONCE_TTL_MS = 25 * 36e5;
|
|
1574
|
+
var MODEL_RE = /^[A-Za-z0-9][A-Za-z0-9._:/@[\]-]*$/;
|
|
1575
|
+
var START_PERMISSIONS = ["read", "safe", "edits", "auto"];
|
|
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;
|
|
1586
|
+
var enc = new TextEncoder();
|
|
1587
|
+
var dec = new TextDecoder();
|
|
1588
|
+
|
|
1363
1589
|
// ../core/src/adapters/codex.ts
|
|
1364
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync4, writeFileSync as
|
|
1365
|
-
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";
|
|
1366
1592
|
|
|
1367
1593
|
// ../core/src/adapters/codex-config.ts
|
|
1368
|
-
var NOTIFY_LINE = /^[ \t]*notify[ \t]*=[ \t]*(\[.*\])[ \t]*(?:#.*)
|
|
1594
|
+
var NOTIFY_LINE = /^[ \t]*notify[ \t]*=[ \t]*(\[.*\])[ \t]*(?:#.*)?(?=\r?$)/m;
|
|
1369
1595
|
var NOTIFY_KEY = /^[ \t]*notify[ \t]*=/m;
|
|
1370
1596
|
var OURS2 = /(?:hook\.js|sessionpipe-hook)",\s*"codex",\s*"notify"/;
|
|
1371
1597
|
function scan(line, st) {
|
|
@@ -1455,10 +1681,11 @@ function dropLine(text, index, length) {
|
|
|
1455
1681
|
}
|
|
1456
1682
|
function insertTopLevel(text, line) {
|
|
1457
1683
|
const { head, tail } = split(text);
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
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}`;
|
|
1462
1689
|
}
|
|
1463
1690
|
function withNotify(text, line) {
|
|
1464
1691
|
const n = readNotify(text);
|
|
@@ -1507,10 +1734,10 @@ var CODEX_EVENTS = [
|
|
|
1507
1734
|
];
|
|
1508
1735
|
var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
1509
1736
|
var eventsFor2 = (lean) => lean ? CODEX_EVENTS.filter((e) => e !== "PreToolUse") : [...CODEX_EVENTS];
|
|
1510
|
-
var codexHome = (env) => env.CODEX_HOME ||
|
|
1511
|
-
var hooksFile2 = (env) =>
|
|
1512
|
-
var configFile2 = (env) =>
|
|
1513
|
-
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");
|
|
1514
1741
|
var validTool3 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
1515
1742
|
var endReason2 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
|
|
1516
1743
|
function installNotify(cmd, env) {
|
|
@@ -1524,8 +1751,8 @@ function installNotify(cmd, env) {
|
|
|
1524
1751
|
const line = `notify = ${JSON.stringify(argv)}`;
|
|
1525
1752
|
const n = readNotify(text);
|
|
1526
1753
|
const write = (next) => {
|
|
1527
|
-
mkdirSync3(
|
|
1528
|
-
|
|
1754
|
+
mkdirSync3(path7.dirname(file), { recursive: true });
|
|
1755
|
+
writeFileSync4(file, next);
|
|
1529
1756
|
};
|
|
1530
1757
|
if (n && !n.other) {
|
|
1531
1758
|
if (!n.misplaced && JSON.stringify(n.argv) === JSON.stringify(argv))
|
|
@@ -1570,12 +1797,12 @@ function uninstallNotify(env) {
|
|
|
1570
1797
|
const n = readNotify(text);
|
|
1571
1798
|
if (!n) return { file, changed: false };
|
|
1572
1799
|
if (!n.other) {
|
|
1573
|
-
|
|
1800
|
+
writeFileSync4(file, withoutNotify(text));
|
|
1574
1801
|
return { file, changed: true };
|
|
1575
1802
|
}
|
|
1576
1803
|
const prev = previousNotify(n.argv);
|
|
1577
1804
|
if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev))) {
|
|
1578
|
-
|
|
1805
|
+
writeFileSync4(file, replaceNotify(text, n, `notify = ${stripPreviousRaw(n.raw)}`));
|
|
1579
1806
|
return { file, changed: true };
|
|
1580
1807
|
}
|
|
1581
1808
|
return { file, changed: false };
|
|
@@ -1591,7 +1818,7 @@ function findRollout(threadId, env = process.env) {
|
|
|
1591
1818
|
return;
|
|
1592
1819
|
}
|
|
1593
1820
|
for (const n of names.sort().reverse()) {
|
|
1594
|
-
const p =
|
|
1821
|
+
const p = path7.join(dir, n);
|
|
1595
1822
|
if (n.endsWith(`-${threadId}.jsonl`)) {
|
|
1596
1823
|
best = p;
|
|
1597
1824
|
return;
|
|
@@ -1681,7 +1908,7 @@ var codex = {
|
|
|
1681
1908
|
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1682
1909
|
} catch {
|
|
1683
1910
|
}
|
|
1684
|
-
const id = s.session_id;
|
|
1911
|
+
const id = s.session_id ?? s.thread_id ?? s.conversation_id ?? s["thread-id"] ?? s["session-id"];
|
|
1685
1912
|
if (!id) return null;
|
|
1686
1913
|
if (s.transcript_path === null) return null;
|
|
1687
1914
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
@@ -1776,7 +2003,7 @@ var codex = {
|
|
|
1776
2003
|
backfill(sinceMs, env = process.env) {
|
|
1777
2004
|
const rows2 = [];
|
|
1778
2005
|
for (const f of recentFiles(sessionsDir(env), 3, sinceMs, (n) => /^rollout-.*\.jsonl$/.test(n))) {
|
|
1779
|
-
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));
|
|
1780
2007
|
if (!m?.[1]) continue;
|
|
1781
2008
|
if (codexSubagent(rolloutMeta(f))) continue;
|
|
1782
2009
|
const facts = codexFacts(f);
|
|
@@ -1785,12 +2012,40 @@ var codex = {
|
|
|
1785
2012
|
rows2.push({ session: { id: m[1], ...rest }, started_at, last_at });
|
|
1786
2013
|
}
|
|
1787
2014
|
return rows2;
|
|
1788
|
-
}
|
|
2015
|
+
},
|
|
2016
|
+
limits: (transcript) => codexLimits(transcript)
|
|
1789
2017
|
};
|
|
2018
|
+
var epochIso2 = (t) => typeof t === "number" && t > 0 && t < 1e11 ? new Date(t * 1e3).toISOString() : void 0;
|
|
1790
2019
|
function prune3(o) {
|
|
1791
2020
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
1792
2021
|
return o;
|
|
1793
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
|
+
}
|
|
1794
2049
|
function codexFacts(file) {
|
|
1795
2050
|
const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
|
|
1796
2051
|
let H = parseLines(head);
|
|
@@ -1835,7 +2090,7 @@ function codexFacts(file) {
|
|
|
1835
2090
|
// ../core/src/adapters/devin.ts
|
|
1836
2091
|
import { createHash as createHash3 } from "node:crypto";
|
|
1837
2092
|
import { existsSync as existsSync6, readlinkSync } from "node:fs";
|
|
1838
|
-
import
|
|
2093
|
+
import path8 from "node:path";
|
|
1839
2094
|
|
|
1840
2095
|
// ../core/src/readers/sqlite.ts
|
|
1841
2096
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
@@ -2019,11 +2274,11 @@ function foreignEvent(file) {
|
|
|
2019
2274
|
return Object.keys(hooks).find((k) => !ok.includes(k));
|
|
2020
2275
|
}
|
|
2021
2276
|
var homeOf = (env) => env.HOME || homeDir();
|
|
2022
|
-
var appData = (env) => env.APPDATA ||
|
|
2023
|
-
var devinConfigDir = (env = process.env) => process.platform === "win32" ?
|
|
2024
|
-
var configFile3 = (env) =>
|
|
2025
|
-
var devinDataDir = (env = process.env) => process.platform === "win32" ?
|
|
2026
|
-
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");
|
|
2027
2282
|
var SESSION_ID = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
2028
2283
|
var validTool4 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
2029
2284
|
var endReason3 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
|
|
@@ -2195,7 +2450,7 @@ var BACKFILL_SQL = `SELECT s.id, ${SESSION_FIELDS} FROM sessions s
|
|
|
2195
2450
|
function sessionFacts(row) {
|
|
2196
2451
|
const out = {};
|
|
2197
2452
|
if (typeof row.model === "string" && row.model) out.model = row.model;
|
|
2198
|
-
if (typeof row.working_directory === "string" &&
|
|
2453
|
+
if (typeof row.working_directory === "string" && path8.isAbsolute(row.working_directory))
|
|
2199
2454
|
out.cwd = row.working_directory;
|
|
2200
2455
|
const started = Number(row.created_at);
|
|
2201
2456
|
const last = Number(row.last_activity_at);
|
|
@@ -2240,8 +2495,8 @@ function readDevinChain(db, session, fromLine) {
|
|
|
2240
2495
|
}
|
|
2241
2496
|
function devinVersion(env = process.env) {
|
|
2242
2497
|
try {
|
|
2243
|
-
const target = readlinkSync(
|
|
2244
|
-
const name =
|
|
2498
|
+
const target = readlinkSync(path8.join(devinDataDir(env), "_versions", "current"));
|
|
2499
|
+
const name = path8.basename(target);
|
|
2245
2500
|
return /^[0-9][0-9A-Za-z.+-]{0,63}$/.test(name) ? name : void 0;
|
|
2246
2501
|
} catch {
|
|
2247
2502
|
return void 0;
|
|
@@ -2256,7 +2511,7 @@ function devinAccount(env = process.env) {
|
|
|
2256
2511
|
// ../core/src/adapters/gemini-cli.ts
|
|
2257
2512
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
2258
2513
|
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
|
|
2259
|
-
import
|
|
2514
|
+
import path9 from "node:path";
|
|
2260
2515
|
var NAME5 = "gemini-cli";
|
|
2261
2516
|
var GEMINI_EVENTS = [
|
|
2262
2517
|
"SessionStart",
|
|
@@ -2269,26 +2524,31 @@ var GEMINI_EVENTS = [
|
|
|
2269
2524
|
"SessionEnd"
|
|
2270
2525
|
];
|
|
2271
2526
|
var OPTS4 = { matcherFor: (e) => /Tool/.test(e) ? ".*" : void 0, timeout: 5e3 };
|
|
2272
|
-
var geminiDir = (env) => env.GEMINI_CLI_HOME ||
|
|
2273
|
-
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");
|
|
2274
2529
|
var validTool5 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
2275
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
|
+
}
|
|
2276
2544
|
function geminiHooksActive(settingsFile) {
|
|
2277
|
-
|
|
2278
|
-
try {
|
|
2279
|
-
tools = JSON.parse(readFileSync5(settingsFile, "utf8")).tools;
|
|
2280
|
-
} catch {
|
|
2281
|
-
}
|
|
2545
|
+
const tools = readJson(settingsFile).tools;
|
|
2282
2546
|
if (tools?.enableHooks === true) return { active: true };
|
|
2283
2547
|
if (tools?.enableHooks === false)
|
|
2284
2548
|
return { active: false, note: "inactive: tools.enableHooks is false in settings.json" };
|
|
2285
2549
|
let version = "";
|
|
2286
2550
|
try {
|
|
2287
|
-
version =
|
|
2288
|
-
encoding: "utf8",
|
|
2289
|
-
timeout: 4e3,
|
|
2290
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
2291
|
-
}).trim();
|
|
2551
|
+
version = geminiVersion();
|
|
2292
2552
|
} catch {
|
|
2293
2553
|
return {
|
|
2294
2554
|
active: true,
|
|
@@ -2306,7 +2566,7 @@ var geminiCli = {
|
|
|
2306
2566
|
name: NAME5,
|
|
2307
2567
|
events: GEMINI_EVENTS,
|
|
2308
2568
|
// ~/.gemini also belongs to Antigravity; Gemini CLI is here when its settings or its chat logs exist.
|
|
2309
|
-
detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(
|
|
2569
|
+
detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(path9.join(geminiDir(env), "tmp")),
|
|
2310
2570
|
configFiles: (env = process.env) => [settings(env)],
|
|
2311
2571
|
install: (cmd, env = process.env, opts = {}) => {
|
|
2312
2572
|
const r = installClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
|
|
@@ -2444,8 +2704,8 @@ var ADAPTERS = [claudeCode, codex, geminiCli, antigravity, devin];
|
|
|
2444
2704
|
var adapterByName = (name) => ADAPTERS.find((a) => a.name === name);
|
|
2445
2705
|
|
|
2446
2706
|
// ../core/src/config.ts
|
|
2447
|
-
import { chmodSync, existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as
|
|
2448
|
-
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";
|
|
2449
2709
|
var DEFAULT_CONFIG = { sinks: [], harnesses: {}, update_check: true, keep_days: 30 };
|
|
2450
2710
|
function readConfig(file = configFile()) {
|
|
2451
2711
|
try {
|
|
@@ -2461,8 +2721,8 @@ function readConfig(file = configFile()) {
|
|
|
2461
2721
|
}
|
|
2462
2722
|
}
|
|
2463
2723
|
function writeConfig(c, file = configFile()) {
|
|
2464
|
-
mkdirSync4(
|
|
2465
|
-
|
|
2724
|
+
mkdirSync4(path10.dirname(file), { recursive: true, mode: 448 });
|
|
2725
|
+
writeFileSync5(file, `${JSON.stringify(c, null, 2)}
|
|
2466
2726
|
`, { mode: 384 });
|
|
2467
2727
|
if (process.platform !== "win32" && existsSync8(file)) {
|
|
2468
2728
|
try {
|
|
@@ -2525,6 +2785,22 @@ function makeEvent(a, ctx) {
|
|
|
2525
2785
|
};
|
|
2526
2786
|
}
|
|
2527
2787
|
|
|
2788
|
+
// ../core/src/launched-by.ts
|
|
2789
|
+
var LAUNCHED_BY_ENV = "SESSIONPIPE_LAUNCHED_BY";
|
|
2790
|
+
var LAUNCHED_BY = /^[a-z][a-z0-9-]{0,39}:[A-Za-z0-9._:-]{1,128}$/;
|
|
2791
|
+
function launchedBy(env) {
|
|
2792
|
+
const v = env[LAUNCHED_BY_ENV]?.trim();
|
|
2793
|
+
return v && LAUNCHED_BY.test(v) ? v : void 0;
|
|
2794
|
+
}
|
|
2795
|
+
function readHook(adapter, input) {
|
|
2796
|
+
const r = adapter.fromHook(input);
|
|
2797
|
+
if (r && !r.session.parent_id) {
|
|
2798
|
+
const launcher = launchedBy(input.env);
|
|
2799
|
+
if (launcher && launcher !== `${adapter.name}:${r.session.id}`) r.session.launched_by = launcher;
|
|
2800
|
+
}
|
|
2801
|
+
return r;
|
|
2802
|
+
}
|
|
2803
|
+
|
|
2528
2804
|
// ../core/src/outbox.ts
|
|
2529
2805
|
import {
|
|
2530
2806
|
appendFileSync,
|
|
@@ -2536,11 +2812,11 @@ import {
|
|
|
2536
2812
|
readFileSync as readFileSync7,
|
|
2537
2813
|
readSync as readSync2,
|
|
2538
2814
|
renameSync,
|
|
2539
|
-
statSync as
|
|
2815
|
+
statSync as statSync5,
|
|
2540
2816
|
unlinkSync as unlinkSync3,
|
|
2541
|
-
writeFileSync as
|
|
2817
|
+
writeFileSync as writeFileSync6
|
|
2542
2818
|
} from "node:fs";
|
|
2543
|
-
import
|
|
2819
|
+
import path11 from "node:path";
|
|
2544
2820
|
var READ_CHUNK = 1024 * 1024;
|
|
2545
2821
|
var READ_MAX = 8 * 1024 * 1024;
|
|
2546
2822
|
var Outbox = class {
|
|
@@ -2549,13 +2825,13 @@ var Outbox = class {
|
|
|
2549
2825
|
}
|
|
2550
2826
|
root;
|
|
2551
2827
|
dir(harness) {
|
|
2552
|
-
return
|
|
2828
|
+
return path11.join(this.root, "outbox", safeId(harness));
|
|
2553
2829
|
}
|
|
2554
2830
|
file(ref) {
|
|
2555
|
-
return
|
|
2831
|
+
return path11.join(this.dir(ref.harness), `${safeId(ref.session)}.jsonl`);
|
|
2556
2832
|
}
|
|
2557
2833
|
seqFile(ref) {
|
|
2558
|
-
return
|
|
2834
|
+
return path11.join(this.dir(ref.harness), `${safeId(ref.session)}.seq`);
|
|
2559
2835
|
}
|
|
2560
2836
|
/** The next seq for a session, persisted (survives a hibernating machine and a
|
|
2561
2837
|
* restart; an in-memory counter would restart at 0 and collide). */
|
|
@@ -2568,8 +2844,8 @@ var Outbox = class {
|
|
|
2568
2844
|
} catch {
|
|
2569
2845
|
}
|
|
2570
2846
|
if (n < 0) n = this.lastSeqInFile(ref) + 1;
|
|
2571
|
-
mkdirSync5(
|
|
2572
|
-
|
|
2847
|
+
mkdirSync5(path11.dirname(f), { recursive: true, mode: 448 });
|
|
2848
|
+
writeFileSync6(f, String(n + 1), { mode: 384 });
|
|
2573
2849
|
return n;
|
|
2574
2850
|
}
|
|
2575
2851
|
lastSeqInFile(ref) {
|
|
@@ -2594,10 +2870,10 @@ var Outbox = class {
|
|
|
2594
2870
|
/** Every session file, newest first. */
|
|
2595
2871
|
sessions() {
|
|
2596
2872
|
const out = [];
|
|
2597
|
-
const base =
|
|
2873
|
+
const base = path11.join(this.root, "outbox");
|
|
2598
2874
|
if (!existsSync9(base)) return [];
|
|
2599
2875
|
for (const h of readdirSync5(base)) {
|
|
2600
|
-
const d =
|
|
2876
|
+
const d = path11.join(base, h);
|
|
2601
2877
|
let names = [];
|
|
2602
2878
|
try {
|
|
2603
2879
|
names = readdirSync5(d).filter((n) => n.endsWith(".jsonl"));
|
|
@@ -2606,7 +2882,7 @@ var Outbox = class {
|
|
|
2606
2882
|
}
|
|
2607
2883
|
for (const n of names) {
|
|
2608
2884
|
try {
|
|
2609
|
-
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 });
|
|
2610
2886
|
} catch {
|
|
2611
2887
|
}
|
|
2612
2888
|
}
|
|
@@ -2622,7 +2898,7 @@ var Outbox = class {
|
|
|
2622
2898
|
read(ref, from, maxEvents = 500) {
|
|
2623
2899
|
const f = this.file(ref);
|
|
2624
2900
|
if (!existsSync9(f)) return { events: [], offset: from };
|
|
2625
|
-
const size =
|
|
2901
|
+
const size = statSync5(f).size;
|
|
2626
2902
|
if (from >= size) return { events: [], offset: from };
|
|
2627
2903
|
const fd = openSync2(f, "r");
|
|
2628
2904
|
try {
|
|
@@ -2664,7 +2940,7 @@ var Outbox = class {
|
|
|
2664
2940
|
for (const ref of this.sessions()) {
|
|
2665
2941
|
const f = this.file(ref);
|
|
2666
2942
|
try {
|
|
2667
|
-
if (
|
|
2943
|
+
if (statSync5(f).mtimeMs < cutoff) {
|
|
2668
2944
|
unlinkSync3(f);
|
|
2669
2945
|
try {
|
|
2670
2946
|
unlinkSync3(this.seqFile(ref));
|
|
@@ -2694,7 +2970,7 @@ var Cursors = class {
|
|
|
2694
2970
|
root;
|
|
2695
2971
|
sink;
|
|
2696
2972
|
file() {
|
|
2697
|
-
return
|
|
2973
|
+
return path11.join(this.root, "cursors", `${safeId(this.sink)}.json`);
|
|
2698
2974
|
}
|
|
2699
2975
|
read() {
|
|
2700
2976
|
try {
|
|
@@ -2705,9 +2981,9 @@ var Cursors = class {
|
|
|
2705
2981
|
}
|
|
2706
2982
|
write(c) {
|
|
2707
2983
|
const f = this.file();
|
|
2708
|
-
mkdirSync5(
|
|
2984
|
+
mkdirSync5(path11.dirname(f), { recursive: true, mode: 448 });
|
|
2709
2985
|
const tmp = `${f}.${process.pid}.tmp`;
|
|
2710
|
-
|
|
2986
|
+
writeFileSync6(tmp, JSON.stringify(c), { mode: 384 });
|
|
2711
2987
|
renameSync(tmp, f);
|
|
2712
2988
|
}
|
|
2713
2989
|
key(ref) {
|
|
@@ -2718,16 +2994,16 @@ function takeLock(root, ref, staleMs = 5 * 6e4) {
|
|
|
2718
2994
|
return takeNamedLock(root, `${safeId(ref.harness)}-${safeId(ref.session)}`, staleMs);
|
|
2719
2995
|
}
|
|
2720
2996
|
function takeNamedLock(root, name, staleMs = 5 * 6e4) {
|
|
2721
|
-
const dir =
|
|
2997
|
+
const dir = path11.join(root, "locks");
|
|
2722
2998
|
mkdirSync5(dir, { recursive: true, mode: 448 });
|
|
2723
|
-
const f =
|
|
2999
|
+
const f = path11.join(dir, `${name}.lock`);
|
|
2724
3000
|
try {
|
|
2725
|
-
if (Date.now() -
|
|
3001
|
+
if (Date.now() - statSync5(f).mtimeMs > staleMs || !processAlive(Number(readFileSync7(f, "utf8")))) unlinkSync3(f);
|
|
2726
3002
|
else return null;
|
|
2727
3003
|
} catch {
|
|
2728
3004
|
}
|
|
2729
3005
|
try {
|
|
2730
|
-
|
|
3006
|
+
writeFileSync6(f, String(process.pid), { flag: "wx" });
|
|
2731
3007
|
} catch {
|
|
2732
3008
|
return null;
|
|
2733
3009
|
}
|
|
@@ -2959,6 +3235,7 @@ var TYPE_TIER = {
|
|
|
2959
3235
|
"subagent.started": 1,
|
|
2960
3236
|
"subagent.ended": 1,
|
|
2961
3237
|
"context.compacted": 1,
|
|
3238
|
+
"limits.observed": 1,
|
|
2962
3239
|
native: 3
|
|
2963
3240
|
};
|
|
2964
3241
|
var DATA_ALLOW = {
|
|
@@ -2967,8 +3244,8 @@ var DATA_ALLOW = {
|
|
|
2967
3244
|
"session.ended": { 0: ["reason"] },
|
|
2968
3245
|
"session.backfill": { 0: ["started_at", "last_at", "turns"] },
|
|
2969
3246
|
"session.forgotten": { 0: [] },
|
|
2970
|
-
"turn.started": { 0: ["turn_id"], 1: ["prompt_chars"] },
|
|
2971
|
-
"turn.ended": { 0: ["turn_id", "reason", "ms"], 1: ["error"] },
|
|
3247
|
+
"turn.started": { 0: ["turn_id", "submitted_by", "injected"], 1: ["prompt_chars"] },
|
|
3248
|
+
"turn.ended": { 0: ["turn_id", "reason", "ms", "background"], 1: ["error"] },
|
|
2972
3249
|
"turn.transcript": { 2: ["turn", "user", "assistant", "at"] },
|
|
2973
3250
|
"turn.progress": { 2: ["turn", "user", "assistant", "at"] },
|
|
2974
3251
|
"tool.started": { 1: ["tool", "call_id", "turn_id", "agent_id"], 3: ["input"] },
|
|
@@ -2978,7 +3255,8 @@ var DATA_ALLOW = {
|
|
|
2978
3255
|
"attention.cleared": { 0: ["attention_id", "how"] },
|
|
2979
3256
|
"subagent.started": { 1: ["agent_id", "agent_type"] },
|
|
2980
3257
|
"subagent.ended": { 1: ["agent_id", "agent_type"] },
|
|
2981
|
-
"context.compacted": { 1: ["trigger"] }
|
|
3258
|
+
"context.compacted": { 1: ["trigger"] },
|
|
3259
|
+
"limits.observed": { 1: ["windows", "plan", "account_id"] }
|
|
2982
3260
|
};
|
|
2983
3261
|
var SESSION_ALLOW = [
|
|
2984
3262
|
"id",
|
|
@@ -2992,7 +3270,9 @@ var SESSION_ALLOW = [
|
|
|
2992
3270
|
"title_source",
|
|
2993
3271
|
"url",
|
|
2994
3272
|
"account_id",
|
|
2995
|
-
"
|
|
3273
|
+
"attended",
|
|
3274
|
+
"parent_id",
|
|
3275
|
+
"launched_by"
|
|
2996
3276
|
];
|
|
2997
3277
|
var MAX_SIDE = 2e4;
|
|
2998
3278
|
var MAX_IO = 64 * 1024;
|
|
@@ -3041,7 +3321,9 @@ function filterEvent(event, tier, pii, opts = {}) {
|
|
|
3041
3321
|
const s = reducePiiDeep(out.session, opts.home);
|
|
3042
3322
|
if (typeof s.machine === "string") s.machine = hashMachine(s.machine);
|
|
3043
3323
|
delete s.account_id;
|
|
3044
|
-
|
|
3324
|
+
const d = reducePiiDeep(out.data, opts.home);
|
|
3325
|
+
delete d.account_id;
|
|
3326
|
+
out = { ...out, session: s, data: d };
|
|
3045
3327
|
rulesets.push(RULESET_PII);
|
|
3046
3328
|
}
|
|
3047
3329
|
out.privacy = { rulesets, pii };
|
|
@@ -3056,7 +3338,7 @@ function shortId(harness, id) {
|
|
|
3056
3338
|
|
|
3057
3339
|
// ../core/src/sinks/file.ts
|
|
3058
3340
|
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync6 } from "node:fs";
|
|
3059
|
-
import
|
|
3341
|
+
import path12 from "node:path";
|
|
3060
3342
|
var FileSink = class {
|
|
3061
3343
|
constructor(name, file, tier, pii) {
|
|
3062
3344
|
this.name = name;
|
|
@@ -3070,7 +3352,7 @@ var FileSink = class {
|
|
|
3070
3352
|
pii;
|
|
3071
3353
|
async send(events) {
|
|
3072
3354
|
try {
|
|
3073
|
-
mkdirSync6(
|
|
3355
|
+
mkdirSync6(path12.dirname(this.file), { recursive: true });
|
|
3074
3356
|
appendFileSync2(this.file, `${events.map((e) => JSON.stringify(e)).join("\n")}
|
|
3075
3357
|
`);
|
|
3076
3358
|
return { ok: true, acknowledged: events.length, accepted: events.length, duplicates: 0, rejected: [] };
|
|
@@ -3104,9 +3386,52 @@ var StdoutSink = class {
|
|
|
3104
3386
|
};
|
|
3105
3387
|
|
|
3106
3388
|
// src/control/aliases.ts
|
|
3107
|
-
import { chmodSync as chmodSync2, mkdirSync as mkdirSync7, readFileSync as readFileSync8, renameSync as renameSync2, writeFileSync as
|
|
3108
|
-
import
|
|
3109
|
-
|
|
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";
|
|
3391
|
+
|
|
3392
|
+
// src/secrets.ts
|
|
3393
|
+
import { spawnSync } from "node:child_process";
|
|
3394
|
+
import { createHash as createHash6, randomBytes as randomBytes2 } from "node:crypto";
|
|
3395
|
+
var SERVICE = "sessionpipe";
|
|
3396
|
+
var defaultRunner = (cmd, args, input) => {
|
|
3397
|
+
const r = spawnSync(cmd, args, {
|
|
3398
|
+
input: input ?? "",
|
|
3399
|
+
encoding: "utf8",
|
|
3400
|
+
timeout: 8e3,
|
|
3401
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
3402
|
+
});
|
|
3403
|
+
return { status: r.error ? null : r.status, stdout: r.stdout ?? "" };
|
|
3404
|
+
};
|
|
3405
|
+
var sinkAccount = (name, what = "token") => {
|
|
3406
|
+
const scope = process.env.SESSIONPIPE_CONFIG ? `@${createHash6("sha256").update(configFile()).digest("hex").slice(0, 8)}` : "";
|
|
3407
|
+
return `sink:${name}:${what}${scope}`;
|
|
3408
|
+
};
|
|
3409
|
+
function secretGet(store, account, run = defaultRunner) {
|
|
3410
|
+
const r = store === "keychain" ? run("security", ["find-generic-password", "-s", SERVICE, "-a", account, "-w"]) : run("secret-tool", ["lookup", "service", SERVICE, "account", account]);
|
|
3411
|
+
if (r.status !== 0) return null;
|
|
3412
|
+
const v = r.stdout.replace(/\r?\n$/, "");
|
|
3413
|
+
return v || null;
|
|
3414
|
+
}
|
|
3415
|
+
function sinkWithSecrets(s, run = defaultRunner) {
|
|
3416
|
+
if (!s.token_in) return s;
|
|
3417
|
+
const token = secretGet(s.token_in, sinkAccount(s.name), run);
|
|
3418
|
+
if (!token) return null;
|
|
3419
|
+
const secret = secretGet(s.token_in, sinkAccount(s.name, "secret"), run);
|
|
3420
|
+
return { ...s, token, ...secret ? { secret } : {} };
|
|
3421
|
+
}
|
|
3422
|
+
|
|
3423
|
+
// src/control/store.ts
|
|
3424
|
+
function startOptionsOf(v) {
|
|
3425
|
+
const o = v;
|
|
3426
|
+
if (!o || typeof o !== "object" || levelRank(o.permission) < 0 && o.permission !== "inherit") return null;
|
|
3427
|
+
return {
|
|
3428
|
+
permission: o.permission,
|
|
3429
|
+
...typeof o.model === "string" && MODEL_RE.test(o.model) ? { model: o.model } : {}
|
|
3430
|
+
};
|
|
3431
|
+
}
|
|
3432
|
+
|
|
3433
|
+
// src/control/aliases.ts
|
|
3434
|
+
var aliasFile = (controlDir) => path13.join(controlDir, "aliases.json");
|
|
3110
3435
|
function readAliases(file) {
|
|
3111
3436
|
try {
|
|
3112
3437
|
const j = JSON.parse(readFileSync8(file, "utf8"));
|
|
@@ -3115,7 +3440,7 @@ function readAliases(file) {
|
|
|
3115
3440
|
for (const [k, v] of Object.entries(j)) {
|
|
3116
3441
|
const a = v;
|
|
3117
3442
|
if (a && typeof a.id === "string" && a.id && typeof a.cwd === "string")
|
|
3118
|
-
out[k] = { id: a.id, cwd: a.cwd, at: typeof a.at === "string" ? a.at : "" };
|
|
3443
|
+
out[k] = { id: a.id, cwd: a.cwd, at: typeof a.at === "string" ? a.at : "", ...startOptionsOf(a) };
|
|
3119
3444
|
}
|
|
3120
3445
|
return out;
|
|
3121
3446
|
} catch {
|
|
@@ -3131,11 +3456,11 @@ function receiverIdOf(all, harness, id) {
|
|
|
3131
3456
|
// src/control/local.ts
|
|
3132
3457
|
import net from "node:net";
|
|
3133
3458
|
import os2 from "node:os";
|
|
3134
|
-
import
|
|
3459
|
+
import path14 from "node:path";
|
|
3135
3460
|
function socketPath(state, env = process.env) {
|
|
3136
3461
|
if (env.SESSIONPIPE_SOCKET) return env.SESSIONPIPE_SOCKET;
|
|
3137
3462
|
if (process.platform === "win32") return `\\\\.\\pipe\\sessionpipe-control-${os2.userInfo().username}`;
|
|
3138
|
-
return
|
|
3463
|
+
return path14.join(state, "control.sock");
|
|
3139
3464
|
}
|
|
3140
3465
|
function ask(sock, req, timeoutMs) {
|
|
3141
3466
|
return new Promise((resolve) => {
|
|
@@ -3167,37 +3492,6 @@ function ask(sock, req, timeoutMs) {
|
|
|
3167
3492
|
});
|
|
3168
3493
|
}
|
|
3169
3494
|
|
|
3170
|
-
// src/secrets.ts
|
|
3171
|
-
import { spawnSync } from "node:child_process";
|
|
3172
|
-
import { createHash as createHash6, randomBytes as randomBytes2 } from "node:crypto";
|
|
3173
|
-
var SERVICE = "sessionpipe";
|
|
3174
|
-
var defaultRunner = (cmd, args, input) => {
|
|
3175
|
-
const r = spawnSync(cmd, args, {
|
|
3176
|
-
input: input ?? "",
|
|
3177
|
-
encoding: "utf8",
|
|
3178
|
-
timeout: 8e3,
|
|
3179
|
-
stdio: ["pipe", "pipe", "ignore"]
|
|
3180
|
-
});
|
|
3181
|
-
return { status: r.error ? null : r.status, stdout: r.stdout ?? "" };
|
|
3182
|
-
};
|
|
3183
|
-
var sinkAccount = (name, what = "token") => {
|
|
3184
|
-
const scope = process.env.SESSIONPIPE_CONFIG ? `@${createHash6("sha256").update(configFile()).digest("hex").slice(0, 8)}` : "";
|
|
3185
|
-
return `sink:${name}:${what}${scope}`;
|
|
3186
|
-
};
|
|
3187
|
-
function secretGet(store, account, run = defaultRunner) {
|
|
3188
|
-
const r = store === "keychain" ? run("security", ["find-generic-password", "-s", SERVICE, "-a", account, "-w"]) : run("secret-tool", ["lookup", "service", SERVICE, "account", account]);
|
|
3189
|
-
if (r.status !== 0) return null;
|
|
3190
|
-
const v = r.stdout.replace(/\r?\n$/, "");
|
|
3191
|
-
return v || null;
|
|
3192
|
-
}
|
|
3193
|
-
function sinkWithSecrets(s, run = defaultRunner) {
|
|
3194
|
-
if (!s.token_in) return s;
|
|
3195
|
-
const token = secretGet(s.token_in, sinkAccount(s.name), run);
|
|
3196
|
-
if (!token) return null;
|
|
3197
|
-
const secret = secretGet(s.token_in, sinkAccount(s.name, "secret"), run);
|
|
3198
|
-
return { ...s, token, ...secret ? { secret } : {} };
|
|
3199
|
-
}
|
|
3200
|
-
|
|
3201
3495
|
// src/run.ts
|
|
3202
3496
|
async function tellDaemon(state, job, session, cwd, transcript) {
|
|
3203
3497
|
const sock = socketPath(state);
|
|
@@ -3217,8 +3511,8 @@ async function tellDaemon(state, job, session, cwd, transcript) {
|
|
|
3217
3511
|
function enqueueJob(state, job) {
|
|
3218
3512
|
const jobs = jobsDir(state);
|
|
3219
3513
|
mkdirSync8(jobs, { recursive: true, mode: 448 });
|
|
3220
|
-
const f =
|
|
3221
|
-
|
|
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 });
|
|
3222
3516
|
return f;
|
|
3223
3517
|
}
|
|
3224
3518
|
function releaseJob(claimed) {
|
|
@@ -3231,9 +3525,9 @@ function releaseJob(claimed) {
|
|
|
3231
3525
|
function log(state, line) {
|
|
3232
3526
|
try {
|
|
3233
3527
|
mkdirSync8(state, { recursive: true, mode: 448 });
|
|
3234
|
-
const f =
|
|
3528
|
+
const f = path15.join(state, "log");
|
|
3235
3529
|
try {
|
|
3236
|
-
if (
|
|
3530
|
+
if (statSync6(f).size > 1024 * 1024) renameSync3(f, `${f}.1`);
|
|
3237
3531
|
} catch {
|
|
3238
3532
|
}
|
|
3239
3533
|
appendFileSync3(f, `${(/* @__PURE__ */ new Date()).toISOString()} ${line}
|
|
@@ -3242,7 +3536,7 @@ function log(state, line) {
|
|
|
3242
3536
|
}
|
|
3243
3537
|
}
|
|
3244
3538
|
function factsState(state) {
|
|
3245
|
-
const file = (h, s) =>
|
|
3539
|
+
const file = (h, s) => path15.join(state, "facts", safeId(h), `${safeId(s)}.json`);
|
|
3246
3540
|
return {
|
|
3247
3541
|
get(h, s) {
|
|
3248
3542
|
try {
|
|
@@ -3255,8 +3549,8 @@ function factsState(state) {
|
|
|
3255
3549
|
const cur = this.get(h, s);
|
|
3256
3550
|
const next = { ...cur, ...patch };
|
|
3257
3551
|
if (JSON.stringify(next) === JSON.stringify(cur)) return;
|
|
3258
|
-
mkdirSync8(
|
|
3259
|
-
|
|
3552
|
+
mkdirSync8(path15.dirname(file(h, s)), { recursive: true, mode: 448 });
|
|
3553
|
+
writeFileSync8(file(h, s), JSON.stringify(next), { mode: 384 });
|
|
3260
3554
|
}
|
|
3261
3555
|
};
|
|
3262
3556
|
}
|
|
@@ -3298,6 +3592,12 @@ function openTurn(read, o) {
|
|
|
3298
3592
|
};
|
|
3299
3593
|
}
|
|
3300
3594
|
var MAX_TURN_SIDE = 2e4;
|
|
3595
|
+
var JOB_OWN_ENV = [
|
|
3596
|
+
"CLAUDE_CODE_SESSION_ATTENDED",
|
|
3597
|
+
"CLAUDE_CODE_HOST_SESSION_ID",
|
|
3598
|
+
"SESSIONPIPE_CONTROL_JOB",
|
|
3599
|
+
"SESSIONPIPE_LAUNCHED_BY"
|
|
3600
|
+
];
|
|
3301
3601
|
async function runJob(job, opts = {}) {
|
|
3302
3602
|
const state = opts.state ?? stateDir();
|
|
3303
3603
|
const cfg = readConfig();
|
|
@@ -3306,12 +3606,23 @@ async function runJob(job, opts = {}) {
|
|
|
3306
3606
|
log(state, `${job.harness} ${job.event}: no adapter for harness "${job.harness}"; event dropped`);
|
|
3307
3607
|
return { events: [], delivered: {} };
|
|
3308
3608
|
}
|
|
3309
|
-
const
|
|
3609
|
+
const env = { ...process.env };
|
|
3610
|
+
for (const k of JOB_OWN_ENV) delete env[k];
|
|
3611
|
+
const r = readHook(adapter, { argv: job.argv, stdin: job.stdin, env: { ...env, ...job.env }, cwd: job.cwd });
|
|
3310
3612
|
if (!r || !r.session.id) {
|
|
3311
|
-
|
|
3613
|
+
let keys = [];
|
|
3614
|
+
try {
|
|
3615
|
+
const payload = JSON.parse(job.argv.find((a) => a.startsWith("{")) ?? job.stdin);
|
|
3616
|
+
if (payload && typeof payload === "object" && !Array.isArray(payload)) keys = Object.keys(payload);
|
|
3617
|
+
} catch {
|
|
3618
|
+
}
|
|
3619
|
+
log(
|
|
3620
|
+
state,
|
|
3621
|
+
`${job.harness} ${job.event}: the ${job.harness} adapter did not claim this payload; event dropped; keys: ${keys.join(", ") || "(none)"}`
|
|
3622
|
+
);
|
|
3312
3623
|
return { events: [], delivered: {} };
|
|
3313
3624
|
}
|
|
3314
|
-
const id = receiverIdOf(readAliases(aliasFile(
|
|
3625
|
+
const id = receiverIdOf(readAliases(aliasFile(path15.join(state, "control"))), job.harness, r.session.id) ?? r.session.id;
|
|
3315
3626
|
const claudeEnd = job.harness === "claude-code" && r.events.some((e) => e.type === "session.ended");
|
|
3316
3627
|
if (!claudeEnd) await tellDaemon(state, job, id, r.session.cwd, r.transcript);
|
|
3317
3628
|
const outbox = new Outbox(state);
|
|
@@ -3340,12 +3651,15 @@ async function runJob(job, opts = {}) {
|
|
|
3340
3651
|
const heavy = !/^(PreToolUse|PostToolUse|PostToolUseFailure|BeforeTool|AfterTool|PostInvocation)$/.test(job.event);
|
|
3341
3652
|
let learned = {};
|
|
3342
3653
|
if (heavy) {
|
|
3343
|
-
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));
|
|
3344
3658
|
const { started_at: _s, last_at: _l, first_ask: _fa, harness_version, ...rest } = f;
|
|
3345
3659
|
learned = rest;
|
|
3346
3660
|
if (harness_version) facts.save(job.harness, id, { harness_version });
|
|
3347
3661
|
facts.save(job.harness, id, { session: { ...mem.session, ...learned } });
|
|
3348
|
-
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))
|
|
3349
3663
|
facts.save(job.harness, id, { cwd: r.session.cwd });
|
|
3350
3664
|
}
|
|
3351
3665
|
const known = mem.session ?? {};
|
|
@@ -3360,7 +3674,8 @@ async function runJob(job, opts = {}) {
|
|
|
3360
3674
|
seq: 0,
|
|
3361
3675
|
machine: machineName(cfg, os3.hostname()),
|
|
3362
3676
|
...cwdRaw ? { cwd: tilde(cwdRaw) } : {},
|
|
3363
|
-
...r.session.model ? { model: r.session.model } : {}
|
|
3677
|
+
...r.session.model ? { model: r.session.model } : {},
|
|
3678
|
+
...r.session.launched_by ? { launched_by: r.session.launched_by } : {}
|
|
3364
3679
|
};
|
|
3365
3680
|
const version = facts.get(job.harness, id).harness_version ?? void 0;
|
|
3366
3681
|
for (const a of r.events) {
|
|
@@ -3426,6 +3741,23 @@ async function runJob(job, opts = {}) {
|
|
|
3426
3741
|
}
|
|
3427
3742
|
if (read.turns.length) facts.save(job.harness, id, { transcript_line: read.line, transcript_turns: turnNo });
|
|
3428
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
|
+
}
|
|
3429
3761
|
outbox.append(
|
|
3430
3762
|
ref,
|
|
3431
3763
|
events.map((e) => ({ ...e, session: redactDeep(e.session), data: redactDeep(e.data) }))
|
|
@@ -3443,11 +3775,11 @@ async function runJob(job, opts = {}) {
|
|
|
3443
3775
|
return { events, delivered };
|
|
3444
3776
|
}
|
|
3445
3777
|
async function drain(state, sinkConfigs, outbox, first) {
|
|
3446
|
-
const more =
|
|
3778
|
+
const more = path15.join(state, "locks", "flush.more");
|
|
3447
3779
|
let release = takeNamedLock(state, "flush");
|
|
3448
3780
|
if (!release) {
|
|
3449
3781
|
try {
|
|
3450
|
-
|
|
3782
|
+
writeFileSync8(more, "", { mode: 384 });
|
|
3451
3783
|
} catch {
|
|
3452
3784
|
}
|
|
3453
3785
|
release = takeNamedLock(state, "flush");
|
|
@@ -3471,7 +3803,7 @@ async function drain(state, sinkConfigs, outbox, first) {
|
|
|
3471
3803
|
}
|
|
3472
3804
|
}
|
|
3473
3805
|
var BACKOFF_MS = [2e3, 1e4, 6e4, 5 * 6e4, 30 * 6e4];
|
|
3474
|
-
var backoffFile = (state, sink) =>
|
|
3806
|
+
var backoffFile = (state, sink) => path15.join(state, "backoff", `${safeId(sink)}.json`);
|
|
3475
3807
|
function readBackoff(state, sink) {
|
|
3476
3808
|
try {
|
|
3477
3809
|
const b = JSON.parse(readFileSync9(backoffFile(state, sink), "utf8"));
|
|
@@ -3487,8 +3819,8 @@ function noteFailure(state, sink, res, prev) {
|
|
|
3487
3819
|
const b = { until: Date.now() + wait, failures, why: `${res.status ?? ""} ${res.error ?? ""}`.trim() };
|
|
3488
3820
|
const f = backoffFile(state, sink);
|
|
3489
3821
|
try {
|
|
3490
|
-
mkdirSync8(
|
|
3491
|
-
|
|
3822
|
+
mkdirSync8(path15.dirname(f), { recursive: true, mode: 448 });
|
|
3823
|
+
writeFileSync8(f, JSON.stringify(b), { mode: 384 });
|
|
3492
3824
|
} catch {
|
|
3493
3825
|
}
|
|
3494
3826
|
return b;
|
|
@@ -3620,7 +3952,7 @@ function applySinkVerdict(name, res) {
|
|
|
3620
3952
|
} catch {
|
|
3621
3953
|
}
|
|
3622
3954
|
}
|
|
3623
|
-
var jobsDir = (state) =>
|
|
3955
|
+
var jobsDir = (state) => path15.join(state, "jobs");
|
|
3624
3956
|
export {
|
|
3625
3957
|
HttpSink,
|
|
3626
3958
|
buildSinks,
|