sessionpipe-conformance 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,25 +1,19 @@
1
- // sessionpipe-conformance 0.10.0 · Apache-2.0 · https://sessionpipe.org
1
+ // sessionpipe-conformance 0.11.0 · Apache-2.0 · https://sessionpipe.org
2
2
 
3
3
  // src/index.ts
4
4
  import { readFileSync as readFileSync6 } from "node:fs";
5
5
 
6
- // ../core/src/adapters/antigravity.ts
7
- import {
8
- existsSync as existsSync2,
9
- mkdirSync as mkdirSync2,
10
- readdirSync as readdirSync2,
11
- readFileSync as readFileSync2,
12
- realpathSync,
13
- statSync as statSync2,
14
- unlinkSync as unlinkSync2,
15
- writeFileSync as writeFileSync2
16
- } from "node:fs";
17
- import path3 from "node:path";
18
-
19
6
  // ../core/src/paths.ts
20
7
  import os from "node:os";
21
8
  var homeDir = () => os.homedir();
22
9
 
10
+ // ../core/src/adapters/claude-code.ts
11
+ import { execFileSync as execFileSync2 } from "node:child_process";
12
+ import { createHash } from "node:crypto";
13
+ import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync2, writeFileSync as writeFileSync2 } from "node:fs";
14
+ import { userInfo } from "node:os";
15
+ import path3 from "node:path";
16
+
23
17
  // ../core/src/readers/files.ts
24
18
  import { closeSync, fstatSync, openSync, readdirSync, readSync, statSync } from "node:fs";
25
19
  import path from "node:path";
@@ -110,6 +104,38 @@ function recentFiles(root, depth, sinceMs, match) {
110
104
  return out;
111
105
  }
112
106
 
