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/worker.js
CHANGED
|
@@ -1,24 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// sessionpipe 0.
|
|
2
|
+
// sessionpipe 0.11.0 · Apache-2.0 · https://sessionpipe.org
|
|
3
3
|
|
|
4
4
|
// src/worker.ts
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
6
|
import { realpathSync as realpathSync2 } from "node:fs";
|
|
7
7
|
import { pathToFileURL } from "node:url";
|
|
8
8
|
|
|
9
|
-
// ../core/src/adapters/antigravity.ts
|
|
10
|
-
import {
|
|
11
|
-
existsSync as existsSync2,
|
|
12
|
-
mkdirSync as mkdirSync2,
|
|
13
|
-
readdirSync as readdirSync2,
|
|
14
|
-
readFileSync as readFileSync2,
|
|
15
|
-
realpathSync,
|
|
16
|
-
statSync as statSync2,
|
|
17
|
-
unlinkSync as unlinkSync2,
|
|
18
|
-
writeFileSync as writeFileSync2
|
|
19
|
-
} from "node:fs";
|
|
20
|
-
import path4 from "node:path";
|
|
21
|
-
|
|
22
9
|
// ../core/src/paths.ts
|
|
23
10
|
import os from "node:os";
|
|
24
11
|
import path from "node:path";
|
|
@@ -42,6 +29,13 @@ function tilde(p, home = homeDir()) {
|
|
|
42
29
|
}
|
|
43
30
|
var safeId = (s) => String(s).replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 160);
|
|
44
31
|
|
|
32
|
+
// ../core/src/adapters/claude-code.ts
|
|
33
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
34
|
+
import { createHash } from "node:crypto";
|
|
35
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
36
|
+
import { userInfo } from "node:os";
|
|
37
|
+
import path4 from "node:path";
|
|
38
|
+
|
|
45
39
|
// ../core/src/readers/files.ts
|
|
46
40
|
import { closeSync, fstatSync, openSync, readdirSync, readSync, statSync } from "node:fs";
|
|
47
41
|
import path2 from "node:path";
|
|
@@ -132,6 +126,38 @@ function recentFiles(root, depth, sinceMs, match) {
|
|
|
132
126
|
return out;
|
|
133
127
|
}
|
|
134
128
|
|
|
129
|
+
// ../core/src/readers/git.ts
|
|
130
|
+
import { execFileSync } from "node:child_process";
|
|
131
|
+
var cache = /* @__PURE__ */ new Map();
|
|
132
|
+
function gitFacts(cwd) {
|
|
133
|
+
if (!cwd) return {};
|
|
134
|
+
const hit = cache.get(cwd);
|
|
135
|
+
if (hit) return hit;
|
|
136
|
+
const run = (args) => {
|
|
137
|
+
try {
|
|
138
|
+
return execFileSync("git", ["-C", cwd, ...args], {
|
|
139
|
+
encoding: "utf8",
|
|
140
|
+
timeout: 1500,
|
|
141
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
142
|
+
}).trim();
|
|
143
|
+
} catch {
|
|
144
|
+
return "";
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
const out = {};
|
|
148
|
+
const repo = run(["remote", "get-url", "origin"]);
|
|
149
|
+
if (repo) out.repo = stripCredentials(repo);
|
|
150
|
+
const branch = run(["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
151
|
+
if (branch && branch !== "HEAD") out.branch = branch;
|
|
152
|
+
cache.set(cwd, out);
|
|
153
|
+
return out;
|
|
154
|
+
}
|
|
155
|
+
function stripCredentials(url) {
|
|
156
|
+
const ssh = /^(?:ssh:\/\/)?git@([^:/]+)[:/](.+?)(?:\.git)?$/.exec(url);
|
|
157
|
+
if (ssh) return `${ssh[1]}/${ssh[2]}`;
|
|
158
|
+
return url.replace(/\/\/[^/@]*@/, "//").replace(/^https?:\/\//, "").replace(/\.git$/, "");
|
|
159
|
+
}
|
|
160
|
+
|
|
135
161
|
// ../core/src/adapters/codex-rollout.ts
|
|
136
162
|
function codexSubagent(meta) {
|
|
137
163
|
if (meta?.type !== "session_meta") return false;
|
|
@@ -291,8 +317,10 @@ function readJsonFile(file) {
|
|
|
291
317
|
try {
|
|
292
318
|
raw = readFileSync(file, "utf8");
|
|
293
319
|
} catch {
|
|
294
|
-
return { ok: true, existed: false, value: {}, raw: "", indent: " ", eol: "\n", finalNewline: true };
|
|
320
|
+
return { ok: true, existed: false, value: {}, raw: "", indent: " ", eol: "\n", finalNewline: true, bom: false };
|
|
295
321
|
}
|
|
322
|
+
const bom = raw.charCodeAt(0) === 65279;
|
|
323
|
+
if (bom) raw = raw.slice(1);
|
|
296
324
|
let value;
|
|
297
325
|
try {
|
|
298
326
|
value = JSON.parse(raw);
|
|
@@ -312,12 +340,13 @@ function readJsonFile(file) {
|
|
|
312
340
|
raw,
|
|
313
341
|
indent,
|
|
314
342
|
eol,
|
|
315
|
-
finalNewline: /\r?\n$/.test(raw)
|
|
343
|
+
finalNewline: /\r?\n$/.test(raw),
|
|
344
|
+
bom
|
|
316
345
|
};
|
|
317
346
|
}
|
|
318
347
|
function formatJson(value, f) {
|
|
319
348
|
const text = JSON.stringify(value, null, f.indent).replace(/\n/g, f.eol);
|
|
320
|
-
return f.finalNewline ? text + f.eol : text;
|
|
349
|
+
return (f.bom ? "\uFEFF" : "") + (f.finalNewline ? text + f.eol : text);
|
|
321
350
|
}
|
|
322
351
|
function readJson(file) {
|
|
323
352
|
const f = readJsonFile(file);
|
|
@@ -413,74 +442,79 @@ function installedClaudeShaped(file, harness, events, cmd, o) {
|
|
|
413
442
|
return current === events.length ? "current" : "stale";
|
|
414
443
|
}
|
|
415
444
|
|
|
416
|
-
// ../core/src/adapters/
|
|
417
|
-
var NAME = "
|
|
418
|
-
var
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
445
|
+
// ../core/src/adapters/claude-code.ts
|
|
446
|
+
var NAME = "claude-code";
|
|
447
|
+
var CLAUDE_EVENTS = [
|
|
448
|
+
"SessionStart",
|
|
449
|
+
"UserPromptSubmit",
|
|
450
|
+
"PreToolUse",
|
|
451
|
+
"PostToolUse",
|
|
452
|
+
"PostToolUseFailure",
|
|
453
|
+
"PermissionRequest",
|
|
454
|
+
"Notification",
|
|
455
|
+
"Stop",
|
|
456
|
+
"SubagentStart",
|
|
457
|
+
"SubagentStop",
|
|
458
|
+
"PreCompact",
|
|
459
|
+
"PostCompact",
|
|
460
|
+
"SessionEnd",
|
|
461
|
+
"StopFailure"
|
|
462
|
+
];
|
|
463
|
+
var LEAN_SKIP = /* @__PURE__ */ new Set(["PreToolUse", "PostToolUseFailure"]);
|
|
464
|
+
var eventsFor = (lean) => lean ? CLAUDE_EVENTS.filter((e) => !LEAN_SKIP.has(e)) : [...CLAUDE_EVENTS];
|
|
465
|
+
var OPTS = {
|
|
466
|
+
matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0,
|
|
467
|
+
timeout: 5,
|
|
468
|
+
// The hook waits up to 125 s for a verified control answer, and only when control
|
|
469
|
+
// is paired on this machine; everywhere else it exits in milliseconds as before.
|
|
470
|
+
timeoutFor: (e) => e === "PermissionRequest" ? 130 : void 0
|
|
471
|
+
};
|
|
472
|
+
var DESKTOP_ID = /^local_[A-Za-z0-9-]{1,64}$/;
|
|
473
|
+
function claudeDirs(env = process.env, home = env.HOME || homeDir()) {
|
|
474
|
+
const out = [];
|
|
475
|
+
const add = (d) => {
|
|
476
|
+
if (!d) return;
|
|
477
|
+
const r = path4.resolve(d);
|
|
478
|
+
if (!out.includes(r)) out.push(r);
|
|
479
|
+
};
|
|
480
|
+
add(path4.join(home, ".claude"));
|
|
481
|
+
add(env.CLAUDE_CONFIG_DIR);
|
|
482
|
+
try {
|
|
483
|
+
for (const e of readdirSync2(home, { withFileTypes: true })) {
|
|
484
|
+
if (!e.isDirectory() || !/^\.claude[-_.]/.test(e.name)) continue;
|
|
485
|
+
const d = path4.join(home, e.name);
|
|
486
|
+
if (existsSync2(path4.join(d, "projects")) || existsSync2(path4.join(d, "settings.json"))) add(d);
|
|
487
|
+
}
|
|
488
|
+
} catch {
|
|
489
|
+
}
|
|
490
|
+
return out.filter((d, i) => i === 0 || existsSync2(d));
|
|
433
491
|
}
|
|
434
|
-
var
|
|
492
|
+
var settingsIn = (dir) => path4.join(dir, "settings.json");
|
|
493
|
+
var configDirOf = (transcript) => path4.dirname(path4.dirname(path4.dirname(transcript)));
|
|
494
|
+
var validTool = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
495
|
+
var endReason = (r) => r === "clear" || r === "logout" || r === "exit" ? r : r === "prompt_input_exit" ? "exit" : "other";
|
|
496
|
+
var notifKind = (t) => t === "permission_prompt" ? "permission" : t === "idle_prompt" ? "idle" : t === "elicitation_dialog" ? "elicitation" : "question";
|
|
497
|
+
var claudeCode = {
|
|
435
498
|
name: NAME,
|
|
436
|
-
events:
|
|
437
|
-
detect: (env = process.env) =>
|
|
438
|
-
configFiles: (env = process.env) =>
|
|
439
|
-
install(cmd, env = process.env) {
|
|
440
|
-
const
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
hooks
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
return
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
}
|
|
456
|
-
];
|
|
457
|
-
},
|
|
458
|
-
uninstall(env = process.env, opts = {}) {
|
|
459
|
-
const file = hooksFile(env);
|
|
460
|
-
if (!existsSync2(file)) return [{ file, changed: false }];
|
|
461
|
-
const f = readJsonFile(file);
|
|
462
|
-
if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
|
|
463
|
-
const hooks = f.value;
|
|
464
|
-
if (!(HOOK_NAME in hooks)) return [{ file, changed: false }];
|
|
465
|
-
delete hooks[HOOK_NAME];
|
|
466
|
-
if (opts.created?.includes(file) && !Object.keys(hooks).length) {
|
|
467
|
-
unlinkSync2(file);
|
|
468
|
-
return [{ file, changed: true, note: "removed (sessionpipe created it)" }];
|
|
469
|
-
}
|
|
470
|
-
writeFileSync2(file, formatJson(hooks, f));
|
|
471
|
-
return [{ file, changed: true }];
|
|
472
|
-
},
|
|
473
|
-
installed(cmd, env = process.env) {
|
|
474
|
-
const file = hooksFile(env);
|
|
475
|
-
const f = readJsonFile(file);
|
|
476
|
-
const have = f.ok ? f.value[HOOK_NAME] : void 0;
|
|
477
|
-
return [
|
|
478
|
-
{
|
|
479
|
-
file,
|
|
480
|
-
state: !have ? "missing" : JSON.stringify(have) === JSON.stringify(entry(cmd)) ? "current" : "stale"
|
|
481
|
-
}
|
|
482
|
-
];
|
|
483
|
-
},
|
|
499
|
+
events: CLAUDE_EVENTS,
|
|
500
|
+
detect: (env = process.env) => claudeDirs(env).some((d) => existsSync2(d)),
|
|
501
|
+
configFiles: (env = process.env) => claudeDirs(env).map(settingsIn),
|
|
502
|
+
install: (cmd, env = process.env, opts = {}) => claudeDirs(env).flatMap((d) => {
|
|
503
|
+
const hooks = installClaudeShaped(settingsIn(d), NAME, eventsFor(opts.lean), cmd, OPTS);
|
|
504
|
+
const line = opts.statusLine === void 0 ? null : statusLine(settingsIn(d), opts.statusLine, cmd);
|
|
505
|
+
return line ? [hooks, line] : [hooks];
|
|
506
|
+
}),
|
|
507
|
+
uninstall: (env = process.env, opts = {}) => claudeDirs(env).map((d) => {
|
|
508
|
+
const line = statusLine(settingsIn(d), false);
|
|
509
|
+
const hooks = uninstallClaudeShaped(settingsIn(d), CLAUDE_EVENTS, {
|
|
510
|
+
created: opts.created?.includes(settingsIn(d))
|
|
511
|
+
});
|
|
512
|
+
return line && !hooks.changed ? line : hooks;
|
|
513
|
+
}),
|
|
514
|
+
installed: (cmd, env = process.env, opts = {}) => claudeDirs(env).map((d) => ({
|
|
515
|
+
file: settingsIn(d),
|
|
516
|
+
state: installedClaudeShaped(settingsIn(d), NAME, eventsFor(opts.lean), cmd, OPTS)
|
|
517
|
+
})),
|
|
484
518
|
fromHook(input) {
|
|
485
519
|
const event = input.argv[1] ?? "";
|
|
486
520
|
let s = {};
|
|
@@ -489,310 +523,735 @@ var antigravity = {
|
|
|
489
523
|
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
490
524
|
} catch {
|
|
491
525
|
}
|
|
492
|
-
const id = s.
|
|
493
|
-
|
|
494
|
-
if (
|
|
495
|
-
const
|
|
496
|
-
|
|
497
|
-
if (typeof s.
|
|
498
|
-
|
|
499
|
-
const
|
|
526
|
+
const id = s.session_id ?? s.sessionId;
|
|
527
|
+
if (!id) return null;
|
|
528
|
+
if (notClaudeCode(String(id), s)) return null;
|
|
529
|
+
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
530
|
+
if (typeof s.model === "string") session.model = s.model;
|
|
531
|
+
if (typeof s.agent_id === "string" && event.startsWith("Subagent") === false && s.agent_id)
|
|
532
|
+
session.parent_id = null;
|
|
533
|
+
const turn_id = typeof s.prompt_id === "string" ? s.prompt_id : void 0;
|
|
534
|
+
const tool = validTool(s.tool_name) ? s.tool_name : void 0;
|
|
535
|
+
const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
|
|
536
|
+
const ms = typeof s.duration_ms === "number" ? Math.round(s.duration_ms) : void 0;
|
|
537
|
+
const agent_id = typeof s.agent_id === "string" ? s.agent_id : void 0;
|
|
538
|
+
const permissionId = () => `perm-${createHash("sha256").update(`${turn_id ?? ""}|${tool ?? ""}|${JSON.stringify(s.tool_input ?? null)}`).digest("hex").slice(0, 16)}`;
|
|
500
539
|
const ev = (type, data) => ({ type, data: prune(data), harnessEvent: event });
|
|
501
540
|
const events = [];
|
|
502
541
|
switch (event) {
|
|
503
|
-
case "
|
|
504
|
-
|
|
505
|
-
events.push(ev("session.started", { source: "unknown" }));
|
|
506
|
-
events.push(ev("turn.started", {}));
|
|
507
|
-
} else events.push(ev("session.heartbeat", {}));
|
|
542
|
+
case "SessionStart":
|
|
543
|
+
events.push(ev("session.started", { source: pickSource(s.source ?? s.trigger) }));
|
|
508
544
|
break;
|
|
509
|
-
case "
|
|
545
|
+
case "UserPromptSubmit": {
|
|
546
|
+
const prompt = typeof s.prompt === "string" ? s.prompt : void 0;
|
|
547
|
+
events.push(
|
|
548
|
+
ev("turn.started", {
|
|
549
|
+
turn_id,
|
|
550
|
+
prompt_chars: prompt === void 0 ? void 0 : [...prompt].length,
|
|
551
|
+
// Claude Code's hook schema documents `source`
|
|
552
|
+
// (user | sdk | system | loop_wakeup | schedule_wakeup | poll_event) and says a
|
|
553
|
+
// payload may omit it while the field rolls out; 2.1.304's payload has no such
|
|
554
|
+
// key at all. Forwarded verbatim, shape-guarded only: the list is the harness's
|
|
555
|
+
// to extend.
|
|
556
|
+
submitted_by: harnessWord(s.source),
|
|
557
|
+
// One of Claude Code's own prompt wrappers: the prompt is a block the harness
|
|
558
|
+
// wrote, so it is not the person's words. Absent is unknown, never "the person" —
|
|
559
|
+
// and `true` is never "no person caused this turn", because a Talk or
|
|
560
|
+
// `sessionpipe wait` delivery hands the person's own message over inside a
|
|
561
|
+
// <task-notification> (PROTOCOL.md §4).
|
|
562
|
+
injected: prompt !== void 0 && injectedBlock(prompt) ? true : void 0
|
|
563
|
+
})
|
|
564
|
+
);
|
|
510
565
|
break;
|
|
566
|
+
}
|
|
511
567
|
case "PreToolUse":
|
|
512
|
-
if (tool) events.push(ev("tool.started", { tool, input:
|
|
568
|
+
if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, agent_id, input: s.tool_input }));
|
|
513
569
|
break;
|
|
514
570
|
case "PostToolUse":
|
|
515
|
-
if (
|
|
516
|
-
else
|
|
571
|
+
if (tool)
|
|
517
572
|
events.push(
|
|
518
573
|
ev("tool.ended", {
|
|
519
574
|
tool,
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
575
|
+
call_id,
|
|
576
|
+
turn_id,
|
|
577
|
+
agent_id,
|
|
578
|
+
ms,
|
|
579
|
+
ok: true,
|
|
580
|
+
input: s.tool_input,
|
|
581
|
+
output: s.tool_response
|
|
582
|
+
})
|
|
583
|
+
);
|
|
584
|
+
break;
|
|
585
|
+
case "PostToolUseFailure":
|
|
586
|
+
if (tool)
|
|
587
|
+
events.push(
|
|
588
|
+
ev("tool.ended", {
|
|
589
|
+
tool,
|
|
590
|
+
call_id,
|
|
591
|
+
turn_id,
|
|
592
|
+
agent_id,
|
|
593
|
+
ms,
|
|
594
|
+
ok: false,
|
|
595
|
+
error: errText(s.error ?? s.tool_response),
|
|
596
|
+
input: s.tool_input,
|
|
597
|
+
output: s.tool_response
|
|
523
598
|
})
|
|
524
599
|
);
|
|
525
600
|
break;
|
|
601
|
+
case "PermissionRequest":
|
|
602
|
+
events.push(
|
|
603
|
+
ev("attention.needed", {
|
|
604
|
+
attention_id: call_id ?? permissionId(),
|
|
605
|
+
kind: "permission",
|
|
606
|
+
tool,
|
|
607
|
+
message: tool ? `${tool}${describe(s.tool_input)}` : void 0
|
|
608
|
+
})
|
|
609
|
+
);
|
|
610
|
+
break;
|
|
611
|
+
case "Notification":
|
|
612
|
+
events.push(
|
|
613
|
+
ev("attention.needed", {
|
|
614
|
+
attention_id: `notif-${Date.now()}`,
|
|
615
|
+
kind: notifKind(s.notification_type),
|
|
616
|
+
message: typeof s.message === "string" ? s.message.slice(0, 200) : void 0
|
|
617
|
+
})
|
|
618
|
+
);
|
|
619
|
+
break;
|
|
526
620
|
case "Stop":
|
|
527
|
-
events.push(
|
|
621
|
+
events.push(
|
|
622
|
+
ev("turn.ended", {
|
|
623
|
+
turn_id,
|
|
624
|
+
reason: "stop",
|
|
625
|
+
background: Array.isArray(s.background_tasks) ? s.background_tasks.length : void 0
|
|
626
|
+
})
|
|
627
|
+
);
|
|
528
628
|
break;
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
629
|
+
case "StopFailure":
|
|
630
|
+
events.push(
|
|
631
|
+
ev("turn.ended", {
|
|
632
|
+
turn_id,
|
|
633
|
+
reason: "error",
|
|
634
|
+
error: typeof s.error === "string" ? s.error.slice(0, 200) : void 0
|
|
635
|
+
})
|
|
636
|
+
);
|
|
637
|
+
break;
|
|
638
|
+
case "SubagentStart":
|
|
639
|
+
events.push(
|
|
640
|
+
ev("subagent.started", {
|
|
641
|
+
agent_id: String(s.agent_id ?? ""),
|
|
642
|
+
agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
|
|
643
|
+
})
|
|
644
|
+
);
|
|
645
|
+
break;
|
|
646
|
+
case "SubagentStop":
|
|
647
|
+
events.push(
|
|
648
|
+
ev("subagent.ended", {
|
|
649
|
+
agent_id: String(s.agent_id ?? ""),
|
|
650
|
+
agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
|
|
651
|
+
})
|
|
652
|
+
);
|
|
653
|
+
break;
|
|
654
|
+
case "PreCompact":
|
|
655
|
+
events.push(ev("context.compacted", { trigger: s.trigger === "manual" ? "manual" : "auto" }));
|
|
656
|
+
break;
|
|
657
|
+
case "PostCompact":
|
|
658
|
+
break;
|
|
659
|
+
// PreCompact already said it; a second event would double-count
|
|
660
|
+
case "SessionEnd":
|
|
661
|
+
events.push(ev("session.ended", { reason: endReason(s.reason) }));
|
|
662
|
+
break;
|
|
663
|
+
case "StatusLine": {
|
|
664
|
+
const limits = claudeLimits(s.rate_limits);
|
|
665
|
+
if (!limits) return null;
|
|
666
|
+
events.push(ev("limits.observed", limits));
|
|
667
|
+
break;
|
|
668
|
+
}
|
|
669
|
+
default:
|
|
670
|
+
return null;
|
|
671
|
+
}
|
|
672
|
+
const hints = {};
|
|
673
|
+
if (event === "UserPromptSubmit" && typeof s.prompt === "string") hints.prompt = s.prompt;
|
|
674
|
+
const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
|
|
675
|
+
if (host && DESKTOP_ID.test(host)) hints.desktop_id = host;
|
|
676
|
+
const attended = input.env.CLAUDE_CODE_SESSION_ATTENDED;
|
|
677
|
+
if (attended === "1" || attended === "0") hints.attended = attended === "1" || !!input.env.SESSIONPIPE_CONTROL_JOB;
|
|
678
|
+
if (event !== "PreToolUse" && event !== "PostToolUse") hints.claude_pid = claudePid(input.env);
|
|
679
|
+
const r = { session, events, hints };
|
|
680
|
+
if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
|
|
681
|
+
return r;
|
|
682
|
+
},
|
|
683
|
+
readTranscript(file, fromLine) {
|
|
684
|
+
let text = "";
|
|
685
|
+
try {
|
|
686
|
+
text = readFileSync2(file, "utf8");
|
|
687
|
+
} catch {
|
|
688
|
+
return { turns: [], line: fromLine };
|
|
689
|
+
}
|
|
690
|
+
return readClaude(text.split("\n"), fromLine);
|
|
691
|
+
},
|
|
692
|
+
facts(session, transcript, hints, state) {
|
|
693
|
+
const out = {};
|
|
694
|
+
if (transcript) Object.assign(out, claudeFacts(transcript, session.id, hints, state));
|
|
695
|
+
const cwd = out.cwd ?? session.cwd;
|
|
696
|
+
Object.assign(out, gitFacts(cwd), out.branch ? { branch: out.branch } : {});
|
|
697
|
+
if (!out.url && typeof hints.desktop_id === "string") out.url = `claude://code/continue/${hints.desktop_id}`;
|
|
698
|
+
if (typeof hints.attended === "boolean") out.attended = hints.attended || !!out.url;
|
|
699
|
+
return out;
|
|
552
700
|
},
|
|
701
|
+
remoteFacts: (session, local, transcript, state, env = process.env) => claudeRemoteFacts(session.id, local, transcript, state, env),
|
|
553
702
|
backfill(sinceMs, env = process.env) {
|
|
554
703
|
const rows2 = [];
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
}
|
|
704
|
+
const files = [];
|
|
705
|
+
for (const d of claudeDirs(env))
|
|
706
|
+
files.push(...recentFiles(path4.join(d, "projects"), 1, sinceMs, (n) => /^[0-9a-f-]{36}\.jsonl$/.test(n)));
|
|
707
|
+
const noState = { get: () => ({}), save: () => {
|
|
708
|
+
} };
|
|
709
|
+
for (const f of files) {
|
|
710
|
+
const id = path4.basename(f, ".jsonl");
|
|
711
|
+
const facts = claudeFacts(f, id, {}, noState);
|
|
712
|
+
if (!facts.started_at || !facts.last_at) continue;
|
|
713
|
+
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
714
|
+
const cwd = rest.cwd;
|
|
715
|
+
rows2.push({
|
|
716
|
+
session: { id, ...rest, ...gitFacts(cwd), ...rest.branch ? { branch: rest.branch } : {} },
|
|
717
|
+
started_at,
|
|
718
|
+
last_at,
|
|
719
|
+
transcript: f
|
|
720
|
+
});
|
|
573
721
|
}
|
|
574
722
|
return rows2;
|
|
575
723
|
}
|
|
576
724
|
};
|
|
725
|
+
var LINE_REFRESH_S = 60;
|
|
726
|
+
function statusLine(file, want, cmd) {
|
|
727
|
+
const f = readJsonFile(file);
|
|
728
|
+
if (!f.ok) return null;
|
|
729
|
+
const cur = f.value.statusLine;
|
|
730
|
+
const ours = typeof cur?.command === "string" && OURS.test(cur.command);
|
|
731
|
+
const command = cmd?.([NAME, "StatusLine"]).command;
|
|
732
|
+
if (want && cur && !ours)
|
|
733
|
+
return { file, changed: false, skipped: true, note: "a status line of your own is set; limits are not read" };
|
|
734
|
+
if (want ? ours && cur?.command === command && cur?.refreshInterval === LINE_REFRESH_S : !ours) return null;
|
|
735
|
+
if (want) f.value.statusLine = { type: "command", command, refreshInterval: LINE_REFRESH_S };
|
|
736
|
+
else delete f.value.statusLine;
|
|
737
|
+
writeFileSync2(file, formatJson(f.value, f));
|
|
738
|
+
return { file, changed: true, note: want ? "status line written (account limits)" : "status line removed" };
|
|
739
|
+
}
|
|
740
|
+
var epochIso = (t) => typeof t === "number" && t > 0 && t < 1e11 ? new Date(t * 1e3).toISOString() : void 0;
|
|
741
|
+
var WINDOW_MINUTES = { five_hour: 300, seven_day: 10080 };
|
|
742
|
+
function claudeLimits(rl) {
|
|
743
|
+
if (!rl || typeof rl !== "object" || Array.isArray(rl)) return null;
|
|
744
|
+
const windows = [];
|
|
745
|
+
for (const [id, w] of Object.entries(rl)) {
|
|
746
|
+
if (!/^[a-z0-9_]{1,64}$/.test(id) || typeof w?.used_percentage !== "number" || w.used_percentage < 0) continue;
|
|
747
|
+
windows.push(
|
|
748
|
+
prune({
|
|
749
|
+
id,
|
|
750
|
+
used_percent: w.used_percentage,
|
|
751
|
+
window_minutes: WINDOW_MINUTES[id],
|
|
752
|
+
resets_at: epochIso(w.resets_at)
|
|
753
|
+
})
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
return windows.length ? { windows: windows.slice(0, 8) } : null;
|
|
757
|
+
}
|
|
577
758
|
function prune(o) {
|
|
578
759
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
579
760
|
return o;
|
|
580
761
|
}
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
if (/^file:/i.test(s)) {
|
|
585
|
-
try {
|
|
586
|
-
s = decodeURIComponent(new URL(s).pathname);
|
|
587
|
-
} catch {
|
|
588
|
-
return null;
|
|
589
|
-
}
|
|
590
|
-
if (process.platform === "win32" && /^\/[A-Za-z]:/.test(s)) s = s.slice(1);
|
|
591
|
-
} else if (/^[a-z][a-z0-9+.-]*:\/\//i.test(s)) return null;
|
|
592
|
-
if (!path4.isAbsolute(s)) return null;
|
|
593
|
-
return path4.resolve(s);
|
|
762
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
763
|
+
function notClaudeCode(id, s) {
|
|
764
|
+
return typeof s.transcript_path !== "string" && !UUID.test(id);
|
|
594
765
|
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
766
|
+
function pickSource(v) {
|
|
767
|
+
return typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
|
|
768
|
+
}
|
|
769
|
+
var harnessWord = (v) => typeof v === "string" && /^[A-Za-z0-9][A-Za-z0-9_.-]{0,39}$/.test(v) ? v : void 0;
|
|
770
|
+
var WRAPPER = /^<(?:task-notification|cross-session-message|system-reminder|local-command-stdout|command-[a-z-]+)[\s/>]/i;
|
|
771
|
+
var injectedBlock = (prompt) => WRAPPER.test(prompt.trim());
|
|
772
|
+
function errText(v) {
|
|
773
|
+
if (typeof v === "string") return v.slice(0, 2e3);
|
|
774
|
+
if (v && typeof v === "object") {
|
|
775
|
+
const e = v;
|
|
776
|
+
if (typeof e.error === "string") return e.error.slice(0, 2e3);
|
|
777
|
+
if (typeof e.message === "string") return e.message.slice(0, 2e3);
|
|
602
778
|
}
|
|
603
|
-
return
|
|
779
|
+
return void 0;
|
|
604
780
|
}
|
|
605
|
-
function
|
|
606
|
-
|
|
781
|
+
function describe(input) {
|
|
782
|
+
if (!input || typeof input !== "object") return "";
|
|
783
|
+
const i = input;
|
|
784
|
+
const v = i.command ?? i.file_path ?? i.path ?? i.url ?? i.pattern;
|
|
785
|
+
return typeof v === "string" ? ` ${v.slice(0, 120)}` : "";
|
|
786
|
+
}
|
|
787
|
+
var NAME_RANK = {
|
|
788
|
+
user: "custom",
|
|
789
|
+
auto: "harness",
|
|
790
|
+
hook: "harness",
|
|
791
|
+
peer: "harness"
|
|
792
|
+
};
|
|
793
|
+
function registryEntry(j) {
|
|
794
|
+
if (!j || typeof j !== "object") return null;
|
|
795
|
+
const r = j;
|
|
796
|
+
const source = r.nameSource ? NAME_RANK[r.nameSource] : void 0;
|
|
797
|
+
return {
|
|
798
|
+
sessionId: r.sessionId ? String(r.sessionId) : null,
|
|
799
|
+
name: source && typeof r.name === "string" && r.name.trim() ? r.name.trim() : null,
|
|
800
|
+
name_source: source ?? null,
|
|
801
|
+
bridge: typeof r.bridgeSessionId === "string" ? r.bridgeSessionId : null
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
var liveCache = /* @__PURE__ */ new Map();
|
|
805
|
+
function claudeLive(dir) {
|
|
806
|
+
const hit = liveCache.get(dir);
|
|
807
|
+
if (hit) return hit;
|
|
808
|
+
const map = /* @__PURE__ */ new Map();
|
|
607
809
|
let names = [];
|
|
608
810
|
try {
|
|
609
|
-
names = readdirSync2(dir).filter((n) => n.endsWith(".json"));
|
|
811
|
+
names = readdirSync2(path4.join(dir, "sessions")).filter((n) => n.endsWith(".json"));
|
|
610
812
|
} catch {
|
|
611
|
-
return out;
|
|
612
813
|
}
|
|
613
814
|
for (const n of names) {
|
|
614
|
-
const id = n.slice(0, -5);
|
|
615
|
-
if (!ID_RE.test(id)) continue;
|
|
616
|
-
let j;
|
|
617
815
|
try {
|
|
618
|
-
|
|
816
|
+
const e = registryEntry(JSON.parse(readFileSync2(path4.join(dir, "sessions", n), "utf8")));
|
|
817
|
+
if (e?.sessionId) map.set(e.sessionId, e);
|
|
619
818
|
} catch {
|
|
620
|
-
continue;
|
|
621
|
-
}
|
|
622
|
-
const folders = [];
|
|
623
|
-
for (const r of j?.projectResources?.resources ?? []) {
|
|
624
|
-
const f = agPath(r?.folderUri ?? r?.gitFolder?.folderUri);
|
|
625
|
-
if (f) folders.push(...agVariants(f));
|
|
626
819
|
}
|
|
627
|
-
if (folders.length) out.push({ id, folders });
|
|
628
820
|
}
|
|
629
|
-
|
|
821
|
+
liveCache.set(dir, map);
|
|
822
|
+
return map;
|
|
630
823
|
}
|
|
631
|
-
function
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
824
|
+
function claudeLiveByPid(dir, pid) {
|
|
825
|
+
if (!pid) return null;
|
|
826
|
+
try {
|
|
827
|
+
return registryEntry(JSON.parse(readFileSync2(path4.join(dir, "sessions", `${pid}.json`), "utf8")));
|
|
828
|
+
} catch {
|
|
829
|
+
return null;
|
|
636
830
|
}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
831
|
+
}
|
|
832
|
+
function claudePid(env = process.env) {
|
|
833
|
+
try {
|
|
834
|
+
const dirs = claudeDirs(env);
|
|
835
|
+
const has = (pid2) => dirs.some((d) => existsSync2(path4.join(d, "sessions", `${pid2}.json`)));
|
|
836
|
+
let pid = process.ppid;
|
|
837
|
+
if (has(pid)) return pid;
|
|
838
|
+
const parent = parentOf();
|
|
839
|
+
for (let hop = 0; hop < 6 && pid > 1; hop++) {
|
|
840
|
+
pid = parent(pid);
|
|
841
|
+
if (pid > 1 && has(pid)) return pid;
|
|
842
|
+
}
|
|
843
|
+
} catch {
|
|
844
|
+
}
|
|
845
|
+
return null;
|
|
846
|
+
}
|
|
847
|
+
function parentOf() {
|
|
848
|
+
if (process.platform === "linux") {
|
|
849
|
+
return (pid) => {
|
|
850
|
+
try {
|
|
851
|
+
const st = readFileSync2(`/proc/${pid}/stat`, "utf8");
|
|
852
|
+
return Number(st.slice(st.lastIndexOf(")") + 2).split(" ")[1]) || 0;
|
|
853
|
+
} catch {
|
|
854
|
+
return 0;
|
|
645
855
|
}
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
const table = /* @__PURE__ */ new Map();
|
|
859
|
+
try {
|
|
860
|
+
const out = execFileSync2("ps", ["-axo", "pid=,ppid="], {
|
|
861
|
+
encoding: "utf8",
|
|
862
|
+
timeout: 2e3,
|
|
863
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
864
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
865
|
+
});
|
|
866
|
+
for (const l of out.split("\n")) {
|
|
867
|
+
const m = /^\s*(\d+)\s+(\d+)/.exec(l);
|
|
868
|
+
if (m) table.set(Number(m[1]), Number(m[2]));
|
|
646
869
|
}
|
|
647
|
-
|
|
870
|
+
} catch {
|
|
871
|
+
}
|
|
872
|
+
return (pid) => table.get(pid) || 0;
|
|
648
873
|
}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
const
|
|
652
|
-
|
|
874
|
+
var EMAIL_RE = /^[^@\s]{1,64}@[^@\s]{1,189}$/;
|
|
875
|
+
function claudeLogin(dir, home = homeDir()) {
|
|
876
|
+
const files = [path4.join(dir, ".claude.json")];
|
|
877
|
+
if (path4.resolve(dir) === path4.join(home, ".claude")) files.unshift(path4.join(home, ".claude.json"));
|
|
878
|
+
for (const f of files) {
|
|
653
879
|
try {
|
|
654
|
-
const
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
const conv = opts.conversation ?? "";
|
|
660
|
-
if (!ID_RE.test(conv) || !opts.workspaces?.length) return base;
|
|
661
|
-
const project = agMatchProject(agProjects(opts.projectsDir ?? projectsDir(opts.env)), opts.workspaces);
|
|
662
|
-
return project ? `${base}?p=${encodeURIComponent(`c/${conv}?section=${project}`)}` : base;
|
|
880
|
+
const a = JSON.parse(readFileSync2(f, "utf8")).oauthAccount;
|
|
881
|
+
if (typeof a?.accountUuid === "string" && a.accountUuid) {
|
|
882
|
+
const email = typeof a.emailAddress === "string" && EMAIL_RE.test(a.emailAddress) ? a.emailAddress : void 0;
|
|
883
|
+
return { id: a.accountUuid.slice(0, 128), ...email ? { email } : {} };
|
|
884
|
+
}
|
|
663
885
|
} catch {
|
|
664
886
|
}
|
|
665
887
|
}
|
|
666
|
-
return
|
|
888
|
+
return apiAccount(dir);
|
|
667
889
|
}
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
const
|
|
890
|
+
var ANTHROPIC_HOST = /(^|\.)anthropic\.com$/i;
|
|
891
|
+
function apiAccount(dir) {
|
|
892
|
+
const base = readJson(settingsIn(dir)).env?.ANTHROPIC_BASE_URL;
|
|
893
|
+
if (typeof base !== "string") return null;
|
|
894
|
+
let u;
|
|
895
|
+
try {
|
|
896
|
+
u = new URL(base.trim());
|
|
897
|
+
} catch {
|
|
898
|
+
return null;
|
|
899
|
+
}
|
|
900
|
+
if (u.protocol !== "https:" && u.protocol !== "http:" || !u.hostname || ANTHROPIC_HOST.test(u.hostname))
|
|
901
|
+
return null;
|
|
902
|
+
const origin = `${u.protocol}//${u.host}`;
|
|
903
|
+
return { id: `api-${createHash("sha256").update(origin).digest("hex").slice(0, 16)}`, name: u.host };
|
|
904
|
+
}
|
|
905
|
+
var accountCache = /* @__PURE__ */ new Map();
|
|
906
|
+
function claudeAccount(dir) {
|
|
907
|
+
const hit = accountCache.get(dir);
|
|
908
|
+
if (hit !== void 0) return hit;
|
|
909
|
+
const acct = claudeLogin(dir)?.id ?? null;
|
|
910
|
+
accountCache.set(dir, acct);
|
|
911
|
+
return acct;
|
|
912
|
+
}
|
|
913
|
+
var LINK_RE = /^https:\/\/claude\.ai\/code\/session_[A-Za-z0-9]+$/;
|
|
914
|
+
function transcriptLink(records) {
|
|
915
|
+
for (let i = records.length - 1; i >= 0; i--) {
|
|
916
|
+
const j = records[i];
|
|
917
|
+
if (j.type === "system" && j.subtype === "bridge_status" && typeof j.url === "string" && LINK_RE.test(j.url))
|
|
918
|
+
return j.url;
|
|
919
|
+
if (j.type === "bridge-session" && typeof j.bridgeSessionId === "string") {
|
|
920
|
+
const m = /^(?:cse|session)_([A-Za-z0-9]+)$/.exec(j.bridgeSessionId);
|
|
921
|
+
if (m) return `https://claude.ai/code/session_${m[1]}`;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
return null;
|
|
925
|
+
}
|
|
926
|
+
function appTitle(text) {
|
|
927
|
+
const t = String(text).replace(/\s+/g, " ").trim();
|
|
928
|
+
return t.length > 50 ? `${t.slice(0, 50)}...` : t;
|
|
929
|
+
}
|
|
930
|
+
function askText(j) {
|
|
931
|
+
if (!j || j.type !== "user" || j.isMeta || j.isSidechain) return null;
|
|
932
|
+
const origin = j.origin;
|
|
933
|
+
if (origin?.kind && origin.kind !== "human") return null;
|
|
934
|
+
const c = j.message?.content;
|
|
935
|
+
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();
|
|
936
|
+
if (!text || isInjected(text)) return null;
|
|
937
|
+
return text;
|
|
938
|
+
}
|
|
939
|
+
function firstAsk(records) {
|
|
940
|
+
for (const j of records) {
|
|
941
|
+
const t = askText(j);
|
|
942
|
+
if (t) return appTitle(t);
|
|
943
|
+
}
|
|
944
|
+
return null;
|
|
945
|
+
}
|
|
946
|
+
function scanTitles(file, from, found) {
|
|
947
|
+
const out = { custom: found.custom ?? null, ai: found.ai ?? null, upto: from };
|
|
948
|
+
let size = 0;
|
|
949
|
+
try {
|
|
950
|
+
size = statSync2(file).size;
|
|
951
|
+
} catch {
|
|
952
|
+
return out;
|
|
953
|
+
}
|
|
954
|
+
if (size < from) {
|
|
955
|
+
out.upto = 0;
|
|
956
|
+
out.custom = out.ai = null;
|
|
957
|
+
from = 0;
|
|
958
|
+
}
|
|
959
|
+
let pos = 0;
|
|
960
|
+
scanLines(
|
|
961
|
+
file,
|
|
962
|
+
(l) => {
|
|
963
|
+
pos += Buffer.byteLength(l, "utf8") + 1;
|
|
964
|
+
if (pos <= from || l.indexOf('-title"') < 0) return null;
|
|
965
|
+
try {
|
|
966
|
+
const j = JSON.parse(l);
|
|
967
|
+
if (j.type === "custom-title" && j.customTitle) out.custom = j.customTitle;
|
|
968
|
+
if (j.type === "ai-title" && j.aiTitle) out.ai = j.aiTitle;
|
|
969
|
+
} catch {
|
|
970
|
+
}
|
|
971
|
+
return null;
|
|
972
|
+
},
|
|
973
|
+
512 * 1024 * 1024
|
|
974
|
+
);
|
|
975
|
+
out.upto = size;
|
|
976
|
+
return out;
|
|
977
|
+
}
|
|
978
|
+
function claudeFacts(file, sessionId, hints, state) {
|
|
979
|
+
const { head, tail } = ends(file, 64 * 1024, 256 * 1024);
|
|
980
|
+
const H = parseLines(head);
|
|
671
981
|
const T = parseLines(tail);
|
|
672
982
|
const out = {};
|
|
673
|
-
const
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
983
|
+
const id = sessionId || path4.basename(file, ".jsonl");
|
|
984
|
+
const mem = state.get(NAME, id);
|
|
985
|
+
let custom = null;
|
|
986
|
+
let ai = null;
|
|
987
|
+
for (const j of H.concat(T)) {
|
|
988
|
+
if (j.type === "custom-title" && typeof j.customTitle === "string") custom = j.customTitle;
|
|
989
|
+
if (j.type === "ai-title" && typeof j.aiTitle === "string") ai = j.aiTitle;
|
|
990
|
+
}
|
|
991
|
+
let size = 0;
|
|
992
|
+
try {
|
|
993
|
+
size = statSync2(file).size;
|
|
994
|
+
} catch {
|
|
995
|
+
}
|
|
996
|
+
if (!custom && !ai && size > 64 * 1024 + 256 * 1024) {
|
|
997
|
+
const s = scanTitles(file, Number(mem.scan_upto) || 0, {
|
|
998
|
+
custom: mem.scan_custom,
|
|
999
|
+
ai: mem.scan_ai
|
|
1000
|
+
});
|
|
1001
|
+
custom = s.custom;
|
|
1002
|
+
ai = s.ai;
|
|
1003
|
+
state.save(NAME, id, { scan_upto: s.upto, scan_custom: s.custom, scan_ai: s.ai });
|
|
1004
|
+
}
|
|
1005
|
+
const dir = configDirOf(file);
|
|
1006
|
+
const live = claudeLive(dir).get(id) ?? claudeLiveByPid(dir, hints.claude_pid);
|
|
1007
|
+
if (live?.name) state.save(NAME, id, { name: live.name, name_source: live.name_source });
|
|
1008
|
+
const bridge = live?.bridge && /^session_[A-Za-z0-9]+$/.test(live.bridge) ? `https://claude.ai/code/${live.bridge}` : null;
|
|
1009
|
+
const url = bridge ?? transcriptLink(T) ?? transcriptLink(H) ?? (typeof mem.url === "string" ? mem.url : null);
|
|
1010
|
+
if (url) {
|
|
1011
|
+
out.url = url;
|
|
1012
|
+
state.save(NAME, id, { url });
|
|
1013
|
+
}
|
|
1014
|
+
const name = live?.name ? { title: live.name, source: live.name_source } : typeof mem.name === "string" ? { title: mem.name, source: mem.name_source } : null;
|
|
1015
|
+
const pick = name && name.source === "custom" && name || custom && { title: custom, source: "custom" } || name || ai && { title: ai, source: "harness" };
|
|
1016
|
+
let ask2 = typeof mem.first_ask === "string" ? mem.first_ask : firstAsk(H);
|
|
1017
|
+
if (!ask2 && size > 64 * 1024 && !mem.first_ask_none) {
|
|
1018
|
+
ask2 = scanLines(file, (l) => {
|
|
1019
|
+
try {
|
|
1020
|
+
const t = askText(JSON.parse(l));
|
|
1021
|
+
return t ? appTitle(t) : null;
|
|
1022
|
+
} catch {
|
|
1023
|
+
return null;
|
|
1024
|
+
}
|
|
1025
|
+
});
|
|
1026
|
+
if (!ask2 && size > 16 * 1024 * 1024) state.save(NAME, id, { first_ask_none: true });
|
|
1027
|
+
}
|
|
1028
|
+
if (ask2) {
|
|
1029
|
+
out.first_ask = ask2;
|
|
1030
|
+
if (!mem.first_ask) state.save(NAME, id, { first_ask: ask2 });
|
|
1031
|
+
}
|
|
1032
|
+
if (pick) {
|
|
1033
|
+
out.title = String(pick.title).slice(0, 120);
|
|
1034
|
+
out.title_source = pick.source;
|
|
1035
|
+
} else if (ask2) {
|
|
1036
|
+
out.title = ask2;
|
|
682
1037
|
out.title_source = "first-ask";
|
|
683
|
-
break;
|
|
684
1038
|
}
|
|
685
|
-
const
|
|
1039
|
+
const acct = claudeAccount(dir);
|
|
1040
|
+
if (acct) out.account_id = acct;
|
|
1041
|
+
for (let i = T.length - 1; i >= 0; i--) {
|
|
1042
|
+
const m = T[i].message?.model;
|
|
1043
|
+
if (T[i]?.type === "assistant" && m && m !== "<synthetic>") {
|
|
1044
|
+
out.model = m;
|
|
1045
|
+
break;
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
const meta = [...T].reverse().find((j) => j.version) ?? H.find((j) => j.version);
|
|
1049
|
+
if (meta?.version) out.harness_version = String(meta.version);
|
|
1050
|
+
const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
|
|
686
1051
|
if (times.length) {
|
|
687
1052
|
out.started_at = Math.min(...times);
|
|
688
1053
|
out.last_at = Math.max(...times);
|
|
689
1054
|
}
|
|
1055
|
+
const withCwd = H.find((j) => typeof j.cwd === "string");
|
|
1056
|
+
if (withCwd) {
|
|
1057
|
+
out.cwd = withCwd.cwd;
|
|
1058
|
+
if (typeof withCwd.gitBranch === "string" && withCwd.gitBranch !== "HEAD") out.branch = withCwd.gitBranch;
|
|
1059
|
+
}
|
|
690
1060
|
return out;
|
|
691
1061
|
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
1062
|
+
var REMOTE_RETRY_MS = 6e4;
|
|
1063
|
+
var REMOTE_REFRESH_MS = 30 * 6e4;
|
|
1064
|
+
var CLAUDE_API = "https://api.anthropic.com";
|
|
1065
|
+
var KEYCHAIN = "Claude Code-credentials";
|
|
1066
|
+
var REMOTE_TIMEOUT_MS = 3e3;
|
|
1067
|
+
var KEYCHAIN_TIMEOUT_MS = 2e3;
|
|
1068
|
+
var REMOTE_MAX_BYTES = 1 << 20;
|
|
1069
|
+
var cleanName = (s) => s.replace(/[\p{Cc}\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]/gu, " ").replace(/\s+/g, " ").trim();
|
|
1070
|
+
async function claudeRemoteFacts(id, local, transcript, state, env, now = Date.now()) {
|
|
1071
|
+
if (!transcript || !local.url || !LINK_RE.test(local.url) || local.title_source === "custom") return {};
|
|
1072
|
+
const mem = state.get(NAME, id);
|
|
1073
|
+
let title = typeof mem.remote_title === "string" ? mem.remote_title : null;
|
|
1074
|
+
if (now - (Number(mem.remote_at) || 0) >= (title ? REMOTE_REFRESH_MS : REMOTE_RETRY_MS)) {
|
|
1075
|
+
state.save(NAME, id, { remote_at: now });
|
|
1076
|
+
const token = claudeAiToken(configDirOf(transcript), env, now);
|
|
1077
|
+
const got = token ? await claudeAiTitle(local.url.slice(local.url.lastIndexOf("/") + 1), token) : void 0;
|
|
1078
|
+
if (got !== void 0) {
|
|
1079
|
+
title = got?.slice(0, 200) ?? null;
|
|
1080
|
+
state.save(NAME, id, { remote_title: title });
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
return title ? { title: title.slice(0, 120), title_source: "harness" } : {};
|
|
1084
|
+
}
|
|
1085
|
+
function keychainServices(dir, env) {
|
|
1086
|
+
const named = (p) => `${KEYCHAIN}-${createHash("sha256").update(p.normalize("NFC")).digest("hex").slice(0, 8)}`;
|
|
1087
|
+
const out = /* @__PURE__ */ new Set();
|
|
1088
|
+
if (env.CLAUDE_CONFIG_DIR && path4.resolve(env.CLAUDE_CONFIG_DIR) === dir) out.add(named(env.CLAUDE_CONFIG_DIR));
|
|
1089
|
+
if (dir === path4.join(env.HOME || homeDir(), ".claude")) out.add(KEYCHAIN);
|
|
1090
|
+
out.add(named(dir));
|
|
1091
|
+
return [...out];
|
|
1092
|
+
}
|
|
1093
|
+
var tokenCache = /* @__PURE__ */ new Map();
|
|
1094
|
+
function claudeAiToken(dir, env, now) {
|
|
1095
|
+
const hit = tokenCache.get(dir);
|
|
1096
|
+
if (hit && hit.expires > now + 6e4) return hit.token;
|
|
1097
|
+
const raw = [];
|
|
1098
|
+
if (process.platform === "darwin")
|
|
1099
|
+
for (const service of keychainServices(dir, env)) {
|
|
1100
|
+
try {
|
|
1101
|
+
const user = env.USER || userInfo().username;
|
|
1102
|
+
const account = /^[\w.-]+$/.test(user) ? user : "claude-code-user";
|
|
1103
|
+
raw.push(
|
|
1104
|
+
execFileSync2("security", ["find-generic-password", "-a", account, "-w", "-s", service], {
|
|
1105
|
+
encoding: "utf8",
|
|
1106
|
+
timeout: KEYCHAIN_TIMEOUT_MS,
|
|
1107
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1108
|
+
})
|
|
1109
|
+
);
|
|
1110
|
+
break;
|
|
1111
|
+
} catch {
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
try {
|
|
1115
|
+
raw.push(readFileSync2(path4.join(dir, ".credentials.json"), "utf8"));
|
|
1116
|
+
} catch {
|
|
1117
|
+
}
|
|
1118
|
+
for (const r of raw) {
|
|
707
1119
|
try {
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
1120
|
+
const t = r.trim();
|
|
1121
|
+
const o = JSON.parse(/^(?:[0-9a-f]{2})+$/i.test(t) ? Buffer.from(t, "hex").toString("utf8") : t).claudeAiOauth;
|
|
1122
|
+
if (typeof o?.accessToken !== "string" || !o.accessToken) continue;
|
|
1123
|
+
const expires = typeof o.expiresAt === "number" ? o.expiresAt : Number.POSITIVE_INFINITY;
|
|
1124
|
+
if (expires <= now + 6e4) continue;
|
|
1125
|
+
tokenCache.set(dir, { token: o.accessToken, expires });
|
|
1126
|
+
return o.accessToken;
|
|
713
1127
|
} catch {
|
|
714
|
-
return "";
|
|
715
1128
|
}
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
const repo = run(["remote", "get-url", "origin"]);
|
|
719
|
-
if (repo) out.repo = stripCredentials(repo);
|
|
720
|
-
const branch = run(["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
721
|
-
if (branch && branch !== "HEAD") out.branch = branch;
|
|
722
|
-
cache.set(cwd, out);
|
|
723
|
-
return out;
|
|
1129
|
+
}
|
|
1130
|
+
return null;
|
|
724
1131
|
}
|
|
725
|
-
function
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
1132
|
+
async function claudeAiTitle(bridge, token) {
|
|
1133
|
+
try {
|
|
1134
|
+
const r = await fetch(`${CLAUDE_API}/v1/code/sessions/${bridge}`, {
|
|
1135
|
+
headers: { Authorization: `Bearer ${token}`, "anthropic-version": "2023-06-01", "User-Agent": "sessionpipe" },
|
|
1136
|
+
redirect: "error",
|
|
1137
|
+
signal: AbortSignal.timeout(REMOTE_TIMEOUT_MS)
|
|
1138
|
+
});
|
|
1139
|
+
if (r.status !== 200 || Number(r.headers.get("content-length")) > REMOTE_MAX_BYTES) return void 0;
|
|
1140
|
+
const text = await r.text();
|
|
1141
|
+
if (text.length > REMOTE_MAX_BYTES) return void 0;
|
|
1142
|
+
const j = JSON.parse(text);
|
|
1143
|
+
const s = j?.response_shape ?? j?.session ?? j;
|
|
1144
|
+
const bare = (x) => x.replace(/^(?:session|cse)_/, "");
|
|
1145
|
+
if (typeof s?.id !== "string" || bare(s.id) !== bare(bridge)) return void 0;
|
|
1146
|
+
return typeof s.title === "string" && cleanName(s.title) || null;
|
|
1147
|
+
} catch {
|
|
1148
|
+
return void 0;
|
|
1149
|
+
}
|
|
729
1150
|
}
|
|
730
1151
|
|
|
731
|
-
// ../core/src/adapters/
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
"PostToolUse",
|
|
738
|
-
"PostToolUseFailure",
|
|
739
|
-
"PermissionRequest",
|
|
740
|
-
"Notification",
|
|
741
|
-
"Stop",
|
|
742
|
-
"SubagentStart",
|
|
743
|
-
"SubagentStop",
|
|
744
|
-
"PreCompact",
|
|
745
|
-
"PostCompact",
|
|
746
|
-
"SessionEnd",
|
|
747
|
-
"StopFailure"
|
|
748
|
-
];
|
|
749
|
-
var LEAN_SKIP = /* @__PURE__ */ new Set(["PreToolUse", "PostToolUseFailure"]);
|
|
750
|
-
var eventsFor = (lean) => lean ? CLAUDE_EVENTS.filter((e) => !LEAN_SKIP.has(e)) : [...CLAUDE_EVENTS];
|
|
751
|
-
var OPTS = {
|
|
752
|
-
matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0,
|
|
753
|
-
timeout: 5,
|
|
754
|
-
// The hook waits up to 125 s for a verified control answer, and only when control
|
|
755
|
-
// is paired on this machine; everywhere else it exits in milliseconds as before.
|
|
756
|
-
timeoutFor: (e) => e === "PermissionRequest" ? 130 : void 0
|
|
757
|
-
};
|
|
758
|
-
var DESKTOP_ID = /^local_[A-Za-z0-9-]{1,64}$/;
|
|
759
|
-
function claudeDirs(env = process.env, home = env.HOME || homeDir()) {
|
|
760
|
-
const out = [];
|
|
761
|
-
const add = (d) => {
|
|
762
|
-
if (!d) return;
|
|
763
|
-
const r = path5.resolve(d);
|
|
764
|
-
if (!out.includes(r)) out.push(r);
|
|
765
|
-
};
|
|
766
|
-
add(path5.join(home, ".claude"));
|
|
767
|
-
add(env.CLAUDE_CONFIG_DIR);
|
|
1152
|
+
// ../core/src/adapters/headless.ts
|
|
1153
|
+
import { accessSync, constants, statSync as statSync3 } from "node:fs";
|
|
1154
|
+
import path5 from "node:path";
|
|
1155
|
+
var JOB_TIMEOUT_MS = 30 * 6e4;
|
|
1156
|
+
var OUT_CAP = 1024 * 1024;
|
|
1157
|
+
function isExe(p) {
|
|
768
1158
|
try {
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
if (existsSync3(path5.join(d, "projects")) || existsSync3(path5.join(d, "settings.json"))) add(d);
|
|
773
|
-
}
|
|
1159
|
+
if (!statSync3(p).isFile()) return false;
|
|
1160
|
+
accessSync(p, constants.X_OK);
|
|
1161
|
+
return true;
|
|
774
1162
|
} catch {
|
|
1163
|
+
return false;
|
|
775
1164
|
}
|
|
776
|
-
return out.filter((d, i) => i === 0 || existsSync3(d));
|
|
777
1165
|
}
|
|
778
|
-
|
|
779
|
-
|
|
1166
|
+
function findBinary(o) {
|
|
1167
|
+
if (o.override && isExe(o.override)) return o.override;
|
|
1168
|
+
for (const d of String(o.env?.PATH || "").split(path5.delimiter)) {
|
|
1169
|
+
if (!d || !path5.isAbsolute(d)) continue;
|
|
1170
|
+
for (const n of o.names) if (isExe(path5.join(d, n))) return path5.join(d, n);
|
|
1171
|
+
}
|
|
1172
|
+
for (const d of o.dirs) for (const n of o.names) if (isExe(path5.join(d, n))) return path5.join(d, n);
|
|
1173
|
+
return null;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// ../core/src/adapters/antigravity.ts
|
|
1177
|
+
import {
|
|
1178
|
+
existsSync as existsSync3,
|
|
1179
|
+
mkdirSync as mkdirSync2,
|
|
1180
|
+
readdirSync as readdirSync3,
|
|
1181
|
+
readFileSync as readFileSync3,
|
|
1182
|
+
realpathSync,
|
|
1183
|
+
statSync as statSync4,
|
|
1184
|
+
unlinkSync as unlinkSync2,
|
|
1185
|
+
writeFileSync as writeFileSync3
|
|
1186
|
+
} from "node:fs";
|
|
1187
|
+
import path6 from "node:path";
|
|
1188
|
+
var NAME2 = "antigravity";
|
|
1189
|
+
var HOOK_NAME = "sessionpipe";
|
|
1190
|
+
var AG_EVENTS = ["PreInvocation", "PostInvocation", "PostToolUse", "Stop"];
|
|
1191
|
+
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';
|
|
1192
|
+
var antigravityAnswer = (event) => event === "PreToolUse" ? AG_STALE_PRETOOL : "{}\n";
|
|
1193
|
+
var gemini = (env) => env.GEMINI_CLI_HOME || path6.join(homeDir(), ".gemini");
|
|
1194
|
+
var hooksFile = (env) => path6.join(gemini(env), "config", "hooks.json");
|
|
1195
|
+
var projectsDir = (env) => path6.join(gemini(env), "config", "projects");
|
|
1196
|
+
var dataDirs = (env) => ["antigravity", "antigravity-cli", "antigravity-ide"].map((d) => path6.join(gemini(env), d));
|
|
1197
|
+
var ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
|
|
780
1198
|
var validTool2 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
1199
|
+
function entry(cmd) {
|
|
1200
|
+
const handler = (e) => ({ type: "command", command: cmd([NAME2, e]).command, timeout: 5 });
|
|
1201
|
+
const out = { enabled: true };
|
|
1202
|
+
for (const e of AG_EVENTS) out[e] = /Tool/.test(e) ? [{ matcher: "*", hooks: [handler(e)] }] : [handler(e)];
|
|
1203
|
+
return out;
|
|
1204
|
+
}
|
|
1205
|
+
var antigravity = {
|
|
784
1206
|
name: NAME2,
|
|
785
|
-
events:
|
|
786
|
-
detect: (env = process.env) =>
|
|
787
|
-
configFiles: (env = process.env) =>
|
|
788
|
-
install
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
1207
|
+
events: AG_EVENTS,
|
|
1208
|
+
detect: (env = process.env) => existsSync3(path6.join(gemini(env), "config")) || dataDirs(env).some((d) => existsSync3(d)),
|
|
1209
|
+
configFiles: (env = process.env) => [hooksFile(env)],
|
|
1210
|
+
install(cmd, env = process.env) {
|
|
1211
|
+
const file = hooksFile(env);
|
|
1212
|
+
const f = readJsonFile(file);
|
|
1213
|
+
if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
|
|
1214
|
+
const hooks = f.value;
|
|
1215
|
+
const e = entry(cmd);
|
|
1216
|
+
if (JSON.stringify(hooks[HOOK_NAME]) === JSON.stringify(e)) return [{ file, changed: false }];
|
|
1217
|
+
hooks[HOOK_NAME] = e;
|
|
1218
|
+
mkdirSync2(path6.dirname(file), { recursive: true });
|
|
1219
|
+
writeFileSync3(file, formatJson(hooks, f));
|
|
1220
|
+
return [
|
|
1221
|
+
{
|
|
1222
|
+
file,
|
|
1223
|
+
changed: true,
|
|
1224
|
+
created: !f.existed,
|
|
1225
|
+
note: "hooks.json is read when a conversation starts; open ones report after a restart"
|
|
1226
|
+
}
|
|
1227
|
+
];
|
|
1228
|
+
},
|
|
1229
|
+
uninstall(env = process.env, opts = {}) {
|
|
1230
|
+
const file = hooksFile(env);
|
|
1231
|
+
if (!existsSync3(file)) return [{ file, changed: false }];
|
|
1232
|
+
const f = readJsonFile(file);
|
|
1233
|
+
if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
|
|
1234
|
+
const hooks = f.value;
|
|
1235
|
+
if (!(HOOK_NAME in hooks)) return [{ file, changed: false }];
|
|
1236
|
+
delete hooks[HOOK_NAME];
|
|
1237
|
+
if (opts.created?.includes(file) && !Object.keys(hooks).length) {
|
|
1238
|
+
unlinkSync2(file);
|
|
1239
|
+
return [{ file, changed: true, note: "removed (sessionpipe created it)" }];
|
|
1240
|
+
}
|
|
1241
|
+
writeFileSync3(file, formatJson(hooks, f));
|
|
1242
|
+
return [{ file, changed: true }];
|
|
1243
|
+
},
|
|
1244
|
+
installed(cmd, env = process.env) {
|
|
1245
|
+
const file = hooksFile(env);
|
|
1246
|
+
const f = readJsonFile(file);
|
|
1247
|
+
const have = f.ok ? f.value[HOOK_NAME] : void 0;
|
|
1248
|
+
return [
|
|
1249
|
+
{
|
|
1250
|
+
file,
|
|
1251
|
+
state: !have ? "missing" : JSON.stringify(have) === JSON.stringify(entry(cmd)) ? "current" : "stale"
|
|
1252
|
+
}
|
|
1253
|
+
];
|
|
1254
|
+
},
|
|
796
1255
|
fromHook(input) {
|
|
797
1256
|
const event = input.argv[1] ?? "";
|
|
798
1257
|
let s = {};
|
|
@@ -801,133 +1260,48 @@ var claudeCode = {
|
|
|
801
1260
|
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
802
1261
|
} catch {
|
|
803
1262
|
}
|
|
804
|
-
const id = s.session_id ?? s.sessionId;
|
|
805
|
-
|
|
806
|
-
if (
|
|
807
|
-
const
|
|
808
|
-
|
|
809
|
-
if (typeof s.
|
|
810
|
-
|
|
811
|
-
const
|
|
812
|
-
const tool = validTool2(s.tool_name) ? s.tool_name : void 0;
|
|
813
|
-
const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
|
|
814
|
-
const ms = typeof s.duration_ms === "number" ? Math.round(s.duration_ms) : void 0;
|
|
815
|
-
const agent_id = typeof s.agent_id === "string" ? s.agent_id : void 0;
|
|
816
|
-
const permissionId = () => `perm-${createHash("sha256").update(`${turn_id ?? ""}|${tool ?? ""}|${JSON.stringify(s.tool_input ?? null)}`).digest("hex").slice(0, 16)}`;
|
|
1263
|
+
const id = s.conversationId ?? s.session_id ?? s.sessionId;
|
|
1264
|
+
const stdout = antigravityAnswer(event);
|
|
1265
|
+
if (!id) return { session: { id: "" }, events: [], stdout };
|
|
1266
|
+
const workspaces = agWorkspaces(s.workspacePaths);
|
|
1267
|
+
const session = { id: String(id), cwd: workspaces[0] ?? input.cwd };
|
|
1268
|
+
if (typeof s.modelName === "string" && s.modelName !== "auto") session.model = s.modelName;
|
|
1269
|
+
const toolCall = s.toolCall;
|
|
1270
|
+
const tool = validTool2(toolCall?.name) ? toolCall?.name : validTool2(s.toolName) ? s.toolName : void 0;
|
|
817
1271
|
const ev = (type, data) => ({ type, data: prune2(data), harnessEvent: event });
|
|
818
1272
|
const events = [];
|
|
819
1273
|
switch (event) {
|
|
820
|
-
case "
|
|
821
|
-
|
|
1274
|
+
case "PreInvocation":
|
|
1275
|
+
if (Number(s.invocationNum) === 0 || s.invocationNum === void 0) {
|
|
1276
|
+
events.push(ev("session.started", { source: "unknown" }));
|
|
1277
|
+
events.push(ev("turn.started", {}));
|
|
1278
|
+
} else events.push(ev("session.heartbeat", {}));
|
|
822
1279
|
break;
|
|
823
|
-
case "
|
|
824
|
-
events.push(
|
|
825
|
-
ev("turn.started", {
|
|
826
|
-
turn_id,
|
|
827
|
-
prompt_chars: typeof s.prompt === "string" ? [...s.prompt].length : void 0
|
|
828
|
-
})
|
|
829
|
-
);
|
|
1280
|
+
case "PostInvocation":
|
|
830
1281
|
break;
|
|
831
1282
|
case "PreToolUse":
|
|
832
|
-
if (tool) events.push(ev("tool.started", { tool,
|
|
1283
|
+
if (tool) events.push(ev("tool.started", { tool, input: toolCall?.args }));
|
|
833
1284
|
break;
|
|
834
1285
|
case "PostToolUse":
|
|
835
|
-
if (tool)
|
|
836
|
-
|
|
837
|
-
ev("tool.ended", {
|
|
838
|
-
tool,
|
|
839
|
-
call_id,
|
|
840
|
-
turn_id,
|
|
841
|
-
agent_id,
|
|
842
|
-
ms,
|
|
843
|
-
ok: true,
|
|
844
|
-
input: s.tool_input,
|
|
845
|
-
output: s.tool_response
|
|
846
|
-
})
|
|
847
|
-
);
|
|
848
|
-
break;
|
|
849
|
-
case "PostToolUseFailure":
|
|
850
|
-
if (tool)
|
|
1286
|
+
if (!tool) events.push(ev("session.heartbeat", {}));
|
|
1287
|
+
else
|
|
851
1288
|
events.push(
|
|
852
1289
|
ev("tool.ended", {
|
|
853
1290
|
tool,
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
error: errText(s.error ?? s.tool_response),
|
|
860
|
-
input: s.tool_input,
|
|
861
|
-
output: s.tool_response
|
|
862
|
-
})
|
|
863
|
-
);
|
|
864
|
-
break;
|
|
865
|
-
case "PermissionRequest":
|
|
866
|
-
events.push(
|
|
867
|
-
ev("attention.needed", {
|
|
868
|
-
attention_id: call_id ?? permissionId(),
|
|
869
|
-
kind: "permission",
|
|
870
|
-
tool,
|
|
871
|
-
message: tool ? `${tool}${describe(s.tool_input)}` : void 0
|
|
872
|
-
})
|
|
873
|
-
);
|
|
874
|
-
break;
|
|
875
|
-
case "Notification":
|
|
876
|
-
events.push(
|
|
877
|
-
ev("attention.needed", {
|
|
878
|
-
attention_id: `notif-${Date.now()}`,
|
|
879
|
-
kind: notifKind(s.notification_type),
|
|
880
|
-
message: typeof s.message === "string" ? s.message.slice(0, 200) : void 0
|
|
881
|
-
})
|
|
882
|
-
);
|
|
883
|
-
break;
|
|
884
|
-
case "Stop":
|
|
885
|
-
events.push(ev("turn.ended", { turn_id, reason: "stop" }));
|
|
886
|
-
break;
|
|
887
|
-
case "StopFailure":
|
|
888
|
-
events.push(
|
|
889
|
-
ev("turn.ended", {
|
|
890
|
-
turn_id,
|
|
891
|
-
reason: "error",
|
|
892
|
-
error: typeof s.error === "string" ? s.error.slice(0, 200) : void 0
|
|
893
|
-
})
|
|
894
|
-
);
|
|
895
|
-
break;
|
|
896
|
-
case "SubagentStart":
|
|
897
|
-
events.push(
|
|
898
|
-
ev("subagent.started", {
|
|
899
|
-
agent_id: String(s.agent_id ?? ""),
|
|
900
|
-
agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
|
|
901
|
-
})
|
|
902
|
-
);
|
|
903
|
-
break;
|
|
904
|
-
case "SubagentStop":
|
|
905
|
-
events.push(
|
|
906
|
-
ev("subagent.ended", {
|
|
907
|
-
agent_id: String(s.agent_id ?? ""),
|
|
908
|
-
agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
|
|
909
|
-
})
|
|
910
|
-
);
|
|
911
|
-
break;
|
|
912
|
-
case "PreCompact":
|
|
913
|
-
events.push(ev("context.compacted", { trigger: s.trigger === "manual" ? "manual" : "auto" }));
|
|
914
|
-
break;
|
|
915
|
-
case "PostCompact":
|
|
1291
|
+
ok: !s.error,
|
|
1292
|
+
error: typeof s.error === "string" ? s.error.slice(0, 2e3) : void 0,
|
|
1293
|
+
input: toolCall?.args
|
|
1294
|
+
})
|
|
1295
|
+
);
|
|
916
1296
|
break;
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
events.push(ev("session.ended", { reason: endReason(s.reason) }));
|
|
1297
|
+
case "Stop":
|
|
1298
|
+
events.push(ev("turn.ended", { reason: s.error ? "error" : "stop" }));
|
|
920
1299
|
break;
|
|
921
1300
|
default:
|
|
922
|
-
return
|
|
1301
|
+
return { session, events: [], stdout };
|
|
923
1302
|
}
|
|
924
|
-
const
|
|
925
|
-
if (
|
|
926
|
-
const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
|
|
927
|
-
if (host && DESKTOP_ID.test(host)) hints.desktop_id = host;
|
|
928
|
-
if (event !== "PreToolUse" && event !== "PostToolUse") hints.claude_pid = claudePid(input.env);
|
|
929
|
-
const r = { session, events, hints };
|
|
930
|
-
if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
|
|
1303
|
+
const r = { session, events, stdout, hints: { workspaces } };
|
|
1304
|
+
if (typeof s.transcriptPath === "string") r.transcript = s.transcriptPath;
|
|
931
1305
|
return r;
|
|
932
1306
|
},
|
|
933
1307
|
readTranscript(file, fromLine) {
|
|
@@ -937,34 +1311,36 @@ var claudeCode = {
|
|
|
937
1311
|
} catch {
|
|
938
1312
|
return { turns: [], line: fromLine };
|
|
939
1313
|
}
|
|
940
|
-
return
|
|
1314
|
+
return readAntigravity(text.split("\n"), fromLine);
|
|
941
1315
|
},
|
|
942
|
-
facts(session, transcript, hints
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
1316
|
+
facts(session, transcript, hints) {
|
|
1317
|
+
if (!transcript) return {};
|
|
1318
|
+
return antigravityFacts(transcript, {
|
|
1319
|
+
conversation: session.id,
|
|
1320
|
+
workspaces: hints.workspaces ?? (session.cwd ? [session.cwd] : []),
|
|
1321
|
+
env: process.env
|
|
1322
|
+
});
|
|
949
1323
|
},
|
|
950
1324
|
backfill(sinceMs, env = process.env) {
|
|
951
1325
|
const rows2 = [];
|
|
952
|
-
const
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
const id
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
last_at
|
|
967
|
-
|
|
1326
|
+
for (const data of dataDirs(env)) {
|
|
1327
|
+
let ids = [];
|
|
1328
|
+
try {
|
|
1329
|
+
ids = readdirSync3(path6.join(data, "brain")).filter((n) => /^[0-9a-f-]{36}$/i.test(n));
|
|
1330
|
+
} catch {
|
|
1331
|
+
}
|
|
1332
|
+
for (const id of ids) {
|
|
1333
|
+
const f = path6.join(data, "brain", id, ".system_generated", "logs", "transcript.jsonl");
|
|
1334
|
+
try {
|
|
1335
|
+
if (statSync4(f).mtimeMs < sinceMs) continue;
|
|
1336
|
+
} catch {
|
|
1337
|
+
continue;
|
|
1338
|
+
}
|
|
1339
|
+
const facts = antigravityFacts(f, { conversation: id, workspaces: [], env });
|
|
1340
|
+
if (!facts.started_at || !facts.last_at) continue;
|
|
1341
|
+
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
1342
|
+
rows2.push({ session: { id, ...rest }, started_at, last_at });
|
|
1343
|
+
}
|
|
968
1344
|
}
|
|
969
1345
|
return rows2;
|
|
970
1346
|
}
|
|
@@ -973,295 +1349,145 @@ function prune2(o) {
|
|
|
973
1349
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
974
1350
|
return o;
|
|
975
1351
|
}
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
function pickSource(v) {
|
|
981
|
-
return typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
|
|
982
|
-
}
|
|
983
|
-
function errText(v) {
|
|
984
|
-
if (typeof v === "string") return v.slice(0, 2e3);
|
|
985
|
-
if (v && typeof v === "object") {
|
|
986
|
-
const e = v;
|
|
987
|
-
if (typeof e.error === "string") return e.error.slice(0, 2e3);
|
|
988
|
-
if (typeof e.message === "string") return e.message.slice(0, 2e3);
|
|
989
|
-
}
|
|
990
|
-
return void 0;
|
|
991
|
-
}
|
|
992
|
-
function describe(input) {
|
|
993
|
-
if (!input || typeof input !== "object") return "";
|
|
994
|
-
const i = input;
|
|
995
|
-
const v = i.command ?? i.file_path ?? i.path ?? i.url ?? i.pattern;
|
|
996
|
-
return typeof v === "string" ? ` ${v.slice(0, 120)}` : "";
|
|
997
|
-
}
|
|
998
|
-
var NAME_RANK = {
|
|
999
|
-
user: "custom",
|
|
1000
|
-
auto: "harness",
|
|
1001
|
-
hook: "harness",
|
|
1002
|
-
peer: "harness"
|
|
1003
|
-
};
|
|
1004
|
-
function registryEntry(j) {
|
|
1005
|
-
if (!j || typeof j !== "object") return null;
|
|
1006
|
-
const r = j;
|
|
1007
|
-
const source = r.nameSource ? NAME_RANK[r.nameSource] : void 0;
|
|
1008
|
-
return {
|
|
1009
|
-
sessionId: r.sessionId ? String(r.sessionId) : null,
|
|
1010
|
-
name: source && typeof r.name === "string" && r.name.trim() ? r.name.trim() : null,
|
|
1011
|
-
name_source: source ?? null,
|
|
1012
|
-
bridge: typeof r.bridgeSessionId === "string" ? r.bridgeSessionId : null
|
|
1013
|
-
};
|
|
1014
|
-
}
|
|
1015
|
-
var liveCache = /* @__PURE__ */ new Map();
|
|
1016
|
-
function claudeLive(dir) {
|
|
1017
|
-
const hit = liveCache.get(dir);
|
|
1018
|
-
if (hit) return hit;
|
|
1019
|
-
const map = /* @__PURE__ */ new Map();
|
|
1020
|
-
let names = [];
|
|
1021
|
-
try {
|
|
1022
|
-
names = readdirSync3(path5.join(dir, "sessions")).filter((n) => n.endsWith(".json"));
|
|
1023
|
-
} catch {
|
|
1024
|
-
}
|
|
1025
|
-
for (const n of names) {
|
|
1026
|
-
try {
|
|
1027
|
-
const e = registryEntry(JSON.parse(readFileSync3(path5.join(dir, "sessions", n), "utf8")));
|
|
1028
|
-
if (e?.sessionId) map.set(e.sessionId, e);
|
|
1029
|
-
} catch {
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
|
-
liveCache.set(dir, map);
|
|
1033
|
-
return map;
|
|
1034
|
-
}
|
|
1035
|
-
function claudeLiveByPid(dir, pid) {
|
|
1036
|
-
if (!pid) return null;
|
|
1037
|
-
try {
|
|
1038
|
-
return registryEntry(JSON.parse(readFileSync3(path5.join(dir, "sessions", `${pid}.json`), "utf8")));
|
|
1039
|
-
} catch {
|
|
1040
|
-
return null;
|
|
1041
|
-
}
|
|
1042
|
-
}
|
|
1043
|
-
function claudePid(env = process.env) {
|
|
1044
|
-
try {
|
|
1045
|
-
const dirs = claudeDirs(env);
|
|
1046
|
-
const has = (pid2) => dirs.some((d) => existsSync3(path5.join(d, "sessions", `${pid2}.json`)));
|
|
1047
|
-
let pid = process.ppid;
|
|
1048
|
-
if (has(pid)) return pid;
|
|
1049
|
-
const parent = parentOf();
|
|
1050
|
-
for (let hop = 0; hop < 6 && pid > 1; hop++) {
|
|
1051
|
-
pid = parent(pid);
|
|
1052
|
-
if (pid > 1 && has(pid)) return pid;
|
|
1053
|
-
}
|
|
1054
|
-
} catch {
|
|
1055
|
-
}
|
|
1056
|
-
return null;
|
|
1057
|
-
}
|
|
1058
|
-
function parentOf() {
|
|
1059
|
-
if (process.platform === "linux") {
|
|
1060
|
-
return (pid) => {
|
|
1061
|
-
try {
|
|
1062
|
-
const st = readFileSync3(`/proc/${pid}/stat`, "utf8");
|
|
1063
|
-
return Number(st.slice(st.lastIndexOf(")") + 2).split(" ")[1]) || 0;
|
|
1064
|
-
} catch {
|
|
1065
|
-
return 0;
|
|
1066
|
-
}
|
|
1067
|
-
};
|
|
1068
|
-
}
|
|
1069
|
-
const table = /* @__PURE__ */ new Map();
|
|
1070
|
-
try {
|
|
1071
|
-
const out = execFileSync2("ps", ["-axo", "pid=,ppid="], {
|
|
1072
|
-
encoding: "utf8",
|
|
1073
|
-
timeout: 2e3,
|
|
1074
|
-
maxBuffer: 8 * 1024 * 1024,
|
|
1075
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
1076
|
-
});
|
|
1077
|
-
for (const l of out.split("\n")) {
|
|
1078
|
-
const m = /^\s*(\d+)\s+(\d+)/.exec(l);
|
|
1079
|
-
if (m) table.set(Number(m[1]), Number(m[2]));
|
|
1080
|
-
}
|
|
1081
|
-
} catch {
|
|
1082
|
-
}
|
|
1083
|
-
return (pid) => table.get(pid) || 0;
|
|
1084
|
-
}
|
|
1085
|
-
var EMAIL_RE = /^[^@\s]{1,64}@[^@\s]{1,189}$/;
|
|
1086
|
-
function claudeLogin(dir, home = homeDir()) {
|
|
1087
|
-
const files = [path5.join(dir, ".claude.json")];
|
|
1088
|
-
if (path5.resolve(dir) === path5.join(home, ".claude")) files.unshift(path5.join(home, ".claude.json"));
|
|
1089
|
-
for (const f of files) {
|
|
1352
|
+
function agPath(p) {
|
|
1353
|
+
if (typeof p !== "string" || !p) return null;
|
|
1354
|
+
let s = p;
|
|
1355
|
+
if (/^file:/i.test(s)) {
|
|
1090
1356
|
try {
|
|
1091
|
-
|
|
1092
|
-
if (typeof a?.accountUuid === "string" && a.accountUuid) {
|
|
1093
|
-
const email = typeof a.emailAddress === "string" && EMAIL_RE.test(a.emailAddress) ? a.emailAddress : void 0;
|
|
1094
|
-
return { id: a.accountUuid.slice(0, 128), ...email ? { email } : {} };
|
|
1095
|
-
}
|
|
1357
|
+
s = decodeURIComponent(new URL(s).pathname);
|
|
1096
1358
|
} catch {
|
|
1359
|
+
return null;
|
|
1097
1360
|
}
|
|
1098
|
-
|
|
1099
|
-
return null;
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
function claudeAccount(dir) {
|
|
1103
|
-
const hit = accountCache.get(dir);
|
|
1104
|
-
if (hit !== void 0) return hit;
|
|
1105
|
-
const acct = claudeLogin(dir)?.id ?? null;
|
|
1106
|
-
accountCache.set(dir, acct);
|
|
1107
|
-
return acct;
|
|
1108
|
-
}
|
|
1109
|
-
var LINK_RE = /^https:\/\/claude\.ai\/code\/session_[A-Za-z0-9]+$/;
|
|
1110
|
-
function transcriptLink(records) {
|
|
1111
|
-
for (let i = records.length - 1; i >= 0; i--) {
|
|
1112
|
-
const j = records[i];
|
|
1113
|
-
if (j.type === "system" && j.subtype === "bridge_status" && typeof j.url === "string" && LINK_RE.test(j.url))
|
|
1114
|
-
return j.url;
|
|
1115
|
-
if (j.type === "bridge-session" && typeof j.bridgeSessionId === "string") {
|
|
1116
|
-
const m = /^(?:cse|session)_([A-Za-z0-9]+)$/.exec(j.bridgeSessionId);
|
|
1117
|
-
if (m) return `https://claude.ai/code/session_${m[1]}`;
|
|
1118
|
-
}
|
|
1119
|
-
}
|
|
1120
|
-
return null;
|
|
1121
|
-
}
|
|
1122
|
-
function appTitle(text) {
|
|
1123
|
-
const t = String(text).replace(/\s+/g, " ").trim();
|
|
1124
|
-
return t.length > 50 ? `${t.slice(0, 50)}...` : t;
|
|
1125
|
-
}
|
|
1126
|
-
function askText(j) {
|
|
1127
|
-
if (!j || j.type !== "user" || j.isMeta || j.isSidechain) return null;
|
|
1128
|
-
const origin = j.origin;
|
|
1129
|
-
if (origin?.kind && origin.kind !== "human") return null;
|
|
1130
|
-
const c = j.message?.content;
|
|
1131
|
-
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();
|
|
1132
|
-
if (!text || isInjected(text)) return null;
|
|
1133
|
-
return text;
|
|
1134
|
-
}
|
|
1135
|
-
function firstAsk(records) {
|
|
1136
|
-
for (const j of records) {
|
|
1137
|
-
const t = askText(j);
|
|
1138
|
-
if (t) return appTitle(t);
|
|
1139
|
-
}
|
|
1140
|
-
return null;
|
|
1361
|
+
if (process.platform === "win32" && /^\/[A-Za-z]:/.test(s)) s = s.slice(1);
|
|
1362
|
+
} else if (/^[a-z][a-z0-9+.-]*:\/\//i.test(s)) return null;
|
|
1363
|
+
if (!path6.isAbsolute(s)) return null;
|
|
1364
|
+
return path6.resolve(s);
|
|
1141
1365
|
}
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1366
|
+
var agWorkspaces = (paths) => (Array.isArray(paths) ? paths : []).map(agPath).filter((p) => !!p).filter((p, i, a) => a.indexOf(p) === i);
|
|
1367
|
+
function agVariants(p) {
|
|
1368
|
+
const out = [p];
|
|
1145
1369
|
try {
|
|
1146
|
-
|
|
1370
|
+
const r = realpathSync.native(p);
|
|
1371
|
+
if (r !== p) out.push(r);
|
|
1147
1372
|
} catch {
|
|
1148
|
-
|
|
1149
|
-
}
|
|
1150
|
-
if (size < from) {
|
|
1151
|
-
out.upto = 0;
|
|
1152
|
-
out.custom = out.ai = null;
|
|
1153
|
-
from = 0;
|
|
1154
|
-
}
|
|
1155
|
-
let pos = 0;
|
|
1156
|
-
scanLines(
|
|
1157
|
-
file,
|
|
1158
|
-
(l) => {
|
|
1159
|
-
pos += Buffer.byteLength(l, "utf8") + 1;
|
|
1160
|
-
if (pos <= from || l.indexOf('-title"') < 0) return null;
|
|
1161
|
-
try {
|
|
1162
|
-
const j = JSON.parse(l);
|
|
1163
|
-
if (j.type === "custom-title" && j.customTitle) out.custom = j.customTitle;
|
|
1164
|
-
if (j.type === "ai-title" && j.aiTitle) out.ai = j.aiTitle;
|
|
1165
|
-
} catch {
|
|
1166
|
-
}
|
|
1167
|
-
return null;
|
|
1168
|
-
},
|
|
1169
|
-
512 * 1024 * 1024
|
|
1170
|
-
);
|
|
1171
|
-
out.upto = size;
|
|
1373
|
+
}
|
|
1172
1374
|
return out;
|
|
1173
1375
|
}
|
|
1174
|
-
function
|
|
1175
|
-
const
|
|
1176
|
-
|
|
1177
|
-
const T = parseLines(tail);
|
|
1178
|
-
const out = {};
|
|
1179
|
-
const id = sessionId || path5.basename(file, ".jsonl");
|
|
1180
|
-
const mem = state.get(NAME2, id);
|
|
1181
|
-
let custom = null;
|
|
1182
|
-
let ai = null;
|
|
1183
|
-
for (const j of H.concat(T)) {
|
|
1184
|
-
if (j.type === "custom-title" && typeof j.customTitle === "string") custom = j.customTitle;
|
|
1185
|
-
if (j.type === "ai-title" && typeof j.aiTitle === "string") ai = j.aiTitle;
|
|
1186
|
-
}
|
|
1187
|
-
let size = 0;
|
|
1376
|
+
function agProjects(dir) {
|
|
1377
|
+
const out = [];
|
|
1378
|
+
let names = [];
|
|
1188
1379
|
try {
|
|
1189
|
-
|
|
1380
|
+
names = readdirSync3(dir).filter((n) => n.endsWith(".json"));
|
|
1190
1381
|
} catch {
|
|
1382
|
+
return out;
|
|
1191
1383
|
}
|
|
1192
|
-
|
|
1193
|
-
const
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1384
|
+
for (const n of names) {
|
|
1385
|
+
const id = n.slice(0, -5);
|
|
1386
|
+
if (!ID_RE.test(id)) continue;
|
|
1387
|
+
let j;
|
|
1388
|
+
try {
|
|
1389
|
+
j = JSON.parse(readFileSync3(path6.join(dir, n), "utf8"));
|
|
1390
|
+
} catch {
|
|
1391
|
+
continue;
|
|
1392
|
+
}
|
|
1393
|
+
const folders = [];
|
|
1394
|
+
for (const r of j?.projectResources?.resources ?? []) {
|
|
1395
|
+
const f = agPath(r?.folderUri ?? r?.gitFolder?.folderUri);
|
|
1396
|
+
if (f) folders.push(...agVariants(f));
|
|
1397
|
+
}
|
|
1398
|
+
if (folders.length) out.push({ id, folders });
|
|
1200
1399
|
}
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
const
|
|
1205
|
-
const
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
state.save(NAME2, id, { url });
|
|
1400
|
+
return out;
|
|
1401
|
+
}
|
|
1402
|
+
function agMatchProject(projects, workspaces) {
|
|
1403
|
+
const ws = [];
|
|
1404
|
+
for (const w of workspaces) {
|
|
1405
|
+
const p = agPath(w);
|
|
1406
|
+
if (p) ws.push(...agVariants(p));
|
|
1209
1407
|
}
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
} catch {
|
|
1219
|
-
return null;
|
|
1408
|
+
let best = null;
|
|
1409
|
+
let bestLen = -1;
|
|
1410
|
+
for (const pr of projects)
|
|
1411
|
+
for (const f of pr.folders) {
|
|
1412
|
+
const within = ws.some((w) => w === f || w.startsWith(f.endsWith(path6.sep) ? f : f + path6.sep));
|
|
1413
|
+
if (within && f.length > bestLen) {
|
|
1414
|
+
best = pr.id;
|
|
1415
|
+
bestLen = f.length;
|
|
1220
1416
|
}
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1417
|
+
}
|
|
1418
|
+
return best;
|
|
1419
|
+
}
|
|
1420
|
+
function antigravityRemoteUrl(transcript, opts) {
|
|
1421
|
+
const i = transcript.indexOf(`${path6.sep}brain${path6.sep}`);
|
|
1422
|
+
const dirs = (i > 0 ? [transcript.slice(0, i)] : []).concat(dataDirs(opts.env));
|
|
1423
|
+
for (const d of dirs) {
|
|
1424
|
+
try {
|
|
1425
|
+
const m = /installation_uuid:\s*"([0-9a-f-]{36})"/i.exec(
|
|
1426
|
+
readFileSync3(path6.join(d, "antigravity_state.pbtxt"), "utf8")
|
|
1427
|
+
);
|
|
1428
|
+
if (!m) continue;
|
|
1429
|
+
const base = `https://antigravity.google.com/r/${m[1]}-v2`;
|
|
1430
|
+
const conv = opts.conversation ?? "";
|
|
1431
|
+
if (!ID_RE.test(conv) || !opts.workspaces?.length) return base;
|
|
1432
|
+
const project = agMatchProject(agProjects(opts.projectsDir ?? projectsDir(opts.env)), opts.workspaces);
|
|
1433
|
+
return project ? `${base}?p=${encodeURIComponent(`c/${conv}?section=${project}`)}` : base;
|
|
1434
|
+
} catch {
|
|
1435
|
+
}
|
|
1227
1436
|
}
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1437
|
+
return null;
|
|
1438
|
+
}
|
|
1439
|
+
function antigravityFacts(file, opts) {
|
|
1440
|
+
const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
|
|
1441
|
+
const H = parseLines(head);
|
|
1442
|
+
const T = parseLines(tail);
|
|
1443
|
+
const out = {};
|
|
1444
|
+
const url = antigravityRemoteUrl(file, opts);
|
|
1445
|
+
if (url) out.url = url;
|
|
1446
|
+
for (const j of H) {
|
|
1447
|
+
if (j.type !== "USER_INPUT" || typeof j.content !== "string") continue;
|
|
1448
|
+
const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
|
|
1449
|
+
const text = (m ? m[1] : j.content).trim();
|
|
1450
|
+
const line = (text.split("\n").find((l) => l.trim()) ?? "").trim();
|
|
1451
|
+
if (!line || /^<[a-z_-]+>/i.test(line)) continue;
|
|
1452
|
+
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
1233
1453
|
out.title_source = "first-ask";
|
|
1454
|
+
break;
|
|
1234
1455
|
}
|
|
1235
|
-
const
|
|
1236
|
-
if (acct) out.account_id = acct;
|
|
1237
|
-
for (let i = T.length - 1; i >= 0; i--) {
|
|
1238
|
-
const m = T[i].message?.model;
|
|
1239
|
-
if (T[i]?.type === "assistant" && m && m !== "<synthetic>") {
|
|
1240
|
-
out.model = m;
|
|
1241
|
-
break;
|
|
1242
|
-
}
|
|
1243
|
-
}
|
|
1244
|
-
const meta = [...T].reverse().find((j) => j.version) ?? H.find((j) => j.version);
|
|
1245
|
-
if (meta?.version) out.harness_version = String(meta.version);
|
|
1246
|
-
const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
|
|
1456
|
+
const times = H.concat(T).map((j) => Date.parse(String(j.created_at ?? j.timestamp ?? ""))).filter((n) => n > 0);
|
|
1247
1457
|
if (times.length) {
|
|
1248
1458
|
out.started_at = Math.min(...times);
|
|
1249
1459
|
out.last_at = Math.max(...times);
|
|
1250
1460
|
}
|
|
1251
|
-
const withCwd = H.find((j) => typeof j.cwd === "string");
|
|
1252
|
-
if (withCwd) {
|
|
1253
|
-
out.cwd = withCwd.cwd;
|
|
1254
|
-
if (typeof withCwd.gitBranch === "string" && withCwd.gitBranch !== "HEAD") out.branch = withCwd.gitBranch;
|
|
1255
|
-
}
|
|
1256
1461
|
return out;
|
|
1257
1462
|
}
|
|
1258
1463
|
|
|
1464
|
+
// ../core/src/control/verify.ts
|
|
1465
|
+
var GRANT_MAX_MS = 24 * 36e5;
|
|
1466
|
+
var SKEW_MS = 5 * 6e4;
|
|
1467
|
+
var MACHINE_MAX_AGE_MS = 24 * 36e5;
|
|
1468
|
+
var RECEIVER_MAX_AGE_MS = 10 * 6e4;
|
|
1469
|
+
var NONCE_TTL_MS = 25 * 36e5;
|
|
1470
|
+
var MODEL_RE = /^[A-Za-z0-9][A-Za-z0-9._:/@[\]-]*$/;
|
|
1471
|
+
var START_PERMISSIONS = ["read", "safe", "edits", "auto"];
|
|
1472
|
+
var levelRank = (l) => START_PERMISSIONS.indexOf(l);
|
|
1473
|
+
var FIELDS = {
|
|
1474
|
+
prompt: ["text", "files"],
|
|
1475
|
+
"permission.answer": ["for", "decision"],
|
|
1476
|
+
cancel: [],
|
|
1477
|
+
start: ["text", "cwd", "files", "permission", "model"],
|
|
1478
|
+
"key.add": ["key", "name"],
|
|
1479
|
+
pool: ["pool_action", "pool_account", "pool_order", "pool_code"]
|
|
1480
|
+
};
|
|
1481
|
+
var POOL_FIELDS = FIELDS.pool;
|
|
1482
|
+
var enc = new TextEncoder();
|
|
1483
|
+
var dec = new TextDecoder();
|
|
1484
|
+
|
|
1259
1485
|
// ../core/src/adapters/codex.ts
|
|
1260
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync4, writeFileSync as
|
|
1261
|
-
import
|
|
1486
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
1487
|
+
import path7 from "node:path";
|
|
1262
1488
|
|
|
1263
1489
|
// ../core/src/adapters/codex-config.ts
|
|
1264
|
-
var NOTIFY_LINE = /^[ \t]*notify[ \t]*=[ \t]*(\[.*\])[ \t]*(?:#.*)
|
|
1490
|
+
var NOTIFY_LINE = /^[ \t]*notify[ \t]*=[ \t]*(\[.*\])[ \t]*(?:#.*)?(?=\r?$)/m;
|
|
1265
1491
|
var NOTIFY_KEY = /^[ \t]*notify[ \t]*=/m;
|
|
1266
1492
|
var OURS2 = /(?:hook\.js|sessionpipe-hook)",\s*"codex",\s*"notify"/;
|
|
1267
1493
|
function scan(line, st) {
|
|
@@ -1351,10 +1577,11 @@ function dropLine(text, index, length) {
|
|
|
1351
1577
|
}
|
|
1352
1578
|
function insertTopLevel(text, line) {
|
|
1353
1579
|
const { head, tail } = split(text);
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1580
|
+
const body = head.replace(/\s*$/, "");
|
|
1581
|
+
const eol = (body.includes("\n") ? body : text).includes("\r\n") ? "\r\n" : "\n";
|
|
1582
|
+
const top = head.trim() ? head.replace(/\s*$/, eol) : "";
|
|
1583
|
+
if (!tail) return top + line + eol;
|
|
1584
|
+
return `${top + line}${eol}${eol}${tail}`;
|
|
1358
1585
|
}
|
|
1359
1586
|
function withNotify(text, line) {
|
|
1360
1587
|
const n = readNotify(text);
|
|
@@ -1403,10 +1630,10 @@ var CODEX_EVENTS = [
|
|
|
1403
1630
|
];
|
|
1404
1631
|
var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
1405
1632
|
var eventsFor2 = (lean) => lean ? CODEX_EVENTS.filter((e) => e !== "PreToolUse") : [...CODEX_EVENTS];
|
|
1406
|
-
var codexHome = (env) => env.CODEX_HOME ||
|
|
1407
|
-
var hooksFile2 = (env) =>
|
|
1408
|
-
var configFile2 = (env) =>
|
|
1409
|
-
var sessionsDir = (env) =>
|
|
1633
|
+
var codexHome = (env) => env.CODEX_HOME || path7.join(homeDir(), ".codex");
|
|
1634
|
+
var hooksFile2 = (env) => path7.join(codexHome(env), "hooks.json");
|
|
1635
|
+
var configFile2 = (env) => path7.join(codexHome(env), "config.toml");
|
|
1636
|
+
var sessionsDir = (env) => path7.join(codexHome(env), "sessions");
|
|
1410
1637
|
var validTool3 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
1411
1638
|
var endReason2 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
|
|
1412
1639
|
function installNotify(cmd, env) {
|
|
@@ -1420,8 +1647,8 @@ function installNotify(cmd, env) {
|
|
|
1420
1647
|
const line = `notify = ${JSON.stringify(argv)}`;
|
|
1421
1648
|
const n = readNotify(text);
|
|
1422
1649
|
const write = (next) => {
|
|
1423
|
-
mkdirSync3(
|
|
1424
|
-
|
|
1650
|
+
mkdirSync3(path7.dirname(file), { recursive: true });
|
|
1651
|
+
writeFileSync4(file, next);
|
|
1425
1652
|
};
|
|
1426
1653
|
if (n && !n.other) {
|
|
1427
1654
|
if (!n.misplaced && JSON.stringify(n.argv) === JSON.stringify(argv))
|
|
@@ -1466,12 +1693,12 @@ function uninstallNotify(env) {
|
|
|
1466
1693
|
const n = readNotify(text);
|
|
1467
1694
|
if (!n) return { file, changed: false };
|
|
1468
1695
|
if (!n.other) {
|
|
1469
|
-
|
|
1696
|
+
writeFileSync4(file, withoutNotify(text));
|
|
1470
1697
|
return { file, changed: true };
|
|
1471
1698
|
}
|
|
1472
1699
|
const prev = previousNotify(n.argv);
|
|
1473
1700
|
if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev))) {
|
|
1474
|
-
|
|
1701
|
+
writeFileSync4(file, replaceNotify(text, n, `notify = ${stripPreviousRaw(n.raw)}`));
|
|
1475
1702
|
return { file, changed: true };
|
|
1476
1703
|
}
|
|
1477
1704
|
return { file, changed: false };
|
|
@@ -1487,7 +1714,7 @@ function findRollout(threadId, env = process.env) {
|
|
|
1487
1714
|
return;
|
|
1488
1715
|
}
|
|
1489
1716
|
for (const n of names.sort().reverse()) {
|
|
1490
|
-
const p =
|
|
1717
|
+
const p = path7.join(dir, n);
|
|
1491
1718
|
if (n.endsWith(`-${threadId}.jsonl`)) {
|
|
1492
1719
|
best = p;
|
|
1493
1720
|
return;
|
|
@@ -1577,7 +1804,7 @@ var codex = {
|
|
|
1577
1804
|
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1578
1805
|
} catch {
|
|
1579
1806
|
}
|
|
1580
|
-
const id = s.session_id;
|
|
1807
|
+
const id = s.session_id ?? s.thread_id ?? s.conversation_id ?? s["thread-id"] ?? s["session-id"];
|
|
1581
1808
|
if (!id) return null;
|
|
1582
1809
|
if (s.transcript_path === null) return null;
|
|
1583
1810
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
@@ -1672,7 +1899,7 @@ var codex = {
|
|
|
1672
1899
|
backfill(sinceMs, env = process.env) {
|
|
1673
1900
|
const rows2 = [];
|
|
1674
1901
|
for (const f of recentFiles(sessionsDir(env), 3, sinceMs, (n) => /^rollout-.*\.jsonl$/.test(n))) {
|
|
1675
|
-
const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(
|
|
1902
|
+
const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(path7.basename(f));
|
|
1676
1903
|
if (!m?.[1]) continue;
|
|
1677
1904
|
if (codexSubagent(rolloutMeta(f))) continue;
|
|
1678
1905
|
const facts = codexFacts(f);
|
|
@@ -1681,12 +1908,40 @@ var codex = {
|
|
|
1681
1908
|
rows2.push({ session: { id: m[1], ...rest }, started_at, last_at });
|
|
1682
1909
|
}
|
|
1683
1910
|
return rows2;
|
|
1684
|
-
}
|
|
1911
|
+
},
|
|
1912
|
+
limits: (transcript) => codexLimits(transcript)
|
|
1685
1913
|
};
|
|
1914
|
+
var epochIso2 = (t) => typeof t === "number" && t > 0 && t < 1e11 ? new Date(t * 1e3).toISOString() : void 0;
|
|
1686
1915
|
function prune3(o) {
|
|
1687
1916
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
1688
1917
|
return o;
|
|
1689
1918
|
}
|
|
1919
|
+
function codexLimits(file) {
|
|
1920
|
+
const { tail } = ends(file, 0, 256 * 1024);
|
|
1921
|
+
for (const r of parseLines(tail).reverse()) {
|
|
1922
|
+
const p = r.payload;
|
|
1923
|
+
if (r.type !== "event_msg" || p?.type !== "token_count") continue;
|
|
1924
|
+
if (!p.rate_limits || typeof p.rate_limits !== "object") continue;
|
|
1925
|
+
const rl = p.rate_limits;
|
|
1926
|
+
if (rl.limit_id != null && rl.limit_id !== "codex") continue;
|
|
1927
|
+
const windows = [];
|
|
1928
|
+
for (const id of ["primary", "secondary"]) {
|
|
1929
|
+
const w = rl[id];
|
|
1930
|
+
if (typeof w?.used_percent !== "number" || w.used_percent < 0) continue;
|
|
1931
|
+
windows.push(
|
|
1932
|
+
prune3({
|
|
1933
|
+
id,
|
|
1934
|
+
used_percent: w.used_percent,
|
|
1935
|
+
window_minutes: Number.isInteger(w.window_minutes) && Number(w.window_minutes) > 0 ? Number(w.window_minutes) : void 0,
|
|
1936
|
+
resets_at: epochIso2(w.resets_at)
|
|
1937
|
+
})
|
|
1938
|
+
);
|
|
1939
|
+
}
|
|
1940
|
+
if (!windows.length) return null;
|
|
1941
|
+
return prune3({ windows, plan: typeof rl.plan_type === "string" ? rl.plan_type.slice(0, 64) : void 0 });
|
|
1942
|
+
}
|
|
1943
|
+
return null;
|
|
1944
|
+
}
|
|
1690
1945
|
function codexFacts(file) {
|
|
1691
1946
|
const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
|
|
1692
1947
|
let H = parseLines(head);
|
|
@@ -1731,7 +1986,7 @@ function codexFacts(file) {
|
|
|
1731
1986
|
// ../core/src/adapters/devin.ts
|
|
1732
1987
|
import { createHash as createHash2 } from "node:crypto";
|
|
1733
1988
|
import { existsSync as existsSync6, readlinkSync } from "node:fs";
|
|
1734
|
-
import
|
|
1989
|
+
import path8 from "node:path";
|
|
1735
1990
|
|
|
1736
1991
|
// ../core/src/readers/sqlite.ts
|
|
1737
1992
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
@@ -1915,11 +2170,11 @@ function foreignEvent(file) {
|
|
|
1915
2170
|
return Object.keys(hooks).find((k) => !ok.includes(k));
|
|
1916
2171
|
}
|
|
1917
2172
|
var homeOf = (env) => env.HOME || homeDir();
|
|
1918
|
-
var appData = (env) => env.APPDATA ||
|
|
1919
|
-
var devinConfigDir = (env = process.env) => process.platform === "win32" ?
|
|
1920
|
-
var configFile3 = (env) =>
|
|
1921
|
-
var devinDataDir = (env = process.env) => process.platform === "win32" ?
|
|
1922
|
-
var sessionsDb = (env) =>
|
|
2173
|
+
var appData = (env) => env.APPDATA || path8.join(homeOf(env), "AppData", "Roaming");
|
|
2174
|
+
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");
|
|
2175
|
+
var configFile3 = (env) => path8.join(devinConfigDir(env), "config.json");
|
|
2176
|
+
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");
|
|
2177
|
+
var sessionsDb = (env) => path8.join(devinDataDir(env), "sessions.db");
|
|
1923
2178
|
var SESSION_ID = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
1924
2179
|
var validTool4 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
1925
2180
|
var endReason3 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
|
|
@@ -2091,7 +2346,7 @@ var BACKFILL_SQL = `SELECT s.id, ${SESSION_FIELDS} FROM sessions s
|
|
|
2091
2346
|
function sessionFacts(row) {
|
|
2092
2347
|
const out = {};
|
|
2093
2348
|
if (typeof row.model === "string" && row.model) out.model = row.model;
|
|
2094
|
-
if (typeof row.working_directory === "string" &&
|
|
2349
|
+
if (typeof row.working_directory === "string" && path8.isAbsolute(row.working_directory))
|
|
2095
2350
|
out.cwd = row.working_directory;
|
|
2096
2351
|
const started = Number(row.created_at);
|
|
2097
2352
|
const last = Number(row.last_activity_at);
|
|
@@ -2136,8 +2391,8 @@ function readDevinChain(db, session, fromLine) {
|
|
|
2136
2391
|
}
|
|
2137
2392
|
function devinVersion(env = process.env) {
|
|
2138
2393
|
try {
|
|
2139
|
-
const target = readlinkSync(
|
|
2140
|
-
const name =
|
|
2394
|
+
const target = readlinkSync(path8.join(devinDataDir(env), "_versions", "current"));
|
|
2395
|
+
const name = path8.basename(target);
|
|
2141
2396
|
return /^[0-9][0-9A-Za-z.+-]{0,63}$/.test(name) ? name : void 0;
|
|
2142
2397
|
} catch {
|
|
2143
2398
|
return void 0;
|
|
@@ -2152,7 +2407,7 @@ function devinAccount(env = process.env) {
|
|
|
2152
2407
|
// ../core/src/adapters/gemini-cli.ts
|
|
2153
2408
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
2154
2409
|
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
|
|
2155
|
-
import
|
|
2410
|
+
import path9 from "node:path";
|
|
2156
2411
|
var NAME5 = "gemini-cli";
|
|
2157
2412
|
var GEMINI_EVENTS = [
|
|
2158
2413
|
"SessionStart",
|
|
@@ -2165,26 +2420,31 @@ var GEMINI_EVENTS = [
|
|
|
2165
2420
|
"SessionEnd"
|
|
2166
2421
|
];
|
|
2167
2422
|
var OPTS4 = { matcherFor: (e) => /Tool/.test(e) ? ".*" : void 0, timeout: 5e3 };
|
|
2168
|
-
var geminiDir = (env) => env.GEMINI_CLI_HOME ||
|
|
2169
|
-
var settings = (env) =>
|
|
2423
|
+
var geminiDir = (env) => env.GEMINI_CLI_HOME || path9.join(homeDir(), ".gemini");
|
|
2424
|
+
var settings = (env) => path9.join(geminiDir(env), "settings.json");
|
|
2170
2425
|
var validTool5 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
2171
2426
|
var eventsFor4 = (lean) => lean ? GEMINI_EVENTS.filter((e) => e !== "BeforeTool") : [...GEMINI_EVENTS];
|
|
2427
|
+
function geminiVersion(platform = process.platform, env = process.env) {
|
|
2428
|
+
const win = platform === "win32";
|
|
2429
|
+
const bin = win ? findBinary({ names: ["gemini.exe", "gemini.cmd"], dirs: [path9.dirname(process.execPath)], env }) : "gemini";
|
|
2430
|
+
if (!bin) throw new Error("no gemini on PATH");
|
|
2431
|
+
const o = {
|
|
2432
|
+
encoding: "utf8",
|
|
2433
|
+
timeout: 4e3,
|
|
2434
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2435
|
+
env
|
|
2436
|
+
};
|
|
2437
|
+
const out = /\.cmd$/i.test(bin) ? execFileSync4(`"${bin}" --version`, { ...o, shell: true }) : execFileSync4(bin, ["--version"], o);
|
|
2438
|
+
return out.trim();
|
|
2439
|
+
}
|
|
2172
2440
|
function geminiHooksActive(settingsFile) {
|
|
2173
|
-
|
|
2174
|
-
try {
|
|
2175
|
-
tools = JSON.parse(readFileSync5(settingsFile, "utf8")).tools;
|
|
2176
|
-
} catch {
|
|
2177
|
-
}
|
|
2441
|
+
const tools = readJson(settingsFile).tools;
|
|
2178
2442
|
if (tools?.enableHooks === true) return { active: true };
|
|
2179
2443
|
if (tools?.enableHooks === false)
|
|
2180
2444
|
return { active: false, note: "inactive: tools.enableHooks is false in settings.json" };
|
|
2181
2445
|
let version = "";
|
|
2182
2446
|
try {
|
|
2183
|
-
version =
|
|
2184
|
-
encoding: "utf8",
|
|
2185
|
-
timeout: 4e3,
|
|
2186
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
2187
|
-
}).trim();
|
|
2447
|
+
version = geminiVersion();
|
|
2188
2448
|
} catch {
|
|
2189
2449
|
return {
|
|
2190
2450
|
active: true,
|
|
@@ -2202,7 +2462,7 @@ var geminiCli = {
|
|
|
2202
2462
|
name: NAME5,
|
|
2203
2463
|
events: GEMINI_EVENTS,
|
|
2204
2464
|
// ~/.gemini also belongs to Antigravity; Gemini CLI is here when its settings or its chat logs exist.
|
|
2205
|
-
detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(
|
|
2465
|
+
detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(path9.join(geminiDir(env), "tmp")),
|
|
2206
2466
|
configFiles: (env = process.env) => [settings(env)],
|
|
2207
2467
|
install: (cmd, env = process.env, opts = {}) => {
|
|
2208
2468
|
const r = installClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
|
|
@@ -2340,8 +2600,8 @@ var ADAPTERS = [claudeCode, codex, geminiCli, antigravity, devin];
|
|
|
2340
2600
|
var adapterByName = (name) => ADAPTERS.find((a) => a.name === name);
|
|
2341
2601
|
|
|
2342
2602
|
// ../core/src/config.ts
|
|
2343
|
-
import { chmodSync, existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as
|
|
2344
|
-
import
|
|
2603
|
+
import { chmodSync, existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
|
|
2604
|
+
import path10 from "node:path";
|
|
2345
2605
|
var DEFAULT_CONFIG = { sinks: [], harnesses: {}, update_check: true, keep_days: 30 };
|
|
2346
2606
|
function readConfig(file = configFile()) {
|
|
2347
2607
|
try {
|
|
@@ -2357,8 +2617,8 @@ function readConfig(file = configFile()) {
|
|
|
2357
2617
|
}
|
|
2358
2618
|
}
|
|
2359
2619
|
function writeConfig(c, file = configFile()) {
|
|
2360
|
-
mkdirSync4(
|
|
2361
|
-
|
|
2620
|
+
mkdirSync4(path10.dirname(file), { recursive: true, mode: 448 });
|
|
2621
|
+
writeFileSync5(file, `${JSON.stringify(c, null, 2)}
|
|
2362
2622
|
`, { mode: 384 });
|
|
2363
2623
|
if (process.platform !== "win32" && existsSync8(file)) {
|
|
2364
2624
|
try {
|
|
@@ -2421,6 +2681,22 @@ function makeEvent(a, ctx) {
|
|
|
2421
2681
|
};
|
|
2422
2682
|
}
|
|
2423
2683
|
|
|
2684
|
+
// ../core/src/launched-by.ts
|
|
2685
|
+
var LAUNCHED_BY_ENV = "SESSIONPIPE_LAUNCHED_BY";
|
|
2686
|
+
var LAUNCHED_BY = /^[a-z][a-z0-9-]{0,39}:[A-Za-z0-9._:-]{1,128}$/;
|
|
2687
|
+
function launchedBy(env) {
|
|
2688
|
+
const v = env[LAUNCHED_BY_ENV]?.trim();
|
|
2689
|
+
return v && LAUNCHED_BY.test(v) ? v : void 0;
|
|
2690
|
+
}
|
|
2691
|
+
function readHook(adapter, input) {
|
|
2692
|
+
const r = adapter.fromHook(input);
|
|
2693
|
+
if (r && !r.session.parent_id) {
|
|
2694
|
+
const launcher = launchedBy(input.env);
|
|
2695
|
+
if (launcher && launcher !== `${adapter.name}:${r.session.id}`) r.session.launched_by = launcher;
|
|
2696
|
+
}
|
|
2697
|
+
return r;
|
|
2698
|
+
}
|
|
2699
|
+
|
|
2424
2700
|
// ../core/src/outbox.ts
|
|
2425
2701
|
import {
|
|
2426
2702
|
appendFileSync,
|
|
@@ -2432,11 +2708,11 @@ import {
|
|
|
2432
2708
|
readFileSync as readFileSync7,
|
|
2433
2709
|
readSync as readSync2,
|
|
2434
2710
|
renameSync,
|
|
2435
|
-
statSync as
|
|
2711
|
+
statSync as statSync5,
|
|
2436
2712
|
unlinkSync as unlinkSync3,
|
|
2437
|
-
writeFileSync as
|
|
2713
|
+
writeFileSync as writeFileSync6
|
|
2438
2714
|
} from "node:fs";
|
|
2439
|
-
import
|
|
2715
|
+
import path11 from "node:path";
|
|
2440
2716
|
var READ_CHUNK = 1024 * 1024;
|
|
2441
2717
|
var READ_MAX = 8 * 1024 * 1024;
|
|
2442
2718
|
var Outbox = class {
|
|
@@ -2445,13 +2721,13 @@ var Outbox = class {
|
|
|
2445
2721
|
}
|
|
2446
2722
|
root;
|
|
2447
2723
|
dir(harness) {
|
|
2448
|
-
return
|
|
2724
|
+
return path11.join(this.root, "outbox", safeId(harness));
|
|
2449
2725
|
}
|
|
2450
2726
|
file(ref) {
|
|
2451
|
-
return
|
|
2727
|
+
return path11.join(this.dir(ref.harness), `${safeId(ref.session)}.jsonl`);
|
|
2452
2728
|
}
|
|
2453
2729
|
seqFile(ref) {
|
|
2454
|
-
return
|
|
2730
|
+
return path11.join(this.dir(ref.harness), `${safeId(ref.session)}.seq`);
|
|
2455
2731
|
}
|
|
2456
2732
|
/** The next seq for a session, persisted (survives a hibernating machine and a
|
|
2457
2733
|
* restart; an in-memory counter would restart at 0 and collide). */
|
|
@@ -2464,8 +2740,8 @@ var Outbox = class {
|
|
|
2464
2740
|
} catch {
|
|
2465
2741
|
}
|
|
2466
2742
|
if (n < 0) n = this.lastSeqInFile(ref) + 1;
|
|
2467
|
-
mkdirSync5(
|
|
2468
|
-
|
|
2743
|
+
mkdirSync5(path11.dirname(f), { recursive: true, mode: 448 });
|
|
2744
|
+
writeFileSync6(f, String(n + 1), { mode: 384 });
|
|
2469
2745
|
return n;
|
|
2470
2746
|
}
|
|
2471
2747
|
lastSeqInFile(ref) {
|
|
@@ -2490,10 +2766,10 @@ var Outbox = class {
|
|
|
2490
2766
|
/** Every session file, newest first. */
|
|
2491
2767
|
sessions() {
|
|
2492
2768
|
const out = [];
|
|
2493
|
-
const base =
|
|
2769
|
+
const base = path11.join(this.root, "outbox");
|
|
2494
2770
|
if (!existsSync9(base)) return [];
|
|
2495
2771
|
for (const h of readdirSync5(base)) {
|
|
2496
|
-
const d =
|
|
2772
|
+
const d = path11.join(base, h);
|
|
2497
2773
|
let names = [];
|
|
2498
2774
|
try {
|
|
2499
2775
|
names = readdirSync5(d).filter((n) => n.endsWith(".jsonl"));
|
|
@@ -2502,7 +2778,7 @@ var Outbox = class {
|
|
|
2502
2778
|
}
|
|
2503
2779
|
for (const n of names) {
|
|
2504
2780
|
try {
|
|
2505
|
-
out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime:
|
|
2781
|
+
out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime: statSync5(path11.join(d, n)).mtimeMs });
|
|
2506
2782
|
} catch {
|
|
2507
2783
|
}
|
|
2508
2784
|
}
|
|
@@ -2518,7 +2794,7 @@ var Outbox = class {
|
|
|
2518
2794
|
read(ref, from, maxEvents = 500) {
|
|
2519
2795
|
const f = this.file(ref);
|
|
2520
2796
|
if (!existsSync9(f)) return { events: [], offset: from };
|
|
2521
|
-
const size =
|
|
2797
|
+
const size = statSync5(f).size;
|
|
2522
2798
|
if (from >= size) return { events: [], offset: from };
|
|
2523
2799
|
const fd = openSync2(f, "r");
|
|
2524
2800
|
try {
|
|
@@ -2560,7 +2836,7 @@ var Outbox = class {
|
|
|
2560
2836
|
for (const ref of this.sessions()) {
|
|
2561
2837
|
const f = this.file(ref);
|
|
2562
2838
|
try {
|
|
2563
|
-
if (
|
|
2839
|
+
if (statSync5(f).mtimeMs < cutoff) {
|
|
2564
2840
|
unlinkSync3(f);
|
|
2565
2841
|
try {
|
|
2566
2842
|
unlinkSync3(this.seqFile(ref));
|
|
@@ -2590,7 +2866,7 @@ var Cursors = class {
|
|
|
2590
2866
|
root;
|
|
2591
2867
|
sink;
|
|
2592
2868
|
file() {
|
|
2593
|
-
return
|
|
2869
|
+
return path11.join(this.root, "cursors", `${safeId(this.sink)}.json`);
|
|
2594
2870
|
}
|
|
2595
2871
|
read() {
|
|
2596
2872
|
try {
|
|
@@ -2601,9 +2877,9 @@ var Cursors = class {
|
|
|
2601
2877
|
}
|
|
2602
2878
|
write(c) {
|
|
2603
2879
|
const f = this.file();
|
|
2604
|
-
mkdirSync5(
|
|
2880
|
+
mkdirSync5(path11.dirname(f), { recursive: true, mode: 448 });
|
|
2605
2881
|
const tmp = `${f}.${process.pid}.tmp`;
|
|
2606
|
-
|
|
2882
|
+
writeFileSync6(tmp, JSON.stringify(c), { mode: 384 });
|
|
2607
2883
|
renameSync(tmp, f);
|
|
2608
2884
|
}
|
|
2609
2885
|
key(ref) {
|
|
@@ -2614,16 +2890,16 @@ function takeLock(root, ref, staleMs = 5 * 6e4) {
|
|
|
2614
2890
|
return takeNamedLock(root, `${safeId(ref.harness)}-${safeId(ref.session)}`, staleMs);
|
|
2615
2891
|
}
|
|
2616
2892
|
function takeNamedLock(root, name, staleMs = 5 * 6e4) {
|
|
2617
|
-
const dir =
|
|
2893
|
+
const dir = path11.join(root, "locks");
|
|
2618
2894
|
mkdirSync5(dir, { recursive: true, mode: 448 });
|
|
2619
|
-
const f =
|
|
2895
|
+
const f = path11.join(dir, `${name}.lock`);
|
|
2620
2896
|
try {
|
|
2621
|
-
if (Date.now() -
|
|
2897
|
+
if (Date.now() - statSync5(f).mtimeMs > staleMs || !processAlive(Number(readFileSync7(f, "utf8")))) unlinkSync3(f);
|
|
2622
2898
|
else return null;
|
|
2623
2899
|
} catch {
|
|
2624
2900
|
}
|
|
2625
2901
|
try {
|
|
2626
|
-
|
|
2902
|
+
writeFileSync6(f, String(process.pid), { flag: "wx" });
|
|
2627
2903
|
} catch {
|
|
2628
2904
|
return null;
|
|
2629
2905
|
}
|
|
@@ -2855,6 +3131,7 @@ var TYPE_TIER = {
|
|
|
2855
3131
|
"subagent.started": 1,
|
|
2856
3132
|
"subagent.ended": 1,
|
|
2857
3133
|
"context.compacted": 1,
|
|
3134
|
+
"limits.observed": 1,
|
|
2858
3135
|
native: 3
|
|
2859
3136
|
};
|
|
2860
3137
|
var DATA_ALLOW = {
|
|
@@ -2863,8 +3140,8 @@ var DATA_ALLOW = {
|
|
|
2863
3140
|
"session.ended": { 0: ["reason"] },
|
|
2864
3141
|
"session.backfill": { 0: ["started_at", "last_at", "turns"] },
|
|
2865
3142
|
"session.forgotten": { 0: [] },
|
|
2866
|
-
"turn.started": { 0: ["turn_id"], 1: ["prompt_chars"] },
|
|
2867
|
-
"turn.ended": { 0: ["turn_id", "reason", "ms"], 1: ["error"] },
|
|
3143
|
+
"turn.started": { 0: ["turn_id", "submitted_by", "injected"], 1: ["prompt_chars"] },
|
|
3144
|
+
"turn.ended": { 0: ["turn_id", "reason", "ms", "background"], 1: ["error"] },
|
|
2868
3145
|
"turn.transcript": { 2: ["turn", "user", "assistant", "at"] },
|
|
2869
3146
|
"turn.progress": { 2: ["turn", "user", "assistant", "at"] },
|
|
2870
3147
|
"tool.started": { 1: ["tool", "call_id", "turn_id", "agent_id"], 3: ["input"] },
|
|
@@ -2874,7 +3151,8 @@ var DATA_ALLOW = {
|
|
|
2874
3151
|
"attention.cleared": { 0: ["attention_id", "how"] },
|
|
2875
3152
|
"subagent.started": { 1: ["agent_id", "agent_type"] },
|
|
2876
3153
|
"subagent.ended": { 1: ["agent_id", "agent_type"] },
|
|
2877
|
-
"context.compacted": { 1: ["trigger"] }
|
|
3154
|
+
"context.compacted": { 1: ["trigger"] },
|
|
3155
|
+
"limits.observed": { 1: ["windows", "plan", "account_id"] }
|
|
2878
3156
|
};
|
|
2879
3157
|
var SESSION_ALLOW = [
|
|
2880
3158
|
"id",
|
|
@@ -2888,7 +3166,9 @@ var SESSION_ALLOW = [
|
|
|
2888
3166
|
"title_source",
|
|
2889
3167
|
"url",
|
|
2890
3168
|
"account_id",
|
|
2891
|
-
"
|
|
3169
|
+
"attended",
|
|
3170
|
+
"parent_id",
|
|
3171
|
+
"launched_by"
|
|
2892
3172
|
];
|
|
2893
3173
|
var MAX_SIDE = 2e4;
|
|
2894
3174
|
var MAX_IO = 64 * 1024;
|
|
@@ -2937,7 +3217,9 @@ function filterEvent(event, tier, pii, opts = {}) {
|
|
|
2937
3217
|
const s = reducePiiDeep(out.session, opts.home);
|
|
2938
3218
|
if (typeof s.machine === "string") s.machine = hashMachine(s.machine);
|
|
2939
3219
|
delete s.account_id;
|
|
2940
|
-
|
|
3220
|
+
const d = reducePiiDeep(out.data, opts.home);
|
|
3221
|
+
delete d.account_id;
|
|
3222
|
+
out = { ...out, session: s, data: d };
|
|
2941
3223
|
rulesets.push(RULESET_PII);
|
|
2942
3224
|
}
|
|
2943
3225
|
out.privacy = { rulesets, pii };
|
|
@@ -2952,7 +3234,7 @@ function shortId(harness, id) {
|
|
|
2952
3234
|
|
|
2953
3235
|
// ../core/src/sinks/file.ts
|
|
2954
3236
|
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync6 } from "node:fs";
|
|
2955
|
-
import
|
|
3237
|
+
import path12 from "node:path";
|
|
2956
3238
|
var FileSink = class {
|
|
2957
3239
|
constructor(name, file, tier, pii) {
|
|
2958
3240
|
this.name = name;
|
|
@@ -2966,7 +3248,7 @@ var FileSink = class {
|
|
|
2966
3248
|
pii;
|
|
2967
3249
|
async send(events) {
|
|
2968
3250
|
try {
|
|
2969
|
-
mkdirSync6(
|
|
3251
|
+
mkdirSync6(path12.dirname(this.file), { recursive: true });
|
|
2970
3252
|
appendFileSync2(this.file, `${events.map((e) => JSON.stringify(e)).join("\n")}
|
|
2971
3253
|
`);
|
|
2972
3254
|
return { ok: true, acknowledged: events.length, accepted: events.length, duplicates: 0, rejected: [] };
|
|
@@ -3007,17 +3289,60 @@ import {
|
|
|
3007
3289
|
readdirSync as readdirSync6,
|
|
3008
3290
|
readFileSync as readFileSync9,
|
|
3009
3291
|
renameSync as renameSync3,
|
|
3010
|
-
statSync as
|
|
3292
|
+
statSync as statSync6,
|
|
3011
3293
|
unlinkSync as unlinkSync4,
|
|
3012
|
-
writeFileSync as
|
|
3294
|
+
writeFileSync as writeFileSync8
|
|
3013
3295
|
} from "node:fs";
|
|
3014
3296
|
import os3 from "node:os";
|
|
3015
|
-
import
|
|
3297
|
+
import path15 from "node:path";
|
|
3016
3298
|
|
|
3017
3299
|
// src/control/aliases.ts
|
|
3018
|
-
import { chmodSync as chmodSync2, mkdirSync as mkdirSync7, readFileSync as readFileSync8, renameSync as renameSync2, writeFileSync as
|
|
3019
|
-
import
|
|
3020
|
-
|
|
3300
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync7, readFileSync as readFileSync8, renameSync as renameSync2, writeFileSync as writeFileSync7 } from "node:fs";
|
|
3301
|
+
import path13 from "node:path";
|
|
3302
|
+
|
|
3303
|
+
// src/secrets.ts
|
|
3304
|
+
import { spawnSync } from "node:child_process";
|
|
3305
|
+
import { createHash as createHash5, randomBytes as randomBytes2 } from "node:crypto";
|
|
3306
|
+
var SERVICE = "sessionpipe";
|
|
3307
|
+
var defaultRunner = (cmd, args, input) => {
|
|
3308
|
+
const r = spawnSync(cmd, args, {
|
|
3309
|
+
input: input ?? "",
|
|
3310
|
+
encoding: "utf8",
|
|
3311
|
+
timeout: 8e3,
|
|
3312
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
3313
|
+
});
|
|
3314
|
+
return { status: r.error ? null : r.status, stdout: r.stdout ?? "" };
|
|
3315
|
+
};
|
|
3316
|
+
var sinkAccount = (name, what = "token") => {
|
|
3317
|
+
const scope = process.env.SESSIONPIPE_CONFIG ? `@${createHash5("sha256").update(configFile()).digest("hex").slice(0, 8)}` : "";
|
|
3318
|
+
return `sink:${name}:${what}${scope}`;
|
|
3319
|
+
};
|
|
3320
|
+
function secretGet(store, account, run = defaultRunner) {
|
|
3321
|
+
const r = store === "keychain" ? run("security", ["find-generic-password", "-s", SERVICE, "-a", account, "-w"]) : run("secret-tool", ["lookup", "service", SERVICE, "account", account]);
|
|
3322
|
+
if (r.status !== 0) return null;
|
|
3323
|
+
const v = r.stdout.replace(/\r?\n$/, "");
|
|
3324
|
+
return v || null;
|
|
3325
|
+
}
|
|
3326
|
+
function sinkWithSecrets(s, run = defaultRunner) {
|
|
3327
|
+
if (!s.token_in) return s;
|
|
3328
|
+
const token = secretGet(s.token_in, sinkAccount(s.name), run);
|
|
3329
|
+
if (!token) return null;
|
|
3330
|
+
const secret = secretGet(s.token_in, sinkAccount(s.name, "secret"), run);
|
|
3331
|
+
return { ...s, token, ...secret ? { secret } : {} };
|
|
3332
|
+
}
|
|
3333
|
+
|
|
3334
|
+
// src/control/store.ts
|
|
3335
|
+
function startOptionsOf(v) {
|
|
3336
|
+
const o = v;
|
|
3337
|
+
if (!o || typeof o !== "object" || levelRank(o.permission) < 0 && o.permission !== "inherit") return null;
|
|
3338
|
+
return {
|
|
3339
|
+
permission: o.permission,
|
|
3340
|
+
...typeof o.model === "string" && MODEL_RE.test(o.model) ? { model: o.model } : {}
|
|
3341
|
+
};
|
|
3342
|
+
}
|
|
3343
|
+
|
|
3344
|
+
// src/control/aliases.ts
|
|
3345
|
+
var aliasFile = (controlDir) => path13.join(controlDir, "aliases.json");
|
|
3021
3346
|
function readAliases(file) {
|
|
3022
3347
|
try {
|
|
3023
3348
|
const j = JSON.parse(readFileSync8(file, "utf8"));
|
|
@@ -3026,7 +3351,7 @@ function readAliases(file) {
|
|
|
3026
3351
|
for (const [k, v] of Object.entries(j)) {
|
|
3027
3352
|
const a = v;
|
|
3028
3353
|
if (a && typeof a.id === "string" && a.id && typeof a.cwd === "string")
|
|
3029
|
-
out[k] = { id: a.id, cwd: a.cwd, at: typeof a.at === "string" ? a.at : "" };
|
|
3354
|
+
out[k] = { id: a.id, cwd: a.cwd, at: typeof a.at === "string" ? a.at : "", ...startOptionsOf(a) };
|
|
3030
3355
|
}
|
|
3031
3356
|
return out;
|
|
3032
3357
|
} catch {
|
|
@@ -3042,11 +3367,11 @@ function receiverIdOf(all, harness, id) {
|
|
|
3042
3367
|
// src/control/local.ts
|
|
3043
3368
|
import net from "node:net";
|
|
3044
3369
|
import os2 from "node:os";
|
|
3045
|
-
import
|
|
3370
|
+
import path14 from "node:path";
|
|
3046
3371
|
function socketPath(state, env = process.env) {
|
|
3047
3372
|
if (env.SESSIONPIPE_SOCKET) return env.SESSIONPIPE_SOCKET;
|
|
3048
3373
|
if (process.platform === "win32") return `\\\\.\\pipe\\sessionpipe-control-${os2.userInfo().username}`;
|
|
3049
|
-
return
|
|
3374
|
+
return path14.join(state, "control.sock");
|
|
3050
3375
|
}
|
|
3051
3376
|
function ask(sock, req, timeoutMs) {
|
|
3052
3377
|
return new Promise((resolve) => {
|
|
@@ -3079,10 +3404,10 @@ function ask(sock, req, timeoutMs) {
|
|
|
3079
3404
|
}
|
|
3080
3405
|
|
|
3081
3406
|
// src/http-sink.ts
|
|
3082
|
-
import { createHash as
|
|
3407
|
+
import { createHash as createHash6, createHmac } from "node:crypto";
|
|
3083
3408
|
var TIMEOUT_MS = 15e3;
|
|
3084
3409
|
function webhookId(events) {
|
|
3085
|
-
return `msg_${
|
|
3410
|
+
return `msg_${createHash6("sha256").update(events.map((e) => e.id).join(",")).digest("base64url").slice(0, 32)}`;
|
|
3086
3411
|
}
|
|
3087
3412
|
function retryAfterMs(header, now = Date.now()) {
|
|
3088
3413
|
if (header === null || header.trim() === "") return 6e4;
|
|
@@ -3173,37 +3498,6 @@ var HttpSink = class {
|
|
|
3173
3498
|
}
|
|
3174
3499
|
};
|
|
3175
3500
|
|
|
3176
|
-
// src/secrets.ts
|
|
3177
|
-
import { spawnSync } from "node:child_process";
|
|
3178
|
-
import { createHash as createHash6, randomBytes as randomBytes2 } from "node:crypto";
|
|
3179
|
-
var SERVICE = "sessionpipe";
|
|
3180
|
-
var defaultRunner = (cmd, args, input) => {
|
|
3181
|
-
const r = spawnSync(cmd, args, {
|
|
3182
|
-
input: input ?? "",
|
|
3183
|
-
encoding: "utf8",
|
|
3184
|
-
timeout: 8e3,
|
|
3185
|
-
stdio: ["pipe", "pipe", "ignore"]
|
|
3186
|
-
});
|
|
3187
|
-
return { status: r.error ? null : r.status, stdout: r.stdout ?? "" };
|
|
3188
|
-
};
|
|
3189
|
-
var sinkAccount = (name, what = "token") => {
|
|
3190
|
-
const scope = process.env.SESSIONPIPE_CONFIG ? `@${createHash6("sha256").update(configFile()).digest("hex").slice(0, 8)}` : "";
|
|
3191
|
-
return `sink:${name}:${what}${scope}`;
|
|
3192
|
-
};
|
|
3193
|
-
function secretGet(store, account, run = defaultRunner) {
|
|
3194
|
-
const r = store === "keychain" ? run("security", ["find-generic-password", "-s", SERVICE, "-a", account, "-w"]) : run("secret-tool", ["lookup", "service", SERVICE, "account", account]);
|
|
3195
|
-
if (r.status !== 0) return null;
|
|
3196
|
-
const v = r.stdout.replace(/\r?\n$/, "");
|
|
3197
|
-
return v || null;
|
|
3198
|
-
}
|
|
3199
|
-
function sinkWithSecrets(s, run = defaultRunner) {
|
|
3200
|
-
if (!s.token_in) return s;
|
|
3201
|
-
const token = secretGet(s.token_in, sinkAccount(s.name), run);
|
|
3202
|
-
if (!token) return null;
|
|
3203
|
-
const secret = secretGet(s.token_in, sinkAccount(s.name, "secret"), run);
|
|
3204
|
-
return { ...s, token, ...secret ? { secret } : {} };
|
|
3205
|
-
}
|
|
3206
|
-
|
|
3207
3501
|
// src/run.ts
|
|
3208
3502
|
async function tellDaemon(state, job, session, cwd, transcript) {
|
|
3209
3503
|
const sock = socketPath(state);
|
|
@@ -3224,8 +3518,8 @@ var MAX_TRIES = 10;
|
|
|
3224
3518
|
function enqueueJob(state, job) {
|
|
3225
3519
|
const jobs = jobsDir(state);
|
|
3226
3520
|
mkdirSync8(jobs, { recursive: true, mode: 448 });
|
|
3227
|
-
const f =
|
|
3228
|
-
|
|
3521
|
+
const f = path15.join(jobs, `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.json`);
|
|
3522
|
+
writeFileSync8(f, JSON.stringify(job), { mode: 384 });
|
|
3229
3523
|
return f;
|
|
3230
3524
|
}
|
|
3231
3525
|
function claimJob(file) {
|
|
@@ -3261,19 +3555,19 @@ async function sweepJobs(state, olderThanMs = 3e3, limit = 20, abandonedMs = 6e4
|
|
|
3261
3555
|
for (const n of names) {
|
|
3262
3556
|
const m = /^(.*\.json)\.(\d+)\.running$/.exec(n);
|
|
3263
3557
|
if (!m) continue;
|
|
3264
|
-
const f =
|
|
3558
|
+
const f = path15.join(jobs, n);
|
|
3265
3559
|
try {
|
|
3266
|
-
if (processAlive(Number(m[2])) && Date.now() -
|
|
3267
|
-
renameSync3(f,
|
|
3560
|
+
if (processAlive(Number(m[2])) && Date.now() - statSync6(f).mtimeMs < abandonedMs) continue;
|
|
3561
|
+
renameSync3(f, path15.join(jobs, m[1]));
|
|
3268
3562
|
queued.push(m[1]);
|
|
3269
3563
|
} catch {
|
|
3270
3564
|
}
|
|
3271
3565
|
}
|
|
3272
3566
|
let ran = 0;
|
|
3273
3567
|
for (const n of queued.sort().slice(0, limit)) {
|
|
3274
|
-
const f =
|
|
3568
|
+
const f = path15.join(jobs, n);
|
|
3275
3569
|
try {
|
|
3276
|
-
if (Date.now() -
|
|
3570
|
+
if (Date.now() - statSync6(f).mtimeMs < olderThanMs) continue;
|
|
3277
3571
|
} catch {
|
|
3278
3572
|
continue;
|
|
3279
3573
|
}
|
|
@@ -3292,9 +3586,9 @@ async function sweepJobs(state, olderThanMs = 3e3, limit = 20, abandonedMs = 6e4
|
|
|
3292
3586
|
function log(state, line) {
|
|
3293
3587
|
try {
|
|
3294
3588
|
mkdirSync8(state, { recursive: true, mode: 448 });
|
|
3295
|
-
const f =
|
|
3589
|
+
const f = path15.join(state, "log");
|
|
3296
3590
|
try {
|
|
3297
|
-
if (
|
|
3591
|
+
if (statSync6(f).size > 1024 * 1024) renameSync3(f, `${f}.1`);
|
|
3298
3592
|
} catch {
|
|
3299
3593
|
}
|
|
3300
3594
|
appendFileSync3(f, `${(/* @__PURE__ */ new Date()).toISOString()} ${line}
|
|
@@ -3303,7 +3597,7 @@ function log(state, line) {
|
|
|
3303
3597
|
}
|
|
3304
3598
|
}
|
|
3305
3599
|
function factsState(state) {
|
|
3306
|
-
const file = (h, s) =>
|
|
3600
|
+
const file = (h, s) => path15.join(state, "facts", safeId(h), `${safeId(s)}.json`);
|
|
3307
3601
|
return {
|
|
3308
3602
|
get(h, s) {
|
|
3309
3603
|
try {
|
|
@@ -3316,8 +3610,8 @@ function factsState(state) {
|
|
|
3316
3610
|
const cur = this.get(h, s);
|
|
3317
3611
|
const next = { ...cur, ...patch };
|
|
3318
3612
|
if (JSON.stringify(next) === JSON.stringify(cur)) return;
|
|
3319
|
-
mkdirSync8(
|
|
3320
|
-
|
|
3613
|
+
mkdirSync8(path15.dirname(file(h, s)), { recursive: true, mode: 448 });
|
|
3614
|
+
writeFileSync8(file(h, s), JSON.stringify(next), { mode: 384 });
|
|
3321
3615
|
}
|
|
3322
3616
|
};
|
|
3323
3617
|
}
|
|
@@ -3359,6 +3653,12 @@ function openTurn(read, o) {
|
|
|
3359
3653
|
};
|
|
3360
3654
|
}
|
|
3361
3655
|
var MAX_TURN_SIDE = 2e4;
|
|
3656
|
+
var JOB_OWN_ENV = [
|
|
3657
|
+
"CLAUDE_CODE_SESSION_ATTENDED",
|
|
3658
|
+
"CLAUDE_CODE_HOST_SESSION_ID",
|
|
3659
|
+
"SESSIONPIPE_CONTROL_JOB",
|
|
3660
|
+
"SESSIONPIPE_LAUNCHED_BY"
|
|
3661
|
+
];
|
|
3362
3662
|
async function runJob(job, opts = {}) {
|
|
3363
3663
|
const state = opts.state ?? stateDir();
|
|
3364
3664
|
const cfg = readConfig();
|
|
@@ -3367,12 +3667,23 @@ async function runJob(job, opts = {}) {
|
|
|
3367
3667
|
log(state, `${job.harness} ${job.event}: no adapter for harness "${job.harness}"; event dropped`);
|
|
3368
3668
|
return { events: [], delivered: {} };
|
|
3369
3669
|
}
|
|
3370
|
-
const
|
|
3670
|
+
const env = { ...process.env };
|
|
3671
|
+
for (const k of JOB_OWN_ENV) delete env[k];
|
|
3672
|
+
const r = readHook(adapter, { argv: job.argv, stdin: job.stdin, env: { ...env, ...job.env }, cwd: job.cwd });
|
|
3371
3673
|
if (!r || !r.session.id) {
|
|
3372
|
-
|
|
3674
|
+
let keys = [];
|
|
3675
|
+
try {
|
|
3676
|
+
const payload = JSON.parse(job.argv.find((a) => a.startsWith("{")) ?? job.stdin);
|
|
3677
|
+
if (payload && typeof payload === "object" && !Array.isArray(payload)) keys = Object.keys(payload);
|
|
3678
|
+
} catch {
|
|
3679
|
+
}
|
|
3680
|
+
log(
|
|
3681
|
+
state,
|
|
3682
|
+
`${job.harness} ${job.event}: the ${job.harness} adapter did not claim this payload; event dropped; keys: ${keys.join(", ") || "(none)"}`
|
|
3683
|
+
);
|
|
3373
3684
|
return { events: [], delivered: {} };
|
|
3374
3685
|
}
|
|
3375
|
-
const id = receiverIdOf(readAliases(aliasFile(
|
|
3686
|
+
const id = receiverIdOf(readAliases(aliasFile(path15.join(state, "control"))), job.harness, r.session.id) ?? r.session.id;
|
|
3376
3687
|
const claudeEnd = job.harness === "claude-code" && r.events.some((e) => e.type === "session.ended");
|
|
3377
3688
|
if (!claudeEnd) await tellDaemon(state, job, id, r.session.cwd, r.transcript);
|
|
3378
3689
|
const outbox = new Outbox(state);
|
|
@@ -3401,12 +3712,15 @@ async function runJob(job, opts = {}) {
|
|
|
3401
3712
|
const heavy = !/^(PreToolUse|PostToolUse|PostToolUseFailure|BeforeTool|AfterTool|PostInvocation)$/.test(job.event);
|
|
3402
3713
|
let learned = {};
|
|
3403
3714
|
if (heavy) {
|
|
3404
|
-
const
|
|
3715
|
+
const hookEnv = { ...process.env, ...job.env };
|
|
3716
|
+
const f = adapter.facts(r.session, r.transcript, r.hints ?? {}, facts, hookEnv);
|
|
3717
|
+
if (cfg.remote_titles && adapter.remoteFacts)
|
|
3718
|
+
Object.assign(f, await adapter.remoteFacts(r.session, f, r.transcript, facts, hookEnv));
|
|
3405
3719
|
const { started_at: _s, last_at: _l, first_ask: _fa, harness_version, ...rest } = f;
|
|
3406
3720
|
learned = rest;
|
|
3407
3721
|
if (harness_version) facts.save(job.harness, id, { harness_version });
|
|
3408
3722
|
facts.save(job.harness, id, { session: { ...mem.session, ...learned } });
|
|
3409
|
-
if (CWD_FROM_HOOKS.has(job.harness) && r.session.cwd &&
|
|
3723
|
+
if (CWD_FROM_HOOKS.has(job.harness) && r.session.cwd && path15.isAbsolute(r.session.cwd))
|
|
3410
3724
|
facts.save(job.harness, id, { cwd: r.session.cwd });
|
|
3411
3725
|
}
|
|
3412
3726
|
const known = mem.session ?? {};
|
|
@@ -3421,7 +3735,8 @@ async function runJob(job, opts = {}) {
|
|
|
3421
3735
|
seq: 0,
|
|
3422
3736
|
machine: machineName(cfg, os3.hostname()),
|
|
3423
3737
|
...cwdRaw ? { cwd: tilde(cwdRaw) } : {},
|
|
3424
|
-
...r.session.model ? { model: r.session.model } : {}
|
|
3738
|
+
...r.session.model ? { model: r.session.model } : {},
|
|
3739
|
+
...r.session.launched_by ? { launched_by: r.session.launched_by } : {}
|
|
3425
3740
|
};
|
|
3426
3741
|
const version = facts.get(job.harness, id).harness_version ?? void 0;
|
|
3427
3742
|
for (const a of r.events) {
|
|
@@ -3487,6 +3802,23 @@ async function runJob(job, opts = {}) {
|
|
|
3487
3802
|
}
|
|
3488
3803
|
if (read.turns.length) facts.save(job.harness, id, { transcript_line: read.line, transcript_turns: turnNo });
|
|
3489
3804
|
}
|
|
3805
|
+
if (adapter.limits && r.transcript && ending && cfg.sinks.some((s) => !s.paused && Math.min(s.tier, s.max_tier ?? 3) >= 1)) {
|
|
3806
|
+
const limits = adapter.limits(r.transcript);
|
|
3807
|
+
const sig = limits && JSON.stringify(limits);
|
|
3808
|
+
if (limits && sig !== facts.get(job.harness, id).limits_sig) {
|
|
3809
|
+
events.push(
|
|
3810
|
+
makeEvent(
|
|
3811
|
+
{ type: "limits.observed", harnessEvent: job.event, data: limits },
|
|
3812
|
+
{
|
|
3813
|
+
harness: { name: job.harness, ...version ? { version } : {} },
|
|
3814
|
+
session: { ...session, seq: outbox.nextSeq(ref) },
|
|
3815
|
+
now: opts.now ?? job.at
|
|
3816
|
+
}
|
|
3817
|
+
)
|
|
3818
|
+
);
|
|
3819
|
+
facts.save(job.harness, id, { limits_sig: sig });
|
|
3820
|
+
}
|
|
3821
|
+
}
|
|
3490
3822
|
outbox.append(
|
|
3491
3823
|
ref,
|
|
3492
3824
|
events.map((e) => ({ ...e, session: redactDeep(e.session), data: redactDeep(e.data) }))
|
|
@@ -3504,11 +3836,11 @@ async function runJob(job, opts = {}) {
|
|
|
3504
3836
|
return { events, delivered };
|
|
3505
3837
|
}
|
|
3506
3838
|
async function drain(state, sinkConfigs, outbox, first) {
|
|
3507
|
-
const more =
|
|
3839
|
+
const more = path15.join(state, "locks", "flush.more");
|
|
3508
3840
|
let release = takeNamedLock(state, "flush");
|
|
3509
3841
|
if (!release) {
|
|
3510
3842
|
try {
|
|
3511
|
-
|
|
3843
|
+
writeFileSync8(more, "", { mode: 384 });
|
|
3512
3844
|
} catch {
|
|
3513
3845
|
}
|
|
3514
3846
|
release = takeNamedLock(state, "flush");
|
|
@@ -3532,7 +3864,7 @@ async function drain(state, sinkConfigs, outbox, first) {
|
|
|
3532
3864
|
}
|
|
3533
3865
|
}
|
|
3534
3866
|
var BACKOFF_MS = [2e3, 1e4, 6e4, 5 * 6e4, 30 * 6e4];
|
|
3535
|
-
var backoffFile = (state, sink) =>
|
|
3867
|
+
var backoffFile = (state, sink) => path15.join(state, "backoff", `${safeId(sink)}.json`);
|
|
3536
3868
|
function readBackoff(state, sink) {
|
|
3537
3869
|
try {
|
|
3538
3870
|
const b = JSON.parse(readFileSync9(backoffFile(state, sink), "utf8"));
|
|
@@ -3548,8 +3880,8 @@ function noteFailure(state, sink, res, prev) {
|
|
|
3548
3880
|
const b = { until: Date.now() + wait, failures, why: `${res.status ?? ""} ${res.error ?? ""}`.trim() };
|
|
3549
3881
|
const f = backoffFile(state, sink);
|
|
3550
3882
|
try {
|
|
3551
|
-
mkdirSync8(
|
|
3552
|
-
|
|
3883
|
+
mkdirSync8(path15.dirname(f), { recursive: true, mode: 448 });
|
|
3884
|
+
writeFileSync8(f, JSON.stringify(b), { mode: 384 });
|
|
3553
3885
|
} catch {
|
|
3554
3886
|
}
|
|
3555
3887
|
return b;
|
|
@@ -3681,7 +4013,7 @@ function applySinkVerdict(name, res) {
|
|
|
3681
4013
|
} catch {
|
|
3682
4014
|
}
|
|
3683
4015
|
}
|
|
3684
|
-
var jobsDir = (state) =>
|
|
4016
|
+
var jobsDir = (state) => path15.join(state, "jobs");
|
|
3685
4017
|
|
|
3686
4018
|
// src/worker.ts
|
|
3687
4019
|
async function workerMain(argv) {
|