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