sessionpipe-core 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/control.js +180 -13
  2. package/dist/control.js.map +2 -2
  3. package/dist/index.js +2133 -965
  4. package/dist/index.js.map +4 -4
  5. package/dist/schema/v1.d.ts +443 -34
  6. package/dist/schema/v1.d.ts.map +1 -1
  7. package/dist/schema.js +201 -34
  8. package/dist/schema.js.map +2 -2
  9. package/dist/service.js +155 -0
  10. package/dist/service.js.map +7 -0
  11. package/dist/src/adapters/acp-control.d.ts +87 -0
  12. package/dist/src/adapters/acp-control.d.ts.map +1 -0
  13. package/dist/src/adapters/antigravity-control.d.ts +15 -5
  14. package/dist/src/adapters/antigravity-control.d.ts.map +1 -1
  15. package/dist/src/adapters/claude-code-control.d.ts +39 -4
  16. package/dist/src/adapters/claude-code-control.d.ts.map +1 -1
  17. package/dist/src/adapters/claude-code-pool.d.ts +116 -0
  18. package/dist/src/adapters/claude-code-pool.d.ts.map +1 -0
  19. package/dist/src/adapters/claude-code.d.ts +20 -1
  20. package/dist/src/adapters/claude-code.d.ts.map +1 -1
  21. package/dist/src/adapters/claude-shaped.d.ts +3 -0
  22. package/dist/src/adapters/claude-shaped.d.ts.map +1 -1
  23. package/dist/src/adapters/codex-config.d.ts +2 -1
  24. package/dist/src/adapters/codex-config.d.ts.map +1 -1
  25. package/dist/src/adapters/codex-control.d.ts +23 -7
  26. package/dist/src/adapters/codex-control.d.ts.map +1 -1
  27. package/dist/src/adapters/codex.d.ts +9 -0
  28. package/dist/src/adapters/codex.d.ts.map +1 -1
  29. package/dist/src/adapters/gemini-cli.d.ts +3 -0
  30. package/dist/src/adapters/gemini-cli.d.ts.map +1 -1
  31. package/dist/src/adapters/headless.d.ts +4 -1
  32. package/dist/src/adapters/headless.d.ts.map +1 -1
  33. package/dist/src/adapters/index.d.ts +7 -1
  34. package/dist/src/adapters/index.d.ts.map +1 -1
  35. package/dist/src/adapters/types.d.ts +18 -1
  36. package/dist/src/adapters/types.d.ts.map +1 -1
  37. package/dist/src/config.d.ts +6 -0
  38. package/dist/src/config.d.ts.map +1 -1
  39. package/dist/src/control/verify.d.ts +99 -17
  40. package/dist/src/control/verify.d.ts.map +1 -1
  41. package/dist/src/index.d.ts +3 -1
  42. package/dist/src/index.d.ts.map +1 -1
  43. package/dist/src/launched-by.d.ts +12 -0
  44. package/dist/src/launched-by.d.ts.map +1 -0
  45. package/dist/src/paths.d.ts +4 -0
  46. package/dist/src/paths.d.ts.map +1 -1
  47. package/dist/src/privacy/filter.d.ts.map +1 -1
  48. package/dist/src/service/index.d.ts +83 -0
  49. package/dist/src/service/index.d.ts.map +1 -0
  50. package/dist/src/types.d.ts +4 -0
  51. package/dist/src/types.d.ts.map +1 -1
  52. package/dist/src/version.d.ts +39 -0
  53. package/dist/src/version.d.ts.map +1 -0
  54. package/dist/version.js +75 -0
  55. package/dist/version.js.map +7 -0
  56. package/package.json +10 -2
package/dist/index.js CHANGED
@@ -1,22 +1,33 @@
1
- // sessionpipe-core 0.9.0 · Apache-2.0 · https://sessionpipe.org
1
+ // sessionpipe-core 0.11.0 · Apache-2.0 · https://sessionpipe.org
2
2
  var __defProp = Object.defineProperty;
3
3
  var __export = (target, all) => {
4
4
  for (var name in all)
5
5
  __defProp(target, name, { get: all[name], enumerable: true });
6
6
  };
7
7
 
8
- // src/adapters/antigravity.ts
9
- import {
10
- existsSync as existsSync2,
11
- mkdirSync as mkdirSync2,
12
- readdirSync as readdirSync2,
13
- readFileSync as readFileSync2,
14
- realpathSync,
15
- statSync as statSync2,
16
- unlinkSync as unlinkSync2,
17
- writeFileSync as writeFileSync2
18
- } from "node:fs";
19
- import path4 from "node:path";
8
+ // src/adapters/claude-code-pool.ts
9
+ var claude_code_pool_exports = {};
10
+ __export(claude_code_pool_exports, {
11
+ POOL_ENDPOINTS: () => POOL_ENDPOINTS,
12
+ PoolAuthError: () => PoolAuthError,
13
+ PoolUpstreamError: () => PoolUpstreamError,
14
+ beginLogin: () => beginLogin,
15
+ completeLogin: () => completeLogin,
16
+ fetchProfile: () => fetchProfile,
17
+ fetchUsage: () => fetchUsage,
18
+ importableLogins: () => importableLogins,
19
+ keychainService: () => keychainService,
20
+ parseUserId: () => parseUserId,
21
+ readLimits: () => readLimits,
22
+ refreshCredential: () => refreshCredential,
23
+ upstreamHeaders: () => upstreamHeaders,
24
+ usageLimits: () => usageLimits,
25
+ withAccount: () => withAccount
26
+ });
27
+ import { createHash as createHash2, randomBytes } from "node:crypto";
28
+ import { readFileSync as readFileSync3 } from "node:fs";
29
+ import os2 from "node:os";
30
+ import path5 from "node:path";
20
31
 
21
32
  // src/paths.ts
22
33
  import os from "node:os";
@@ -39,8 +50,19 @@ function tilde(p, home = homeDir()) {
39
50
  if (p === home) return "~";
40
51
  return p.startsWith(home + path.sep) || p.startsWith(`${home}/`) ? `~${p.slice(home.length)}` : p;
41
52
  }
53
+ function untilde(p, home = homeDir()) {
54
+ if (p === "~") return home;
55
+ return /^~[\\/]/.test(p) ? path.join(home, p.slice(2)) : p;
56
+ }
42
57
  var safeId = (s) => String(s).replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 160);
43
58
 
59
+ // src/adapters/claude-code.ts
60
+ import { execFileSync as execFileSync2 } from "node:child_process";
61
+ import { createHash } from "node:crypto";
62
+ import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync2, writeFileSync as writeFileSync2 } from "node:fs";
63
+ import { userInfo } from "node:os";
64
+ import path4 from "node:path";
65
+
44
66
  // src/readers/files.ts
45
67
  import { closeSync, fstatSync, openSync, readdirSync, readSync, statSync } from "node:fs";
46
68
  import path2 from "node:path";
@@ -131,6 +153,38 @@ function recentFiles(root, depth, sinceMs, match) {
131
153
  return out;
132
154
  }
133
155
 
