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