sessionpipe 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +99 -2
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +8737 -4942
  4. package/dist/cli.js.map +4 -4
  5. package/dist/control/aliases.d.ts +5 -0
  6. package/dist/control/aliases.d.ts.map +1 -1
  7. package/dist/control/autoupdate.d.ts +1 -18
  8. package/dist/control/autoupdate.d.ts.map +1 -1
  9. package/dist/control/cli.d.ts +5 -1
  10. package/dist/control/cli.d.ts.map +1 -1
  11. package/dist/control/daemon.d.ts +107 -12
  12. package/dist/control/daemon.d.ts.map +1 -1
  13. package/dist/control/files.d.ts +6 -4
  14. package/dist/control/files.d.ts.map +1 -1
  15. package/dist/control/local.d.ts +24 -0
  16. package/dist/control/local.d.ts.map +1 -1
  17. package/dist/control/pair.d.ts +15 -23
  18. package/dist/control/pair.d.ts.map +1 -1
  19. package/dist/control/pool.d.ts +49 -0
  20. package/dist/control/pool.d.ts.map +1 -0
  21. package/dist/control/route.d.ts +2 -2
  22. package/dist/control/route.d.ts.map +1 -1
  23. package/dist/control/sdk.d.ts.map +1 -1
  24. package/dist/control/store.d.ts +21 -2
  25. package/dist/control/store.d.ts.map +1 -1
  26. package/dist/help.d.ts.map +1 -1
  27. package/dist/hook.d.ts.map +1 -1
  28. package/dist/hook.js +43 -1
  29. package/dist/hook.js.map +2 -2
  30. package/dist/index.js +1179 -847
  31. package/dist/index.js.map +4 -4
  32. package/dist/pool/cli.d.ts +39 -0
  33. package/dist/pool/cli.d.ts.map +1 -0
  34. package/dist/pool/credentials.d.ts +44 -0
  35. package/dist/pool/credentials.d.ts.map +1 -0
  36. package/dist/pool/import.d.ts +23 -0
  37. package/dist/pool/import.d.ts.map +1 -0
  38. package/dist/pool/index.d.ts +138 -0
  39. package/dist/pool/index.d.ts.map +1 -0
  40. package/dist/pool/install.d.ts +46 -0
  41. package/dist/pool/install.d.ts.map +1 -0
  42. package/dist/pool/login.d.ts +27 -0
  43. package/dist/pool/login.d.ts.map +1 -0
  44. package/dist/pool/policy.d.ts +100 -0
  45. package/dist/pool/policy.d.ts.map +1 -0
  46. package/dist/pool/server.d.ts +76 -0
  47. package/dist/pool/server.d.ts.map +1 -0
  48. package/dist/pool/store.d.ts +89 -0
  49. package/dist/pool/store.d.ts.map +1 -0
  50. package/dist/pool/types.d.ts +92 -0
  51. package/dist/pool/types.d.ts.map +1 -0
  52. package/dist/run.d.ts +14 -1
  53. package/dist/run.d.ts.map +1 -1
  54. package/dist/update.d.ts +1 -7
  55. package/dist/update.d.ts.map +1 -1
  56. package/dist/worker.js +1182 -850
  57. package/dist/worker.js.map +4 -4
  58. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- // sessionpipe 0.9.0 · Apache-2.0 · https://sessionpipe.org
1
+ // sessionpipe 0.11.0 · Apache-2.0 · https://sessionpipe.org
2
2
 
3
3
  // src/http-sink.ts
4
4
  import { createHash, createHmac } from "node:crypto";
@@ -103,25 +103,12 @@ import {
103
103
  readdirSync as readdirSync6,
104
104
  readFileSync as readFileSync9,
105
105
  renameSync as renameSync3,
106
- statSync as statSync5,
106
+ statSync as statSync6,
107
107
  unlinkSync as unlinkSync4,
108
- writeFileSync as writeFileSync7
108
+ writeFileSync as writeFileSync8
109
109
  } from "node:fs";
110
110
  import os3 from "node:os";
111
- import path14 from "node:path";
112
-
113
- // ../core/src/adapters/antigravity.ts
114
- import {
115
- existsSync as existsSync2,
116
- mkdirSync as mkdirSync2,
117
- readdirSync as readdirSync2,
118
- readFileSync as readFileSync2,
119
- realpathSync,
120
- statSync as statSync2,
121
- unlinkSync as unlinkSync2,
122
- writeFileSync as writeFileSync2
123
- } from "node:fs";
124
- import path4 from "node:path";
111
+ import path15 from "node:path";
125
112
 
126
113
  // ../core/src/paths.ts
127
114
  import os from "node:os";
@@ -146,6 +133,13 @@ function tilde(p, home = homeDir()) {
146
133
  }
147
134
  var safeId = (s) => String(s).replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 160);
148
135
 
136
+ // ../core/src/adapters/claude-code.ts
137
+ import { execFileSync as execFileSync2 } from "node:child_process";
138
+ import { createHash as createHash2 } from "node:crypto";
139
+ import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync2, writeFileSync as writeFileSync2 } from "node:fs";
140
+ import { userInfo } from "node:os";
141
+ import path4 from "node:path";
142
+
149
143
  // ../core/src/readers/files.ts
150
144
  import { closeSync, fstatSync, openSync, readdirSync, readSync, statSync } from "node:fs";
151
145
  import path2 from "node:path";
@@ -236,6 +230,38 @@ function recentFiles(root, depth, sinceMs, match) {
236
230
  return out;
237
231
  }
238
232
 