156
+ // src/readers/git.ts
157
+ import { execFileSync } from "node:child_process";
158
+ var cache = /* @__PURE__ */ new Map();
159
+ function gitFacts(cwd) {
160
+ if (!cwd) return {};
161
+ const hit = cache.get(cwd);
162
+ if (hit) return hit;
163
+ const run = (args) => {
164
+ try {
165
+ return execFileSync("git", ["-C", cwd, ...args], {
166
+ encoding: "utf8",
167
+ timeout: 1500,
168
+ stdio: ["ignore", "pipe", "ignore"]
169
+ }).trim();
170
+ } catch {
171
+ return "";
172
+ }
173
+ };
174
+ const out = {};
175
+ const repo = run(["remote", "get-url", "origin"]);
176
+ if (repo) out.repo = stripCredentials(repo);
177
+ const branch = run(["rev-parse", "--abbrev-ref", "HEAD"]);
178
+ if (branch && branch !== "HEAD") out.branch = branch;
179
+ cache.set(cwd, out);
180
+ return out;
181
+ }
182
+ function stripCredentials(url) {
183
+ const ssh = /^(?:ssh:\/\/)?git@([^:/]+)[:/](.+?)(?:\.git)?$/.exec(url);
184
+ if (ssh) return `${ssh[1]}/${ssh[2]}`;
185
+ return url.replace(/\/\/[^/@]*@/, "//").replace(/^https?:\/\//, "").replace(/\.git$/, "");
186
+ }
187
+
134
188
  // src/adapters/codex-rollout.ts
135
189
  function codexSubagent(meta) {
136
190
  if (meta?.type !== "session_meta") return false;
@@ -290,8 +344,10 @@ function readJsonFile(file) {
290
344
  try {
291
345
  raw = readFileSync(file, "utf8");
292
346
  } catch {
293
- return { ok: true, existed: false, value: {}, raw: "", indent: " ", eol: "\n", finalNewline: true };
347
+ return { ok: true, existed: false, value: {}, raw: "", indent: " ", eol: "\n", finalNewline: true, bom: false };
294
348
  }
349
+ const bom = raw.charCodeAt(0) === 65279;
350
+ if (bom) raw = raw.slice(1);
295
351
  let value;
296
352
  try {
297
353
  value = JSON.parse(raw);
@@ -311,12 +367,13 @@ function readJsonFile(file) {
311
367
  raw,
312
368
  indent,
313
369
  eol,
314
- finalNewline: /\r?\n$/.test(raw)
370
+ finalNewline: /\r?\n$/.test(raw),
371
+ bom
315
372
  };
316
373
  }
317
374
  function formatJson(value, f) {
318
375
  const text = JSON.stringify(value, null, f.indent).replace(/\n/g, f.eol);
319
- return f.finalNewline ? text + f.eol : text;
376
+ return (f.bom ? "\uFEFF" : "") + (f.finalNewline ? text + f.eol : text);
320
377
  }
321
378
  function readJson(file) {
322
379
  const f = readJsonFile(file);
@@ -412,74 +469,79 @@ function installedClaudeShaped(file, harness, events, cmd, o) {
412
469
  return current === events.length ? "current" : "stale";
413
470
  }
414
471
 
415
- // src/adapters/antigravity.ts
416
- var NAME = "antigravity";
417
- var HOOK_NAME = "sessionpipe";
418
- var AG_EVENTS = ["PreInvocation", "PostInvocation", "PostToolUse", "Stop"];
419
- 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';
420
- var antigravityAnswer = (event) => event === "PreToolUse" ? AG_STALE_PRETOOL : "{}\n";
421
- var gemini = (env) => env.GEMINI_CLI_HOME || path4.join(homeDir(), ".gemini");
422
- var hooksFile = (env) => path4.join(gemini(env), "config", "hooks.json");
423
- var projectsDir = (env) => path4.join(gemini(env), "config", "projects");
424
- var dataDirs = (env) => ["antigravity", "antigravity-cli", "antigravity-ide"].map((d) => path4.join(gemini(env), d));
425
- var ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
426
- var validTool = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
427
- function entry(cmd) {
428
- const handler = (e) => ({ type: "command", command: cmd([NAME, e]).command, timeout: 5 });
429
- const out = { enabled: true };
430
- for (const e of AG_EVENTS) out[e] = /Tool/.test(e) ? [{ matcher: "*", hooks: [handler(e)] }] : [handler(e)];
431
- return out;
472
+ // src/adapters/claude-code.ts
473
+ var NAME = "claude-code";
474
+ var CLAUDE_EVENTS = [
475
+ "SessionStart",
476
+ "UserPromptSubmit",
477
+ "PreToolUse",
478
+ "PostToolUse",
479
+ "PostToolUseFailure",
480
+ "PermissionRequest",
481
+ "Notification",
482
+ "Stop",
483
+ "SubagentStart",
484
+ "SubagentStop",
485
+ "PreCompact",
486
+ "PostCompact",
487
+ "SessionEnd",
488
+ "StopFailure"
489
+ ];
490
+ var LEAN_SKIP = /* @__PURE__ */ new Set(["PreToolUse", "PostToolUseFailure"]);
491
+ var eventsFor = (lean) => lean ? CLAUDE_EVENTS.filter((e) => !LEAN_SKIP.has(e)) : [...CLAUDE_EVENTS];
492
+ var OPTS = {
493
+ matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0,
494
+ timeout: 5,
495
+ // The hook waits up to 125 s for a verified control answer, and only when control
496
+ // is paired on this machine; everywhere else it exits in milliseconds as before.
497
+ timeoutFor: (e) => e === "PermissionRequest" ? 130 : void 0
498
+ };
499
+ var DESKTOP_ID = /^local_[A-Za-z0-9-]{1,64}$/;
500
+ function claudeDirs(env = process.env, home = env.HOME || homeDir()) {
501
+ const out = [];
502
+ const add = (d) => {
503
+ if (!d) return;
504
+ const r = path4.resolve(d);
505
+ if (!out.includes(r)) out.push(r);
506
+ };
507
+ add(path4.join(home, ".claude"));
508
+ add(env.CLAUDE_CONFIG_DIR);
509
+ try {
510
+ for (const e of readdirSync2(home, { withFileTypes: true })) {
511
+ if (!e.isDirectory() || !/^\.claude[-_.]/.test(e.name)) continue;
512
+ const d = path4.join(home, e.name);
513
+ if (existsSync2(path4.join(d, "projects")) || existsSync2(path4.join(d, "settings.json"))) add(d);
514
+ }
515
+ } catch {
516
+ }
517
+ return out.filter((d, i) => i === 0 || existsSync2(d));
432
518
  }
433
- var antigravity = {
519
+ var settingsIn = (dir) => path4.join(dir, "settings.json");
520
+ var configDirOf = (transcript) => path4.dirname(path4.dirname(path4.dirname(transcript)));
521
+ var validTool = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
522
+ var endReason = (r) => r === "clear" || r === "logout" || r === "exit" ? r : r === "prompt_input_exit" ? "exit" : "other";
523
+ var notifKind = (t) => t === "permission_prompt" ? "permission" : t === "idle_prompt" ? "idle" : t === "elicitation_dialog" ? "elicitation" : "question";
524
+ var claudeCode = {
434
525
  name: NAME,
435
- events: AG_EVENTS,
436
- detect: (env = process.env) => existsSync2(path4.join(gemini(env), "config")) || dataDirs(env).some((d) => existsSync2(d)),
437
- configFiles: (env = process.env) => [hooksFile(env)],
438
- install(cmd, env = process.env) {
439
- const file = hooksFile(env);
440
- const f = readJsonFile(file);
441
- if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
442
- const hooks = f.value;
443
- const e = entry(cmd);
444
- if (JSON.stringify(hooks[HOOK_NAME]) === JSON.stringify(e)) return [{ file, changed: false }];
445
- hooks[HOOK_NAME] = e;
446
- mkdirSync2(path4.dirname(file), { recursive: true });
447
- writeFileSync2(file, formatJson(hooks, f));
448
- return [
449
- {
450
- file,
451
- changed: true,
452
- created: !f.existed,
453
- note: "hooks.json is read when a conversation starts; open ones report after a restart"
454
- }
455
- ];
456
- },
457
- uninstall(env = process.env, opts = {}) {
458
- const file = hooksFile(env);
459
- if (!existsSync2(file)) return [{ file, changed: false }];
460
- const f = readJsonFile(file);
461
- if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
462
- const hooks = f.value;
463
- if (!(HOOK_NAME in hooks)) return [{ file, changed: false }];
464
- delete hooks[HOOK_NAME];
465
- if (opts.created?.includes(file) && !Object.keys(hooks).length) {
466
- unlinkSync2(file);
467
- return [{ file, changed: true, note: "removed (sessionpipe created it)" }];
468
- }
469
- writeFileSync2(file, formatJson(hooks, f));
470
- return [{ file, changed: true }];
471
- },
472
- installed(cmd, env = process.env) {
473
- const file = hooksFile(env);
474
- const f = readJsonFile(file);
475
- const have = f.ok ? f.value[HOOK_NAME] : void 0;
476
- return [
477
- {
478
- file,
479
- state: !have ? "missing" : JSON.stringify(have) === JSON.stringify(entry(cmd)) ? "current" : "stale"
480
- }
481
- ];
482
- },
526
+ events: CLAUDE_EVENTS,
527
+ detect: (env = process.env) => claudeDirs(env).some((d) => existsSync2(d)),
528
+ configFiles: (env = process.env) => claudeDirs(env).map(settingsIn),
529
+ install: (cmd, env = process.env, opts = {}) => claudeDirs(env).flatMap((d) => {
530
+ const hooks = installClaudeShaped(settingsIn(d), NAME, eventsFor(opts.lean), cmd, OPTS);
531
+ const line = opts.statusLine === void 0 ? null : statusLine(settingsIn(d), opts.statusLine, cmd);
532
+ return line ? [hooks, line] : [hooks];
533
+ }),
534
+ uninstall: (env = process.env, opts = {}) => claudeDirs(env).map((d) => {
535
+ const line = statusLine(settingsIn(d), false);
536
+ const hooks = uninstallClaudeShaped(settingsIn(d), CLAUDE_EVENTS, {
537
+ created: opts.created?.includes(settingsIn(d))
538
+ });
539
+ return line && !hooks.changed ? line : hooks;
540
+ }),
541
+ installed: (cmd, env = process.env, opts = {}) => claudeDirs(env).map((d) => ({
542
+ file: settingsIn(d),
543
+ state: installedClaudeShaped(settingsIn(d), NAME, eventsFor(opts.lean), cmd, OPTS)
544
+ })),
483
545
  fromHook(input) {
484
546
  const event = input.argv[1] ?? "";
485
547
  let s = {};
@@ -488,226 +550,1087 @@ var antigravity = {
488
550
  if (j && typeof j === "object" && !Array.isArray(j)) s = j;
489
551
  } catch {
490
552
  }
491
- const id = s.conversationId ?? s.session_id ?? s.sessionId;
492
- const stdout = antigravityAnswer(event);
493
- if (!id) return { session: { id: "" }, events: [], stdout };
494
- const workspaces = agWorkspaces(s.workspacePaths);
495
- const session = { id: String(id), cwd: workspaces[0] ?? input.cwd };
496
- if (typeof s.modelName === "string" && s.modelName !== "auto") session.model = s.modelName;
497
- const toolCall = s.toolCall;
498
- const tool = validTool(toolCall?.name) ? toolCall?.name : validTool(s.toolName) ? s.toolName : void 0;
553
+ const id = s.session_id ?? s.sessionId;
554
+ if (!id) return null;
555
+ if (notClaudeCode(String(id), s)) return null;
556
+ const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
557
+ if (typeof s.model === "string") session.model = s.model;
558
+ if (typeof s.agent_id === "string" && event.startsWith("Subagent") === false && s.agent_id)
559
+ session.parent_id = null;
560
+ const turn_id = typeof s.prompt_id === "string" ? s.prompt_id : void 0;
561
+ const tool = validTool(s.tool_name) ? s.tool_name : void 0;
562
+ const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
563
+ const ms = typeof s.duration_ms === "number" ? Math.round(s.duration_ms) : void 0;
564
+ const agent_id = typeof s.agent_id === "string" ? s.agent_id : void 0;
565
+ const permissionId = () => `perm-${createHash("sha256").update(`${turn_id ?? ""}|${tool ?? ""}|${JSON.stringify(s.tool_input ?? null)}`).digest("hex").slice(0, 16)}`;
499
566
  const ev = (type, data) => ({ type, data: prune(data), harnessEvent: event });
500
567
  const events = [];
501
568
  switch (event) {
502
- case "PreInvocation":
503
- if (Number(s.invocationNum) === 0 || s.invocationNum === void 0) {
504
- events.push(ev("session.started", { source: "unknown" }));
505
- events.push(ev("turn.started", {}));
506
- } else events.push(ev("session.heartbeat", {}));
569
+ case "SessionStart":
570
+ events.push(ev("session.started", { source: pickSource(s.source ?? s.trigger) }));
507
571
  break;
508
- case "PostInvocation":
572
+ case "UserPromptSubmit": {
573
+ const prompt = typeof s.prompt === "string" ? s.prompt : void 0;
574
+ events.push(
575
+ ev("turn.started", {
576
+ turn_id,
577
+ prompt_chars: prompt === void 0 ? void 0 : [...prompt].length,
578
+ // Claude Code's hook schema documents `source`
579
+ // (user | sdk | system | loop_wakeup | schedule_wakeup | poll_event) and says a
580
+ // payload may omit it while the field rolls out; 2.1.304's payload has no such
581
+ // key at all. Forwarded verbatim, shape-guarded only: the list is the harness's
582
+ // to extend.
583
+ submitted_by: harnessWord(s.source),
584
+ // One of Claude Code's own prompt wrappers: the prompt is a block the harness
585
+ // wrote, so it is not the person's words. Absent is unknown, never "the person" —
586
+ // and `true` is never "no person caused this turn", because a Talk or
587
+ // `sessionpipe wait` delivery hands the person's own message over inside a
588
+ // <task-notification> (PROTOCOL.md §4).
589
+ injected: prompt !== void 0 && injectedBlock(prompt) ? true : void 0
590
+ })
591
+ );
509
592
  break;
593
+ }
510
594
  case "PreToolUse":
511
- if (tool) events.push(ev("tool.started", { tool, input: toolCall?.args }));
595
+ if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, agent_id, input: s.tool_input }));
512
596
  break;
513
597
  case "PostToolUse":
514
- if (!tool) events.push(ev("session.heartbeat", {}));
515
- else
598
+ if (tool)
516
599
  events.push(
517
600
  ev("tool.ended", {
518
601
  tool,
519
- ok: !s.error,
520
- error: typeof s.error === "string" ? s.error.slice(0, 2e3) : void 0,
521
- input: toolCall?.args
602
+ call_id,
603
+ turn_id,
604
+ agent_id,
605
+ ms,
606
+ ok: true,
607
+ input: s.tool_input,
608
+ output: s.tool_response
609
+ })
610
+ );
611
+ break;
612
+ case "PostToolUseFailure":
613
+ if (tool)
614
+ events.push(
615
+ ev("tool.ended", {
616
+ tool,
617
+ call_id,
618
+ turn_id,
619
+ agent_id,
620
+ ms,
621
+ ok: false,
622
+ error: errText(s.error ?? s.tool_response),
623
+ input: s.tool_input,
624
+ output: s.tool_response
522
625
  })
523
626
  );
524
627
  break;
628
+ case "PermissionRequest":
629
+ events.push(
630
+ ev("attention.needed", {
631
+ attention_id: call_id ?? permissionId(),
632
+ kind: "permission",
633
+ tool,
634
+ message: tool ? `${tool}${describe(s.tool_input)}` : void 0
635
+ })
636
+ );
637
+ break;
638
+ case "Notification":
639
+ events.push(
640
+ ev("attention.needed", {
641
+ attention_id: `notif-${Date.now()}`,
642
+ kind: notifKind(s.notification_type),
643
+ message: typeof s.message === "string" ? s.message.slice(0, 200) : void 0
644
+ })
645
+ );
646
+ break;
525
647
  case "Stop":
526
- events.push(ev("turn.ended", { reason: s.error ? "error" : "stop" }));
648
+ events.push(
649
+ ev("turn.ended", {
650
+ turn_id,
651
+ reason: "stop",
652
+ background: Array.isArray(s.background_tasks) ? s.background_tasks.length : void 0
653
+ })
654
+ );
527
655
  break;
528
- default:
529
- return { session, events: [], stdout };
530
- }
531
- const r = { session, events, stdout, hints: { workspaces } };
532
- if (typeof s.transcriptPath === "string") r.transcript = s.transcriptPath;
533
- return r;
534
- },
535
- readTranscript(file, fromLine) {
536
- let text = "";
537
- try {
538
- text = readFileSync2(file, "utf8");
539
- } catch {
540
- return { turns: [], line: fromLine };
541
- }
542
- return readAntigravity(text.split("\n"), fromLine);
543
- },
544
- facts(session, transcript, hints) {
545
- if (!transcript) return {};
546
- return antigravityFacts(transcript, {
547
- conversation: session.id,
548
- workspaces: hints.workspaces ?? (session.cwd ? [session.cwd] : []),
549
- env: process.env
550
- });
656
+ case "StopFailure":
657
+ events.push(
658
+ ev("turn.ended", {
659
+ turn_id,
660
+ reason: "error",
661
+ error: typeof s.error === "string" ? s.error.slice(0, 200) : void 0
662
+ })
663
+ );
664
+ break;
665
+ case "SubagentStart":
666
+ events.push(
667
+ ev("subagent.started", {
668
+ agent_id: String(s.agent_id ?? ""),
669
+ agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
670
+ })
671
+ );
672
+ break;
673
+ case "SubagentStop":
674
+ events.push(
675
+ ev("subagent.ended", {
676
+ agent_id: String(s.agent_id ?? ""),
677
+ agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
678
+ })
679
+ );
680
+ break;
681
+ case "PreCompact":
682
+ events.push(ev("context.compacted", { trigger: s.trigger === "manual" ? "manual" : "auto" }));
683
+ break;
684
+ case "PostCompact":
685
+ break;
686
+ // PreCompact already said it; a second event would double-count
687
+ case "SessionEnd":
688
+ events.push(ev("session.ended", { reason: endReason(s.reason) }));
689
+ break;
690
+ case "StatusLine": {
691
+ const limits = claudeLimits(s.rate_limits);
692
+ if (!limits) return null;
693
+ events.push(ev("limits.observed", limits));
694
+ break;
695
+ }
696
+ default:
697
+ return null;
698
+ }
699
+ const hints = {};
700
+ if (event === "UserPromptSubmit" && typeof s.prompt === "string") hints.prompt = s.prompt;
701
+ const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
702
+ if (host && DESKTOP_ID.test(host)) hints.desktop_id = host;
703
+ const attended = input.env.CLAUDE_CODE_SESSION_ATTENDED;
704
+ if (attended === "1" || attended === "0") hints.attended = attended === "1" || !!input.env.SESSIONPIPE_CONTROL_JOB;
705
+ if (event !== "PreToolUse" && event !== "PostToolUse") hints.claude_pid = claudePid(input.env);
706
+ const r = { session, events, hints };
707
+ if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
708
+ return r;
709
+ },
710
+ readTranscript(file, fromLine) {
711
+ let text = "";
712
+ try {
713
+ text = readFileSync2(file, "utf8");
714
+ } catch {
715
+ return { turns: [], line: fromLine };
716
+ }
717
+ return readClaude(text.split("\n"), fromLine);
718
+ },
719
+ facts(session, transcript, hints, state) {
720
+ const out = {};
721
+ if (transcript) Object.assign(out, claudeFacts(transcript, session.id, hints, state));
722
+ const cwd = out.cwd ?? session.cwd;
723
+ Object.assign(out, gitFacts(cwd), out.branch ? { branch: out.branch } : {});
724
+ if (!out.url && typeof hints.desktop_id === "string") out.url = `claude://code/continue/${hints.desktop_id}`;
725
+ if (typeof hints.attended === "boolean") out.attended = hints.attended || !!out.url;
726
+ return out;
551
727
  },
728
+ remoteFacts: (session, local, transcript, state, env = process.env) => claudeRemoteFacts(session.id, local, transcript, state, env),
552
729
  backfill(sinceMs, env = process.env) {
553
730
  const rows2 = [];
554
- for (const data of dataDirs(env)) {
555
- let ids = [];
556
- try {
557
- ids = readdirSync2(path4.join(data, "brain")).filter((n) => /^[0-9a-f-]{36}$/i.test(n));
558
- } catch {
559
- }
560
- for (const id of ids) {
561
- const f = path4.join(data, "brain", id, ".system_generated", "logs", "transcript.jsonl");
562
- try {
563
- if (statSync2(f).mtimeMs < sinceMs) continue;
564
- } catch {
565
- continue;
566
- }
567
- const facts = antigravityFacts(f, { conversation: id, workspaces: [], env });
568
- if (!facts.started_at || !facts.last_at) continue;
569
- const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
570
- rows2.push({ session: { id, ...rest }, started_at, last_at });
571
- }
731
+ const files = [];
732
+ for (const d of claudeDirs(env))
733
+ files.push(...recentFiles(path4.join(d, "projects"), 1, sinceMs, (n) => /^[0-9a-f-]{36}\.jsonl$/.test(n)));
734
+ const noState = { get: () => ({}), save: () => {
735
+ } };
736
+ for (const f of files) {
737
+ const id = path4.basename(f, ".jsonl");
738
+ const facts = claudeFacts(f, id, {}, noState);
739
+ if (!facts.started_at || !facts.last_at) continue;
740
+ const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
741
+ const cwd = rest.cwd;
742
+ rows2.push({
743
+ session: { id, ...rest, ...gitFacts(cwd), ...rest.branch ? { branch: rest.branch } : {} },
744
+ started_at,
745
+ last_at,
746
+ transcript: f
747
+ });
572
748
  }
573
749
  return rows2;
574
750
  }
575
751
  };
752
+ var LINE_REFRESH_S = 60;
753
+ function statusLine(file, want, cmd) {
754
+ const f = readJsonFile(file);
755
+ if (!f.ok) return null;
756
+ const cur = f.value.statusLine;
757
+ const ours = typeof cur?.command === "string" && OURS.test(cur.command);
758
+ const command = cmd?.([NAME, "StatusLine"]).command;
759
+ if (want && cur && !ours)
760
+ return { file, changed: false, skipped: true, note: "a status line of your own is set; limits are not read" };
761
+ if (want ? ours && cur?.command === command && cur?.refreshInterval === LINE_REFRESH_S : !ours) return null;
762
+ if (want) f.value.statusLine = { type: "command", command, refreshInterval: LINE_REFRESH_S };
763
+ else delete f.value.statusLine;
764
+ writeFileSync2(file, formatJson(f.value, f));
765
+ return { file, changed: true, note: want ? "status line written (account limits)" : "status line removed" };
766
+ }
767
+ var epochIso = (t) => typeof t === "number" && t > 0 && t < 1e11 ? new Date(t * 1e3).toISOString() : void 0;
768
+ var WINDOW_MINUTES = { five_hour: 300, seven_day: 10080 };
769
+ function claudeLimits(rl) {
770
+ if (!rl || typeof rl !== "object" || Array.isArray(rl)) return null;
771
+ const windows = [];
772
+ for (const [id, w] of Object.entries(rl)) {
773
+ if (!/^[a-z0-9_]{1,64}$/.test(id) || typeof w?.used_percentage !== "number" || w.used_percentage < 0) continue;
774
+ windows.push(
775
+ prune({
776
+ id,
777
+ used_percent: w.used_percentage,
778
+ window_minutes: WINDOW_MINUTES[id],
779
+ resets_at: epochIso(w.resets_at)
780
+ })
781
+ );
782
+ }
783
+ return windows.length ? { windows: windows.slice(0, 8) } : null;
784
+ }
576
785
  function prune(o) {
577
786
  for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
578
787
  return o;
579
788
  }
580
- function agPath(p) {
581
- if (typeof p !== "string" || !p) return null;
582
- let s = p;
583
- if (/^file:/i.test(s)) {
789
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
790
+ function notClaudeCode(id, s) {
791
+ return typeof s.transcript_path !== "string" && !UUID.test(id);
792
+ }
793
+ function pickSource(v) {
794
+ return typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
795
+ }
796
+ var harnessWord = (v) => typeof v === "string" && /^[A-Za-z0-9][A-Za-z0-9_.-]{0,39}$/.test(v) ? v : void 0;
797
+ var WRAPPER = /^<(?:task-notification|cross-session-message|system-reminder|local-command-stdout|command-[a-z-]+)[\s/>]/i;
798
+ var injectedBlock = (prompt) => WRAPPER.test(prompt.trim());
799
+ function errText(v) {
800
+ if (typeof v === "string") return v.slice(0, 2e3);
801
+ if (v && typeof v === "object") {
802
+ const e = v;
803
+ if (typeof e.error === "string") return e.error.slice(0, 2e3);
804
+ if (typeof e.message === "string") return e.message.slice(0, 2e3);
805
+ }
806
+ return void 0;
807
+ }
808
+ function describe(input) {
809
+ if (!input || typeof input !== "object") return "";
810
+ const i = input;
811
+ const v = i.command ?? i.file_path ?? i.path ?? i.url ?? i.pattern;
812
+ return typeof v === "string" ? ` ${v.slice(0, 120)}` : "";
813
+ }
814
+ var NAME_RANK = {
815
+ user: "custom",
816
+ auto: "harness",
817
+ hook: "harness",
818
+ peer: "harness"
819
+ };
820
+ function registryEntry(j) {
821
+ if (!j || typeof j !== "object") return null;
822
+ const r = j;
823
+ const source = r.nameSource ? NAME_RANK[r.nameSource] : void 0;
824
+ return {
825
+ sessionId: r.sessionId ? String(r.sessionId) : null,
826
+ name: source && typeof r.name === "string" && r.name.trim() ? r.name.trim() : null,
827
+ name_source: source ?? null,
828
+ bridge: typeof r.bridgeSessionId === "string" ? r.bridgeSessionId : null
829
+ };
830
+ }
831
+ var liveCache = /* @__PURE__ */ new Map();
832
+ function claudeLive(dir) {
833
+ const hit = liveCache.get(dir);
834
+ if (hit) return hit;
835
+ const map = /* @__PURE__ */ new Map();
836
+ let names = [];
837
+ try {
838
+ names = readdirSync2(path4.join(dir, "sessions")).filter((n) => n.endsWith(".json"));
839
+ } catch {
840
+ }
841
+ for (const n of names) {
584
842
  try {
585
- s = decodeURIComponent(new URL(s).pathname);
843
+ const e = registryEntry(JSON.parse(readFileSync2(path4.join(dir, "sessions", n), "utf8")));
844
+ if (e?.sessionId) map.set(e.sessionId, e);
586
845
  } catch {
587
- return null;
588
846
  }
589
- if (process.platform === "win32" && /^\/[A-Za-z]:/.test(s)) s = s.slice(1);
590
- } else if (/^[a-z][a-z0-9+.-]*:\/\//i.test(s)) return null;
591
- if (!path4.isAbsolute(s)) return null;
592
- return path4.resolve(s);
847
+ }
848
+ liveCache.set(dir, map);
849
+ return map;
593
850
  }
594
- var agWorkspaces = (paths) => (Array.isArray(paths) ? paths : []).map(agPath).filter((p) => !!p).filter((p, i, a) => a.indexOf(p) === i);
595
- function agVariants(p) {
596
- const out = [p];
851
+ function claudeLiveByPid(dir, pid) {
852
+ if (!pid) return null;
597
853
  try {
598
- const r = realpathSync.native(p);
599
- if (r !== p) out.push(r);
854
+ return registryEntry(JSON.parse(readFileSync2(path4.join(dir, "sessions", `${pid}.json`), "utf8")));
600
855
  } catch {
856
+ return null;
601
857
  }
602
- return out;
603
858
  }
604
- function agProjects(dir) {
859
+ function claudePid(env = process.env) {
860
+ try {
861
+ const dirs = claudeDirs(env);
862
+ const has = (pid2) => dirs.some((d) => existsSync2(path4.join(d, "sessions", `${pid2}.json`)));
863
+ let pid = process.ppid;
864
+ if (has(pid)) return pid;
865
+ const parent = parentOf();
866
+ for (let hop = 0; hop < 6 && pid > 1; hop++) {
867
+ pid = parent(pid);
868
+ if (pid > 1 && has(pid)) return pid;
869
+ }
870
+ } catch {
871
+ }
872
+ return null;
873
+ }
874
+ function parentOf() {
875
+ if (process.platform === "linux") {
876
+ return (pid) => {
877
+ try {
878
+ const st = readFileSync2(`/proc/${pid}/stat`, "utf8");
879
+ return Number(st.slice(st.lastIndexOf(")") + 2).split(" ")[1]) || 0;
880
+ } catch {
881
+ return 0;
882
+ }
883
+ };
884
+ }
885
+ const table = /* @__PURE__ */ new Map();
886
+ try {
887
+ const out = execFileSync2("ps", ["-axo", "pid=,ppid="], {
888
+ encoding: "utf8",
889
+ timeout: 2e3,
890
+ maxBuffer: 8 * 1024 * 1024,
891
+ stdio: ["ignore", "pipe", "ignore"]
892
+ });
893
+ for (const l of out.split("\n")) {
894
+ const m = /^\s*(\d+)\s+(\d+)/.exec(l);
895
+ if (m) table.set(Number(m[1]), Number(m[2]));
896
+ }
897
+ } catch {
898
+ }
899
+ return (pid) => table.get(pid) || 0;
900
+ }
901
+ var EMAIL_RE = /^[^@\s]{1,64}@[^@\s]{1,189}$/;
902
+ function claudeLogin(dir, home = homeDir()) {
903
+ const files = [path4.join(dir, ".claude.json")];
904
+ if (path4.resolve(dir) === path4.join(home, ".claude")) files.unshift(path4.join(home, ".claude.json"));
905
+ for (const f of files) {
906
+ try {
907
+ const a = JSON.parse(readFileSync2(f, "utf8")).oauthAccount;
908
+ if (typeof a?.accountUuid === "string" && a.accountUuid) {
909
+ const email = typeof a.emailAddress === "string" && EMAIL_RE.test(a.emailAddress) ? a.emailAddress : void 0;
910
+ return { id: a.accountUuid.slice(0, 128), ...email ? { email } : {} };
911
+ }
912
+ } catch {
913
+ }
914
+ }
915
+ return apiAccount(dir);
916
+ }
917
+ var ANTHROPIC_HOST = /(^|\.)anthropic\.com$/i;
918
+ function apiAccount(dir) {
919
+ const base = readJson(settingsIn(dir)).env?.ANTHROPIC_BASE_URL;
920
+ if (typeof base !== "string") return null;
921
+ let u;
922
+ try {
923
+ u = new URL(base.trim());
924
+ } catch {
925
+ return null;
926
+ }
927
+ if (u.protocol !== "https:" && u.protocol !== "http:" || !u.hostname || ANTHROPIC_HOST.test(u.hostname))
928
+ return null;
929
+ const origin = `${u.protocol}//${u.host}`;
930
+ return { id: `api-${createHash("sha256").update(origin).digest("hex").slice(0, 16)}`, name: u.host };
931
+ }
932
+ var accountCache = /* @__PURE__ */ new Map();
933
+ function claudeAccount(dir) {
934
+ const hit = accountCache.get(dir);
935
+ if (hit !== void 0) return hit;
936
+ const acct = claudeLogin(dir)?.id ?? null;
937
+ accountCache.set(dir, acct);
938
+ return acct;
939
+ }
940
+ function claudeAccounts(dirs, home = homeDir()) {
605
941
  const out = [];
606
- let names = [];
942
+ for (const d of dirs) {
943
+ const l = claudeLogin(d, home);
944
+ if (!l) continue;
945
+ const seen = out.find((a) => a.id === l.id);
946
+ if (!seen) out.push({ harness: NAME, ...l });
947
+ else if (!seen.email && l.email) seen.email = l.email;
948
+ }
949
+ return out;
950
+ }
951
+ var LINK_RE = /^https:\/\/claude\.ai\/code\/session_[A-Za-z0-9]+$/;
952
+ function transcriptLink(records) {
953
+ for (let i = records.length - 1; i >= 0; i--) {
954
+ const j = records[i];
955
+ if (j.type === "system" && j.subtype === "bridge_status" && typeof j.url === "string" && LINK_RE.test(j.url))
956
+ return j.url;
957
+ if (j.type === "bridge-session" && typeof j.bridgeSessionId === "string") {
958
+ const m = /^(?:cse|session)_([A-Za-z0-9]+)$/.exec(j.bridgeSessionId);
959
+ if (m) return `https://claude.ai/code/session_${m[1]}`;
960
+ }
961
+ }
962
+ return null;
963
+ }
964
+ function appTitle(text) {
965
+ const t = String(text).replace(/\s+/g, " ").trim();
966
+ return t.length > 50 ? `${t.slice(0, 50)}...` : t;
967
+ }
968
+ function askText(j) {
969
+ if (!j || j.type !== "user" || j.isMeta || j.isSidechain) return null;
970
+ const origin = j.origin;
971
+ if (origin?.kind && origin.kind !== "human") return null;
972
+ const c = j.message?.content;
973
+ 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();
974
+ if (!text || isInjected(text)) return null;
975
+ return text;
976
+ }
977
+ function firstAsk(records) {
978
+ for (const j of records) {
979
+ const t = askText(j);
980
+ if (t) return appTitle(t);
981
+ }
982
+ return null;
983
+ }
984
+ function scanTitles(file, from, found) {
985
+ const out = { custom: found.custom ?? null, ai: found.ai ?? null, upto: from };
986
+ let size = 0;
607
987
  try {
608
- names = readdirSync2(dir).filter((n) => n.endsWith(".json"));
988
+ size = statSync2(file).size;
609
989
  } catch {
610
990
  return out;
611
991
  }
612
- for (const n of names) {
613
- const id = n.slice(0, -5);
614
- if (!ID_RE.test(id)) continue;
615
- let j;
992
+ if (size < from) {
993
+ out.upto = 0;
994
+ out.custom = out.ai = null;
995
+ from = 0;
996
+ }
997
+ let pos = 0;
998
+ scanLines(
999
+ file,
1000
+ (l) => {
1001
+ pos += Buffer.byteLength(l, "utf8") + 1;
1002
+ if (pos <= from || l.indexOf('-title"') < 0) return null;
1003
+ try {
1004
+ const j = JSON.parse(l);
1005
+ if (j.type === "custom-title" && j.customTitle) out.custom = j.customTitle;
1006
+ if (j.type === "ai-title" && j.aiTitle) out.ai = j.aiTitle;
1007
+ } catch {
1008
+ }
1009
+ return null;
1010
+ },
1011
+ 512 * 1024 * 1024
1012
+ );
1013
+ out.upto = size;
1014
+ return out;
1015
+ }
1016
+ function claudeFacts(file, sessionId, hints, state) {
1017
+ const { head, tail } = ends(file, 64 * 1024, 256 * 1024);
1018
+ const H = parseLines(head);
1019
+ const T = parseLines(tail);
1020
+ const out = {};
1021
+ const id = sessionId || path4.basename(file, ".jsonl");
1022
+ const mem = state.get(NAME, id);
1023
+ let custom = null;
1024
+ let ai = null;
1025
+ for (const j of H.concat(T)) {
1026
+ if (j.type === "custom-title" && typeof j.customTitle === "string") custom = j.customTitle;
1027
+ if (j.type === "ai-title" && typeof j.aiTitle === "string") ai = j.aiTitle;
1028
+ }
1029
+ let size = 0;
1030
+ try {
1031
+ size = statSync2(file).size;
1032
+ } catch {
1033
+ }
1034
+ if (!custom && !ai && size > 64 * 1024 + 256 * 1024) {
1035
+ const s = scanTitles(file, Number(mem.scan_upto) || 0, {
1036
+ custom: mem.scan_custom,
1037
+ ai: mem.scan_ai
1038
+ });
1039
+ custom = s.custom;
1040
+ ai = s.ai;
1041
+ state.save(NAME, id, { scan_upto: s.upto, scan_custom: s.custom, scan_ai: s.ai });
1042
+ }
1043
+ const dir = configDirOf(file);
1044
+ const live = claudeLive(dir).get(id) ?? claudeLiveByPid(dir, hints.claude_pid);
1045
+ if (live?.name) state.save(NAME, id, { name: live.name, name_source: live.name_source });
1046
+ const bridge = live?.bridge && /^session_[A-Za-z0-9]+$/.test(live.bridge) ? `https://claude.ai/code/${live.bridge}` : null;
1047
+ const url = bridge ?? transcriptLink(T) ?? transcriptLink(H) ?? (typeof mem.url === "string" ? mem.url : null);
1048
+ if (url) {
1049
+ out.url = url;
1050
+ state.save(NAME, id, { url });
1051
+ }
1052
+ const name = live?.name ? { title: live.name, source: live.name_source } : typeof mem.name === "string" ? { title: mem.name, source: mem.name_source } : null;
1053
+ const pick = name && name.source === "custom" && name || custom && { title: custom, source: "custom" } || name || ai && { title: ai, source: "harness" };
1054
+ let ask = typeof mem.first_ask === "string" ? mem.first_ask : firstAsk(H);
1055
+ if (!ask && size > 64 * 1024 && !mem.first_ask_none) {
1056
+ ask = scanLines(file, (l) => {
1057
+ try {
1058
+ const t = askText(JSON.parse(l));
1059
+ return t ? appTitle(t) : null;
1060
+ } catch {
1061
+ return null;
1062
+ }
1063
+ });
1064
+ if (!ask && size > 16 * 1024 * 1024) state.save(NAME, id, { first_ask_none: true });
1065
+ }
1066
+ if (ask) {
1067
+ out.first_ask = ask;
1068
+ if (!mem.first_ask) state.save(NAME, id, { first_ask: ask });
1069
+ }
1070
+ if (pick) {
1071
+ out.title = String(pick.title).slice(0, 120);
1072
+ out.title_source = pick.source;
1073
+ } else if (ask) {
1074
+ out.title = ask;
1075
+ out.title_source = "first-ask";
1076
+ }
1077
+ const acct = claudeAccount(dir);
1078
+ if (acct) out.account_id = acct;
1079
+ for (let i = T.length - 1; i >= 0; i--) {
1080
+ const m = T[i].message?.model;
1081
+ if (T[i]?.type === "assistant" && m && m !== "<synthetic>") {
1082
+ out.model = m;
1083
+ break;
1084
+ }
1085
+ }
1086
+ const meta = [...T].reverse().find((j) => j.version) ?? H.find((j) => j.version);
1087
+ if (meta?.version) out.harness_version = String(meta.version);
1088
+ const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
1089
+ if (times.length) {
1090
+ out.started_at = Math.min(...times);
1091
+ out.last_at = Math.max(...times);
1092
+ }
1093
+ const withCwd = H.find((j) => typeof j.cwd === "string");
1094
+ if (withCwd) {
1095
+ out.cwd = withCwd.cwd;
1096
+ if (typeof withCwd.gitBranch === "string" && withCwd.gitBranch !== "HEAD") out.branch = withCwd.gitBranch;
1097
+ }
1098
+ return out;
1099
+ }
1100
+ var REMOTE_RETRY_MS = 6e4;
1101
+ var REMOTE_REFRESH_MS = 30 * 6e4;
1102
+ var CLAUDE_API = "https://api.anthropic.com";
1103
+ var KEYCHAIN = "Claude Code-credentials";
1104
+ var REMOTE_TIMEOUT_MS = 3e3;
1105
+ var KEYCHAIN_TIMEOUT_MS = 2e3;
1106
+ var REMOTE_MAX_BYTES = 1 << 20;
1107
+ var cleanName = (s) => s.replace(/[\p{Cc}\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]/gu, " ").replace(/\s+/g, " ").trim();
1108
+ async function claudeRemoteFacts(id, local, transcript, state, env, now = Date.now()) {
1109
+ if (!transcript || !local.url || !LINK_RE.test(local.url) || local.title_source === "custom") return {};
1110
+ const mem = state.get(NAME, id);
1111
+ let title = typeof mem.remote_title === "string" ? mem.remote_title : null;
1112
+ if (now - (Number(mem.remote_at) || 0) >= (title ? REMOTE_REFRESH_MS : REMOTE_RETRY_MS)) {
1113
+ state.save(NAME, id, { remote_at: now });
1114
+ const token = claudeAiToken(configDirOf(transcript), env, now);
1115
+ const got = token ? await claudeAiTitle(local.url.slice(local.url.lastIndexOf("/") + 1), token) : void 0;
1116
+ if (got !== void 0) {
1117
+ title = got?.slice(0, 200) ?? null;
1118
+ state.save(NAME, id, { remote_title: title });
1119
+ }
1120
+ }
1121
+ return title ? { title: title.slice(0, 120), title_source: "harness" } : {};
1122
+ }
1123
+ function keychainServices(dir, env) {
1124
+ const named = (p) => `${KEYCHAIN}-${createHash("sha256").update(p.normalize("NFC")).digest("hex").slice(0, 8)}`;
1125
+ const out = /* @__PURE__ */ new Set();
1126
+ if (env.CLAUDE_CONFIG_DIR && path4.resolve(env.CLAUDE_CONFIG_DIR) === dir) out.add(named(env.CLAUDE_CONFIG_DIR));
1127
+ if (dir === path4.join(env.HOME || homeDir(), ".claude")) out.add(KEYCHAIN);
1128
+ out.add(named(dir));
1129
+ return [...out];
1130
+ }
1131
+ var tokenCache = /* @__PURE__ */ new Map();
1132
+ function claudeAiToken(dir, env, now) {
1133
+ const hit = tokenCache.get(dir);
1134
+ if (hit && hit.expires > now + 6e4) return hit.token;
1135
+ const raw = [];
1136
+ if (process.platform === "darwin")
1137
+ for (const service of keychainServices(dir, env)) {
1138
+ try {
1139
+ const user = env.USER || userInfo().username;
1140
+ const account = /^[\w.-]+$/.test(user) ? user : "claude-code-user";
1141
+ raw.push(
1142
+ execFileSync2("security", ["find-generic-password", "-a", account, "-w", "-s", service], {
1143
+ encoding: "utf8",
1144
+ timeout: KEYCHAIN_TIMEOUT_MS,
1145
+ stdio: ["ignore", "pipe", "ignore"]
1146
+ })
1147
+ );
1148
+ break;
1149
+ } catch {
1150
+ }
1151
+ }
1152
+ try {
1153
+ raw.push(readFileSync2(path4.join(dir, ".credentials.json"), "utf8"));
1154
+ } catch {
1155
+ }
1156
+ for (const r of raw) {
616
1157
  try {
617
- j = JSON.parse(readFileSync2(path4.join(dir, n), "utf8"));
1158
+ const t = r.trim();
1159
+ const o = JSON.parse(/^(?:[0-9a-f]{2})+$/i.test(t) ? Buffer.from(t, "hex").toString("utf8") : t).claudeAiOauth;
1160
+ if (typeof o?.accessToken !== "string" || !o.accessToken) continue;
1161
+ const expires = typeof o.expiresAt === "number" ? o.expiresAt : Number.POSITIVE_INFINITY;
1162
+ if (expires <= now + 6e4) continue;
1163
+ tokenCache.set(dir, { token: o.accessToken, expires });
1164
+ return o.accessToken;
618
1165
  } catch {
1166
+ }
1167
+ }
1168
+ return null;
1169
+ }
1170
+ async function claudeAiTitle(bridge, token) {
1171
+ try {
1172
+ const r = await fetch(`${CLAUDE_API}/v1/code/sessions/${bridge}`, {
1173
+ headers: { Authorization: `Bearer ${token}`, "anthropic-version": "2023-06-01", "User-Agent": "sessionpipe" },
1174
+ redirect: "error",
1175
+ signal: AbortSignal.timeout(REMOTE_TIMEOUT_MS)
1176
+ });
1177
+ if (r.status !== 200 || Number(r.headers.get("content-length")) > REMOTE_MAX_BYTES) return void 0;
1178
+ const text = await r.text();
1179
+ if (text.length > REMOTE_MAX_BYTES) return void 0;
1180
+ const j = JSON.parse(text);
1181
+ const s = j?.response_shape ?? j?.session ?? j;
1182
+ const bare = (x) => x.replace(/^(?:session|cse)_/, "");
1183
+ if (typeof s?.id !== "string" || bare(s.id) !== bare(bridge)) return void 0;
1184
+ return typeof s.title === "string" && cleanName(s.title) || null;
1185
+ } catch {
1186
+ return void 0;
1187
+ }
1188
+ }
1189
+
1190
+ // src/adapters/claude-code-pool.ts
1191
+ var POOL_ENDPOINTS = {
1192
+ authorize: "https://claude.com/cai/oauth/authorize",
1193
+ token: "https://platform.claude.com/v1/oauth/token",
1194
+ callback: "https://platform.claude.com/oauth/code/callback",
1195
+ profile: "https://api.anthropic.com/api/oauth/profile",
1196
+ usage: "https://api.anthropic.com/api/oauth/usage",
1197
+ api: "https://api.anthropic.com",
1198
+ clientId: "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
1199
+ // The six Claude Code itself asks for with this client id: a token minted with fewer
1200
+ // than the client's usual set is the kind of thing that fails only live.
1201
+ scopes: [
1202
+ "user:inference",
1203
+ "user:profile",
1204
+ "org:create_api_key",
1205
+ "user:sessions:claude_code",
1206
+ "user:mcp_servers",
1207
+ "user:file_upload"
1208
+ ],
1209
+ beta: "oauth-2025-04-20"
1210
+ };
1211
+ var PoolAuthError = class extends Error {
1212
+ };
1213
+ var PoolUpstreamError = class extends Error {
1214
+ };
1215
+ var PREFIX = "anthropic-ratelimit-unified-";
1216
+ var WINDOW_IDS = { "5h": "five_hour", "7d": "seven_day" };
1217
+ var WINDOW_MINUTES2 = { five_hour: 300, seven_day: 10080 };
1218
+ var STATUSES = /* @__PURE__ */ new Set(["allowed", "allowed_warning", "rejected"]);
1219
+ var ID_RE = /^[a-z0-9_]{1,64}$/;
1220
+ function entries(h) {
1221
+ const out = [];
1222
+ if (h instanceof Headers) {
1223
+ h.forEach((v, k) => {
1224
+ out.push([k.toLowerCase(), v]);
1225
+ });
1226
+ return out;
1227
+ }
1228
+ for (const [k, v] of Object.entries(h)) {
1229
+ const s = Array.isArray(v) ? v[0] : v;
1230
+ if (typeof s === "string") out.push([k.toLowerCase(), s]);
1231
+ }
1232
+ return out;
1233
+ }
1234
+ function isoTime(v) {
1235
+ if (typeof v === "number") return v > 0 && v < 1e11 ? new Date(v * 1e3).toISOString() : void 0;
1236
+ if (typeof v !== "string") return void 0;
1237
+ const s = v.trim();
1238
+ if (/^\d+(\.\d+)?$/.test(s)) return isoTime(Number(s));
1239
+ if (!/^\d{4}-\d{2}-\d{2}T/.test(s)) return void 0;
1240
+ const t = Date.parse(s);
1241
+ return Number.isFinite(t) ? new Date(t).toISOString() : void 0;
1242
+ }
1243
+ var percentOf = (n) => Math.round(Math.max(0, n) * 10) / 10;
1244
+ var windowId = (part) => WINDOW_IDS[part] ?? `seven_day_${part.slice(3)}`;
1245
+ function readLimits(headers, now) {
1246
+ const seen = /* @__PURE__ */ new Map();
1247
+ const out = { windows: [], observed_at: now };
1248
+ let any = false;
1249
+ for (const [name, value] of entries(headers)) {
1250
+ if (!name.startsWith(PREFIX)) continue;
1251
+ any = true;
1252
+ const rest = name.slice(PREFIX.length);
1253
+ if (rest === "status") {
1254
+ if (STATUSES.has(value)) out.status = value;
619
1255
  continue;
620
1256
  }
621
- const folders = [];
622
- for (const r of j?.projectResources?.resources ?? []) {
623
- const f = agPath(r?.folderUri ?? r?.gitFolder?.folderUri);
624
- if (f) folders.push(...agVariants(f));
1257
+ if (rest === "representative-claim") {
1258
+ if (ID_RE.test(value)) out.claim = value;
1259
+ continue;
1260
+ }
1261
+ if (rest === "reset") {
1262
+ const t = isoTime(value);
1263
+ if (t) out.resets_at = t;
1264
+ continue;
1265
+ }
1266
+ const m = /^(5h|7d|7d_[a-z0-9_]+)-(utilization|reset)$/.exec(rest);
1267
+ if (!m) continue;
1268
+ const id = windowId(m[1]);
1269
+ if (!ID_RE.test(id)) continue;
1270
+ const w = seen.get(id) ?? {};
1271
+ seen.set(id, w);
1272
+ if (m[2] === "utilization") {
1273
+ const n = Number(value);
1274
+ if (value.trim() && Number.isFinite(n)) w.used = percentOf(n * 100);
1275
+ } else {
1276
+ const t = isoTime(value);
1277
+ if (t) w.resets_at = t;
625
1278
  }
626
- if (folders.length) out.push({ id, folders });
1279
+ }
1280
+ if (!any) return null;
1281
+ for (const [id, w] of seen) {
1282
+ if (w.used === void 0) continue;
1283
+ const minutes = WINDOW_MINUTES2[id];
1284
+ out.windows.push({
1285
+ id,
1286
+ used_percent: w.used,
1287
+ ...w.resets_at ? { resets_at: w.resets_at } : {},
1288
+ ...minutes ? { window_minutes: minutes } : {}
1289
+ });
627
1290
  }
628
1291
  return out;
629
1292
  }
630
- function agMatchProject(projects, workspaces) {
631
- const ws = [];
632
- for (const w of workspaces) {
633
- const p = agPath(w);
634
- if (p) ws.push(...agVariants(p));
1293
+ function usageLimits(json, now) {
1294
+ if (!json || typeof json !== "object" || Array.isArray(json)) return null;
1295
+ const windows = [];
1296
+ for (const [id, v] of Object.entries(json)) {
1297
+ if (!ID_RE.test(id) || !v || typeof v !== "object" || Array.isArray(v)) continue;
1298
+ const w = v;
1299
+ if (typeof w.utilization !== "number" || !Number.isFinite(w.utilization)) continue;
1300
+ const resets_at = isoTime(w.resets_at);
1301
+ const minutes = WINDOW_MINUTES2[id];
1302
+ windows.push({
1303
+ id,
1304
+ used_percent: percentOf(w.utilization),
1305
+ ...resets_at ? { resets_at } : {},
1306
+ ...minutes ? { window_minutes: minutes } : {}
1307
+ });
635
1308
  }
636
- let best = null;
637
- let bestLen = -1;
638
- for (const pr of projects)
639
- for (const f of pr.folders) {
640
- const within = ws.some((w) => w === f || w.startsWith(f.endsWith(path4.sep) ? f : f + path4.sep));
641
- if (within && f.length > bestLen) {
642
- best = pr.id;
643
- bestLen = f.length;
644
- }
1309
+ return windows.length ? { windows: windows.slice(0, 8), observed_at: now } : null;
1310
+ }
1311
+ var UUID2 = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
1312
+ var UUID_RE = new RegExp(`^${UUID2}$`, "i");
1313
+ var LEGACY_ACCOUNT = new RegExp(`(?:^|_)account_(${UUID2})(?=_|$)`, "i");
1314
+ var LEGACY_SESSION = new RegExp(`(?:^|_)session_(${UUID2})$`, "i");
1315
+ function userIdOf(body) {
1316
+ let v = body;
1317
+ if (Buffer.isBuffer(v) || typeof v === "string") {
1318
+ try {
1319
+ v = JSON.parse(v.toString());
1320
+ } catch {
1321
+ return null;
645
1322
  }
646
- return best;
1323
+ }
1324
+ const id = v?.metadata?.user_id;
1325
+ return typeof id === "string" && id.length <= 4096 ? id : null;
1326
+ }
1327
+ function partsOf(id) {
1328
+ if (id.startsWith("{")) {
1329
+ try {
1330
+ const j = JSON.parse(id);
1331
+ return {
1332
+ ...typeof j.session_id === "string" && j.session_id ? { session: j.session_id } : {},
1333
+ ...typeof j.account_uuid === "string" && UUID_RE.test(j.account_uuid) ? { account: j.account_uuid } : {}
1334
+ };
1335
+ } catch {
1336
+ return {};
1337
+ }
1338
+ }
1339
+ const s = LEGACY_SESSION.exec(id)?.[1];
1340
+ const a = LEGACY_ACCOUNT.exec(id)?.[1];
1341
+ return { ...s ? { session: s } : {}, ...a ? { account: a } : {} };
1342
+ }
1343
+ function parseUserId(body) {
1344
+ const id = userIdOf(body);
1345
+ return id === null ? null : partsOf(id);
1346
+ }
1347
+ function withAccount(body, accountUuid) {
1348
+ if (!UUID_RE.test(accountUuid)) return body;
1349
+ const id = userIdOf(body);
1350
+ if (id === null) return body;
1351
+ const current = partsOf(id).account;
1352
+ if (current && current.toLowerCase() === accountUuid.toLowerCase()) return body;
1353
+ const empty = !current && id.startsWith("{") && emptyAccountSlot(id);
1354
+ if (!current && !empty) return body;
1355
+ const text = body.toString("utf8");
1356
+ const literal = JSON.stringify(id).slice(1, -1);
1357
+ const was = current ?? "";
1358
+ const at = current ? literal.indexOf(current) : literal.indexOf(EMPTY_SLOT) + EMPTY_SLOT.length;
1359
+ const ms = [...text.matchAll(/"user_id"\s*:\s*"((?:[^"\\]|\\.)*)"/g)].filter((x) => x[1] === literal);
1360
+ if (!ms.length) return body;
1361
+ if (ms.length === 1) {
1362
+ const m = ms[0];
1363
+ const start = m.index + m[0].length - 1 - literal.length + at;
1364
+ return Buffer.from(text.slice(0, start) + accountUuid + text.slice(start + was.length), "utf8");
1365
+ }
1366
+ const j = JSON.parse(text);
1367
+ if (current) j.metadata.user_id = id.replace(current, accountUuid);
1368
+ else
1369
+ j.metadata.user_id = JSON.stringify({ ...JSON.parse(id), account_uuid: accountUuid });
1370
+ return Buffer.from(JSON.stringify(j), "utf8");
1371
+ }
1372
+ var EMPTY_SLOT = '\\"account_uuid\\":\\"';
1373
+ function emptyAccountSlot(id) {
1374
+ try {
1375
+ return JSON.parse(id).account_uuid === "";
1376
+ } catch {
1377
+ return false;
1378
+ }
1379
+ }
1380
+ var DROP = /* @__PURE__ */ new Set([
1381
+ "connection",
1382
+ "keep-alive",
1383
+ "transfer-encoding",
1384
+ "te",
1385
+ "trailer",
1386
+ "upgrade",
1387
+ "host",
1388
+ "content-length",
1389
+ "authorization",
1390
+ "x-api-key"
1391
+ ]);
1392
+ function upstreamHeaders(req, access) {
1393
+ const out = {};
1394
+ for (const [k, v] of Object.entries(req)) {
1395
+ const name = k.toLowerCase();
1396
+ if (v === void 0 || DROP.has(name) || name.startsWith("proxy-")) continue;
1397
+ out[name] = Array.isArray(v) ? v.join(", ") : v;
1398
+ }
1399
+ out.authorization = `Bearer ${access}`;
1400
+ const betas = (out["anthropic-beta"] ?? "").split(",").map((s) => s.trim()).filter(Boolean);
1401
+ if (!betas.includes(POOL_ENDPOINTS.beta)) betas.push(POOL_ENDPOINTS.beta);
1402
+ out["anthropic-beta"] = betas.join(",");
1403
+ return out;
1404
+ }
1405
+ function beginLogin(random = randomBytes) {
1406
+ const verifier = random(32).toString("base64url");
1407
+ const state = random(32).toString("base64url");
1408
+ const challenge = createHash2("sha256").update(verifier).digest("base64url");
1409
+ const u = new URL(POOL_ENDPOINTS.authorize);
1410
+ u.searchParams.append("code", "true");
1411
+ u.searchParams.append("client_id", POOL_ENDPOINTS.clientId);
1412
+ u.searchParams.append("response_type", "code");
1413
+ u.searchParams.append("redirect_uri", POOL_ENDPOINTS.callback);
1414
+ u.searchParams.append("scope", POOL_ENDPOINTS.scopes.join(" "));
1415
+ u.searchParams.append("code_challenge", challenge);
1416
+ u.searchParams.append("code_challenge_method", "S256");
1417
+ u.searchParams.append("state", state);
1418
+ return { url: u.toString(), verifier, state };
1419
+ }
1420
+ function pastedCode(pasted, state) {
1421
+ const s = pasted.trim();
1422
+ if (!s) throw new PoolAuthError("Paste the code the sign-in page shows.");
1423
+ if (/^https?:\/\//i.test(s)) {
1424
+ let u;
1425
+ try {
1426
+ u = new URL(s);
1427
+ } catch {
1428
+ throw new PoolAuthError("That is not the sign-in page's code.");
1429
+ }
1430
+ const code2 = u.searchParams.get("code");
1431
+ const st2 = u.searchParams.get("state");
1432
+ if (!code2) throw new PoolAuthError("That URL carries no code.");
1433
+ if (st2 !== null && st2 !== state) throw new PoolAuthError("That code belongs to another sign-in; start again.");
1434
+ return code2;
1435
+ }
1436
+ const hash = s.indexOf("#");
1437
+ if (hash < 0) return s;
1438
+ const code = s.slice(0, hash).trim();
1439
+ const st = s.slice(hash + 1).trim();
1440
+ if (!code) throw new PoolAuthError("Paste the code the sign-in page shows.");
1441
+ if (st && st !== state) throw new PoolAuthError("That code belongs to another sign-in; start again.");
1442
+ return code;
1443
+ }
1444
+ var JSON_HEADERS = { "content-type": "application/json", "anthropic-beta": POOL_ENDPOINTS.beta };
1445
+ async function post(fetch2, url, body, what) {
1446
+ try {
1447
+ return await fetch2(url, { method: "POST", headers: JSON_HEADERS, body: JSON.stringify(body) });
1448
+ } catch (e) {
1449
+ throw new PoolUpstreamError(`${what}: ${e.message || "no answer from Anthropic"}`);
1450
+ }
1451
+ }
1452
+ async function get(fetch2, url, access, what) {
1453
+ try {
1454
+ return await fetch2(url, { headers: { authorization: `Bearer ${access}`, "anthropic-beta": POOL_ENDPOINTS.beta } });
1455
+ } catch (e) {
1456
+ throw new PoolUpstreamError(`${what}: ${e.message || "no answer from Anthropic"}`);
1457
+ }
1458
+ }
1459
+ async function jsonOf(r) {
1460
+ try {
1461
+ return await r.json();
1462
+ } catch {
1463
+ return null;
1464
+ }
647
1465
  }
648
- function antigravityRemoteUrl(transcript, opts) {
649
- const i = transcript.indexOf(`${path4.sep}brain${path4.sep}`);
650
- const dirs = (i > 0 ? [transcript.slice(0, i)] : []).concat(dataDirs(opts.env));
651
- for (const d of dirs) {
1466
+ function credentialOf(j, now, keepRefresh, what) {
1467
+ const t = j;
1468
+ const refresh = typeof t?.refresh_token === "string" && t.refresh_token ? t.refresh_token : keepRefresh;
1469
+ if (!t || typeof t.access_token !== "string" || !t.access_token || !refresh || typeof t.expires_in !== "number")
1470
+ throw new PoolUpstreamError(`${what}: Anthropic's answer had no token in it`);
1471
+ return { access: t.access_token, refresh, expires_at: now + t.expires_in * 1e3 };
1472
+ }
1473
+ async function completeLogin(code, pending, fetch2, now) {
1474
+ const c = pastedCode(code, pending.state);
1475
+ const r = await post(
1476
+ fetch2,
1477
+ POOL_ENDPOINTS.token,
1478
+ {
1479
+ grant_type: "authorization_code",
1480
+ code: c,
1481
+ state: pending.state,
1482
+ client_id: POOL_ENDPOINTS.clientId,
1483
+ redirect_uri: POOL_ENDPOINTS.callback,
1484
+ code_verifier: pending.verifier
1485
+ },
1486
+ "sign-in"
1487
+ );
1488
+ if (r.status >= 400 && r.status < 500)
1489
+ throw new PoolAuthError(`Anthropic did not accept the code (HTTP ${r.status}); start the sign-in again.`);
1490
+ if (!r.ok) throw new PoolUpstreamError(`sign-in: Anthropic answered HTTP ${r.status}; try again in a minute`);
1491
+ const credential = credentialOf(await jsonOf(r), now(), void 0, "sign-in");
1492
+ const profile = await fetchProfile(credential.access, fetch2);
1493
+ return { credential, profile };
1494
+ }
1495
+ async function refreshCredential(cred, fetch2, now) {
1496
+ const r = await post(
1497
+ fetch2,
1498
+ POOL_ENDPOINTS.token,
1499
+ { grant_type: "refresh_token", refresh_token: cred.refresh, client_id: POOL_ENDPOINTS.clientId },
1500
+ "refresh"
1501
+ );
1502
+ if (r.status === 400 || r.status === 401)
1503
+ throw new PoolAuthError("This account's sign-in has expired; sign in again.");
1504
+ if (!r.ok) throw new PoolUpstreamError(`refresh: Anthropic answered HTTP ${r.status}; try again in a minute`);
1505
+ return credentialOf(await jsonOf(r), now(), cred.refresh, "refresh");
1506
+ }
1507
+ async function fetchProfile(access, fetch2) {
1508
+ const r = await get(fetch2, POOL_ENDPOINTS.profile, access, "profile");
1509
+ if (r.status === 401 || r.status === 403)
1510
+ throw new PoolAuthError("Anthropic does not know this token; sign in again.");
1511
+ if (!r.ok) throw new PoolUpstreamError(`profile: Anthropic answered HTTP ${r.status}; try again in a minute`);
1512
+ const j = await jsonOf(r);
1513
+ const a = j?.account;
1514
+ if (typeof a?.uuid !== "string" || !UUID_RE.test(a.uuid) || typeof a.email !== "string" || !a.email)
1515
+ throw new PoolUpstreamError("profile: Anthropic's answer did not say whose token this is");
1516
+ const org = j?.organization?.name;
1517
+ const plan = a.subscription_type;
1518
+ return {
1519
+ account_uuid: a.uuid.toLowerCase(),
1520
+ email: a.email,
1521
+ ...typeof org === "string" && org ? { org } : {},
1522
+ ...typeof plan === "string" && plan ? { plan } : {}
1523
+ };
1524
+ }
1525
+ async function fetchUsage(access, fetch2, now) {
1526
+ const r = await get(fetch2, POOL_ENDPOINTS.usage, access, "usage");
1527
+ if (r.status === 401 || r.status === 403)
1528
+ throw new PoolAuthError("Anthropic does not know this token; sign in again.");
1529
+ if (!r.ok) throw new PoolUpstreamError(`usage: Anthropic answered HTTP ${r.status}; try again in a minute`);
1530
+ return usageLimits(await jsonOf(r), now());
1531
+ }
1532
+ var SERVICE = "Claude Code-credentials";
1533
+ function keychainService(dir, home, platform = process.platform) {
1534
+ const norm = (x) => platform === "win32" ? path5.resolve(x).toLowerCase() : path5.resolve(x);
1535
+ if (norm(dir) === norm(path5.join(home, ".claude"))) return SERVICE;
1536
+ return `${SERVICE}-${createHash2("sha256").update(dir).digest("hex").slice(0, 8)}`;
1537
+ }
1538
+ function keychainUser(env) {
1539
+ let u = env.USER;
1540
+ if (!u) {
652
1541
  try {
653
- const m = /installation_uuid:\s*"([0-9a-f-]{36})"/i.exec(
654
- readFileSync2(path4.join(d, "antigravity_state.pbtxt"), "utf8")
655
- );
656
- if (!m) continue;
657
- const base = `https://antigravity.google.com/r/${m[1]}-v2`;
658
- const conv = opts.conversation ?? "";
659
- if (!ID_RE.test(conv) || !opts.workspaces?.length) return base;
660
- const project = agMatchProject(agProjects(opts.projectsDir ?? projectsDir(opts.env)), opts.workspaces);
661
- return project ? `${base}?p=${encodeURIComponent(`c/${conv}?section=${project}`)}` : base;
1542
+ u = os2.userInfo().username;
662
1543
  } catch {
1544
+ u = "";
663
1545
  }
664
1546
  }
665
- return null;
1547
+ return /^[a-zA-Z0-9._-]+$/.test(u) ? u : "claude-code-user";
666
1548
  }
667
- function antigravityFacts(file, opts) {
668
- const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
669
- const H = parseLines(head);
670
- const T = parseLines(tail);
671
- const out = {};
672
- const url = antigravityRemoteUrl(file, opts);
673
- if (url) out.url = url;
674
- for (const j of H) {
675
- if (j.type !== "USER_INPUT" || typeof j.content !== "string") continue;
676
- const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
677
- const text = (m ? m[1] : j.content).trim();
678
- const line = (text.split("\n").find((l) => l.trim()) ?? "").trim();
679
- if (!line || /^<[a-z_-]+>/i.test(line)) continue;
680
- out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
681
- out.title_source = "first-ask";
682
- break;
1549
+ function credentialIn(text) {
1550
+ try {
1551
+ const o = JSON.parse(text).claudeAiOauth;
1552
+ if (typeof o?.accessToken !== "string" || !o.accessToken || typeof o.refreshToken !== "string" || !o.refreshToken)
1553
+ return null;
1554
+ return {
1555
+ access: o.accessToken,
1556
+ refresh: o.refreshToken,
1557
+ expires_at: typeof o.expiresAt === "number" ? o.expiresAt : 0
1558
+ };
1559
+ } catch {
1560
+ return null;
683
1561
  }
684
- const times = H.concat(T).map((j) => Date.parse(String(j.created_at ?? j.timestamp ?? ""))).filter((n) => n > 0);
685
- if (times.length) {
686
- out.started_at = Math.min(...times);
687
- out.last_at = Math.max(...times);
1562
+ }
1563
+ var EMAIL_RE2 = /^[^@\s]{1,64}@[^@\s]{1,189}$/;
1564
+ var readText = (f) => readFileSync3(f, "utf8");
1565
+ function importableLogins(env, exec, readFile = readText, platform = process.platform) {
1566
+ const out = [];
1567
+ const home = env.HOME || homeDir();
1568
+ const user = keychainUser(env);
1569
+ for (const dir of claudeDirs(env, home)) {
1570
+ let credential = null;
1571
+ let source = "file";
1572
+ if (platform === "darwin") {
1573
+ const service = keychainService(dir, home, platform);
1574
+ for (const args of [
1575
+ ["find-generic-password", "-s", service, "-a", user, "-w"],
1576
+ ["find-generic-password", "-s", service, "-w"]
1577
+ ]) {
1578
+ try {
1579
+ credential = credentialIn(exec("security", args).trim());
1580
+ } catch {
1581
+ }
1582
+ if (credential) break;
1583
+ }
1584
+ if (credential) source = "keychain";
1585
+ }
1586
+ if (!credential) {
1587
+ try {
1588
+ credential = credentialIn(readFile(path5.join(dir, ".credentials.json")));
1589
+ } catch {
1590
+ }
1591
+ }
1592
+ if (!credential) continue;
1593
+ out.push({ dir, credential, ...whoIs(dir, home, readFile), source });
688
1594
  }
689
1595
  return out;
690
1596
  }
1597
+ function whoIs(dir, home, readFile) {
1598
+ const files = [path5.join(dir, ".claude.json")];
1599
+ if (path5.resolve(dir) === path5.join(home, ".claude")) files.unshift(path5.join(home, ".claude.json"));
1600
+ for (const f of files) {
1601
+ try {
1602
+ const a = JSON.parse(readFile(f)).oauthAccount;
1603
+ if (typeof a?.accountUuid === "string" && UUID_RE.test(a.accountUuid)) {
1604
+ const email = typeof a.emailAddress === "string" && EMAIL_RE2.test(a.emailAddress) ? a.emailAddress : void 0;
1605
+ return { account_uuid: a.accountUuid.toLowerCase(), ...email ? { email } : {} };
1606
+ }
1607
+ } catch {
1608
+ }
1609
+ }
1610
+ return {};
1611
+ }
691
1612
 
692
- // src/adapters/antigravity-control.ts
693
- var antigravity_control_exports = {};
694
- __export(antigravity_control_exports, {
695
- AgyReading: () => AgyReading,
696
- agyArgs: () => agyArgs,
697
- agyEnv: () => agyEnv,
698
- agyError: () => agyError,
699
- agyLine: () => agyLine,
700
- agyModeFlags: () => agyModeFlags,
701
- findAgy: () => findAgy,
702
- reading: () => reading
1613
+ // src/adapters/acp-control.ts
1614
+ var acp_control_exports = {};
1615
+ __export(acp_control_exports, {
1616
+ ACP_AGENTS: () => ACP_AGENTS,
1617
+ ACP_HARNESSES: () => ACP_HARNESSES,
1618
+ acpAgent: () => acpAgent,
1619
+ acpHarnesses: () => acpHarnesses,
1620
+ acpOverride: () => acpOverride,
1621
+ errorWords: () => errorWords,
1622
+ findAcpAgent: () => findAcpAgent,
1623
+ permissionChoice: () => permissionChoice,
1624
+ startAcp: () => startAcp
703
1625
  });
704
- import os2 from "node:os";
705
- import path6 from "node:path";
1626
+ import { spawn as spawn2 } from "node:child_process";
1627
+ import os3 from "node:os";
1628
+ import path7 from "node:path";
706
1629
 
707
1630
  // src/adapters/headless.ts
708
1631
  import { spawn } from "node:child_process";
709
1632
  import { accessSync, constants, statSync as statSync3 } from "node:fs";
710
- import path5 from "node:path";
1633
+ import path6 from "node:path";
711
1634
  var JOB_TIMEOUT_MS = 30 * 6e4;
712
1635
  var OUT_CAP = 1024 * 1024;
713
1636
  function isExe(p) {
@@ -721,11 +1644,11 @@ function isExe(p) {
721
1644
  }
722
1645
  function findBinary(o) {
723
1646
  if (o.override && isExe(o.override)) return o.override;
724
- for (const d of String(o.env?.PATH || "").split(path5.delimiter)) {
725
- if (!d || !path5.isAbsolute(d)) continue;
726
- for (const n of o.names) if (isExe(path5.join(d, n))) return path5.join(d, n);
1647
+ for (const d of String(o.env?.PATH || "").split(path6.delimiter)) {
1648
+ if (!d || !path6.isAbsolute(d)) continue;
1649
+ for (const n of o.names) if (isExe(path6.join(d, n))) return path6.join(d, n);
727
1650
  }
728
- for (const d of o.dirs) for (const n of o.names) if (isExe(path5.join(d, n))) return path5.join(d, n);
1651
+ for (const d of o.dirs) for (const n of o.names) if (isExe(path6.join(d, n))) return path6.join(d, n);
729
1652
  return null;
730
1653
  }
731
1654
  function jobEnv(env = process.env) {
@@ -846,229 +1769,441 @@ function runFailure(label, res) {
846
1769
  return null;
847
1770
  }
848
1771
 
849
- // src/adapters/antigravity-control.ts
850
- var LABEL = "Antigravity";
851
- function findAgy(env = process.env, home = os2.homedir()) {
852
- return findBinary({
853
- override: env.SESSIONPIPE_AGY,
854
- names: process.platform === "win32" ? ["agy.exe", "agy.cmd"] : ["agy"],
1772
+ // src/adapters/acp-control.ts
1773
+ var ACP_AGENTS = [
1774
+ { harness: "gemini-cli", registry: "gemini", label: "Gemini CLI", bin: "gemini", args: ["--acp"] },
1775
+ { harness: "devin", registry: "devin", label: "Devin", bin: "devin", args: ["acp"] },
1776
+ { harness: "opencode", registry: "opencode", label: "OpenCode", bin: "opencode", args: ["acp"] },
1777
+ { harness: "cursor", registry: "cursor", label: "Cursor", bin: "cursor-agent", args: ["acp"] },
1778
+ { harness: "copilot-cli", registry: "github-copilot-cli", label: "Copilot CLI", bin: "copilot", args: ["--acp"] },
1779
+ {
1780
+ harness: "droid",
1781
+ registry: "factory-droid",
1782
+ label: "Droid",
1783
+ bin: "droid",
1784
+ args: ["exec", "--output-format", "acp-daemon"]
1785
+ },
1786
+ { harness: "goose", registry: "goose", label: "goose", bin: "goose", args: ["acp"] },
1787
+ { harness: "kilo", registry: "kilo", label: "Kilo", bin: "kilo", args: ["acp"] },
1788
+ {
1789
+ harness: "qwen-code",
1790
+ registry: "qwen-code",
1791
+ label: "Qwen Code",
1792
+ bin: "qwen",
1793
+ args: ["--acp", "--experimental-skills"]
1794
+ },
1795
+ { harness: "cline", registry: "cline", label: "Cline", bin: "cline", args: ["--acp"] },
1796
+ { harness: "auggie", registry: "auggie", label: "Auggie", bin: "auggie", args: ["--acp"] },
1797
+ { harness: "kimi", registry: "kimi", label: "Kimi CLI", bin: "kimi", args: ["acp"] },
1798
+ { harness: "amp", registry: "amp-acp", label: "Amp", bin: "amp-acp", args: [] },
1799
+ { harness: "mistral-vibe", registry: "mistral-vibe", label: "Mistral Vibe", bin: "vibe-acp", args: [] },
1800
+ { harness: "bob", registry: "bob", label: "Bob", bin: "bob", args: ["acp"] },
1801
+ { harness: "grok-build", registry: "grok-build", label: "Grok Build", bin: "grok", args: ["agent", "stdio"] },
1802
+ { harness: "qoder", registry: "qoder", label: "Qoder CLI", bin: "qodercli", args: ["--acp"] },
1803
+ { harness: "minimax-code", registry: "minimax-code", label: "MiniMax Code", bin: "mcode", args: ["acp"] },
1804
+ { harness: "codebuddy-code", registry: "codebuddy-code", label: "Codebuddy Code", bin: "codebuddy", args: ["--acp"] },
1805
+ { harness: "nova", registry: "nova", label: "Nova", bin: "nova", args: ["acp"] },
1806
+ { harness: "dimcode", registry: "dimcode", label: "DimCode", bin: "dim", args: ["acp"] },
1807
+ { harness: "dirac", registry: "dirac", label: "Dirac", bin: "dirac", args: ["--acp"] },
1808
+ { harness: "harn", registry: "harn", label: "Harn", bin: "harn", args: ["serve", "acp"] },
1809
+ { harness: "vtcode", registry: "vtcode", label: "VT Code", bin: "vtcode", args: ["acp"] },
1810
+ { harness: "stakpak", registry: "stakpak", label: "Stakpak", bin: "stakpak", args: ["acp"] },
1811
+ { harness: "crow-cli", registry: "crow-cli", label: "crow-cli", bin: "crow-cli", args: ["acp"] },
1812
+ { harness: "corust-agent", registry: "corust-agent", label: "Corust Agent", bin: "corust-agent-acp", args: [] },
1813
+ { harness: "cortex-code", registry: "cortex-code", label: "Cortex Code", bin: "cortex", args: ["acp", "serve"] },
1814
+ { harness: "kimchi", registry: "kimchi", label: "Kimchi", bin: "kimchi", args: ["--mode", "acp"] }
1815
+ ];
1816
+ var ACP_HARNESSES = ACP_AGENTS.map((a) => a.harness);
1817
+ var acpAgent = (harness) => ACP_AGENTS.find((a) => a.harness === harness);
1818
+ var acpOverride = (harness) => `SESSIONPIPE_ACP_${harness.toUpperCase().replace(/-/g, "_")}`;
1819
+ function findAcpAgent(harness, env = process.env, home = os3.homedir(), nodeDir = path7.dirname(process.execPath)) {
1820
+ const a = acpAgent(harness);
1821
+ if (!a) return null;
1822
+ const bin = findBinary({
1823
+ override: env[acpOverride(harness)],
1824
+ names: process.platform === "win32" ? [`${a.bin}.exe`, `${a.bin}.cmd`] : [a.bin],
855
1825
  env,
856
- dirs: [path6.join(home, ".local", "bin"), "/opt/homebrew/bin", "/usr/local/bin"]
1826
+ dirs: [
1827
+ nodeDir,
1828
+ path7.join(home, ".local", "bin"),
1829
+ path7.join(home, ".cargo", "bin"),
1830
+ "/opt/homebrew/bin",
1831
+ "/usr/local/bin"
1832
+ ]
857
1833
  });
1834
+ return bin ? { bin, args: a.args } : null;
858
1835
  }
859
- function agyModeFlags(mode) {
860
- return mode === "auto" ? ["--mode", "accept-edits"] : [];
861
- }
862
- function agyArgs(o) {
863
- const prompt = o.prompt.startsWith("-") ? ` ${o.prompt}` : o.prompt;
864
- return [
865
- ...o.resume ? ["--conversation", o.resume] : [],
866
- "-p",
867
- prompt,
868
- "--output-format",
869
- "stream-json",
870
- ...agyModeFlags(o.mode)
871
- ];
1836
+ function acpHarnesses(env = process.env) {
1837
+ return ACP_HARNESSES.filter((h) => findAcpAgent(h, env));
872
1838
  }
873
- var agyEnv = (env = process.env) => jobEnv(env);
874
- function agyLine(line) {
875
- let j;
876
- try {
877
- j = JSON.parse(line);
878
- } catch {
879
- return null;
1839
+ var obj = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : null;
1840
+ var str = (v) => typeof v === "string" ? v : "";
1841
+ function permissionChoice(options, mode) {
1842
+ const list = (Array.isArray(options) ? options : []).map(obj).filter((o) => !!o && !!str(o.optionId)).map((o) => ({ optionId: str(o.optionId), kind: str(o.kind) }));
1843
+ const want = mode === "auto" ? ["allow_once", "allow_always"] : ["reject_once", "reject_always"];
1844
+ for (const k of want) {
1845
+ const hit = list.find((o) => o.kind === k);
1846
+ if (hit) return hit;
880
1847
  }
881
- if (!j || typeof j !== "object") return null;
882
- const str = (v) => typeof v === "string" ? v : "";
883
- const out = {};
884
- if (j.event === "init") {
885
- if (str(j.conversation_id)) out.session = str(j.conversation_id);
886
- } else if (j.event === "step_update") {
887
- const s = j.step_update ?? {};
888
- if (str(s.conversation_id)) out.session = str(s.conversation_id);
889
- if (s.step_type === "agent_response" && str(s.text_delta))
890
- out.delta = { step: Number(s.step_index ?? 0), text: str(s.text_delta) };
891
- if (s.step_type === "error_message") {
892
- const why = str(s.text_delta) || str(s.error) || str(s.message);
893
- if (why) out.note = why;
894
- }
895
- } else if (j.event === "result") {
896
- const r = j.result ?? {};
897
- if (str(r.conversation_id)) out.session = str(r.conversation_id);
898
- out.result = {
899
- status: str(r.status),
900
- response: str(r.response),
901
- error: str(r.error),
902
- denied: Array.isArray(r.denied_actions) ? r.denied_actions : []
903
- };
904
- } else return null;
905
- return Object.keys(out).length ? out : null;
906
- }
907
- function deniedName(d) {
908
- if (typeof d === "string") return d.slice(0, 80);
909
- if (!d || typeof d !== "object") return null;
910
- const o = d;
911
- for (const k of ["tool_name", "toolName", "name", "tool", "action", "type"])
912
- if (typeof o[k] === "string" && o[k]) return o[k].slice(0, 80);
913
1848
  return null;
914
1849
  }
915
- function agyError(stderr) {
916
- const s = String(stderr || "");
917
- const i = s.lastIndexOf("AGY_ERROR:");
918
- if (i < 0) return "";
919
- const raw = s.slice(i + "AGY_ERROR:".length).trim();
920
- try {
921
- const j = JSON.parse(raw);
922
- for (const k of ["message", "error", "detail"]) if (typeof j[k] === "string" && j[k]) return j[k];
923
- } catch {
924
- }
925
- return raw.split("\n")[0].slice(0, 300);
1850
+ function errorWords(err) {
1851
+ const message = str(err.message);
1852
+ const data = str(err.data) || str(obj(err.data)?.message);
1853
+ return data && data !== message ? message ? `${message}: ${data}` : data : message;
926
1854
  }
927
- var AgyReading = class {
928
- session = null;
929
- lines = 0;
930
- buf = "";
931
- step = null;
932
- result = null;
933
- note = null;
934
- get text() {
935
- return this.buf;
936
- }
937
- feed(line) {
938
- this.lines++;
939
- const p = agyLine(line);
940
- if (!p) return false;
941
- if (p.session && !this.session) this.session = p.session;
942
- if (p.note) this.note = p.note;
943
- if (p.result) this.result = p.result;
944
- if (!p.delta) return false;
945
- if (this.step !== null && p.delta.step !== this.step && this.buf && !this.buf.endsWith("\n\n")) this.buf += "\n\n";
946
- this.step = p.delta.step;
947
- this.buf += p.delta.text;
948
- return true;
1855
+ function toolName(call, titles) {
1856
+ if (!call) return null;
1857
+ const t = str(call.title) || titles.get(str(call.toolCallId)) || str(call.name) || str(call.toolCallId);
1858
+ return t ? t.slice(0, 80) : null;
1859
+ }
1860
+ var CLIENT_INFO = { name: "sessionpipe", title: "sessionpipe control", version: "1" };
1861
+ function startAcp(o) {
1862
+ const now = o.now ?? Date.now;
1863
+ const log = o.log ?? (() => {
1864
+ });
1865
+ const label = o.label;
1866
+ let child;
1867
+ try {
1868
+ child = spawn2(o.bin, o.args, {
1869
+ cwd: o.cwd,
1870
+ env: jobEnv(o.env),
1871
+ stdio: ["pipe", "pipe", "pipe"],
1872
+ detached: process.platform !== "win32",
1873
+ windowsHide: true
1874
+ });
1875
+ } catch (e) {
1876
+ return Promise.resolve({ failed: `couldn't start ${label}: ${e.message}` });
949
1877
  }
950
- outcome(res) {
951
- const broke = runFailure(LABEL, res);
952
- if (broke) return { failed: broke };
953
- const r = this.result;
954
- if (!r) {
955
- const why = agyError(res.stderr) || this.note || "";
956
- return {
957
- failed: `Antigravity exited (${res.signal || `code ${res.code}`}) without an answer${why ? `: ${why}` : ""}`
958
- };
1878
+ const kill = (sig) => {
1879
+ try {
1880
+ if (process.platform !== "win32" && child.pid) process.kill(-child.pid, sig);
1881
+ else child.kill(sig);
1882
+ } catch {
959
1883
  }
960
- if (r.status !== "SUCCESS")
961
- return { failed: r.error || agyError(res.stderr) || this.note || "Antigravity reported an error" };
962
- const names = [...new Set(r.denied.map(deniedName).filter((n) => !!n))];
963
- const text = r.response.trim() ? r.response.trim() : this.buf.trim() || "(Antigravity finished without a written reply.)";
964
- return { reply: text + deniedNote(names, r.denied.length) };
965
- }
966
- };
967
- var reading = () => new AgyReading();
968
-
969
- // src/adapters/claude-code.ts
970
- import { execFileSync as execFileSync2 } from "node:child_process";
971
- import { createHash } from "node:crypto";
972
- import { existsSync as existsSync3, readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as statSync4 } from "node:fs";
973
- import path7 from "node:path";
974
-
975
- // src/readers/git.ts
976
- import { execFileSync } from "node:child_process";
977
- var cache = /* @__PURE__ */ new Map();
978
- function gitFacts(cwd) {
979
- if (!cwd) return {};
980
- const hit = cache.get(cwd);
981
- if (hit) return hit;
982
- const run = (args) => {
1884
+ };
1885
+ let gone = null;
1886
+ let errTail = "";
1887
+ let nextId = 1;
1888
+ const pending = /* @__PURE__ */ new Map();
1889
+ const write = (m) => {
1890
+ if (gone) return;
983
1891
  try {
984
- return execFileSync("git", ["-C", cwd, ...args], {
985
- encoding: "utf8",
986
- timeout: 1500,
987
- stdio: ["ignore", "pipe", "ignore"]
988
- }).trim();
1892
+ child.stdin?.write(`${JSON.stringify(m)}
1893
+ `);
989
1894
  } catch {
990
- return "";
991
1895
  }
992
1896
  };
993
- const out = {};
994
- const repo = run(["remote", "get-url", "origin"]);
995
- if (repo) out.repo = stripCredentials(repo);
996
- const branch = run(["rev-parse", "--abbrev-ref", "HEAD"]);
997
- if (branch && branch !== "HEAD") out.branch = branch;
998
- cache.set(cwd, out);
999
- return out;
1000
- }
1001
- function stripCredentials(url) {
1002
- const ssh = /^(?:ssh:\/\/)?git@([^:/]+)[:/](.+?)(?:\.git)?$/.exec(url);
1003
- if (ssh) return `${ssh[1]}/${ssh[2]}`;
1004
- return url.replace(/\/\/[^/@]*@/, "//").replace(/^https?:\/\//, "").replace(/\.git$/, "");
1005
- }
1897
+ const request = (method, params, timeoutMs) => new Promise((resolve, reject) => {
1898
+ if (gone) return reject(new Error(gone));
1899
+ const id = nextId++;
1900
+ const t = setTimeout(() => {
1901
+ pending.delete(id);
1902
+ reject(new Error(`${label} didn't answer ${method} in ${Math.round(timeoutMs / 1e3)} s`));
1903
+ }, timeoutMs);
1904
+ t.unref();
1905
+ pending.set(id, {
1906
+ resolve: (r) => {
1907
+ clearTimeout(t);
1908
+ resolve(r);
1909
+ },
1910
+ reject: (e) => {
1911
+ clearTimeout(t);
1912
+ reject(e);
1913
+ }
1914
+ });
1915
+ write({ jsonrpc: "2.0", id, method, params });
1916
+ });
1917
+ const notify = (method, params) => write({ jsonrpc: "2.0", method, params });
1918
+ let turn = null;
1919
+ const titles = /* @__PURE__ */ new Map();
1920
+ const onUpdate = (u) => {
1921
+ if (!u) return;
1922
+ const kind = str(u.sessionUpdate);
1923
+ if (kind === "tool_call" && str(u.toolCallId) && str(u.title)) {
1924
+ if (titles.size >= 200) titles.clear();
1925
+ titles.set(str(u.toolCallId), str(u.title));
1926
+ return;
1927
+ }
1928
+ if (kind !== "agent_message_chunk" || !turn) return;
1929
+ const c = obj(u.content);
1930
+ if (c?.type !== "text" || !str(c.text)) return;
1931
+ const mid = str(u.messageId) || null;
1932
+ if (turn.message !== void 0 && mid !== turn.message && turn.text.trim())
1933
+ turn.text = `${turn.text.trimEnd()}
1006
1934
 
1007
- // src/adapters/claude-code.ts
1008
- var NAME2 = "claude-code";
1009
- var CLAUDE_EVENTS = [
1010
- "SessionStart",
1011
- "UserPromptSubmit",
1012
- "PreToolUse",
1013
- "PostToolUse",
1014
- "PostToolUseFailure",
1015
- "PermissionRequest",
1016
- "Notification",
1017
- "Stop",
1018
- "SubagentStart",
1019
- "SubagentStop",
1020
- "PreCompact",
1021
- "PostCompact",
1022
- "SessionEnd",
1023
- "StopFailure"
1024
- ];
1025
- var LEAN_SKIP = /* @__PURE__ */ new Set(["PreToolUse", "PostToolUseFailure"]);
1026
- var eventsFor = (lean) => lean ? CLAUDE_EVENTS.filter((e) => !LEAN_SKIP.has(e)) : [...CLAUDE_EVENTS];
1027
- var OPTS = {
1028
- matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0,
1029
- timeout: 5,
1030
- // The hook waits up to 125 s for a verified control answer, and only when control
1031
- // is paired on this machine; everywhere else it exits in milliseconds as before.
1032
- timeoutFor: (e) => e === "PermissionRequest" ? 130 : void 0
1033
- };
1034
- var DESKTOP_ID = /^local_[A-Za-z0-9-]{1,64}$/;
1035
- function claudeDirs(env = process.env, home = env.HOME || homeDir()) {
1036
- const out = [];
1037
- const add = (d) => {
1038
- if (!d) return;
1039
- const r = path7.resolve(d);
1040
- if (!out.includes(r)) out.push(r);
1935
+ `;
1936
+ turn.message = mid;
1937
+ turn.text += str(c.text);
1938
+ o.onText?.(turn.text);
1939
+ };
1940
+ const onRequest = (id, method, params) => {
1941
+ if (method !== "session/request_permission") {
1942
+ write({ jsonrpc: "2.0", id, error: { code: -32601, message: `Method not found: ${method}` } });
1943
+ return;
1944
+ }
1945
+ const pick = permissionChoice(params?.options, o.mode);
1946
+ if (!pick?.kind.startsWith("allow")) {
1947
+ const name = toolName(obj(params?.toolCall), titles);
1948
+ if (turn) {
1949
+ turn.count++;
1950
+ if (name && !turn.denied.includes(name) && turn.denied.length < 20) turn.denied.push(name);
1951
+ }
1952
+ }
1953
+ write({
1954
+ jsonrpc: "2.0",
1955
+ id,
1956
+ result: { outcome: pick ? { outcome: "selected", optionId: pick.optionId } : { outcome: "cancelled" } }
1957
+ });
1041
1958
  };
1042
- add(path7.join(home, ".claude"));
1043
- add(env.CLAUDE_CONFIG_DIR);
1044
- try {
1045
- for (const e of readdirSync3(home, { withFileTypes: true })) {
1046
- if (!e.isDirectory() || !/^\.claude[-_.]/.test(e.name)) continue;
1047
- const d = path7.join(home, e.name);
1048
- if (existsSync3(path7.join(d, "projects")) || existsSync3(path7.join(d, "settings.json"))) add(d);
1959
+ const onLine = (line) => {
1960
+ let m;
1961
+ try {
1962
+ m = obj(JSON.parse(line));
1963
+ } catch {
1964
+ return;
1049
1965
  }
1050
- } catch {
1966
+ if (!m) return;
1967
+ const method = str(m.method);
1968
+ if (method) {
1969
+ if (m.id !== void 0) onRequest(m.id, method, obj(m.params));
1970
+ else if (method === "session/update") onUpdate(obj(obj(m.params)?.update));
1971
+ return;
1972
+ }
1973
+ const p = typeof m.id === "number" ? pending.get(m.id) : void 0;
1974
+ if (!p) return;
1975
+ pending.delete(m.id);
1976
+ const err = obj(m.error);
1977
+ if (err) p.reject(new Error(errorWords(err) || `${label} returned error ${String(err.code ?? "?")}`));
1978
+ else p.resolve(m.result);
1979
+ };
1980
+ let partial = "";
1981
+ child.stdout?.on("data", (b) => {
1982
+ const lines = (partial + b.toString("utf8")).split("\n");
1983
+ partial = lines.pop();
1984
+ for (const l of lines) if (l.trim()) onLine(l);
1985
+ if (partial.length > 4 * 1024 * 1024) partial = "";
1986
+ });
1987
+ child.stderr?.on("data", (b) => {
1988
+ errTail = (errTail + b.toString("utf8")).slice(-4e3);
1989
+ });
1990
+ const left = (why) => {
1991
+ if (gone) return;
1992
+ gone = why;
1993
+ for (const p of pending.values()) p.reject(new Error(why));
1994
+ pending.clear();
1995
+ };
1996
+ child.on("error", (e) => left(`couldn't start ${label}: ${e.message}`));
1997
+ const exited = (code, signal) => {
1998
+ const tail = errTail.trim().split("\n").slice(-3).join(" ").trim();
1999
+ left(`${label} exited (${signal || `code ${code}`})${tail ? `: ${tail}` : ""}`);
2000
+ };
2001
+ child.on("exit", exited);
2002
+ child.on("close", exited);
2003
+ let closed = false;
2004
+ const close = () => {
2005
+ if (closed) return;
2006
+ closed = true;
2007
+ try {
2008
+ child.stdin?.end();
2009
+ } catch {
2010
+ }
2011
+ const grace = o.closeGraceMs ?? 2e3;
2012
+ const t = setTimeout(() => kill("SIGTERM"), grace);
2013
+ t.unref();
2014
+ const k = setTimeout(() => kill("SIGKILL"), grace * 6);
2015
+ k.unref();
2016
+ child.once("exit", () => {
2017
+ clearTimeout(t);
2018
+ clearTimeout(k);
2019
+ });
2020
+ };
2021
+ const open = o.openTimeoutMs ?? 6e4;
2022
+ function held(session) {
2023
+ let busy = false;
2024
+ let lastUsed = now();
2025
+ return {
2026
+ session,
2027
+ mode: "api",
2028
+ get busy() {
2029
+ return busy;
2030
+ },
2031
+ get lastUsed() {
2032
+ return lastUsed;
2033
+ },
2034
+ get closed() {
2035
+ return closed || !!gone;
2036
+ },
2037
+ async send(text) {
2038
+ if (closed) return { reply: "", error: `${label} was closed` };
2039
+ busy = true;
2040
+ lastUsed = now();
2041
+ const t = { text: "", denied: [], count: 0 };
2042
+ turn = t;
2043
+ const limit = o.timeoutMs ?? JOB_TIMEOUT_MS;
2044
+ let timedOut = false;
2045
+ const timer = setTimeout(() => {
2046
+ timedOut = true;
2047
+ notify("session/cancel", { sessionId: session });
2048
+ setTimeout(close, 5e3).unref();
2049
+ }, limit);
2050
+ timer.unref();
2051
+ try {
2052
+ const res = obj(
2053
+ await request("session/prompt", { sessionId: session, prompt: [{ type: "text", text }] }, limit + 1e4)
2054
+ );
2055
+ const stop = str(res?.stopReason);
2056
+ const words = t.text.trim() || `(${label} finished without a written reply.)`;
2057
+ const refused = deniedNote(t.denied, t.count);
2058
+ if (stop === "end_turn" || stop === "max_tokens") return { reply: words + refused };
2059
+ if (stop === "refusal") return { reply: `${words}
2060
+
2061
+ (${label} refused the request.)${refused}` };
2062
+ if (stop === "cancelled")
2063
+ return timedOut ? {
2064
+ reply: "",
2065
+ error: `${label} was still working after ${Math.round(limit / 6e4)} minutes, so it was stopped`
2066
+ } : { reply: `${words}
2067
+
2068
+ (The turn was cancelled.)${refused}` };
2069
+ return { reply: "", error: `${label} ended the turn with ${stop || "no stop reason"}` };
2070
+ } catch (e) {
2071
+ return { reply: "", error: e.message };
2072
+ } finally {
2073
+ clearTimeout(timer);
2074
+ turn = null;
2075
+ busy = false;
2076
+ lastUsed = now();
2077
+ }
2078
+ },
2079
+ async interrupt() {
2080
+ notify("session/cancel", { sessionId: session });
2081
+ },
2082
+ close
2083
+ };
1051
2084
  }
1052
- return out.filter((d, i) => i === 0 || existsSync3(d));
2085
+ return (async () => {
2086
+ let signIn = "";
2087
+ try {
2088
+ const init = obj(
2089
+ await request(
2090
+ "initialize",
2091
+ {
2092
+ protocolVersion: 1,
2093
+ clientCapabilities: { fs: { readTextFile: false, writeTextFile: false }, terminal: false },
2094
+ clientInfo: CLIENT_INFO
2095
+ },
2096
+ open
2097
+ )
2098
+ );
2099
+ const caps = obj(init?.agentCapabilities);
2100
+ const info = obj(init?.agentInfo);
2101
+ log(
2102
+ `${label}: ${str(info?.name) || "agent"} ${str(info?.version)} (ACP ${String(init?.protocolVersion ?? "?")})`
2103
+ );
2104
+ const methods = Array.isArray(init?.authMethods) ? init.authMethods.map(obj) : [];
2105
+ if (methods.length) {
2106
+ const how = str(methods[0]?.description) || str(methods[0]?.name);
2107
+ signIn = ` (if ${label} wants a sign-in, do it at the machine's terminal${how ? `: ${how}` : ""})`;
2108
+ }
2109
+ if (o.load) {
2110
+ if (!caps?.loadSession) {
2111
+ close();
2112
+ return { unsupported: `${label} can't reopen a session once it has been closed (no session/load)` };
2113
+ }
2114
+ await request("session/load", { sessionId: o.load, cwd: o.cwd, mcpServers: [] }, open);
2115
+ return { held: held(o.load) };
2116
+ }
2117
+ const res = obj(await request("session/new", { cwd: o.cwd, mcpServers: [] }, open));
2118
+ const session = str(res?.sessionId);
2119
+ if (!session) throw new Error(`${label} opened a session without naming it`);
2120
+ return { held: held(session) };
2121
+ } catch (e) {
2122
+ close();
2123
+ return { failed: `${e.message}${signIn}` };
2124
+ }
2125
+ })();
1053
2126
  }
1054
- var settingsIn = (dir) => path7.join(dir, "settings.json");
1055
- var configDirOf = (transcript) => path7.dirname(path7.dirname(path7.dirname(transcript)));
2127
+
2128
+ // src/adapters/antigravity.ts
2129
+ import {
2130
+ existsSync as existsSync3,
2131
+ mkdirSync as mkdirSync2,
2132
+ readdirSync as readdirSync3,
2133
+ readFileSync as readFileSync4,
2134
+ realpathSync,
2135
+ statSync as statSync4,
2136
+ unlinkSync as unlinkSync2,
2137
+ writeFileSync as writeFileSync3
2138
+ } from "node:fs";
2139
+ import path8 from "node:path";
2140
+ var NAME2 = "antigravity";
2141
+ var HOOK_NAME = "sessionpipe";
2142
+ var AG_EVENTS = ["PreInvocation", "PostInvocation", "PostToolUse", "Stop"];
2143
+ 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';
2144
+ var antigravityAnswer = (event) => event === "PreToolUse" ? AG_STALE_PRETOOL : "{}\n";
2145
+ var gemini = (env) => env.GEMINI_CLI_HOME || path8.join(homeDir(), ".gemini");
2146
+ var hooksFile = (env) => path8.join(gemini(env), "config", "hooks.json");
2147
+ var projectsDir = (env) => path8.join(gemini(env), "config", "projects");
2148
+ var dataDirs = (env) => ["antigravity", "antigravity-cli", "antigravity-ide"].map((d) => path8.join(gemini(env), d));
2149
+ var ID_RE2 = /^[A-Za-z0-9_-]{1,128}$/;
1056
2150
  var validTool2 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
1057
- var endReason = (r) => r === "clear" || r === "logout" || r === "exit" ? r : r === "prompt_input_exit" ? "exit" : "other";
1058
- var notifKind = (t) => t === "permission_prompt" ? "permission" : t === "idle_prompt" ? "idle" : t === "elicitation_dialog" ? "elicitation" : "question";
1059
- var claudeCode = {
2151
+ function entry(cmd) {
2152
+ const handler = (e) => ({ type: "command", command: cmd([NAME2, e]).command, timeout: 5 });
2153
+ const out = { enabled: true };
2154
+ for (const e of AG_EVENTS) out[e] = /Tool/.test(e) ? [{ matcher: "*", hooks: [handler(e)] }] : [handler(e)];
2155
+ return out;
2156
+ }
2157
+ var antigravity = {
1060
2158
  name: NAME2,
1061
- events: CLAUDE_EVENTS,
1062
- detect: (env = process.env) => claudeDirs(env).some((d) => existsSync3(d)),
1063
- configFiles: (env = process.env) => claudeDirs(env).map(settingsIn),
1064
- install: (cmd, env = process.env, opts = {}) => claudeDirs(env).map((d) => installClaudeShaped(settingsIn(d), NAME2, eventsFor(opts.lean), cmd, OPTS)),
1065
- uninstall: (env = process.env, opts = {}) => claudeDirs(env).map(
1066
- (d) => uninstallClaudeShaped(settingsIn(d), CLAUDE_EVENTS, { created: opts.created?.includes(settingsIn(d)) })
1067
- ),
1068
- installed: (cmd, env = process.env, opts = {}) => claudeDirs(env).map((d) => ({
1069
- file: settingsIn(d),
1070
- state: installedClaudeShaped(settingsIn(d), NAME2, eventsFor(opts.lean), cmd, OPTS)
1071
- })),
2159
+ events: AG_EVENTS,
2160
+ detect: (env = process.env) => existsSync3(path8.join(gemini(env), "config")) || dataDirs(env).some((d) => existsSync3(d)),
2161
+ configFiles: (env = process.env) => [hooksFile(env)],
2162
+ install(cmd, env = process.env) {
2163
+ const file = hooksFile(env);
2164
+ const f = readJsonFile(file);
2165
+ if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
2166
+ const hooks = f.value;
2167
+ const e = entry(cmd);
2168
+ if (JSON.stringify(hooks[HOOK_NAME]) === JSON.stringify(e)) return [{ file, changed: false }];
2169
+ hooks[HOOK_NAME] = e;
2170
+ mkdirSync2(path8.dirname(file), { recursive: true });
2171
+ writeFileSync3(file, formatJson(hooks, f));
2172
+ return [
2173
+ {
2174
+ file,
2175
+ changed: true,
2176
+ created: !f.existed,
2177
+ note: "hooks.json is read when a conversation starts; open ones report after a restart"
2178
+ }
2179
+ ];
2180
+ },
2181
+ uninstall(env = process.env, opts = {}) {
2182
+ const file = hooksFile(env);
2183
+ if (!existsSync3(file)) return [{ file, changed: false }];
2184
+ const f = readJsonFile(file);
2185
+ if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
2186
+ const hooks = f.value;
2187
+ if (!(HOOK_NAME in hooks)) return [{ file, changed: false }];
2188
+ delete hooks[HOOK_NAME];
2189
+ if (opts.created?.includes(file) && !Object.keys(hooks).length) {
2190
+ unlinkSync2(file);
2191
+ return [{ file, changed: true, note: "removed (sessionpipe created it)" }];
2192
+ }
2193
+ writeFileSync3(file, formatJson(hooks, f));
2194
+ return [{ file, changed: true }];
2195
+ },
2196
+ installed(cmd, env = process.env) {
2197
+ const file = hooksFile(env);
2198
+ const f = readJsonFile(file);
2199
+ const have = f.ok ? f.value[HOOK_NAME] : void 0;
2200
+ return [
2201
+ {
2202
+ file,
2203
+ state: !have ? "missing" : JSON.stringify(have) === JSON.stringify(entry(cmd)) ? "current" : "stale"
2204
+ }
2205
+ ];
2206
+ },
1072
2207
  fromHook(input) {
1073
2208
  const event = input.argv[1] ?? "";
1074
2209
  let s = {};
@@ -1077,170 +2212,87 @@ var claudeCode = {
1077
2212
  if (j && typeof j === "object" && !Array.isArray(j)) s = j;
1078
2213
  } catch {
1079
2214
  }
1080
- const id = s.session_id ?? s.sessionId;
1081
- if (!id) return null;
1082
- if (notClaudeCode(String(id), s)) return null;
1083
- const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
1084
- if (typeof s.model === "string") session.model = s.model;
1085
- if (typeof s.agent_id === "string" && event.startsWith("Subagent") === false && s.agent_id)
1086
- session.parent_id = null;
1087
- const turn_id = typeof s.prompt_id === "string" ? s.prompt_id : void 0;
1088
- const tool = validTool2(s.tool_name) ? s.tool_name : void 0;
1089
- const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
1090
- const ms = typeof s.duration_ms === "number" ? Math.round(s.duration_ms) : void 0;
1091
- const agent_id = typeof s.agent_id === "string" ? s.agent_id : void 0;
1092
- const permissionId = () => `perm-${createHash("sha256").update(`${turn_id ?? ""}|${tool ?? ""}|${JSON.stringify(s.tool_input ?? null)}`).digest("hex").slice(0, 16)}`;
2215
+ const id = s.conversationId ?? s.session_id ?? s.sessionId;
2216
+ const stdout = antigravityAnswer(event);
2217
+ if (!id) return { session: { id: "" }, events: [], stdout };
2218
+ const workspaces = agWorkspaces(s.workspacePaths);
2219
+ const session = { id: String(id), cwd: workspaces[0] ?? input.cwd };
2220
+ if (typeof s.modelName === "string" && s.modelName !== "auto") session.model = s.modelName;
2221
+ const toolCall = s.toolCall;
2222
+ const tool = validTool2(toolCall?.name) ? toolCall?.name : validTool2(s.toolName) ? s.toolName : void 0;
1093
2223
  const ev = (type, data) => ({ type, data: prune2(data), harnessEvent: event });
1094
2224
  const events = [];
1095
2225
  switch (event) {
1096
- case "SessionStart":
1097
- events.push(ev("session.started", { source: pickSource(s.source ?? s.trigger) }));
2226
+ case "PreInvocation":
2227
+ if (Number(s.invocationNum) === 0 || s.invocationNum === void 0) {
2228
+ events.push(ev("session.started", { source: "unknown" }));
2229
+ events.push(ev("turn.started", {}));
2230
+ } else events.push(ev("session.heartbeat", {}));
1098
2231
  break;
1099
- case "UserPromptSubmit":
1100
- events.push(
1101
- ev("turn.started", {
1102
- turn_id,
1103
- prompt_chars: typeof s.prompt === "string" ? [...s.prompt].length : void 0
1104
- })
1105
- );
2232
+ case "PostInvocation":
1106
2233
  break;
1107
2234
  case "PreToolUse":
1108
- if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, agent_id, input: s.tool_input }));
2235
+ if (tool) events.push(ev("tool.started", { tool, input: toolCall?.args }));
1109
2236
  break;
1110
2237
  case "PostToolUse":
1111
- if (tool)
1112
- events.push(
1113
- ev("tool.ended", {
1114
- tool,
1115
- call_id,
1116
- turn_id,
1117
- agent_id,
1118
- ms,
1119
- ok: true,
1120
- input: s.tool_input,
1121
- output: s.tool_response
1122
- })
1123
- );
1124
- break;
1125
- case "PostToolUseFailure":
1126
- if (tool)
2238
+ if (!tool) events.push(ev("session.heartbeat", {}));
2239
+ else
1127
2240
  events.push(
1128
2241
  ev("tool.ended", {
1129
2242
  tool,
1130
- call_id,
1131
- turn_id,
1132
- agent_id,
1133
- ms,
1134
- ok: false,
1135
- error: errText(s.error ?? s.tool_response),
1136
- input: s.tool_input,
1137
- output: s.tool_response
2243
+ ok: !s.error,
2244
+ error: typeof s.error === "string" ? s.error.slice(0, 2e3) : void 0,
2245
+ input: toolCall?.args
1138
2246
  })
1139
2247
  );
1140
2248
  break;
1141
- case "PermissionRequest":
1142
- events.push(
1143
- ev("attention.needed", {
1144
- attention_id: call_id ?? permissionId(),
1145
- kind: "permission",
1146
- tool,
1147
- message: tool ? `${tool}${describe(s.tool_input)}` : void 0
1148
- })
1149
- );
1150
- break;
1151
- case "Notification":
1152
- events.push(
1153
- ev("attention.needed", {
1154
- attention_id: `notif-${Date.now()}`,
1155
- kind: notifKind(s.notification_type),
1156
- message: typeof s.message === "string" ? s.message.slice(0, 200) : void 0
1157
- })
1158
- );
1159
- break;
1160
2249
  case "Stop":
1161
- events.push(ev("turn.ended", { turn_id, reason: "stop" }));
1162
- break;
1163
- case "StopFailure":
1164
- events.push(
1165
- ev("turn.ended", {
1166
- turn_id,
1167
- reason: "error",
1168
- error: typeof s.error === "string" ? s.error.slice(0, 200) : void 0
1169
- })
1170
- );
1171
- break;
1172
- case "SubagentStart":
1173
- events.push(
1174
- ev("subagent.started", {
1175
- agent_id: String(s.agent_id ?? ""),
1176
- agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
1177
- })
1178
- );
1179
- break;
1180
- case "SubagentStop":
1181
- events.push(
1182
- ev("subagent.ended", {
1183
- agent_id: String(s.agent_id ?? ""),
1184
- agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
1185
- })
1186
- );
1187
- break;
1188
- case "PreCompact":
1189
- events.push(ev("context.compacted", { trigger: s.trigger === "manual" ? "manual" : "auto" }));
1190
- break;
1191
- case "PostCompact":
1192
- break;
1193
- // PreCompact already said it; a second event would double-count
1194
- case "SessionEnd":
1195
- events.push(ev("session.ended", { reason: endReason(s.reason) }));
2250
+ events.push(ev("turn.ended", { reason: s.error ? "error" : "stop" }));
1196
2251
  break;
1197
2252
  default:
1198
- return null;
2253
+ return { session, events: [], stdout };
1199
2254
  }
1200
- const hints = {};
1201
- if (event === "UserPromptSubmit" && typeof s.prompt === "string") hints.prompt = s.prompt;
1202
- const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
1203
- if (host && DESKTOP_ID.test(host)) hints.desktop_id = host;
1204
- if (event !== "PreToolUse" && event !== "PostToolUse") hints.claude_pid = claudePid(input.env);
1205
- const r = { session, events, hints };
1206
- if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
2255
+ const r = { session, events, stdout, hints: { workspaces } };
2256
+ if (typeof s.transcriptPath === "string") r.transcript = s.transcriptPath;
1207
2257
  return r;
1208
2258
  },
1209
2259
  readTranscript(file, fromLine) {
1210
2260
  let text = "";
1211
2261
  try {
1212
- text = readFileSync3(file, "utf8");
2262
+ text = readFileSync4(file, "utf8");
1213
2263
  } catch {
1214
2264
  return { turns: [], line: fromLine };
1215
2265
  }
1216
- return readClaude(text.split("\n"), fromLine);
2266
+ return readAntigravity(text.split("\n"), fromLine);
1217
2267
  },
1218
- facts(session, transcript, hints, state) {
1219
- const out = {};
1220
- if (transcript) Object.assign(out, claudeFacts(transcript, session.id, hints, state));
1221
- const cwd = out.cwd ?? session.cwd;
1222
- Object.assign(out, gitFacts(cwd), out.branch ? { branch: out.branch } : {});
1223
- if (!out.url && typeof hints.desktop_id === "string") out.url = `claude://code/continue/${hints.desktop_id}`;
1224
- return out;
2268
+ facts(session, transcript, hints) {
2269
+ if (!transcript) return {};
2270
+ return antigravityFacts(transcript, {
2271
+ conversation: session.id,
2272
+ workspaces: hints.workspaces ?? (session.cwd ? [session.cwd] : []),
2273
+ env: process.env
2274
+ });
1225
2275
  },
1226
2276
  backfill(sinceMs, env = process.env) {
1227
2277
  const rows2 = [];
1228
- const files = [];
1229
- for (const d of claudeDirs(env))
1230
- files.push(...recentFiles(path7.join(d, "projects"), 1, sinceMs, (n) => /^[0-9a-f-]{36}\.jsonl$/.test(n)));
1231
- const noState = { get: () => ({}), save: () => {
1232
- } };
1233
- for (const f of files) {
1234
- const id = path7.basename(f, ".jsonl");
1235
- const facts = claudeFacts(f, id, {}, noState);
1236
- if (!facts.started_at || !facts.last_at) continue;
1237
- const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
1238
- const cwd = rest.cwd;
1239
- rows2.push({
1240
- session: { id, ...rest, ...gitFacts(cwd), ...rest.branch ? { branch: rest.branch } : {} },
1241
- started_at,
1242
- last_at
1243
- });
2278
+ for (const data of dataDirs(env)) {
2279
+ let ids = [];
2280
+ try {
2281
+ ids = readdirSync3(path8.join(data, "brain")).filter((n) => /^[0-9a-f-]{36}$/i.test(n));
2282
+ } catch {
2283
+ }
2284
+ for (const id of ids) {
2285
+ const f = path8.join(data, "brain", id, ".system_generated", "logs", "transcript.jsonl");
2286
+ try {
2287
+ if (statSync4(f).mtimeMs < sinceMs) continue;
2288
+ } catch {
2289
+ continue;
2290
+ }
2291
+ const facts = antigravityFacts(f, { conversation: id, workspaces: [], env });
2292
+ if (!facts.started_at || !facts.last_at) continue;
2293
+ const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
2294
+ rows2.push({ session: { id, ...rest }, started_at, last_at });
2295
+ }
1244
2296
  }
1245
2297
  return rows2;
1246
2298
  }
@@ -1249,307 +2301,266 @@ function prune2(o) {
1249
2301
  for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
1250
2302
  return o;
1251
2303
  }
1252
- var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1253
- function notClaudeCode(id, s) {
1254
- return typeof s.transcript_path !== "string" && !UUID.test(id);
1255
- }
1256
- function pickSource(v) {
1257
- return typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
2304
+ function agPath(p) {
2305
+ if (typeof p !== "string" || !p) return null;
2306
+ let s = p;
2307
+ if (/^file:/i.test(s)) {
2308
+ try {
2309
+ s = decodeURIComponent(new URL(s).pathname);
2310
+ } catch {
2311
+ return null;
2312
+ }
2313
+ if (process.platform === "win32" && /^\/[A-Za-z]:/.test(s)) s = s.slice(1);
2314
+ } else if (/^[a-z][a-z0-9+.-]*:\/\//i.test(s)) return null;
2315
+ if (!path8.isAbsolute(s)) return null;
2316
+ return path8.resolve(s);
1258
2317
  }
1259
- function errText(v) {
1260
- if (typeof v === "string") return v.slice(0, 2e3);
1261
- if (v && typeof v === "object") {
1262
- const e = v;
1263
- if (typeof e.error === "string") return e.error.slice(0, 2e3);
1264
- if (typeof e.message === "string") return e.message.slice(0, 2e3);
2318
+ var agWorkspaces = (paths) => (Array.isArray(paths) ? paths : []).map(agPath).filter((p) => !!p).filter((p, i, a) => a.indexOf(p) === i);
2319
+ function agVariants(p) {
2320
+ const out = [p];
2321
+ try {
2322
+ const r = realpathSync.native(p);
2323
+ if (r !== p) out.push(r);
2324
+ } catch {
1265
2325
  }
1266
- return void 0;
1267
- }
1268
- function describe(input) {
1269
- if (!input || typeof input !== "object") return "";
1270
- const i = input;
1271
- const v = i.command ?? i.file_path ?? i.path ?? i.url ?? i.pattern;
1272
- return typeof v === "string" ? ` ${v.slice(0, 120)}` : "";
1273
- }
1274
- var NAME_RANK = {
1275
- user: "custom",
1276
- auto: "harness",
1277
- hook: "harness",
1278
- peer: "harness"
1279
- };
1280
- function registryEntry(j) {
1281
- if (!j || typeof j !== "object") return null;
1282
- const r = j;
1283
- const source = r.nameSource ? NAME_RANK[r.nameSource] : void 0;
1284
- return {
1285
- sessionId: r.sessionId ? String(r.sessionId) : null,
1286
- name: source && typeof r.name === "string" && r.name.trim() ? r.name.trim() : null,
1287
- name_source: source ?? null,
1288
- bridge: typeof r.bridgeSessionId === "string" ? r.bridgeSessionId : null
1289
- };
2326
+ return out;
1290
2327
  }
1291
- var liveCache = /* @__PURE__ */ new Map();
1292
- function claudeLive(dir) {
1293
- const hit = liveCache.get(dir);
1294
- if (hit) return hit;
1295
- const map = /* @__PURE__ */ new Map();
2328
+ function agProjects(dir) {
2329
+ const out = [];
1296
2330
  let names = [];
1297
2331
  try {
1298
- names = readdirSync3(path7.join(dir, "sessions")).filter((n) => n.endsWith(".json"));
2332
+ names = readdirSync3(dir).filter((n) => n.endsWith(".json"));
1299
2333
  } catch {
2334
+ return out;
1300
2335
  }
1301
2336
  for (const n of names) {
2337
+ const id = n.slice(0, -5);
2338
+ if (!ID_RE2.test(id)) continue;
2339
+ let j;
1302
2340
  try {
1303
- const e = registryEntry(JSON.parse(readFileSync3(path7.join(dir, "sessions", n), "utf8")));
1304
- if (e?.sessionId) map.set(e.sessionId, e);
2341
+ j = JSON.parse(readFileSync4(path8.join(dir, n), "utf8"));
1305
2342
  } catch {
2343
+ continue;
1306
2344
  }
1307
- }
1308
- liveCache.set(dir, map);
1309
- return map;
1310
- }
1311
- function claudeLiveByPid(dir, pid) {
1312
- if (!pid) return null;
1313
- try {
1314
- return registryEntry(JSON.parse(readFileSync3(path7.join(dir, "sessions", `${pid}.json`), "utf8")));
1315
- } catch {
1316
- return null;
1317
- }
1318
- }
1319
- function claudePid(env = process.env) {
1320
- try {
1321
- const dirs = claudeDirs(env);
1322
- const has = (pid2) => dirs.some((d) => existsSync3(path7.join(d, "sessions", `${pid2}.json`)));
1323
- let pid = process.ppid;
1324
- if (has(pid)) return pid;
1325
- const parent = parentOf();
1326
- for (let hop = 0; hop < 6 && pid > 1; hop++) {
1327
- pid = parent(pid);
1328
- if (pid > 1 && has(pid)) return pid;
2345
+ const folders = [];
2346
+ for (const r of j?.projectResources?.resources ?? []) {
2347
+ const f = agPath(r?.folderUri ?? r?.gitFolder?.folderUri);
2348
+ if (f) folders.push(...agVariants(f));
1329
2349
  }
1330
- } catch {
2350
+ if (folders.length) out.push({ id, folders });
1331
2351
  }
1332
- return null;
2352
+ return out;
1333
2353
  }
1334
- function parentOf() {
1335
- if (process.platform === "linux") {
1336
- return (pid) => {
1337
- try {
1338
- const st = readFileSync3(`/proc/${pid}/stat`, "utf8");
1339
- return Number(st.slice(st.lastIndexOf(")") + 2).split(" ")[1]) || 0;
1340
- } catch {
1341
- return 0;
1342
- }
1343
- };
2354
+ function agMatchProject(projects, workspaces) {
2355
+ const ws = [];
2356
+ for (const w of workspaces) {
2357
+ const p = agPath(w);
2358
+ if (p) ws.push(...agVariants(p));
1344
2359
  }
1345
- const table = /* @__PURE__ */ new Map();
1346
- try {
1347
- const out = execFileSync2("ps", ["-axo", "pid=,ppid="], {
1348
- encoding: "utf8",
1349
- timeout: 2e3,
1350
- maxBuffer: 8 * 1024 * 1024,
1351
- stdio: ["ignore", "pipe", "ignore"]
1352
- });
1353
- for (const l of out.split("\n")) {
1354
- const m = /^\s*(\d+)\s+(\d+)/.exec(l);
1355
- if (m) table.set(Number(m[1]), Number(m[2]));
2360
+ let best = null;
2361
+ let bestLen = -1;
2362
+ for (const pr of projects)
2363
+ for (const f of pr.folders) {
2364
+ const within = ws.some((w) => w === f || w.startsWith(f.endsWith(path8.sep) ? f : f + path8.sep));
2365
+ if (within && f.length > bestLen) {
2366
+ best = pr.id;
2367
+ bestLen = f.length;
2368
+ }
1356
2369
  }
1357
- } catch {
1358
- }
1359
- return (pid) => table.get(pid) || 0;
2370
+ return best;
1360
2371
  }
1361
- var EMAIL_RE = /^[^@\s]{1,64}@[^@\s]{1,189}$/;
1362
- function claudeLogin(dir, home = homeDir()) {
1363
- const files = [path7.join(dir, ".claude.json")];
1364
- if (path7.resolve(dir) === path7.join(home, ".claude")) files.unshift(path7.join(home, ".claude.json"));
1365
- for (const f of files) {
2372
+ function antigravityRemoteUrl(transcript, opts) {
2373
+ const i = transcript.indexOf(`${path8.sep}brain${path8.sep}`);
2374
+ const dirs = (i > 0 ? [transcript.slice(0, i)] : []).concat(dataDirs(opts.env));
2375
+ for (const d of dirs) {
1366
2376
  try {
1367
- const a = JSON.parse(readFileSync3(f, "utf8")).oauthAccount;
1368
- if (typeof a?.accountUuid === "string" && a.accountUuid) {
1369
- const email = typeof a.emailAddress === "string" && EMAIL_RE.test(a.emailAddress) ? a.emailAddress : void 0;
1370
- return { id: a.accountUuid.slice(0, 128), ...email ? { email } : {} };
1371
- }
2377
+ const m = /installation_uuid:\s*"([0-9a-f-]{36})"/i.exec(
2378
+ readFileSync4(path8.join(d, "antigravity_state.pbtxt"), "utf8")
2379
+ );
2380
+ if (!m) continue;
2381
+ const base = `https://antigravity.google.com/r/${m[1]}-v2`;
2382
+ const conv = opts.conversation ?? "";
2383
+ if (!ID_RE2.test(conv) || !opts.workspaces?.length) return base;
2384
+ const project = agMatchProject(agProjects(opts.projectsDir ?? projectsDir(opts.env)), opts.workspaces);
2385
+ return project ? `${base}?p=${encodeURIComponent(`c/${conv}?section=${project}`)}` : base;
1372
2386
  } catch {
1373
2387
  }
1374
2388
  }
1375
2389
  return null;
1376
2390
  }
1377
- var accountCache = /* @__PURE__ */ new Map();
1378
- function claudeAccount(dir) {
1379
- const hit = accountCache.get(dir);
1380
- if (hit !== void 0) return hit;
1381
- const acct = claudeLogin(dir)?.id ?? null;
1382
- accountCache.set(dir, acct);
1383
- return acct;
1384
- }
1385
- function claudeAccounts(dirs, home = homeDir()) {
1386
- const out = [];
1387
- for (const d of dirs) {
1388
- const l = claudeLogin(d, home);
1389
- if (!l) continue;
1390
- const seen = out.find((a) => a.id === l.id);
1391
- if (!seen) out.push({ harness: NAME2, ...l });
1392
- else if (!seen.email && l.email) seen.email = l.email;
2391
+ function antigravityFacts(file, opts) {
2392
+ const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
2393
+ const H = parseLines(head);
2394
+ const T = parseLines(tail);
2395
+ const out = {};
2396
+ const url = antigravityRemoteUrl(file, opts);
2397
+ if (url) out.url = url;
2398
+ for (const j of H) {
2399
+ if (j.type !== "USER_INPUT" || typeof j.content !== "string") continue;
2400
+ const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
2401
+ const text = (m ? m[1] : j.content).trim();
2402
+ const line = (text.split("\n").find((l) => l.trim()) ?? "").trim();
2403
+ if (!line || /^<[a-z_-]+>/i.test(line)) continue;
2404
+ out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
2405
+ out.title_source = "first-ask";
2406
+ break;
1393
2407
  }
1394
- return out;
1395
- }
1396
- var LINK_RE = /^https:\/\/claude\.ai\/code\/session_[A-Za-z0-9]+$/;
1397
- function transcriptLink(records) {
1398
- for (let i = records.length - 1; i >= 0; i--) {
1399
- const j = records[i];
1400
- if (j.type === "system" && j.subtype === "bridge_status" && typeof j.url === "string" && LINK_RE.test(j.url))
1401
- return j.url;
1402
- if (j.type === "bridge-session" && typeof j.bridgeSessionId === "string") {
1403
- const m = /^(?:cse|session)_([A-Za-z0-9]+)$/.exec(j.bridgeSessionId);
1404
- if (m) return `https://claude.ai/code/session_${m[1]}`;
1405
- }
2408
+ const times = H.concat(T).map((j) => Date.parse(String(j.created_at ?? j.timestamp ?? ""))).filter((n) => n > 0);
2409
+ if (times.length) {
2410
+ out.started_at = Math.min(...times);
2411
+ out.last_at = Math.max(...times);
1406
2412
  }
1407
- return null;
1408
- }
1409
- function appTitle(text) {
1410
- const t = String(text).replace(/\s+/g, " ").trim();
1411
- return t.length > 50 ? `${t.slice(0, 50)}...` : t;
2413
+ return out;
1412
2414
  }
1413
- function askText(j) {
1414
- if (!j || j.type !== "user" || j.isMeta || j.isSidechain) return null;
1415
- const origin = j.origin;
1416
- if (origin?.kind && origin.kind !== "human") return null;
1417
- const c = j.message?.content;
1418
- 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();
1419
- if (!text || isInjected(text)) return null;
1420
- return text;
2415
+
2416
+ // src/adapters/antigravity-control.ts
2417
+ var antigravity_control_exports = {};
2418
+ __export(antigravity_control_exports, {
2419
+ AgyReading: () => AgyReading,
2420
+ agyArgs: () => agyArgs,
2421
+ agyEnv: () => agyEnv,
2422
+ agyError: () => agyError,
2423
+ agyLine: () => agyLine,
2424
+ agyModeFlags: () => agyModeFlags,
2425
+ defaultLevel: () => defaultLevel,
2426
+ findAgy: () => findAgy,
2427
+ offeredLevels: () => offeredLevels,
2428
+ reading: () => reading
2429
+ });
2430
+ import os4 from "node:os";
2431
+ import path9 from "node:path";
2432
+ var LABEL = "Antigravity";
2433
+ function findAgy(env = process.env, home = os4.homedir()) {
2434
+ return findBinary({
2435
+ override: env.SESSIONPIPE_AGY,
2436
+ names: process.platform === "win32" ? ["agy.exe", "agy.cmd"] : ["agy"],
2437
+ env,
2438
+ dirs: [path9.join(home, ".local", "bin"), "/opt/homebrew/bin", "/usr/local/bin"]
2439
+ });
1421
2440
  }
1422
- function firstAsk(records) {
1423
- for (const j of records) {
1424
- const t = askText(j);
1425
- if (t) return appTitle(t);
1426
- }
1427
- return null;
2441
+ function agyModeFlags(level) {
2442
+ if (level === "read") throw new Error("Antigravity has no read-only mode");
2443
+ return level === "safe" || level === "inherit" ? [] : ["--mode", "accept-edits"];
1428
2444
  }
1429
- function scanTitles(file, from, found) {
1430
- const out = { custom: found.custom ?? null, ai: found.ai ?? null, upto: from };
1431
- let size = 0;
2445
+ var offeredLevels = (mode) => mode === "auto" ? ["safe", "edits"] : ["safe"];
2446
+ var defaultLevel = (mode) => mode === "auto" ? "edits" : mode;
2447
+ function agyArgs(o) {
2448
+ const prompt = o.prompt.startsWith("-") ? ` ${o.prompt}` : o.prompt;
2449
+ return [
2450
+ ...o.resume ? ["--conversation", o.resume] : [],
2451
+ "-p",
2452
+ prompt,
2453
+ "--output-format",
2454
+ "stream-json",
2455
+ ...agyModeFlags(o.permission)
2456
+ ];
2457
+ }
2458
+ var agyEnv = (env = process.env) => jobEnv(env);
2459
+ function agyLine(line) {
2460
+ let j;
1432
2461
  try {
1433
- size = statSync4(file).size;
2462
+ j = JSON.parse(line);
1434
2463
  } catch {
1435
- return out;
1436
- }
1437
- if (size < from) {
1438
- out.upto = 0;
1439
- out.custom = out.ai = null;
1440
- from = 0;
2464
+ return null;
1441
2465
  }
1442
- let pos = 0;
1443
- scanLines(
1444
- file,
1445
- (l) => {
1446
- pos += Buffer.byteLength(l, "utf8") + 1;
1447
- if (pos <= from || l.indexOf('-title"') < 0) return null;
1448
- try {
1449
- const j = JSON.parse(l);
1450
- if (j.type === "custom-title" && j.customTitle) out.custom = j.customTitle;
1451
- if (j.type === "ai-title" && j.aiTitle) out.ai = j.aiTitle;
1452
- } catch {
1453
- }
1454
- return null;
1455
- },
1456
- 512 * 1024 * 1024
1457
- );
1458
- out.upto = size;
1459
- return out;
1460
- }
1461
- function claudeFacts(file, sessionId, hints, state) {
1462
- const { head, tail } = ends(file, 64 * 1024, 256 * 1024);
1463
- const H = parseLines(head);
1464
- const T = parseLines(tail);
2466
+ if (!j || typeof j !== "object") return null;
2467
+ const str2 = (v) => typeof v === "string" ? v : "";
1465
2468
  const out = {};
1466
- const id = sessionId || path7.basename(file, ".jsonl");
1467
- const mem = state.get(NAME2, id);
1468
- let custom = null;
1469
- let ai = null;
1470
- for (const j of H.concat(T)) {
1471
- if (j.type === "custom-title" && typeof j.customTitle === "string") custom = j.customTitle;
1472
- if (j.type === "ai-title" && typeof j.aiTitle === "string") ai = j.aiTitle;
1473
- }
1474
- let size = 0;
2469
+ if (j.event === "init") {
2470
+ if (str2(j.conversation_id)) out.session = str2(j.conversation_id);
2471
+ } else if (j.event === "step_update") {
2472
+ const s = j.step_update ?? {};
2473
+ if (str2(s.conversation_id)) out.session = str2(s.conversation_id);
2474
+ if (s.step_type === "agent_response" && str2(s.text_delta))
2475
+ out.delta = { step: Number(s.step_index ?? 0), text: str2(s.text_delta) };
2476
+ if (s.step_type === "error_message") {
2477
+ const why = str2(s.text_delta) || str2(s.error) || str2(s.message);
2478
+ if (why) out.note = why;
2479
+ }
2480
+ } else if (j.event === "result") {
2481
+ const r = j.result ?? {};
2482
+ if (str2(r.conversation_id)) out.session = str2(r.conversation_id);
2483
+ out.result = {
2484
+ status: str2(r.status),
2485
+ response: str2(r.response),
2486
+ error: str2(r.error),
2487
+ denied: Array.isArray(r.denied_actions) ? r.denied_actions : []
2488
+ };
2489
+ } else return null;
2490
+ return Object.keys(out).length ? out : null;
2491
+ }
2492
+ function deniedName(d) {
2493
+ if (typeof d === "string") return d.slice(0, 80);
2494
+ if (!d || typeof d !== "object") return null;
2495
+ const o = d;
2496
+ for (const k of ["tool_name", "toolName", "name", "tool", "action", "type"])
2497
+ if (typeof o[k] === "string" && o[k]) return o[k].slice(0, 80);
2498
+ return null;
2499
+ }
2500
+ function agyError(stderr) {
2501
+ const s = String(stderr || "");
2502
+ const i = s.lastIndexOf("AGY_ERROR:");
2503
+ if (i < 0) return "";
2504
+ const raw = s.slice(i + "AGY_ERROR:".length).trim();
1475
2505
  try {
1476
- size = statSync4(file).size;
2506
+ const j = JSON.parse(raw);
2507
+ for (const k of ["message", "error", "detail"]) if (typeof j[k] === "string" && j[k]) return j[k];
1477
2508
  } catch {
1478
2509
  }
1479
- if (!custom && !ai && size > 64 * 1024 + 256 * 1024) {
1480
- const s = scanTitles(file, Number(mem.scan_upto) || 0, {
1481
- custom: mem.scan_custom,
1482
- ai: mem.scan_ai
1483
- });
1484
- custom = s.custom;
1485
- ai = s.ai;
1486
- state.save(NAME2, id, { scan_upto: s.upto, scan_custom: s.custom, scan_ai: s.ai });
1487
- }
1488
- const dir = configDirOf(file);
1489
- const live = claudeLive(dir).get(id) ?? claudeLiveByPid(dir, hints.claude_pid);
1490
- if (live?.name) state.save(NAME2, id, { name: live.name, name_source: live.name_source });
1491
- const bridge = live?.bridge && /^session_[A-Za-z0-9]+$/.test(live.bridge) ? `https://claude.ai/code/${live.bridge}` : null;
1492
- const url = bridge ?? transcriptLink(T) ?? transcriptLink(H) ?? (typeof mem.url === "string" ? mem.url : null);
1493
- if (url) {
1494
- out.url = url;
1495
- state.save(NAME2, id, { url });
1496
- }
1497
- const name = live?.name ? { title: live.name, source: live.name_source } : typeof mem.name === "string" ? { title: mem.name, source: mem.name_source } : null;
1498
- const pick = name && name.source === "custom" && name || custom && { title: custom, source: "custom" } || name || ai && { title: ai, source: "harness" };
1499
- let ask = typeof mem.first_ask === "string" ? mem.first_ask : firstAsk(H);
1500
- if (!ask && size > 64 * 1024 && !mem.first_ask_none) {
1501
- ask = scanLines(file, (l) => {
1502
- try {
1503
- const t = askText(JSON.parse(l));
1504
- return t ? appTitle(t) : null;
1505
- } catch {
1506
- return null;
1507
- }
1508
- });
1509
- if (!ask && size > 16 * 1024 * 1024) state.save(NAME2, id, { first_ask_none: true });
1510
- }
1511
- if (ask) {
1512
- out.first_ask = ask;
1513
- if (!mem.first_ask) state.save(NAME2, id, { first_ask: ask });
2510
+ return raw.split("\n")[0].slice(0, 300);
2511
+ }
2512
+ var AgyReading = class {
2513
+ session = null;
2514
+ lines = 0;
2515
+ buf = "";
2516
+ step = null;
2517
+ result = null;
2518
+ note = null;
2519
+ get text() {
2520
+ return this.buf;
1514
2521
  }
1515
- if (pick) {
1516
- out.title = String(pick.title).slice(0, 120);
1517
- out.title_source = pick.source;
1518
- } else if (ask) {
1519
- out.title = ask;
1520
- out.title_source = "first-ask";
2522
+ feed(line) {
2523
+ this.lines++;
2524
+ const p = agyLine(line);
2525
+ if (!p) return false;
2526
+ if (p.session && !this.session) this.session = p.session;
2527
+ if (p.note) this.note = p.note;
2528
+ if (p.result) this.result = p.result;
2529
+ if (!p.delta) return false;
2530
+ if (this.step !== null && p.delta.step !== this.step && this.buf && !this.buf.endsWith("\n\n")) this.buf += "\n\n";
2531
+ this.step = p.delta.step;
2532
+ this.buf += p.delta.text;
2533
+ return true;
1521
2534
  }
1522
- const acct = claudeAccount(dir);
1523
- if (acct) out.account_id = acct;
1524
- for (let i = T.length - 1; i >= 0; i--) {
1525
- const m = T[i].message?.model;
1526
- if (T[i]?.type === "assistant" && m && m !== "<synthetic>") {
1527
- out.model = m;
1528
- break;
2535
+ outcome(res) {
2536
+ const broke = runFailure(LABEL, res);
2537
+ if (broke) return { failed: broke };
2538
+ const r = this.result;
2539
+ if (!r) {
2540
+ const why = agyError(res.stderr) || this.note || "";
2541
+ return {
2542
+ failed: `Antigravity exited (${res.signal || `code ${res.code}`}) without an answer${why ? `: ${why}` : ""}`
2543
+ };
1529
2544
  }
2545
+ if (r.status !== "SUCCESS")
2546
+ return { failed: r.error || agyError(res.stderr) || this.note || "Antigravity reported an error" };
2547
+ const names = [...new Set(r.denied.map(deniedName).filter((n) => !!n))];
2548
+ const text = r.response.trim() ? r.response.trim() : this.buf.trim() || "(Antigravity finished without a written reply.)";
2549
+ return { reply: text + deniedNote(names, r.denied.length) };
1530
2550
  }
1531
- const meta = [...T].reverse().find((j) => j.version) ?? H.find((j) => j.version);
1532
- if (meta?.version) out.harness_version = String(meta.version);
1533
- const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
1534
- if (times.length) {
1535
- out.started_at = Math.min(...times);
1536
- out.last_at = Math.max(...times);
1537
- }
1538
- const withCwd = H.find((j) => typeof j.cwd === "string");
1539
- if (withCwd) {
1540
- out.cwd = withCwd.cwd;
1541
- if (typeof withCwd.gitBranch === "string" && withCwd.gitBranch !== "HEAD") out.branch = withCwd.gitBranch;
1542
- }
1543
- return out;
1544
- }
2551
+ };
2552
+ var reading = () => new AgyReading();
1545
2553
 
1546
2554
  // src/adapters/claude-code-control.ts
1547
2555
  var claude_code_control_exports = {};
1548
2556
  __export(claude_code_control_exports, {
1549
2557
  JOB_TIMEOUT_MS: () => JOB_TIMEOUT_MS,
2558
+ ceilingLevel: () => ceilingLevel,
2559
+ claudeArgs: () => claudeArgs,
1550
2560
  claudeEnv: () => claudeEnv,
1551
2561
  claudeLogin: () => claudeLogin2,
1552
2562
  cmpVersion: () => cmpVersion,
2563
+ defaultLevel: () => defaultLevel2,
1553
2564
  detectCaps: () => detectCaps,
1554
2565
  findClaude: () => findClaude,
1555
2566
  findTranscript: () => findTranscript,
@@ -1558,6 +2569,8 @@ __export(claude_code_control_exports, {
1558
2569
  loginCommand: () => loginCommand,
1559
2570
  loginHint: () => loginHint,
1560
2571
  modeFlags: () => modeFlags,
2572
+ offeredLevels: () => offeredLevels2,
2573
+ outputArgs: () => outputArgs,
1561
2574
  parseAuthStatus: () => parseAuthStatus,
1562
2575
  parseCaps: () => parseCaps,
1563
2576
  parseResult: () => parseResult,
@@ -1566,10 +2579,33 @@ __export(claude_code_control_exports, {
1566
2579
  sessionFolder: () => sessionFolder
1567
2580
  });
1568
2581
  import { execFile, execFileSync as execFileSync3 } from "node:child_process";
1569
- import { closeSync as closeSync2, openSync as openSync2, readdirSync as readdirSync4, readFileSync as readFileSync4, readSync as readSync2, statSync as statSync5 } from "node:fs";
1570
- import os3 from "node:os";
1571
- import path8 from "node:path";
1572
- var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
2582
+ import { closeSync as closeSync2, openSync as openSync2, readdirSync as readdirSync4, readFileSync as readFileSync5, readSync as readSync2, statSync as statSync5 } from "node:fs";
2583
+ import os5 from "node:os";
2584
+ import path10 from "node:path";
2585
+
2586
+ // src/control/verify.ts
2587
+ var GRANT_MAX_MS = 24 * 36e5;
2588
+ var SKEW_MS = 5 * 6e4;
2589
+ var MACHINE_MAX_AGE_MS = 24 * 36e5;
2590
+ var RECEIVER_MAX_AGE_MS = 10 * 6e4;
2591
+ var NONCE_TTL_MS = 25 * 36e5;
2592
+ var MODEL_RE = /^[A-Za-z0-9][A-Za-z0-9._:/@[\]-]*$/;
2593
+ var START_PERMISSIONS = ["read", "safe", "edits", "auto"];
2594
+ var levelRank = (l) => START_PERMISSIONS.indexOf(l);
2595
+ var FIELDS = {
2596
+ prompt: ["text", "files"],
2597
+ "permission.answer": ["for", "decision"],
2598
+ cancel: [],
2599
+ start: ["text", "cwd", "files", "permission", "model"],
2600
+ "key.add": ["key", "name"],
2601
+ pool: ["pool_action", "pool_account", "pool_order", "pool_code"]
2602
+ };
2603
+ var POOL_FIELDS = FIELDS.pool;
2604
+ var enc = new TextEncoder();
2605
+ var dec = new TextDecoder();
2606
+
2607
+ // src/adapters/claude-code-control.ts
2608
+ var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1573
2609
  var LIVE_WRITE_MS = 9e4;
1574
2610
  var cmpVersion = (a, b) => {
1575
2611
  const pa = a.split(".").map(Number);
@@ -1578,28 +2614,28 @@ var cmpVersion = (a, b) => {
1578
2614
  if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) - (pb[i] ?? 0);
1579
2615
  return 0;
1580
2616
  };
1581
- function findClaude(env = process.env, home = os3.homedir()) {
2617
+ function findClaude(env = process.env, home = os5.homedir()) {
1582
2618
  if (env.SESSIONPIPE_CLAUDE && isExe(env.SESSIONPIPE_CLAUDE)) return env.SESSIONPIPE_CLAUDE;
1583
2619
  const names = process.platform === "win32" ? ["claude.exe", "claude.cmd"] : ["claude"];
1584
- for (const d of String(env.PATH || "").split(path8.delimiter)) {
1585
- if (!d || !path8.isAbsolute(d)) continue;
1586
- for (const n of names) if (isExe(path8.join(d, n))) return path8.join(d, n);
2620
+ for (const d of String(env.PATH || "").split(path10.delimiter)) {
2621
+ if (!d || !path10.isAbsolute(d)) continue;
2622
+ for (const n of names) if (isExe(path10.join(d, n))) return path10.join(d, n);
1587
2623
  }
1588
2624
  for (const p of [
1589
- path8.join(home, ".claude", "local", "claude"),
1590
- path8.join(home, ".local", "bin", "claude"),
2625
+ path10.join(home, ".claude", "local", "claude"),
2626
+ path10.join(home, ".local", "bin", "claude"),
1591
2627
  "/opt/homebrew/bin/claude",
1592
2628
  "/usr/local/bin/claude"
1593
2629
  ])
1594
2630
  if (isExe(p)) return p;
1595
- const app = path8.join(home, "Library", "Application Support", "Claude", "claude-code");
2631
+ const app = path10.join(home, "Library", "Application Support", "Claude", "claude-code");
1596
2632
  let versions = [];
1597
2633
  try {
1598
2634
  versions = readdirSync4(app).filter((n) => /^\d+(\.\d+)*$/.test(n)).sort(cmpVersion).reverse();
1599
2635
  } catch {
1600
2636
  }
1601
2637
  for (const v of versions) {
1602
- const p = path8.join(app, v, "claude.app", "Contents", "MacOS", "claude");
2638
+ const p = path10.join(app, v, "claude.app", "Contents", "MacOS", "claude");
1603
2639
  if (isExe(p)) return p;
1604
2640
  }
1605
2641
  return null;
@@ -1625,11 +2661,19 @@ function parseCaps(help) {
1625
2661
  stream: !!optionBlock(help, "--include-partial-messages")
1626
2662
  };
1627
2663
  }
1628
- function modeFlags(mode, caps) {
1629
- if (mode === "auto") {
1630
- if (!caps.modes.includes("auto"))
1631
- return { error: "this machine is in auto mode, but its Claude Code has no auto permission mode" };
1632
- return { args: ["--permission-mode", "auto"] };
2664
+ var LEVEL_MODE = {
2665
+ read: "plan",
2666
+ edits: "acceptEdits",
2667
+ auto: "auto"
2668
+ };
2669
+ function modeFlags(level, caps) {
2670
+ if (level === "inherit") return { args: [] };
2671
+ if (level === "auto" && !caps.modes.includes("auto"))
2672
+ return { error: "this machine is in auto mode, but its Claude Code has no auto permission mode" };
2673
+ if (level !== "safe") {
2674
+ const m = LEVEL_MODE[level];
2675
+ if (!caps.modes.includes(m)) return { error: `this machine's Claude Code has no ${m} permission mode` };
2676
+ return { args: ["--permission-mode", m] };
1633
2677
  }
1634
2678
  if (caps.modes.includes("dontAsk")) return { args: ["--permission-mode", "dontAsk"] };
1635
2679
  const args = [];
@@ -1641,6 +2685,32 @@ function modeFlags(mode, caps) {
1641
2685
  if (caps.promptsNone) args.push("--permission-prompts", "none");
1642
2686
  return { args };
1643
2687
  }
2688
+ var ceilingLevel = (mode) => mode === "inherit" ? "safe" : mode;
2689
+ function offeredLevels2(mode, caps) {
2690
+ return START_PERMISSIONS.filter(
2691
+ (l) => levelRank(l) <= levelRank(ceilingLevel(mode)) && (l === "safe" || caps.modes.includes(LEVEL_MODE[l]))
2692
+ );
2693
+ }
2694
+ var defaultLevel2 = (mode) => mode;
2695
+ function outputArgs(caps) {
2696
+ return caps.stream ? ["--output-format", "stream-json", "--verbose", "--include-partial-messages"] : ["--output-format", "json"];
2697
+ }
2698
+ function claudeArgs(o) {
2699
+ const mf = modeFlags(o.level, o.caps);
2700
+ if ("error" in mf) return mf;
2701
+ const where = o.resume ? ["--resume", o.resume, ...o.fork ? ["--fork-session"] : []] : ["--session-id", o.session];
2702
+ return {
2703
+ args: [
2704
+ "-p",
2705
+ ...outputArgs(o.caps),
2706
+ ...mf.args,
2707
+ ...o.model ? ["--model", o.model] : [],
2708
+ ...o.allowedTools?.length ? [`--allowedTools=${o.allowedTools.join(",")}`] : [],
2709
+ ...where,
2710
+ o.prompt.startsWith("-") ? ` ${o.prompt}` : o.prompt
2711
+ ]
2712
+ };
2713
+ }
1644
2714
  function claudeEnv(configDir, env = process.env) {
1645
2715
  const e = { ...env };
1646
2716
  for (const k of [
@@ -1654,7 +2724,7 @@ function claudeEnv(configDir, env = process.env) {
1654
2724
  delete e[k];
1655
2725
  e.SESSIONPIPE_CONTROL_JOB = "1";
1656
2726
  if (configDir !== void 0) {
1657
- if (path8.resolve(configDir) === path8.resolve(os3.homedir(), ".claude")) delete e.CLAUDE_CONFIG_DIR;
2727
+ if (path10.resolve(configDir) === path10.resolve(os5.homedir(), ".claude")) delete e.CLAUDE_CONFIG_DIR;
1658
2728
  else e.CLAUDE_CONFIG_DIR = configDir;
1659
2729
  }
1660
2730
  return e;
@@ -1687,12 +2757,12 @@ function claudeLogin2(bin, configDir, env = process.env) {
1687
2757
  });
1688
2758
  }
1689
2759
  function loginCommand(configDir) {
1690
- const home = os3.homedir();
1691
- const isDefault = !configDir || path8.resolve(configDir) === path8.resolve(home, ".claude");
2760
+ const home = os5.homedir();
2761
+ const isDefault = !configDir || path10.resolve(configDir) === path10.resolve(home, ".claude");
1692
2762
  return isDefault ? "`claude auth login`" : `\`CLAUDE_CONFIG_DIR=${configDir.replace(home, "~")} claude auth login\``;
1693
2763
  }
1694
2764
  function loginHint(configDir, platform = process.platform) {
1695
- const shown = configDir ? configDir.replace(os3.homedir(), "~") : "~/.claude";
2765
+ const shown = configDir ? configDir.replace(os5.homedir(), "~") : "~/.claude";
1696
2766
  const login = loginCommand(configDir);
1697
2767
  if (platform === "darwin")
1698
2768
  return `Claude Code (${shown}) isn't signed in as this machine's background service sees it. On a Mac its login lives in the login keychain, which a background service can read only while someone is logged in at the screen: log in at the Mac, or run ${login} there.`;
@@ -1714,17 +2784,17 @@ function detectCaps(bin) {
1714
2784
  return parseCaps(help);
1715
2785
  }
1716
2786
  function findTranscript(session, dirs) {
1717
- if (!UUID_RE.test(session)) return null;
2787
+ if (!UUID_RE2.test(session)) return null;
1718
2788
  for (const dir of dirs) {
1719
2789
  let projects = [];
1720
2790
  try {
1721
- projects = readdirSync4(path8.join(dir, "projects"), { withFileTypes: true });
2791
+ projects = readdirSync4(path10.join(dir, "projects"), { withFileTypes: true });
1722
2792
  } catch {
1723
2793
  continue;
1724
2794
  }
1725
2795
  for (const p of projects) {
1726
2796
  if (!p.isDirectory()) continue;
1727
- const file = path8.join(dir, "projects", p.name, `${session}.jsonl`);
2797
+ const file = path10.join(dir, "projects", p.name, `${session}.jsonl`);
1728
2798
  try {
1729
2799
  if (statSync5(file).isFile()) return { file, configDir: dir, project: p.name };
1730
2800
  } catch {
@@ -1746,7 +2816,7 @@ function sessionFolder(t, cap2 = 8 * 1024 * 1024) {
1746
2816
  } catch {
1747
2817
  return null;
1748
2818
  }
1749
- if (!j || typeof j.cwd !== "string" || !path8.isAbsolute(j.cwd)) return null;
2819
+ if (!j || typeof j.cwd !== "string" || !path10.isAbsolute(j.cwd)) return null;
1750
2820
  if (projectName(j.cwd) === t.project) return j.cwd;
1751
2821
  first ??= j.cwd;
1752
2822
  count++;
@@ -1807,13 +2877,13 @@ function pidAlive(pid) {
1807
2877
  function liveProcess(session, configDir) {
1808
2878
  let names = [];
1809
2879
  try {
1810
- names = readdirSync4(path8.join(configDir, "sessions")).filter((n) => /^\d+\.json$/.test(n));
2880
+ names = readdirSync4(path10.join(configDir, "sessions")).filter((n) => /^\d+\.json$/.test(n));
1811
2881
  } catch {
1812
2882
  return false;
1813
2883
  }
1814
2884
  for (const n of names) {
1815
2885
  try {
1816
- const j = JSON.parse(readFileSync4(path8.join(configDir, "sessions", n), "utf8"));
2886
+ const j = JSON.parse(readFileSync5(path10.join(configDir, "sessions", n), "utf8"));
1817
2887
  if (String(j.sessionId) !== session) continue;
1818
2888
  if (pidAlive(Number(j.pid ?? n.slice(0, -5)))) return true;
1819
2889
  } catch {
@@ -1858,14 +2928,14 @@ function parseResult(stdout) {
1858
2928
  }
1859
2929
  return null;
1860
2930
  }
1861
- var isUuid = (s) => typeof s === "string" && UUID_RE.test(s);
2931
+ var isUuid = (s) => typeof s === "string" && UUID_RE2.test(s);
1862
2932
 
1863
2933
  // src/adapters/codex.ts
1864
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
1865
- import path9 from "node:path";
2934
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
2935
+ import path11 from "node:path";
1866
2936
 
1867
2937
  // src/adapters/codex-config.ts
1868
- var NOTIFY_LINE = /^[ \t]*notify[ \t]*=[ \t]*(\[.*\])[ \t]*(?:#.*)?$/m;
2938
+ var NOTIFY_LINE = /^[ \t]*notify[ \t]*=[ \t]*(\[.*\])[ \t]*(?:#.*)?(?=\r?$)/m;
1869
2939
  var NOTIFY_KEY = /^[ \t]*notify[ \t]*=/m;
1870
2940
  var OURS2 = /(?:hook\.js|sessionpipe-hook)",\s*"codex",\s*"notify"/;
1871
2941
  function scan(line, st) {
@@ -1955,10 +3025,11 @@ function dropLine(text, index, length) {
1955
3025
  }
1956
3026
  function insertTopLevel(text, line) {
1957
3027
  const { head, tail } = split(text);
1958
- if (!tail) return (head.trim() ? head.replace(/\s*$/, "\n") : "") + line + "\n";
1959
- return `${(head.trim() ? head.replace(/\s*$/, "\n") : "") + line}
1960
-
1961
- ${tail}`;
3028
+ const body = head.replace(/\s*$/, "");
3029
+ const eol = (body.includes("\n") ? body : text).includes("\r\n") ? "\r\n" : "\n";
3030
+ const top = head.trim() ? head.replace(/\s*$/, eol) : "";
3031
+ if (!tail) return top + line + eol;
3032
+ return `${top + line}${eol}${eol}${tail}`;
1962
3033
  }
1963
3034
  function withNotify(text, line) {
1964
3035
  const n = readNotify(text);
@@ -2007,25 +3078,25 @@ var CODEX_EVENTS = [
2007
3078
  ];
2008
3079
  var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
2009
3080
  var eventsFor2 = (lean) => lean ? CODEX_EVENTS.filter((e) => e !== "PreToolUse") : [...CODEX_EVENTS];
2010
- var codexHome = (env) => env.CODEX_HOME || path9.join(homeDir(), ".codex");
2011
- var hooksFile2 = (env) => path9.join(codexHome(env), "hooks.json");
2012
- var configFile2 = (env) => path9.join(codexHome(env), "config.toml");
2013
- var sessionsDir = (env) => path9.join(codexHome(env), "sessions");
3081
+ var codexHome = (env) => env.CODEX_HOME || path11.join(homeDir(), ".codex");
3082
+ var hooksFile2 = (env) => path11.join(codexHome(env), "hooks.json");
3083
+ var configFile2 = (env) => path11.join(codexHome(env), "config.toml");
3084
+ var sessionsDir = (env) => path11.join(codexHome(env), "sessions");
2014
3085
  var validTool3 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
2015
3086
  var endReason2 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
2016
3087
  function installNotify(cmd, env) {
2017
3088
  const file = configFile2(env);
2018
3089
  let text = "";
2019
3090
  try {
2020
- text = readFileSync5(file, "utf8");
3091
+ text = readFileSync6(file, "utf8");
2021
3092
  } catch {
2022
3093
  }
2023
3094
  const argv = cmd([NAME3, "notify"]).argv;
2024
3095
  const line = `notify = ${JSON.stringify(argv)}`;
2025
3096
  const n = readNotify(text);
2026
3097
  const write = (next) => {
2027
- mkdirSync3(path9.dirname(file), { recursive: true });
2028
- writeFileSync3(file, next);
3098
+ mkdirSync3(path11.dirname(file), { recursive: true });
3099
+ writeFileSync4(file, next);
2029
3100
  };
2030
3101
  if (n && !n.other) {
2031
3102
  if (!n.misplaced && JSON.stringify(n.argv) === JSON.stringify(argv))
@@ -2063,19 +3134,19 @@ function uninstallNotify(env) {
2063
3134
  const file = configFile2(env);
2064
3135
  let text;
2065
3136
  try {
2066
- text = readFileSync5(file, "utf8");
3137
+ text = readFileSync6(file, "utf8");
2067
3138
  } catch {
2068
3139
  return { file, changed: false };
2069
3140
  }
2070
3141
  const n = readNotify(text);
2071
3142
  if (!n) return { file, changed: false };
2072
3143
  if (!n.other) {
2073
- writeFileSync3(file, withoutNotify(text));
3144
+ writeFileSync4(file, withoutNotify(text));
2074
3145
  return { file, changed: true };
2075
3146
  }
2076
3147
  const prev = previousNotify(n.argv);
2077
3148
  if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev))) {
2078
- writeFileSync3(file, replaceNotify(text, n, `notify = ${stripPreviousRaw(n.raw)}`));
3149
+ writeFileSync4(file, replaceNotify(text, n, `notify = ${stripPreviousRaw(n.raw)}`));
2079
3150
  return { file, changed: true };
2080
3151
  }
2081
3152
  return { file, changed: false };
@@ -2091,7 +3162,7 @@ function findRollout(threadId, env = process.env) {
2091
3162
  return;
2092
3163
  }
2093
3164
  for (const n of names.sort().reverse()) {
2094
- const p = path9.join(dir, n);
3165
+ const p = path11.join(dir, n);
2095
3166
  if (n.endsWith(`-${threadId}.jsonl`)) {
2096
3167
  best = p;
2097
3168
  return;
@@ -2135,7 +3206,7 @@ var codex = {
2135
3206
  ];
2136
3207
  let text = "";
2137
3208
  try {
2138
- text = readFileSync5(configFile2(env), "utf8");
3209
+ text = readFileSync6(configFile2(env), "utf8");
2139
3210
  } catch {
2140
3211
  }
2141
3212
  const n = readNotify(text);
@@ -2181,7 +3252,7 @@ var codex = {
2181
3252
  if (j && typeof j === "object" && !Array.isArray(j)) s = j;
2182
3253
  } catch {
2183
3254
  }
2184
- const id = s.session_id;
3255
+ const id = s.session_id ?? s.thread_id ?? s.conversation_id ?? s["thread-id"] ?? s["session-id"];
2185
3256
  if (!id) return null;
2186
3257
  if (s.transcript_path === null) return null;
2187
3258
  const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
@@ -2262,7 +3333,7 @@ var codex = {
2262
3333
  readTranscript(file, fromLine) {
2263
3334
  let text = "";
2264
3335
  try {
2265
- text = readFileSync5(file, "utf8");
3336
+ text = readFileSync6(file, "utf8");
2266
3337
  } catch {
2267
3338
  return { turns: [], line: fromLine };
2268
3339
  }
@@ -2276,7 +3347,7 @@ var codex = {
2276
3347
  backfill(sinceMs, env = process.env) {
2277
3348
  const rows2 = [];
2278
3349
  for (const f of recentFiles(sessionsDir(env), 3, sinceMs, (n) => /^rollout-.*\.jsonl$/.test(n))) {
2279
- const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(path9.basename(f));
3350
+ const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(path11.basename(f));
2280
3351
  if (!m?.[1]) continue;
2281
3352
  if (codexSubagent(rolloutMeta(f))) continue;
2282
3353
  const facts = codexFacts(f);
@@ -2285,12 +3356,40 @@ var codex = {
2285
3356
  rows2.push({ session: { id: m[1], ...rest }, started_at, last_at });
2286
3357
  }
2287
3358
  return rows2;
2288
- }
3359
+ },
3360
+ limits: (transcript) => codexLimits(transcript)
2289
3361
  };
3362
+ var epochIso2 = (t) => typeof t === "number" && t > 0 && t < 1e11 ? new Date(t * 1e3).toISOString() : void 0;
2290
3363
  function prune3(o) {
2291
3364
  for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
2292
3365
  return o;
2293
3366
  }
3367
+ function codexLimits(file) {
3368
+ const { tail } = ends(file, 0, 256 * 1024);
3369
+ for (const r of parseLines(tail).reverse()) {
3370
+ const p = r.payload;
3371
+ if (r.type !== "event_msg" || p?.type !== "token_count") continue;
3372
+ if (!p.rate_limits || typeof p.rate_limits !== "object") continue;
3373
+ const rl = p.rate_limits;
3374
+ if (rl.limit_id != null && rl.limit_id !== "codex") continue;
3375
+ const windows = [];
3376
+ for (const id of ["primary", "secondary"]) {
3377
+ const w = rl[id];
3378
+ if (typeof w?.used_percent !== "number" || w.used_percent < 0) continue;
3379
+ windows.push(
3380
+ prune3({
3381
+ id,
3382
+ used_percent: w.used_percent,
3383
+ window_minutes: Number.isInteger(w.window_minutes) && Number(w.window_minutes) > 0 ? Number(w.window_minutes) : void 0,
3384
+ resets_at: epochIso2(w.resets_at)
3385
+ })
3386
+ );
3387
+ }
3388
+ if (!windows.length) return null;
3389
+ return prune3({ windows, plan: typeof rl.plan_type === "string" ? rl.plan_type.slice(0, 64) : void 0 });
3390
+ }
3391
+ return null;
3392
+ }
2294
3393
  function codexFacts(file) {
2295
3394
  const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
2296
3395
  let H = parseLines(head);
@@ -2343,38 +3442,71 @@ __export(codex_control_exports, {
2343
3442
  codexLine: () => codexLine,
2344
3443
  codexLogin: () => codexLogin,
2345
3444
  codexModeFlags: () => codexModeFlags,
3445
+ codexModels: () => codexModels,
3446
+ defaultLevel: () => defaultLevel3,
2346
3447
  findCodex: () => findCodex,
2347
3448
  findRollout: () => findRollout,
3449
+ offeredLevels: () => offeredLevels3,
2348
3450
  reading: () => reading2
2349
3451
  });
2350
3452
  import { execFile as execFile2 } from "node:child_process";
2351
- import os4 from "node:os";
2352
- import path10 from "node:path";
3453
+ import { readFileSync as readFileSync7 } from "node:fs";
3454
+ import os6 from "node:os";
3455
+ import path12 from "node:path";
2353
3456
  var LABEL2 = "Codex";
2354
- function findCodex(env = process.env, home = os4.homedir(), nodeDir = path10.dirname(process.execPath)) {
3457
+ function findCodex(env = process.env, home = os6.homedir(), nodeDir = path12.dirname(process.execPath)) {
2355
3458
  return findBinary({
2356
3459
  override: env.SESSIONPIPE_CODEX,
2357
3460
  names: process.platform === "win32" ? ["codex.exe", "codex.cmd"] : ["codex"],
2358
3461
  env,
2359
- dirs: [nodeDir, path10.join(home, ".local", "bin"), "/opt/homebrew/bin", "/usr/local/bin"]
3462
+ dirs: [nodeDir, path12.join(home, ".local", "bin"), "/opt/homebrew/bin", "/usr/local/bin"]
2360
3463
  });
2361
3464
  }
2362
- function codexModeFlags(mode, resume = false) {
3465
+ function codexModeFlags(level, resume = false) {
3466
+ if (level === "inherit") return [];
3467
+ const sandbox = level === "read" ? "read-only" : "workspace-write";
2363
3468
  if (resume)
2364
- return mode === "auto" ? [
3469
+ return level === "auto" ? [
2365
3470
  "-c",
2366
3471
  'sandbox_mode="workspace-write"',
2367
3472
  "-c",
2368
3473
  'approval_policy="on-request"',
2369
3474
  "-c",
2370
3475
  'approvals_reviewer="auto_review"'
2371
- ] : ["-c", 'sandbox_mode="workspace-write"'];
2372
- return mode === "auto" ? ["--approve-for-me"] : ["-s", "workspace-write"];
3476
+ ] : ["-c", `sandbox_mode="${sandbox}"`];
3477
+ return level === "auto" ? ["--approve-for-me"] : ["-s", sandbox];
2373
3478
  }
3479
+ var offeredLevels3 = (mode) => mode === "auto" ? ["read", "safe", "auto"] : ["read", "safe"];
3480
+ var defaultLevel3 = (mode) => mode;
2374
3481
  function codexArgs(o) {
2375
3482
  const common = ["--json", "--skip-git-repo-check"];
2376
- if (o.resume) return ["exec", "resume", ...common, ...codexModeFlags(o.mode, true), "--", o.resume, o.prompt];
2377
- return ["exec", ...common, ...o.cwd ? ["-C", o.cwd] : [], ...codexModeFlags(o.mode), "--", o.prompt];
3483
+ const model = o.model ? ["-m", o.model] : [];
3484
+ if (o.resume)
3485
+ return ["exec", "resume", ...common, ...codexModeFlags(o.permission, true), ...model, "--", o.resume, o.prompt];
3486
+ return [
3487
+ "exec",
3488
+ ...common,
3489
+ ...o.cwd ? ["-C", o.cwd] : [],
3490
+ ...codexModeFlags(o.permission),
3491
+ ...model,
3492
+ "--",
3493
+ o.prompt
3494
+ ];
3495
+ }
3496
+ function codexModels(env = process.env, home = os6.homedir()) {
3497
+ try {
3498
+ const file = path12.join(env.CODEX_HOME || path12.join(home, ".codex"), "models_cache.json");
3499
+ const j = JSON.parse(readFileSync7(file, "utf8"));
3500
+ if (!Array.isArray(j.models)) return [];
3501
+ const out = [];
3502
+ for (const m of j.models)
3503
+ if (m && m.visibility === "list" && typeof m.slug === "string" && MODEL_RE.test(m.slug) && m.slug.length <= 100) {
3504
+ if (!out.includes(m.slug)) out.push(m.slug);
3505
+ }
3506
+ return out.slice(0, 50);
3507
+ } catch {
3508
+ return [];
3509
+ }
2378
3510
  }
2379
3511
  var codexEnv = (env = process.env) => jobEnv(env);
2380
3512
  var LOGIN_HINT = "Codex isn't signed in on this machine: run `codex login --device-auth` there.";
@@ -2401,22 +3533,22 @@ function codexLine(line) {
2401
3533
  return null;
2402
3534
  }
2403
3535
  if (!j || typeof j !== "object") return null;
2404
- const str = (v) => typeof v === "string" ? v : "";
3536
+ const str2 = (v) => typeof v === "string" ? v : "";
2405
3537
  switch (j.type) {
2406
3538
  case "thread.started":
2407
- return str(j.thread_id) ? { session: str(j.thread_id) } : null;
3539
+ return str2(j.thread_id) ? { session: str2(j.thread_id) } : null;
2408
3540
  case "item.completed": {
2409
3541
  const it = j.item;
2410
- if (it?.type === "agent_message" && str(it.text)) return { message: str(it.text) };
2411
- if (it?.type === "error" && str(it.message)) return { warning: str(it.message) };
3542
+ if (it?.type === "agent_message" && str2(it.text)) return { message: str2(it.text) };
3543
+ if (it?.type === "error" && str2(it.message)) return { warning: str2(it.message) };
2412
3544
  return null;
2413
3545
  }
2414
3546
  case "turn.completed":
2415
3547
  return { done: true };
2416
3548
  case "turn.failed":
2417
- return { done: true, failed: str(j.error?.message) || "Codex reported an error" };
3549
+ return { done: true, failed: str2(j.error?.message) || "Codex reported an error" };
2418
3550
  case "error":
2419
- return str(j.message) ? { note: str(j.message) } : null;
3551
+ return str2(j.message) ? { note: str2(j.message) } : null;
2420
3552
  default:
2421
3553
  return null;
2422
3554
  }
@@ -2467,9 +3599,9 @@ function codexStderr(stderr) {
2467
3599
  var reading2 = () => new CodexReading();
2468
3600
 
2469
3601
  // src/adapters/devin.ts
2470
- import { createHash as createHash2 } from "node:crypto";
3602
+ import { createHash as createHash3 } from "node:crypto";
2471
3603
  import { existsSync as existsSync6, readlinkSync } from "node:fs";
2472
- import path11 from "node:path";
3604
+ import path13 from "node:path";
2473
3605
 
2474
3606
  // src/readers/sqlite.ts
2475
3607
  import { execFileSync as execFileSync4 } from "node:child_process";
@@ -2653,11 +3785,11 @@ function foreignEvent(file) {
2653
3785
  return Object.keys(hooks).find((k) => !ok.includes(k));
2654
3786
  }
2655
3787
  var homeOf = (env) => env.HOME || homeDir();
2656
- var appData = (env) => env.APPDATA || path11.join(homeOf(env), "AppData", "Roaming");
2657
- var devinConfigDir = (env = process.env) => process.platform === "win32" ? path11.join(appData(env), "devin") : path11.join(env.XDG_CONFIG_HOME || path11.join(homeOf(env), ".config"), "devin");
2658
- var configFile3 = (env) => path11.join(devinConfigDir(env), "config.json");
2659
- var devinDataDir = (env = process.env) => process.platform === "win32" ? path11.join(appData(env), "devin", "cli") : path11.join(env.XDG_DATA_HOME || path11.join(homeOf(env), ".local", "share"), "devin", "cli");
2660
- var sessionsDb = (env) => path11.join(devinDataDir(env), "sessions.db");
3788
+ var appData = (env) => env.APPDATA || path13.join(homeOf(env), "AppData", "Roaming");
3789
+ var devinConfigDir = (env = process.env) => process.platform === "win32" ? path13.join(appData(env), "devin") : path13.join(env.XDG_CONFIG_HOME || path13.join(homeOf(env), ".config"), "devin");
3790
+ var configFile3 = (env) => path13.join(devinConfigDir(env), "config.json");
3791
+ var devinDataDir = (env = process.env) => process.platform === "win32" ? path13.join(appData(env), "devin", "cli") : path13.join(env.XDG_DATA_HOME || path13.join(homeOf(env), ".local", "share"), "devin", "cli");
3792
+ var sessionsDb = (env) => path13.join(devinDataDir(env), "sessions.db");
2661
3793
  var SESSION_ID = /^[A-Za-z0-9._:-]{1,128}$/;
2662
3794
  var validTool4 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
2663
3795
  var endReason3 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
@@ -2708,7 +3840,7 @@ var devin = {
2708
3840
  const tool = validTool4(s.tool_name) ? s.tool_name : void 0;
2709
3841
  const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
2710
3842
  const response = s.tool_response && typeof s.tool_response === "object" && !Array.isArray(s.tool_response) ? s.tool_response : void 0;
2711
- const permissionId = () => `perm-${createHash2("sha256").update(`${turn_id ?? ""}|${tool ?? ""}|${JSON.stringify(s.tool_input ?? null)}`).digest("hex").slice(0, 16)}`;
3843
+ const permissionId = () => `perm-${createHash3("sha256").update(`${turn_id ?? ""}|${tool ?? ""}|${JSON.stringify(s.tool_input ?? null)}`).digest("hex").slice(0, 16)}`;
2712
3844
  const ev = (type, data) => ({ type, data: prune4(data), harnessEvent: event });
2713
3845
  const events = [];
2714
3846
  switch (event) {
@@ -2829,7 +3961,7 @@ var BACKFILL_SQL = `SELECT s.id, ${SESSION_FIELDS} FROM sessions s
2829
3961
  function sessionFacts(row) {
2830
3962
  const out = {};
2831
3963
  if (typeof row.model === "string" && row.model) out.model = row.model;
2832
- if (typeof row.working_directory === "string" && path11.isAbsolute(row.working_directory))
3964
+ if (typeof row.working_directory === "string" && path13.isAbsolute(row.working_directory))
2833
3965
  out.cwd = row.working_directory;
2834
3966
  const started = Number(row.created_at);
2835
3967
  const last = Number(row.last_activity_at);
@@ -2874,8 +4006,8 @@ function readDevinChain(db, session, fromLine) {
2874
4006
  }
2875
4007
  function devinVersion(env = process.env) {
2876
4008
  try {
2877
- const target = readlinkSync(path11.join(devinDataDir(env), "_versions", "current"));
2878
- const name = path11.basename(target);
4009
+ const target = readlinkSync(path13.join(devinDataDir(env), "_versions", "current"));
4010
+ const name = path13.basename(target);
2879
4011
  return /^[0-9][0-9A-Za-z.+-]{0,63}$/.test(name) ? name : void 0;
2880
4012
  } catch {
2881
4013
  return void 0;
@@ -2889,8 +4021,8 @@ function devinAccount(env = process.env) {
2889
4021
 
2890
4022
  // src/adapters/gemini-cli.ts
2891
4023
  import { execFileSync as execFileSync5 } from "node:child_process";
2892
- import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
2893
- import path12 from "node:path";
4024
+ import { existsSync as existsSync7, readFileSync as readFileSync8 } from "node:fs";
4025
+ import path14 from "node:path";
2894
4026
  var NAME5 = "gemini-cli";
2895
4027
  var GEMINI_EVENTS = [
2896
4028
  "SessionStart",
@@ -2903,26 +4035,31 @@ var GEMINI_EVENTS = [
2903
4035
  "SessionEnd"
2904
4036
  ];
2905
4037
  var OPTS4 = { matcherFor: (e) => /Tool/.test(e) ? ".*" : void 0, timeout: 5e3 };
2906
- var geminiDir = (env) => env.GEMINI_CLI_HOME || path12.join(homeDir(), ".gemini");
2907
- var settings = (env) => path12.join(geminiDir(env), "settings.json");
4038
+ var geminiDir = (env) => env.GEMINI_CLI_HOME || path14.join(homeDir(), ".gemini");
4039
+ var settings = (env) => path14.join(geminiDir(env), "settings.json");
2908
4040
  var validTool5 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
2909
4041
  var eventsFor4 = (lean) => lean ? GEMINI_EVENTS.filter((e) => e !== "BeforeTool") : [...GEMINI_EVENTS];
4042
+ function geminiVersion(platform = process.platform, env = process.env) {
4043
+ const win = platform === "win32";
4044
+ const bin = win ? findBinary({ names: ["gemini.exe", "gemini.cmd"], dirs: [path14.dirname(process.execPath)], env }) : "gemini";
4045
+ if (!bin) throw new Error("no gemini on PATH");
4046
+ const o = {
4047
+ encoding: "utf8",
4048
+ timeout: 4e3,
4049
+ stdio: ["ignore", "pipe", "ignore"],
4050
+ env
4051
+ };
4052
+ const out = /\.cmd$/i.test(bin) ? execFileSync5(`"${bin}" --version`, { ...o, shell: true }) : execFileSync5(bin, ["--version"], o);
4053
+ return out.trim();
4054
+ }
2910
4055
  function geminiHooksActive(settingsFile) {
2911
- let tools;
2912
- try {
2913
- tools = JSON.parse(readFileSync6(settingsFile, "utf8")).tools;
2914
- } catch {
2915
- }
4056
+ const tools = readJson(settingsFile).tools;
2916
4057
  if (tools?.enableHooks === true) return { active: true };
2917
4058
  if (tools?.enableHooks === false)
2918
4059
  return { active: false, note: "inactive: tools.enableHooks is false in settings.json" };
2919
4060
  let version = "";
2920
4061
  try {
2921
- version = execFileSync5("gemini", ["--version"], {
2922
- encoding: "utf8",
2923
- timeout: 4e3,
2924
- stdio: ["ignore", "pipe", "ignore"]
2925
- }).trim();
4062
+ version = geminiVersion();
2926
4063
  } catch {
2927
4064
  return {
2928
4065
  active: true,
@@ -2940,7 +4077,7 @@ var geminiCli = {
2940
4077
  name: NAME5,
2941
4078
  events: GEMINI_EVENTS,
2942
4079
  // ~/.gemini also belongs to Antigravity; Gemini CLI is here when its settings or its chat logs exist.
2943
- detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(path12.join(geminiDir(env), "tmp")),
4080
+ detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(path14.join(geminiDir(env), "tmp")),
2944
4081
  configFiles: (env = process.env) => [settings(env)],
2945
4082
  install: (cmd, env = process.env, opts = {}) => {
2946
4083
  const r = installClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
@@ -3038,7 +4175,7 @@ var geminiCli = {
3038
4175
  readTranscript(file, fromLine) {
3039
4176
  let text = "";
3040
4177
  try {
3041
- text = readFileSync6(file, "utf8");
4178
+ text = readFileSync8(file, "utf8");
3042
4179
  } catch {
3043
4180
  return { turns: [], line: fromLine };
3044
4181
  }
@@ -3076,21 +4213,23 @@ function prune5(o) {
3076
4213
  // src/adapters/index.ts
3077
4214
  var ADAPTERS = [claudeCode, codex, geminiCli, antigravity, devin];
3078
4215
  var adapterByName = (name) => ADAPTERS.find((a) => a.name === name);
4216
+ var HELLO_HARNESSES_MAX = 20;
3079
4217
  function drivableHarnesses(env = process.env) {
3080
4218
  return [
3081
4219
  ...findClaude(env) ? ["claude-code"] : [],
3082
4220
  ...findCodex(env) ? ["codex"] : [],
3083
- ...findAgy(env) ? ["antigravity"] : []
3084
- ];
4221
+ ...findAgy(env) ? ["antigravity"] : [],
4222
+ ...acpHarnesses(env)
4223
+ ].slice(0, HELLO_HARNESSES_MAX);
3085
4224
  }
3086
4225
 
3087
4226
  // src/config.ts
3088
- import { chmodSync, existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
3089
- import path13 from "node:path";
4227
+ import { chmodSync, existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "node:fs";
4228
+ import path15 from "node:path";
3090
4229
  var DEFAULT_CONFIG = { sinks: [], harnesses: {}, update_check: true, keep_days: 30 };
3091
4230
  function readConfig(file = configFile()) {
3092
4231
  try {
3093
- const j = JSON.parse(readFileSync7(file, "utf8"));
4232
+ const j = JSON.parse(readFileSync9(file, "utf8"));
3094
4233
  return {
3095
4234
  ...DEFAULT_CONFIG,
3096
4235
  ...j,
@@ -3102,8 +4241,8 @@ function readConfig(file = configFile()) {
3102
4241
  }
3103
4242
  }
3104
4243
  function writeConfig(c, file = configFile()) {
3105
- mkdirSync4(path13.dirname(file), { recursive: true, mode: 448 });
3106
- writeFileSync4(file, `${JSON.stringify(c, null, 2)}
4244
+ mkdirSync4(path15.dirname(file), { recursive: true, mode: 448 });
4245
+ writeFileSync5(file, `${JSON.stringify(c, null, 2)}
3107
4246
  `, { mode: 384 });
3108
4247
  if (process.platform !== "win32" && existsSync8(file)) {
3109
4248
  try {
@@ -3118,7 +4257,7 @@ function machineName(c, hostname) {
3118
4257
  }
3119
4258
 
3120
4259
  // src/ulid.ts
3121
- import { randomBytes } from "node:crypto";
4260
+ import { randomBytes as randomBytes2 } from "node:crypto";
3122
4261
  var ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
3123
4262
  var lastTime = 0;
3124
4263
  var lastRand = [];
@@ -3143,7 +4282,7 @@ function ulid(now = Date.now()) {
3143
4282
  }
3144
4283
  } else {
3145
4284
  lastTime = now;
3146
- lastRand = Array.from(randomBytes(16), (b) => b % 32);
4285
+ lastRand = Array.from(randomBytes2(16), (b) => b % 32);
3147
4286
  }
3148
4287
  return encodeTime(now) + lastRand.map((v) => ALPHABET[v]).join("");
3149
4288
  }
@@ -3167,6 +4306,22 @@ function makeEvent(a, ctx) {
3167
4306
  };
3168
4307
  }
3169
4308
 
4309
+ // src/launched-by.ts
4310
+ var LAUNCHED_BY_ENV = "SESSIONPIPE_LAUNCHED_BY";
4311
+ var LAUNCHED_BY = /^[a-z][a-z0-9-]{0,39}:[A-Za-z0-9._:-]{1,128}$/;
4312
+ function launchedBy(env) {
4313
+ const v = env[LAUNCHED_BY_ENV]?.trim();
4314
+ return v && LAUNCHED_BY.test(v) ? v : void 0;
4315
+ }
4316
+ function readHook(adapter, input) {
4317
+ const r = adapter.fromHook(input);
4318
+ if (r && !r.session.parent_id) {
4319
+ const launcher = launchedBy(input.env);
4320
+ if (launcher && launcher !== `${adapter.name}:${r.session.id}`) r.session.launched_by = launcher;
4321
+ }
4322
+ return r;
4323
+ }
4324
+
3170
4325
  // src/outbox.ts
3171
4326
  import {
3172
4327
  appendFileSync,
@@ -3175,14 +4330,14 @@ import {
3175
4330
  mkdirSync as mkdirSync5,
3176
4331
  openSync as openSync3,
3177
4332
  readdirSync as readdirSync6,
3178
- readFileSync as readFileSync8,
4333
+ readFileSync as readFileSync10,
3179
4334
  readSync as readSync3,
3180
4335
  renameSync,
3181
4336
  statSync as statSync6,
3182
4337
  unlinkSync as unlinkSync3,
3183
- writeFileSync as writeFileSync5
4338
+ writeFileSync as writeFileSync6
3184
4339
  } from "node:fs";
3185
- import path14 from "node:path";
4340
+ import path16 from "node:path";
3186
4341
  var READ_CHUNK = 1024 * 1024;
3187
4342
  var READ_MAX = 8 * 1024 * 1024;
3188
4343
  var Outbox = class {
@@ -3191,13 +4346,13 @@ var Outbox = class {
3191
4346
  }
3192
4347
  root;
3193
4348
  dir(harness) {
3194
- return path14.join(this.root, "outbox", safeId(harness));
4349
+ return path16.join(this.root, "outbox", safeId(harness));
3195
4350
  }
3196
4351
  file(ref) {
3197
- return path14.join(this.dir(ref.harness), `${safeId(ref.session)}.jsonl`);
4352
+ return path16.join(this.dir(ref.harness), `${safeId(ref.session)}.jsonl`);
3198
4353
  }
3199
4354
  seqFile(ref) {
3200
- return path14.join(this.dir(ref.harness), `${safeId(ref.session)}.seq`);
4355
+ return path16.join(this.dir(ref.harness), `${safeId(ref.session)}.seq`);
3201
4356
  }
3202
4357
  /** The next seq for a session, persisted (survives a hibernating machine and a
3203
4358
  * restart; an in-memory counter would restart at 0 and collide). */
@@ -3205,19 +4360,19 @@ var Outbox = class {
3205
4360
  const f = this.seqFile(ref);
3206
4361
  let n = -1;
3207
4362
  try {
3208
- const t = readFileSync8(f, "utf8").trim();
4363
+ const t = readFileSync10(f, "utf8").trim();
3209
4364
  if (/^\d+$/.test(t)) n = Number(t);
3210
4365
  } catch {
3211
4366
  }
3212
4367
  if (n < 0) n = this.lastSeqInFile(ref) + 1;
3213
- mkdirSync5(path14.dirname(f), { recursive: true, mode: 448 });
3214
- writeFileSync5(f, String(n + 1), { mode: 384 });
4368
+ mkdirSync5(path16.dirname(f), { recursive: true, mode: 448 });
4369
+ writeFileSync6(f, String(n + 1), { mode: 384 });
3215
4370
  return n;
3216
4371
  }
3217
4372
  lastSeqInFile(ref) {
3218
4373
  const f = this.file(ref);
3219
4374
  if (!existsSync9(f)) return -1;
3220
- const lines = readFileSync8(f, "utf8").trim().split("\n");
4375
+ const lines = readFileSync10(f, "utf8").trim().split("\n");
3221
4376
  for (let i = lines.length - 1; i >= 0; i--) {
3222
4377
  try {
3223
4378
  const j = JSON.parse(lines[i]);
@@ -3236,10 +4391,10 @@ var Outbox = class {
3236
4391
  /** Every session file, newest first. */
3237
4392
  sessions() {
3238
4393
  const out = [];
3239
- const base = path14.join(this.root, "outbox");
4394
+ const base = path16.join(this.root, "outbox");
3240
4395
  if (!existsSync9(base)) return [];
3241
4396
  for (const h of readdirSync6(base)) {
3242
- const d = path14.join(base, h);
4397
+ const d = path16.join(base, h);
3243
4398
  let names = [];
3244
4399
  try {
3245
4400
  names = readdirSync6(d).filter((n) => n.endsWith(".jsonl"));
@@ -3248,7 +4403,7 @@ var Outbox = class {
3248
4403
  }
3249
4404
  for (const n of names) {
3250
4405
  try {
3251
- out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime: statSync6(path14.join(d, n)).mtimeMs });
4406
+ out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime: statSync6(path16.join(d, n)).mtimeMs });
3252
4407
  } catch {
3253
4408
  }
3254
4409
  }
@@ -3336,20 +4491,20 @@ var Cursors = class {
3336
4491
  root;
3337
4492
  sink;
3338
4493
  file() {
3339
- return path14.join(this.root, "cursors", `${safeId(this.sink)}.json`);
4494
+ return path16.join(this.root, "cursors", `${safeId(this.sink)}.json`);
3340
4495
  }
3341
4496
  read() {
3342
4497
  try {
3343
- return JSON.parse(readFileSync8(this.file(), "utf8"));
4498
+ return JSON.parse(readFileSync10(this.file(), "utf8"));
3344
4499
  } catch {
3345
4500
  return {};
3346
4501
  }
3347
4502
  }
3348
4503
  write(c) {
3349
4504
  const f = this.file();
3350
- mkdirSync5(path14.dirname(f), { recursive: true, mode: 448 });
4505
+ mkdirSync5(path16.dirname(f), { recursive: true, mode: 448 });
3351
4506
  const tmp = `${f}.${process.pid}.tmp`;
3352
- writeFileSync5(tmp, JSON.stringify(c), { mode: 384 });
4507
+ writeFileSync6(tmp, JSON.stringify(c), { mode: 384 });
3353
4508
  renameSync(tmp, f);
3354
4509
  }
3355
4510
  key(ref) {
@@ -3360,16 +4515,16 @@ function takeLock(root, ref, staleMs = 5 * 6e4) {
3360
4515
  return takeNamedLock(root, `${safeId(ref.harness)}-${safeId(ref.session)}`, staleMs);
3361
4516
  }
3362
4517
  function takeNamedLock(root, name, staleMs = 5 * 6e4) {
3363
- const dir = path14.join(root, "locks");
4518
+ const dir = path16.join(root, "locks");
3364
4519
  mkdirSync5(dir, { recursive: true, mode: 448 });
3365
- const f = path14.join(dir, `${name}.lock`);
4520
+ const f = path16.join(dir, `${name}.lock`);
3366
4521
  try {
3367
- if (Date.now() - statSync6(f).mtimeMs > staleMs || !processAlive(Number(readFileSync8(f, "utf8")))) unlinkSync3(f);
4522
+ if (Date.now() - statSync6(f).mtimeMs > staleMs || !processAlive(Number(readFileSync10(f, "utf8")))) unlinkSync3(f);
3368
4523
  else return null;
3369
4524
  } catch {
3370
4525
  }
3371
4526
  try {
3372
- writeFileSync5(f, String(process.pid), { flag: "wx" });
4527
+ writeFileSync6(f, String(process.pid), { flag: "wx" });
3373
4528
  } catch {
3374
4529
  return null;
3375
4530
  }
@@ -3391,7 +4546,7 @@ function processAlive(pid) {
3391
4546
  }
3392
4547
 
3393
4548
  // src/privacy/pii.ts
3394
- import { createHash as createHash3 } from "node:crypto";
4549
+ import { createHash as createHash4 } from "node:crypto";
3395
4550
  var RULESET_PII = "pii@1";
3396
4551
  var EMAIL = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g;
3397
4552
  var PHONE = /(?<![\w.])(?:\+\d{1,3}[ .-]?)?(?:\(\d{2,4}\)[ .-]?)?\d{2,4}(?:[ .-]?\d{2,4}){1,3}(?!\w)/g;
@@ -3413,7 +4568,7 @@ function reducePii(text, home) {
3413
4568
  return out;
3414
4569
  }
3415
4570
  function hashMachine(name) {
3416
- return createHash3("sha256").update(name).digest("hex").slice(0, 8);
4571
+ return createHash4("sha256").update(name).digest("hex").slice(0, 8);
3417
4572
  }
3418
4573
  function reducePiiDeep(v, home) {
3419
4574
  if (typeof v === "string") return reducePii(v, home);
@@ -3604,6 +4759,7 @@ var TYPE_TIER = {
3604
4759
  "subagent.started": 1,
3605
4760
  "subagent.ended": 1,
3606
4761
  "context.compacted": 1,
4762
+ "limits.observed": 1,
3607
4763
  native: 3
3608
4764
  };
3609
4765
  var DATA_ALLOW = {
@@ -3612,8 +4768,8 @@ var DATA_ALLOW = {
3612
4768
  "session.ended": { 0: ["reason"] },
3613
4769
  "session.backfill": { 0: ["started_at", "last_at", "turns"] },
3614
4770
  "session.forgotten": { 0: [] },
3615
- "turn.started": { 0: ["turn_id"], 1: ["prompt_chars"] },
3616
- "turn.ended": { 0: ["turn_id", "reason", "ms"], 1: ["error"] },
4771
+ "turn.started": { 0: ["turn_id", "submitted_by", "injected"], 1: ["prompt_chars"] },
4772
+ "turn.ended": { 0: ["turn_id", "reason", "ms", "background"], 1: ["error"] },
3617
4773
  "turn.transcript": { 2: ["turn", "user", "assistant", "at"] },
3618
4774
  "turn.progress": { 2: ["turn", "user", "assistant", "at"] },
3619
4775
  "tool.started": { 1: ["tool", "call_id", "turn_id", "agent_id"], 3: ["input"] },
@@ -3623,7 +4779,8 @@ var DATA_ALLOW = {
3623
4779
  "attention.cleared": { 0: ["attention_id", "how"] },
3624
4780
  "subagent.started": { 1: ["agent_id", "agent_type"] },
3625
4781
  "subagent.ended": { 1: ["agent_id", "agent_type"] },
3626
- "context.compacted": { 1: ["trigger"] }
4782
+ "context.compacted": { 1: ["trigger"] },
4783
+ "limits.observed": { 1: ["windows", "plan", "account_id"] }
3627
4784
  };
3628
4785
  var SESSION_ALLOW = [
3629
4786
  "id",
@@ -3637,7 +4794,9 @@ var SESSION_ALLOW = [
3637
4794
  "title_source",
3638
4795
  "url",
3639
4796
  "account_id",
3640
- "parent_id"
4797
+ "attended",
4798
+ "parent_id",
4799
+ "launched_by"
3641
4800
  ];
3642
4801
  var MAX_SIDE = 2e4;
3643
4802
  var MAX_IO = 64 * 1024;
@@ -3686,7 +4845,9 @@ function filterEvent(event, tier, pii, opts = {}) {
3686
4845
  const s = reducePiiDeep(out.session, opts.home);
3687
4846
  if (typeof s.machine === "string") s.machine = hashMachine(s.machine);
3688
4847
  delete s.account_id;
3689
- out = { ...out, session: s, data: reducePiiDeep(out.data, opts.home) };
4848
+ const d = reducePiiDeep(out.data, opts.home);
4849
+ delete d.account_id;
4850
+ out = { ...out, session: s, data: d };
3690
4851
  rulesets.push(RULESET_PII);
3691
4852
  }
3692
4853
  out.privacy = { rulesets, pii };
@@ -3694,14 +4855,14 @@ function filterEvent(event, tier, pii, opts = {}) {
3694
4855
  }
3695
4856
 
3696
4857
  // src/short-id.ts
3697
- import { createHash as createHash4 } from "node:crypto";
4858
+ import { createHash as createHash5 } from "node:crypto";
3698
4859
  function shortId(harness, id) {
3699
- return createHash4("sha256").update(`sessionpipe.session:${harness}:${id}`).digest("hex").slice(0, 8);
4860
+ return createHash5("sha256").update(`sessionpipe.session:${harness}:${id}`).digest("hex").slice(0, 8);
3700
4861
  }
3701
4862
 
3702
4863
  // src/sinks/file.ts
3703
4864
  import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync6 } from "node:fs";
3704
- import path15 from "node:path";
4865
+ import path17 from "node:path";
3705
4866
  var FileSink = class {
3706
4867
  constructor(name, file, tier, pii) {
3707
4868
  this.name = name;
@@ -3715,7 +4876,7 @@ var FileSink = class {
3715
4876
  pii;
3716
4877
  async send(events) {
3717
4878
  try {
3718
- mkdirSync6(path15.dirname(this.file), { recursive: true });
4879
+ mkdirSync6(path17.dirname(this.file), { recursive: true });
3719
4880
  appendFileSync2(this.file, `${events.map((e) => JSON.stringify(e)).join("\n")}
3720
4881
  `);
3721
4882
  return { ok: true, acknowledged: events.length, accepted: events.length, duplicates: 0, rejected: [] };
@@ -3757,7 +4918,9 @@ export {
3757
4918
  DEFAULT_CONFIG,
3758
4919
  DEVIN_EVENTS,
3759
4920
  FileSink,
4921
+ HELLO_HARNESSES_MAX,
3760
4922
  JOB_TIMEOUT_MS,
4923
+ LAUNCHED_BY_ENV,
3761
4924
  Outbox,
3762
4925
  PROTOCOL_VERSION,
3763
4926
  REDACTED,
@@ -3765,6 +4928,7 @@ export {
3765
4928
  RULESET_SECRETS,
3766
4929
  StdoutSink,
3767
4930
  TYPE_TIER,
4931
+ acp_control_exports as acpControl,
3768
4932
  adapterByName,
3769
4933
  antigravity,
3770
4934
  antigravityAnswer,
@@ -3774,6 +4938,7 @@ export {
3774
4938
  claudeCode,
3775
4939
  claude_code_control_exports as claudeControl,
3776
4940
  claudeDirs,
4941
+ claude_code_pool_exports as claudePool,
3777
4942
  codex,
3778
4943
  codex_control_exports as codexControl,
3779
4944
  configFile,
@@ -3792,6 +4957,7 @@ export {
3792
4957
  isSecretName,
3793
4958
  isUlid,
3794
4959
  jobEnv,
4960
+ launchedBy,
3795
4961
  machineName,
3796
4962
  makeEvent,
3797
4963
  notClaudeCode,
@@ -3803,6 +4969,7 @@ export {
3803
4969
  readConfig,
3804
4970
  readDevinChain,
3805
4971
  readGemini,
4972
+ readHook,
3806
4973
  redactDeep,
3807
4974
  redactSecrets,
3808
4975
  reducePii,
@@ -3816,6 +4983,7 @@ export {
3816
4983
  takeNamedLock,
3817
4984
  tilde,
3818
4985
  ulid,
4986
+ untilde,
3819
4987
  writeConfig
3820
4988
  };
3821
4989
  //# sourceMappingURL=index.js.map