107
+ // ../core/src/readers/git.ts
108
+ import { execFileSync } from "node:child_process";
109
+ var cache = /* @__PURE__ */ new Map();
110
+ function gitFacts(cwd) {
111
+ if (!cwd) return {};
112
+ const hit = cache.get(cwd);
113
+ if (hit) return hit;
114
+ const run = (args) => {
115
+ try {
116
+ return execFileSync("git", ["-C", cwd, ...args], {
117
+ encoding: "utf8",
118
+ timeout: 1500,
119
+ stdio: ["ignore", "pipe", "ignore"]
120
+ }).trim();
121
+ } catch {
122
+ return "";
123
+ }
124
+ };
125
+ const out = {};
126
+ const repo = run(["remote", "get-url", "origin"]);
127
+ if (repo) out.repo = stripCredentials(repo);
128
+ const branch = run(["rev-parse", "--abbrev-ref", "HEAD"]);
129
+ if (branch && branch !== "HEAD") out.branch = branch;
130
+ cache.set(cwd, out);
131
+ return out;
132
+ }
133
+ function stripCredentials(url) {
134
+ const ssh = /^(?:ssh:\/\/)?git@([^:/]+)[:/](.+?)(?:\.git)?$/.exec(url);
135
+ if (ssh) return `${ssh[1]}/${ssh[2]}`;
136
+ return url.replace(/\/\/[^/@]*@/, "//").replace(/^https?:\/\//, "").replace(/\.git$/, "");
137
+ }
138
+
113
139
  // ../core/src/adapters/codex-rollout.ts
114
140
  function codexSubagent(meta) {
115
141
  if (meta?.type !== "session_meta") return false;
@@ -269,8 +295,10 @@ function readJsonFile(file) {
269
295
  try {
270
296
  raw = readFileSync(file, "utf8");
271
297
  } catch {
272
- return { ok: true, existed: false, value: {}, raw: "", indent: " ", eol: "\n", finalNewline: true };
298
+ return { ok: true, existed: false, value: {}, raw: "", indent: " ", eol: "\n", finalNewline: true, bom: false };
273
299
  }
300
+ const bom = raw.charCodeAt(0) === 65279;
301
+ if (bom) raw = raw.slice(1);
274
302
  let value;
275
303
  try {
276
304
  value = JSON.parse(raw);
@@ -290,12 +318,13 @@ function readJsonFile(file) {
290
318
  raw,
291
319
  indent,
292
320
  eol,
293
- finalNewline: /\r?\n$/.test(raw)
321
+ finalNewline: /\r?\n$/.test(raw),
322
+ bom
294
323
  };
295
324
  }
296
325
  function formatJson(value, f) {
297
326
  const text = JSON.stringify(value, null, f.indent).replace(/\n/g, f.eol);
298
- return f.finalNewline ? text + f.eol : text;
327
+ return (f.bom ? "\uFEFF" : "") + (f.finalNewline ? text + f.eol : text);
299
328
  }
300
329
  function readJson(file) {
301
330
  const f = readJsonFile(file);
@@ -391,74 +420,79 @@ function installedClaudeShaped(file, harness, events, cmd, o) {
391
420
  return current === events.length ? "current" : "stale";
392
421
  }
393
422
 
394
- // ../core/src/adapters/antigravity.ts
395
- var NAME = "antigravity";
396
- var HOOK_NAME = "sessionpipe";
397
- var AG_EVENTS = ["PreInvocation", "PostInvocation", "PostToolUse", "Stop"];
398
- 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';
399
- var antigravityAnswer = (event) => event === "PreToolUse" ? AG_STALE_PRETOOL : "{}\n";
400
- var gemini = (env) => env.GEMINI_CLI_HOME || path3.join(homeDir(), ".gemini");
401
- var hooksFile = (env) => path3.join(gemini(env), "config", "hooks.json");
402
- var projectsDir = (env) => path3.join(gemini(env), "config", "projects");
403
- var dataDirs = (env) => ["antigravity", "antigravity-cli", "antigravity-ide"].map((d) => path3.join(gemini(env), d));
404
- var ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
405
- var validTool = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
406
- function entry(cmd) {
407
- const handler = (e) => ({ type: "command", command: cmd([NAME, e]).command, timeout: 5 });
408
- const out = { enabled: true };
409
- for (const e of AG_EVENTS) out[e] = /Tool/.test(e) ? [{ matcher: "*", hooks: [handler(e)] }] : [handler(e)];
410
- return out;
423
+ // ../core/src/adapters/claude-code.ts
424
+ var NAME = "claude-code";
425
+ var CLAUDE_EVENTS = [
426
+ "SessionStart",
427
+ "UserPromptSubmit",
428
+ "PreToolUse",
429
+ "PostToolUse",
430
+ "PostToolUseFailure",
431
+ "PermissionRequest",
432
+ "Notification",
433
+ "Stop",
434
+ "SubagentStart",
435
+ "SubagentStop",
436
+ "PreCompact",
437
+ "PostCompact",
438
+ "SessionEnd",
439
+ "StopFailure"
440
+ ];
441
+ var LEAN_SKIP = /* @__PURE__ */ new Set(["PreToolUse", "PostToolUseFailure"]);
442
+ var eventsFor = (lean) => lean ? CLAUDE_EVENTS.filter((e) => !LEAN_SKIP.has(e)) : [...CLAUDE_EVENTS];
443
+ var OPTS = {
444
+ matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0,
445
+ timeout: 5,
446
+ // The hook waits up to 125 s for a verified control answer, and only when control
447
+ // is paired on this machine; everywhere else it exits in milliseconds as before.
448
+ timeoutFor: (e) => e === "PermissionRequest" ? 130 : void 0
449
+ };
450
+ var DESKTOP_ID = /^local_[A-Za-z0-9-]{1,64}$/;
451
+ function claudeDirs(env = process.env, home = env.HOME || homeDir()) {
452
+ const out = [];
453
+ const add = (d) => {
454
+ if (!d) return;
455
+ const r = path3.resolve(d);
456
+ if (!out.includes(r)) out.push(r);
457
+ };
458
+ add(path3.join(home, ".claude"));
459
+ add(env.CLAUDE_CONFIG_DIR);
460
+ try {
461
+ for (const e of readdirSync2(home, { withFileTypes: true })) {
462
+ if (!e.isDirectory() || !/^\.claude[-_.]/.test(e.name)) continue;
463
+ const d = path3.join(home, e.name);
464
+ if (existsSync2(path3.join(d, "projects")) || existsSync2(path3.join(d, "settings.json"))) add(d);
465
+ }
466
+ } catch {
467
+ }
468
+ return out.filter((d, i) => i === 0 || existsSync2(d));
411
469
  }
412
- var antigravity = {
470
+ var settingsIn = (dir) => path3.join(dir, "settings.json");
471
+ var configDirOf = (transcript) => path3.dirname(path3.dirname(path3.dirname(transcript)));
472
+ var validTool = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
473
+ var endReason = (r) => r === "clear" || r === "logout" || r === "exit" ? r : r === "prompt_input_exit" ? "exit" : "other";
474
+ var notifKind = (t) => t === "permission_prompt" ? "permission" : t === "idle_prompt" ? "idle" : t === "elicitation_dialog" ? "elicitation" : "question";
475
+ var claudeCode = {
413
476
  name: NAME,
414
- events: AG_EVENTS,
415
- detect: (env = process.env) => existsSync2(path3.join(gemini(env), "config")) || dataDirs(env).some((d) => existsSync2(d)),
416
- configFiles: (env = process.env) => [hooksFile(env)],
417
- install(cmd, env = process.env) {
418
- const file = hooksFile(env);
419
- const f = readJsonFile(file);
420
- if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
421
- const hooks = f.value;
422
- const e = entry(cmd);
423
- if (JSON.stringify(hooks[HOOK_NAME]) === JSON.stringify(e)) return [{ file, changed: false }];
424
- hooks[HOOK_NAME] = e;
425
- mkdirSync2(path3.dirname(file), { recursive: true });
426
- writeFileSync2(file, formatJson(hooks, f));
427
- return [
428
- {
429
- file,
430
- changed: true,
431
- created: !f.existed,
432
- note: "hooks.json is read when a conversation starts; open ones report after a restart"
433
- }
434
- ];
435
- },
436
- uninstall(env = process.env, opts = {}) {
437
- const file = hooksFile(env);
438
- if (!existsSync2(file)) return [{ file, changed: false }];
439
- const f = readJsonFile(file);
440
- if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
441
- const hooks = f.value;
442
- if (!(HOOK_NAME in hooks)) return [{ file, changed: false }];
443
- delete hooks[HOOK_NAME];
444
- if (opts.created?.includes(file) && !Object.keys(hooks).length) {
445
- unlinkSync2(file);
446
- return [{ file, changed: true, note: "removed (sessionpipe created it)" }];
447
- }
448
- writeFileSync2(file, formatJson(hooks, f));
449
- return [{ file, changed: true }];
450
- },
451
- installed(cmd, env = process.env) {
452
- const file = hooksFile(env);
453
- const f = readJsonFile(file);
454
- const have = f.ok ? f.value[HOOK_NAME] : void 0;
455
- return [
456
- {
457
- file,
458
- state: !have ? "missing" : JSON.stringify(have) === JSON.stringify(entry(cmd)) ? "current" : "stale"
459
- }
460
- ];
461
- },
477
+ events: CLAUDE_EVENTS,
478
+ detect: (env = process.env) => claudeDirs(env).some((d) => existsSync2(d)),
479
+ configFiles: (env = process.env) => claudeDirs(env).map(settingsIn),
480
+ install: (cmd, env = process.env, opts = {}) => claudeDirs(env).flatMap((d) => {
481
+ const hooks = installClaudeShaped(settingsIn(d), NAME, eventsFor(opts.lean), cmd, OPTS);
482
+ const line = opts.statusLine === void 0 ? null : statusLine(settingsIn(d), opts.statusLine, cmd);
483
+ return line ? [hooks, line] : [hooks];
484
+ }),
485
+ uninstall: (env = process.env, opts = {}) => claudeDirs(env).map((d) => {
486
+ const line = statusLine(settingsIn(d), false);
487
+ const hooks = uninstallClaudeShaped(settingsIn(d), CLAUDE_EVENTS, {
488
+ created: opts.created?.includes(settingsIn(d))
489
+ });
490
+ return line && !hooks.changed ? line : hooks;
491
+ }),
492
+ installed: (cmd, env = process.env, opts = {}) => claudeDirs(env).map((d) => ({
493
+ file: settingsIn(d),
494
+ state: installedClaudeShaped(settingsIn(d), NAME, eventsFor(opts.lean), cmd, OPTS)
495
+ })),
462
496
  fromHook(input) {
463
497
  const event = input.argv[1] ?? "";
464
498
  let s = {};
@@ -467,48 +501,161 @@ var antigravity = {
467
501
  if (j && typeof j === "object" && !Array.isArray(j)) s = j;
468
502
  } catch {
469
503
  }
470
- const id = s.conversationId ?? s.session_id ?? s.sessionId;
471
- const stdout = antigravityAnswer(event);
472
- if (!id) return { session: { id: "" }, events: [], stdout };
473
- const workspaces = agWorkspaces(s.workspacePaths);
474
- const session = { id: String(id), cwd: workspaces[0] ?? input.cwd };
475
- if (typeof s.modelName === "string" && s.modelName !== "auto") session.model = s.modelName;
476
- const toolCall = s.toolCall;
477
- const tool = validTool(toolCall?.name) ? toolCall?.name : validTool(s.toolName) ? s.toolName : void 0;
504
+ const id = s.session_id ?? s.sessionId;
505
+ if (!id) return null;
506
+ if (notClaudeCode(String(id), s)) return null;
507
+ const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
508
+ if (typeof s.model === "string") session.model = s.model;
509
+ if (typeof s.agent_id === "string" && event.startsWith("Subagent") === false && s.agent_id)
510
+ session.parent_id = null;
511
+ const turn_id = typeof s.prompt_id === "string" ? s.prompt_id : void 0;
512
+ const tool = validTool(s.tool_name) ? s.tool_name : void 0;
513
+ const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
514
+ const ms = typeof s.duration_ms === "number" ? Math.round(s.duration_ms) : void 0;
515
+ const agent_id = typeof s.agent_id === "string" ? s.agent_id : void 0;
516
+ const permissionId = () => `perm-${createHash("sha256").update(`${turn_id ?? ""}|${tool ?? ""}|${JSON.stringify(s.tool_input ?? null)}`).digest("hex").slice(0, 16)}`;
478
517
  const ev = (type, data) => ({ type, data: prune(data), harnessEvent: event });
479
518
  const events = [];
480
519
  switch (event) {
481
- case "PreInvocation":
482
- if (Number(s.invocationNum) === 0 || s.invocationNum === void 0) {
483
- events.push(ev("session.started", { source: "unknown" }));
484
- events.push(ev("turn.started", {}));
485
- } else events.push(ev("session.heartbeat", {}));
520
+ case "SessionStart":
521
+ events.push(ev("session.started", { source: pickSource(s.source ?? s.trigger) }));
486
522
  break;
487
- case "PostInvocation":
523
+ case "UserPromptSubmit": {
524
+ const prompt = typeof s.prompt === "string" ? s.prompt : void 0;
525
+ events.push(
526
+ ev("turn.started", {
527
+ turn_id,
528
+ prompt_chars: prompt === void 0 ? void 0 : [...prompt].length,
529
+ // Claude Code's hook schema documents `source`
530
+ // (user | sdk | system | loop_wakeup | schedule_wakeup | poll_event) and says a
531
+ // payload may omit it while the field rolls out; 2.1.304's payload has no such
532
+ // key at all. Forwarded verbatim, shape-guarded only: the list is the harness's
533
+ // to extend.
534
+ submitted_by: harnessWord(s.source),
535
+ // One of Claude Code's own prompt wrappers: the prompt is a block the harness
536
+ // wrote, so it is not the person's words. Absent is unknown, never "the person" —
537
+ // and `true` is never "no person caused this turn", because a Talk or
538
+ // `sessionpipe wait` delivery hands the person's own message over inside a
539
+ // <task-notification> (PROTOCOL.md §4).
540
+ injected: prompt !== void 0 && injectedBlock(prompt) ? true : void 0
541
+ })
542
+ );
488
543
  break;
544
+ }
489
545
  case "PreToolUse":
490
- if (tool) events.push(ev("tool.started", { tool, input: toolCall?.args }));
546
+ if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, agent_id, input: s.tool_input }));
491
547
  break;
492
548
  case "PostToolUse":
493
- if (!tool) events.push(ev("session.heartbeat", {}));
494
- else
549
+ if (tool)
495
550
  events.push(
496
551
  ev("tool.ended", {
497
552
  tool,
498
- ok: !s.error,
499
- error: typeof s.error === "string" ? s.error.slice(0, 2e3) : void 0,
500
- input: toolCall?.args
553
+ call_id,
554
+ turn_id,
555
+ agent_id,
556
+ ms,
557
+ ok: true,
558
+ input: s.tool_input,
559
+ output: s.tool_response
560
+ })
561
+ );
562
+ break;
563
+ case "PostToolUseFailure":
564
+ if (tool)
565
+ events.push(
566
+ ev("tool.ended", {
567
+ tool,
568
+ call_id,
569
+ turn_id,
570
+ agent_id,
571
+ ms,
572
+ ok: false,
573
+ error: errText(s.error ?? s.tool_response),
574
+ input: s.tool_input,
575
+ output: s.tool_response
501
576
  })
502
577
  );
503
578
  break;
579
+ case "PermissionRequest":
580
+ events.push(
581
+ ev("attention.needed", {
582
+ attention_id: call_id ?? permissionId(),
583
+ kind: "permission",
584
+ tool,
585
+ message: tool ? `${tool}${describe(s.tool_input)}` : void 0
586
+ })
587
+ );
588
+ break;
589
+ case "Notification":
590
+ events.push(
591
+ ev("attention.needed", {
592
+ attention_id: `notif-${Date.now()}`,
593
+ kind: notifKind(s.notification_type),
594
+ message: typeof s.message === "string" ? s.message.slice(0, 200) : void 0
595
+ })
596
+ );
597
+ break;
504
598
  case "Stop":
505
- events.push(ev("turn.ended", { reason: s.error ? "error" : "stop" }));
599
+ events.push(
600
+ ev("turn.ended", {
601
+ turn_id,
602
+ reason: "stop",
603
+ background: Array.isArray(s.background_tasks) ? s.background_tasks.length : void 0
604
+ })
605
+ );
506
606
  break;
507
- default:
508
- return { session, events: [], stdout };
607
+ case "StopFailure":
608
+ events.push(
609
+ ev("turn.ended", {
610
+ turn_id,
611
+ reason: "error",
612
+ error: typeof s.error === "string" ? s.error.slice(0, 200) : void 0
613
+ })
614
+ );
615
+ break;
616
+ case "SubagentStart":
617
+ events.push(
618
+ ev("subagent.started", {
619
+ agent_id: String(s.agent_id ?? ""),
620
+ agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
621
+ })
622
+ );
623
+ break;
624
+ case "SubagentStop":
625
+ events.push(
626
+ ev("subagent.ended", {
627
+ agent_id: String(s.agent_id ?? ""),
628
+ agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
629
+ })
630
+ );
631
+ break;
632
+ case "PreCompact":
633
+ events.push(ev("context.compacted", { trigger: s.trigger === "manual" ? "manual" : "auto" }));
634
+ break;
635
+ case "PostCompact":
636
+ break;
637
+ // PreCompact already said it; a second event would double-count
638
+ case "SessionEnd":
639
+ events.push(ev("session.ended", { reason: endReason(s.reason) }));
640
+ break;
641
+ case "StatusLine": {
642
+ const limits = claudeLimits(s.rate_limits);
643
+ if (!limits) return null;
644
+ events.push(ev("limits.observed", limits));
645
+ break;
646
+ }
647
+ default:
648
+ return null;
509
649
  }
510
- const r = { session, events, stdout, hints: { workspaces } };
511
- if (typeof s.transcriptPath === "string") r.transcript = s.transcriptPath;
650
+ const hints = {};
651
+ if (event === "UserPromptSubmit" && typeof s.prompt === "string") hints.prompt = s.prompt;
652
+ const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
653
+ if (host && DESKTOP_ID.test(host)) hints.desktop_id = host;
654
+ const attended = input.env.CLAUDE_CODE_SESSION_ATTENDED;
655
+ if (attended === "1" || attended === "0") hints.attended = attended === "1" || !!input.env.SESSIONPIPE_CONTROL_JOB;
656
+ if (event !== "PreToolUse" && event !== "PostToolUse") hints.claude_pid = claudePid(input.env);
657
+ const r = { session, events, hints };
658
+ if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
512
659
  return r;
513
660
  },
514
661
  readTranscript(file, fromLine) {
@@ -518,259 +665,571 @@ var antigravity = {
518
665
  } catch {
519
666
  return { turns: [], line: fromLine };
520
667
  }
521
- return readAntigravity(text.split("\n"), fromLine);
668
+ return readClaude(text.split("\n"), fromLine);
522
669
  },
523
- facts(session, transcript, hints) {
524
- if (!transcript) return {};
525
- return antigravityFacts(transcript, {
526
- conversation: session.id,
527
- workspaces: hints.workspaces ?? (session.cwd ? [session.cwd] : []),
528
- env: process.env
529
- });
670
+ facts(session, transcript, hints, state) {
671
+ const out = {};
672
+ if (transcript) Object.assign(out, claudeFacts(transcript, session.id, hints, state));
673
+ const cwd = out.cwd ?? session.cwd;
674
+ Object.assign(out, gitFacts(cwd), out.branch ? { branch: out.branch } : {});
675
+ if (!out.url && typeof hints.desktop_id === "string") out.url = `claude://code/continue/${hints.desktop_id}`;
676
+ if (typeof hints.attended === "boolean") out.attended = hints.attended || !!out.url;
677
+ return out;
530
678
  },
679
+ remoteFacts: (session, local, transcript, state, env = process.env) => claudeRemoteFacts(session.id, local, transcript, state, env),
531
680
  backfill(sinceMs, env = process.env) {
532
681
  const rows2 = [];
533
- for (const data of dataDirs(env)) {
534
- let ids = [];
535
- try {
536
- ids = readdirSync2(path3.join(data, "brain")).filter((n) => /^[0-9a-f-]{36}$/i.test(n));
537
- } catch {
538
- }
539
- for (const id of ids) {
540
- const f = path3.join(data, "brain", id, ".system_generated", "logs", "transcript.jsonl");
541
- try {
542
- if (statSync2(f).mtimeMs < sinceMs) continue;
543
- } catch {
544
- continue;
545
- }
546
- const facts = antigravityFacts(f, { conversation: id, workspaces: [], env });
547
- if (!facts.started_at || !facts.last_at) continue;
548
- const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
549
- rows2.push({ session: { id, ...rest }, started_at, last_at });
550
- }
682
+ const files = [];
683
+ for (const d of claudeDirs(env))
684
+ files.push(...recentFiles(path3.join(d, "projects"), 1, sinceMs, (n) => /^[0-9a-f-]{36}\.jsonl$/.test(n)));
685
+ const noState = { get: () => ({}), save: () => {
686
+ } };
687
+ for (const f of files) {
688
+ const id = path3.basename(f, ".jsonl");
689
+ const facts = claudeFacts(f, id, {}, noState);
690
+ if (!facts.started_at || !facts.last_at) continue;
691
+ const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
692
+ const cwd = rest.cwd;
693
+ rows2.push({
694
+ session: { id, ...rest, ...gitFacts(cwd), ...rest.branch ? { branch: rest.branch } : {} },
695
+ started_at,
696
+ last_at,
697
+ transcript: f
698
+ });
551
699
  }
552
700
  return rows2;
553
701
  }
554
702
  };
703
+ var LINE_REFRESH_S = 60;
704
+ function statusLine(file, want, cmd) {
705
+ const f = readJsonFile(file);
706
+ if (!f.ok) return null;
707
+ const cur = f.value.statusLine;
708
+ const ours = typeof cur?.command === "string" && OURS.test(cur.command);
709
+ const command = cmd?.([NAME, "StatusLine"]).command;
710
+ if (want && cur && !ours)
711
+ return { file, changed: false, skipped: true, note: "a status line of your own is set; limits are not read" };
712
+ if (want ? ours && cur?.command === command && cur?.refreshInterval === LINE_REFRESH_S : !ours) return null;
713
+ if (want) f.value.statusLine = { type: "command", command, refreshInterval: LINE_REFRESH_S };
714
+ else delete f.value.statusLine;
715
+ writeFileSync2(file, formatJson(f.value, f));
716
+ return { file, changed: true, note: want ? "status line written (account limits)" : "status line removed" };
717
+ }
718
+ var epochIso = (t) => typeof t === "number" && t > 0 && t < 1e11 ? new Date(t * 1e3).toISOString() : void 0;
719
+ var WINDOW_MINUTES = { five_hour: 300, seven_day: 10080 };
720
+ function claudeLimits(rl) {
721
+ if (!rl || typeof rl !== "object" || Array.isArray(rl)) return null;
722
+ const windows = [];
723
+ for (const [id, w] of Object.entries(rl)) {
724
+ if (!/^[a-z0-9_]{1,64}$/.test(id) || typeof w?.used_percentage !== "number" || w.used_percentage < 0) continue;
725
+ windows.push(
726
+ prune({
727
+ id,
728
+ used_percent: w.used_percentage,
729
+ window_minutes: WINDOW_MINUTES[id],
730
+ resets_at: epochIso(w.resets_at)
731
+ })
732
+ );
733
+ }
734
+ return windows.length ? { windows: windows.slice(0, 8) } : null;
735
+ }
555
736
  function prune(o) {
556
737
  for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
557
738
  return o;
558
739
  }
559
- function agPath(p) {
560
- if (typeof p !== "string" || !p) return null;
561
- let s = p;
562
- if (/^file:/i.test(s)) {
563
- try {
564
- s = decodeURIComponent(new URL(s).pathname);
565
- } catch {
566
- return null;
567
- }
568
- if (process.platform === "win32" && /^\/[A-Za-z]:/.test(s)) s = s.slice(1);
569
- } else if (/^[a-z][a-z0-9+.-]*:\/\//i.test(s)) return null;
570
- if (!path3.isAbsolute(s)) return null;
571
- return path3.resolve(s);
740
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
741
+ function notClaudeCode(id, s) {
742
+ return typeof s.transcript_path !== "string" && !UUID.test(id);
572
743
  }
573
- var agWorkspaces = (paths) => (Array.isArray(paths) ? paths : []).map(agPath).filter((p) => !!p).filter((p, i, a) => a.indexOf(p) === i);
574
- function agVariants(p) {
575
- const out = [p];
576
- try {
577
- const r = realpathSync.native(p);
578
- if (r !== p) out.push(r);
579
- } catch {
744
+ function pickSource(v) {
745
+ return typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
746
+ }
747
+ var harnessWord = (v) => typeof v === "string" && /^[A-Za-z0-9][A-Za-z0-9_.-]{0,39}$/.test(v) ? v : void 0;
748
+ var WRAPPER = /^<(?:task-notification|cross-session-message|system-reminder|local-command-stdout|command-[a-z-]+)[\s/>]/i;
749
+ var injectedBlock = (prompt) => WRAPPER.test(prompt.trim());
750
+ function errText(v) {
751
+ if (typeof v === "string") return v.slice(0, 2e3);
752
+ if (v && typeof v === "object") {
753
+ const e = v;
754
+ if (typeof e.error === "string") return e.error.slice(0, 2e3);
755
+ if (typeof e.message === "string") return e.message.slice(0, 2e3);
580
756
  }
581
- return out;
757
+ return void 0;
582
758
  }
583
- function agProjects(dir) {
584
- const out = [];
759
+ function describe(input) {
760
+ if (!input || typeof input !== "object") return "";
761
+ const i = input;
762
+ const v = i.command ?? i.file_path ?? i.path ?? i.url ?? i.pattern;
763
+ return typeof v === "string" ? ` ${v.slice(0, 120)}` : "";
764
+ }
765
+ var NAME_RANK = {
766
+ user: "custom",
767
+ auto: "harness",
768
+ hook: "harness",
769
+ peer: "harness"
770
+ };
771
+ function registryEntry(j) {
772
+ if (!j || typeof j !== "object") return null;
773
+ const r = j;
774
+ const source = r.nameSource ? NAME_RANK[r.nameSource] : void 0;
775
+ return {
776
+ sessionId: r.sessionId ? String(r.sessionId) : null,
777
+ name: source && typeof r.name === "string" && r.name.trim() ? r.name.trim() : null,
778
+ name_source: source ?? null,
779
+ bridge: typeof r.bridgeSessionId === "string" ? r.bridgeSessionId : null
780
+ };
781
+ }
782
+ var liveCache = /* @__PURE__ */ new Map();
783
+ function claudeLive(dir) {
784
+ const hit = liveCache.get(dir);
785
+ if (hit) return hit;
786
+ const map = /* @__PURE__ */ new Map();
585
787
  let names = [];
586
788
  try {
587
- names = readdirSync2(dir).filter((n) => n.endsWith(".json"));
789
+ names = readdirSync2(path3.join(dir, "sessions")).filter((n) => n.endsWith(".json"));
588
790
  } catch {
589
- return out;
590
791
  }
591
792
  for (const n of names) {
592
- const id = n.slice(0, -5);
593
- if (!ID_RE.test(id)) continue;
594
- let j;
595
793
  try {
596
- j = JSON.parse(readFileSync2(path3.join(dir, n), "utf8"));
794
+ const e = registryEntry(JSON.parse(readFileSync2(path3.join(dir, "sessions", n), "utf8")));
795
+ if (e?.sessionId) map.set(e.sessionId, e);
597
796
  } catch {
598
- continue;
599
- }
600
- const folders = [];
601
- for (const r of j?.projectResources?.resources ?? []) {
602
- const f = agPath(r?.folderUri ?? r?.gitFolder?.folderUri);
603
- if (f) folders.push(...agVariants(f));
604
797
  }
605
- if (folders.length) out.push({ id, folders });
606
798
  }
607
- return out;
799
+ liveCache.set(dir, map);
800
+ return map;
608
801
  }
609
- function agMatchProject(projects, workspaces) {
610
- const ws = [];
611
- for (const w of workspaces) {
612
- const p = agPath(w);
613
- if (p) ws.push(...agVariants(p));
802
+ function claudeLiveByPid(dir, pid) {
803
+ if (!pid) return null;
804
+ try {
805
+ return registryEntry(JSON.parse(readFileSync2(path3.join(dir, "sessions", `${pid}.json`), "utf8")));
806
+ } catch {
807
+ return null;
614
808
  }
615
- let best = null;
616
- let bestLen = -1;
617
- for (const pr of projects)
618
- for (const f of pr.folders) {
619
- const within = ws.some((w) => w === f || w.startsWith(f.endsWith(path3.sep) ? f : f + path3.sep));
620
- if (within && f.length > bestLen) {
621
- best = pr.id;
622
- bestLen = f.length;
809
+ }
810
+ function claudePid(env = process.env) {
811
+ try {
812
+ const dirs = claudeDirs(env);
813
+ const has = (pid2) => dirs.some((d) => existsSync2(path3.join(d, "sessions", `${pid2}.json`)));
814
+ let pid = process.ppid;
815
+ if (has(pid)) return pid;
816
+ const parent = parentOf();
817
+ for (let hop = 0; hop < 6 && pid > 1; hop++) {
818
+ pid = parent(pid);
819
+ if (pid > 1 && has(pid)) return pid;
820
+ }
821
+ } catch {
822
+ }
823
+ return null;
824
+ }
825
+ function parentOf() {
826
+ if (process.platform === "linux") {
827
+ return (pid) => {
828
+ try {
829
+ const st = readFileSync2(`/proc/${pid}/stat`, "utf8");
830
+ return Number(st.slice(st.lastIndexOf(")") + 2).split(" ")[1]) || 0;
831
+ } catch {
832
+ return 0;
623
833
  }
834
+ };
835
+ }
836
+ const table = /* @__PURE__ */ new Map();
837
+ try {
838
+ const out = execFileSync2("ps", ["-axo", "pid=,ppid="], {
839
+ encoding: "utf8",
840
+ timeout: 2e3,
841
+ maxBuffer: 8 * 1024 * 1024,
842
+ stdio: ["ignore", "pipe", "ignore"]
843
+ });
844
+ for (const l of out.split("\n")) {
845
+ const m = /^\s*(\d+)\s+(\d+)/.exec(l);
846
+ if (m) table.set(Number(m[1]), Number(m[2]));
624
847
  }
625
- return best;
848
+ } catch {
849
+ }
850
+ return (pid) => table.get(pid) || 0;
626
851
  }
627
- function antigravityRemoteUrl(transcript, opts) {
628
- const i = transcript.indexOf(`${path3.sep}brain${path3.sep}`);
629
- const dirs = (i > 0 ? [transcript.slice(0, i)] : []).concat(dataDirs(opts.env));
630
- for (const d of dirs) {
852
+ var EMAIL_RE = /^[^@\s]{1,64}@[^@\s]{1,189}$/;
853
+ function claudeLogin(dir, home = homeDir()) {
854
+ const files = [path3.join(dir, ".claude.json")];
855
+ if (path3.resolve(dir) === path3.join(home, ".claude")) files.unshift(path3.join(home, ".claude.json"));
856
+ for (const f of files) {
631
857
  try {
632
- const m = /installation_uuid:\s*"([0-9a-f-]{36})"/i.exec(
633
- readFileSync2(path3.join(d, "antigravity_state.pbtxt"), "utf8")
634
- );
635
- if (!m) continue;
636
- const base = `https://antigravity.google.com/r/${m[1]}-v2`;
637
- const conv = opts.conversation ?? "";
638
- if (!ID_RE.test(conv) || !opts.workspaces?.length) return base;
639
- const project = agMatchProject(agProjects(opts.projectsDir ?? projectsDir(opts.env)), opts.workspaces);
640
- return project ? `${base}?p=${encodeURIComponent(`c/${conv}?section=${project}`)}` : base;
858
+ const a = JSON.parse(readFileSync2(f, "utf8")).oauthAccount;
859
+ if (typeof a?.accountUuid === "string" && a.accountUuid) {
860
+ const email = typeof a.emailAddress === "string" && EMAIL_RE.test(a.emailAddress) ? a.emailAddress : void 0;
861
+ return { id: a.accountUuid.slice(0, 128), ...email ? { email } : {} };
862
+ }
641
863
  } catch {
642
864
  }
643
865
  }
644
- return null;
866
+ return apiAccount(dir);
645
867
  }
646
- function antigravityFacts(file, opts) {
647
- const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
868
+ var ANTHROPIC_HOST = /(^|\.)anthropic\.com$/i;
869
+ function apiAccount(dir) {
870
+ const base = readJson(settingsIn(dir)).env?.ANTHROPIC_BASE_URL;
871
+ if (typeof base !== "string") return null;
872
+ let u;
873
+ try {
874
+ u = new URL(base.trim());
875
+ } catch {
876
+ return null;
877
+ }
878
+ if (u.protocol !== "https:" && u.protocol !== "http:" || !u.hostname || ANTHROPIC_HOST.test(u.hostname))
879
+ return null;
880
+ const origin = `${u.protocol}//${u.host}`;
881
+ return { id: `api-${createHash("sha256").update(origin).digest("hex").slice(0, 16)}`, name: u.host };
882
+ }
883
+ var accountCache = /* @__PURE__ */ new Map();
884
+ function claudeAccount(dir) {
885
+ const hit = accountCache.get(dir);
886
+ if (hit !== void 0) return hit;
887
+ const acct = claudeLogin(dir)?.id ?? null;
888
+ accountCache.set(dir, acct);
889
+ return acct;
890
+ }
891
+ var LINK_RE = /^https:\/\/claude\.ai\/code\/session_[A-Za-z0-9]+$/;
892
+ function transcriptLink(records) {
893
+ for (let i = records.length - 1; i >= 0; i--) {
894
+ const j = records[i];
895
+ if (j.type === "system" && j.subtype === "bridge_status" && typeof j.url === "string" && LINK_RE.test(j.url))
896
+ return j.url;
897
+ if (j.type === "bridge-session" && typeof j.bridgeSessionId === "string") {
898
+ const m = /^(?:cse|session)_([A-Za-z0-9]+)$/.exec(j.bridgeSessionId);
899
+ if (m) return `https://claude.ai/code/session_${m[1]}`;
900
+ }
901
+ }
902
+ return null;
903
+ }
904
+ function appTitle(text) {
905
+ const t = String(text).replace(/\s+/g, " ").trim();
906
+ return t.length > 50 ? `${t.slice(0, 50)}...` : t;
907
+ }
908
+ function askText(j) {
909
+ if (!j || j.type !== "user" || j.isMeta || j.isSidechain) return null;
910
+ const origin = j.origin;
911
+ if (origin?.kind && origin.kind !== "human") return null;
912
+ const c = j.message?.content;
913
+ 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();
914
+ if (!text || isInjected(text)) return null;
915
+ return text;
916
+ }
917
+ function firstAsk(records) {
918
+ for (const j of records) {
919
+ const t = askText(j);
920
+ if (t) return appTitle(t);
921
+ }
922
+ return null;
923
+ }
924
+ function scanTitles(file, from, found) {
925
+ const out = { custom: found.custom ?? null, ai: found.ai ?? null, upto: from };
926
+ let size = 0;
927
+ try {
928
+ size = statSync2(file).size;
929
+ } catch {
930
+ return out;
931
+ }
932
+ if (size < from) {
933
+ out.upto = 0;
934
+ out.custom = out.ai = null;
935
+ from = 0;
936
+ }
937
+ let pos = 0;
938
+ scanLines(
939
+ file,
940
+ (l) => {
941
+ pos += Buffer.byteLength(l, "utf8") + 1;
942
+ if (pos <= from || l.indexOf('-title"') < 0) return null;
943
+ try {
944
+ const j = JSON.parse(l);
945
+ if (j.type === "custom-title" && j.customTitle) out.custom = j.customTitle;
946
+ if (j.type === "ai-title" && j.aiTitle) out.ai = j.aiTitle;
947
+ } catch {
948
+ }
949
+ return null;
950
+ },
951
+ 512 * 1024 * 1024
952
+ );
953
+ out.upto = size;
954
+ return out;
955
+ }
956
+ function claudeFacts(file, sessionId, hints, state) {
957
+ const { head, tail } = ends(file, 64 * 1024, 256 * 1024);
648
958
  const H = parseLines(head);
649
959
  const T = parseLines(tail);
650
960
  const out = {};
651
- const url = antigravityRemoteUrl(file, opts);
652
- if (url) out.url = url;
653
- for (const j of H) {
654
- if (j.type !== "USER_INPUT" || typeof j.content !== "string") continue;
655
- const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
656
- const text = (m ? m[1] : j.content).trim();
657
- const line = (text.split("\n").find((l) => l.trim()) ?? "").trim();
658
- if (!line || /^<[a-z_-]+>/i.test(line)) continue;
659
- out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
961
+ const id = sessionId || path3.basename(file, ".jsonl");
962
+ const mem = state.get(NAME, id);
963
+ let custom = null;
964
+ let ai = null;
965
+ for (const j of H.concat(T)) {
966
+ if (j.type === "custom-title" && typeof j.customTitle === "string") custom = j.customTitle;
967
+ if (j.type === "ai-title" && typeof j.aiTitle === "string") ai = j.aiTitle;
968
+ }
969
+ let size = 0;
970
+ try {
971
+ size = statSync2(file).size;
972
+ } catch {
973
+ }
974
+ if (!custom && !ai && size > 64 * 1024 + 256 * 1024) {
975
+ const s = scanTitles(file, Number(mem.scan_upto) || 0, {
976
+ custom: mem.scan_custom,
977
+ ai: mem.scan_ai
978
+ });
979
+ custom = s.custom;
980
+ ai = s.ai;
981
+ state.save(NAME, id, { scan_upto: s.upto, scan_custom: s.custom, scan_ai: s.ai });
982
+ }
983
+ const dir = configDirOf(file);
984
+ const live = claudeLive(dir).get(id) ?? claudeLiveByPid(dir, hints.claude_pid);
985
+ if (live?.name) state.save(NAME, id, { name: live.name, name_source: live.name_source });
986
+ const bridge = live?.bridge && /^session_[A-Za-z0-9]+$/.test(live.bridge) ? `https://claude.ai/code/${live.bridge}` : null;
987
+ const url = bridge ?? transcriptLink(T) ?? transcriptLink(H) ?? (typeof mem.url === "string" ? mem.url : null);
988
+ if (url) {
989
+ out.url = url;
990
+ state.save(NAME, id, { url });
991
+ }
992
+ const name = live?.name ? { title: live.name, source: live.name_source } : typeof mem.name === "string" ? { title: mem.name, source: mem.name_source } : null;
993
+ const pick = name && name.source === "custom" && name || custom && { title: custom, source: "custom" } || name || ai && { title: ai, source: "harness" };
994
+ let ask = typeof mem.first_ask === "string" ? mem.first_ask : firstAsk(H);
995
+ if (!ask && size > 64 * 1024 && !mem.first_ask_none) {
996
+ ask = scanLines(file, (l) => {
997
+ try {
998
+ const t = askText(JSON.parse(l));
999
+ return t ? appTitle(t) : null;
1000
+ } catch {
1001
+ return null;
1002
+ }
1003
+ });
1004
+ if (!ask && size > 16 * 1024 * 1024) state.save(NAME, id, { first_ask_none: true });
1005
+ }
1006
+ if (ask) {
1007
+ out.first_ask = ask;
1008
+ if (!mem.first_ask) state.save(NAME, id, { first_ask: ask });
1009
+ }
1010
+ if (pick) {
1011
+ out.title = String(pick.title).slice(0, 120);
1012
+ out.title_source = pick.source;
1013
+ } else if (ask) {
1014
+ out.title = ask;
660
1015
  out.title_source = "first-ask";
661
- break;
662
1016
  }
663
- const times = H.concat(T).map((j) => Date.parse(String(j.created_at ?? j.timestamp ?? ""))).filter((n) => n > 0);
1017
+ const acct = claudeAccount(dir);
1018
+ if (acct) out.account_id = acct;
1019
+ for (let i = T.length - 1; i >= 0; i--) {
1020
+ const m = T[i].message?.model;
1021
+ if (T[i]?.type === "assistant" && m && m !== "<synthetic>") {
1022
+ out.model = m;
1023
+ break;
1024
+ }
1025
+ }
1026
+ const meta = [...T].reverse().find((j) => j.version) ?? H.find((j) => j.version);
1027
+ if (meta?.version) out.harness_version = String(meta.version);
1028
+ const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
664
1029
  if (times.length) {
665
1030
  out.started_at = Math.min(...times);
666
1031
  out.last_at = Math.max(...times);
667
1032
  }
1033
+ const withCwd = H.find((j) => typeof j.cwd === "string");
1034
+ if (withCwd) {
1035
+ out.cwd = withCwd.cwd;
1036
+ if (typeof withCwd.gitBranch === "string" && withCwd.gitBranch !== "HEAD") out.branch = withCwd.gitBranch;
1037
+ }
668
1038
  return out;
669
1039
  }
670
-
671
- // ../core/src/adapters/claude-code.ts
672
- import { execFileSync as execFileSync2 } from "node:child_process";
673
- import { createHash } from "node:crypto";
674
- import { existsSync as existsSync3, readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as statSync3 } from "node:fs";
675
- import path4 from "node:path";
676
-
677
- // ../core/src/readers/git.ts
678
- import { execFileSync } from "node:child_process";
679
- var cache = /* @__PURE__ */ new Map();
680
- function gitFacts(cwd) {
681
- if (!cwd) return {};
682
- const hit = cache.get(cwd);
683
- if (hit) return hit;
684
- const run = (args) => {
1040
+ var REMOTE_RETRY_MS = 6e4;
1041
+ var REMOTE_REFRESH_MS = 30 * 6e4;
1042
+ var CLAUDE_API = "https://api.anthropic.com";
1043
+ var KEYCHAIN = "Claude Code-credentials";
1044
+ var REMOTE_TIMEOUT_MS = 3e3;
1045
+ var KEYCHAIN_TIMEOUT_MS = 2e3;
1046
+ var REMOTE_MAX_BYTES = 1 << 20;
1047
+ var cleanName = (s) => s.replace(/[\p{Cc}\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]/gu, " ").replace(/\s+/g, " ").trim();
1048
+ async function claudeRemoteFacts(id, local, transcript, state, env, now = Date.now()) {
1049
+ if (!transcript || !local.url || !LINK_RE.test(local.url) || local.title_source === "custom") return {};
1050
+ const mem = state.get(NAME, id);
1051
+ let title = typeof mem.remote_title === "string" ? mem.remote_title : null;
1052
+ if (now - (Number(mem.remote_at) || 0) >= (title ? REMOTE_REFRESH_MS : REMOTE_RETRY_MS)) {
1053
+ state.save(NAME, id, { remote_at: now });
1054
+ const token = claudeAiToken(configDirOf(transcript), env, now);
1055
+ const got = token ? await claudeAiTitle(local.url.slice(local.url.lastIndexOf("/") + 1), token) : void 0;
1056
+ if (got !== void 0) {
1057
+ title = got?.slice(0, 200) ?? null;
1058
+ state.save(NAME, id, { remote_title: title });
1059
+ }
1060
+ }
1061
+ return title ? { title: title.slice(0, 120), title_source: "harness" } : {};
1062
+ }
1063
+ function keychainServices(dir, env) {
1064
+ const named = (p) => `${KEYCHAIN}-${createHash("sha256").update(p.normalize("NFC")).digest("hex").slice(0, 8)}`;
1065
+ const out = /* @__PURE__ */ new Set();
1066
+ if (env.CLAUDE_CONFIG_DIR && path3.resolve(env.CLAUDE_CONFIG_DIR) === dir) out.add(named(env.CLAUDE_CONFIG_DIR));
1067
+ if (dir === path3.join(env.HOME || homeDir(), ".claude")) out.add(KEYCHAIN);
1068
+ out.add(named(dir));
1069
+ return [...out];
1070
+ }
1071
+ var tokenCache = /* @__PURE__ */ new Map();
1072
+ function claudeAiToken(dir, env, now) {
1073
+ const hit = tokenCache.get(dir);
1074
+ if (hit && hit.expires > now + 6e4) return hit.token;
1075
+ const raw = [];
1076
+ if (process.platform === "darwin")
1077
+ for (const service of keychainServices(dir, env)) {
1078
+ try {
1079
+ const user = env.USER || userInfo().username;
1080
+ const account = /^[\w.-]+$/.test(user) ? user : "claude-code-user";
1081
+ raw.push(
1082
+ execFileSync2("security", ["find-generic-password", "-a", account, "-w", "-s", service], {
1083
+ encoding: "utf8",
1084
+ timeout: KEYCHAIN_TIMEOUT_MS,
1085
+ stdio: ["ignore", "pipe", "ignore"]
1086
+ })
1087
+ );
1088
+ break;
1089
+ } catch {
1090
+ }
1091
+ }
1092
+ try {
1093
+ raw.push(readFileSync2(path3.join(dir, ".credentials.json"), "utf8"));
1094
+ } catch {
1095
+ }
1096
+ for (const r of raw) {
685
1097
  try {
686
- return execFileSync("git", ["-C", cwd, ...args], {
687
- encoding: "utf8",
688
- timeout: 1500,
689
- stdio: ["ignore", "pipe", "ignore"]
690
- }).trim();
1098
+ const t = r.trim();
1099
+ const o = JSON.parse(/^(?:[0-9a-f]{2})+$/i.test(t) ? Buffer.from(t, "hex").toString("utf8") : t).claudeAiOauth;
1100
+ if (typeof o?.accessToken !== "string" || !o.accessToken) continue;
1101
+ const expires = typeof o.expiresAt === "number" ? o.expiresAt : Number.POSITIVE_INFINITY;
1102
+ if (expires <= now + 6e4) continue;
1103
+ tokenCache.set(dir, { token: o.accessToken, expires });
1104
+ return o.accessToken;
691
1105
  } catch {
692
- return "";
693
1106
  }
694
- };
695
- const out = {};
696
- const repo = run(["remote", "get-url", "origin"]);
697
- if (repo) out.repo = stripCredentials(repo);
698
- const branch = run(["rev-parse", "--abbrev-ref", "HEAD"]);
699
- if (branch && branch !== "HEAD") out.branch = branch;
700
- cache.set(cwd, out);
701
- return out;
1107
+ }
1108
+ return null;
702
1109
  }
703
- function stripCredentials(url) {
704
- const ssh = /^(?:ssh:\/\/)?git@([^:/]+)[:/](.+?)(?:\.git)?$/.exec(url);
705
- if (ssh) return `${ssh[1]}/${ssh[2]}`;
706
- return url.replace(/\/\/[^/@]*@/, "//").replace(/^https?:\/\//, "").replace(/\.git$/, "");
1110
+ async function claudeAiTitle(bridge, token) {
1111
+ try {
1112
+ const r = await fetch(`${CLAUDE_API}/v1/code/sessions/${bridge}`, {
1113
+ headers: { Authorization: `Bearer ${token}`, "anthropic-version": "2023-06-01", "User-Agent": "sessionpipe" },
1114
+ redirect: "error",
1115
+ signal: AbortSignal.timeout(REMOTE_TIMEOUT_MS)
1116
+ });
1117
+ if (r.status !== 200 || Number(r.headers.get("content-length")) > REMOTE_MAX_BYTES) return void 0;
1118
+ const text = await r.text();
1119
+ if (text.length > REMOTE_MAX_BYTES) return void 0;
1120
+ const j = JSON.parse(text);
1121
+ const s = j?.response_shape ?? j?.session ?? j;
1122
+ const bare = (x) => x.replace(/^(?:session|cse)_/, "");
1123
+ if (typeof s?.id !== "string" || bare(s.id) !== bare(bridge)) return void 0;
1124
+ return typeof s.title === "string" && cleanName(s.title) || null;
1125
+ } catch {
1126
+ return void 0;
1127
+ }
707
1128
  }
708
1129
 
709
- // ../core/src/adapters/claude-code.ts
710
- var NAME2 = "claude-code";
711
- var CLAUDE_EVENTS = [
712
- "SessionStart",
713
- "UserPromptSubmit",
714
- "PreToolUse",
715
- "PostToolUse",
716
- "PostToolUseFailure",
717
- "PermissionRequest",
718
- "Notification",
719
- "Stop",
720
- "SubagentStart",
721
- "SubagentStop",
722
- "PreCompact",
723
- "PostCompact",
724
- "SessionEnd",
725
- "StopFailure"
726
- ];
727
- var LEAN_SKIP = /* @__PURE__ */ new Set(["PreToolUse", "PostToolUseFailure"]);
728
- var eventsFor = (lean) => lean ? CLAUDE_EVENTS.filter((e) => !LEAN_SKIP.has(e)) : [...CLAUDE_EVENTS];
729
- var OPTS = {
730
- matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0,
731
- timeout: 5,
732
- // The hook waits up to 125 s for a verified control answer, and only when control
733
- // is paired on this machine; everywhere else it exits in milliseconds as before.
734
- timeoutFor: (e) => e === "PermissionRequest" ? 130 : void 0
735
- };
736
- var DESKTOP_ID = /^local_[A-Za-z0-9-]{1,64}$/;
737
- function claudeDirs(env = process.env, home = env.HOME || homeDir()) {
738
- const out = [];
739
- const add = (d) => {
740
- if (!d) return;
741
- const r = path4.resolve(d);
742
- if (!out.includes(r)) out.push(r);
743
- };
744
- add(path4.join(home, ".claude"));
745
- add(env.CLAUDE_CONFIG_DIR);
1130
+ // ../core/src/adapters/headless.ts
1131
+ import { accessSync, constants, statSync as statSync3 } from "node:fs";
1132
+ import path4 from "node:path";
1133
+ var JOB_TIMEOUT_MS = 30 * 6e4;
1134
+ var OUT_CAP = 1024 * 1024;
1135
+ function isExe(p) {
746
1136
  try {
747
- for (const e of readdirSync3(home, { withFileTypes: true })) {
748
- if (!e.isDirectory() || !/^\.claude[-_.]/.test(e.name)) continue;
749
- const d = path4.join(home, e.name);
750
- if (existsSync3(path4.join(d, "projects")) || existsSync3(path4.join(d, "settings.json"))) add(d);
751
- }
1137
+ if (!statSync3(p).isFile()) return false;
1138
+ accessSync(p, constants.X_OK);
1139
+ return true;
752
1140
  } catch {
1141
+ return false;
753
1142
  }
754
- return out.filter((d, i) => i === 0 || existsSync3(d));
755
1143
  }
756
- var settingsIn = (dir) => path4.join(dir, "settings.json");
757
- var configDirOf = (transcript) => path4.dirname(path4.dirname(path4.dirname(transcript)));
1144
+ function findBinary(o) {
1145
+ if (o.override && isExe(o.override)) return o.override;
1146
+ for (const d of String(o.env?.PATH || "").split(path4.delimiter)) {
1147
+ if (!d || !path4.isAbsolute(d)) continue;
1148
+ for (const n of o.names) if (isExe(path4.join(d, n))) return path4.join(d, n);
1149
+ }
1150
+ for (const d of o.dirs) for (const n of o.names) if (isExe(path4.join(d, n))) return path4.join(d, n);
1151
+ return null;
1152
+ }
1153
+
1154
+ // ../core/src/adapters/antigravity.ts
1155
+ import {
1156
+ existsSync as existsSync3,
1157
+ mkdirSync as mkdirSync2,
1158
+ readdirSync as readdirSync3,
1159
+ readFileSync as readFileSync3,
1160
+ realpathSync,
1161
+ statSync as statSync4,
1162
+ unlinkSync as unlinkSync2,
1163
+ writeFileSync as writeFileSync3
1164
+ } from "node:fs";
1165
+ import path5 from "node:path";
1166
+ var NAME2 = "antigravity";
1167
+ var HOOK_NAME = "sessionpipe";
1168
+ var AG_EVENTS = ["PreInvocation", "PostInvocation", "PostToolUse", "Stop"];
1169
+ 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';
1170
+ var antigravityAnswer = (event) => event === "PreToolUse" ? AG_STALE_PRETOOL : "{}\n";
1171
+ var gemini = (env) => env.GEMINI_CLI_HOME || path5.join(homeDir(), ".gemini");
1172
+ var hooksFile = (env) => path5.join(gemini(env), "config", "hooks.json");
1173
+ var projectsDir = (env) => path5.join(gemini(env), "config", "projects");
1174
+ var dataDirs = (env) => ["antigravity", "antigravity-cli", "antigravity-ide"].map((d) => path5.join(gemini(env), d));
1175
+ var ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
758
1176
  var validTool2 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
759
- var endReason = (r) => r === "clear" || r === "logout" || r === "exit" ? r : r === "prompt_input_exit" ? "exit" : "other";
760
- var notifKind = (t) => t === "permission_prompt" ? "permission" : t === "idle_prompt" ? "idle" : t === "elicitation_dialog" ? "elicitation" : "question";
761
- var claudeCode = {
1177
+ function entry(cmd) {
1178
+ const handler = (e) => ({ type: "command", command: cmd([NAME2, e]).command, timeout: 5 });
1179
+ const out = { enabled: true };
1180
+ for (const e of AG_EVENTS) out[e] = /Tool/.test(e) ? [{ matcher: "*", hooks: [handler(e)] }] : [handler(e)];
1181
+ return out;
1182
+ }
1183
+ var antigravity = {
762
1184
  name: NAME2,
763
- events: CLAUDE_EVENTS,
764
- detect: (env = process.env) => claudeDirs(env).some((d) => existsSync3(d)),
765
- configFiles: (env = process.env) => claudeDirs(env).map(settingsIn),
766
- install: (cmd, env = process.env, opts = {}) => claudeDirs(env).map((d) => installClaudeShaped(settingsIn(d), NAME2, eventsFor(opts.lean), cmd, OPTS)),
767
- uninstall: (env = process.env, opts = {}) => claudeDirs(env).map(
768
- (d) => uninstallClaudeShaped(settingsIn(d), CLAUDE_EVENTS, { created: opts.created?.includes(settingsIn(d)) })
769
- ),
770
- installed: (cmd, env = process.env, opts = {}) => claudeDirs(env).map((d) => ({
771
- file: settingsIn(d),
772
- state: installedClaudeShaped(settingsIn(d), NAME2, eventsFor(opts.lean), cmd, OPTS)
773
- })),
1185
+ events: AG_EVENTS,
1186
+ detect: (env = process.env) => existsSync3(path5.join(gemini(env), "config")) || dataDirs(env).some((d) => existsSync3(d)),
1187
+ configFiles: (env = process.env) => [hooksFile(env)],
1188
+ install(cmd, env = process.env) {
1189
+ const file = hooksFile(env);
1190
+ const f = readJsonFile(file);
1191
+ if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
1192
+ const hooks = f.value;
1193
+ const e = entry(cmd);
1194
+ if (JSON.stringify(hooks[HOOK_NAME]) === JSON.stringify(e)) return [{ file, changed: false }];
1195
+ hooks[HOOK_NAME] = e;
1196
+ mkdirSync2(path5.dirname(file), { recursive: true });
1197
+ writeFileSync3(file, formatJson(hooks, f));
1198
+ return [
1199
+ {
1200
+ file,
1201
+ changed: true,
1202
+ created: !f.existed,
1203
+ note: "hooks.json is read when a conversation starts; open ones report after a restart"
1204
+ }
1205
+ ];
1206
+ },
1207
+ uninstall(env = process.env, opts = {}) {
1208
+ const file = hooksFile(env);
1209
+ if (!existsSync3(file)) return [{ file, changed: false }];
1210
+ const f = readJsonFile(file);
1211
+ if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
1212
+ const hooks = f.value;
1213
+ if (!(HOOK_NAME in hooks)) return [{ file, changed: false }];
1214
+ delete hooks[HOOK_NAME];
1215
+ if (opts.created?.includes(file) && !Object.keys(hooks).length) {
1216
+ unlinkSync2(file);
1217
+ return [{ file, changed: true, note: "removed (sessionpipe created it)" }];
1218
+ }
1219
+ writeFileSync3(file, formatJson(hooks, f));
1220
+ return [{ file, changed: true }];
1221
+ },
1222
+ installed(cmd, env = process.env) {
1223
+ const file = hooksFile(env);
1224
+ const f = readJsonFile(file);
1225
+ const have = f.ok ? f.value[HOOK_NAME] : void 0;
1226
+ return [
1227
+ {
1228
+ file,
1229
+ state: !have ? "missing" : JSON.stringify(have) === JSON.stringify(entry(cmd)) ? "current" : "stale"
1230
+ }
1231
+ ];
1232
+ },
774
1233
  fromHook(input) {
775
1234
  const event = input.argv[1] ?? "";
776
1235
  let s = {};
@@ -779,155 +1238,48 @@ var claudeCode = {
779
1238
  if (j && typeof j === "object" && !Array.isArray(j)) s = j;
780
1239
  } catch {
781
1240
  }
782
- const id = s.session_id ?? s.sessionId;
783
- if (!id) return null;
784
- if (notClaudeCode(String(id), s)) return null;
785
- const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
786
- if (typeof s.model === "string") session.model = s.model;
787
- if (typeof s.agent_id === "string" && event.startsWith("Subagent") === false && s.agent_id)
788
- session.parent_id = null;
789
- const turn_id = typeof s.prompt_id === "string" ? s.prompt_id : void 0;
790
- const tool = validTool2(s.tool_name) ? s.tool_name : void 0;
791
- const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
792
- const ms = typeof s.duration_ms === "number" ? Math.round(s.duration_ms) : void 0;
793
- const agent_id = typeof s.agent_id === "string" ? s.agent_id : void 0;
794
- const permissionId = () => `perm-${createHash("sha256").update(`${turn_id ?? ""}|${tool ?? ""}|${JSON.stringify(s.tool_input ?? null)}`).digest("hex").slice(0, 16)}`;
1241
+ const id = s.conversationId ?? s.session_id ?? s.sessionId;
1242
+ const stdout = antigravityAnswer(event);
1243
+ if (!id) return { session: { id: "" }, events: [], stdout };
1244
+ const workspaces = agWorkspaces(s.workspacePaths);
1245
+ const session = { id: String(id), cwd: workspaces[0] ?? input.cwd };
1246
+ if (typeof s.modelName === "string" && s.modelName !== "auto") session.model = s.modelName;
1247
+ const toolCall = s.toolCall;
1248
+ const tool = validTool2(toolCall?.name) ? toolCall?.name : validTool2(s.toolName) ? s.toolName : void 0;
795
1249
  const ev = (type, data) => ({ type, data: prune2(data), harnessEvent: event });
796
1250
  const events = [];
797
1251
  switch (event) {
798
- case "SessionStart":
799
- events.push(ev("session.started", { source: pickSource(s.source ?? s.trigger) }));
1252
+ case "PreInvocation":
1253
+ if (Number(s.invocationNum) === 0 || s.invocationNum === void 0) {
1254
+ events.push(ev("session.started", { source: "unknown" }));
1255
+ events.push(ev("turn.started", {}));
1256
+ } else events.push(ev("session.heartbeat", {}));
800
1257
  break;
801
- case "UserPromptSubmit": {
802
- const prompt = typeof s.prompt === "string" ? s.prompt : void 0;
803
- events.push(
804
- ev("turn.started", {
805
- turn_id,
806
- prompt_chars: prompt === void 0 ? void 0 : [...prompt].length,
807
- // Claude Code's hook schema documents `source`
808
- // (user | sdk | system | loop_wakeup | schedule_wakeup | poll_event) and says a
809
- // payload may omit it while the field rolls out; 2.1.304's payload has no such
810
- // key at all. Forwarded verbatim, shape-guarded only: the list is the harness's
811
- // to extend.
812
- submitted_by: harnessWord(s.source),
813
- // One of Claude Code's own prompt wrappers: the prompt is a block the harness
814
- // wrote, so it is not the person's words. Absent is unknown, never "the person" —
815
- // and `true` is never "no person caused this turn", because a Talk or
816
- // `sessionpipe wait` delivery hands the person's own message over inside a
817
- // <task-notification> (PROTOCOL.md §4).
818
- injected: prompt !== void 0 && injectedBlock(prompt) ? true : void 0
819
- })
820
- );
1258
+ case "PostInvocation":
821
1259
  break;
822
- }
823
1260
  case "PreToolUse":
824
- if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, agent_id, input: s.tool_input }));
1261
+ if (tool) events.push(ev("tool.started", { tool, input: toolCall?.args }));
825
1262
  break;
826
1263
  case "PostToolUse":
827
- if (tool)
828
- events.push(
829
- ev("tool.ended", {
830
- tool,
831
- call_id,
832
- turn_id,
833
- agent_id,
834
- ms,
835
- ok: true,
836
- input: s.tool_input,
837
- output: s.tool_response
838
- })
839
- );
840
- break;
841
- case "PostToolUseFailure":
842
- if (tool)
1264
+ if (!tool) events.push(ev("session.heartbeat", {}));
1265
+ else
843
1266
  events.push(
844
1267
  ev("tool.ended", {
845
1268
  tool,
846
- call_id,
847
- turn_id,
848
- agent_id,
849
- ms,
850
- ok: false,
851
- error: errText(s.error ?? s.tool_response),
852
- input: s.tool_input,
853
- output: s.tool_response
1269
+ ok: !s.error,
1270
+ error: typeof s.error === "string" ? s.error.slice(0, 2e3) : void 0,
1271
+ input: toolCall?.args
854
1272
  })
855
1273
  );
856
1274
  break;
857
- case "PermissionRequest":
858
- events.push(
859
- ev("attention.needed", {
860
- attention_id: call_id ?? permissionId(),
861
- kind: "permission",
862
- tool,
863
- message: tool ? `${tool}${describe(s.tool_input)}` : void 0
864
- })
865
- );
866
- break;
867
- case "Notification":
868
- events.push(
869
- ev("attention.needed", {
870
- attention_id: `notif-${Date.now()}`,
871
- kind: notifKind(s.notification_type),
872
- message: typeof s.message === "string" ? s.message.slice(0, 200) : void 0
873
- })
874
- );
875
- break;
876
1275
  case "Stop":
877
- events.push(
878
- ev("turn.ended", {
879
- turn_id,
880
- reason: "stop",
881
- background: Array.isArray(s.background_tasks) ? s.background_tasks.length : void 0
882
- })
883
- );
884
- break;
885
- case "StopFailure":
886
- events.push(
887
- ev("turn.ended", {
888
- turn_id,
889
- reason: "error",
890
- error: typeof s.error === "string" ? s.error.slice(0, 200) : void 0
891
- })
892
- );
893
- break;
894
- case "SubagentStart":
895
- events.push(
896
- ev("subagent.started", {
897
- agent_id: String(s.agent_id ?? ""),
898
- agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
899
- })
900
- );
901
- break;
902
- case "SubagentStop":
903
- events.push(
904
- ev("subagent.ended", {
905
- agent_id: String(s.agent_id ?? ""),
906
- agent_type: typeof s.agent_type === "string" ? s.agent_type : void 0
907
- })
908
- );
909
- break;
910
- case "PreCompact":
911
- events.push(ev("context.compacted", { trigger: s.trigger === "manual" ? "manual" : "auto" }));
912
- break;
913
- case "PostCompact":
914
- break;
915
- // PreCompact already said it; a second event would double-count
916
- case "SessionEnd":
917
- events.push(ev("session.ended", { reason: endReason(s.reason) }));
1276
+ events.push(ev("turn.ended", { reason: s.error ? "error" : "stop" }));
918
1277
  break;
919
1278
  default:
920
- return null;
1279
+ return { session, events: [], stdout };
921
1280
  }
922
- const hints = {};
923
- if (event === "UserPromptSubmit" && typeof s.prompt === "string") hints.prompt = s.prompt;
924
- const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
925
- if (host && DESKTOP_ID.test(host)) hints.desktop_id = host;
926
- const attended = input.env.CLAUDE_CODE_SESSION_ATTENDED;
927
- if (attended === "1" || attended === "0") hints.attended = attended === "1" || !!input.env.SESSIONPIPE_CONTROL_JOB;
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;
1281
+ const r = { session, events, stdout, hints: { workspaces } };
1282
+ if (typeof s.transcriptPath === "string") r.transcript = s.transcriptPath;
931
1283
  return r;
932
1284
  },
933
1285
  readTranscript(file, fromLine) {
@@ -937,355 +1289,162 @@ var claudeCode = {
937
1289
  } catch {
938
1290
  return { turns: [], line: fromLine };
939
1291
  }
940
- return readClaude(text.split("\n"), fromLine);
1292
+ return readAntigravity(text.split("\n"), fromLine);
941
1293
  },
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
- if (typeof hints.attended === "boolean") out.attended = hints.attended || !!out.url;
949
- return out;
1294
+ facts(session, transcript, hints) {
1295
+ if (!transcript) return {};
1296
+ return antigravityFacts(transcript, {
1297
+ conversation: session.id,
1298
+ workspaces: hints.workspaces ?? (session.cwd ? [session.cwd] : []),
1299
+ env: process.env
1300
+ });
950
1301
  },
951
1302
  backfill(sinceMs, env = process.env) {
952
1303
  const rows2 = [];
953
- const files = [];
954
- for (const d of claudeDirs(env))
955
- files.push(...recentFiles(path4.join(d, "projects"), 1, sinceMs, (n) => /^[0-9a-f-]{36}\.jsonl$/.test(n)));
956
- const noState = { get: () => ({}), save: () => {
957
- } };
958
- for (const f of files) {
959
- const id = path4.basename(f, ".jsonl");
960
- const facts = claudeFacts(f, id, {}, noState);
961
- if (!facts.started_at || !facts.last_at) continue;
962
- const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
963
- const cwd = rest.cwd;
964
- rows2.push({
965
- session: { id, ...rest, ...gitFacts(cwd), ...rest.branch ? { branch: rest.branch } : {} },
966
- started_at,
967
- last_at
968
- });
969
- }
970
- return rows2;
971
- }
972
- };
973
- function prune2(o) {
974
- for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
975
- return o;
976
- }
977
- var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
978
- function notClaudeCode(id, s) {
979
- return typeof s.transcript_path !== "string" && !UUID.test(id);
980
- }
981
- function pickSource(v) {
982
- return typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
983
- }
984
- var harnessWord = (v) => typeof v === "string" && /^[A-Za-z0-9][A-Za-z0-9_.-]{0,39}$/.test(v) ? v : void 0;
985
- var WRAPPER = /^<(?:task-notification|cross-session-message|system-reminder|local-command-stdout|command-[a-z-]+)[\s/>]/i;
986
- var injectedBlock = (prompt) => WRAPPER.test(prompt.trim());
987
- function errText(v) {
988
- if (typeof v === "string") return v.slice(0, 2e3);
989
- if (v && typeof v === "object") {
990
- const e = v;
991
- if (typeof e.error === "string") return e.error.slice(0, 2e3);
992
- if (typeof e.message === "string") return e.message.slice(0, 2e3);
993
- }
994
- return void 0;
995
- }
996
- function describe(input) {
997
- if (!input || typeof input !== "object") return "";
998
- const i = input;
999
- const v = i.command ?? i.file_path ?? i.path ?? i.url ?? i.pattern;
1000
- return typeof v === "string" ? ` ${v.slice(0, 120)}` : "";
1001
- }
1002
- var NAME_RANK = {
1003
- user: "custom",
1004
- auto: "harness",
1005
- hook: "harness",
1006
- peer: "harness"
1007
- };
1008
- function registryEntry(j) {
1009
- if (!j || typeof j !== "object") return null;
1010
- const r = j;
1011
- const source = r.nameSource ? NAME_RANK[r.nameSource] : void 0;
1012
- return {
1013
- sessionId: r.sessionId ? String(r.sessionId) : null,
1014
- name: source && typeof r.name === "string" && r.name.trim() ? r.name.trim() : null,
1015
- name_source: source ?? null,
1016
- bridge: typeof r.bridgeSessionId === "string" ? r.bridgeSessionId : null
1017
- };
1018
- }
1019
- var liveCache = /* @__PURE__ */ new Map();
1020
- function claudeLive(dir) {
1021
- const hit = liveCache.get(dir);
1022
- if (hit) return hit;
1023
- const map = /* @__PURE__ */ new Map();
1024
- let names = [];
1025
- try {
1026
- names = readdirSync3(path4.join(dir, "sessions")).filter((n) => n.endsWith(".json"));
1027
- } catch {
1028
- }
1029
- for (const n of names) {
1030
- try {
1031
- const e = registryEntry(JSON.parse(readFileSync3(path4.join(dir, "sessions", n), "utf8")));
1032
- if (e?.sessionId) map.set(e.sessionId, e);
1033
- } catch {
1034
- }
1035
- }
1036
- liveCache.set(dir, map);
1037
- return map;
1038
- }
1039
- function claudeLiveByPid(dir, pid) {
1040
- if (!pid) return null;
1041
- try {
1042
- return registryEntry(JSON.parse(readFileSync3(path4.join(dir, "sessions", `${pid}.json`), "utf8")));
1043
- } catch {
1044
- return null;
1045
- }
1046
- }
1047
- function claudePid(env = process.env) {
1048
- try {
1049
- const dirs = claudeDirs(env);
1050
- const has = (pid2) => dirs.some((d) => existsSync3(path4.join(d, "sessions", `${pid2}.json`)));
1051
- let pid = process.ppid;
1052
- if (has(pid)) return pid;
1053
- const parent = parentOf();
1054
- for (let hop = 0; hop < 6 && pid > 1; hop++) {
1055
- pid = parent(pid);
1056
- if (pid > 1 && has(pid)) return pid;
1057
- }
1058
- } catch {
1059
- }
1060
- return null;
1061
- }
1062
- function parentOf() {
1063
- if (process.platform === "linux") {
1064
- return (pid) => {
1304
+ for (const data of dataDirs(env)) {
1305
+ let ids = [];
1065
1306
  try {
1066
- const st = readFileSync3(`/proc/${pid}/stat`, "utf8");
1067
- return Number(st.slice(st.lastIndexOf(")") + 2).split(" ")[1]) || 0;
1307
+ ids = readdirSync3(path5.join(data, "brain")).filter((n) => /^[0-9a-f-]{36}$/i.test(n));
1068
1308
  } catch {
1069
- return 0;
1070
1309
  }
1071
- };
1072
- }
1073
- const table = /* @__PURE__ */ new Map();
1074
- try {
1075
- const out = execFileSync2("ps", ["-axo", "pid=,ppid="], {
1076
- encoding: "utf8",
1077
- timeout: 2e3,
1078
- maxBuffer: 8 * 1024 * 1024,
1079
- stdio: ["ignore", "pipe", "ignore"]
1080
- });
1081
- for (const l of out.split("\n")) {
1082
- const m = /^\s*(\d+)\s+(\d+)/.exec(l);
1083
- if (m) table.set(Number(m[1]), Number(m[2]));
1310
+ for (const id of ids) {
1311
+ const f = path5.join(data, "brain", id, ".system_generated", "logs", "transcript.jsonl");
1312
+ try {
1313
+ if (statSync4(f).mtimeMs < sinceMs) continue;
1314
+ } catch {
1315
+ continue;
1316
+ }
1317
+ const facts = antigravityFacts(f, { conversation: id, workspaces: [], env });
1318
+ if (!facts.started_at || !facts.last_at) continue;
1319
+ const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
1320
+ rows2.push({ session: { id, ...rest }, started_at, last_at });
1321
+ }
1084
1322
  }
1085
- } catch {
1323
+ return rows2;
1086
1324
  }
1087
- return (pid) => table.get(pid) || 0;
1325
+ };
1326
+ function prune2(o) {
1327
+ for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
1328
+ return o;
1088
1329
  }
1089
- var EMAIL_RE = /^[^@\s]{1,64}@[^@\s]{1,189}$/;
1090
- function claudeLogin(dir, home = homeDir()) {
1091
- const files = [path4.join(dir, ".claude.json")];
1092
- if (path4.resolve(dir) === path4.join(home, ".claude")) files.unshift(path4.join(home, ".claude.json"));
1093
- for (const f of files) {
1330
+ function agPath(p) {
1331
+ if (typeof p !== "string" || !p) return null;
1332
+ let s = p;
1333
+ if (/^file:/i.test(s)) {
1094
1334
  try {
1095
- const a = JSON.parse(readFileSync3(f, "utf8")).oauthAccount;
1096
- if (typeof a?.accountUuid === "string" && a.accountUuid) {
1097
- const email = typeof a.emailAddress === "string" && EMAIL_RE.test(a.emailAddress) ? a.emailAddress : void 0;
1098
- return { id: a.accountUuid.slice(0, 128), ...email ? { email } : {} };
1099
- }
1335
+ s = decodeURIComponent(new URL(s).pathname);
1100
1336
  } catch {
1337
+ return null;
1101
1338
  }
1102
- }
1103
- return apiAccount(dir);
1339
+ if (process.platform === "win32" && /^\/[A-Za-z]:/.test(s)) s = s.slice(1);
1340
+ } else if (/^[a-z][a-z0-9+.-]*:\/\//i.test(s)) return null;
1341
+ if (!path5.isAbsolute(s)) return null;
1342
+ return path5.resolve(s);
1104
1343
  }
1105
- var ANTHROPIC_HOST = /(^|\.)anthropic\.com$/i;
1106
- function apiAccount(dir) {
1107
- let base;
1344
+ var agWorkspaces = (paths) => (Array.isArray(paths) ? paths : []).map(agPath).filter((p) => !!p).filter((p, i, a) => a.indexOf(p) === i);
1345
+ function agVariants(p) {
1346
+ const out = [p];
1108
1347
  try {
1109
- base = JSON.parse(readFileSync3(settingsIn(dir), "utf8")).env?.ANTHROPIC_BASE_URL;
1348
+ const r = realpathSync.native(p);
1349
+ if (r !== p) out.push(r);
1110
1350
  } catch {
1111
- return null;
1112
1351
  }
1113
- if (typeof base !== "string") return null;
1114
- let u;
1352
+ return out;
1353
+ }
1354
+ function agProjects(dir) {
1355
+ const out = [];
1356
+ let names = [];
1115
1357
  try {
1116
- u = new URL(base.trim());
1358
+ names = readdirSync3(dir).filter((n) => n.endsWith(".json"));
1117
1359
  } catch {
1118
- return null;
1360
+ return out;
1119
1361
  }
1120
- if (u.protocol !== "https:" && u.protocol !== "http:" || !u.hostname || ANTHROPIC_HOST.test(u.hostname))
1121
- return null;
1122
- const origin = `${u.protocol}//${u.host}`;
1123
- return { id: `api-${createHash("sha256").update(origin).digest("hex").slice(0, 16)}`, name: u.host };
1124
- }
1125
- var accountCache = /* @__PURE__ */ new Map();
1126
- function claudeAccount(dir) {
1127
- const hit = accountCache.get(dir);
1128
- if (hit !== void 0) return hit;
1129
- const acct = claudeLogin(dir)?.id ?? null;
1130
- accountCache.set(dir, acct);
1131
- return acct;
1132
- }
1133
- var LINK_RE = /^https:\/\/claude\.ai\/code\/session_[A-Za-z0-9]+$/;
1134
- function transcriptLink(records) {
1135
- for (let i = records.length - 1; i >= 0; i--) {
1136
- const j = records[i];
1137
- if (j.type === "system" && j.subtype === "bridge_status" && typeof j.url === "string" && LINK_RE.test(j.url))
1138
- return j.url;
1139
- if (j.type === "bridge-session" && typeof j.bridgeSessionId === "string") {
1140
- const m = /^(?:cse|session)_([A-Za-z0-9]+)$/.exec(j.bridgeSessionId);
1141
- if (m) return `https://claude.ai/code/session_${m[1]}`;
1362
+ for (const n of names) {
1363
+ const id = n.slice(0, -5);
1364
+ if (!ID_RE.test(id)) continue;
1365
+ let j;
1366
+ try {
1367
+ j = JSON.parse(readFileSync3(path5.join(dir, n), "utf8"));
1368
+ } catch {
1369
+ continue;
1370
+ }
1371
+ const folders = [];
1372
+ for (const r of j?.projectResources?.resources ?? []) {
1373
+ const f = agPath(r?.folderUri ?? r?.gitFolder?.folderUri);
1374
+ if (f) folders.push(...agVariants(f));
1142
1375
  }
1376
+ if (folders.length) out.push({ id, folders });
1143
1377
  }
1144
- return null;
1145
- }
1146
- function appTitle(text) {
1147
- const t = String(text).replace(/\s+/g, " ").trim();
1148
- return t.length > 50 ? `${t.slice(0, 50)}...` : t;
1149
- }
1150
- function askText(j) {
1151
- if (!j || j.type !== "user" || j.isMeta || j.isSidechain) return null;
1152
- const origin = j.origin;
1153
- if (origin?.kind && origin.kind !== "human") return null;
1154
- const c = j.message?.content;
1155
- 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();
1156
- if (!text || isInjected(text)) return null;
1157
- return text;
1378
+ return out;
1158
1379
  }
1159
- function firstAsk(records) {
1160
- for (const j of records) {
1161
- const t = askText(j);
1162
- if (t) return appTitle(t);
1380
+ function agMatchProject(projects, workspaces) {
1381
+ const ws = [];
1382
+ for (const w of workspaces) {
1383
+ const p = agPath(w);
1384
+ if (p) ws.push(...agVariants(p));
1163
1385
  }
1164
- return null;
1386
+ let best = null;
1387
+ let bestLen = -1;
1388
+ for (const pr of projects)
1389
+ for (const f of pr.folders) {
1390
+ const within = ws.some((w) => w === f || w.startsWith(f.endsWith(path5.sep) ? f : f + path5.sep));
1391
+ if (within && f.length > bestLen) {
1392
+ best = pr.id;
1393
+ bestLen = f.length;
1394
+ }
1395
+ }
1396
+ return best;
1165
1397
  }
1166
- function scanTitles(file, from, found) {
1167
- const out = { custom: found.custom ?? null, ai: found.ai ?? null, upto: from };
1168
- let size = 0;
1169
- try {
1170
- size = statSync3(file).size;
1171
- } catch {
1172
- return out;
1173
- }
1174
- if (size < from) {
1175
- out.upto = 0;
1176
- out.custom = out.ai = null;
1177
- from = 0;
1398
+ function antigravityRemoteUrl(transcript, opts) {
1399
+ const i = transcript.indexOf(`${path5.sep}brain${path5.sep}`);
1400
+ const dirs = (i > 0 ? [transcript.slice(0, i)] : []).concat(dataDirs(opts.env));
1401
+ for (const d of dirs) {
1402
+ try {
1403
+ const m = /installation_uuid:\s*"([0-9a-f-]{36})"/i.exec(
1404
+ readFileSync3(path5.join(d, "antigravity_state.pbtxt"), "utf8")
1405
+ );
1406
+ if (!m) continue;
1407
+ const base = `https://antigravity.google.com/r/${m[1]}-v2`;
1408
+ const conv = opts.conversation ?? "";
1409
+ if (!ID_RE.test(conv) || !opts.workspaces?.length) return base;
1410
+ const project = agMatchProject(agProjects(opts.projectsDir ?? projectsDir(opts.env)), opts.workspaces);
1411
+ return project ? `${base}?p=${encodeURIComponent(`c/${conv}?section=${project}`)}` : base;
1412
+ } catch {
1413
+ }
1178
1414
  }
1179
- let pos = 0;
1180
- scanLines(
1181
- file,
1182
- (l) => {
1183
- pos += Buffer.byteLength(l, "utf8") + 1;
1184
- if (pos <= from || l.indexOf('-title"') < 0) return null;
1185
- try {
1186
- const j = JSON.parse(l);
1187
- if (j.type === "custom-title" && j.customTitle) out.custom = j.customTitle;
1188
- if (j.type === "ai-title" && j.aiTitle) out.ai = j.aiTitle;
1189
- } catch {
1190
- }
1191
- return null;
1192
- },
1193
- 512 * 1024 * 1024
1194
- );
1195
- out.upto = size;
1196
- return out;
1415
+ return null;
1197
1416
  }
1198
- function claudeFacts(file, sessionId, hints, state) {
1199
- const { head, tail } = ends(file, 64 * 1024, 256 * 1024);
1417
+ function antigravityFacts(file, opts) {
1418
+ const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
1200
1419
  const H = parseLines(head);
1201
1420
  const T = parseLines(tail);
1202
1421
  const out = {};
1203
- const id = sessionId || path4.basename(file, ".jsonl");
1204
- const mem = state.get(NAME2, id);
1205
- let custom = null;
1206
- let ai = null;
1207
- for (const j of H.concat(T)) {
1208
- if (j.type === "custom-title" && typeof j.customTitle === "string") custom = j.customTitle;
1209
- if (j.type === "ai-title" && typeof j.aiTitle === "string") ai = j.aiTitle;
1210
- }
1211
- let size = 0;
1212
- try {
1213
- size = statSync3(file).size;
1214
- } catch {
1215
- }
1216
- if (!custom && !ai && size > 64 * 1024 + 256 * 1024) {
1217
- const s = scanTitles(file, Number(mem.scan_upto) || 0, {
1218
- custom: mem.scan_custom,
1219
- ai: mem.scan_ai
1220
- });
1221
- custom = s.custom;
1222
- ai = s.ai;
1223
- state.save(NAME2, id, { scan_upto: s.upto, scan_custom: s.custom, scan_ai: s.ai });
1224
- }
1225
- const dir = configDirOf(file);
1226
- const live = claudeLive(dir).get(id) ?? claudeLiveByPid(dir, hints.claude_pid);
1227
- if (live?.name) state.save(NAME2, id, { name: live.name, name_source: live.name_source });
1228
- const bridge = live?.bridge && /^session_[A-Za-z0-9]+$/.test(live.bridge) ? `https://claude.ai/code/${live.bridge}` : null;
1229
- const url = bridge ?? transcriptLink(T) ?? transcriptLink(H) ?? (typeof mem.url === "string" ? mem.url : null);
1230
- if (url) {
1231
- out.url = url;
1232
- state.save(NAME2, id, { url });
1233
- }
1234
- const name = live?.name ? { title: live.name, source: live.name_source } : typeof mem.name === "string" ? { title: mem.name, source: mem.name_source } : null;
1235
- const pick = name && name.source === "custom" && name || custom && { title: custom, source: "custom" } || name || ai && { title: ai, source: "harness" };
1236
- let ask = typeof mem.first_ask === "string" ? mem.first_ask : firstAsk(H);
1237
- if (!ask && size > 64 * 1024 && !mem.first_ask_none) {
1238
- ask = scanLines(file, (l) => {
1239
- try {
1240
- const t = askText(JSON.parse(l));
1241
- return t ? appTitle(t) : null;
1242
- } catch {
1243
- return null;
1244
- }
1245
- });
1246
- if (!ask && size > 16 * 1024 * 1024) state.save(NAME2, id, { first_ask_none: true });
1247
- }
1248
- if (ask) {
1249
- out.first_ask = ask;
1250
- if (!mem.first_ask) state.save(NAME2, id, { first_ask: ask });
1251
- }
1252
- if (pick) {
1253
- out.title = String(pick.title).slice(0, 120);
1254
- out.title_source = pick.source;
1255
- } else if (ask) {
1256
- out.title = ask;
1422
+ const url = antigravityRemoteUrl(file, opts);
1423
+ if (url) out.url = url;
1424
+ for (const j of H) {
1425
+ if (j.type !== "USER_INPUT" || typeof j.content !== "string") continue;
1426
+ const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
1427
+ const text = (m ? m[1] : j.content).trim();
1428
+ const line = (text.split("\n").find((l) => l.trim()) ?? "").trim();
1429
+ if (!line || /^<[a-z_-]+>/i.test(line)) continue;
1430
+ out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
1257
1431
  out.title_source = "first-ask";
1432
+ break;
1258
1433
  }
1259
- const acct = claudeAccount(dir);
1260
- if (acct) out.account_id = acct;
1261
- for (let i = T.length - 1; i >= 0; i--) {
1262
- const m = T[i].message?.model;
1263
- if (T[i]?.type === "assistant" && m && m !== "<synthetic>") {
1264
- out.model = m;
1265
- break;
1266
- }
1267
- }
1268
- const meta = [...T].reverse().find((j) => j.version) ?? H.find((j) => j.version);
1269
- if (meta?.version) out.harness_version = String(meta.version);
1270
- const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
1434
+ const times = H.concat(T).map((j) => Date.parse(String(j.created_at ?? j.timestamp ?? ""))).filter((n) => n > 0);
1271
1435
  if (times.length) {
1272
1436
  out.started_at = Math.min(...times);
1273
1437
  out.last_at = Math.max(...times);
1274
1438
  }
1275
- const withCwd = H.find((j) => typeof j.cwd === "string");
1276
- if (withCwd) {
1277
- out.cwd = withCwd.cwd;
1278
- if (typeof withCwd.gitBranch === "string" && withCwd.gitBranch !== "HEAD") out.branch = withCwd.gitBranch;
1279
- }
1280
1439
  return out;
1281
1440
  }
1282
1441
 
1283
1442
  // ../core/src/adapters/codex.ts
1284
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
1285
- import path5 from "node:path";
1443
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
1444
+ import path6 from "node:path";
1286
1445
 
1287
1446
  // ../core/src/adapters/codex-config.ts
1288
- var NOTIFY_LINE = /^[ \t]*notify[ \t]*=[ \t]*(\[.*\])[ \t]*(?:#.*)?$/m;
1447
+ var NOTIFY_LINE = /^[ \t]*notify[ \t]*=[ \t]*(\[.*\])[ \t]*(?:#.*)?(?=\r?$)/m;
1289
1448
  var NOTIFY_KEY = /^[ \t]*notify[ \t]*=/m;
1290
1449
  var OURS2 = /(?:hook\.js|sessionpipe-hook)",\s*"codex",\s*"notify"/;
1291
1450
  function scan(line, st) {
@@ -1375,10 +1534,11 @@ function dropLine(text, index, length) {
1375
1534
  }
1376
1535
  function insertTopLevel(text, line) {
1377
1536
  const { head, tail } = split(text);
1378
- if (!tail) return (head.trim() ? head.replace(/\s*$/, "\n") : "") + line + "\n";
1379
- return `${(head.trim() ? head.replace(/\s*$/, "\n") : "") + line}
1380
-
1381
- ${tail}`;
1537
+ const body = head.replace(/\s*$/, "");
1538
+ const eol = (body.includes("\n") ? body : text).includes("\r\n") ? "\r\n" : "\n";
1539
+ const top = head.trim() ? head.replace(/\s*$/, eol) : "";
1540
+ if (!tail) return top + line + eol;
1541
+ return `${top + line}${eol}${eol}${tail}`;
1382
1542
  }
1383
1543
  function withNotify(text, line) {
1384
1544
  const n = readNotify(text);
@@ -1427,10 +1587,10 @@ var CODEX_EVENTS = [
1427
1587
  ];
1428
1588
  var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
1429
1589
  var eventsFor2 = (lean) => lean ? CODEX_EVENTS.filter((e) => e !== "PreToolUse") : [...CODEX_EVENTS];
1430
- var codexHome = (env) => env.CODEX_HOME || path5.join(homeDir(), ".codex");
1431
- var hooksFile2 = (env) => path5.join(codexHome(env), "hooks.json");
1432
- var configFile = (env) => path5.join(codexHome(env), "config.toml");
1433
- var sessionsDir = (env) => path5.join(codexHome(env), "sessions");
1590
+ var codexHome = (env) => env.CODEX_HOME || path6.join(homeDir(), ".codex");
1591
+ var hooksFile2 = (env) => path6.join(codexHome(env), "hooks.json");
1592
+ var configFile = (env) => path6.join(codexHome(env), "config.toml");
1593
+ var sessionsDir = (env) => path6.join(codexHome(env), "sessions");
1434
1594
  var validTool3 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
1435
1595
  var endReason2 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
1436
1596
  function installNotify(cmd, env) {
@@ -1444,8 +1604,8 @@ function installNotify(cmd, env) {
1444
1604
  const line = `notify = ${JSON.stringify(argv)}`;
1445
1605
  const n = readNotify(text);
1446
1606
  const write = (next) => {
1447
- mkdirSync3(path5.dirname(file), { recursive: true });
1448
- writeFileSync3(file, next);
1607
+ mkdirSync3(path6.dirname(file), { recursive: true });
1608
+ writeFileSync4(file, next);
1449
1609
  };
1450
1610
  if (n && !n.other) {
1451
1611
  if (!n.misplaced && JSON.stringify(n.argv) === JSON.stringify(argv))
@@ -1490,12 +1650,12 @@ function uninstallNotify(env) {
1490
1650
  const n = readNotify(text);
1491
1651
  if (!n) return { file, changed: false };
1492
1652
  if (!n.other) {
1493
- writeFileSync3(file, withoutNotify(text));
1653
+ writeFileSync4(file, withoutNotify(text));
1494
1654
  return { file, changed: true };
1495
1655
  }
1496
1656
  const prev = previousNotify(n.argv);
1497
1657
  if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev))) {
1498
- writeFileSync3(file, replaceNotify(text, n, `notify = ${stripPreviousRaw(n.raw)}`));
1658
+ writeFileSync4(file, replaceNotify(text, n, `notify = ${stripPreviousRaw(n.raw)}`));
1499
1659
  return { file, changed: true };
1500
1660
  }
1501
1661
  return { file, changed: false };
@@ -1511,7 +1671,7 @@ function findRollout(threadId, env = process.env) {
1511
1671
  return;
1512
1672
  }
1513
1673
  for (const n of names.sort().reverse()) {
1514
- const p = path5.join(dir, n);
1674
+ const p = path6.join(dir, n);
1515
1675
  if (n.endsWith(`-${threadId}.jsonl`)) {
1516
1676
  best = p;
1517
1677
  return;
@@ -1696,7 +1856,7 @@ var codex = {
1696
1856
  backfill(sinceMs, env = process.env) {
1697
1857
  const rows2 = [];
1698
1858
  for (const f of recentFiles(sessionsDir(env), 3, sinceMs, (n) => /^rollout-.*\.jsonl$/.test(n))) {
1699
- const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(path5.basename(f));
1859
+ const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(path6.basename(f));
1700
1860
  if (!m?.[1]) continue;
1701
1861
  if (codexSubagent(rolloutMeta(f))) continue;
1702
1862
  const facts = codexFacts(f);
@@ -1705,12 +1865,40 @@ var codex = {
1705
1865
  rows2.push({ session: { id: m[1], ...rest }, started_at, last_at });
1706
1866
  }
1707
1867
  return rows2;
1708
- }
1868
+ },
1869
+ limits: (transcript) => codexLimits(transcript)
1709
1870
  };
1871
+ var epochIso2 = (t) => typeof t === "number" && t > 0 && t < 1e11 ? new Date(t * 1e3).toISOString() : void 0;
1710
1872
  function prune3(o) {
1711
1873
  for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
1712
1874
  return o;
1713
1875
  }
1876
+ function codexLimits(file) {
1877
+ const { tail } = ends(file, 0, 256 * 1024);
1878
+ for (const r of parseLines(tail).reverse()) {
1879
+ const p = r.payload;
1880
+ if (r.type !== "event_msg" || p?.type !== "token_count") continue;
1881
+ if (!p.rate_limits || typeof p.rate_limits !== "object") continue;
1882
+ const rl = p.rate_limits;
1883
+ if (rl.limit_id != null && rl.limit_id !== "codex") continue;
1884
+ const windows = [];
1885
+ for (const id of ["primary", "secondary"]) {
1886
+ const w = rl[id];
1887
+ if (typeof w?.used_percent !== "number" || w.used_percent < 0) continue;
1888
+ windows.push(
1889
+ prune3({
1890
+ id,
1891
+ used_percent: w.used_percent,
1892
+ window_minutes: Number.isInteger(w.window_minutes) && Number(w.window_minutes) > 0 ? Number(w.window_minutes) : void 0,
1893
+ resets_at: epochIso2(w.resets_at)
1894
+ })
1895
+ );
1896
+ }
1897
+ if (!windows.length) return null;
1898
+ return prune3({ windows, plan: typeof rl.plan_type === "string" ? rl.plan_type.slice(0, 64) : void 0 });
1899
+ }
1900
+ return null;
1901
+ }
1714
1902
  function codexFacts(file) {
1715
1903
  const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
1716
1904
  let H = parseLines(head);
@@ -1755,7 +1943,7 @@ function codexFacts(file) {
1755
1943
  // ../core/src/adapters/devin.ts
1756
1944
  import { createHash as createHash2 } from "node:crypto";
1757
1945
  import { existsSync as existsSync6, readlinkSync } from "node:fs";
1758
- import path6 from "node:path";
1946
+ import path7 from "node:path";
1759
1947
 
1760
1948
  // ../core/src/readers/sqlite.ts
1761
1949
  import { execFileSync as execFileSync3 } from "node:child_process";
@@ -1939,11 +2127,11 @@ function foreignEvent(file) {
1939
2127
  return Object.keys(hooks).find((k) => !ok.includes(k));
1940
2128
  }
1941
2129
  var homeOf = (env) => env.HOME || homeDir();
1942
- var appData = (env) => env.APPDATA || path6.join(homeOf(env), "AppData", "Roaming");
1943
- var devinConfigDir = (env = process.env) => process.platform === "win32" ? path6.join(appData(env), "devin") : path6.join(env.XDG_CONFIG_HOME || path6.join(homeOf(env), ".config"), "devin");
1944
- var configFile2 = (env) => path6.join(devinConfigDir(env), "config.json");
1945
- var devinDataDir = (env = process.env) => process.platform === "win32" ? path6.join(appData(env), "devin", "cli") : path6.join(env.XDG_DATA_HOME || path6.join(homeOf(env), ".local", "share"), "devin", "cli");
1946
- var sessionsDb = (env) => path6.join(devinDataDir(env), "sessions.db");
2130
+ var appData = (env) => env.APPDATA || path7.join(homeOf(env), "AppData", "Roaming");
2131
+ 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");
2132
+ var configFile2 = (env) => path7.join(devinConfigDir(env), "config.json");
2133
+ 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");
2134
+ var sessionsDb = (env) => path7.join(devinDataDir(env), "sessions.db");
1947
2135
  var SESSION_ID = /^[A-Za-z0-9._:-]{1,128}$/;
1948
2136
  var validTool4 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
1949
2137
  var endReason3 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
@@ -2115,7 +2303,7 @@ var BACKFILL_SQL = `SELECT s.id, ${SESSION_FIELDS} FROM sessions s
2115
2303
  function sessionFacts(row) {
2116
2304
  const out = {};
2117
2305
  if (typeof row.model === "string" && row.model) out.model = row.model;
2118
- if (typeof row.working_directory === "string" && path6.isAbsolute(row.working_directory))
2306
+ if (typeof row.working_directory === "string" && path7.isAbsolute(row.working_directory))
2119
2307
  out.cwd = row.working_directory;
2120
2308
  const started = Number(row.created_at);
2121
2309
  const last = Number(row.last_activity_at);
@@ -2160,8 +2348,8 @@ function readDevinChain(db, session, fromLine) {
2160
2348
  }
2161
2349
  function devinVersion(env = process.env) {
2162
2350
  try {
2163
- const target = readlinkSync(path6.join(devinDataDir(env), "_versions", "current"));
2164
- const name = path6.basename(target);
2351
+ const target = readlinkSync(path7.join(devinDataDir(env), "_versions", "current"));
2352
+ const name = path7.basename(target);
2165
2353
  return /^[0-9][0-9A-Za-z.+-]{0,63}$/.test(name) ? name : void 0;
2166
2354
  } catch {
2167
2355
  return void 0;
@@ -2176,7 +2364,7 @@ function devinAccount(env = process.env) {
2176
2364
  // ../core/src/adapters/gemini-cli.ts
2177
2365
  import { execFileSync as execFileSync4 } from "node:child_process";
2178
2366
  import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
2179
- import path7 from "node:path";
2367
+ import path8 from "node:path";
2180
2368
  var NAME5 = "gemini-cli";
2181
2369
  var GEMINI_EVENTS = [
2182
2370
  "SessionStart",
@@ -2189,26 +2377,31 @@ var GEMINI_EVENTS = [
2189
2377
  "SessionEnd"
2190
2378
  ];
2191
2379
  var OPTS4 = { matcherFor: (e) => /Tool/.test(e) ? ".*" : void 0, timeout: 5e3 };
2192
- var geminiDir = (env) => env.GEMINI_CLI_HOME || path7.join(homeDir(), ".gemini");
2193
- var settings = (env) => path7.join(geminiDir(env), "settings.json");
2380
+ var geminiDir = (env) => env.GEMINI_CLI_HOME || path8.join(homeDir(), ".gemini");
2381
+ var settings = (env) => path8.join(geminiDir(env), "settings.json");
2194
2382
  var validTool5 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
2195
2383
  var eventsFor4 = (lean) => lean ? GEMINI_EVENTS.filter((e) => e !== "BeforeTool") : [...GEMINI_EVENTS];
2384
+ function geminiVersion(platform = process.platform, env = process.env) {
2385
+ const win = platform === "win32";
2386
+ const bin = win ? findBinary({ names: ["gemini.exe", "gemini.cmd"], dirs: [path8.dirname(process.execPath)], env }) : "gemini";
2387
+ if (!bin) throw new Error("no gemini on PATH");
2388
+ const o = {
2389
+ encoding: "utf8",
2390
+ timeout: 4e3,
2391
+ stdio: ["ignore", "pipe", "ignore"],
2392
+ env
2393
+ };
2394
+ const out = /\.cmd$/i.test(bin) ? execFileSync4(`"${bin}" --version`, { ...o, shell: true }) : execFileSync4(bin, ["--version"], o);
2395
+ return out.trim();
2396
+ }
2196
2397
  function geminiHooksActive(settingsFile) {
2197
- let tools;
2198
- try {
2199
- tools = JSON.parse(readFileSync5(settingsFile, "utf8")).tools;
2200
- } catch {
2201
- }
2398
+ const tools = readJson(settingsFile).tools;
2202
2399
  if (tools?.enableHooks === true) return { active: true };
2203
2400
  if (tools?.enableHooks === false)
2204
2401
  return { active: false, note: "inactive: tools.enableHooks is false in settings.json" };
2205
2402
  let version = "";
2206
2403
  try {
2207
- version = execFileSync4("gemini", ["--version"], {
2208
- encoding: "utf8",
2209
- timeout: 4e3,
2210
- stdio: ["ignore", "pipe", "ignore"]
2211
- }).trim();
2404
+ version = geminiVersion();
2212
2405
  } catch {
2213
2406
  return {
2214
2407
  active: true,
@@ -2226,7 +2419,7 @@ var geminiCli = {
2226
2419
  name: NAME5,
2227
2420
  events: GEMINI_EVENTS,
2228
2421
  // ~/.gemini also belongs to Antigravity; Gemini CLI is here when its settings or its chat logs exist.
2229
- detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(path7.join(geminiDir(env), "tmp")),
2422
+ detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(path8.join(geminiDir(env), "tmp")),
2230
2423
  configFiles: (env = process.env) => [settings(env)],
2231
2424
  install: (cmd, env = process.env, opts = {}) => {
2232
2425
  const r = installClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);