233
+ // ../core/src/readers/git.ts
234
+ import { execFileSync } from "node:child_process";
235
+ var cache = /* @__PURE__ */ new Map();
236
+ function gitFacts(cwd) {
237
+ if (!cwd) return {};
238
+ const hit = cache.get(cwd);
239
+ if (hit) return hit;
240
+ const run = (args) => {
241
+ try {
242
+ return execFileSync("git", ["-C", cwd, ...args], {
243
+ encoding: "utf8",
244
+ timeout: 1500,
245
+ stdio: ["ignore", "pipe", "ignore"]
246
+ }).trim();
247
+ } catch {
248
+ return "";
249
+ }
250
+ };
251
+ const out = {};
252
+ const repo = run(["remote", "get-url", "origin"]);
253
+ if (repo) out.repo = stripCredentials(repo);
254
+ const branch = run(["rev-parse", "--abbrev-ref", "HEAD"]);
255
+ if (branch && branch !== "HEAD") out.branch = branch;
256
+ cache.set(cwd, out);
257
+ return out;
258
+ }
259
+ function stripCredentials(url) {
260
+ const ssh = /^(?:ssh:\/\/)?git@([^:/]+)[:/](.+?)(?:\.git)?$/.exec(url);
261
+ if (ssh) return `${ssh[1]}/${ssh[2]}`;
262
+ return url.replace(/\/\/[^/@]*@/, "//").replace(/^https?:\/\//, "").replace(/\.git$/, "");
263
+ }
264
+
239
265
  // ../core/src/adapters/codex-rollout.ts
240
266
  function codexSubagent(meta) {
241
267
  if (meta?.type !== "session_meta") return false;
@@ -395,8 +421,10 @@ function readJsonFile(file) {
395
421
  try {
396
422
  raw = readFileSync(file, "utf8");
397
423
  } catch {
398
- return { ok: true, existed: false, value: {}, raw: "", indent: " ", eol: "\n", finalNewline: true };
424
+ return { ok: true, existed: false, value: {}, raw: "", indent: " ", eol: "\n", finalNewline: true, bom: false };
399
425
  }
426
+ const bom = raw.charCodeAt(0) === 65279;
427
+ if (bom) raw = raw.slice(1);
400
428
  let value;
401
429
  try {
402
430
  value = JSON.parse(raw);
@@ -416,12 +444,13 @@ function readJsonFile(file) {
416
444
  raw,
417
445
  indent,
418
446
  eol,
419
- finalNewline: /\r?\n$/.test(raw)
447
+ finalNewline: /\r?\n$/.test(raw),
448
+ bom
420
449
  };
421
450
  }
422
451
  function formatJson(value, f) {
423
452
  const text = JSON.stringify(value, null, f.indent).replace(/\n/g, f.eol);
424
- return f.finalNewline ? text + f.eol : text;
453
+ return (f.bom ? "\uFEFF" : "") + (f.finalNewline ? text + f.eol : text);
425
454
  }
426
455
  function readJson(file) {
427
456
  const f = readJsonFile(file);
@@ -517,74 +546,79 @@ function installedClaudeShaped(file, harness, events, cmd, o) {
517
546
  return current === events.length ? "current" : "stale";
518
547
  }
519
548
 
520
- // ../core/src/adapters/antigravity.ts
521
- var NAME = "antigravity";
522
- var HOOK_NAME = "sessionpipe";
523
- var AG_EVENTS = ["PreInvocation", "PostInvocation", "PostToolUse", "Stop"];
524
- 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';
525
- var antigravityAnswer = (event) => event === "PreToolUse" ? AG_STALE_PRETOOL : "{}\n";
526
- var gemini = (env) => env.GEMINI_CLI_HOME || path4.join(homeDir(), ".gemini");
527
- var hooksFile = (env) => path4.join(gemini(env), "config", "hooks.json");
528
- var projectsDir = (env) => path4.join(gemini(env), "config", "projects");
529
- var dataDirs = (env) => ["antigravity", "antigravity-cli", "antigravity-ide"].map((d) => path4.join(gemini(env), d));
530
- var ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
531
- var validTool = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
532
- function entry(cmd) {
533
- const handler = (e) => ({ type: "command", command: cmd([NAME, e]).command, timeout: 5 });
534
- const out = { enabled: true };
535
- for (const e of AG_EVENTS) out[e] = /Tool/.test(e) ? [{ matcher: "*", hooks: [handler(e)] }] : [handler(e)];
536
- return out;
549
+ // ../core/src/adapters/claude-code.ts
550
+ var NAME = "claude-code";
551
+ var CLAUDE_EVENTS = [
552
+ "SessionStart",
553
+ "UserPromptSubmit",
554
+ "PreToolUse",
555
+ "PostToolUse",
556
+ "PostToolUseFailure",
557
+ "PermissionRequest",
558
+ "Notification",
559
+ "Stop",
560
+ "SubagentStart",
561
+ "SubagentStop",
562
+ "PreCompact",
563
+ "PostCompact",
564
+ "SessionEnd",
565
+ "StopFailure"
566
+ ];
567
+ var LEAN_SKIP = /* @__PURE__ */ new Set(["PreToolUse", "PostToolUseFailure"]);
568
+ var eventsFor = (lean) => lean ? CLAUDE_EVENTS.filter((e) => !LEAN_SKIP.has(e)) : [...CLAUDE_EVENTS];
569
+ var OPTS = {
570
+ matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0,
571
+ timeout: 5,
572
+ // The hook waits up to 125 s for a verified control answer, and only when control
573
+ // is paired on this machine; everywhere else it exits in milliseconds as before.
574
+ timeoutFor: (e) => e === "PermissionRequest" ? 130 : void 0
575
+ };
576
+ var DESKTOP_ID = /^local_[A-Za-z0-9-]{1,64}$/;
577
+ function claudeDirs(env = process.env, home = env.HOME || homeDir()) {
578
+ const out = [];
579
+ const add = (d) => {
580
+ if (!d) return;
581
+ const r = path4.resolve(d);
582
+ if (!out.includes(r)) out.push(r);
583
+ };
584
+ add(path4.join(home, ".claude"));
585
+ add(env.CLAUDE_CONFIG_DIR);
586
+ try {
587
+ for (const e of readdirSync2(home, { withFileTypes: true })) {
588
+ if (!e.isDirectory() || !/^\.claude[-_.]/.test(e.name)) continue;
589
+ const d = path4.join(home, e.name);
590
+ if (existsSync2(path4.join(d, "projects")) || existsSync2(path4.join(d, "settings.json"))) add(d);
591
+ }
592
+ } catch {
593
+ }
594
+ return out.filter((d, i) => i === 0 || existsSync2(d));
537
595
  }
538
- var antigravity = {
596
+ var settingsIn = (dir) => path4.join(dir, "settings.json");
597
+ var configDirOf = (transcript) => path4.dirname(path4.dirname(path4.dirname(transcript)));
598
+ var validTool = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
599
+ var endReason = (r) => r === "clear" || r === "logout" || r === "exit" ? r : r === "prompt_input_exit" ? "exit" : "other";
600
+ var notifKind = (t) => t === "permission_prompt" ? "permission" : t === "idle_prompt" ? "idle" : t === "elicitation_dialog" ? "elicitation" : "question";
601
+ var claudeCode = {
539
602
  name: NAME,
540
- events: AG_EVENTS,
541
- detect: (env = process.env) => existsSync2(path4.join(gemini(env), "config")) || dataDirs(env).some((d) => existsSync2(d)),
542
- configFiles: (env = process.env) => [hooksFile(env)],
543
- install(cmd, env = process.env) {
544
- const file = hooksFile(env);
545
- const f = readJsonFile(file);
546
- if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
547
- const hooks = f.value;
548
- const e = entry(cmd);
549
- if (JSON.stringify(hooks[HOOK_NAME]) === JSON.stringify(e)) return [{ file, changed: false }];
550
- hooks[HOOK_NAME] = e;
551
- mkdirSync2(path4.dirname(file), { recursive: true });
552
- writeFileSync2(file, formatJson(hooks, f));
553
- return [
554
- {
555
- file,
556
- changed: true,
557
- created: !f.existed,
558
- note: "hooks.json is read when a conversation starts; open ones report after a restart"
559
- }
560
- ];
561
- },
562
- uninstall(env = process.env, opts = {}) {
563
- const file = hooksFile(env);
564
- if (!existsSync2(file)) return [{ file, changed: false }];
565
- const f = readJsonFile(file);
566
- if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
567
- const hooks = f.value;
568
- if (!(HOOK_NAME in hooks)) return [{ file, changed: false }];
569
- delete hooks[HOOK_NAME];
570
- if (opts.created?.includes(file) && !Object.keys(hooks).length) {
571
- unlinkSync2(file);
572
- return [{ file, changed: true, note: "removed (sessionpipe created it)" }];
573
- }
574
- writeFileSync2(file, formatJson(hooks, f));
575
- return [{ file, changed: true }];
576
- },
577
- installed(cmd, env = process.env) {
578
- const file = hooksFile(env);
579
- const f = readJsonFile(file);
580
- const have = f.ok ? f.value[HOOK_NAME] : void 0;
581
- return [
582
- {
583
- file,
584
- state: !have ? "missing" : JSON.stringify(have) === JSON.stringify(entry(cmd)) ? "current" : "stale"
585
- }
586
- ];
587
- },
603
+ events: CLAUDE_EVENTS,
604
+ detect: (env = process.env) => claudeDirs(env).some((d) => existsSync2(d)),
605
+ configFiles: (env = process.env) => claudeDirs(env).map(settingsIn),
606
+ install: (cmd, env = process.env, opts = {}) => claudeDirs(env).flatMap((d) => {
607
+ const hooks = installClaudeShaped(settingsIn(d), NAME, eventsFor(opts.lean), cmd, OPTS);
608
+ const line = opts.statusLine === void 0 ? null : statusLine(settingsIn(d), opts.statusLine, cmd);
609
+ return line ? [hooks, line] : [hooks];
610
+ }),
611
+ uninstall: (env = process.env, opts = {}) => claudeDirs(env).map((d) => {
612
+ const line = statusLine(settingsIn(d), false);
613
+ const hooks = uninstallClaudeShaped(settingsIn(d), CLAUDE_EVENTS, {
614
+ created: opts.created?.includes(settingsIn(d))
615
+ });
616
+ return line && !hooks.changed ? line : hooks;
617
+ }),
618
+ installed: (cmd, env = process.env, opts = {}) => claudeDirs(env).map((d) => ({
619
+ file: settingsIn(d),
620
+ state: installedClaudeShaped(settingsIn(d), NAME, eventsFor(opts.lean), cmd, OPTS)
621
+ })),
588
622
  fromHook(input) {
589
623
  const event = input.argv[1] ?? "";
590
624
  let s = {};
@@ -593,310 +627,735 @@ var antigravity = {
593
627
  if (j && typeof j === "object" && !Array.isArray(j)) s = j;
594
628
  } catch {
595
629
  }
596
- const id = s.conversationId ?? s.session_id ?? s.sessionId;
597
- const stdout = antigravityAnswer(event);
598
- if (!id) return { session: { id: "" }, events: [], stdout };
599
- const workspaces = agWorkspaces(s.workspacePaths);
600
- const session = { id: String(id), cwd: workspaces[0] ?? input.cwd };
601
- if (typeof s.modelName === "string" && s.modelName !== "auto") session.model = s.modelName;
602
- const toolCall = s.toolCall;
603
- const tool = validTool(toolCall?.name) ? toolCall?.name : validTool(s.toolName) ? s.toolName : void 0;
630
+ const id = s.session_id ?? s.sessionId;
631
+ if (!id) return null;
632
+ if (notClaudeCode(String(id), s)) return null;
633
+ const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
634
+ if (typeof s.model === "string") session.model = s.model;
635
+ if (typeof s.agent_id === "string" && event.startsWith("Subagent") === false && s.agent_id)
636
+ session.parent_id = null;
637
+ const turn_id = typeof s.prompt_id === "string" ? s.prompt_id : void 0;
638
+ const tool = validTool(s.tool_name) ? s.tool_name : void 0;
639
+ const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
640
+ const ms = typeof s.duration_ms === "number" ? Math.round(s.duration_ms) : void 0;
641
+ const agent_id = typeof s.agent_id === "string" ? s.agent_id : void 0;
642
+ const permissionId = () => `perm-${createHash2("sha256").update(`${turn_id ?? ""}|${tool ?? ""}|${JSON.stringify(s.tool_input ?? null)}`).digest("hex").slice(0, 16)}`;
604
643
  const ev = (type, data) => ({ type, data: prune(data), harnessEvent: event });
605
644
  const events = [];
606
645
  switch (event) {
607
- case "PreInvocation":
608
- if (Number(s.invocationNum) === 0 || s.invocationNum === void 0) {
609
- events.push(ev("session.started", { source: "unknown" }));
610
- events.push(ev("turn.started", {}));
611
- } else events.push(ev("session.heartbeat", {}));
646
+ case "SessionStart":
647
+ events.push(ev("session.started", { source: pickSource(s.source ?? s.trigger) }));
612
648
  break;
613
- case "PostInvocation":
649
+ case "UserPromptSubmit": {
650
+ const prompt = typeof s.prompt === "string" ? s.prompt : void 0;
651
+ events.push(
652
+ ev("turn.started", {
653
+ turn_id,
654
+ prompt_chars: prompt === void 0 ? void 0 : [...prompt].length,
655
+ // Claude Code's hook schema documents `source`
656
+ // (user | sdk | system | loop_wakeup | schedule_wakeup | poll_event) and says a
657
+ // payload may omit it while the field rolls out; 2.1.304's payload has no such
658
+ // key at all. Forwarded verbatim, shape-guarded only: the list is the harness's
659
+ // to extend.
660
+ submitted_by: harnessWord(s.source),
661
+ // One of Claude Code's own prompt wrappers: the prompt is a block the harness
662
+ // wrote, so it is not the person's words. Absent is unknown, never "the person" —
663
+ // and `true` is never "no person caused this turn", because a Talk or
664
+ // `sessionpipe wait` delivery hands the person's own message over inside a
665
+ // <task-notification> (PROTOCOL.md §4).
666
+ injected: prompt !== void 0 && injectedBlock(prompt) ? true : void 0
667
+ })
668
+ );
614
669
  break;
670
+ }
615
671
  case "PreToolUse":
616
- if (tool) events.push(ev("tool.started", { tool, input: toolCall?.args }));
672
+ if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, agent_id, input: s.tool_input }));
617
673
  break;
618
674
  case "PostToolUse":
619
- if (!tool) events.push(ev("session.heartbeat", {}));
620
- else
675
+ if (tool)
621
676
  events.push(
622
677
  ev("tool.ended", {
623
678
  tool,
624
- ok: !s.error,
625
- error: typeof s.error === "string" ? s.error.slice(0, 2e3) : void 0,
626
- input: toolCall?.args
679
+ call_id,
680
+ turn_id,
681
+ agent_id,
682
+ ms,
683
+ ok: true,
684
+ input: s.tool_input,
685
+ output: s.tool_response
686
+ })
687
+ );
688
+ break;
689
+ case "PostToolUseFailure":
690
+ if (tool)
691
+ events.push(
692
+ ev("tool.ended", {
693
+ tool,
694
+ call_id,
695
+ turn_id,
696
+ agent_id,
697
+ ms,
698
+ ok: false,
699
+ error: errText(s.error ?? s.tool_response),
700
+ input: s.tool_input,
701
+ output: s.tool_response
627
702
  })
628
703
  );
629
704
  break;
705
+ case "PermissionRequest":
706
+ events.push(
707
+ ev("attention.needed", {
708
+ attention_id: call_id ?? permissionId(),
709
+ kind: "permission",
710
+ tool,
711
+ message: tool ? `${tool}${describe(s.tool_input)}` : void 0
712
+ })
713
+ );
714
+ break;
715
+ case "Notification":
716
+ events.push(
717
+ ev("attention.needed", {
718
+ attention_id: `notif-${Date.now()}`,
719
+ kind: notifKind(s.notification_type),
720
+ message: typeof s.message === "string" ? s.message.slice(0, 200) : void 0
721
+ })
722
+ );
723
+ break;
630
724
  case "Stop":
631
- events.push(ev("turn.ended", { reason: s.error ? "error" : "stop" }));
725
+ events.push(
726
+ ev("turn.ended", {
727
+ turn_id,
728
+ reason: "stop",
729
+ background: Array.isArray(s.background_tasks) ? s.background_tasks.length : void 0
730
+ })
731
+ );
632
732
  break;
633
- default:
634
- return { session, events: [], stdout };
635
- }
636
- const r = { session, events, stdout, hints: { workspaces } };
637
- if (typeof s.transcriptPath === "string") r.transcript = s.transcriptPath;
638
- return r;
639
- },
640
- readTranscript(file, fromLine) {
641
- let text = "";
642
- try {
643
- text = readFileSync2(file, "utf8");
644
- } catch {
645
- return { turns: [], line: fromLine };
646
- }
647
- return readAntigravity(text.split("\n"), fromLine);
648
- },
649
- facts(session, transcript, hints) {
650
- if (!transcript) return {};
651
- return antigravityFacts(transcript, {
652
- conversation: session.id,
653
- workspaces: hints.workspaces ?? (session.cwd ? [session.cwd] : []),
654
- env: process.env
655
- });
733
+ case "StopFailure":
734
+ events.push(
735
+ ev("turn.ended", {
736
+ turn_id,
737
+ reason: "error",
738
+ error: typeof s.error === "string" ? s.error.slice(0, 200) : void 0
739
+ })
740
+ );
741
+ break;
742
+ case "SubagentStart":
743
+ events.push(
744
+ ev("subagent.started", {
745
+ agent_id: String(s.agent_id ?? ""),
746
+ agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
747
+ })
748
+ );
749
+ break;
750
+ case "SubagentStop":
751
+ events.push(
752
+ ev("subagent.ended", {
753
+ agent_id: String(s.agent_id ?? ""),
754
+ agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
755
+ })
756
+ );
757
+ break;
758
+ case "PreCompact":
759
+ events.push(ev("context.compacted", { trigger: s.trigger === "manual" ? "manual" : "auto" }));
760
+ break;
761
+ case "PostCompact":
762
+ break;
763
+ // PreCompact already said it; a second event would double-count
764
+ case "SessionEnd":
765
+ events.push(ev("session.ended", { reason: endReason(s.reason) }));
766
+ break;
767
+ case "StatusLine": {
768
+ const limits = claudeLimits(s.rate_limits);
769
+ if (!limits) return null;
770
+ events.push(ev("limits.observed", limits));
771
+ break;
772
+ }
773
+ default:
774
+ return null;
775
+ }
776
+ const hints = {};
777
+ if (event === "UserPromptSubmit" && typeof s.prompt === "string") hints.prompt = s.prompt;
778
+ const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
779
+ if (host && DESKTOP_ID.test(host)) hints.desktop_id = host;
780
+ const attended = input.env.CLAUDE_CODE_SESSION_ATTENDED;
781
+ if (attended === "1" || attended === "0") hints.attended = attended === "1" || !!input.env.SESSIONPIPE_CONTROL_JOB;
782
+ if (event !== "PreToolUse" && event !== "PostToolUse") hints.claude_pid = claudePid(input.env);
783
+ const r = { session, events, hints };
784
+ if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
785
+ return r;
786
+ },
787
+ readTranscript(file, fromLine) {
788
+ let text = "";
789
+ try {
790
+ text = readFileSync2(file, "utf8");
791
+ } catch {
792
+ return { turns: [], line: fromLine };
793
+ }
794
+ return readClaude(text.split("\n"), fromLine);
795
+ },
796
+ facts(session, transcript, hints, state) {
797
+ const out = {};
798
+ if (transcript) Object.assign(out, claudeFacts(transcript, session.id, hints, state));
799
+ const cwd = out.cwd ?? session.cwd;
800
+ Object.assign(out, gitFacts(cwd), out.branch ? { branch: out.branch } : {});
801
+ if (!out.url && typeof hints.desktop_id === "string") out.url = `claude://code/continue/${hints.desktop_id}`;
802
+ if (typeof hints.attended === "boolean") out.attended = hints.attended || !!out.url;
803
+ return out;
656
804
  },
805
+ remoteFacts: (session, local, transcript, state, env = process.env) => claudeRemoteFacts(session.id, local, transcript, state, env),
657
806
  backfill(sinceMs, env = process.env) {
658
807
  const rows2 = [];
659
- for (const data of dataDirs(env)) {
660
- let ids = [];
661
- try {
662
- ids = readdirSync2(path4.join(data, "brain")).filter((n) => /^[0-9a-f-]{36}$/i.test(n));
663
- } catch {
664
- }
665
- for (const id of ids) {
666
- const f = path4.join(data, "brain", id, ".system_generated", "logs", "transcript.jsonl");
667
- try {
668
- if (statSync2(f).mtimeMs < sinceMs) continue;
669
- } catch {
670
- continue;
671
- }
672
- const facts = antigravityFacts(f, { conversation: id, workspaces: [], env });
673
- if (!facts.started_at || !facts.last_at) continue;
674
- const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
675
- rows2.push({ session: { id, ...rest }, started_at, last_at });
676
- }
808
+ const files = [];
809
+ for (const d of claudeDirs(env))
810
+ files.push(...recentFiles(path4.join(d, "projects"), 1, sinceMs, (n) => /^[0-9a-f-]{36}\.jsonl$/.test(n)));
811
+ const noState = { get: () => ({}), save: () => {
812
+ } };
813
+ for (const f of files) {
814
+ const id = path4.basename(f, ".jsonl");
815
+ const facts = claudeFacts(f, id, {}, noState);
816
+ if (!facts.started_at || !facts.last_at) continue;
817
+ const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
818
+ const cwd = rest.cwd;
819
+ rows2.push({
820
+ session: { id, ...rest, ...gitFacts(cwd), ...rest.branch ? { branch: rest.branch } : {} },
821
+ started_at,
822
+ last_at,
823
+ transcript: f
824
+ });
677
825
  }
678
826
  return rows2;
679
827
  }
680
828
  };
829
+ var LINE_REFRESH_S = 60;
830
+ function statusLine(file, want, cmd) {
831
+ const f = readJsonFile(file);
832
+ if (!f.ok) return null;
833
+ const cur = f.value.statusLine;
834
+ const ours = typeof cur?.command === "string" && OURS.test(cur.command);
835
+ const command = cmd?.([NAME, "StatusLine"]).command;
836
+ if (want && cur && !ours)
837
+ return { file, changed: false, skipped: true, note: "a status line of your own is set; limits are not read" };
838
+ if (want ? ours && cur?.command === command && cur?.refreshInterval === LINE_REFRESH_S : !ours) return null;
839
+ if (want) f.value.statusLine = { type: "command", command, refreshInterval: LINE_REFRESH_S };
840
+ else delete f.value.statusLine;
841
+ writeFileSync2(file, formatJson(f.value, f));
842
+ return { file, changed: true, note: want ? "status line written (account limits)" : "status line removed" };
843
+ }
844
+ var epochIso = (t) => typeof t === "number" && t > 0 && t < 1e11 ? new Date(t * 1e3).toISOString() : void 0;
845
+ var WINDOW_MINUTES = { five_hour: 300, seven_day: 10080 };
846
+ function claudeLimits(rl) {
847
+ if (!rl || typeof rl !== "object" || Array.isArray(rl)) return null;
848
+ const windows = [];
849
+ for (const [id, w] of Object.entries(rl)) {
850
+ if (!/^[a-z0-9_]{1,64}$/.test(id) || typeof w?.used_percentage !== "number" || w.used_percentage < 0) continue;
851
+ windows.push(
852
+ prune({
853
+ id,
854
+ used_percent: w.used_percentage,
855
+ window_minutes: WINDOW_MINUTES[id],
856
+ resets_at: epochIso(w.resets_at)
857
+ })
858
+ );
859
+ }
860
+ return windows.length ? { windows: windows.slice(0, 8) } : null;
861
+ }
681
862
  function prune(o) {
682
863
  for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
683
864
  return o;
684
865
  }
685
- function agPath(p) {
686
- if (typeof p !== "string" || !p) return null;
687
- let s = p;
688
- if (/^file:/i.test(s)) {
689
- try {
690
- s = decodeURIComponent(new URL(s).pathname);
691
- } catch {
692
- return null;
693
- }
694
- if (process.platform === "win32" && /^\/[A-Za-z]:/.test(s)) s = s.slice(1);
695
- } else if (/^[a-z][a-z0-9+.-]*:\/\//i.test(s)) return null;
696
- if (!path4.isAbsolute(s)) return null;
697
- return path4.resolve(s);
866
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
867
+ function notClaudeCode(id, s) {
868
+ return typeof s.transcript_path !== "string" && !UUID.test(id);
698
869
  }
699
- var agWorkspaces = (paths) => (Array.isArray(paths) ? paths : []).map(agPath).filter((p) => !!p).filter((p, i, a) => a.indexOf(p) === i);
700
- function agVariants(p) {
701
- const out = [p];
702
- try {
703
- const r = realpathSync.native(p);
704
- if (r !== p) out.push(r);
705
- } catch {
870
+ function pickSource(v) {
871
+ return typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
872
+ }
873
+ var harnessWord = (v) => typeof v === "string" && /^[A-Za-z0-9][A-Za-z0-9_.-]{0,39}$/.test(v) ? v : void 0;
874
+ var WRAPPER = /^<(?:task-notification|cross-session-message|system-reminder|local-command-stdout|command-[a-z-]+)[\s/>]/i;
875
+ var injectedBlock = (prompt) => WRAPPER.test(prompt.trim());
876
+ function errText(v) {
877
+ if (typeof v === "string") return v.slice(0, 2e3);
878
+ if (v && typeof v === "object") {
879
+ const e = v;
880
+ if (typeof e.error === "string") return e.error.slice(0, 2e3);
881
+ if (typeof e.message === "string") return e.message.slice(0, 2e3);
706
882
  }
707
- return out;
883
+ return void 0;
708
884
  }
709
- function agProjects(dir) {
710
- const out = [];
885
+ function describe(input) {
886
+ if (!input || typeof input !== "object") return "";
887
+ const i = input;
888
+ const v = i.command ?? i.file_path ?? i.path ?? i.url ?? i.pattern;
889
+ return typeof v === "string" ? ` ${v.slice(0, 120)}` : "";
890
+ }
891
+ var NAME_RANK = {
892
+ user: "custom",
893
+ auto: "harness",
894
+ hook: "harness",
895
+ peer: "harness"
896
+ };
897
+ function registryEntry(j) {
898
+ if (!j || typeof j !== "object") return null;
899
+ const r = j;
900
+ const source = r.nameSource ? NAME_RANK[r.nameSource] : void 0;
901
+ return {
902
+ sessionId: r.sessionId ? String(r.sessionId) : null,
903
+ name: source && typeof r.name === "string" && r.name.trim() ? r.name.trim() : null,
904
+ name_source: source ?? null,
905
+ bridge: typeof r.bridgeSessionId === "string" ? r.bridgeSessionId : null
906
+ };
907
+ }
908
+ var liveCache = /* @__PURE__ */ new Map();
909
+ function claudeLive(dir) {
910
+ const hit = liveCache.get(dir);
911
+ if (hit) return hit;
912
+ const map = /* @__PURE__ */ new Map();
711
913
  let names = [];
712
914
  try {
713
- names = readdirSync2(dir).filter((n) => n.endsWith(".json"));
915
+ names = readdirSync2(path4.join(dir, "sessions")).filter((n) => n.endsWith(".json"));
714
916
  } catch {
715
- return out;
716
917
  }
717
918
  for (const n of names) {
718
- const id = n.slice(0, -5);
719
- if (!ID_RE.test(id)) continue;
720
- let j;
721
919
  try {
722
- j = JSON.parse(readFileSync2(path4.join(dir, n), "utf8"));
920
+ const e = registryEntry(JSON.parse(readFileSync2(path4.join(dir, "sessions", n), "utf8")));
921
+ if (e?.sessionId) map.set(e.sessionId, e);
723
922
  } catch {
724
- continue;
725
- }
726
- const folders = [];
727
- for (const r of j?.projectResources?.resources ?? []) {
728
- const f = agPath(r?.folderUri ?? r?.gitFolder?.folderUri);
729
- if (f) folders.push(...agVariants(f));
730
923
  }
731
- if (folders.length) out.push({ id, folders });
732
924
  }
733
- return out;
925
+ liveCache.set(dir, map);
926
+ return map;
734
927
  }
735
- function agMatchProject(projects, workspaces) {
736
- const ws = [];
737
- for (const w of workspaces) {
738
- const p = agPath(w);
739
- if (p) ws.push(...agVariants(p));
928
+ function claudeLiveByPid(dir, pid) {
929
+ if (!pid) return null;
930
+ try {
931
+ return registryEntry(JSON.parse(readFileSync2(path4.join(dir, "sessions", `${pid}.json`), "utf8")));
932
+ } catch {
933
+ return null;
740
934
  }
741
- let best = null;
742
- let bestLen = -1;
743
- for (const pr of projects)
744
- for (const f of pr.folders) {
745
- const within = ws.some((w) => w === f || w.startsWith(f.endsWith(path4.sep) ? f : f + path4.sep));
746
- if (within && f.length > bestLen) {
747
- best = pr.id;
748
- bestLen = f.length;
935
+ }
936
+ function claudePid(env = process.env) {
937
+ try {
938
+ const dirs = claudeDirs(env);
939
+ const has = (pid2) => dirs.some((d) => existsSync2(path4.join(d, "sessions", `${pid2}.json`)));
940
+ let pid = process.ppid;
941
+ if (has(pid)) return pid;
942
+ const parent = parentOf();
943
+ for (let hop = 0; hop < 6 && pid > 1; hop++) {
944
+ pid = parent(pid);
945
+ if (pid > 1 && has(pid)) return pid;
946
+ }
947
+ } catch {
948
+ }
949
+ return null;
950
+ }
951
+ function parentOf() {
952
+ if (process.platform === "linux") {
953
+ return (pid) => {
954
+ try {
955
+ const st = readFileSync2(`/proc/${pid}/stat`, "utf8");
956
+ return Number(st.slice(st.lastIndexOf(")") + 2).split(" ")[1]) || 0;
957
+ } catch {
958
+ return 0;
749
959
  }
960
+ };
961
+ }
962
+ const table = /* @__PURE__ */ new Map();
963
+ try {
964
+ const out = execFileSync2("ps", ["-axo", "pid=,ppid="], {
965
+ encoding: "utf8",
966
+ timeout: 2e3,
967
+ maxBuffer: 8 * 1024 * 1024,
968
+ stdio: ["ignore", "pipe", "ignore"]
969
+ });
970
+ for (const l of out.split("\n")) {
971
+ const m = /^\s*(\d+)\s+(\d+)/.exec(l);
972
+ if (m) table.set(Number(m[1]), Number(m[2]));
750
973
  }
751
- return best;
974
+ } catch {
975
+ }
976
+ return (pid) => table.get(pid) || 0;
752
977
  }
753
- function antigravityRemoteUrl(transcript, opts) {
754
- const i = transcript.indexOf(`${path4.sep}brain${path4.sep}`);
755
- const dirs = (i > 0 ? [transcript.slice(0, i)] : []).concat(dataDirs(opts.env));
756
- for (const d of dirs) {
978
+ var EMAIL_RE = /^[^@\s]{1,64}@[^@\s]{1,189}$/;
979
+ function claudeLogin(dir, home = homeDir()) {
980
+ const files = [path4.join(dir, ".claude.json")];
981
+ if (path4.resolve(dir) === path4.join(home, ".claude")) files.unshift(path4.join(home, ".claude.json"));
982
+ for (const f of files) {
757
983
  try {
758
- const m = /installation_uuid:\s*"([0-9a-f-]{36})"/i.exec(
759
- readFileSync2(path4.join(d, "antigravity_state.pbtxt"), "utf8")
760
- );
761
- if (!m) continue;
762
- const base = `https://antigravity.google.com/r/${m[1]}-v2`;
763
- const conv = opts.conversation ?? "";
764
- if (!ID_RE.test(conv) || !opts.workspaces?.length) return base;
765
- const project = agMatchProject(agProjects(opts.projectsDir ?? projectsDir(opts.env)), opts.workspaces);
766
- return project ? `${base}?p=${encodeURIComponent(`c/${conv}?section=${project}`)}` : base;
984
+ const a = JSON.parse(readFileSync2(f, "utf8")).oauthAccount;
985
+ if (typeof a?.accountUuid === "string" && a.accountUuid) {
986
+ const email = typeof a.emailAddress === "string" && EMAIL_RE.test(a.emailAddress) ? a.emailAddress : void 0;
987
+ return { id: a.accountUuid.slice(0, 128), ...email ? { email } : {} };
988
+ }
767
989
  } catch {
768
990
  }
769
991
  }
770
- return null;
992
+ return apiAccount(dir);
771
993
  }
772
- function antigravityFacts(file, opts) {
773
- const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
994
+ var ANTHROPIC_HOST = /(^|\.)anthropic\.com$/i;
995
+ function apiAccount(dir) {
996
+ const base = readJson(settingsIn(dir)).env?.ANTHROPIC_BASE_URL;
997
+ if (typeof base !== "string") return null;
998
+ let u;
999
+ try {
1000
+ u = new URL(base.trim());
1001
+ } catch {
1002
+ return null;
1003
+ }
1004
+ if (u.protocol !== "https:" && u.protocol !== "http:" || !u.hostname || ANTHROPIC_HOST.test(u.hostname))
1005
+ return null;
1006
+ const origin = `${u.protocol}//${u.host}`;
1007
+ return { id: `api-${createHash2("sha256").update(origin).digest("hex").slice(0, 16)}`, name: u.host };
1008
+ }
1009
+ var accountCache = /* @__PURE__ */ new Map();
1010
+ function claudeAccount(dir) {
1011
+ const hit = accountCache.get(dir);
1012
+ if (hit !== void 0) return hit;
1013
+ const acct = claudeLogin(dir)?.id ?? null;
1014
+ accountCache.set(dir, acct);
1015
+ return acct;
1016
+ }
1017
+ var LINK_RE = /^https:\/\/claude\.ai\/code\/session_[A-Za-z0-9]+$/;
1018
+ function transcriptLink(records) {
1019
+ for (let i = records.length - 1; i >= 0; i--) {
1020
+ const j = records[i];
1021
+ if (j.type === "system" && j.subtype === "bridge_status" && typeof j.url === "string" && LINK_RE.test(j.url))
1022
+ return j.url;
1023
+ if (j.type === "bridge-session" && typeof j.bridgeSessionId === "string") {
1024
+ const m = /^(?:cse|session)_([A-Za-z0-9]+)$/.exec(j.bridgeSessionId);
1025
+ if (m) return `https://claude.ai/code/session_${m[1]}`;
1026
+ }
1027
+ }
1028
+ return null;
1029
+ }
1030
+ function appTitle(text) {
1031
+ const t = String(text).replace(/\s+/g, " ").trim();
1032
+ return t.length > 50 ? `${t.slice(0, 50)}...` : t;
1033
+ }
1034
+ function askText(j) {
1035
+ if (!j || j.type !== "user" || j.isMeta || j.isSidechain) return null;
1036
+ const origin = j.origin;
1037
+ if (origin?.kind && origin.kind !== "human") return null;
1038
+ const c = j.message?.content;
1039
+ const text = (typeof c === "string" ? c : Array.isArray(c) ? c.filter((b) => b && b.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n") : "").trim();
1040
+ if (!text || isInjected(text)) return null;
1041
+ return text;
1042
+ }
1043
+ function firstAsk(records) {
1044
+ for (const j of records) {
1045
+ const t = askText(j);
1046
+ if (t) return appTitle(t);
1047
+ }
1048
+ return null;
1049
+ }
1050
+ function scanTitles(file, from, found) {
1051
+ const out = { custom: found.custom ?? null, ai: found.ai ?? null, upto: from };
1052
+ let size = 0;
1053
+ try {
1054
+ size = statSync2(file).size;
1055
+ } catch {
1056
+ return out;
1057
+ }
1058
+ if (size < from) {
1059
+ out.upto = 0;
1060
+ out.custom = out.ai = null;
1061
+ from = 0;
1062
+ }
1063
+ let pos = 0;
1064
+ scanLines(
1065
+ file,
1066
+ (l) => {
1067
+ pos += Buffer.byteLength(l, "utf8") + 1;
1068
+ if (pos <= from || l.indexOf('-title"') < 0) return null;
1069
+ try {
1070
+ const j = JSON.parse(l);
1071
+ if (j.type === "custom-title" && j.customTitle) out.custom = j.customTitle;
1072
+ if (j.type === "ai-title" && j.aiTitle) out.ai = j.aiTitle;
1073
+ } catch {
1074
+ }
1075
+ return null;
1076
+ },
1077
+ 512 * 1024 * 1024
1078
+ );
1079
+ out.upto = size;
1080
+ return out;
1081
+ }
1082
+ function claudeFacts(file, sessionId, hints, state) {
1083
+ const { head, tail } = ends(file, 64 * 1024, 256 * 1024);
774
1084
  const H = parseLines(head);
775
1085
  const T = parseLines(tail);
776
1086
  const out = {};
777
- const url = antigravityRemoteUrl(file, opts);
778
- if (url) out.url = url;
779
- for (const j of H) {
780
- if (j.type !== "USER_INPUT" || typeof j.content !== "string") continue;
781
- const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
782
- const text = (m ? m[1] : j.content).trim();
783
- const line = (text.split("\n").find((l) => l.trim()) ?? "").trim();
784
- if (!line || /^<[a-z_-]+>/i.test(line)) continue;
785
- out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
1087
+ const id = sessionId || path4.basename(file, ".jsonl");
1088
+ const mem = state.get(NAME, id);
1089
+ let custom = null;
1090
+ let ai = null;
1091
+ for (const j of H.concat(T)) {
1092
+ if (j.type === "custom-title" && typeof j.customTitle === "string") custom = j.customTitle;
1093
+ if (j.type === "ai-title" && typeof j.aiTitle === "string") ai = j.aiTitle;
1094
+ }
1095
+ let size = 0;
1096
+ try {
1097
+ size = statSync2(file).size;
1098
+ } catch {
1099
+ }
1100
+ if (!custom && !ai && size > 64 * 1024 + 256 * 1024) {
1101
+ const s = scanTitles(file, Number(mem.scan_upto) || 0, {
1102
+ custom: mem.scan_custom,
1103
+ ai: mem.scan_ai
1104
+ });
1105
+ custom = s.custom;
1106
+ ai = s.ai;
1107
+ state.save(NAME, id, { scan_upto: s.upto, scan_custom: s.custom, scan_ai: s.ai });
1108
+ }
1109
+ const dir = configDirOf(file);
1110
+ const live = claudeLive(dir).get(id) ?? claudeLiveByPid(dir, hints.claude_pid);
1111
+ if (live?.name) state.save(NAME, id, { name: live.name, name_source: live.name_source });
1112
+ const bridge = live?.bridge && /^session_[A-Za-z0-9]+$/.test(live.bridge) ? `https://claude.ai/code/${live.bridge}` : null;
1113
+ const url = bridge ?? transcriptLink(T) ?? transcriptLink(H) ?? (typeof mem.url === "string" ? mem.url : null);
1114
+ if (url) {
1115
+ out.url = url;
1116
+ state.save(NAME, id, { url });
1117
+ }
1118
+ const name = live?.name ? { title: live.name, source: live.name_source } : typeof mem.name === "string" ? { title: mem.name, source: mem.name_source } : null;
1119
+ const pick = name && name.source === "custom" && name || custom && { title: custom, source: "custom" } || name || ai && { title: ai, source: "harness" };
1120
+ let ask2 = typeof mem.first_ask === "string" ? mem.first_ask : firstAsk(H);
1121
+ if (!ask2 && size > 64 * 1024 && !mem.first_ask_none) {
1122
+ ask2 = scanLines(file, (l) => {
1123
+ try {
1124
+ const t = askText(JSON.parse(l));
1125
+ return t ? appTitle(t) : null;
1126
+ } catch {
1127
+ return null;
1128
+ }
1129
+ });
1130
+ if (!ask2 && size > 16 * 1024 * 1024) state.save(NAME, id, { first_ask_none: true });
1131
+ }
1132
+ if (ask2) {
1133
+ out.first_ask = ask2;
1134
+ if (!mem.first_ask) state.save(NAME, id, { first_ask: ask2 });
1135
+ }
1136
+ if (pick) {
1137
+ out.title = String(pick.title).slice(0, 120);
1138
+ out.title_source = pick.source;
1139
+ } else if (ask2) {
1140
+ out.title = ask2;
786
1141
  out.title_source = "first-ask";
787
- break;
788
1142
  }
789
- const times = H.concat(T).map((j) => Date.parse(String(j.created_at ?? j.timestamp ?? ""))).filter((n) => n > 0);
1143
+ const acct = claudeAccount(dir);
1144
+ if (acct) out.account_id = acct;
1145
+ for (let i = T.length - 1; i >= 0; i--) {
1146
+ const m = T[i].message?.model;
1147
+ if (T[i]?.type === "assistant" && m && m !== "<synthetic>") {
1148
+ out.model = m;
1149
+ break;
1150
+ }
1151
+ }
1152
+ const meta = [...T].reverse().find((j) => j.version) ?? H.find((j) => j.version);
1153
+ if (meta?.version) out.harness_version = String(meta.version);
1154
+ const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
790
1155
  if (times.length) {
791
1156
  out.started_at = Math.min(...times);
792
1157
  out.last_at = Math.max(...times);
793
1158
  }
1159
+ const withCwd = H.find((j) => typeof j.cwd === "string");
1160
+ if (withCwd) {
1161
+ out.cwd = withCwd.cwd;
1162
+ if (typeof withCwd.gitBranch === "string" && withCwd.gitBranch !== "HEAD") out.branch = withCwd.gitBranch;
1163
+ }
794
1164
  return out;
795
1165
  }
796
-
797
- // ../core/src/adapters/claude-code.ts
798
- import { execFileSync as execFileSync2 } from "node:child_process";
799
- import { createHash as createHash2 } from "node:crypto";
800
- import { existsSync as existsSync3, readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as statSync3 } from "node:fs";
801
- import path5 from "node:path";
802
-
803
- // ../core/src/readers/git.ts
804
- import { execFileSync } from "node:child_process";
805
- var cache = /* @__PURE__ */ new Map();
806
- function gitFacts(cwd) {
807
- if (!cwd) return {};
808
- const hit = cache.get(cwd);
809
- if (hit) return hit;
810
- const run = (args) => {
1166
+ var REMOTE_RETRY_MS = 6e4;
1167
+ var REMOTE_REFRESH_MS = 30 * 6e4;
1168
+ var CLAUDE_API = "https://api.anthropic.com";
1169
+ var KEYCHAIN = "Claude Code-credentials";
1170
+ var REMOTE_TIMEOUT_MS = 3e3;
1171
+ var KEYCHAIN_TIMEOUT_MS = 2e3;
1172
+ var REMOTE_MAX_BYTES = 1 << 20;
1173
+ var cleanName = (s) => s.replace(/[\p{Cc}\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]/gu, " ").replace(/\s+/g, " ").trim();
1174
+ async function claudeRemoteFacts(id, local, transcript, state, env, now = Date.now()) {
1175
+ if (!transcript || !local.url || !LINK_RE.test(local.url) || local.title_source === "custom") return {};
1176
+ const mem = state.get(NAME, id);
1177
+ let title = typeof mem.remote_title === "string" ? mem.remote_title : null;
1178
+ if (now - (Number(mem.remote_at) || 0) >= (title ? REMOTE_REFRESH_MS : REMOTE_RETRY_MS)) {
1179
+ state.save(NAME, id, { remote_at: now });
1180
+ const token = claudeAiToken(configDirOf(transcript), env, now);
1181
+ const got = token ? await claudeAiTitle(local.url.slice(local.url.lastIndexOf("/") + 1), token) : void 0;
1182
+ if (got !== void 0) {
1183
+ title = got?.slice(0, 200) ?? null;
1184
+ state.save(NAME, id, { remote_title: title });
1185
+ }
1186
+ }
1187
+ return title ? { title: title.slice(0, 120), title_source: "harness" } : {};
1188
+ }
1189
+ function keychainServices(dir, env) {
1190
+ const named = (p) => `${KEYCHAIN}-${createHash2("sha256").update(p.normalize("NFC")).digest("hex").slice(0, 8)}`;
1191
+ const out = /* @__PURE__ */ new Set();
1192
+ if (env.CLAUDE_CONFIG_DIR && path4.resolve(env.CLAUDE_CONFIG_DIR) === dir) out.add(named(env.CLAUDE_CONFIG_DIR));
1193
+ if (dir === path4.join(env.HOME || homeDir(), ".claude")) out.add(KEYCHAIN);
1194
+ out.add(named(dir));
1195
+ return [...out];
1196
+ }
1197
+ var tokenCache = /* @__PURE__ */ new Map();
1198
+ function claudeAiToken(dir, env, now) {
1199
+ const hit = tokenCache.get(dir);
1200
+ if (hit && hit.expires > now + 6e4) return hit.token;
1201
+ const raw = [];
1202
+ if (process.platform === "darwin")
1203
+ for (const service of keychainServices(dir, env)) {
1204
+ try {
1205
+ const user = env.USER || userInfo().username;
1206
+ const account = /^[\w.-]+$/.test(user) ? user : "claude-code-user";
1207
+ raw.push(
1208
+ execFileSync2("security", ["find-generic-password", "-a", account, "-w", "-s", service], {
1209
+ encoding: "utf8",
1210
+ timeout: KEYCHAIN_TIMEOUT_MS,
1211
+ stdio: ["ignore", "pipe", "ignore"]
1212
+ })
1213
+ );
1214
+ break;
1215
+ } catch {
1216
+ }
1217
+ }
1218
+ try {
1219
+ raw.push(readFileSync2(path4.join(dir, ".credentials.json"), "utf8"));
1220
+ } catch {
1221
+ }
1222
+ for (const r of raw) {
811
1223
  try {
812
- return execFileSync("git", ["-C", cwd, ...args], {
813
- encoding: "utf8",
814
- timeout: 1500,
815
- stdio: ["ignore", "pipe", "ignore"]
816
- }).trim();
1224
+ const t = r.trim();
1225
+ const o = JSON.parse(/^(?:[0-9a-f]{2})+$/i.test(t) ? Buffer.from(t, "hex").toString("utf8") : t).claudeAiOauth;
1226
+ if (typeof o?.accessToken !== "string" || !o.accessToken) continue;
1227
+ const expires = typeof o.expiresAt === "number" ? o.expiresAt : Number.POSITIVE_INFINITY;
1228
+ if (expires <= now + 6e4) continue;
1229
+ tokenCache.set(dir, { token: o.accessToken, expires });
1230
+ return o.accessToken;
817
1231
  } catch {
818
- return "";
819
1232
  }
820
- };
821
- const out = {};
822
- const repo = run(["remote", "get-url", "origin"]);
823
- if (repo) out.repo = stripCredentials(repo);
824
- const branch = run(["rev-parse", "--abbrev-ref", "HEAD"]);
825
- if (branch && branch !== "HEAD") out.branch = branch;
826
- cache.set(cwd, out);
827
- return out;
1233
+ }
1234
+ return null;
828
1235
  }
829
- function stripCredentials(url) {
830
- const ssh = /^(?:ssh:\/\/)?git@([^:/]+)[:/](.+?)(?:\.git)?$/.exec(url);
831
- if (ssh) return `${ssh[1]}/${ssh[2]}`;
832
- return url.replace(/\/\/[^/@]*@/, "//").replace(/^https?:\/\//, "").replace(/\.git$/, "");
1236
+ async function claudeAiTitle(bridge, token) {
1237
+ try {
1238
+ const r = await fetch(`${CLAUDE_API}/v1/code/sessions/${bridge}`, {
1239
+ headers: { Authorization: `Bearer ${token}`, "anthropic-version": "2023-06-01", "User-Agent": "sessionpipe" },
1240
+ redirect: "error",
1241
+ signal: AbortSignal.timeout(REMOTE_TIMEOUT_MS)
1242
+ });
1243
+ if (r.status !== 200 || Number(r.headers.get("content-length")) > REMOTE_MAX_BYTES) return void 0;
1244
+ const text = await r.text();
1245
+ if (text.length > REMOTE_MAX_BYTES) return void 0;
1246
+ const j = JSON.parse(text);
1247
+ const s = j?.response_shape ?? j?.session ?? j;
1248
+ const bare = (x) => x.replace(/^(?:session|cse)_/, "");
1249
+ if (typeof s?.id !== "string" || bare(s.id) !== bare(bridge)) return void 0;
1250
+ return typeof s.title === "string" && cleanName(s.title) || null;
1251
+ } catch {
1252
+ return void 0;
1253
+ }
833
1254
  }
834
1255
 
835
- // ../core/src/adapters/claude-code.ts
836
- var NAME2 = "claude-code";
837
- var CLAUDE_EVENTS = [
838
- "SessionStart",
839
- "UserPromptSubmit",
840
- "PreToolUse",
841
- "PostToolUse",
842
- "PostToolUseFailure",
843
- "PermissionRequest",
844
- "Notification",
845
- "Stop",
846
- "SubagentStart",
847
- "SubagentStop",
848
- "PreCompact",
849
- "PostCompact",
850
- "SessionEnd",
851
- "StopFailure"
852
- ];
853
- var LEAN_SKIP = /* @__PURE__ */ new Set(["PreToolUse", "PostToolUseFailure"]);
854
- var eventsFor = (lean) => lean ? CLAUDE_EVENTS.filter((e) => !LEAN_SKIP.has(e)) : [...CLAUDE_EVENTS];
855
- var OPTS = {
856
- matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0,
857
- timeout: 5,
858
- // The hook waits up to 125 s for a verified control answer, and only when control
859
- // is paired on this machine; everywhere else it exits in milliseconds as before.
860
- timeoutFor: (e) => e === "PermissionRequest" ? 130 : void 0
861
- };
862
- var DESKTOP_ID = /^local_[A-Za-z0-9-]{1,64}$/;
863
- function claudeDirs(env = process.env, home = env.HOME || homeDir()) {
864
- const out = [];
865
- const add = (d) => {
866
- if (!d) return;
867
- const r = path5.resolve(d);
868
- if (!out.includes(r)) out.push(r);
869
- };
870
- add(path5.join(home, ".claude"));
871
- add(env.CLAUDE_CONFIG_DIR);
1256
+ // ../core/src/adapters/headless.ts
1257
+ import { accessSync, constants, statSync as statSync3 } from "node:fs";
1258
+ import path5 from "node:path";
1259
+ var JOB_TIMEOUT_MS = 30 * 6e4;
1260
+ var OUT_CAP = 1024 * 1024;
1261
+ function isExe(p) {
872
1262
  try {
873
- for (const e of readdirSync3(home, { withFileTypes: true })) {
874
- if (!e.isDirectory() || !/^\.claude[-_.]/.test(e.name)) continue;
875
- const d = path5.join(home, e.name);
876
- if (existsSync3(path5.join(d, "projects")) || existsSync3(path5.join(d, "settings.json"))) add(d);
877
- }
1263
+ if (!statSync3(p).isFile()) return false;
1264
+ accessSync(p, constants.X_OK);
1265
+ return true;
878
1266
  } catch {
1267
+ return false;
879
1268
  }
880
- return out.filter((d, i) => i === 0 || existsSync3(d));
881
1269
  }
882
- var settingsIn = (dir) => path5.join(dir, "settings.json");
883
- var configDirOf = (transcript) => path5.dirname(path5.dirname(path5.dirname(transcript)));
1270
+ function findBinary(o) {
1271
+ if (o.override && isExe(o.override)) return o.override;
1272
+ for (const d of String(o.env?.PATH || "").split(path5.delimiter)) {
1273
+ if (!d || !path5.isAbsolute(d)) continue;
1274
+ for (const n of o.names) if (isExe(path5.join(d, n))) return path5.join(d, n);
1275
+ }
1276
+ for (const d of o.dirs) for (const n of o.names) if (isExe(path5.join(d, n))) return path5.join(d, n);
1277
+ return null;
1278
+ }
1279
+
1280
+ // ../core/src/adapters/antigravity.ts
1281
+ import {
1282
+ existsSync as existsSync3,
1283
+ mkdirSync as mkdirSync2,
1284
+ readdirSync as readdirSync3,
1285
+ readFileSync as readFileSync3,
1286
+ realpathSync,
1287
+ statSync as statSync4,
1288
+ unlinkSync as unlinkSync2,
1289
+ writeFileSync as writeFileSync3
1290
+ } from "node:fs";
1291
+ import path6 from "node:path";
1292
+ var NAME2 = "antigravity";
1293
+ var HOOK_NAME = "sessionpipe";
1294
+ var AG_EVENTS = ["PreInvocation", "PostInvocation", "PostToolUse", "Stop"];
1295
+ var AG_STALE_PRETOOL = '{"decision":"ask","reason":"sessionpipe: an outdated PreToolUse hook is still loaded. Restart this conversation (or run sessionpipe install) to drop it."}\n';
1296
+ var antigravityAnswer = (event) => event === "PreToolUse" ? AG_STALE_PRETOOL : "{}\n";
1297
+ var gemini = (env) => env.GEMINI_CLI_HOME || path6.join(homeDir(), ".gemini");
1298
+ var hooksFile = (env) => path6.join(gemini(env), "config", "hooks.json");
1299
+ var projectsDir = (env) => path6.join(gemini(env), "config", "projects");
1300
+ var dataDirs = (env) => ["antigravity", "antigravity-cli", "antigravity-ide"].map((d) => path6.join(gemini(env), d));
1301
+ var ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
884
1302
  var validTool2 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
885
- var endReason = (r) => r === "clear" || r === "logout" || r === "exit" ? r : r === "prompt_input_exit" ? "exit" : "other";
886
- var notifKind = (t) => t === "permission_prompt" ? "permission" : t === "idle_prompt" ? "idle" : t === "elicitation_dialog" ? "elicitation" : "question";
887
- var claudeCode = {
1303
+ function entry(cmd) {
1304
+ const handler = (e) => ({ type: "command", command: cmd([NAME2, e]).command, timeout: 5 });
1305
+ const out = { enabled: true };
1306
+ for (const e of AG_EVENTS) out[e] = /Tool/.test(e) ? [{ matcher: "*", hooks: [handler(e)] }] : [handler(e)];
1307
+ return out;
1308
+ }
1309
+ var antigravity = {
888
1310
  name: NAME2,
889
- events: CLAUDE_EVENTS,
890
- detect: (env = process.env) => claudeDirs(env).some((d) => existsSync3(d)),
891
- configFiles: (env = process.env) => claudeDirs(env).map(settingsIn),
892
- install: (cmd, env = process.env, opts = {}) => claudeDirs(env).map((d) => installClaudeShaped(settingsIn(d), NAME2, eventsFor(opts.lean), cmd, OPTS)),
893
- uninstall: (env = process.env, opts = {}) => claudeDirs(env).map(
894
- (d) => uninstallClaudeShaped(settingsIn(d), CLAUDE_EVENTS, { created: opts.created?.includes(settingsIn(d)) })
895
- ),
896
- installed: (cmd, env = process.env, opts = {}) => claudeDirs(env).map((d) => ({
897
- file: settingsIn(d),
898
- state: installedClaudeShaped(settingsIn(d), NAME2, eventsFor(opts.lean), cmd, OPTS)
899
- })),
1311
+ events: AG_EVENTS,
1312
+ detect: (env = process.env) => existsSync3(path6.join(gemini(env), "config")) || dataDirs(env).some((d) => existsSync3(d)),
1313
+ configFiles: (env = process.env) => [hooksFile(env)],
1314
+ install(cmd, env = process.env) {
1315
+ const file = hooksFile(env);
1316
+ const f = readJsonFile(file);
1317
+ if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
1318
+ const hooks = f.value;
1319
+ const e = entry(cmd);
1320
+ if (JSON.stringify(hooks[HOOK_NAME]) === JSON.stringify(e)) return [{ file, changed: false }];
1321
+ hooks[HOOK_NAME] = e;
1322
+ mkdirSync2(path6.dirname(file), { recursive: true });
1323
+ writeFileSync3(file, formatJson(hooks, f));
1324
+ return [
1325
+ {
1326
+ file,
1327
+ changed: true,
1328
+ created: !f.existed,
1329
+ note: "hooks.json is read when a conversation starts; open ones report after a restart"
1330
+ }
1331
+ ];
1332
+ },
1333
+ uninstall(env = process.env, opts = {}) {
1334
+ const file = hooksFile(env);
1335
+ if (!existsSync3(file)) return [{ file, changed: false }];
1336
+ const f = readJsonFile(file);
1337
+ if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
1338
+ const hooks = f.value;
1339
+ if (!(HOOK_NAME in hooks)) return [{ file, changed: false }];
1340
+ delete hooks[HOOK_NAME];
1341
+ if (opts.created?.includes(file) && !Object.keys(hooks).length) {
1342
+ unlinkSync2(file);
1343
+ return [{ file, changed: true, note: "removed (sessionpipe created it)" }];
1344
+ }
1345
+ writeFileSync3(file, formatJson(hooks, f));
1346
+ return [{ file, changed: true }];
1347
+ },
1348
+ installed(cmd, env = process.env) {
1349
+ const file = hooksFile(env);
1350
+ const f = readJsonFile(file);
1351
+ const have = f.ok ? f.value[HOOK_NAME] : void 0;
1352
+ return [
1353
+ {
1354
+ file,
1355
+ state: !have ? "missing" : JSON.stringify(have) === JSON.stringify(entry(cmd)) ? "current" : "stale"
1356
+ }
1357
+ ];
1358
+ },
900
1359
  fromHook(input) {
901
1360
  const event = input.argv[1] ?? "";
902
1361
  let s = {};
@@ -905,133 +1364,48 @@ var claudeCode = {
905
1364
  if (j && typeof j === "object" && !Array.isArray(j)) s = j;
906
1365
  } catch {
907
1366
  }
908
- const id = s.session_id ?? s.sessionId;
909
- if (!id) return null;
910
- if (notClaudeCode(String(id), s)) return null;
911
- const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
912
- if (typeof s.model === "string") session.model = s.model;
913
- if (typeof s.agent_id === "string" && event.startsWith("Subagent") === false && s.agent_id)
914
- session.parent_id = null;
915
- const turn_id = typeof s.prompt_id === "string" ? s.prompt_id : void 0;
916
- const tool = validTool2(s.tool_name) ? s.tool_name : void 0;
917
- const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
918
- const ms = typeof s.duration_ms === "number" ? Math.round(s.duration_ms) : void 0;
919
- const agent_id = typeof s.agent_id === "string" ? s.agent_id : void 0;
920
- const permissionId = () => `perm-${createHash2("sha256").update(`${turn_id ?? ""}|${tool ?? ""}|${JSON.stringify(s.tool_input ?? null)}`).digest("hex").slice(0, 16)}`;
1367
+ const id = s.conversationId ?? s.session_id ?? s.sessionId;
1368
+ const stdout = antigravityAnswer(event);
1369
+ if (!id) return { session: { id: "" }, events: [], stdout };
1370
+ const workspaces = agWorkspaces(s.workspacePaths);
1371
+ const session = { id: String(id), cwd: workspaces[0] ?? input.cwd };
1372
+ if (typeof s.modelName === "string" && s.modelName !== "auto") session.model = s.modelName;
1373
+ const toolCall = s.toolCall;
1374
+ const tool = validTool2(toolCall?.name) ? toolCall?.name : validTool2(s.toolName) ? s.toolName : void 0;
921
1375
  const ev = (type, data) => ({ type, data: prune2(data), harnessEvent: event });
922
1376
  const events = [];
923
1377
  switch (event) {
924
- case "SessionStart":
925
- events.push(ev("session.started", { source: pickSource(s.source ?? s.trigger) }));
1378
+ case "PreInvocation":
1379
+ if (Number(s.invocationNum) === 0 || s.invocationNum === void 0) {
1380
+ events.push(ev("session.started", { source: "unknown" }));
1381
+ events.push(ev("turn.started", {}));
1382
+ } else events.push(ev("session.heartbeat", {}));
926
1383
  break;
927
- case "UserPromptSubmit":
928
- events.push(
929
- ev("turn.started", {
930
- turn_id,
931
- prompt_chars: typeof s.prompt === "string" ? [...s.prompt].length : void 0
932
- })
933
- );
1384
+ case "PostInvocation":
934
1385
  break;
935
1386
  case "PreToolUse":
936
- if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, agent_id, input: s.tool_input }));
1387
+ if (tool) events.push(ev("tool.started", { tool, input: toolCall?.args }));
937
1388
  break;
938
1389
  case "PostToolUse":
939
- if (tool)
940
- events.push(
941
- ev("tool.ended", {
942
- tool,
943
- call_id,
944
- turn_id,
945
- agent_id,
946
- ms,
947
- ok: true,
948
- input: s.tool_input,
949
- output: s.tool_response
950
- })
951
- );
952
- break;
953
- case "PostToolUseFailure":
954
- if (tool)
1390
+ if (!tool) events.push(ev("session.heartbeat", {}));
1391
+ else
955
1392
  events.push(
956
1393
  ev("tool.ended", {
957
1394
  tool,
958
- call_id,
959
- turn_id,
960
- agent_id,
961
- ms,
962
- ok: false,
963
- error: errText(s.error ?? s.tool_response),
964
- input: s.tool_input,
965
- output: s.tool_response
1395
+ ok: !s.error,
1396
+ error: typeof s.error === "string" ? s.error.slice(0, 2e3) : void 0,
1397
+ input: toolCall?.args
966
1398
  })
967
1399
  );
968
1400
  break;
969
- case "PermissionRequest":
970
- events.push(
971
- ev("attention.needed", {
972
- attention_id: call_id ?? permissionId(),
973
- kind: "permission",
974
- tool,
975
- message: tool ? `${tool}${describe(s.tool_input)}` : void 0
976
- })
977
- );
978
- break;
979
- case "Notification":
980
- events.push(
981
- ev("attention.needed", {
982
- attention_id: `notif-${Date.now()}`,
983
- kind: notifKind(s.notification_type),
984
- message: typeof s.message === "string" ? s.message.slice(0, 200) : void 0
985
- })
986
- );
987
- break;
988
- case "Stop":
989
- events.push(ev("turn.ended", { turn_id, reason: "stop" }));
990
- break;
991
- case "StopFailure":
992
- events.push(
993
- ev("turn.ended", {
994
- turn_id,
995
- reason: "error",
996
- error: typeof s.error === "string" ? s.error.slice(0, 200) : void 0
997
- })
998
- );
999
- break;
1000
- case "SubagentStart":
1001
- events.push(
1002
- ev("subagent.started", {
1003
- agent_id: String(s.agent_id ?? ""),
1004
- agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
1005
- })
1006
- );
1007
- break;
1008
- case "SubagentStop":
1009
- events.push(
1010
- ev("subagent.ended", {
1011
- agent_id: String(s.agent_id ?? ""),
1012
- agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
1013
- })
1014
- );
1015
- break;
1016
- case "PreCompact":
1017
- events.push(ev("context.compacted", { trigger: s.trigger === "manual" ? "manual" : "auto" }));
1018
- break;
1019
- case "PostCompact":
1020
- break;
1021
- // PreCompact already said it; a second event would double-count
1022
- case "SessionEnd":
1023
- events.push(ev("session.ended", { reason: endReason(s.reason) }));
1024
- break;
1025
- default:
1026
- return null;
1027
- }
1028
- const hints = {};
1029
- if (event === "UserPromptSubmit" && typeof s.prompt === "string") hints.prompt = s.prompt;
1030
- const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
1031
- if (host && DESKTOP_ID.test(host)) hints.desktop_id = host;
1032
- if (event !== "PreToolUse" && event !== "PostToolUse") hints.claude_pid = claudePid(input.env);
1033
- const r = { session, events, hints };
1034
- if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
1401
+ case "Stop":
1402
+ events.push(ev("turn.ended", { reason: s.error ? "error" : "stop" }));
1403
+ break;
1404
+ default:
1405
+ return { session, events: [], stdout };
1406
+ }
1407
+ const r = { session, events, stdout, hints: { workspaces } };
1408
+ if (typeof s.transcriptPath === "string") r.transcript = s.transcriptPath;
1035
1409
  return r;
1036
1410
  },
1037
1411
  readTranscript(file, fromLine) {
@@ -1041,34 +1415,36 @@ var claudeCode = {
1041
1415
  } catch {
1042
1416
  return { turns: [], line: fromLine };
1043
1417
  }
1044
- return readClaude(text.split("\n"), fromLine);
1418
+ return readAntigravity(text.split("\n"), fromLine);
1045
1419
  },
1046
- facts(session, transcript, hints, state) {
1047
- const out = {};
1048
- if (transcript) Object.assign(out, claudeFacts(transcript, session.id, hints, state));
1049
- const cwd = out.cwd ?? session.cwd;
1050
- Object.assign(out, gitFacts(cwd), out.branch ? { branch: out.branch } : {});
1051
- if (!out.url && typeof hints.desktop_id === "string") out.url = `claude://code/continue/${hints.desktop_id}`;
1052
- return out;
1420
+ facts(session, transcript, hints) {
1421
+ if (!transcript) return {};
1422
+ return antigravityFacts(transcript, {
1423
+ conversation: session.id,
1424
+ workspaces: hints.workspaces ?? (session.cwd ? [session.cwd] : []),
1425
+ env: process.env
1426
+ });
1053
1427
  },
1054
1428
  backfill(sinceMs, env = process.env) {
1055
1429
  const rows2 = [];
1056
- const files = [];
1057
- for (const d of claudeDirs(env))
1058
- files.push(...recentFiles(path5.join(d, "projects"), 1, sinceMs, (n) => /^[0-9a-f-]{36}\.jsonl$/.test(n)));
1059
- const noState = { get: () => ({}), save: () => {
1060
- } };
1061
- for (const f of files) {
1062
- const id = path5.basename(f, ".jsonl");
1063
- const facts = claudeFacts(f, id, {}, noState);
1064
- if (!facts.started_at || !facts.last_at) continue;
1065
- const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
1066
- const cwd = rest.cwd;
1067
- rows2.push({
1068
- session: { id, ...rest, ...gitFacts(cwd), ...rest.branch ? { branch: rest.branch } : {} },
1069
- started_at,
1070
- last_at
1071
- });
1430
+ for (const data of dataDirs(env)) {
1431
+ let ids = [];
1432
+ try {
1433
+ ids = readdirSync3(path6.join(data, "brain")).filter((n) => /^[0-9a-f-]{36}$/i.test(n));
1434
+ } catch {
1435
+ }
1436
+ for (const id of ids) {
1437
+ const f = path6.join(data, "brain", id, ".system_generated", "logs", "transcript.jsonl");
1438
+ try {
1439
+ if (statSync4(f).mtimeMs < sinceMs) continue;
1440
+ } catch {
1441
+ continue;
1442
+ }
1443
+ const facts = antigravityFacts(f, { conversation: id, workspaces: [], env });
1444
+ if (!facts.started_at || !facts.last_at) continue;
1445
+ const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
1446
+ rows2.push({ session: { id, ...rest }, started_at, last_at });
1447
+ }
1072
1448
  }
1073
1449
  return rows2;
1074
1450
  }
@@ -1077,295 +1453,145 @@ function prune2(o) {
1077
1453
  for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
1078
1454
  return o;
1079
1455
  }
1080
- var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1081
- function notClaudeCode(id, s) {
1082
- return typeof s.transcript_path !== "string" && !UUID.test(id);
1083
- }
1084
- function pickSource(v) {
1085
- return typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
1086
- }
1087
- function errText(v) {
1088
- if (typeof v === "string") return v.slice(0, 2e3);
1089
- if (v && typeof v === "object") {
1090
- const e = v;
1091
- if (typeof e.error === "string") return e.error.slice(0, 2e3);
1092
- if (typeof e.message === "string") return e.message.slice(0, 2e3);
1093
- }
1094
- return void 0;
1095
- }
1096
- function describe(input) {
1097
- if (!input || typeof input !== "object") return "";
1098
- const i = input;
1099
- const v = i.command ?? i.file_path ?? i.path ?? i.url ?? i.pattern;
1100
- return typeof v === "string" ? ` ${v.slice(0, 120)}` : "";
1101
- }
1102
- var NAME_RANK = {
1103
- user: "custom",
1104
- auto: "harness",
1105
- hook: "harness",
1106
- peer: "harness"
1107
- };
1108
- function registryEntry(j) {
1109
- if (!j || typeof j !== "object") return null;
1110
- const r = j;
1111
- const source = r.nameSource ? NAME_RANK[r.nameSource] : void 0;
1112
- return {
1113
- sessionId: r.sessionId ? String(r.sessionId) : null,
1114
- name: source && typeof r.name === "string" && r.name.trim() ? r.name.trim() : null,
1115
- name_source: source ?? null,
1116
- bridge: typeof r.bridgeSessionId === "string" ? r.bridgeSessionId : null
1117
- };
1118
- }
1119
- var liveCache = /* @__PURE__ */ new Map();
1120
- function claudeLive(dir) {
1121
- const hit = liveCache.get(dir);
1122
- if (hit) return hit;
1123
- const map = /* @__PURE__ */ new Map();
1124
- let names = [];
1125
- try {
1126
- names = readdirSync3(path5.join(dir, "sessions")).filter((n) => n.endsWith(".json"));
1127
- } catch {
1128
- }
1129
- for (const n of names) {
1130
- try {
1131
- const e = registryEntry(JSON.parse(readFileSync3(path5.join(dir, "sessions", n), "utf8")));
1132
- if (e?.sessionId) map.set(e.sessionId, e);
1133
- } catch {
1134
- }
1135
- }
1136
- liveCache.set(dir, map);
1137
- return map;
1138
- }
1139
- function claudeLiveByPid(dir, pid) {
1140
- if (!pid) return null;
1141
- try {
1142
- return registryEntry(JSON.parse(readFileSync3(path5.join(dir, "sessions", `${pid}.json`), "utf8")));
1143
- } catch {
1144
- return null;
1145
- }
1146
- }
1147
- function claudePid(env = process.env) {
1148
- try {
1149
- const dirs = claudeDirs(env);
1150
- const has = (pid2) => dirs.some((d) => existsSync3(path5.join(d, "sessions", `${pid2}.json`)));
1151
- let pid = process.ppid;
1152
- if (has(pid)) return pid;
1153
- const parent = parentOf();
1154
- for (let hop = 0; hop < 6 && pid > 1; hop++) {
1155
- pid = parent(pid);
1156
- if (pid > 1 && has(pid)) return pid;
1157
- }
1158
- } catch {
1159
- }
1160
- return null;
1161
- }
1162
- function parentOf() {
1163
- if (process.platform === "linux") {
1164
- return (pid) => {
1165
- try {
1166
- const st = readFileSync3(`/proc/${pid}/stat`, "utf8");
1167
- return Number(st.slice(st.lastIndexOf(")") + 2).split(" ")[1]) || 0;
1168
- } catch {
1169
- return 0;
1170
- }
1171
- };
1172
- }
1173
- const table = /* @__PURE__ */ new Map();
1174
- try {
1175
- const out = execFileSync2("ps", ["-axo", "pid=,ppid="], {
1176
- encoding: "utf8",
1177
- timeout: 2e3,
1178
- maxBuffer: 8 * 1024 * 1024,
1179
- stdio: ["ignore", "pipe", "ignore"]
1180
- });
1181
- for (const l of out.split("\n")) {
1182
- const m = /^\s*(\d+)\s+(\d+)/.exec(l);
1183
- if (m) table.set(Number(m[1]), Number(m[2]));
1184
- }
1185
- } catch {
1186
- }
1187
- return (pid) => table.get(pid) || 0;
1188
- }
1189
- var EMAIL_RE = /^[^@\s]{1,64}@[^@\s]{1,189}$/;
1190
- function claudeLogin(dir, home = homeDir()) {
1191
- const files = [path5.join(dir, ".claude.json")];
1192
- if (path5.resolve(dir) === path5.join(home, ".claude")) files.unshift(path5.join(home, ".claude.json"));
1193
- for (const f of files) {
1456
+ function agPath(p) {
1457
+ if (typeof p !== "string" || !p) return null;
1458
+ let s = p;
1459
+ if (/^file:/i.test(s)) {
1194
1460
  try {
1195
- const a = JSON.parse(readFileSync3(f, "utf8")).oauthAccount;
1196
- if (typeof a?.accountUuid === "string" && a.accountUuid) {
1197
- const email = typeof a.emailAddress === "string" && EMAIL_RE.test(a.emailAddress) ? a.emailAddress : void 0;
1198
- return { id: a.accountUuid.slice(0, 128), ...email ? { email } : {} };
1199
- }
1461
+ s = decodeURIComponent(new URL(s).pathname);
1200
1462
  } catch {
1201
- }
1202
- }
1203
- return null;
1204
- }
1205
- var accountCache = /* @__PURE__ */ new Map();
1206
- function claudeAccount(dir) {
1207
- const hit = accountCache.get(dir);
1208
- if (hit !== void 0) return hit;
1209
- const acct = claudeLogin(dir)?.id ?? null;
1210
- accountCache.set(dir, acct);
1211
- return acct;
1212
- }
1213
- var LINK_RE = /^https:\/\/claude\.ai\/code\/session_[A-Za-z0-9]+$/;
1214
- function transcriptLink(records) {
1215
- for (let i = records.length - 1; i >= 0; i--) {
1216
- const j = records[i];
1217
- if (j.type === "system" && j.subtype === "bridge_status" && typeof j.url === "string" && LINK_RE.test(j.url))
1218
- return j.url;
1219
- if (j.type === "bridge-session" && typeof j.bridgeSessionId === "string") {
1220
- const m = /^(?:cse|session)_([A-Za-z0-9]+)$/.exec(j.bridgeSessionId);
1221
- if (m) return `https://claude.ai/code/session_${m[1]}`;
1222
- }
1223
- }
1224
- return null;
1225
- }
1226
- function appTitle(text) {
1227
- const t = String(text).replace(/\s+/g, " ").trim();
1228
- return t.length > 50 ? `${t.slice(0, 50)}...` : t;
1229
- }
1230
- function askText(j) {
1231
- if (!j || j.type !== "user" || j.isMeta || j.isSidechain) return null;
1232
- const origin = j.origin;
1233
- if (origin?.kind && origin.kind !== "human") return null;
1234
- const c = j.message?.content;
1235
- const text = (typeof c === "string" ? c : Array.isArray(c) ? c.filter((b) => b && b.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n") : "").trim();
1236
- if (!text || isInjected(text)) return null;
1237
- return text;
1238
- }
1239
- function firstAsk(records) {
1240
- for (const j of records) {
1241
- const t = askText(j);
1242
- if (t) return appTitle(t);
1243
- }
1244
- return null;
1245
- }
1246
- function scanTitles(file, from, found) {
1247
- const out = { custom: found.custom ?? null, ai: found.ai ?? null, upto: from };
1248
- let size = 0;
1249
- try {
1250
- size = statSync3(file).size;
1251
- } catch {
1252
- return out;
1253
- }
1254
- if (size < from) {
1255
- out.upto = 0;
1256
- out.custom = out.ai = null;
1257
- from = 0;
1258
- }
1259
- let pos = 0;
1260
- scanLines(
1261
- file,
1262
- (l) => {
1263
- pos += Buffer.byteLength(l, "utf8") + 1;
1264
- if (pos <= from || l.indexOf('-title"') < 0) return null;
1265
- try {
1266
- const j = JSON.parse(l);
1267
- if (j.type === "custom-title" && j.customTitle) out.custom = j.customTitle;
1268
- if (j.type === "ai-title" && j.aiTitle) out.ai = j.aiTitle;
1269
- } catch {
1270
- }
1271
1463
  return null;
1272
- },
1273
- 512 * 1024 * 1024
1274
- );
1275
- out.upto = size;
1276
- return out;
1277
- }
1278
- function claudeFacts(file, sessionId, hints, state) {
1279
- const { head, tail } = ends(file, 64 * 1024, 256 * 1024);
1280
- const H = parseLines(head);
1281
- const T = parseLines(tail);
1282
- const out = {};
1283
- const id = sessionId || path5.basename(file, ".jsonl");
1284
- const mem = state.get(NAME2, id);
1285
- let custom = null;
1286
- let ai = null;
1287
- for (const j of H.concat(T)) {
1288
- if (j.type === "custom-title" && typeof j.customTitle === "string") custom = j.customTitle;
1289
- if (j.type === "ai-title" && typeof j.aiTitle === "string") ai = j.aiTitle;
1464
+ }
1465
+ if (process.platform === "win32" && /^\/[A-Za-z]:/.test(s)) s = s.slice(1);
1466
+ } else if (/^[a-z][a-z0-9+.-]*:\/\//i.test(s)) return null;
1467
+ if (!path6.isAbsolute(s)) return null;
1468
+ return path6.resolve(s);
1469
+ }
1470
+ var agWorkspaces = (paths) => (Array.isArray(paths) ? paths : []).map(agPath).filter((p) => !!p).filter((p, i, a) => a.indexOf(p) === i);
1471
+ function agVariants(p) {
1472
+ const out = [p];
1473
+ try {
1474
+ const r = realpathSync.native(p);
1475
+ if (r !== p) out.push(r);
1476
+ } catch {
1290
1477
  }
1291
- let size = 0;
1478
+ return out;
1479
+ }
1480
+ function agProjects(dir) {
1481
+ const out = [];
1482
+ let names = [];
1292
1483
  try {
1293
- size = statSync3(file).size;
1484
+ names = readdirSync3(dir).filter((n) => n.endsWith(".json"));
1294
1485
  } catch {
1486
+ return out;
1295
1487
  }
1296
- if (!custom && !ai && size > 64 * 1024 + 256 * 1024) {
1297
- const s = scanTitles(file, Number(mem.scan_upto) || 0, {
1298
- custom: mem.scan_custom,
1299
- ai: mem.scan_ai
1300
- });
1301
- custom = s.custom;
1302
- ai = s.ai;
1303
- state.save(NAME2, id, { scan_upto: s.upto, scan_custom: s.custom, scan_ai: s.ai });
1488
+ for (const n of names) {
1489
+ const id = n.slice(0, -5);
1490
+ if (!ID_RE.test(id)) continue;
1491
+ let j;
1492
+ try {
1493
+ j = JSON.parse(readFileSync3(path6.join(dir, n), "utf8"));
1494
+ } catch {
1495
+ continue;
1496
+ }
1497
+ const folders = [];
1498
+ for (const r of j?.projectResources?.resources ?? []) {
1499
+ const f = agPath(r?.folderUri ?? r?.gitFolder?.folderUri);
1500
+ if (f) folders.push(...agVariants(f));
1501
+ }
1502
+ if (folders.length) out.push({ id, folders });
1304
1503
  }
1305
- const dir = configDirOf(file);
1306
- const live = claudeLive(dir).get(id) ?? claudeLiveByPid(dir, hints.claude_pid);
1307
- if (live?.name) state.save(NAME2, id, { name: live.name, name_source: live.name_source });
1308
- const bridge = live?.bridge && /^session_[A-Za-z0-9]+$/.test(live.bridge) ? `https://claude.ai/code/${live.bridge}` : null;
1309
- const url = bridge ?? transcriptLink(T) ?? transcriptLink(H) ?? (typeof mem.url === "string" ? mem.url : null);
1310
- if (url) {
1311
- out.url = url;
1312
- state.save(NAME2, id, { url });
1504
+ return out;
1505
+ }
1506
+ function agMatchProject(projects, workspaces) {
1507
+ const ws = [];
1508
+ for (const w of workspaces) {
1509
+ const p = agPath(w);
1510
+ if (p) ws.push(...agVariants(p));
1313
1511
  }
1314
- const name = live?.name ? { title: live.name, source: live.name_source } : typeof mem.name === "string" ? { title: mem.name, source: mem.name_source } : null;
1315
- const pick = name && name.source === "custom" && name || custom && { title: custom, source: "custom" } || name || ai && { title: ai, source: "harness" };
1316
- let ask2 = typeof mem.first_ask === "string" ? mem.first_ask : firstAsk(H);
1317
- if (!ask2 && size > 64 * 1024 && !mem.first_ask_none) {
1318
- ask2 = scanLines(file, (l) => {
1319
- try {
1320
- const t = askText(JSON.parse(l));
1321
- return t ? appTitle(t) : null;
1322
- } catch {
1323
- return null;
1512
+ let best = null;
1513
+ let bestLen = -1;
1514
+ for (const pr of projects)
1515
+ for (const f of pr.folders) {
1516
+ const within = ws.some((w) => w === f || w.startsWith(f.endsWith(path6.sep) ? f : f + path6.sep));
1517
+ if (within && f.length > bestLen) {
1518
+ best = pr.id;
1519
+ bestLen = f.length;
1324
1520
  }
1325
- });
1326
- if (!ask2 && size > 16 * 1024 * 1024) state.save(NAME2, id, { first_ask_none: true });
1327
- }
1328
- if (ask2) {
1329
- out.first_ask = ask2;
1330
- if (!mem.first_ask) state.save(NAME2, id, { first_ask: ask2 });
1521
+ }
1522
+ return best;
1523
+ }
1524
+ function antigravityRemoteUrl(transcript, opts) {
1525
+ const i = transcript.indexOf(`${path6.sep}brain${path6.sep}`);
1526
+ const dirs = (i > 0 ? [transcript.slice(0, i)] : []).concat(dataDirs(opts.env));
1527
+ for (const d of dirs) {
1528
+ try {
1529
+ const m = /installation_uuid:\s*"([0-9a-f-]{36})"/i.exec(
1530
+ readFileSync3(path6.join(d, "antigravity_state.pbtxt"), "utf8")
1531
+ );
1532
+ if (!m) continue;
1533
+ const base = `https://antigravity.google.com/r/${m[1]}-v2`;
1534
+ const conv = opts.conversation ?? "";
1535
+ if (!ID_RE.test(conv) || !opts.workspaces?.length) return base;
1536
+ const project = agMatchProject(agProjects(opts.projectsDir ?? projectsDir(opts.env)), opts.workspaces);
1537
+ return project ? `${base}?p=${encodeURIComponent(`c/${conv}?section=${project}`)}` : base;
1538
+ } catch {
1539
+ }
1331
1540
  }
1332
- if (pick) {
1333
- out.title = String(pick.title).slice(0, 120);
1334
- out.title_source = pick.source;
1335
- } else if (ask2) {
1336
- out.title = ask2;
1541
+ return null;
1542
+ }
1543
+ function antigravityFacts(file, opts) {
1544
+ const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
1545
+ const H = parseLines(head);
1546
+ const T = parseLines(tail);
1547
+ const out = {};
1548
+ const url = antigravityRemoteUrl(file, opts);
1549
+ if (url) out.url = url;
1550
+ for (const j of H) {
1551
+ if (j.type !== "USER_INPUT" || typeof j.content !== "string") continue;
1552
+ const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
1553
+ const text = (m ? m[1] : j.content).trim();
1554
+ const line = (text.split("\n").find((l) => l.trim()) ?? "").trim();
1555
+ if (!line || /^<[a-z_-]+>/i.test(line)) continue;
1556
+ out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
1337
1557
  out.title_source = "first-ask";
1558
+ break;
1338
1559
  }
1339
- const acct = claudeAccount(dir);
1340
- if (acct) out.account_id = acct;
1341
- for (let i = T.length - 1; i >= 0; i--) {
1342
- const m = T[i].message?.model;
1343
- if (T[i]?.type === "assistant" && m && m !== "<synthetic>") {
1344
- out.model = m;
1345
- break;
1346
- }
1347
- }
1348
- const meta = [...T].reverse().find((j) => j.version) ?? H.find((j) => j.version);
1349
- if (meta?.version) out.harness_version = String(meta.version);
1350
- const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
1560
+ const times = H.concat(T).map((j) => Date.parse(String(j.created_at ?? j.timestamp ?? ""))).filter((n) => n > 0);
1351
1561
  if (times.length) {
1352
1562
  out.started_at = Math.min(...times);
1353
1563
  out.last_at = Math.max(...times);
1354
1564
  }
1355
- const withCwd = H.find((j) => typeof j.cwd === "string");
1356
- if (withCwd) {
1357
- out.cwd = withCwd.cwd;
1358
- if (typeof withCwd.gitBranch === "string" && withCwd.gitBranch !== "HEAD") out.branch = withCwd.gitBranch;
1359
- }
1360
1565
  return out;
1361
1566
  }
1362
1567
 
1568
+ // ../core/src/control/verify.ts
1569
+ var GRANT_MAX_MS = 24 * 36e5;
1570
+ var SKEW_MS = 5 * 6e4;
1571
+ var MACHINE_MAX_AGE_MS = 24 * 36e5;
1572
+ var RECEIVER_MAX_AGE_MS = 10 * 6e4;
1573
+ var NONCE_TTL_MS = 25 * 36e5;
1574
+ var MODEL_RE = /^[A-Za-z0-9][A-Za-z0-9._:/@[\]-]*$/;
1575
+ var START_PERMISSIONS = ["read", "safe", "edits", "auto"];
1576
+ var levelRank = (l) => START_PERMISSIONS.indexOf(l);
1577
+ var FIELDS = {
1578
+ prompt: ["text", "files"],
1579
+ "permission.answer": ["for", "decision"],
1580
+ cancel: [],
1581
+ start: ["text", "cwd", "files", "permission", "model"],
1582
+ "key.add": ["key", "name"],
1583
+ pool: ["pool_action", "pool_account", "pool_order", "pool_code"]
1584
+ };
1585
+ var POOL_FIELDS = FIELDS.pool;
1586
+ var enc = new TextEncoder();
1587
+ var dec = new TextDecoder();
1588
+
1363
1589
  // ../core/src/adapters/codex.ts
1364
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
1365
- import path6 from "node:path";
1590
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
1591
+ import path7 from "node:path";
1366
1592
 
1367
1593
  // ../core/src/adapters/codex-config.ts
1368
- var NOTIFY_LINE = /^[ \t]*notify[ \t]*=[ \t]*(\[.*\])[ \t]*(?:#.*)?$/m;
1594
+ var NOTIFY_LINE = /^[ \t]*notify[ \t]*=[ \t]*(\[.*\])[ \t]*(?:#.*)?(?=\r?$)/m;
1369
1595
  var NOTIFY_KEY = /^[ \t]*notify[ \t]*=/m;
1370
1596
  var OURS2 = /(?:hook\.js|sessionpipe-hook)",\s*"codex",\s*"notify"/;
1371
1597
  function scan(line, st) {
@@ -1455,10 +1681,11 @@ function dropLine(text, index, length) {
1455
1681
  }
1456
1682
  function insertTopLevel(text, line) {
1457
1683
  const { head, tail } = split(text);
1458
- if (!tail) return (head.trim() ? head.replace(/\s*$/, "\n") : "") + line + "\n";
1459
- return `${(head.trim() ? head.replace(/\s*$/, "\n") : "") + line}
1460
-
1461
- ${tail}`;
1684
+ const body = head.replace(/\s*$/, "");
1685
+ const eol = (body.includes("\n") ? body : text).includes("\r\n") ? "\r\n" : "\n";
1686
+ const top = head.trim() ? head.replace(/\s*$/, eol) : "";
1687
+ if (!tail) return top + line + eol;
1688
+ return `${top + line}${eol}${eol}${tail}`;
1462
1689
  }
1463
1690
  function withNotify(text, line) {
1464
1691
  const n = readNotify(text);
@@ -1507,10 +1734,10 @@ var CODEX_EVENTS = [
1507
1734
  ];
1508
1735
  var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
1509
1736
  var eventsFor2 = (lean) => lean ? CODEX_EVENTS.filter((e) => e !== "PreToolUse") : [...CODEX_EVENTS];
1510
- var codexHome = (env) => env.CODEX_HOME || path6.join(homeDir(), ".codex");
1511
- var hooksFile2 = (env) => path6.join(codexHome(env), "hooks.json");
1512
- var configFile2 = (env) => path6.join(codexHome(env), "config.toml");
1513
- var sessionsDir = (env) => path6.join(codexHome(env), "sessions");
1737
+ var codexHome = (env) => env.CODEX_HOME || path7.join(homeDir(), ".codex");
1738
+ var hooksFile2 = (env) => path7.join(codexHome(env), "hooks.json");
1739
+ var configFile2 = (env) => path7.join(codexHome(env), "config.toml");
1740
+ var sessionsDir = (env) => path7.join(codexHome(env), "sessions");
1514
1741
  var validTool3 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
1515
1742
  var endReason2 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
1516
1743
  function installNotify(cmd, env) {
@@ -1524,8 +1751,8 @@ function installNotify(cmd, env) {
1524
1751
  const line = `notify = ${JSON.stringify(argv)}`;
1525
1752
  const n = readNotify(text);
1526
1753
  const write = (next) => {
1527
- mkdirSync3(path6.dirname(file), { recursive: true });
1528
- writeFileSync3(file, next);
1754
+ mkdirSync3(path7.dirname(file), { recursive: true });
1755
+ writeFileSync4(file, next);
1529
1756
  };
1530
1757
  if (n && !n.other) {
1531
1758
  if (!n.misplaced && JSON.stringify(n.argv) === JSON.stringify(argv))
@@ -1570,12 +1797,12 @@ function uninstallNotify(env) {
1570
1797
  const n = readNotify(text);
1571
1798
  if (!n) return { file, changed: false };
1572
1799
  if (!n.other) {
1573
- writeFileSync3(file, withoutNotify(text));
1800
+ writeFileSync4(file, withoutNotify(text));
1574
1801
  return { file, changed: true };
1575
1802
  }
1576
1803
  const prev = previousNotify(n.argv);
1577
1804
  if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev))) {
1578
- writeFileSync3(file, replaceNotify(text, n, `notify = ${stripPreviousRaw(n.raw)}`));
1805
+ writeFileSync4(file, replaceNotify(text, n, `notify = ${stripPreviousRaw(n.raw)}`));
1579
1806
  return { file, changed: true };
1580
1807
  }
1581
1808
  return { file, changed: false };
@@ -1591,7 +1818,7 @@ function findRollout(threadId, env = process.env) {
1591
1818
  return;
1592
1819
  }
1593
1820
  for (const n of names.sort().reverse()) {
1594
- const p = path6.join(dir, n);
1821
+ const p = path7.join(dir, n);
1595
1822
  if (n.endsWith(`-${threadId}.jsonl`)) {
1596
1823
  best = p;
1597
1824
  return;
@@ -1681,7 +1908,7 @@ var codex = {
1681
1908
  if (j && typeof j === "object" && !Array.isArray(j)) s = j;
1682
1909
  } catch {
1683
1910
  }
1684
- const id = s.session_id;
1911
+ const id = s.session_id ?? s.thread_id ?? s.conversation_id ?? s["thread-id"] ?? s["session-id"];
1685
1912
  if (!id) return null;
1686
1913
  if (s.transcript_path === null) return null;
1687
1914
  const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
@@ -1776,7 +2003,7 @@ var codex = {
1776
2003
  backfill(sinceMs, env = process.env) {
1777
2004
  const rows2 = [];
1778
2005
  for (const f of recentFiles(sessionsDir(env), 3, sinceMs, (n) => /^rollout-.*\.jsonl$/.test(n))) {
1779
- const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(path6.basename(f));
2006
+ const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(path7.basename(f));
1780
2007
  if (!m?.[1]) continue;
1781
2008
  if (codexSubagent(rolloutMeta(f))) continue;
1782
2009
  const facts = codexFacts(f);
@@ -1785,12 +2012,40 @@ var codex = {
1785
2012
  rows2.push({ session: { id: m[1], ...rest }, started_at, last_at });
1786
2013
  }
1787
2014
  return rows2;
1788
- }
2015
+ },
2016
+ limits: (transcript) => codexLimits(transcript)
1789
2017
  };
2018
+ var epochIso2 = (t) => typeof t === "number" && t > 0 && t < 1e11 ? new Date(t * 1e3).toISOString() : void 0;
1790
2019
  function prune3(o) {
1791
2020
  for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
1792
2021
  return o;
1793
2022
  }
2023
+ function codexLimits(file) {
2024
+ const { tail } = ends(file, 0, 256 * 1024);
2025
+ for (const r of parseLines(tail).reverse()) {
2026
+ const p = r.payload;
2027
+ if (r.type !== "event_msg" || p?.type !== "token_count") continue;
2028
+ if (!p.rate_limits || typeof p.rate_limits !== "object") continue;
2029
+ const rl = p.rate_limits;
2030
+ if (rl.limit_id != null && rl.limit_id !== "codex") continue;
2031
+ const windows = [];
2032
+ for (const id of ["primary", "secondary"]) {
2033
+ const w = rl[id];
2034
+ if (typeof w?.used_percent !== "number" || w.used_percent < 0) continue;
2035
+ windows.push(
2036
+ prune3({
2037
+ id,
2038
+ used_percent: w.used_percent,
2039
+ window_minutes: Number.isInteger(w.window_minutes) && Number(w.window_minutes) > 0 ? Number(w.window_minutes) : void 0,
2040
+ resets_at: epochIso2(w.resets_at)
2041
+ })
2042
+ );
2043
+ }
2044
+ if (!windows.length) return null;
2045
+ return prune3({ windows, plan: typeof rl.plan_type === "string" ? rl.plan_type.slice(0, 64) : void 0 });
2046
+ }
2047
+ return null;
2048
+ }
1794
2049
  function codexFacts(file) {
1795
2050
  const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
1796
2051
  let H = parseLines(head);
@@ -1835,7 +2090,7 @@ function codexFacts(file) {
1835
2090
  // ../core/src/adapters/devin.ts
1836
2091
  import { createHash as createHash3 } from "node:crypto";
1837
2092
  import { existsSync as existsSync6, readlinkSync } from "node:fs";
1838
- import path7 from "node:path";
2093
+ import path8 from "node:path";
1839
2094
 
1840
2095
  // ../core/src/readers/sqlite.ts
1841
2096
  import { execFileSync as execFileSync3 } from "node:child_process";
@@ -2019,11 +2274,11 @@ function foreignEvent(file) {
2019
2274
  return Object.keys(hooks).find((k) => !ok.includes(k));
2020
2275
  }
2021
2276
  var homeOf = (env) => env.HOME || homeDir();
2022
- var appData = (env) => env.APPDATA || path7.join(homeOf(env), "AppData", "Roaming");
2023
- var devinConfigDir = (env = process.env) => process.platform === "win32" ? path7.join(appData(env), "devin") : path7.join(env.XDG_CONFIG_HOME || path7.join(homeOf(env), ".config"), "devin");
2024
- var configFile3 = (env) => path7.join(devinConfigDir(env), "config.json");
2025
- var devinDataDir = (env = process.env) => process.platform === "win32" ? path7.join(appData(env), "devin", "cli") : path7.join(env.XDG_DATA_HOME || path7.join(homeOf(env), ".local", "share"), "devin", "cli");
2026
- var sessionsDb = (env) => path7.join(devinDataDir(env), "sessions.db");
2277
+ var appData = (env) => env.APPDATA || path8.join(homeOf(env), "AppData", "Roaming");
2278
+ var devinConfigDir = (env = process.env) => process.platform === "win32" ? path8.join(appData(env), "devin") : path8.join(env.XDG_CONFIG_HOME || path8.join(homeOf(env), ".config"), "devin");
2279
+ var configFile3 = (env) => path8.join(devinConfigDir(env), "config.json");
2280
+ var devinDataDir = (env = process.env) => process.platform === "win32" ? path8.join(appData(env), "devin", "cli") : path8.join(env.XDG_DATA_HOME || path8.join(homeOf(env), ".local", "share"), "devin", "cli");
2281
+ var sessionsDb = (env) => path8.join(devinDataDir(env), "sessions.db");
2027
2282
  var SESSION_ID = /^[A-Za-z0-9._:-]{1,128}$/;
2028
2283
  var validTool4 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
2029
2284
  var endReason3 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
@@ -2195,7 +2450,7 @@ var BACKFILL_SQL = `SELECT s.id, ${SESSION_FIELDS} FROM sessions s
2195
2450
  function sessionFacts(row) {
2196
2451
  const out = {};
2197
2452
  if (typeof row.model === "string" && row.model) out.model = row.model;
2198
- if (typeof row.working_directory === "string" && path7.isAbsolute(row.working_directory))
2453
+ if (typeof row.working_directory === "string" && path8.isAbsolute(row.working_directory))
2199
2454
  out.cwd = row.working_directory;
2200
2455
  const started = Number(row.created_at);
2201
2456
  const last = Number(row.last_activity_at);
@@ -2240,8 +2495,8 @@ function readDevinChain(db, session, fromLine) {
2240
2495
  }
2241
2496
  function devinVersion(env = process.env) {
2242
2497
  try {
2243
- const target = readlinkSync(path7.join(devinDataDir(env), "_versions", "current"));
2244
- const name = path7.basename(target);
2498
+ const target = readlinkSync(path8.join(devinDataDir(env), "_versions", "current"));
2499
+ const name = path8.basename(target);
2245
2500
  return /^[0-9][0-9A-Za-z.+-]{0,63}$/.test(name) ? name : void 0;
2246
2501
  } catch {
2247
2502
  return void 0;
@@ -2256,7 +2511,7 @@ function devinAccount(env = process.env) {
2256
2511
  // ../core/src/adapters/gemini-cli.ts
2257
2512
  import { execFileSync as execFileSync4 } from "node:child_process";
2258
2513
  import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
2259
- import path8 from "node:path";
2514
+ import path9 from "node:path";
2260
2515
  var NAME5 = "gemini-cli";
2261
2516
  var GEMINI_EVENTS = [
2262
2517
  "SessionStart",
@@ -2269,26 +2524,31 @@ var GEMINI_EVENTS = [
2269
2524
  "SessionEnd"
2270
2525
  ];
2271
2526
  var OPTS4 = { matcherFor: (e) => /Tool/.test(e) ? ".*" : void 0, timeout: 5e3 };
2272
- var geminiDir = (env) => env.GEMINI_CLI_HOME || path8.join(homeDir(), ".gemini");
2273
- var settings = (env) => path8.join(geminiDir(env), "settings.json");
2527
+ var geminiDir = (env) => env.GEMINI_CLI_HOME || path9.join(homeDir(), ".gemini");
2528
+ var settings = (env) => path9.join(geminiDir(env), "settings.json");
2274
2529
  var validTool5 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
2275
2530
  var eventsFor4 = (lean) => lean ? GEMINI_EVENTS.filter((e) => e !== "BeforeTool") : [...GEMINI_EVENTS];
2531
+ function geminiVersion(platform = process.platform, env = process.env) {
2532
+ const win = platform === "win32";
2533
+ const bin = win ? findBinary({ names: ["gemini.exe", "gemini.cmd"], dirs: [path9.dirname(process.execPath)], env }) : "gemini";
2534
+ if (!bin) throw new Error("no gemini on PATH");
2535
+ const o = {
2536
+ encoding: "utf8",
2537
+ timeout: 4e3,
2538
+ stdio: ["ignore", "pipe", "ignore"],
2539
+ env
2540
+ };
2541
+ const out = /\.cmd$/i.test(bin) ? execFileSync4(`"${bin}" --version`, { ...o, shell: true }) : execFileSync4(bin, ["--version"], o);
2542
+ return out.trim();
2543
+ }
2276
2544
  function geminiHooksActive(settingsFile) {
2277
- let tools;
2278
- try {
2279
- tools = JSON.parse(readFileSync5(settingsFile, "utf8")).tools;
2280
- } catch {
2281
- }
2545
+ const tools = readJson(settingsFile).tools;
2282
2546
  if (tools?.enableHooks === true) return { active: true };
2283
2547
  if (tools?.enableHooks === false)
2284
2548
  return { active: false, note: "inactive: tools.enableHooks is false in settings.json" };
2285
2549
  let version = "";
2286
2550
  try {
2287
- version = execFileSync4("gemini", ["--version"], {
2288
- encoding: "utf8",
2289
- timeout: 4e3,
2290
- stdio: ["ignore", "pipe", "ignore"]
2291
- }).trim();
2551
+ version = geminiVersion();
2292
2552
  } catch {
2293
2553
  return {
2294
2554
  active: true,
@@ -2306,7 +2566,7 @@ var geminiCli = {
2306
2566
  name: NAME5,
2307
2567
  events: GEMINI_EVENTS,
2308
2568
  // ~/.gemini also belongs to Antigravity; Gemini CLI is here when its settings or its chat logs exist.
2309
- detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(path8.join(geminiDir(env), "tmp")),
2569
+ detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(path9.join(geminiDir(env), "tmp")),
2310
2570
  configFiles: (env = process.env) => [settings(env)],
2311
2571
  install: (cmd, env = process.env, opts = {}) => {
2312
2572
  const r = installClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
@@ -2444,8 +2704,8 @@ var ADAPTERS = [claudeCode, codex, geminiCli, antigravity, devin];
2444
2704
  var adapterByName = (name) => ADAPTERS.find((a) => a.name === name);
2445
2705
 
2446
2706
  // ../core/src/config.ts
2447
- import { chmodSync, existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
2448
- import path9 from "node:path";
2707
+ import { chmodSync, existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
2708
+ import path10 from "node:path";
2449
2709
  var DEFAULT_CONFIG = { sinks: [], harnesses: {}, update_check: true, keep_days: 30 };
2450
2710
  function readConfig(file = configFile()) {
2451
2711
  try {
@@ -2461,8 +2721,8 @@ function readConfig(file = configFile()) {
2461
2721
  }
2462
2722
  }
2463
2723
  function writeConfig(c, file = configFile()) {
2464
- mkdirSync4(path9.dirname(file), { recursive: true, mode: 448 });
2465
- writeFileSync4(file, `${JSON.stringify(c, null, 2)}
2724
+ mkdirSync4(path10.dirname(file), { recursive: true, mode: 448 });
2725
+ writeFileSync5(file, `${JSON.stringify(c, null, 2)}
2466
2726
  `, { mode: 384 });
2467
2727
  if (process.platform !== "win32" && existsSync8(file)) {
2468
2728
  try {
@@ -2525,6 +2785,22 @@ function makeEvent(a, ctx) {
2525
2785
  };
2526
2786
  }
2527
2787
 
2788
+ // ../core/src/launched-by.ts
2789
+ var LAUNCHED_BY_ENV = "SESSIONPIPE_LAUNCHED_BY";
2790
+ var LAUNCHED_BY = /^[a-z][a-z0-9-]{0,39}:[A-Za-z0-9._:-]{1,128}$/;
2791
+ function launchedBy(env) {
2792
+ const v = env[LAUNCHED_BY_ENV]?.trim();
2793
+ return v && LAUNCHED_BY.test(v) ? v : void 0;
2794
+ }
2795
+ function readHook(adapter, input) {
2796
+ const r = adapter.fromHook(input);
2797
+ if (r && !r.session.parent_id) {
2798
+ const launcher = launchedBy(input.env);
2799
+ if (launcher && launcher !== `${adapter.name}:${r.session.id}`) r.session.launched_by = launcher;
2800
+ }
2801
+ return r;
2802
+ }
2803
+
2528
2804
  // ../core/src/outbox.ts
2529
2805
  import {
2530
2806
  appendFileSync,
@@ -2536,11 +2812,11 @@ import {
2536
2812
  readFileSync as readFileSync7,
2537
2813
  readSync as readSync2,
2538
2814
  renameSync,
2539
- statSync as statSync4,
2815
+ statSync as statSync5,
2540
2816
  unlinkSync as unlinkSync3,
2541
- writeFileSync as writeFileSync5
2817
+ writeFileSync as writeFileSync6
2542
2818
  } from "node:fs";
2543
- import path10 from "node:path";
2819
+ import path11 from "node:path";
2544
2820
  var READ_CHUNK = 1024 * 1024;
2545
2821
  var READ_MAX = 8 * 1024 * 1024;
2546
2822
  var Outbox = class {
@@ -2549,13 +2825,13 @@ var Outbox = class {
2549
2825
  }
2550
2826
  root;
2551
2827
  dir(harness) {
2552
- return path10.join(this.root, "outbox", safeId(harness));
2828
+ return path11.join(this.root, "outbox", safeId(harness));
2553
2829
  }
2554
2830
  file(ref) {
2555
- return path10.join(this.dir(ref.harness), `${safeId(ref.session)}.jsonl`);
2831
+ return path11.join(this.dir(ref.harness), `${safeId(ref.session)}.jsonl`);
2556
2832
  }
2557
2833
  seqFile(ref) {
2558
- return path10.join(this.dir(ref.harness), `${safeId(ref.session)}.seq`);
2834
+ return path11.join(this.dir(ref.harness), `${safeId(ref.session)}.seq`);
2559
2835
  }
2560
2836
  /** The next seq for a session, persisted (survives a hibernating machine and a
2561
2837
  * restart; an in-memory counter would restart at 0 and collide). */
@@ -2568,8 +2844,8 @@ var Outbox = class {
2568
2844
  } catch {
2569
2845
  }
2570
2846
  if (n < 0) n = this.lastSeqInFile(ref) + 1;
2571
- mkdirSync5(path10.dirname(f), { recursive: true, mode: 448 });
2572
- writeFileSync5(f, String(n + 1), { mode: 384 });
2847
+ mkdirSync5(path11.dirname(f), { recursive: true, mode: 448 });
2848
+ writeFileSync6(f, String(n + 1), { mode: 384 });
2573
2849
  return n;
2574
2850
  }
2575
2851
  lastSeqInFile(ref) {
@@ -2594,10 +2870,10 @@ var Outbox = class {
2594
2870
  /** Every session file, newest first. */
2595
2871
  sessions() {
2596
2872
  const out = [];
2597
- const base = path10.join(this.root, "outbox");
2873
+ const base = path11.join(this.root, "outbox");
2598
2874
  if (!existsSync9(base)) return [];
2599
2875
  for (const h of readdirSync5(base)) {
2600
- const d = path10.join(base, h);
2876
+ const d = path11.join(base, h);
2601
2877
  let names = [];
2602
2878
  try {
2603
2879
  names = readdirSync5(d).filter((n) => n.endsWith(".jsonl"));
@@ -2606,7 +2882,7 @@ var Outbox = class {
2606
2882
  }
2607
2883
  for (const n of names) {
2608
2884
  try {
2609
- out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime: statSync4(path10.join(d, n)).mtimeMs });
2885
+ out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime: statSync5(path11.join(d, n)).mtimeMs });
2610
2886
  } catch {
2611
2887
  }
2612
2888
  }
@@ -2622,7 +2898,7 @@ var Outbox = class {
2622
2898
  read(ref, from, maxEvents = 500) {
2623
2899
  const f = this.file(ref);
2624
2900
  if (!existsSync9(f)) return { events: [], offset: from };
2625
- const size = statSync4(f).size;
2901
+ const size = statSync5(f).size;
2626
2902
  if (from >= size) return { events: [], offset: from };
2627
2903
  const fd = openSync2(f, "r");
2628
2904
  try {
@@ -2664,7 +2940,7 @@ var Outbox = class {
2664
2940
  for (const ref of this.sessions()) {
2665
2941
  const f = this.file(ref);
2666
2942
  try {
2667
- if (statSync4(f).mtimeMs < cutoff) {
2943
+ if (statSync5(f).mtimeMs < cutoff) {
2668
2944
  unlinkSync3(f);
2669
2945
  try {
2670
2946
  unlinkSync3(this.seqFile(ref));
@@ -2694,7 +2970,7 @@ var Cursors = class {
2694
2970
  root;
2695
2971
  sink;
2696
2972
  file() {
2697
- return path10.join(this.root, "cursors", `${safeId(this.sink)}.json`);
2973
+ return path11.join(this.root, "cursors", `${safeId(this.sink)}.json`);
2698
2974
  }
2699
2975
  read() {
2700
2976
  try {
@@ -2705,9 +2981,9 @@ var Cursors = class {
2705
2981
  }
2706
2982
  write(c) {
2707
2983
  const f = this.file();
2708
- mkdirSync5(path10.dirname(f), { recursive: true, mode: 448 });
2984
+ mkdirSync5(path11.dirname(f), { recursive: true, mode: 448 });
2709
2985
  const tmp = `${f}.${process.pid}.tmp`;
2710
- writeFileSync5(tmp, JSON.stringify(c), { mode: 384 });
2986
+ writeFileSync6(tmp, JSON.stringify(c), { mode: 384 });
2711
2987
  renameSync(tmp, f);
2712
2988
  }
2713
2989
  key(ref) {
@@ -2718,16 +2994,16 @@ function takeLock(root, ref, staleMs = 5 * 6e4) {
2718
2994
  return takeNamedLock(root, `${safeId(ref.harness)}-${safeId(ref.session)}`, staleMs);
2719
2995
  }
2720
2996
  function takeNamedLock(root, name, staleMs = 5 * 6e4) {
2721
- const dir = path10.join(root, "locks");
2997
+ const dir = path11.join(root, "locks");
2722
2998
  mkdirSync5(dir, { recursive: true, mode: 448 });
2723
- const f = path10.join(dir, `${name}.lock`);
2999
+ const f = path11.join(dir, `${name}.lock`);
2724
3000
  try {
2725
- if (Date.now() - statSync4(f).mtimeMs > staleMs || !processAlive(Number(readFileSync7(f, "utf8")))) unlinkSync3(f);
3001
+ if (Date.now() - statSync5(f).mtimeMs > staleMs || !processAlive(Number(readFileSync7(f, "utf8")))) unlinkSync3(f);
2726
3002
  else return null;
2727
3003
  } catch {
2728
3004
  }
2729
3005
  try {
2730
- writeFileSync5(f, String(process.pid), { flag: "wx" });
3006
+ writeFileSync6(f, String(process.pid), { flag: "wx" });
2731
3007
  } catch {
2732
3008
  return null;
2733
3009
  }
@@ -2959,6 +3235,7 @@ var TYPE_TIER = {
2959
3235
  "subagent.started": 1,
2960
3236
  "subagent.ended": 1,
2961
3237
  "context.compacted": 1,
3238
+ "limits.observed": 1,
2962
3239
  native: 3
2963
3240
  };
2964
3241
  var DATA_ALLOW = {
@@ -2967,8 +3244,8 @@ var DATA_ALLOW = {
2967
3244
  "session.ended": { 0: ["reason"] },
2968
3245
  "session.backfill": { 0: ["started_at", "last_at", "turns"] },
2969
3246
  "session.forgotten": { 0: [] },
2970
- "turn.started": { 0: ["turn_id"], 1: ["prompt_chars"] },
2971
- "turn.ended": { 0: ["turn_id", "reason", "ms"], 1: ["error"] },
3247
+ "turn.started": { 0: ["turn_id", "submitted_by", "injected"], 1: ["prompt_chars"] },
3248
+ "turn.ended": { 0: ["turn_id", "reason", "ms", "background"], 1: ["error"] },
2972
3249
  "turn.transcript": { 2: ["turn", "user", "assistant", "at"] },
2973
3250
  "turn.progress": { 2: ["turn", "user", "assistant", "at"] },
2974
3251
  "tool.started": { 1: ["tool", "call_id", "turn_id", "agent_id"], 3: ["input"] },
@@ -2978,7 +3255,8 @@ var DATA_ALLOW = {
2978
3255
  "attention.cleared": { 0: ["attention_id", "how"] },
2979
3256
  "subagent.started": { 1: ["agent_id", "agent_type"] },
2980
3257
  "subagent.ended": { 1: ["agent_id", "agent_type"] },
2981
- "context.compacted": { 1: ["trigger"] }
3258
+ "context.compacted": { 1: ["trigger"] },
3259
+ "limits.observed": { 1: ["windows", "plan", "account_id"] }
2982
3260
  };
2983
3261
  var SESSION_ALLOW = [
2984
3262
  "id",
@@ -2992,7 +3270,9 @@ var SESSION_ALLOW = [
2992
3270
  "title_source",
2993
3271
  "url",
2994
3272
  "account_id",
2995
- "parent_id"
3273
+ "attended",
3274
+ "parent_id",
3275
+ "launched_by"
2996
3276
  ];
2997
3277
  var MAX_SIDE = 2e4;
2998
3278
  var MAX_IO = 64 * 1024;
@@ -3041,7 +3321,9 @@ function filterEvent(event, tier, pii, opts = {}) {
3041
3321
  const s = reducePiiDeep(out.session, opts.home);
3042
3322
  if (typeof s.machine === "string") s.machine = hashMachine(s.machine);
3043
3323
  delete s.account_id;
3044
- out = { ...out, session: s, data: reducePiiDeep(out.data, opts.home) };
3324
+ const d = reducePiiDeep(out.data, opts.home);
3325
+ delete d.account_id;
3326
+ out = { ...out, session: s, data: d };
3045
3327
  rulesets.push(RULESET_PII);
3046
3328
  }
3047
3329
  out.privacy = { rulesets, pii };
@@ -3056,7 +3338,7 @@ function shortId(harness, id) {
3056
3338
 
3057
3339
  // ../core/src/sinks/file.ts
3058
3340
  import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync6 } from "node:fs";
3059
- import path11 from "node:path";
3341
+ import path12 from "node:path";
3060
3342
  var FileSink = class {
3061
3343
  constructor(name, file, tier, pii) {
3062
3344
  this.name = name;
@@ -3070,7 +3352,7 @@ var FileSink = class {
3070
3352
  pii;
3071
3353
  async send(events) {
3072
3354
  try {
3073
- mkdirSync6(path11.dirname(this.file), { recursive: true });
3355
+ mkdirSync6(path12.dirname(this.file), { recursive: true });
3074
3356
  appendFileSync2(this.file, `${events.map((e) => JSON.stringify(e)).join("\n")}
3075
3357
  `);
3076
3358
  return { ok: true, acknowledged: events.length, accepted: events.length, duplicates: 0, rejected: [] };
@@ -3104,9 +3386,52 @@ var StdoutSink = class {
3104
3386
  };
3105
3387
 
3106
3388
  // src/control/aliases.ts
3107
- import { chmodSync as chmodSync2, mkdirSync as mkdirSync7, readFileSync as readFileSync8, renameSync as renameSync2, writeFileSync as writeFileSync6 } from "node:fs";
3108
- import path12 from "node:path";
3109
- var aliasFile = (controlDir) => path12.join(controlDir, "aliases.json");
3389
+ import { chmodSync as chmodSync2, mkdirSync as mkdirSync7, readFileSync as readFileSync8, renameSync as renameSync2, writeFileSync as writeFileSync7 } from "node:fs";
3390
+ import path13 from "node:path";
3391
+
3392
+ // src/secrets.ts
3393
+ import { spawnSync } from "node:child_process";
3394
+ import { createHash as createHash6, randomBytes as randomBytes2 } from "node:crypto";
3395
+ var SERVICE = "sessionpipe";
3396
+ var defaultRunner = (cmd, args, input) => {
3397
+ const r = spawnSync(cmd, args, {
3398
+ input: input ?? "",
3399
+ encoding: "utf8",
3400
+ timeout: 8e3,
3401
+ stdio: ["pipe", "pipe", "ignore"]
3402
+ });
3403
+ return { status: r.error ? null : r.status, stdout: r.stdout ?? "" };
3404
+ };
3405
+ var sinkAccount = (name, what = "token") => {
3406
+ const scope = process.env.SESSIONPIPE_CONFIG ? `@${createHash6("sha256").update(configFile()).digest("hex").slice(0, 8)}` : "";
3407
+ return `sink:${name}:${what}${scope}`;
3408
+ };
3409
+ function secretGet(store, account, run = defaultRunner) {
3410
+ const r = store === "keychain" ? run("security", ["find-generic-password", "-s", SERVICE, "-a", account, "-w"]) : run("secret-tool", ["lookup", "service", SERVICE, "account", account]);
3411
+ if (r.status !== 0) return null;
3412
+ const v = r.stdout.replace(/\r?\n$/, "");
3413
+ return v || null;
3414
+ }
3415
+ function sinkWithSecrets(s, run = defaultRunner) {
3416
+ if (!s.token_in) return s;
3417
+ const token = secretGet(s.token_in, sinkAccount(s.name), run);
3418
+ if (!token) return null;
3419
+ const secret = secretGet(s.token_in, sinkAccount(s.name, "secret"), run);
3420
+ return { ...s, token, ...secret ? { secret } : {} };
3421
+ }
3422
+
3423
+ // src/control/store.ts
3424
+ function startOptionsOf(v) {
3425
+ const o = v;
3426
+ if (!o || typeof o !== "object" || levelRank(o.permission) < 0 && o.permission !== "inherit") return null;
3427
+ return {
3428
+ permission: o.permission,
3429
+ ...typeof o.model === "string" && MODEL_RE.test(o.model) ? { model: o.model } : {}
3430
+ };
3431
+ }
3432
+
3433
+ // src/control/aliases.ts
3434
+ var aliasFile = (controlDir) => path13.join(controlDir, "aliases.json");
3110
3435
  function readAliases(file) {
3111
3436
  try {
3112
3437
  const j = JSON.parse(readFileSync8(file, "utf8"));
@@ -3115,7 +3440,7 @@ function readAliases(file) {
3115
3440
  for (const [k, v] of Object.entries(j)) {
3116
3441
  const a = v;
3117
3442
  if (a && typeof a.id === "string" && a.id && typeof a.cwd === "string")
3118
- out[k] = { id: a.id, cwd: a.cwd, at: typeof a.at === "string" ? a.at : "" };
3443
+ out[k] = { id: a.id, cwd: a.cwd, at: typeof a.at === "string" ? a.at : "", ...startOptionsOf(a) };
3119
3444
  }
3120
3445
  return out;
3121
3446
  } catch {
@@ -3131,11 +3456,11 @@ function receiverIdOf(all, harness, id) {
3131
3456
  // src/control/local.ts
3132
3457
  import net from "node:net";
3133
3458
  import os2 from "node:os";
3134
- import path13 from "node:path";
3459
+ import path14 from "node:path";
3135
3460
  function socketPath(state, env = process.env) {
3136
3461
  if (env.SESSIONPIPE_SOCKET) return env.SESSIONPIPE_SOCKET;
3137
3462
  if (process.platform === "win32") return `\\\\.\\pipe\\sessionpipe-control-${os2.userInfo().username}`;
3138
- return path13.join(state, "control.sock");
3463
+ return path14.join(state, "control.sock");
3139
3464
  }
3140
3465
  function ask(sock, req, timeoutMs) {
3141
3466
  return new Promise((resolve) => {
@@ -3167,37 +3492,6 @@ function ask(sock, req, timeoutMs) {
3167
3492
  });
3168
3493
  }
3169
3494
 
3170
- // src/secrets.ts
3171
- import { spawnSync } from "node:child_process";
3172
- import { createHash as createHash6, randomBytes as randomBytes2 } from "node:crypto";
3173
- var SERVICE = "sessionpipe";
3174
- var defaultRunner = (cmd, args, input) => {
3175
- const r = spawnSync(cmd, args, {
3176
- input: input ?? "",
3177
- encoding: "utf8",
3178
- timeout: 8e3,
3179
- stdio: ["pipe", "pipe", "ignore"]
3180
- });
3181
- return { status: r.error ? null : r.status, stdout: r.stdout ?? "" };
3182
- };
3183
- var sinkAccount = (name, what = "token") => {
3184
- const scope = process.env.SESSIONPIPE_CONFIG ? `@${createHash6("sha256").update(configFile()).digest("hex").slice(0, 8)}` : "";
3185
- return `sink:${name}:${what}${scope}`;
3186
- };
3187
- function secretGet(store, account, run = defaultRunner) {
3188
- const r = store === "keychain" ? run("security", ["find-generic-password", "-s", SERVICE, "-a", account, "-w"]) : run("secret-tool", ["lookup", "service", SERVICE, "account", account]);
3189
- if (r.status !== 0) return null;
3190
- const v = r.stdout.replace(/\r?\n$/, "");
3191
- return v || null;
3192
- }
3193
- function sinkWithSecrets(s, run = defaultRunner) {
3194
- if (!s.token_in) return s;
3195
- const token = secretGet(s.token_in, sinkAccount(s.name), run);
3196
- if (!token) return null;
3197
- const secret = secretGet(s.token_in, sinkAccount(s.name, "secret"), run);
3198
- return { ...s, token, ...secret ? { secret } : {} };
3199
- }
3200
-
3201
3495
  // src/run.ts
3202
3496
  async function tellDaemon(state, job, session, cwd, transcript) {
3203
3497
  const sock = socketPath(state);
@@ -3217,8 +3511,8 @@ async function tellDaemon(state, job, session, cwd, transcript) {
3217
3511
  function enqueueJob(state, job) {
3218
3512
  const jobs = jobsDir(state);
3219
3513
  mkdirSync8(jobs, { recursive: true, mode: 448 });
3220
- const f = path14.join(jobs, `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.json`);
3221
- writeFileSync7(f, JSON.stringify(job), { mode: 384 });
3514
+ const f = path15.join(jobs, `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.json`);
3515
+ writeFileSync8(f, JSON.stringify(job), { mode: 384 });
3222
3516
  return f;
3223
3517
  }
3224
3518
  function releaseJob(claimed) {
@@ -3231,9 +3525,9 @@ function releaseJob(claimed) {
3231
3525
  function log(state, line) {
3232
3526
  try {
3233
3527
  mkdirSync8(state, { recursive: true, mode: 448 });
3234
- const f = path14.join(state, "log");
3528
+ const f = path15.join(state, "log");
3235
3529
  try {
3236
- if (statSync5(f).size > 1024 * 1024) renameSync3(f, `${f}.1`);
3530
+ if (statSync6(f).size > 1024 * 1024) renameSync3(f, `${f}.1`);
3237
3531
  } catch {
3238
3532
  }
3239
3533
  appendFileSync3(f, `${(/* @__PURE__ */ new Date()).toISOString()} ${line}
@@ -3242,7 +3536,7 @@ function log(state, line) {
3242
3536
  }
3243
3537
  }
3244
3538
  function factsState(state) {
3245
- const file = (h, s) => path14.join(state, "facts", safeId(h), `${safeId(s)}.json`);
3539
+ const file = (h, s) => path15.join(state, "facts", safeId(h), `${safeId(s)}.json`);
3246
3540
  return {
3247
3541
  get(h, s) {
3248
3542
  try {
@@ -3255,8 +3549,8 @@ function factsState(state) {
3255
3549
  const cur = this.get(h, s);
3256
3550
  const next = { ...cur, ...patch };
3257
3551
  if (JSON.stringify(next) === JSON.stringify(cur)) return;
3258
- mkdirSync8(path14.dirname(file(h, s)), { recursive: true, mode: 448 });
3259
- writeFileSync7(file(h, s), JSON.stringify(next), { mode: 384 });
3552
+ mkdirSync8(path15.dirname(file(h, s)), { recursive: true, mode: 448 });
3553
+ writeFileSync8(file(h, s), JSON.stringify(next), { mode: 384 });
3260
3554
  }
3261
3555
  };
3262
3556
  }
@@ -3298,6 +3592,12 @@ function openTurn(read, o) {
3298
3592
  };
3299
3593
  }
3300
3594
  var MAX_TURN_SIDE = 2e4;
3595
+ var JOB_OWN_ENV = [
3596
+ "CLAUDE_CODE_SESSION_ATTENDED",
3597
+ "CLAUDE_CODE_HOST_SESSION_ID",
3598
+ "SESSIONPIPE_CONTROL_JOB",
3599
+ "SESSIONPIPE_LAUNCHED_BY"
3600
+ ];
3301
3601
  async function runJob(job, opts = {}) {
3302
3602
  const state = opts.state ?? stateDir();
3303
3603
  const cfg = readConfig();
@@ -3306,12 +3606,23 @@ async function runJob(job, opts = {}) {
3306
3606
  log(state, `${job.harness} ${job.event}: no adapter for harness "${job.harness}"; event dropped`);
3307
3607
  return { events: [], delivered: {} };
3308
3608
  }
3309
- const r = adapter.fromHook({ argv: job.argv, stdin: job.stdin, env: { ...process.env, ...job.env }, cwd: job.cwd });
3609
+ const env = { ...process.env };
3610
+ for (const k of JOB_OWN_ENV) delete env[k];
3611
+ const r = readHook(adapter, { argv: job.argv, stdin: job.stdin, env: { ...env, ...job.env }, cwd: job.cwd });
3310
3612
  if (!r || !r.session.id) {
3311
- log(state, `${job.harness} ${job.event}: the ${job.harness} adapter did not claim this payload; event dropped`);
3613
+ let keys = [];
3614
+ try {
3615
+ const payload = JSON.parse(job.argv.find((a) => a.startsWith("{")) ?? job.stdin);
3616
+ if (payload && typeof payload === "object" && !Array.isArray(payload)) keys = Object.keys(payload);
3617
+ } catch {
3618
+ }
3619
+ log(
3620
+ state,
3621
+ `${job.harness} ${job.event}: the ${job.harness} adapter did not claim this payload; event dropped; keys: ${keys.join(", ") || "(none)"}`
3622
+ );
3312
3623
  return { events: [], delivered: {} };
3313
3624
  }
3314
- const id = receiverIdOf(readAliases(aliasFile(path14.join(state, "control"))), job.harness, r.session.id) ?? r.session.id;
3625
+ const id = receiverIdOf(readAliases(aliasFile(path15.join(state, "control"))), job.harness, r.session.id) ?? r.session.id;
3315
3626
  const claudeEnd = job.harness === "claude-code" && r.events.some((e) => e.type === "session.ended");
3316
3627
  if (!claudeEnd) await tellDaemon(state, job, id, r.session.cwd, r.transcript);
3317
3628
  const outbox = new Outbox(state);
@@ -3340,12 +3651,15 @@ async function runJob(job, opts = {}) {
3340
3651
  const heavy = !/^(PreToolUse|PostToolUse|PostToolUseFailure|BeforeTool|AfterTool|PostInvocation)$/.test(job.event);
3341
3652
  let learned = {};
3342
3653
  if (heavy) {
3343
- const f = adapter.facts(r.session, r.transcript, r.hints ?? {}, facts, { ...process.env, ...job.env });
3654
+ const hookEnv = { ...process.env, ...job.env };
3655
+ const f = adapter.facts(r.session, r.transcript, r.hints ?? {}, facts, hookEnv);
3656
+ if (cfg.remote_titles && adapter.remoteFacts)
3657
+ Object.assign(f, await adapter.remoteFacts(r.session, f, r.transcript, facts, hookEnv));
3344
3658
  const { started_at: _s, last_at: _l, first_ask: _fa, harness_version, ...rest } = f;
3345
3659
  learned = rest;
3346
3660
  if (harness_version) facts.save(job.harness, id, { harness_version });
3347
3661
  facts.save(job.harness, id, { session: { ...mem.session, ...learned } });
3348
- if (CWD_FROM_HOOKS.has(job.harness) && r.session.cwd && path14.isAbsolute(r.session.cwd))
3662
+ if (CWD_FROM_HOOKS.has(job.harness) && r.session.cwd && path15.isAbsolute(r.session.cwd))
3349
3663
  facts.save(job.harness, id, { cwd: r.session.cwd });
3350
3664
  }
3351
3665
  const known = mem.session ?? {};
@@ -3360,7 +3674,8 @@ async function runJob(job, opts = {}) {
3360
3674
  seq: 0,
3361
3675
  machine: machineName(cfg, os3.hostname()),
3362
3676
  ...cwdRaw ? { cwd: tilde(cwdRaw) } : {},
3363
- ...r.session.model ? { model: r.session.model } : {}
3677
+ ...r.session.model ? { model: r.session.model } : {},
3678
+ ...r.session.launched_by ? { launched_by: r.session.launched_by } : {}
3364
3679
  };
3365
3680
  const version = facts.get(job.harness, id).harness_version ?? void 0;
3366
3681
  for (const a of r.events) {
@@ -3426,6 +3741,23 @@ async function runJob(job, opts = {}) {
3426
3741
  }
3427
3742
  if (read.turns.length) facts.save(job.harness, id, { transcript_line: read.line, transcript_turns: turnNo });
3428
3743
  }
3744
+ if (adapter.limits && r.transcript && ending && cfg.sinks.some((s) => !s.paused && Math.min(s.tier, s.max_tier ?? 3) >= 1)) {
3745
+ const limits = adapter.limits(r.transcript);
3746
+ const sig = limits && JSON.stringify(limits);
3747
+ if (limits && sig !== facts.get(job.harness, id).limits_sig) {
3748
+ events.push(
3749
+ makeEvent(
3750
+ { type: "limits.observed", harnessEvent: job.event, data: limits },
3751
+ {
3752
+ harness: { name: job.harness, ...version ? { version } : {} },
3753
+ session: { ...session, seq: outbox.nextSeq(ref) },
3754
+ now: opts.now ?? job.at
3755
+ }
3756
+ )
3757
+ );
3758
+ facts.save(job.harness, id, { limits_sig: sig });
3759
+ }
3760
+ }
3429
3761
  outbox.append(
3430
3762
  ref,
3431
3763
  events.map((e) => ({ ...e, session: redactDeep(e.session), data: redactDeep(e.data) }))
@@ -3443,11 +3775,11 @@ async function runJob(job, opts = {}) {
3443
3775
  return { events, delivered };
3444
3776
  }
3445
3777
  async function drain(state, sinkConfigs, outbox, first) {
3446
- const more = path14.join(state, "locks", "flush.more");
3778
+ const more = path15.join(state, "locks", "flush.more");
3447
3779
  let release = takeNamedLock(state, "flush");
3448
3780
  if (!release) {
3449
3781
  try {
3450
- writeFileSync7(more, "", { mode: 384 });
3782
+ writeFileSync8(more, "", { mode: 384 });
3451
3783
  } catch {
3452
3784
  }
3453
3785
  release = takeNamedLock(state, "flush");
@@ -3471,7 +3803,7 @@ async function drain(state, sinkConfigs, outbox, first) {
3471
3803
  }
3472
3804
  }
3473
3805
  var BACKOFF_MS = [2e3, 1e4, 6e4, 5 * 6e4, 30 * 6e4];
3474
- var backoffFile = (state, sink) => path14.join(state, "backoff", `${safeId(sink)}.json`);
3806
+ var backoffFile = (state, sink) => path15.join(state, "backoff", `${safeId(sink)}.json`);
3475
3807
  function readBackoff(state, sink) {
3476
3808
  try {
3477
3809
  const b = JSON.parse(readFileSync9(backoffFile(state, sink), "utf8"));
@@ -3487,8 +3819,8 @@ function noteFailure(state, sink, res, prev) {
3487
3819
  const b = { until: Date.now() + wait, failures, why: `${res.status ?? ""} ${res.error ?? ""}`.trim() };
3488
3820
  const f = backoffFile(state, sink);
3489
3821
  try {
3490
- mkdirSync8(path14.dirname(f), { recursive: true, mode: 448 });
3491
- writeFileSync7(f, JSON.stringify(b), { mode: 384 });
3822
+ mkdirSync8(path15.dirname(f), { recursive: true, mode: 448 });
3823
+ writeFileSync8(f, JSON.stringify(b), { mode: 384 });
3492
3824
  } catch {
3493
3825
  }
3494
3826
  return b;
@@ -3620,7 +3952,7 @@ function applySinkVerdict(name, res) {
3620
3952
  } catch {
3621
3953
  }
3622
3954
  }
3623
- var jobsDir = (state) => path14.join(state, "jobs");
3955
+ var jobsDir = (state) => path15.join(state, "jobs");
3624
3956
  export {
3625
3957
  HttpSink,
3626
3958
  buildSinks,