sessionpipe 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +5165 -2399
- package/dist/cli.js.map +4 -4
- package/dist/control/cli.d.ts +4 -0
- package/dist/control/cli.d.ts.map +1 -1
- package/dist/control/daemon.d.ts +24 -0
- package/dist/control/daemon.d.ts.map +1 -1
- package/dist/control/local.d.ts +24 -0
- package/dist/control/local.d.ts.map +1 -1
- package/dist/control/pool.d.ts +49 -0
- package/dist/control/pool.d.ts.map +1 -0
- package/dist/control/route.d.ts +1 -1
- package/dist/control/route.d.ts.map +1 -1
- package/dist/help.d.ts.map +1 -1
- package/dist/hook.d.ts.map +1 -1
- package/dist/hook.js +38 -2
- package/dist/hook.js.map +2 -2
- package/dist/index.js +1135 -880
- package/dist/index.js.map +4 -4
- package/dist/pool/cli.d.ts +39 -0
- package/dist/pool/cli.d.ts.map +1 -0
- package/dist/pool/credentials.d.ts +44 -0
- package/dist/pool/credentials.d.ts.map +1 -0
- package/dist/pool/import.d.ts +23 -0
- package/dist/pool/import.d.ts.map +1 -0
- package/dist/pool/index.d.ts +138 -0
- package/dist/pool/index.d.ts.map +1 -0
- package/dist/pool/install.d.ts +46 -0
- package/dist/pool/install.d.ts.map +1 -0
- package/dist/pool/login.d.ts +27 -0
- package/dist/pool/login.d.ts.map +1 -0
- package/dist/pool/policy.d.ts +100 -0
- package/dist/pool/policy.d.ts.map +1 -0
- package/dist/pool/server.d.ts +76 -0
- package/dist/pool/server.d.ts.map +1 -0
- package/dist/pool/store.d.ts +89 -0
- package/dist/pool/store.d.ts.map +1 -0
- package/dist/pool/types.d.ts +92 -0
- package/dist/pool/types.d.ts.map +1 -0
- package/dist/run.d.ts +14 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/worker.js +1143 -888
- package/dist/worker.js.map +4 -4
- package/package.json +1 -1
package/dist/worker.js
CHANGED
|
@@ -1,24 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// sessionpipe 0.
|
|
2
|
+
// sessionpipe 0.12.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,816 +523,970 @@ 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(ev("turn.ended", { reason:
|
|
621
|
+
events.push(ev("turn.ended", { turn_id, reason: "stop", background: backgroundCount(s.background_tasks) }));
|
|
528
622
|
break;
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
623
|
+
case "StopFailure":
|
|
624
|
+
events.push(
|
|
625
|
+
ev("turn.ended", {
|
|
626
|
+
turn_id,
|
|
627
|
+
reason: "error",
|
|
628
|
+
error: typeof s.error === "string" ? s.error.slice(0, 200) : void 0
|
|
629
|
+
})
|
|
630
|
+
);
|
|
631
|
+
break;
|
|
632
|
+
case "SubagentStart":
|
|
633
|
+
events.push(
|
|
634
|
+
ev("subagent.started", {
|
|
635
|
+
agent_id: String(s.agent_id ?? ""),
|
|
636
|
+
agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
|
|
637
|
+
})
|
|
638
|
+
);
|
|
639
|
+
break;
|
|
640
|
+
case "SubagentStop":
|
|
641
|
+
events.push(
|
|
642
|
+
ev("subagent.ended", {
|
|
643
|
+
agent_id: String(s.agent_id ?? ""),
|
|
644
|
+
agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0,
|
|
645
|
+
background: backgroundCount(s.background_tasks, String(s.agent_id ?? ""))
|
|
646
|
+
})
|
|
647
|
+
);
|
|
648
|
+
break;
|
|
649
|
+
case "PreCompact":
|
|
650
|
+
events.push(ev("context.compacted", { trigger: s.trigger === "manual" ? "manual" : "auto" }));
|
|
651
|
+
break;
|
|
652
|
+
case "PostCompact":
|
|
653
|
+
break;
|
|
654
|
+
// PreCompact already said it; a second event would double-count
|
|
655
|
+
case "SessionEnd":
|
|
656
|
+
events.push(ev("session.ended", { reason: endReason(s.reason) }));
|
|
657
|
+
break;
|
|
658
|
+
case "StatusLine": {
|
|
659
|
+
const limits = claudeLimits(s.rate_limits);
|
|
660
|
+
if (!limits) return null;
|
|
661
|
+
events.push(ev("limits.observed", limits));
|
|
662
|
+
break;
|
|
663
|
+
}
|
|
664
|
+
default:
|
|
665
|
+
return null;
|
|
666
|
+
}
|
|
667
|
+
const hints = {};
|
|
668
|
+
if (event === "UserPromptSubmit" && typeof s.prompt === "string") hints.prompt = s.prompt;
|
|
669
|
+
const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
|
|
670
|
+
if (host && DESKTOP_ID.test(host)) hints.desktop_id = host;
|
|
671
|
+
const attended = input.env.CLAUDE_CODE_SESSION_ATTENDED;
|
|
672
|
+
if (attended === "1" || attended === "0") hints.attended = attended === "1" || !!input.env.SESSIONPIPE_CONTROL_JOB;
|
|
673
|
+
if (event !== "PreToolUse" && event !== "PostToolUse") hints.claude_pid = claudePid(input.env);
|
|
674
|
+
const r = { session, events, hints };
|
|
675
|
+
if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
|
|
676
|
+
return r;
|
|
677
|
+
},
|
|
678
|
+
readTranscript(file, fromLine) {
|
|
537
679
|
let text = "";
|
|
538
680
|
try {
|
|
539
681
|
text = readFileSync2(file, "utf8");
|
|
540
682
|
} catch {
|
|
541
683
|
return { turns: [], line: fromLine };
|
|
542
684
|
}
|
|
543
|
-
return
|
|
685
|
+
return readClaude(text.split("\n"), fromLine);
|
|
544
686
|
},
|
|
545
|
-
facts(session, transcript, hints) {
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
687
|
+
facts(session, transcript, hints, state) {
|
|
688
|
+
const out = {};
|
|
689
|
+
if (transcript) Object.assign(out, claudeFacts(transcript, session.id, hints, state));
|
|
690
|
+
const cwd = out.cwd ?? session.cwd;
|
|
691
|
+
Object.assign(out, gitFacts(cwd), out.branch ? { branch: out.branch } : {});
|
|
692
|
+
if (!out.url && typeof hints.desktop_id === "string") out.url = `claude://code/continue/${hints.desktop_id}`;
|
|
693
|
+
if (typeof hints.attended === "boolean") out.attended = hints.attended || !!out.url;
|
|
694
|
+
return out;
|
|
552
695
|
},
|
|
696
|
+
remoteFacts: (session, local, transcript, state, env = process.env) => claudeRemoteFacts(session.id, local, transcript, state, env),
|
|
553
697
|
backfill(sinceMs, env = process.env) {
|
|
554
698
|
const rows2 = [];
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
}
|
|
699
|
+
const files = [];
|
|
700
|
+
for (const d of claudeDirs(env))
|
|
701
|
+
files.push(...recentFiles(path4.join(d, "projects"), 1, sinceMs, (n) => /^[0-9a-f-]{36}\.jsonl$/.test(n)));
|
|
702
|
+
const noState = { get: () => ({}), save: () => {
|
|
703
|
+
} };
|
|
704
|
+
for (const f of files) {
|
|
705
|
+
const id = path4.basename(f, ".jsonl");
|
|
706
|
+
const facts = claudeFacts(f, id, {}, noState);
|
|
707
|
+
if (!facts.started_at || !facts.last_at) continue;
|
|
708
|
+
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
709
|
+
const cwd = rest.cwd;
|
|
710
|
+
rows2.push({
|
|
711
|
+
session: { id, ...rest, ...gitFacts(cwd), ...rest.branch ? { branch: rest.branch } : {} },
|
|
712
|
+
started_at,
|
|
713
|
+
last_at,
|
|
714
|
+
transcript: f
|
|
715
|
+
});
|
|
573
716
|
}
|
|
574
717
|
return rows2;
|
|
575
718
|
}
|
|
576
719
|
};
|
|
720
|
+
var WAITER_RE = /(?:^|[\s"'`/\\])sessionpipe(?:\.\w+)?["']?\s+wait(?:\s|$)/;
|
|
721
|
+
function backgroundCount(tasks, ending) {
|
|
722
|
+
if (!Array.isArray(tasks)) return void 0;
|
|
723
|
+
return tasks.filter((t) => {
|
|
724
|
+
const task = t;
|
|
725
|
+
if (ending !== void 0 && task?.id === ending) return false;
|
|
726
|
+
return !(typeof task?.command === "string" && WAITER_RE.test(task.command));
|
|
727
|
+
}).length;
|
|
728
|
+
}
|
|
729
|
+
var LINE_REFRESH_S = 60;
|
|
730
|
+
function statusLine(file, want, cmd) {
|
|
731
|
+
const f = readJsonFile(file);
|
|
732
|
+
if (!f.ok) return null;
|
|
733
|
+
const cur = f.value.statusLine;
|
|
734
|
+
const ours = typeof cur?.command === "string" && OURS.test(cur.command);
|
|
735
|
+
const command = cmd?.([NAME, "StatusLine"]).command;
|
|
736
|
+
if (want && cur && !ours)
|
|
737
|
+
return { file, changed: false, skipped: true, note: "a status line of your own is set; limits are not read" };
|
|
738
|
+
if (want ? ours && cur?.command === command && cur?.refreshInterval === LINE_REFRESH_S : !ours) return null;
|
|
739
|
+
if (want) f.value.statusLine = { type: "command", command, refreshInterval: LINE_REFRESH_S };
|
|
740
|
+
else delete f.value.statusLine;
|
|
741
|
+
writeFileSync2(file, formatJson(f.value, f));
|
|
742
|
+
return { file, changed: true, note: want ? "status line written (account limits)" : "status line removed" };
|
|
743
|
+
}
|
|
744
|
+
var epochIso = (t) => typeof t === "number" && t > 0 && t < 1e11 ? new Date(t * 1e3).toISOString() : void 0;
|
|
745
|
+
var WINDOW_MINUTES = { five_hour: 300, seven_day: 10080 };
|
|
746
|
+
function claudeLimits(rl) {
|
|
747
|
+
if (!rl || typeof rl !== "object" || Array.isArray(rl)) return null;
|
|
748
|
+
const windows = [];
|
|
749
|
+
for (const [id, w] of Object.entries(rl)) {
|
|
750
|
+
if (!/^[a-z0-9_]{1,64}$/.test(id) || typeof w?.used_percentage !== "number" || w.used_percentage < 0) continue;
|
|
751
|
+
windows.push(
|
|
752
|
+
prune({
|
|
753
|
+
id,
|
|
754
|
+
used_percent: w.used_percentage,
|
|
755
|
+
window_minutes: WINDOW_MINUTES[id],
|
|
756
|
+
resets_at: epochIso(w.resets_at)
|
|
757
|
+
})
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
return windows.length ? { windows: windows.slice(0, 8) } : null;
|
|
761
|
+
}
|
|
577
762
|
function prune(o) {
|
|
578
763
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
579
764
|
return o;
|
|
580
765
|
}
|
|
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);
|
|
766
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
767
|
+
function notClaudeCode(id, s) {
|
|
768
|
+
return typeof s.transcript_path !== "string" && !UUID.test(id);
|
|
594
769
|
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
770
|
+
function pickSource(v) {
|
|
771
|
+
return typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
|
|
772
|
+
}
|
|
773
|
+
var harnessWord = (v) => typeof v === "string" && /^[A-Za-z0-9][A-Za-z0-9_.-]{0,39}$/.test(v) ? v : void 0;
|
|
774
|
+
var WRAPPER = /^<(?:task-notification|cross-session-message|system-reminder|local-command-stdout|command-[a-z-]+)[\s/>]/i;
|
|
775
|
+
var injectedBlock = (prompt) => WRAPPER.test(prompt.trim());
|
|
776
|
+
function errText(v) {
|
|
777
|
+
if (typeof v === "string") return v.slice(0, 2e3);
|
|
778
|
+
if (v && typeof v === "object") {
|
|
779
|
+
const e = v;
|
|
780
|
+
if (typeof e.error === "string") return e.error.slice(0, 2e3);
|
|
781
|
+
if (typeof e.message === "string") return e.message.slice(0, 2e3);
|
|
602
782
|
}
|
|
603
|
-
return
|
|
783
|
+
return void 0;
|
|
604
784
|
}
|
|
605
|
-
function
|
|
606
|
-
|
|
785
|
+
function describe(input) {
|
|
786
|
+
if (!input || typeof input !== "object") return "";
|
|
787
|
+
const i = input;
|
|
788
|
+
const v = i.command ?? i.file_path ?? i.path ?? i.url ?? i.pattern;
|
|
789
|
+
return typeof v === "string" ? ` ${v.slice(0, 120)}` : "";
|
|
790
|
+
}
|
|
791
|
+
var NAME_RANK = {
|
|
792
|
+
user: "custom",
|
|
793
|
+
auto: "harness",
|
|
794
|
+
hook: "harness",
|
|
795
|
+
peer: "harness"
|
|
796
|
+
};
|
|
797
|
+
function registryEntry(j) {
|
|
798
|
+
if (!j || typeof j !== "object") return null;
|
|
799
|
+
const r = j;
|
|
800
|
+
const source = r.nameSource ? NAME_RANK[r.nameSource] : void 0;
|
|
801
|
+
return {
|
|
802
|
+
sessionId: r.sessionId ? String(r.sessionId) : null,
|
|
803
|
+
name: source && typeof r.name === "string" && r.name.trim() ? r.name.trim() : null,
|
|
804
|
+
name_source: source ?? null,
|
|
805
|
+
bridge: typeof r.bridgeSessionId === "string" ? r.bridgeSessionId : null
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
var liveCache = /* @__PURE__ */ new Map();
|
|
809
|
+
function claudeLive(dir) {
|
|
810
|
+
const hit = liveCache.get(dir);
|
|
811
|
+
if (hit) return hit;
|
|
812
|
+
const map = /* @__PURE__ */ new Map();
|
|
607
813
|
let names = [];
|
|
608
814
|
try {
|
|
609
|
-
names = readdirSync2(dir).filter((n) => n.endsWith(".json"));
|
|
815
|
+
names = readdirSync2(path4.join(dir, "sessions")).filter((n) => n.endsWith(".json"));
|
|
610
816
|
} catch {
|
|
611
|
-
return out;
|
|
612
817
|
}
|
|
613
818
|
for (const n of names) {
|
|
614
|
-
const id = n.slice(0, -5);
|
|
615
|
-
if (!ID_RE.test(id)) continue;
|
|
616
|
-
let j;
|
|
617
819
|
try {
|
|
618
|
-
|
|
820
|
+
const e = registryEntry(JSON.parse(readFileSync2(path4.join(dir, "sessions", n), "utf8")));
|
|
821
|
+
if (e?.sessionId) map.set(e.sessionId, e);
|
|
619
822
|
} 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
823
|
}
|
|
627
|
-
if (folders.length) out.push({ id, folders });
|
|
628
824
|
}
|
|
629
|
-
|
|
825
|
+
liveCache.set(dir, map);
|
|
826
|
+
return map;
|
|
630
827
|
}
|
|
631
|
-
function
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
828
|
+
function claudeLiveByPid(dir, pid) {
|
|
829
|
+
if (!pid) return null;
|
|
830
|
+
try {
|
|
831
|
+
return registryEntry(JSON.parse(readFileSync2(path4.join(dir, "sessions", `${pid}.json`), "utf8")));
|
|
832
|
+
} catch {
|
|
833
|
+
return null;
|
|
636
834
|
}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
835
|
+
}
|
|
836
|
+
function claudePid(env = process.env) {
|
|
837
|
+
try {
|
|
838
|
+
const dirs = claudeDirs(env);
|
|
839
|
+
const has = (pid2) => dirs.some((d) => existsSync2(path4.join(d, "sessions", `${pid2}.json`)));
|
|
840
|
+
let pid = process.ppid;
|
|
841
|
+
if (has(pid)) return pid;
|
|
842
|
+
const parent = parentOf();
|
|
843
|
+
for (let hop = 0; hop < 6 && pid > 1; hop++) {
|
|
844
|
+
pid = parent(pid);
|
|
845
|
+
if (pid > 1 && has(pid)) return pid;
|
|
846
|
+
}
|
|
847
|
+
} catch {
|
|
848
|
+
}
|
|
849
|
+
return null;
|
|
850
|
+
}
|
|
851
|
+
function parentOf() {
|
|
852
|
+
if (process.platform === "linux") {
|
|
853
|
+
return (pid) => {
|
|
854
|
+
try {
|
|
855
|
+
const st = readFileSync2(`/proc/${pid}/stat`, "utf8");
|
|
856
|
+
return Number(st.slice(st.lastIndexOf(")") + 2).split(" ")[1]) || 0;
|
|
857
|
+
} catch {
|
|
858
|
+
return 0;
|
|
645
859
|
}
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
const table = /* @__PURE__ */ new Map();
|
|
863
|
+
try {
|
|
864
|
+
const out = execFileSync2("ps", ["-axo", "pid=,ppid="], {
|
|
865
|
+
encoding: "utf8",
|
|
866
|
+
timeout: 2e3,
|
|
867
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
868
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
869
|
+
});
|
|
870
|
+
for (const l of out.split("\n")) {
|
|
871
|
+
const m = /^\s*(\d+)\s+(\d+)/.exec(l);
|
|
872
|
+
if (m) table.set(Number(m[1]), Number(m[2]));
|
|
646
873
|
}
|
|
647
|
-
|
|
874
|
+
} catch {
|
|
875
|
+
}
|
|
876
|
+
return (pid) => table.get(pid) || 0;
|
|
648
877
|
}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
const
|
|
652
|
-
|
|
878
|
+
var EMAIL_RE = /^[^@\s]{1,64}@[^@\s]{1,189}$/;
|
|
879
|
+
function claudeLogin(dir, home = homeDir()) {
|
|
880
|
+
const files = [path4.join(dir, ".claude.json")];
|
|
881
|
+
if (path4.resolve(dir) === path4.join(home, ".claude")) files.unshift(path4.join(home, ".claude.json"));
|
|
882
|
+
for (const f of files) {
|
|
653
883
|
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;
|
|
884
|
+
const a = JSON.parse(readFileSync2(f, "utf8")).oauthAccount;
|
|
885
|
+
if (typeof a?.accountUuid === "string" && a.accountUuid) {
|
|
886
|
+
const email = typeof a.emailAddress === "string" && EMAIL_RE.test(a.emailAddress) ? a.emailAddress : void 0;
|
|
887
|
+
return { id: a.accountUuid.slice(0, 128), ...email ? { email } : {} };
|
|
888
|
+
}
|
|
663
889
|
} catch {
|
|
664
890
|
}
|
|
665
891
|
}
|
|
666
|
-
return
|
|
892
|
+
return apiAccount(dir);
|
|
667
893
|
}
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
const
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
const url = antigravityRemoteUrl(file, opts);
|
|
674
|
-
if (url) out.url = url;
|
|
675
|
-
for (const j of H) {
|
|
676
|
-
if (j.type !== "USER_INPUT" || typeof j.content !== "string") continue;
|
|
677
|
-
const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
|
|
678
|
-
const text = (m ? m[1] : j.content).trim();
|
|
679
|
-
const line = (text.split("\n").find((l) => l.trim()) ?? "").trim();
|
|
680
|
-
if (!line || /^<[a-z_-]+>/i.test(line)) continue;
|
|
681
|
-
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
682
|
-
out.title_source = "first-ask";
|
|
683
|
-
break;
|
|
684
|
-
}
|
|
685
|
-
const times = H.concat(T).map((j) => Date.parse(String(j.created_at ?? j.timestamp ?? ""))).filter((n) => n > 0);
|
|
686
|
-
if (times.length) {
|
|
687
|
-
out.started_at = Math.min(...times);
|
|
688
|
-
out.last_at = Math.max(...times);
|
|
689
|
-
}
|
|
690
|
-
return out;
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
// ../core/src/adapters/claude-code.ts
|
|
694
|
-
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
695
|
-
import { createHash } from "node:crypto";
|
|
696
|
-
import { existsSync as existsSync3, readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as statSync3 } from "node:fs";
|
|
697
|
-
import path5 from "node:path";
|
|
698
|
-
|
|
699
|
-
// ../core/src/readers/git.ts
|
|
700
|
-
import { execFileSync } from "node:child_process";
|
|
701
|
-
var cache = /* @__PURE__ */ new Map();
|
|
702
|
-
function gitFacts(cwd) {
|
|
703
|
-
if (!cwd) return {};
|
|
704
|
-
const hit = cache.get(cwd);
|
|
705
|
-
if (hit) return hit;
|
|
706
|
-
const run = (args) => {
|
|
707
|
-
try {
|
|
708
|
-
return execFileSync("git", ["-C", cwd, ...args], {
|
|
709
|
-
encoding: "utf8",
|
|
710
|
-
timeout: 1500,
|
|
711
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
712
|
-
}).trim();
|
|
713
|
-
} catch {
|
|
714
|
-
return "";
|
|
715
|
-
}
|
|
716
|
-
};
|
|
717
|
-
const out = {};
|
|
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;
|
|
724
|
-
}
|
|
725
|
-
function stripCredentials(url) {
|
|
726
|
-
const ssh = /^(?:ssh:\/\/)?git@([^:/]+)[:/](.+?)(?:\.git)?$/.exec(url);
|
|
727
|
-
if (ssh) return `${ssh[1]}/${ssh[2]}`;
|
|
728
|
-
return url.replace(/\/\/[^/@]*@/, "//").replace(/^https?:\/\//, "").replace(/\.git$/, "");
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
// ../core/src/adapters/claude-code.ts
|
|
732
|
-
var NAME2 = "claude-code";
|
|
733
|
-
var CLAUDE_EVENTS = [
|
|
734
|
-
"SessionStart",
|
|
735
|
-
"UserPromptSubmit",
|
|
736
|
-
"PreToolUse",
|
|
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);
|
|
894
|
+
var ANTHROPIC_HOST = /(^|\.)anthropic\.com$/i;
|
|
895
|
+
function apiAccount(dir) {
|
|
896
|
+
const base = readJson(settingsIn(dir)).env?.ANTHROPIC_BASE_URL;
|
|
897
|
+
if (typeof base !== "string") return null;
|
|
898
|
+
let u;
|
|
768
899
|
try {
|
|
769
|
-
|
|
770
|
-
if (!e.isDirectory() || !/^\.claude[-_.]/.test(e.name)) continue;
|
|
771
|
-
const d = path5.join(home, e.name);
|
|
772
|
-
if (existsSync3(path5.join(d, "projects")) || existsSync3(path5.join(d, "settings.json"))) add(d);
|
|
773
|
-
}
|
|
900
|
+
u = new URL(base.trim());
|
|
774
901
|
} catch {
|
|
902
|
+
return null;
|
|
775
903
|
}
|
|
776
|
-
|
|
904
|
+
if (u.protocol !== "https:" && u.protocol !== "http:" || !u.hostname || ANTHROPIC_HOST.test(u.hostname))
|
|
905
|
+
return null;
|
|
906
|
+
const origin = `${u.protocol}//${u.host}`;
|
|
907
|
+
return { id: `api-${createHash("sha256").update(origin).digest("hex").slice(0, 16)}`, name: u.host };
|
|
777
908
|
}
|
|
778
|
-
var
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
(
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
})),
|
|
796
|
-
fromHook(input) {
|
|
797
|
-
const event = input.argv[1] ?? "";
|
|
798
|
-
let s = {};
|
|
799
|
-
try {
|
|
800
|
-
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
801
|
-
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
802
|
-
} catch {
|
|
803
|
-
}
|
|
804
|
-
const id = s.session_id ?? s.sessionId;
|
|
805
|
-
if (!id) return null;
|
|
806
|
-
if (notClaudeCode(String(id), s)) return null;
|
|
807
|
-
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
808
|
-
if (typeof s.model === "string") session.model = s.model;
|
|
809
|
-
if (typeof s.agent_id === "string" && event.startsWith("Subagent") === false && s.agent_id)
|
|
810
|
-
session.parent_id = null;
|
|
811
|
-
const turn_id = typeof s.prompt_id === "string" ? s.prompt_id : void 0;
|
|
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)}`;
|
|
817
|
-
const ev = (type, data) => ({ type, data: prune2(data), harnessEvent: event });
|
|
818
|
-
const events = [];
|
|
819
|
-
switch (event) {
|
|
820
|
-
case "SessionStart":
|
|
821
|
-
events.push(ev("session.started", { source: pickSource(s.source ?? s.trigger) }));
|
|
822
|
-
break;
|
|
823
|
-
case "UserPromptSubmit": {
|
|
824
|
-
const prompt = typeof s.prompt === "string" ? s.prompt : void 0;
|
|
825
|
-
events.push(
|
|
826
|
-
ev("turn.started", {
|
|
827
|
-
turn_id,
|
|
828
|
-
prompt_chars: prompt === void 0 ? void 0 : [...prompt].length,
|
|
829
|
-
// Claude Code's hook schema documents `source`
|
|
830
|
-
// (user | sdk | system | loop_wakeup | schedule_wakeup | poll_event) and says a
|
|
831
|
-
// payload may omit it while the field rolls out; 2.1.304's payload has no such
|
|
832
|
-
// key at all. Forwarded verbatim, shape-guarded only: the list is the harness's
|
|
833
|
-
// to extend.
|
|
834
|
-
submitted_by: harnessWord(s.source),
|
|
835
|
-
// One of Claude Code's own prompt wrappers: the prompt is a block the harness
|
|
836
|
-
// wrote, so it is not the person's words. Absent is unknown, never "the person" —
|
|
837
|
-
// and `true` is never "no person caused this turn", because a Talk or
|
|
838
|
-
// `sessionpipe wait` delivery hands the person's own message over inside a
|
|
839
|
-
// <task-notification> (PROTOCOL.md §4).
|
|
840
|
-
injected: prompt !== void 0 && injectedBlock(prompt) ? true : void 0
|
|
841
|
-
})
|
|
842
|
-
);
|
|
843
|
-
break;
|
|
844
|
-
}
|
|
845
|
-
case "PreToolUse":
|
|
846
|
-
if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, agent_id, input: s.tool_input }));
|
|
847
|
-
break;
|
|
848
|
-
case "PostToolUse":
|
|
849
|
-
if (tool)
|
|
850
|
-
events.push(
|
|
851
|
-
ev("tool.ended", {
|
|
852
|
-
tool,
|
|
853
|
-
call_id,
|
|
854
|
-
turn_id,
|
|
855
|
-
agent_id,
|
|
856
|
-
ms,
|
|
857
|
-
ok: true,
|
|
858
|
-
input: s.tool_input,
|
|
859
|
-
output: s.tool_response
|
|
860
|
-
})
|
|
861
|
-
);
|
|
862
|
-
break;
|
|
863
|
-
case "PostToolUseFailure":
|
|
864
|
-
if (tool)
|
|
865
|
-
events.push(
|
|
866
|
-
ev("tool.ended", {
|
|
867
|
-
tool,
|
|
868
|
-
call_id,
|
|
869
|
-
turn_id,
|
|
870
|
-
agent_id,
|
|
871
|
-
ms,
|
|
872
|
-
ok: false,
|
|
873
|
-
error: errText(s.error ?? s.tool_response),
|
|
874
|
-
input: s.tool_input,
|
|
875
|
-
output: s.tool_response
|
|
876
|
-
})
|
|
877
|
-
);
|
|
878
|
-
break;
|
|
879
|
-
case "PermissionRequest":
|
|
880
|
-
events.push(
|
|
881
|
-
ev("attention.needed", {
|
|
882
|
-
attention_id: call_id ?? permissionId(),
|
|
883
|
-
kind: "permission",
|
|
884
|
-
tool,
|
|
885
|
-
message: tool ? `${tool}${describe(s.tool_input)}` : void 0
|
|
886
|
-
})
|
|
887
|
-
);
|
|
888
|
-
break;
|
|
889
|
-
case "Notification":
|
|
890
|
-
events.push(
|
|
891
|
-
ev("attention.needed", {
|
|
892
|
-
attention_id: `notif-${Date.now()}`,
|
|
893
|
-
kind: notifKind(s.notification_type),
|
|
894
|
-
message: typeof s.message === "string" ? s.message.slice(0, 200) : void 0
|
|
895
|
-
})
|
|
896
|
-
);
|
|
897
|
-
break;
|
|
898
|
-
case "Stop":
|
|
899
|
-
events.push(
|
|
900
|
-
ev("turn.ended", {
|
|
901
|
-
turn_id,
|
|
902
|
-
reason: "stop",
|
|
903
|
-
background: Array.isArray(s.background_tasks) ? s.background_tasks.length : void 0
|
|
904
|
-
})
|
|
905
|
-
);
|
|
906
|
-
break;
|
|
907
|
-
case "StopFailure":
|
|
908
|
-
events.push(
|
|
909
|
-
ev("turn.ended", {
|
|
910
|
-
turn_id,
|
|
911
|
-
reason: "error",
|
|
912
|
-
error: typeof s.error === "string" ? s.error.slice(0, 200) : void 0
|
|
913
|
-
})
|
|
914
|
-
);
|
|
915
|
-
break;
|
|
916
|
-
case "SubagentStart":
|
|
917
|
-
events.push(
|
|
918
|
-
ev("subagent.started", {
|
|
919
|
-
agent_id: String(s.agent_id ?? ""),
|
|
920
|
-
agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
|
|
921
|
-
})
|
|
922
|
-
);
|
|
923
|
-
break;
|
|
924
|
-
case "SubagentStop":
|
|
925
|
-
events.push(
|
|
926
|
-
ev("subagent.ended", {
|
|
927
|
-
agent_id: String(s.agent_id ?? ""),
|
|
928
|
-
agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
|
|
929
|
-
})
|
|
930
|
-
);
|
|
931
|
-
break;
|
|
932
|
-
case "PreCompact":
|
|
933
|
-
events.push(ev("context.compacted", { trigger: s.trigger === "manual" ? "manual" : "auto" }));
|
|
934
|
-
break;
|
|
935
|
-
case "PostCompact":
|
|
936
|
-
break;
|
|
937
|
-
// PreCompact already said it; a second event would double-count
|
|
938
|
-
case "SessionEnd":
|
|
939
|
-
events.push(ev("session.ended", { reason: endReason(s.reason) }));
|
|
940
|
-
break;
|
|
941
|
-
default:
|
|
942
|
-
return null;
|
|
943
|
-
}
|
|
944
|
-
const hints = {};
|
|
945
|
-
if (event === "UserPromptSubmit" && typeof s.prompt === "string") hints.prompt = s.prompt;
|
|
946
|
-
const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
|
|
947
|
-
if (host && DESKTOP_ID.test(host)) hints.desktop_id = host;
|
|
948
|
-
const attended = input.env.CLAUDE_CODE_SESSION_ATTENDED;
|
|
949
|
-
if (attended === "1" || attended === "0") hints.attended = attended === "1" || !!input.env.SESSIONPIPE_CONTROL_JOB;
|
|
950
|
-
if (event !== "PreToolUse" && event !== "PostToolUse") hints.claude_pid = claudePid(input.env);
|
|
951
|
-
const r = { session, events, hints };
|
|
952
|
-
if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
|
|
953
|
-
return r;
|
|
954
|
-
},
|
|
955
|
-
readTranscript(file, fromLine) {
|
|
956
|
-
let text = "";
|
|
957
|
-
try {
|
|
958
|
-
text = readFileSync3(file, "utf8");
|
|
959
|
-
} catch {
|
|
960
|
-
return { turns: [], line: fromLine };
|
|
961
|
-
}
|
|
962
|
-
return readClaude(text.split("\n"), fromLine);
|
|
963
|
-
},
|
|
964
|
-
facts(session, transcript, hints, state) {
|
|
965
|
-
const out = {};
|
|
966
|
-
if (transcript) Object.assign(out, claudeFacts(transcript, session.id, hints, state));
|
|
967
|
-
const cwd = out.cwd ?? session.cwd;
|
|
968
|
-
Object.assign(out, gitFacts(cwd), out.branch ? { branch: out.branch } : {});
|
|
969
|
-
if (!out.url && typeof hints.desktop_id === "string") out.url = `claude://code/continue/${hints.desktop_id}`;
|
|
970
|
-
if (typeof hints.attended === "boolean") out.attended = hints.attended || !!out.url;
|
|
971
|
-
return out;
|
|
972
|
-
},
|
|
973
|
-
backfill(sinceMs, env = process.env) {
|
|
974
|
-
const rows2 = [];
|
|
975
|
-
const files = [];
|
|
976
|
-
for (const d of claudeDirs(env))
|
|
977
|
-
files.push(...recentFiles(path5.join(d, "projects"), 1, sinceMs, (n) => /^[0-9a-f-]{36}\.jsonl$/.test(n)));
|
|
978
|
-
const noState = { get: () => ({}), save: () => {
|
|
979
|
-
} };
|
|
980
|
-
for (const f of files) {
|
|
981
|
-
const id = path5.basename(f, ".jsonl");
|
|
982
|
-
const facts = claudeFacts(f, id, {}, noState);
|
|
983
|
-
if (!facts.started_at || !facts.last_at) continue;
|
|
984
|
-
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
985
|
-
const cwd = rest.cwd;
|
|
986
|
-
rows2.push({
|
|
987
|
-
session: { id, ...rest, ...gitFacts(cwd), ...rest.branch ? { branch: rest.branch } : {} },
|
|
988
|
-
started_at,
|
|
989
|
-
last_at
|
|
990
|
-
});
|
|
909
|
+
var accountCache = /* @__PURE__ */ new Map();
|
|
910
|
+
function claudeAccount(dir) {
|
|
911
|
+
const hit = accountCache.get(dir);
|
|
912
|
+
if (hit !== void 0) return hit;
|
|
913
|
+
const acct = claudeLogin(dir)?.id ?? null;
|
|
914
|
+
accountCache.set(dir, acct);
|
|
915
|
+
return acct;
|
|
916
|
+
}
|
|
917
|
+
var LINK_RE = /^https:\/\/claude\.ai\/code\/session_[A-Za-z0-9]+$/;
|
|
918
|
+
function transcriptLink(records) {
|
|
919
|
+
for (let i = records.length - 1; i >= 0; i--) {
|
|
920
|
+
const j = records[i];
|
|
921
|
+
if (j.type === "system" && j.subtype === "bridge_status" && typeof j.url === "string" && LINK_RE.test(j.url))
|
|
922
|
+
return j.url;
|
|
923
|
+
if (j.type === "bridge-session" && typeof j.bridgeSessionId === "string") {
|
|
924
|
+
const m = /^(?:cse|session)_([A-Za-z0-9]+)$/.exec(j.bridgeSessionId);
|
|
925
|
+
if (m) return `https://claude.ai/code/session_${m[1]}`;
|
|
991
926
|
}
|
|
992
|
-
return rows2;
|
|
993
927
|
}
|
|
994
|
-
|
|
995
|
-
function prune2(o) {
|
|
996
|
-
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
997
|
-
return o;
|
|
928
|
+
return null;
|
|
998
929
|
}
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
return
|
|
930
|
+
function appTitle(text) {
|
|
931
|
+
const t = String(text).replace(/\s+/g, " ").trim();
|
|
932
|
+
return t.length > 50 ? `${t.slice(0, 50)}...` : t;
|
|
1002
933
|
}
|
|
1003
|
-
function
|
|
1004
|
-
|
|
934
|
+
function askText(j) {
|
|
935
|
+
if (!j || j.type !== "user" || j.isMeta || j.isSidechain) return null;
|
|
936
|
+
const origin = j.origin;
|
|
937
|
+
if (origin?.kind && origin.kind !== "human") return null;
|
|
938
|
+
const c = j.message?.content;
|
|
939
|
+
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();
|
|
940
|
+
if (!text || isInjected(text)) return null;
|
|
941
|
+
return text;
|
|
1005
942
|
}
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
if (typeof v === "string") return v.slice(0, 2e3);
|
|
1011
|
-
if (v && typeof v === "object") {
|
|
1012
|
-
const e = v;
|
|
1013
|
-
if (typeof e.error === "string") return e.error.slice(0, 2e3);
|
|
1014
|
-
if (typeof e.message === "string") return e.message.slice(0, 2e3);
|
|
943
|
+
function firstAsk(records) {
|
|
944
|
+
for (const j of records) {
|
|
945
|
+
const t = askText(j);
|
|
946
|
+
if (t) return appTitle(t);
|
|
1015
947
|
}
|
|
1016
|
-
return
|
|
948
|
+
return null;
|
|
1017
949
|
}
|
|
1018
|
-
function
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
950
|
+
function scanTitles(file, from, found) {
|
|
951
|
+
const out = { custom: found.custom ?? null, ai: found.ai ?? null, upto: from };
|
|
952
|
+
let size = 0;
|
|
953
|
+
try {
|
|
954
|
+
size = statSync2(file).size;
|
|
955
|
+
} catch {
|
|
956
|
+
return out;
|
|
957
|
+
}
|
|
958
|
+
if (size < from) {
|
|
959
|
+
out.upto = 0;
|
|
960
|
+
out.custom = out.ai = null;
|
|
961
|
+
from = 0;
|
|
962
|
+
}
|
|
963
|
+
let pos = 0;
|
|
964
|
+
scanLines(
|
|
965
|
+
file,
|
|
966
|
+
(l) => {
|
|
967
|
+
pos += Buffer.byteLength(l, "utf8") + 1;
|
|
968
|
+
if (pos <= from || l.indexOf('-title"') < 0) return null;
|
|
969
|
+
try {
|
|
970
|
+
const j = JSON.parse(l);
|
|
971
|
+
if (j.type === "custom-title" && j.customTitle) out.custom = j.customTitle;
|
|
972
|
+
if (j.type === "ai-title" && j.aiTitle) out.ai = j.aiTitle;
|
|
973
|
+
} catch {
|
|
974
|
+
}
|
|
975
|
+
return null;
|
|
976
|
+
},
|
|
977
|
+
512 * 1024 * 1024
|
|
978
|
+
);
|
|
979
|
+
out.upto = size;
|
|
980
|
+
return out;
|
|
1023
981
|
}
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
const
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
982
|
+
function claudeFacts(file, sessionId, hints, state) {
|
|
983
|
+
const { head, tail } = ends(file, 64 * 1024, 256 * 1024);
|
|
984
|
+
const H = parseLines(head);
|
|
985
|
+
const T = parseLines(tail);
|
|
986
|
+
const out = {};
|
|
987
|
+
const id = sessionId || path4.basename(file, ".jsonl");
|
|
988
|
+
const mem = state.get(NAME, id);
|
|
989
|
+
let custom = null;
|
|
990
|
+
let ai = null;
|
|
991
|
+
for (const j of H.concat(T)) {
|
|
992
|
+
if (j.type === "custom-title" && typeof j.customTitle === "string") custom = j.customTitle;
|
|
993
|
+
if (j.type === "ai-title" && typeof j.aiTitle === "string") ai = j.aiTitle;
|
|
994
|
+
}
|
|
995
|
+
let size = 0;
|
|
996
|
+
try {
|
|
997
|
+
size = statSync2(file).size;
|
|
998
|
+
} catch {
|
|
999
|
+
}
|
|
1000
|
+
if (!custom && !ai && size > 64 * 1024 + 256 * 1024) {
|
|
1001
|
+
const s = scanTitles(file, Number(mem.scan_upto) || 0, {
|
|
1002
|
+
custom: mem.scan_custom,
|
|
1003
|
+
ai: mem.scan_ai
|
|
1004
|
+
});
|
|
1005
|
+
custom = s.custom;
|
|
1006
|
+
ai = s.ai;
|
|
1007
|
+
state.save(NAME, id, { scan_upto: s.upto, scan_custom: s.custom, scan_ai: s.ai });
|
|
1008
|
+
}
|
|
1009
|
+
const dir = configDirOf(file);
|
|
1010
|
+
const live = claudeLive(dir).get(id) ?? claudeLiveByPid(dir, hints.claude_pid);
|
|
1011
|
+
if (live?.name) state.save(NAME, id, { name: live.name, name_source: live.name_source });
|
|
1012
|
+
const bridge = live?.bridge && /^session_[A-Za-z0-9]+$/.test(live.bridge) ? `https://claude.ai/code/${live.bridge}` : null;
|
|
1013
|
+
const url = bridge ?? transcriptLink(T) ?? transcriptLink(H) ?? (typeof mem.url === "string" ? mem.url : null);
|
|
1014
|
+
if (url) {
|
|
1015
|
+
out.url = url;
|
|
1016
|
+
state.save(NAME, id, { url });
|
|
1017
|
+
}
|
|
1018
|
+
const name = live?.name ? { title: live.name, source: live.name_source } : typeof mem.name === "string" ? { title: mem.name, source: mem.name_source } : null;
|
|
1019
|
+
const pick = name && name.source === "custom" && name || custom && { title: custom, source: "custom" } || name || ai && { title: ai, source: "harness" };
|
|
1020
|
+
let ask2 = typeof mem.first_ask === "string" ? mem.first_ask : firstAsk(H);
|
|
1021
|
+
if (!ask2 && size > 64 * 1024 && !mem.first_ask_none) {
|
|
1022
|
+
ask2 = scanLines(file, (l) => {
|
|
1023
|
+
try {
|
|
1024
|
+
const t = askText(JSON.parse(l));
|
|
1025
|
+
return t ? appTitle(t) : null;
|
|
1026
|
+
} catch {
|
|
1027
|
+
return null;
|
|
1028
|
+
}
|
|
1029
|
+
});
|
|
1030
|
+
if (!ask2 && size > 16 * 1024 * 1024) state.save(NAME, id, { first_ask_none: true });
|
|
1031
|
+
}
|
|
1032
|
+
if (ask2) {
|
|
1033
|
+
out.first_ask = ask2;
|
|
1034
|
+
if (!mem.first_ask) state.save(NAME, id, { first_ask: ask2 });
|
|
1035
|
+
}
|
|
1036
|
+
if (pick) {
|
|
1037
|
+
out.title = String(pick.title).slice(0, 120);
|
|
1038
|
+
out.title_source = pick.source;
|
|
1039
|
+
} else if (ask2) {
|
|
1040
|
+
out.title = ask2;
|
|
1041
|
+
out.title_source = "first-ask";
|
|
1042
|
+
}
|
|
1043
|
+
const acct = claudeAccount(dir);
|
|
1044
|
+
if (acct) out.account_id = acct;
|
|
1045
|
+
for (let i = T.length - 1; i >= 0; i--) {
|
|
1046
|
+
const m = T[i].message?.model;
|
|
1047
|
+
if (T[i]?.type === "assistant" && m && m !== "<synthetic>") {
|
|
1048
|
+
out.model = m;
|
|
1049
|
+
break;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
const meta = [...T].reverse().find((j) => j.version) ?? H.find((j) => j.version);
|
|
1053
|
+
if (meta?.version) out.harness_version = String(meta.version);
|
|
1054
|
+
const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
|
|
1055
|
+
if (times.length) {
|
|
1056
|
+
out.started_at = Math.min(...times);
|
|
1057
|
+
out.last_at = Math.max(...times);
|
|
1058
|
+
}
|
|
1059
|
+
const withCwd = H.find((j) => typeof j.cwd === "string");
|
|
1060
|
+
if (withCwd) {
|
|
1061
|
+
out.cwd = withCwd.cwd;
|
|
1062
|
+
if (typeof withCwd.gitBranch === "string" && withCwd.gitBranch !== "HEAD") out.branch = withCwd.gitBranch;
|
|
1063
|
+
}
|
|
1064
|
+
return out;
|
|
1040
1065
|
}
|
|
1041
|
-
var
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1066
|
+
var REMOTE_RETRY_MS = 6e4;
|
|
1067
|
+
var REMOTE_REFRESH_MS = 30 * 6e4;
|
|
1068
|
+
var CLAUDE_API = "https://api.anthropic.com";
|
|
1069
|
+
var KEYCHAIN = "Claude Code-credentials";
|
|
1070
|
+
var REMOTE_TIMEOUT_MS = 3e3;
|
|
1071
|
+
var KEYCHAIN_TIMEOUT_MS = 2e3;
|
|
1072
|
+
var REMOTE_MAX_BYTES = 1 << 20;
|
|
1073
|
+
var cleanName = (s) => s.replace(/[\p{Cc}\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]/gu, " ").replace(/\s+/g, " ").trim();
|
|
1074
|
+
async function claudeRemoteFacts(id, local, transcript, state, env, now = Date.now()) {
|
|
1075
|
+
if (!transcript || !local.url || !LINK_RE.test(local.url) || local.title_source === "custom") return {};
|
|
1076
|
+
const mem = state.get(NAME, id);
|
|
1077
|
+
let title = typeof mem.remote_title === "string" ? mem.remote_title : null;
|
|
1078
|
+
if (now - (Number(mem.remote_at) || 0) >= (title ? REMOTE_REFRESH_MS : REMOTE_RETRY_MS)) {
|
|
1079
|
+
state.save(NAME, id, { remote_at: now });
|
|
1080
|
+
const token = claudeAiToken(configDirOf(transcript), env, now);
|
|
1081
|
+
const got = token ? await claudeAiTitle(local.url.slice(local.url.lastIndexOf("/") + 1), token) : void 0;
|
|
1082
|
+
if (got !== void 0) {
|
|
1083
|
+
title = got?.slice(0, 200) ?? null;
|
|
1084
|
+
state.save(NAME, id, { remote_title: title });
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
return title ? { title: title.slice(0, 120), title_source: "harness" } : {};
|
|
1088
|
+
}
|
|
1089
|
+
function keychainServices(dir, env) {
|
|
1090
|
+
const named = (p) => `${KEYCHAIN}-${createHash("sha256").update(p.normalize("NFC")).digest("hex").slice(0, 8)}`;
|
|
1091
|
+
const out = /* @__PURE__ */ new Set();
|
|
1092
|
+
if (env.CLAUDE_CONFIG_DIR && path4.resolve(env.CLAUDE_CONFIG_DIR) === dir) out.add(named(env.CLAUDE_CONFIG_DIR));
|
|
1093
|
+
if (dir === path4.join(env.HOME || homeDir(), ".claude")) out.add(KEYCHAIN);
|
|
1094
|
+
out.add(named(dir));
|
|
1095
|
+
return [...out];
|
|
1096
|
+
}
|
|
1097
|
+
var tokenCache = /* @__PURE__ */ new Map();
|
|
1098
|
+
function claudeAiToken(dir, env, now) {
|
|
1099
|
+
const hit = tokenCache.get(dir);
|
|
1100
|
+
if (hit && hit.expires > now + 6e4) return hit.token;
|
|
1101
|
+
const raw = [];
|
|
1102
|
+
if (process.platform === "darwin")
|
|
1103
|
+
for (const service of keychainServices(dir, env)) {
|
|
1104
|
+
try {
|
|
1105
|
+
const user = env.USER || userInfo().username;
|
|
1106
|
+
const account = /^[\w.-]+$/.test(user) ? user : "claude-code-user";
|
|
1107
|
+
raw.push(
|
|
1108
|
+
execFileSync2("security", ["find-generic-password", "-a", account, "-w", "-s", service], {
|
|
1109
|
+
encoding: "utf8",
|
|
1110
|
+
timeout: KEYCHAIN_TIMEOUT_MS,
|
|
1111
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1112
|
+
})
|
|
1113
|
+
);
|
|
1114
|
+
break;
|
|
1115
|
+
} catch {
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1047
1118
|
try {
|
|
1048
|
-
|
|
1119
|
+
raw.push(readFileSync2(path4.join(dir, ".credentials.json"), "utf8"));
|
|
1049
1120
|
} catch {
|
|
1050
1121
|
}
|
|
1051
|
-
for (const
|
|
1122
|
+
for (const r of raw) {
|
|
1052
1123
|
try {
|
|
1053
|
-
const
|
|
1054
|
-
|
|
1124
|
+
const t = r.trim();
|
|
1125
|
+
const o = JSON.parse(/^(?:[0-9a-f]{2})+$/i.test(t) ? Buffer.from(t, "hex").toString("utf8") : t).claudeAiOauth;
|
|
1126
|
+
if (typeof o?.accessToken !== "string" || !o.accessToken) continue;
|
|
1127
|
+
const expires = typeof o.expiresAt === "number" ? o.expiresAt : Number.POSITIVE_INFINITY;
|
|
1128
|
+
if (expires <= now + 6e4) continue;
|
|
1129
|
+
tokenCache.set(dir, { token: o.accessToken, expires });
|
|
1130
|
+
return o.accessToken;
|
|
1055
1131
|
} catch {
|
|
1056
1132
|
}
|
|
1057
1133
|
}
|
|
1058
|
-
|
|
1059
|
-
return map;
|
|
1060
|
-
}
|
|
1061
|
-
function claudeLiveByPid(dir, pid) {
|
|
1062
|
-
if (!pid) return null;
|
|
1063
|
-
try {
|
|
1064
|
-
return registryEntry(JSON.parse(readFileSync3(path5.join(dir, "sessions", `${pid}.json`), "utf8")));
|
|
1065
|
-
} catch {
|
|
1066
|
-
return null;
|
|
1067
|
-
}
|
|
1134
|
+
return null;
|
|
1068
1135
|
}
|
|
1069
|
-
function
|
|
1136
|
+
async function claudeAiTitle(bridge, token) {
|
|
1070
1137
|
try {
|
|
1071
|
-
const
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1138
|
+
const r = await fetch(`${CLAUDE_API}/v1/code/sessions/${bridge}`, {
|
|
1139
|
+
headers: { Authorization: `Bearer ${token}`, "anthropic-version": "2023-06-01", "User-Agent": "sessionpipe" },
|
|
1140
|
+
redirect: "error",
|
|
1141
|
+
signal: AbortSignal.timeout(REMOTE_TIMEOUT_MS)
|
|
1142
|
+
});
|
|
1143
|
+
if (r.status !== 200 || Number(r.headers.get("content-length")) > REMOTE_MAX_BYTES) return void 0;
|
|
1144
|
+
const text = await r.text();
|
|
1145
|
+
if (text.length > REMOTE_MAX_BYTES) return void 0;
|
|
1146
|
+
const j = JSON.parse(text);
|
|
1147
|
+
const s = j?.response_shape ?? j?.session ?? j;
|
|
1148
|
+
const bare = (x) => x.replace(/^(?:session|cse)_/, "");
|
|
1149
|
+
if (typeof s?.id !== "string" || bare(s.id) !== bare(bridge)) return void 0;
|
|
1150
|
+
return typeof s.title === "string" && cleanName(s.title) || null;
|
|
1080
1151
|
} catch {
|
|
1152
|
+
return void 0;
|
|
1081
1153
|
}
|
|
1082
|
-
return null;
|
|
1083
1154
|
}
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
return 0;
|
|
1092
|
-
}
|
|
1093
|
-
};
|
|
1094
|
-
}
|
|
1095
|
-
const table = /* @__PURE__ */ new Map();
|
|
1155
|
+
|
|
1156
|
+
// ../core/src/adapters/headless.ts
|
|
1157
|
+
import { accessSync, constants, readFileSync as readFileSync3, statSync as statSync3 } from "node:fs";
|
|
1158
|
+
import path5 from "node:path";
|
|
1159
|
+
var JOB_TIMEOUT_MS = 30 * 6e4;
|
|
1160
|
+
var OUT_CAP = 1024 * 1024;
|
|
1161
|
+
function isExe(p) {
|
|
1096
1162
|
try {
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
maxBuffer: 8 * 1024 * 1024,
|
|
1101
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
1102
|
-
});
|
|
1103
|
-
for (const l of out.split("\n")) {
|
|
1104
|
-
const m = /^\s*(\d+)\s+(\d+)/.exec(l);
|
|
1105
|
-
if (m) table.set(Number(m[1]), Number(m[2]));
|
|
1106
|
-
}
|
|
1163
|
+
if (!statSync3(p).isFile()) return false;
|
|
1164
|
+
accessSync(p, constants.X_OK);
|
|
1165
|
+
return true;
|
|
1107
1166
|
} catch {
|
|
1167
|
+
return false;
|
|
1108
1168
|
}
|
|
1109
|
-
return (pid) => table.get(pid) || 0;
|
|
1110
1169
|
}
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
const
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
try {
|
|
1117
|
-
const a = JSON.parse(readFileSync3(f, "utf8")).oauthAccount;
|
|
1118
|
-
if (typeof a?.accountUuid === "string" && a.accountUuid) {
|
|
1119
|
-
const email = typeof a.emailAddress === "string" && EMAIL_RE.test(a.emailAddress) ? a.emailAddress : void 0;
|
|
1120
|
-
return { id: a.accountUuid.slice(0, 128), ...email ? { email } : {} };
|
|
1121
|
-
}
|
|
1122
|
-
} catch {
|
|
1123
|
-
}
|
|
1170
|
+
function findBinary(o) {
|
|
1171
|
+
if (o.override && isExe(o.override)) return o.override;
|
|
1172
|
+
for (const d of String(o.env?.PATH || "").split(path5.delimiter)) {
|
|
1173
|
+
if (!d || !path5.isAbsolute(d)) continue;
|
|
1174
|
+
for (const n of o.names) if (isExe(path5.join(d, n))) return path5.join(d, n);
|
|
1124
1175
|
}
|
|
1125
|
-
return
|
|
1176
|
+
for (const d of o.dirs) for (const n of o.names) if (isExe(path5.join(d, n))) return path5.join(d, n);
|
|
1177
|
+
return null;
|
|
1126
1178
|
}
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
let base;
|
|
1179
|
+
function shimTarget(shim) {
|
|
1180
|
+
let text = "";
|
|
1130
1181
|
try {
|
|
1131
|
-
|
|
1182
|
+
text = readFileSync3(shim, "utf8");
|
|
1132
1183
|
} catch {
|
|
1133
1184
|
return null;
|
|
1134
1185
|
}
|
|
1135
|
-
|
|
1136
|
-
|
|
1186
|
+
const m = /"%(?:~dp0|dp0%)\\([^"]+)"\s+%\*/.exec(text);
|
|
1187
|
+
if (!m) return null;
|
|
1188
|
+
const target = path5.resolve(path5.dirname(shim), m[1].split("\\").join(path5.sep));
|
|
1137
1189
|
try {
|
|
1138
|
-
|
|
1190
|
+
return statSync3(target).isFile() ? target : null;
|
|
1139
1191
|
} catch {
|
|
1140
1192
|
return null;
|
|
1141
1193
|
}
|
|
1142
|
-
if (u.protocol !== "https:" && u.protocol !== "http:" || !u.hostname || ANTHROPIC_HOST.test(u.hostname))
|
|
1143
|
-
return null;
|
|
1144
|
-
const origin = `${u.protocol}//${u.host}`;
|
|
1145
|
-
return { id: `api-${createHash("sha256").update(origin).digest("hex").slice(0, 16)}`, name: u.host };
|
|
1146
1194
|
}
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
const
|
|
1150
|
-
|
|
1151
|
-
const acct = claudeLogin(dir)?.id ?? null;
|
|
1152
|
-
accountCache.set(dir, acct);
|
|
1153
|
-
return acct;
|
|
1195
|
+
function spawnable(bin, args) {
|
|
1196
|
+
if (!/\.(cmd|bat)$/i.test(bin)) return { file: bin, args };
|
|
1197
|
+
const target = shimTarget(bin);
|
|
1198
|
+
return target ? { file: process.execPath, args: [target, ...args] } : null;
|
|
1154
1199
|
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1200
|
+
function probe(bin, args) {
|
|
1201
|
+
const s = spawnable(bin, args);
|
|
1202
|
+
return s ? { ...s, shell: false } : { file: [`"${bin}"`, ...args].join(" "), args: [], shell: true };
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
// ../core/src/adapters/antigravity.ts
|
|
1206
|
+
import {
|
|
1207
|
+
existsSync as existsSync3,
|
|
1208
|
+
mkdirSync as mkdirSync2,
|
|
1209
|
+
readdirSync as readdirSync3,
|
|
1210
|
+
readFileSync as readFileSync4,
|
|
1211
|
+
realpathSync,
|
|
1212
|
+
statSync as statSync4,
|
|
1213
|
+
unlinkSync as unlinkSync2,
|
|
1214
|
+
writeFileSync as writeFileSync3
|
|
1215
|
+
} from "node:fs";
|
|
1216
|
+
import path6 from "node:path";
|
|
1217
|
+
var NAME2 = "antigravity";
|
|
1218
|
+
var HOOK_NAME = "sessionpipe";
|
|
1219
|
+
var AG_EVENTS = ["PreInvocation", "PostInvocation", "PostToolUse", "Stop"];
|
|
1220
|
+
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';
|
|
1221
|
+
var antigravityAnswer = (event) => event === "PreToolUse" ? AG_STALE_PRETOOL : "{}\n";
|
|
1222
|
+
var gemini = (env) => env.GEMINI_CLI_HOME || path6.join(homeDir(), ".gemini");
|
|
1223
|
+
var hooksFile = (env) => path6.join(gemini(env), "config", "hooks.json");
|
|
1224
|
+
var projectsDir = (env) => path6.join(gemini(env), "config", "projects");
|
|
1225
|
+
var dataDirs = (env) => ["antigravity", "antigravity-cli", "antigravity-ide"].map((d) => path6.join(gemini(env), d));
|
|
1226
|
+
var ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
|
|
1227
|
+
var validTool2 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
1228
|
+
function entry(cmd) {
|
|
1229
|
+
const handler = (e) => ({ type: "command", command: cmd([NAME2, e]).command, timeout: 5 });
|
|
1230
|
+
const out = { enabled: true };
|
|
1231
|
+
for (const e of AG_EVENTS) out[e] = /Tool/.test(e) ? [{ matcher: "*", hooks: [handler(e)] }] : [handler(e)];
|
|
1232
|
+
return out;
|
|
1233
|
+
}
|
|
1234
|
+
var antigravity = {
|
|
1235
|
+
name: NAME2,
|
|
1236
|
+
events: AG_EVENTS,
|
|
1237
|
+
detect: (env = process.env) => existsSync3(path6.join(gemini(env), "config")) || dataDirs(env).some((d) => existsSync3(d)),
|
|
1238
|
+
configFiles: (env = process.env) => [hooksFile(env)],
|
|
1239
|
+
install(cmd, env = process.env) {
|
|
1240
|
+
const file = hooksFile(env);
|
|
1241
|
+
const f = readJsonFile(file);
|
|
1242
|
+
if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
|
|
1243
|
+
const hooks = f.value;
|
|
1244
|
+
const e = entry(cmd);
|
|
1245
|
+
if (JSON.stringify(hooks[HOOK_NAME]) === JSON.stringify(e)) return [{ file, changed: false }];
|
|
1246
|
+
hooks[HOOK_NAME] = e;
|
|
1247
|
+
mkdirSync2(path6.dirname(file), { recursive: true });
|
|
1248
|
+
writeFileSync3(file, formatJson(hooks, f));
|
|
1249
|
+
return [
|
|
1250
|
+
{
|
|
1251
|
+
file,
|
|
1252
|
+
changed: true,
|
|
1253
|
+
created: !f.existed,
|
|
1254
|
+
note: "hooks.json is read when a conversation starts; open ones report after a restart"
|
|
1255
|
+
}
|
|
1256
|
+
];
|
|
1257
|
+
},
|
|
1258
|
+
uninstall(env = process.env, opts = {}) {
|
|
1259
|
+
const file = hooksFile(env);
|
|
1260
|
+
if (!existsSync3(file)) return [{ file, changed: false }];
|
|
1261
|
+
const f = readJsonFile(file);
|
|
1262
|
+
if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
|
|
1263
|
+
const hooks = f.value;
|
|
1264
|
+
if (!(HOOK_NAME in hooks)) return [{ file, changed: false }];
|
|
1265
|
+
delete hooks[HOOK_NAME];
|
|
1266
|
+
if (opts.created?.includes(file) && !Object.keys(hooks).length) {
|
|
1267
|
+
unlinkSync2(file);
|
|
1268
|
+
return [{ file, changed: true, note: "removed (sessionpipe created it)" }];
|
|
1269
|
+
}
|
|
1270
|
+
writeFileSync3(file, formatJson(hooks, f));
|
|
1271
|
+
return [{ file, changed: true }];
|
|
1272
|
+
},
|
|
1273
|
+
installed(cmd, env = process.env) {
|
|
1274
|
+
const file = hooksFile(env);
|
|
1275
|
+
const f = readJsonFile(file);
|
|
1276
|
+
const have = f.ok ? f.value[HOOK_NAME] : void 0;
|
|
1277
|
+
return [
|
|
1278
|
+
{
|
|
1279
|
+
file,
|
|
1280
|
+
state: !have ? "missing" : JSON.stringify(have) === JSON.stringify(entry(cmd)) ? "current" : "stale"
|
|
1281
|
+
}
|
|
1282
|
+
];
|
|
1283
|
+
},
|
|
1284
|
+
fromHook(input) {
|
|
1285
|
+
const event = input.argv[1] ?? "";
|
|
1286
|
+
let s = {};
|
|
1287
|
+
try {
|
|
1288
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
1289
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1290
|
+
} catch {
|
|
1291
|
+
}
|
|
1292
|
+
const id = s.conversationId ?? s.session_id ?? s.sessionId;
|
|
1293
|
+
const stdout = antigravityAnswer(event);
|
|
1294
|
+
if (!id) return { session: { id: "" }, events: [], stdout };
|
|
1295
|
+
const workspaces = agWorkspaces(s.workspacePaths);
|
|
1296
|
+
const session = { id: String(id), cwd: workspaces[0] ?? input.cwd };
|
|
1297
|
+
if (typeof s.modelName === "string" && s.modelName !== "auto") session.model = s.modelName;
|
|
1298
|
+
const toolCall = s.toolCall;
|
|
1299
|
+
const tool = validTool2(toolCall?.name) ? toolCall?.name : validTool2(s.toolName) ? s.toolName : void 0;
|
|
1300
|
+
const ev = (type, data) => ({ type, data: prune2(data), harnessEvent: event });
|
|
1301
|
+
const events = [];
|
|
1302
|
+
switch (event) {
|
|
1303
|
+
case "PreInvocation":
|
|
1304
|
+
if (Number(s.invocationNum) === 0 || s.invocationNum === void 0) {
|
|
1305
|
+
events.push(ev("session.started", { source: "unknown" }));
|
|
1306
|
+
events.push(ev("turn.started", {}));
|
|
1307
|
+
} else events.push(ev("session.heartbeat", {}));
|
|
1308
|
+
break;
|
|
1309
|
+
case "PostInvocation":
|
|
1310
|
+
break;
|
|
1311
|
+
case "PreToolUse":
|
|
1312
|
+
if (tool) events.push(ev("tool.started", { tool, input: toolCall?.args }));
|
|
1313
|
+
break;
|
|
1314
|
+
case "PostToolUse":
|
|
1315
|
+
if (!tool) events.push(ev("session.heartbeat", {}));
|
|
1316
|
+
else
|
|
1317
|
+
events.push(
|
|
1318
|
+
ev("tool.ended", {
|
|
1319
|
+
tool,
|
|
1320
|
+
ok: !s.error,
|
|
1321
|
+
error: typeof s.error === "string" ? s.error.slice(0, 2e3) : void 0,
|
|
1322
|
+
input: toolCall?.args
|
|
1323
|
+
})
|
|
1324
|
+
);
|
|
1325
|
+
break;
|
|
1326
|
+
case "Stop":
|
|
1327
|
+
events.push(ev("turn.ended", { reason: s.error ? "error" : "stop" }));
|
|
1328
|
+
break;
|
|
1329
|
+
default:
|
|
1330
|
+
return { session, events: [], stdout };
|
|
1331
|
+
}
|
|
1332
|
+
const r = { session, events, stdout, hints: { workspaces } };
|
|
1333
|
+
if (typeof s.transcriptPath === "string") r.transcript = s.transcriptPath;
|
|
1334
|
+
return r;
|
|
1335
|
+
},
|
|
1336
|
+
readTranscript(file, fromLine) {
|
|
1337
|
+
let text = "";
|
|
1338
|
+
try {
|
|
1339
|
+
text = readFileSync4(file, "utf8");
|
|
1340
|
+
} catch {
|
|
1341
|
+
return { turns: [], line: fromLine };
|
|
1342
|
+
}
|
|
1343
|
+
return readAntigravity(text.split("\n"), fromLine);
|
|
1344
|
+
},
|
|
1345
|
+
facts(session, transcript, hints) {
|
|
1346
|
+
if (!transcript) return {};
|
|
1347
|
+
return antigravityFacts(transcript, {
|
|
1348
|
+
conversation: session.id,
|
|
1349
|
+
workspaces: hints.workspaces ?? (session.cwd ? [session.cwd] : []),
|
|
1350
|
+
env: process.env
|
|
1351
|
+
});
|
|
1352
|
+
},
|
|
1353
|
+
backfill(sinceMs, env = process.env) {
|
|
1354
|
+
const rows2 = [];
|
|
1355
|
+
for (const data of dataDirs(env)) {
|
|
1356
|
+
let ids = [];
|
|
1357
|
+
try {
|
|
1358
|
+
ids = readdirSync3(path6.join(data, "brain")).filter((n) => /^[0-9a-f-]{36}$/i.test(n));
|
|
1359
|
+
} catch {
|
|
1360
|
+
}
|
|
1361
|
+
for (const id of ids) {
|
|
1362
|
+
const f = path6.join(data, "brain", id, ".system_generated", "logs", "transcript.jsonl");
|
|
1363
|
+
try {
|
|
1364
|
+
if (statSync4(f).mtimeMs < sinceMs) continue;
|
|
1365
|
+
} catch {
|
|
1366
|
+
continue;
|
|
1367
|
+
}
|
|
1368
|
+
const facts = antigravityFacts(f, { conversation: id, workspaces: [], env });
|
|
1369
|
+
if (!facts.started_at || !facts.last_at) continue;
|
|
1370
|
+
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
1371
|
+
rows2.push({ session: { id, ...rest }, started_at, last_at });
|
|
1372
|
+
}
|
|
1164
1373
|
}
|
|
1374
|
+
return rows2;
|
|
1165
1375
|
}
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
return t.length > 50 ? `${t.slice(0, 50)}...` : t;
|
|
1171
|
-
}
|
|
1172
|
-
function askText(j) {
|
|
1173
|
-
if (!j || j.type !== "user" || j.isMeta || j.isSidechain) return null;
|
|
1174
|
-
const origin = j.origin;
|
|
1175
|
-
if (origin?.kind && origin.kind !== "human") return null;
|
|
1176
|
-
const c = j.message?.content;
|
|
1177
|
-
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();
|
|
1178
|
-
if (!text || isInjected(text)) return null;
|
|
1179
|
-
return text;
|
|
1376
|
+
};
|
|
1377
|
+
function prune2(o) {
|
|
1378
|
+
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
1379
|
+
return o;
|
|
1180
1380
|
}
|
|
1181
|
-
function
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1381
|
+
function agPath(p) {
|
|
1382
|
+
if (typeof p !== "string" || !p) return null;
|
|
1383
|
+
let s = p;
|
|
1384
|
+
if (/^file:/i.test(s)) {
|
|
1385
|
+
try {
|
|
1386
|
+
s = decodeURIComponent(new URL(s).pathname);
|
|
1387
|
+
} catch {
|
|
1388
|
+
return null;
|
|
1389
|
+
}
|
|
1390
|
+
if (process.platform === "win32" && /^\/[A-Za-z]:/.test(s)) s = s.slice(1);
|
|
1391
|
+
} else if (/^[a-z][a-z0-9+.-]*:\/\//i.test(s)) return null;
|
|
1392
|
+
if (!path6.isAbsolute(s)) return null;
|
|
1393
|
+
return path6.resolve(s);
|
|
1187
1394
|
}
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1395
|
+
var agWorkspaces = (paths) => (Array.isArray(paths) ? paths : []).map(agPath).filter((p) => !!p).filter((p, i, a) => a.indexOf(p) === i);
|
|
1396
|
+
function agVariants(p) {
|
|
1397
|
+
const out = [p];
|
|
1191
1398
|
try {
|
|
1192
|
-
|
|
1399
|
+
const r = realpathSync.native(p);
|
|
1400
|
+
if (r !== p) out.push(r);
|
|
1193
1401
|
} catch {
|
|
1194
|
-
return out;
|
|
1195
|
-
}
|
|
1196
|
-
if (size < from) {
|
|
1197
|
-
out.upto = 0;
|
|
1198
|
-
out.custom = out.ai = null;
|
|
1199
|
-
from = 0;
|
|
1200
1402
|
}
|
|
1201
|
-
let pos = 0;
|
|
1202
|
-
scanLines(
|
|
1203
|
-
file,
|
|
1204
|
-
(l) => {
|
|
1205
|
-
pos += Buffer.byteLength(l, "utf8") + 1;
|
|
1206
|
-
if (pos <= from || l.indexOf('-title"') < 0) return null;
|
|
1207
|
-
try {
|
|
1208
|
-
const j = JSON.parse(l);
|
|
1209
|
-
if (j.type === "custom-title" && j.customTitle) out.custom = j.customTitle;
|
|
1210
|
-
if (j.type === "ai-title" && j.aiTitle) out.ai = j.aiTitle;
|
|
1211
|
-
} catch {
|
|
1212
|
-
}
|
|
1213
|
-
return null;
|
|
1214
|
-
},
|
|
1215
|
-
512 * 1024 * 1024
|
|
1216
|
-
);
|
|
1217
|
-
out.upto = size;
|
|
1218
1403
|
return out;
|
|
1219
1404
|
}
|
|
1220
|
-
function
|
|
1221
|
-
const
|
|
1222
|
-
|
|
1223
|
-
const T = parseLines(tail);
|
|
1224
|
-
const out = {};
|
|
1225
|
-
const id = sessionId || path5.basename(file, ".jsonl");
|
|
1226
|
-
const mem = state.get(NAME2, id);
|
|
1227
|
-
let custom = null;
|
|
1228
|
-
let ai = null;
|
|
1229
|
-
for (const j of H.concat(T)) {
|
|
1230
|
-
if (j.type === "custom-title" && typeof j.customTitle === "string") custom = j.customTitle;
|
|
1231
|
-
if (j.type === "ai-title" && typeof j.aiTitle === "string") ai = j.aiTitle;
|
|
1232
|
-
}
|
|
1233
|
-
let size = 0;
|
|
1405
|
+
function agProjects(dir) {
|
|
1406
|
+
const out = [];
|
|
1407
|
+
let names = [];
|
|
1234
1408
|
try {
|
|
1235
|
-
|
|
1409
|
+
names = readdirSync3(dir).filter((n) => n.endsWith(".json"));
|
|
1236
1410
|
} catch {
|
|
1411
|
+
return out;
|
|
1237
1412
|
}
|
|
1238
|
-
|
|
1239
|
-
const
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1413
|
+
for (const n of names) {
|
|
1414
|
+
const id = n.slice(0, -5);
|
|
1415
|
+
if (!ID_RE.test(id)) continue;
|
|
1416
|
+
let j;
|
|
1417
|
+
try {
|
|
1418
|
+
j = JSON.parse(readFileSync4(path6.join(dir, n), "utf8"));
|
|
1419
|
+
} catch {
|
|
1420
|
+
continue;
|
|
1421
|
+
}
|
|
1422
|
+
const folders = [];
|
|
1423
|
+
for (const r of j?.projectResources?.resources ?? []) {
|
|
1424
|
+
const f = agPath(r?.folderUri ?? r?.gitFolder?.folderUri);
|
|
1425
|
+
if (f) folders.push(...agVariants(f));
|
|
1426
|
+
}
|
|
1427
|
+
if (folders.length) out.push({ id, folders });
|
|
1246
1428
|
}
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
const
|
|
1251
|
-
const
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
state.save(NAME2, id, { url });
|
|
1429
|
+
return out;
|
|
1430
|
+
}
|
|
1431
|
+
function agMatchProject(projects, workspaces) {
|
|
1432
|
+
const ws = [];
|
|
1433
|
+
for (const w of workspaces) {
|
|
1434
|
+
const p = agPath(w);
|
|
1435
|
+
if (p) ws.push(...agVariants(p));
|
|
1255
1436
|
}
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
} catch {
|
|
1265
|
-
return null;
|
|
1437
|
+
let best = null;
|
|
1438
|
+
let bestLen = -1;
|
|
1439
|
+
for (const pr of projects)
|
|
1440
|
+
for (const f of pr.folders) {
|
|
1441
|
+
const within = ws.some((w) => w === f || w.startsWith(f.endsWith(path6.sep) ? f : f + path6.sep));
|
|
1442
|
+
if (within && f.length > bestLen) {
|
|
1443
|
+
best = pr.id;
|
|
1444
|
+
bestLen = f.length;
|
|
1266
1445
|
}
|
|
1267
|
-
}
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1446
|
+
}
|
|
1447
|
+
return best;
|
|
1448
|
+
}
|
|
1449
|
+
function antigravityRemoteUrl(transcript, opts) {
|
|
1450
|
+
const i = transcript.indexOf(`${path6.sep}brain${path6.sep}`);
|
|
1451
|
+
const dirs = (i > 0 ? [transcript.slice(0, i)] : []).concat(dataDirs(opts.env));
|
|
1452
|
+
for (const d of dirs) {
|
|
1453
|
+
try {
|
|
1454
|
+
const m = /installation_uuid:\s*"([0-9a-f-]{36})"/i.exec(
|
|
1455
|
+
readFileSync4(path6.join(d, "antigravity_state.pbtxt"), "utf8")
|
|
1456
|
+
);
|
|
1457
|
+
if (!m) continue;
|
|
1458
|
+
const base = `https://antigravity.google.com/r/${m[1]}-v2`;
|
|
1459
|
+
const conv = opts.conversation ?? "";
|
|
1460
|
+
if (!ID_RE.test(conv) || !opts.workspaces?.length) return base;
|
|
1461
|
+
const project = agMatchProject(agProjects(opts.projectsDir ?? projectsDir(opts.env)), opts.workspaces);
|
|
1462
|
+
return project ? `${base}?p=${encodeURIComponent(`c/${conv}?section=${project}`)}` : base;
|
|
1463
|
+
} catch {
|
|
1464
|
+
}
|
|
1273
1465
|
}
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1466
|
+
return null;
|
|
1467
|
+
}
|
|
1468
|
+
function antigravityFacts(file, opts) {
|
|
1469
|
+
const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
|
|
1470
|
+
const H = parseLines(head);
|
|
1471
|
+
const T = parseLines(tail);
|
|
1472
|
+
const out = {};
|
|
1473
|
+
const url = antigravityRemoteUrl(file, opts);
|
|
1474
|
+
if (url) out.url = url;
|
|
1475
|
+
for (const j of H) {
|
|
1476
|
+
if (j.type !== "USER_INPUT" || typeof j.content !== "string") continue;
|
|
1477
|
+
const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
|
|
1478
|
+
const text = (m ? m[1] : j.content).trim();
|
|
1479
|
+
const line = (text.split("\n").find((l) => l.trim()) ?? "").trim();
|
|
1480
|
+
if (!line || /^<[a-z_-]+>/i.test(line)) continue;
|
|
1481
|
+
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
1279
1482
|
out.title_source = "first-ask";
|
|
1483
|
+
break;
|
|
1280
1484
|
}
|
|
1281
|
-
const
|
|
1282
|
-
if (acct) out.account_id = acct;
|
|
1283
|
-
for (let i = T.length - 1; i >= 0; i--) {
|
|
1284
|
-
const m = T[i].message?.model;
|
|
1285
|
-
if (T[i]?.type === "assistant" && m && m !== "<synthetic>") {
|
|
1286
|
-
out.model = m;
|
|
1287
|
-
break;
|
|
1288
|
-
}
|
|
1289
|
-
}
|
|
1290
|
-
const meta = [...T].reverse().find((j) => j.version) ?? H.find((j) => j.version);
|
|
1291
|
-
if (meta?.version) out.harness_version = String(meta.version);
|
|
1292
|
-
const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
|
|
1485
|
+
const times = H.concat(T).map((j) => Date.parse(String(j.created_at ?? j.timestamp ?? ""))).filter((n) => n > 0);
|
|
1293
1486
|
if (times.length) {
|
|
1294
1487
|
out.started_at = Math.min(...times);
|
|
1295
1488
|
out.last_at = Math.max(...times);
|
|
1296
1489
|
}
|
|
1297
|
-
const withCwd = H.find((j) => typeof j.cwd === "string");
|
|
1298
|
-
if (withCwd) {
|
|
1299
|
-
out.cwd = withCwd.cwd;
|
|
1300
|
-
if (typeof withCwd.gitBranch === "string" && withCwd.gitBranch !== "HEAD") out.branch = withCwd.gitBranch;
|
|
1301
|
-
}
|
|
1302
1490
|
return out;
|
|
1303
1491
|
}
|
|
1304
1492
|
|
|
@@ -1311,15 +1499,24 @@ var NONCE_TTL_MS = 25 * 36e5;
|
|
|
1311
1499
|
var MODEL_RE = /^[A-Za-z0-9][A-Za-z0-9._:/@[\]-]*$/;
|
|
1312
1500
|
var START_PERMISSIONS = ["read", "safe", "edits", "auto"];
|
|
1313
1501
|
var levelRank = (l) => START_PERMISSIONS.indexOf(l);
|
|
1502
|
+
var FIELDS = {
|
|
1503
|
+
prompt: ["text", "files"],
|
|
1504
|
+
"permission.answer": ["for", "decision"],
|
|
1505
|
+
cancel: [],
|
|
1506
|
+
start: ["text", "cwd", "files", "permission", "model"],
|
|
1507
|
+
"key.add": ["key", "name"],
|
|
1508
|
+
pool: ["pool_action", "pool_account", "pool_order", "pool_code"]
|
|
1509
|
+
};
|
|
1510
|
+
var POOL_FIELDS = FIELDS.pool;
|
|
1314
1511
|
var enc = new TextEncoder();
|
|
1315
1512
|
var dec = new TextDecoder();
|
|
1316
1513
|
|
|
1317
1514
|
// ../core/src/adapters/codex.ts
|
|
1318
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as
|
|
1319
|
-
import
|
|
1515
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
1516
|
+
import path7 from "node:path";
|
|
1320
1517
|
|
|
1321
1518
|
// ../core/src/adapters/codex-config.ts
|
|
1322
|
-
var NOTIFY_LINE = /^[ \t]*notify[ \t]*=[ \t]*(\[.*\])[ \t]*(?:#.*)
|
|
1519
|
+
var NOTIFY_LINE = /^[ \t]*notify[ \t]*=[ \t]*(\[.*\])[ \t]*(?:#.*)?(?=\r?$)/m;
|
|
1323
1520
|
var NOTIFY_KEY = /^[ \t]*notify[ \t]*=/m;
|
|
1324
1521
|
var OURS2 = /(?:hook\.js|sessionpipe-hook)",\s*"codex",\s*"notify"/;
|
|
1325
1522
|
function scan(line, st) {
|
|
@@ -1409,10 +1606,11 @@ function dropLine(text, index, length) {
|
|
|
1409
1606
|
}
|
|
1410
1607
|
function insertTopLevel(text, line) {
|
|
1411
1608
|
const { head, tail } = split(text);
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1609
|
+
const body = head.replace(/\s*$/, "");
|
|
1610
|
+
const eol = (body.includes("\n") ? body : text).includes("\r\n") ? "\r\n" : "\n";
|
|
1611
|
+
const top = head.trim() ? head.replace(/\s*$/, eol) : "";
|
|
1612
|
+
if (!tail) return top + line + eol;
|
|
1613
|
+
return `${top + line}${eol}${eol}${tail}`;
|
|
1416
1614
|
}
|
|
1417
1615
|
function withNotify(text, line) {
|
|
1418
1616
|
const n = readNotify(text);
|
|
@@ -1461,25 +1659,25 @@ var CODEX_EVENTS = [
|
|
|
1461
1659
|
];
|
|
1462
1660
|
var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
1463
1661
|
var eventsFor2 = (lean) => lean ? CODEX_EVENTS.filter((e) => e !== "PreToolUse") : [...CODEX_EVENTS];
|
|
1464
|
-
var codexHome = (env) => env.CODEX_HOME ||
|
|
1465
|
-
var hooksFile2 = (env) =>
|
|
1466
|
-
var configFile2 = (env) =>
|
|
1467
|
-
var sessionsDir = (env) =>
|
|
1662
|
+
var codexHome = (env) => env.CODEX_HOME || path7.join(homeDir(), ".codex");
|
|
1663
|
+
var hooksFile2 = (env) => path7.join(codexHome(env), "hooks.json");
|
|
1664
|
+
var configFile2 = (env) => path7.join(codexHome(env), "config.toml");
|
|
1665
|
+
var sessionsDir = (env) => path7.join(codexHome(env), "sessions");
|
|
1468
1666
|
var validTool3 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
1469
1667
|
var endReason2 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
|
|
1470
1668
|
function installNotify(cmd, env) {
|
|
1471
1669
|
const file = configFile2(env);
|
|
1472
1670
|
let text = "";
|
|
1473
1671
|
try {
|
|
1474
|
-
text =
|
|
1672
|
+
text = readFileSync5(file, "utf8");
|
|
1475
1673
|
} catch {
|
|
1476
1674
|
}
|
|
1477
1675
|
const argv = cmd([NAME3, "notify"]).argv;
|
|
1478
1676
|
const line = `notify = ${JSON.stringify(argv)}`;
|
|
1479
1677
|
const n = readNotify(text);
|
|
1480
1678
|
const write = (next) => {
|
|
1481
|
-
mkdirSync3(
|
|
1482
|
-
|
|
1679
|
+
mkdirSync3(path7.dirname(file), { recursive: true });
|
|
1680
|
+
writeFileSync4(file, next);
|
|
1483
1681
|
};
|
|
1484
1682
|
if (n && !n.other) {
|
|
1485
1683
|
if (!n.misplaced && JSON.stringify(n.argv) === JSON.stringify(argv))
|
|
@@ -1517,19 +1715,19 @@ function uninstallNotify(env) {
|
|
|
1517
1715
|
const file = configFile2(env);
|
|
1518
1716
|
let text;
|
|
1519
1717
|
try {
|
|
1520
|
-
text =
|
|
1718
|
+
text = readFileSync5(file, "utf8");
|
|
1521
1719
|
} catch {
|
|
1522
1720
|
return { file, changed: false };
|
|
1523
1721
|
}
|
|
1524
1722
|
const n = readNotify(text);
|
|
1525
1723
|
if (!n) return { file, changed: false };
|
|
1526
1724
|
if (!n.other) {
|
|
1527
|
-
|
|
1725
|
+
writeFileSync4(file, withoutNotify(text));
|
|
1528
1726
|
return { file, changed: true };
|
|
1529
1727
|
}
|
|
1530
1728
|
const prev = previousNotify(n.argv);
|
|
1531
1729
|
if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev))) {
|
|
1532
|
-
|
|
1730
|
+
writeFileSync4(file, replaceNotify(text, n, `notify = ${stripPreviousRaw(n.raw)}`));
|
|
1533
1731
|
return { file, changed: true };
|
|
1534
1732
|
}
|
|
1535
1733
|
return { file, changed: false };
|
|
@@ -1545,7 +1743,7 @@ function findRollout(threadId, env = process.env) {
|
|
|
1545
1743
|
return;
|
|
1546
1744
|
}
|
|
1547
1745
|
for (const n of names.sort().reverse()) {
|
|
1548
|
-
const p =
|
|
1746
|
+
const p = path7.join(dir, n);
|
|
1549
1747
|
if (n.endsWith(`-${threadId}.jsonl`)) {
|
|
1550
1748
|
best = p;
|
|
1551
1749
|
return;
|
|
@@ -1589,7 +1787,7 @@ var codex = {
|
|
|
1589
1787
|
];
|
|
1590
1788
|
let text = "";
|
|
1591
1789
|
try {
|
|
1592
|
-
text =
|
|
1790
|
+
text = readFileSync5(configFile2(env), "utf8");
|
|
1593
1791
|
} catch {
|
|
1594
1792
|
}
|
|
1595
1793
|
const n = readNotify(text);
|
|
@@ -1716,7 +1914,7 @@ var codex = {
|
|
|
1716
1914
|
readTranscript(file, fromLine) {
|
|
1717
1915
|
let text = "";
|
|
1718
1916
|
try {
|
|
1719
|
-
text =
|
|
1917
|
+
text = readFileSync5(file, "utf8");
|
|
1720
1918
|
} catch {
|
|
1721
1919
|
return { turns: [], line: fromLine };
|
|
1722
1920
|
}
|
|
@@ -1730,7 +1928,7 @@ var codex = {
|
|
|
1730
1928
|
backfill(sinceMs, env = process.env) {
|
|
1731
1929
|
const rows2 = [];
|
|
1732
1930
|
for (const f of recentFiles(sessionsDir(env), 3, sinceMs, (n) => /^rollout-.*\.jsonl$/.test(n))) {
|
|
1733
|
-
const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(
|
|
1931
|
+
const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(path7.basename(f));
|
|
1734
1932
|
if (!m?.[1]) continue;
|
|
1735
1933
|
if (codexSubagent(rolloutMeta(f))) continue;
|
|
1736
1934
|
const facts = codexFacts(f);
|
|
@@ -1739,12 +1937,40 @@ var codex = {
|
|
|
1739
1937
|
rows2.push({ session: { id: m[1], ...rest }, started_at, last_at });
|
|
1740
1938
|
}
|
|
1741
1939
|
return rows2;
|
|
1742
|
-
}
|
|
1940
|
+
},
|
|
1941
|
+
limits: (transcript) => codexLimits(transcript)
|
|
1743
1942
|
};
|
|
1943
|
+
var epochIso2 = (t) => typeof t === "number" && t > 0 && t < 1e11 ? new Date(t * 1e3).toISOString() : void 0;
|
|
1744
1944
|
function prune3(o) {
|
|
1745
1945
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
1746
1946
|
return o;
|
|
1747
1947
|
}
|
|
1948
|
+
function codexLimits(file) {
|
|
1949
|
+
const { tail } = ends(file, 0, 256 * 1024);
|
|
1950
|
+
for (const r of parseLines(tail).reverse()) {
|
|
1951
|
+
const p = r.payload;
|
|
1952
|
+
if (r.type !== "event_msg" || p?.type !== "token_count") continue;
|
|
1953
|
+
if (!p.rate_limits || typeof p.rate_limits !== "object") continue;
|
|
1954
|
+
const rl = p.rate_limits;
|
|
1955
|
+
if (rl.limit_id != null && rl.limit_id !== "codex") continue;
|
|
1956
|
+
const windows = [];
|
|
1957
|
+
for (const id of ["primary", "secondary"]) {
|
|
1958
|
+
const w = rl[id];
|
|
1959
|
+
if (typeof w?.used_percent !== "number" || w.used_percent < 0) continue;
|
|
1960
|
+
windows.push(
|
|
1961
|
+
prune3({
|
|
1962
|
+
id,
|
|
1963
|
+
used_percent: w.used_percent,
|
|
1964
|
+
window_minutes: Number.isInteger(w.window_minutes) && Number(w.window_minutes) > 0 ? Number(w.window_minutes) : void 0,
|
|
1965
|
+
resets_at: epochIso2(w.resets_at)
|
|
1966
|
+
})
|
|
1967
|
+
);
|
|
1968
|
+
}
|
|
1969
|
+
if (!windows.length) return null;
|
|
1970
|
+
return prune3({ windows, plan: typeof rl.plan_type === "string" ? rl.plan_type.slice(0, 64) : void 0 });
|
|
1971
|
+
}
|
|
1972
|
+
return null;
|
|
1973
|
+
}
|
|
1748
1974
|
function codexFacts(file) {
|
|
1749
1975
|
const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
|
|
1750
1976
|
let H = parseLines(head);
|
|
@@ -1789,7 +2015,7 @@ function codexFacts(file) {
|
|
|
1789
2015
|
// ../core/src/adapters/devin.ts
|
|
1790
2016
|
import { createHash as createHash2 } from "node:crypto";
|
|
1791
2017
|
import { existsSync as existsSync6, readlinkSync } from "node:fs";
|
|
1792
|
-
import
|
|
2018
|
+
import path8 from "node:path";
|
|
1793
2019
|
|
|
1794
2020
|
// ../core/src/readers/sqlite.ts
|
|
1795
2021
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
@@ -1973,11 +2199,11 @@ function foreignEvent(file) {
|
|
|
1973
2199
|
return Object.keys(hooks).find((k) => !ok.includes(k));
|
|
1974
2200
|
}
|
|
1975
2201
|
var homeOf = (env) => env.HOME || homeDir();
|
|
1976
|
-
var appData = (env) => env.APPDATA ||
|
|
1977
|
-
var devinConfigDir = (env = process.env) => process.platform === "win32" ?
|
|
1978
|
-
var configFile3 = (env) =>
|
|
1979
|
-
var devinDataDir = (env = process.env) => process.platform === "win32" ?
|
|
1980
|
-
var sessionsDb = (env) =>
|
|
2202
|
+
var appData = (env) => env.APPDATA || path8.join(homeOf(env), "AppData", "Roaming");
|
|
2203
|
+
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");
|
|
2204
|
+
var configFile3 = (env) => path8.join(devinConfigDir(env), "config.json");
|
|
2205
|
+
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");
|
|
2206
|
+
var sessionsDb = (env) => path8.join(devinDataDir(env), "sessions.db");
|
|
1981
2207
|
var SESSION_ID = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
1982
2208
|
var validTool4 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
1983
2209
|
var endReason3 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
|
|
@@ -2149,7 +2375,7 @@ var BACKFILL_SQL = `SELECT s.id, ${SESSION_FIELDS} FROM sessions s
|
|
|
2149
2375
|
function sessionFacts(row) {
|
|
2150
2376
|
const out = {};
|
|
2151
2377
|
if (typeof row.model === "string" && row.model) out.model = row.model;
|
|
2152
|
-
if (typeof row.working_directory === "string" &&
|
|
2378
|
+
if (typeof row.working_directory === "string" && path8.isAbsolute(row.working_directory))
|
|
2153
2379
|
out.cwd = row.working_directory;
|
|
2154
2380
|
const started = Number(row.created_at);
|
|
2155
2381
|
const last = Number(row.last_activity_at);
|
|
@@ -2194,8 +2420,8 @@ function readDevinChain(db, session, fromLine) {
|
|
|
2194
2420
|
}
|
|
2195
2421
|
function devinVersion(env = process.env) {
|
|
2196
2422
|
try {
|
|
2197
|
-
const target = readlinkSync(
|
|
2198
|
-
const name =
|
|
2423
|
+
const target = readlinkSync(path8.join(devinDataDir(env), "_versions", "current"));
|
|
2424
|
+
const name = path8.basename(target);
|
|
2199
2425
|
return /^[0-9][0-9A-Za-z.+-]{0,63}$/.test(name) ? name : void 0;
|
|
2200
2426
|
} catch {
|
|
2201
2427
|
return void 0;
|
|
@@ -2209,8 +2435,8 @@ function devinAccount(env = process.env) {
|
|
|
2209
2435
|
|
|
2210
2436
|
// ../core/src/adapters/gemini-cli.ts
|
|
2211
2437
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
2212
|
-
import { existsSync as existsSync7, readFileSync as
|
|
2213
|
-
import
|
|
2438
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
|
|
2439
|
+
import path9 from "node:path";
|
|
2214
2440
|
var NAME5 = "gemini-cli";
|
|
2215
2441
|
var GEMINI_EVENTS = [
|
|
2216
2442
|
"SessionStart",
|
|
@@ -2223,26 +2449,31 @@ var GEMINI_EVENTS = [
|
|
|
2223
2449
|
"SessionEnd"
|
|
2224
2450
|
];
|
|
2225
2451
|
var OPTS4 = { matcherFor: (e) => /Tool/.test(e) ? ".*" : void 0, timeout: 5e3 };
|
|
2226
|
-
var geminiDir = (env) => env.GEMINI_CLI_HOME ||
|
|
2227
|
-
var settings = (env) =>
|
|
2452
|
+
var geminiDir = (env) => env.GEMINI_CLI_HOME || path9.join(homeDir(), ".gemini");
|
|
2453
|
+
var settings = (env) => path9.join(geminiDir(env), "settings.json");
|
|
2228
2454
|
var validTool5 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
2229
2455
|
var eventsFor4 = (lean) => lean ? GEMINI_EVENTS.filter((e) => e !== "BeforeTool") : [...GEMINI_EVENTS];
|
|
2456
|
+
function geminiVersion(platform = process.platform, env = process.env) {
|
|
2457
|
+
const win = platform === "win32";
|
|
2458
|
+
const bin = win ? findBinary({ names: ["gemini.exe", "gemini.cmd"], dirs: [path9.dirname(process.execPath)], env }) : "gemini";
|
|
2459
|
+
if (!bin) throw new Error("no gemini on PATH");
|
|
2460
|
+
const c = probe(bin, ["--version"]);
|
|
2461
|
+
return execFileSync4(c.file, c.args, {
|
|
2462
|
+
encoding: "utf8",
|
|
2463
|
+
timeout: 4e3,
|
|
2464
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2465
|
+
env,
|
|
2466
|
+
shell: c.shell
|
|
2467
|
+
}).trim();
|
|
2468
|
+
}
|
|
2230
2469
|
function geminiHooksActive(settingsFile) {
|
|
2231
|
-
|
|
2232
|
-
try {
|
|
2233
|
-
tools = JSON.parse(readFileSync5(settingsFile, "utf8")).tools;
|
|
2234
|
-
} catch {
|
|
2235
|
-
}
|
|
2470
|
+
const tools = readJson(settingsFile).tools;
|
|
2236
2471
|
if (tools?.enableHooks === true) return { active: true };
|
|
2237
2472
|
if (tools?.enableHooks === false)
|
|
2238
2473
|
return { active: false, note: "inactive: tools.enableHooks is false in settings.json" };
|
|
2239
2474
|
let version = "";
|
|
2240
2475
|
try {
|
|
2241
|
-
version =
|
|
2242
|
-
encoding: "utf8",
|
|
2243
|
-
timeout: 4e3,
|
|
2244
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
2245
|
-
}).trim();
|
|
2476
|
+
version = geminiVersion();
|
|
2246
2477
|
} catch {
|
|
2247
2478
|
return {
|
|
2248
2479
|
active: true,
|
|
@@ -2260,7 +2491,7 @@ var geminiCli = {
|
|
|
2260
2491
|
name: NAME5,
|
|
2261
2492
|
events: GEMINI_EVENTS,
|
|
2262
2493
|
// ~/.gemini also belongs to Antigravity; Gemini CLI is here when its settings or its chat logs exist.
|
|
2263
|
-
detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(
|
|
2494
|
+
detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(path9.join(geminiDir(env), "tmp")),
|
|
2264
2495
|
configFiles: (env = process.env) => [settings(env)],
|
|
2265
2496
|
install: (cmd, env = process.env, opts = {}) => {
|
|
2266
2497
|
const r = installClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
|
|
@@ -2358,7 +2589,7 @@ var geminiCli = {
|
|
|
2358
2589
|
readTranscript(file, fromLine) {
|
|
2359
2590
|
let text = "";
|
|
2360
2591
|
try {
|
|
2361
|
-
text =
|
|
2592
|
+
text = readFileSync6(file, "utf8");
|
|
2362
2593
|
} catch {
|
|
2363
2594
|
return { turns: [], line: fromLine };
|
|
2364
2595
|
}
|
|
@@ -2398,12 +2629,12 @@ var ADAPTERS = [claudeCode, codex, geminiCli, antigravity, devin];
|
|
|
2398
2629
|
var adapterByName = (name) => ADAPTERS.find((a) => a.name === name);
|
|
2399
2630
|
|
|
2400
2631
|
// ../core/src/config.ts
|
|
2401
|
-
import { chmodSync, existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as
|
|
2402
|
-
import
|
|
2632
|
+
import { chmodSync, existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "node:fs";
|
|
2633
|
+
import path10 from "node:path";
|
|
2403
2634
|
var DEFAULT_CONFIG = { sinks: [], harnesses: {}, update_check: true, keep_days: 30 };
|
|
2404
2635
|
function readConfig(file = configFile()) {
|
|
2405
2636
|
try {
|
|
2406
|
-
const j = JSON.parse(
|
|
2637
|
+
const j = JSON.parse(readFileSync7(file, "utf8"));
|
|
2407
2638
|
return {
|
|
2408
2639
|
...DEFAULT_CONFIG,
|
|
2409
2640
|
...j,
|
|
@@ -2415,8 +2646,8 @@ function readConfig(file = configFile()) {
|
|
|
2415
2646
|
}
|
|
2416
2647
|
}
|
|
2417
2648
|
function writeConfig(c, file = configFile()) {
|
|
2418
|
-
mkdirSync4(
|
|
2419
|
-
|
|
2649
|
+
mkdirSync4(path10.dirname(file), { recursive: true, mode: 448 });
|
|
2650
|
+
writeFileSync5(file, `${JSON.stringify(c, null, 2)}
|
|
2420
2651
|
`, { mode: 384 });
|
|
2421
2652
|
if (process.platform !== "win32" && existsSync8(file)) {
|
|
2422
2653
|
try {
|
|
@@ -2503,14 +2734,14 @@ import {
|
|
|
2503
2734
|
mkdirSync as mkdirSync5,
|
|
2504
2735
|
openSync as openSync2,
|
|
2505
2736
|
readdirSync as readdirSync5,
|
|
2506
|
-
readFileSync as
|
|
2737
|
+
readFileSync as readFileSync8,
|
|
2507
2738
|
readSync as readSync2,
|
|
2508
2739
|
renameSync,
|
|
2509
|
-
statSync as
|
|
2740
|
+
statSync as statSync5,
|
|
2510
2741
|
unlinkSync as unlinkSync3,
|
|
2511
|
-
writeFileSync as
|
|
2742
|
+
writeFileSync as writeFileSync6
|
|
2512
2743
|
} from "node:fs";
|
|
2513
|
-
import
|
|
2744
|
+
import path11 from "node:path";
|
|
2514
2745
|
var READ_CHUNK = 1024 * 1024;
|
|
2515
2746
|
var READ_MAX = 8 * 1024 * 1024;
|
|
2516
2747
|
var Outbox = class {
|
|
@@ -2519,13 +2750,13 @@ var Outbox = class {
|
|
|
2519
2750
|
}
|
|
2520
2751
|
root;
|
|
2521
2752
|
dir(harness) {
|
|
2522
|
-
return
|
|
2753
|
+
return path11.join(this.root, "outbox", safeId(harness));
|
|
2523
2754
|
}
|
|
2524
2755
|
file(ref) {
|
|
2525
|
-
return
|
|
2756
|
+
return path11.join(this.dir(ref.harness), `${safeId(ref.session)}.jsonl`);
|
|
2526
2757
|
}
|
|
2527
2758
|
seqFile(ref) {
|
|
2528
|
-
return
|
|
2759
|
+
return path11.join(this.dir(ref.harness), `${safeId(ref.session)}.seq`);
|
|
2529
2760
|
}
|
|
2530
2761
|
/** The next seq for a session, persisted (survives a hibernating machine and a
|
|
2531
2762
|
* restart; an in-memory counter would restart at 0 and collide). */
|
|
@@ -2533,19 +2764,19 @@ var Outbox = class {
|
|
|
2533
2764
|
const f = this.seqFile(ref);
|
|
2534
2765
|
let n = -1;
|
|
2535
2766
|
try {
|
|
2536
|
-
const t =
|
|
2767
|
+
const t = readFileSync8(f, "utf8").trim();
|
|
2537
2768
|
if (/^\d+$/.test(t)) n = Number(t);
|
|
2538
2769
|
} catch {
|
|
2539
2770
|
}
|
|
2540
2771
|
if (n < 0) n = this.lastSeqInFile(ref) + 1;
|
|
2541
|
-
mkdirSync5(
|
|
2542
|
-
|
|
2772
|
+
mkdirSync5(path11.dirname(f), { recursive: true, mode: 448 });
|
|
2773
|
+
writeFileSync6(f, String(n + 1), { mode: 384 });
|
|
2543
2774
|
return n;
|
|
2544
2775
|
}
|
|
2545
2776
|
lastSeqInFile(ref) {
|
|
2546
2777
|
const f = this.file(ref);
|
|
2547
2778
|
if (!existsSync9(f)) return -1;
|
|
2548
|
-
const lines =
|
|
2779
|
+
const lines = readFileSync8(f, "utf8").trim().split("\n");
|
|
2549
2780
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
2550
2781
|
try {
|
|
2551
2782
|
const j = JSON.parse(lines[i]);
|
|
@@ -2564,10 +2795,10 @@ var Outbox = class {
|
|
|
2564
2795
|
/** Every session file, newest first. */
|
|
2565
2796
|
sessions() {
|
|
2566
2797
|
const out = [];
|
|
2567
|
-
const base =
|
|
2798
|
+
const base = path11.join(this.root, "outbox");
|
|
2568
2799
|
if (!existsSync9(base)) return [];
|
|
2569
2800
|
for (const h of readdirSync5(base)) {
|
|
2570
|
-
const d =
|
|
2801
|
+
const d = path11.join(base, h);
|
|
2571
2802
|
let names = [];
|
|
2572
2803
|
try {
|
|
2573
2804
|
names = readdirSync5(d).filter((n) => n.endsWith(".jsonl"));
|
|
@@ -2576,7 +2807,7 @@ var Outbox = class {
|
|
|
2576
2807
|
}
|
|
2577
2808
|
for (const n of names) {
|
|
2578
2809
|
try {
|
|
2579
|
-
out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime:
|
|
2810
|
+
out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime: statSync5(path11.join(d, n)).mtimeMs });
|
|
2580
2811
|
} catch {
|
|
2581
2812
|
}
|
|
2582
2813
|
}
|
|
@@ -2592,7 +2823,7 @@ var Outbox = class {
|
|
|
2592
2823
|
read(ref, from, maxEvents = 500) {
|
|
2593
2824
|
const f = this.file(ref);
|
|
2594
2825
|
if (!existsSync9(f)) return { events: [], offset: from };
|
|
2595
|
-
const size =
|
|
2826
|
+
const size = statSync5(f).size;
|
|
2596
2827
|
if (from >= size) return { events: [], offset: from };
|
|
2597
2828
|
const fd = openSync2(f, "r");
|
|
2598
2829
|
try {
|
|
@@ -2634,7 +2865,7 @@ var Outbox = class {
|
|
|
2634
2865
|
for (const ref of this.sessions()) {
|
|
2635
2866
|
const f = this.file(ref);
|
|
2636
2867
|
try {
|
|
2637
|
-
if (
|
|
2868
|
+
if (statSync5(f).mtimeMs < cutoff) {
|
|
2638
2869
|
unlinkSync3(f);
|
|
2639
2870
|
try {
|
|
2640
2871
|
unlinkSync3(this.seqFile(ref));
|
|
@@ -2664,20 +2895,20 @@ var Cursors = class {
|
|
|
2664
2895
|
root;
|
|
2665
2896
|
sink;
|
|
2666
2897
|
file() {
|
|
2667
|
-
return
|
|
2898
|
+
return path11.join(this.root, "cursors", `${safeId(this.sink)}.json`);
|
|
2668
2899
|
}
|
|
2669
2900
|
read() {
|
|
2670
2901
|
try {
|
|
2671
|
-
return JSON.parse(
|
|
2902
|
+
return JSON.parse(readFileSync8(this.file(), "utf8"));
|
|
2672
2903
|
} catch {
|
|
2673
2904
|
return {};
|
|
2674
2905
|
}
|
|
2675
2906
|
}
|
|
2676
2907
|
write(c) {
|
|
2677
2908
|
const f = this.file();
|
|
2678
|
-
mkdirSync5(
|
|
2909
|
+
mkdirSync5(path11.dirname(f), { recursive: true, mode: 448 });
|
|
2679
2910
|
const tmp = `${f}.${process.pid}.tmp`;
|
|
2680
|
-
|
|
2911
|
+
writeFileSync6(tmp, JSON.stringify(c), { mode: 384 });
|
|
2681
2912
|
renameSync(tmp, f);
|
|
2682
2913
|
}
|
|
2683
2914
|
key(ref) {
|
|
@@ -2688,16 +2919,16 @@ function takeLock(root, ref, staleMs = 5 * 6e4) {
|
|
|
2688
2919
|
return takeNamedLock(root, `${safeId(ref.harness)}-${safeId(ref.session)}`, staleMs);
|
|
2689
2920
|
}
|
|
2690
2921
|
function takeNamedLock(root, name, staleMs = 5 * 6e4) {
|
|
2691
|
-
const dir =
|
|
2922
|
+
const dir = path11.join(root, "locks");
|
|
2692
2923
|
mkdirSync5(dir, { recursive: true, mode: 448 });
|
|
2693
|
-
const f =
|
|
2924
|
+
const f = path11.join(dir, `${name}.lock`);
|
|
2694
2925
|
try {
|
|
2695
|
-
if (Date.now() -
|
|
2926
|
+
if (Date.now() - statSync5(f).mtimeMs > staleMs || !processAlive(Number(readFileSync8(f, "utf8")))) unlinkSync3(f);
|
|
2696
2927
|
else return null;
|
|
2697
2928
|
} catch {
|
|
2698
2929
|
}
|
|
2699
2930
|
try {
|
|
2700
|
-
|
|
2931
|
+
writeFileSync6(f, String(process.pid), { flag: "wx" });
|
|
2701
2932
|
} catch {
|
|
2702
2933
|
return null;
|
|
2703
2934
|
}
|
|
@@ -2929,6 +3160,7 @@ var TYPE_TIER = {
|
|
|
2929
3160
|
"subagent.started": 1,
|
|
2930
3161
|
"subagent.ended": 1,
|
|
2931
3162
|
"context.compacted": 1,
|
|
3163
|
+
"limits.observed": 1,
|
|
2932
3164
|
native: 3
|
|
2933
3165
|
};
|
|
2934
3166
|
var DATA_ALLOW = {
|
|
@@ -2948,7 +3180,8 @@ var DATA_ALLOW = {
|
|
|
2948
3180
|
"attention.cleared": { 0: ["attention_id", "how"] },
|
|
2949
3181
|
"subagent.started": { 1: ["agent_id", "agent_type"] },
|
|
2950
3182
|
"subagent.ended": { 1: ["agent_id", "agent_type"] },
|
|
2951
|
-
"context.compacted": { 1: ["trigger"] }
|
|
3183
|
+
"context.compacted": { 1: ["trigger"] },
|
|
3184
|
+
"limits.observed": { 1: ["windows", "plan", "account_id"] }
|
|
2952
3185
|
};
|
|
2953
3186
|
var SESSION_ALLOW = [
|
|
2954
3187
|
"id",
|
|
@@ -3013,7 +3246,9 @@ function filterEvent(event, tier, pii, opts = {}) {
|
|
|
3013
3246
|
const s = reducePiiDeep(out.session, opts.home);
|
|
3014
3247
|
if (typeof s.machine === "string") s.machine = hashMachine(s.machine);
|
|
3015
3248
|
delete s.account_id;
|
|
3016
|
-
|
|
3249
|
+
const d = reducePiiDeep(out.data, opts.home);
|
|
3250
|
+
delete d.account_id;
|
|
3251
|
+
out = { ...out, session: s, data: d };
|
|
3017
3252
|
rulesets.push(RULESET_PII);
|
|
3018
3253
|
}
|
|
3019
3254
|
out.privacy = { rulesets, pii };
|
|
@@ -3028,7 +3263,7 @@ function shortId(harness, id) {
|
|
|
3028
3263
|
|
|
3029
3264
|
// ../core/src/sinks/file.ts
|
|
3030
3265
|
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync6 } from "node:fs";
|
|
3031
|
-
import
|
|
3266
|
+
import path12 from "node:path";
|
|
3032
3267
|
var FileSink = class {
|
|
3033
3268
|
constructor(name, file, tier, pii) {
|
|
3034
3269
|
this.name = name;
|
|
@@ -3042,7 +3277,7 @@ var FileSink = class {
|
|
|
3042
3277
|
pii;
|
|
3043
3278
|
async send(events) {
|
|
3044
3279
|
try {
|
|
3045
|
-
mkdirSync6(
|
|
3280
|
+
mkdirSync6(path12.dirname(this.file), { recursive: true });
|
|
3046
3281
|
appendFileSync2(this.file, `${events.map((e) => JSON.stringify(e)).join("\n")}
|
|
3047
3282
|
`);
|
|
3048
3283
|
return { ok: true, acknowledged: events.length, accepted: events.length, duplicates: 0, rejected: [] };
|
|
@@ -3081,18 +3316,18 @@ import {
|
|
|
3081
3316
|
existsSync as existsSync10,
|
|
3082
3317
|
mkdirSync as mkdirSync8,
|
|
3083
3318
|
readdirSync as readdirSync6,
|
|
3084
|
-
readFileSync as
|
|
3319
|
+
readFileSync as readFileSync10,
|
|
3085
3320
|
renameSync as renameSync3,
|
|
3086
|
-
statSync as
|
|
3321
|
+
statSync as statSync6,
|
|
3087
3322
|
unlinkSync as unlinkSync4,
|
|
3088
|
-
writeFileSync as
|
|
3323
|
+
writeFileSync as writeFileSync8
|
|
3089
3324
|
} from "node:fs";
|
|
3090
3325
|
import os3 from "node:os";
|
|
3091
|
-
import
|
|
3326
|
+
import path15 from "node:path";
|
|
3092
3327
|
|
|
3093
3328
|
// src/control/aliases.ts
|
|
3094
|
-
import { chmodSync as chmodSync2, mkdirSync as mkdirSync7, readFileSync as
|
|
3095
|
-
import
|
|
3329
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync7, readFileSync as readFileSync9, renameSync as renameSync2, writeFileSync as writeFileSync7 } from "node:fs";
|
|
3330
|
+
import path13 from "node:path";
|
|
3096
3331
|
|
|
3097
3332
|
// src/secrets.ts
|
|
3098
3333
|
import { spawnSync } from "node:child_process";
|
|
@@ -3136,10 +3371,10 @@ function startOptionsOf(v) {
|
|
|
3136
3371
|
}
|
|
3137
3372
|
|
|
3138
3373
|
// src/control/aliases.ts
|
|
3139
|
-
var aliasFile = (controlDir) =>
|
|
3374
|
+
var aliasFile = (controlDir) => path13.join(controlDir, "aliases.json");
|
|
3140
3375
|
function readAliases(file) {
|
|
3141
3376
|
try {
|
|
3142
|
-
const j = JSON.parse(
|
|
3377
|
+
const j = JSON.parse(readFileSync9(file, "utf8"));
|
|
3143
3378
|
if (!j || typeof j !== "object" || Array.isArray(j)) return {};
|
|
3144
3379
|
const out = {};
|
|
3145
3380
|
for (const [k, v] of Object.entries(j)) {
|
|
@@ -3161,11 +3396,11 @@ function receiverIdOf(all, harness, id) {
|
|
|
3161
3396
|
// src/control/local.ts
|
|
3162
3397
|
import net from "node:net";
|
|
3163
3398
|
import os2 from "node:os";
|
|
3164
|
-
import
|
|
3399
|
+
import path14 from "node:path";
|
|
3165
3400
|
function socketPath(state, env = process.env) {
|
|
3166
3401
|
if (env.SESSIONPIPE_SOCKET) return env.SESSIONPIPE_SOCKET;
|
|
3167
3402
|
if (process.platform === "win32") return `\\\\.\\pipe\\sessionpipe-control-${os2.userInfo().username}`;
|
|
3168
|
-
return
|
|
3403
|
+
return path14.join(state, "control.sock");
|
|
3169
3404
|
}
|
|
3170
3405
|
function ask(sock, req, timeoutMs) {
|
|
3171
3406
|
return new Promise((resolve) => {
|
|
@@ -3312,8 +3547,8 @@ var MAX_TRIES = 10;
|
|
|
3312
3547
|
function enqueueJob(state, job) {
|
|
3313
3548
|
const jobs = jobsDir(state);
|
|
3314
3549
|
mkdirSync8(jobs, { recursive: true, mode: 448 });
|
|
3315
|
-
const f =
|
|
3316
|
-
|
|
3550
|
+
const f = path15.join(jobs, `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.json`);
|
|
3551
|
+
writeFileSync8(f, JSON.stringify(job), { mode: 384 });
|
|
3317
3552
|
return f;
|
|
3318
3553
|
}
|
|
3319
3554
|
function claimJob(file) {
|
|
@@ -3324,7 +3559,7 @@ function claimJob(file) {
|
|
|
3324
3559
|
return null;
|
|
3325
3560
|
}
|
|
3326
3561
|
try {
|
|
3327
|
-
return { job: JSON.parse(
|
|
3562
|
+
return { job: JSON.parse(readFileSync10(claimed, "utf8")), file: claimed };
|
|
3328
3563
|
} catch {
|
|
3329
3564
|
releaseJob(claimed);
|
|
3330
3565
|
return null;
|
|
@@ -3349,19 +3584,19 @@ async function sweepJobs(state, olderThanMs = 3e3, limit = 20, abandonedMs = 6e4
|
|
|
3349
3584
|
for (const n of names) {
|
|
3350
3585
|
const m = /^(.*\.json)\.(\d+)\.running$/.exec(n);
|
|
3351
3586
|
if (!m) continue;
|
|
3352
|
-
const f =
|
|
3587
|
+
const f = path15.join(jobs, n);
|
|
3353
3588
|
try {
|
|
3354
|
-
if (processAlive(Number(m[2])) && Date.now() -
|
|
3355
|
-
renameSync3(f,
|
|
3589
|
+
if (processAlive(Number(m[2])) && Date.now() - statSync6(f).mtimeMs < abandonedMs) continue;
|
|
3590
|
+
renameSync3(f, path15.join(jobs, m[1]));
|
|
3356
3591
|
queued.push(m[1]);
|
|
3357
3592
|
} catch {
|
|
3358
3593
|
}
|
|
3359
3594
|
}
|
|
3360
3595
|
let ran = 0;
|
|
3361
3596
|
for (const n of queued.sort().slice(0, limit)) {
|
|
3362
|
-
const f =
|
|
3597
|
+
const f = path15.join(jobs, n);
|
|
3363
3598
|
try {
|
|
3364
|
-
if (Date.now() -
|
|
3599
|
+
if (Date.now() - statSync6(f).mtimeMs < olderThanMs) continue;
|
|
3365
3600
|
} catch {
|
|
3366
3601
|
continue;
|
|
3367
3602
|
}
|
|
@@ -3380,9 +3615,9 @@ async function sweepJobs(state, olderThanMs = 3e3, limit = 20, abandonedMs = 6e4
|
|
|
3380
3615
|
function log(state, line) {
|
|
3381
3616
|
try {
|
|
3382
3617
|
mkdirSync8(state, { recursive: true, mode: 448 });
|
|
3383
|
-
const f =
|
|
3618
|
+
const f = path15.join(state, "log");
|
|
3384
3619
|
try {
|
|
3385
|
-
if (
|
|
3620
|
+
if (statSync6(f).size > 1024 * 1024) renameSync3(f, `${f}.1`);
|
|
3386
3621
|
} catch {
|
|
3387
3622
|
}
|
|
3388
3623
|
appendFileSync3(f, `${(/* @__PURE__ */ new Date()).toISOString()} ${line}
|
|
@@ -3391,11 +3626,11 @@ function log(state, line) {
|
|
|
3391
3626
|
}
|
|
3392
3627
|
}
|
|
3393
3628
|
function factsState(state) {
|
|
3394
|
-
const file = (h, s) =>
|
|
3629
|
+
const file = (h, s) => path15.join(state, "facts", safeId(h), `${safeId(s)}.json`);
|
|
3395
3630
|
return {
|
|
3396
3631
|
get(h, s) {
|
|
3397
3632
|
try {
|
|
3398
|
-
return JSON.parse(
|
|
3633
|
+
return JSON.parse(readFileSync10(file(h, s), "utf8"));
|
|
3399
3634
|
} catch {
|
|
3400
3635
|
return {};
|
|
3401
3636
|
}
|
|
@@ -3404,8 +3639,8 @@ function factsState(state) {
|
|
|
3404
3639
|
const cur = this.get(h, s);
|
|
3405
3640
|
const next = { ...cur, ...patch };
|
|
3406
3641
|
if (JSON.stringify(next) === JSON.stringify(cur)) return;
|
|
3407
|
-
mkdirSync8(
|
|
3408
|
-
|
|
3642
|
+
mkdirSync8(path15.dirname(file(h, s)), { recursive: true, mode: 448 });
|
|
3643
|
+
writeFileSync8(file(h, s), JSON.stringify(next), { mode: 384 });
|
|
3409
3644
|
}
|
|
3410
3645
|
};
|
|
3411
3646
|
}
|
|
@@ -3477,7 +3712,7 @@ async function runJob(job, opts = {}) {
|
|
|
3477
3712
|
);
|
|
3478
3713
|
return { events: [], delivered: {} };
|
|
3479
3714
|
}
|
|
3480
|
-
const id = receiverIdOf(readAliases(aliasFile(
|
|
3715
|
+
const id = receiverIdOf(readAliases(aliasFile(path15.join(state, "control"))), job.harness, r.session.id) ?? r.session.id;
|
|
3481
3716
|
const claudeEnd = job.harness === "claude-code" && r.events.some((e) => e.type === "session.ended");
|
|
3482
3717
|
if (!claudeEnd) await tellDaemon(state, job, id, r.session.cwd, r.transcript);
|
|
3483
3718
|
const outbox = new Outbox(state);
|
|
@@ -3506,12 +3741,15 @@ async function runJob(job, opts = {}) {
|
|
|
3506
3741
|
const heavy = !/^(PreToolUse|PostToolUse|PostToolUseFailure|BeforeTool|AfterTool|PostInvocation)$/.test(job.event);
|
|
3507
3742
|
let learned = {};
|
|
3508
3743
|
if (heavy) {
|
|
3509
|
-
const
|
|
3744
|
+
const hookEnv = { ...process.env, ...job.env };
|
|
3745
|
+
const f = adapter.facts(r.session, r.transcript, r.hints ?? {}, facts, hookEnv);
|
|
3746
|
+
if (cfg.remote_titles && adapter.remoteFacts)
|
|
3747
|
+
Object.assign(f, await adapter.remoteFacts(r.session, f, r.transcript, facts, hookEnv));
|
|
3510
3748
|
const { started_at: _s, last_at: _l, first_ask: _fa, harness_version, ...rest } = f;
|
|
3511
3749
|
learned = rest;
|
|
3512
3750
|
if (harness_version) facts.save(job.harness, id, { harness_version });
|
|
3513
3751
|
facts.save(job.harness, id, { session: { ...mem.session, ...learned } });
|
|
3514
|
-
if (CWD_FROM_HOOKS.has(job.harness) && r.session.cwd &&
|
|
3752
|
+
if (CWD_FROM_HOOKS.has(job.harness) && r.session.cwd && path15.isAbsolute(r.session.cwd))
|
|
3515
3753
|
facts.save(job.harness, id, { cwd: r.session.cwd });
|
|
3516
3754
|
}
|
|
3517
3755
|
const known = mem.session ?? {};
|
|
@@ -3593,6 +3831,23 @@ async function runJob(job, opts = {}) {
|
|
|
3593
3831
|
}
|
|
3594
3832
|
if (read.turns.length) facts.save(job.harness, id, { transcript_line: read.line, transcript_turns: turnNo });
|
|
3595
3833
|
}
|
|
3834
|
+
if (adapter.limits && r.transcript && ending && cfg.sinks.some((s) => !s.paused && Math.min(s.tier, s.max_tier ?? 3) >= 1)) {
|
|
3835
|
+
const limits = adapter.limits(r.transcript);
|
|
3836
|
+
const sig = limits && JSON.stringify(limits);
|
|
3837
|
+
if (limits && sig !== facts.get(job.harness, id).limits_sig) {
|
|
3838
|
+
events.push(
|
|
3839
|
+
makeEvent(
|
|
3840
|
+
{ type: "limits.observed", harnessEvent: job.event, data: limits },
|
|
3841
|
+
{
|
|
3842
|
+
harness: { name: job.harness, ...version ? { version } : {} },
|
|
3843
|
+
session: { ...session, seq: outbox.nextSeq(ref) },
|
|
3844
|
+
now: opts.now ?? job.at
|
|
3845
|
+
}
|
|
3846
|
+
)
|
|
3847
|
+
);
|
|
3848
|
+
facts.save(job.harness, id, { limits_sig: sig });
|
|
3849
|
+
}
|
|
3850
|
+
}
|
|
3596
3851
|
outbox.append(
|
|
3597
3852
|
ref,
|
|
3598
3853
|
events.map((e) => ({ ...e, session: redactDeep(e.session), data: redactDeep(e.data) }))
|
|
@@ -3610,11 +3865,11 @@ async function runJob(job, opts = {}) {
|
|
|
3610
3865
|
return { events, delivered };
|
|
3611
3866
|
}
|
|
3612
3867
|
async function drain(state, sinkConfigs, outbox, first) {
|
|
3613
|
-
const more =
|
|
3868
|
+
const more = path15.join(state, "locks", "flush.more");
|
|
3614
3869
|
let release = takeNamedLock(state, "flush");
|
|
3615
3870
|
if (!release) {
|
|
3616
3871
|
try {
|
|
3617
|
-
|
|
3872
|
+
writeFileSync8(more, "", { mode: 384 });
|
|
3618
3873
|
} catch {
|
|
3619
3874
|
}
|
|
3620
3875
|
release = takeNamedLock(state, "flush");
|
|
@@ -3638,10 +3893,10 @@ async function drain(state, sinkConfigs, outbox, first) {
|
|
|
3638
3893
|
}
|
|
3639
3894
|
}
|
|
3640
3895
|
var BACKOFF_MS = [2e3, 1e4, 6e4, 5 * 6e4, 30 * 6e4];
|
|
3641
|
-
var backoffFile = (state, sink) =>
|
|
3896
|
+
var backoffFile = (state, sink) => path15.join(state, "backoff", `${safeId(sink)}.json`);
|
|
3642
3897
|
function readBackoff(state, sink) {
|
|
3643
3898
|
try {
|
|
3644
|
-
const b = JSON.parse(
|
|
3899
|
+
const b = JSON.parse(readFileSync10(backoffFile(state, sink), "utf8"));
|
|
3645
3900
|
return typeof b.until === "number" ? b : null;
|
|
3646
3901
|
} catch {
|
|
3647
3902
|
return null;
|
|
@@ -3654,8 +3909,8 @@ function noteFailure(state, sink, res, prev) {
|
|
|
3654
3909
|
const b = { until: Date.now() + wait, failures, why: `${res.status ?? ""} ${res.error ?? ""}`.trim() };
|
|
3655
3910
|
const f = backoffFile(state, sink);
|
|
3656
3911
|
try {
|
|
3657
|
-
mkdirSync8(
|
|
3658
|
-
|
|
3912
|
+
mkdirSync8(path15.dirname(f), { recursive: true, mode: 448 });
|
|
3913
|
+
writeFileSync8(f, JSON.stringify(b), { mode: 384 });
|
|
3659
3914
|
} catch {
|
|
3660
3915
|
}
|
|
3661
3916
|
return b;
|
|
@@ -3787,7 +4042,7 @@ function applySinkVerdict(name, res) {
|
|
|
3787
4042
|
} catch {
|
|
3788
4043
|
}
|
|
3789
4044
|
}
|
|
3790
|
-
var jobsDir = (state) =>
|
|
4045
|
+
var jobsDir = (state) => path15.join(state, "jobs");
|
|
3791
4046
|
|
|
3792
4047
|
// src/worker.ts
|
|
3793
4048
|
async function workerMain(argv) {
|