@agtd/agent 0.1.2 → 0.1.3

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 (110) hide show
  1. package/dist/agent.js +6 -49
  2. package/dist/{chunk-24ORBJSI.js → chunk-RJLNNX7L.js} +489 -32
  3. package/dist/cli.js +186 -120
  4. package/package.json +2 -1
  5. package/dist/__tests__/codexAdapterFindFile.test.d.ts +0 -2
  6. package/dist/__tests__/codexAdapterFindFile.test.d.ts.map +0 -1
  7. package/dist/__tests__/codexAdapterFindFile.test.js +0 -298
  8. package/dist/__tests__/codexAdapterFindFile.test.js.map +0 -1
  9. package/dist/__tests__/enrichers.test.d.ts +0 -2
  10. package/dist/__tests__/enrichers.test.d.ts.map +0 -1
  11. package/dist/__tests__/enrichers.test.js +0 -47
  12. package/dist/__tests__/enrichers.test.js.map +0 -1
  13. package/dist/__tests__/tmux.integration.test.d.ts +0 -2
  14. package/dist/__tests__/tmux.integration.test.d.ts.map +0 -1
  15. package/dist/__tests__/tmux.integration.test.js +0 -112
  16. package/dist/__tests__/tmux.integration.test.js.map +0 -1
  17. package/dist/__tests__/tmux.test.d.ts +0 -2
  18. package/dist/__tests__/tmux.test.d.ts.map +0 -1
  19. package/dist/__tests__/tmux.test.js +0 -26
  20. package/dist/__tests__/tmux.test.js.map +0 -1
  21. package/dist/__tests__/transcriptAdapters.test.d.ts +0 -2
  22. package/dist/__tests__/transcriptAdapters.test.d.ts.map +0 -1
  23. package/dist/__tests__/transcriptAdapters.test.js +0 -133
  24. package/dist/__tests__/transcriptAdapters.test.js.map +0 -1
  25. package/dist/adapters/claude-code.d.ts +0 -3
  26. package/dist/adapters/claude-code.d.ts.map +0 -1
  27. package/dist/adapters/claude-code.js +0 -48
  28. package/dist/adapters/claude-code.js.map +0 -1
  29. package/dist/adapters/codex.d.ts +0 -3
  30. package/dist/adapters/codex.d.ts.map +0 -1
  31. package/dist/adapters/codex.js +0 -33
  32. package/dist/adapters/codex.js.map +0 -1
  33. package/dist/adapters/generic.d.ts +0 -3
  34. package/dist/adapters/generic.d.ts.map +0 -1
  35. package/dist/adapters/generic.js +0 -38
  36. package/dist/adapters/generic.js.map +0 -1
  37. package/dist/adapters/index.d.ts +0 -7
  38. package/dist/adapters/index.d.ts.map +0 -1
  39. package/dist/adapters/index.js +0 -12
  40. package/dist/adapters/index.js.map +0 -1
  41. package/dist/agent.d.ts +0 -3
  42. package/dist/agent.d.ts.map +0 -1
  43. package/dist/agent.js.map +0 -1
  44. package/dist/cli.d.ts +0 -11
  45. package/dist/cli.d.ts.map +0 -1
  46. package/dist/cli.js.map +0 -1
  47. package/dist/config.d.ts +0 -26
  48. package/dist/config.d.ts.map +0 -1
  49. package/dist/config.js +0 -48
  50. package/dist/config.js.map +0 -1
  51. package/dist/enrichers/git.d.ts +0 -6
  52. package/dist/enrichers/git.d.ts.map +0 -1
  53. package/dist/enrichers/git.js +0 -20
  54. package/dist/enrichers/git.js.map +0 -1
  55. package/dist/enrichers/pr.d.ts +0 -2
  56. package/dist/enrichers/pr.d.ts.map +0 -1
  57. package/dist/enrichers/pr.js +0 -10
  58. package/dist/enrichers/pr.js.map +0 -1
  59. package/dist/enrichers/transcript.d.ts +0 -8
  60. package/dist/enrichers/transcript.d.ts.map +0 -1
  61. package/dist/enrichers/transcript.js +0 -33
  62. package/dist/enrichers/transcript.js.map +0 -1
  63. package/dist/heartbeat.d.ts +0 -4
  64. package/dist/heartbeat.d.ts.map +0 -1
  65. package/dist/heartbeat.js +0 -15
  66. package/dist/heartbeat.js.map +0 -1
  67. package/dist/init.d.ts +0 -10
  68. package/dist/init.d.ts.map +0 -1
  69. package/dist/init.js +0 -70
  70. package/dist/init.js.map +0 -1
  71. package/dist/register.d.ts +0 -3
  72. package/dist/register.d.ts.map +0 -1
  73. package/dist/register.js +0 -22
  74. package/dist/register.js.map +0 -1
  75. package/dist/sessionScanner.d.ts +0 -20
  76. package/dist/sessionScanner.d.ts.map +0 -1
  77. package/dist/sessionScanner.js +0 -479
  78. package/dist/sessionScanner.js.map +0 -1
  79. package/dist/spawn.d.ts +0 -8
  80. package/dist/spawn.d.ts.map +0 -1
  81. package/dist/spawn.js +0 -73
  82. package/dist/spawn.js.map +0 -1
  83. package/dist/syncProjects.d.ts +0 -3
  84. package/dist/syncProjects.d.ts.map +0 -1
  85. package/dist/syncProjects.js +0 -91
  86. package/dist/syncProjects.js.map +0 -1
  87. package/dist/terminalBridge.d.ts +0 -4
  88. package/dist/terminalBridge.d.ts.map +0 -1
  89. package/dist/terminalBridge.js +0 -42
  90. package/dist/terminalBridge.js.map +0 -1
  91. package/dist/tmux.d.ts +0 -21
  92. package/dist/tmux.d.ts.map +0 -1
  93. package/dist/tmux.js +0 -89
  94. package/dist/tmux.js.map +0 -1
  95. package/dist/transcriptAdapters/claude-code.d.ts +0 -3
  96. package/dist/transcriptAdapters/claude-code.d.ts.map +0 -1
  97. package/dist/transcriptAdapters/claude-code.js +0 -147
  98. package/dist/transcriptAdapters/claude-code.js.map +0 -1
  99. package/dist/transcriptAdapters/codex.d.ts +0 -3
  100. package/dist/transcriptAdapters/codex.d.ts.map +0 -1
  101. package/dist/transcriptAdapters/codex.js +0 -251
  102. package/dist/transcriptAdapters/codex.js.map +0 -1
  103. package/dist/transcriptAdapters/index.d.ts +0 -17
  104. package/dist/transcriptAdapters/index.d.ts.map +0 -1
  105. package/dist/transcriptAdapters/index.js +0 -9
  106. package/dist/transcriptAdapters/index.js.map +0 -1
  107. package/dist/wsClient.d.ts +0 -6
  108. package/dist/wsClient.d.ts.map +0 -1
  109. package/dist/wsClient.js +0 -156
  110. package/dist/wsClient.js.map +0 -1
@@ -354,7 +354,26 @@ async function sendHeartbeat(config, payload) {
354
354
  }
355
355
 
356
356
  // src/spawn.ts
357
+ import { existsSync as existsSync3 } from "fs";
358
+ import { execSync as execSync3 } from "child_process";
359
+ import { join as join2 } from "path";
360
+ import { homedir as homedir2 } from "os";
357
361
  var TMUX_SESSION_PREFIX = "aidash";
362
+ function findCodexThreadId(cwd, afterTimeSec) {
363
+ const dbPath = join2(homedir2(), ".codex", "state_5.sqlite");
364
+ if (!existsSync3(dbPath)) return null;
365
+ try {
366
+ const safeDb = dbPath.replace(/'/g, "'\\''");
367
+ const safeCwd = cwd.replace(/'/g, "'\\''");
368
+ const output = execSync3(
369
+ `sqlite3 '${safeDb}' "SELECT id FROM threads WHERE cwd='${safeCwd}' AND archived=0 AND created_at >= ${afterTimeSec} ORDER BY created_at ASC LIMIT 1;"`,
370
+ { encoding: "utf-8", timeout: 3e3, stdio: "pipe" }
371
+ ).trim();
372
+ return output || null;
373
+ } catch {
374
+ return null;
375
+ }
376
+ }
358
377
  function delay(ms) {
359
378
  return new Promise((resolve2) => setTimeout(resolve2, ms));
360
379
  }
@@ -363,6 +382,7 @@ async function spawnAgentSession(config, params) {
363
382
  const tmuxSessionName = `${TMUX_SESSION_PREFIX}-${sessionId}`;
364
383
  const adapter = getAdapter(params.agentType);
365
384
  const spawnCmd = adapter.buildSpawnCommand(params.task, params.projectPath);
385
+ const spawnTimeSec = Math.floor(Date.now() / 1e3);
366
386
  createSession(tmuxSessionName, params.projectPath);
367
387
  tmuxExec(
368
388
  `tmux send-keys -t '${sanitizeShellArg(tmuxSessionName)}' '${sanitizeShellArg(spawnCmd)}' Enter`
@@ -373,6 +393,15 @@ async function spawnAgentSession(config, params) {
373
393
  `tmux send-keys -t '${sanitizeShellArg(tmuxSessionName)}' '${sanitizeShellArg(params.task)}' Enter`
374
394
  );
375
395
  }
396
+ let codexThreadId = null;
397
+ if (params.agentType === "codex") {
398
+ for (let i = 0; i < 4; i++) {
399
+ codexThreadId = findCodexThreadId(params.projectPath, spawnTimeSec);
400
+ if (codexThreadId) break;
401
+ await delay(2e3);
402
+ }
403
+ console.log(`[spawn] codexThreadId=${codexThreadId ?? "null"}`);
404
+ }
376
405
  await sendHeartbeat(config, {
377
406
  deviceId: config.deviceId,
378
407
  sessionId,
@@ -383,7 +412,8 @@ async function spawnAgentSession(config, params) {
383
412
  status: "working",
384
413
  task: params.task,
385
414
  tmuxSession: tmuxSessionName,
386
- tmuxWindow: params.projectName
415
+ tmuxWindow: params.projectName,
416
+ codexThreadId
387
417
  });
388
418
  console.log(`Spawned session ${sessionId} in tmux:${tmuxSessionName}`);
389
419
  return sessionId;
@@ -436,6 +466,352 @@ function stopTerminalBridge(sessionId) {
436
466
  }
437
467
  }
438
468
 
469
+ // src/transcriptAdapters/index.ts
470
+ var adapters2 = /* @__PURE__ */ new Map();
471
+ function registerAdapter(agentType, adapter) {
472
+ adapters2.set(agentType, adapter);
473
+ }
474
+ function getAdapter2(agentType) {
475
+ return adapters2.get(agentType);
476
+ }
477
+
478
+ // src/transcriptAdapters/claude-code.ts
479
+ import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
480
+ import { join as join3 } from "path";
481
+ import { homedir as homedir3 } from "os";
482
+ function truncate(s, max) {
483
+ if (!s) return "";
484
+ return s.length > max ? s.slice(0, max) + "..." : s;
485
+ }
486
+ function summarizeToolInput(toolName, input) {
487
+ if (!input) return {};
488
+ switch (toolName) {
489
+ case "Read":
490
+ return { file_path: input.file_path };
491
+ case "Write":
492
+ return { file_path: input.file_path, content: truncate(input.content, 100) };
493
+ case "Edit":
494
+ return { file_path: input.file_path, old_string: truncate(input.old_string, 80), new_string: truncate(input.new_string, 80) };
495
+ case "Bash":
496
+ return { command: truncate(input.command, 200) };
497
+ case "Grep":
498
+ return { pattern: input.pattern, path: input.path };
499
+ case "Glob":
500
+ return { pattern: input.pattern, path: input.path };
501
+ case "Agent":
502
+ return { description: input.description, subagent_type: input.subagent_type };
503
+ default:
504
+ return Object.fromEntries(
505
+ Object.entries(input).slice(0, 3).map(([k, v]) => [k, typeof v === "string" ? truncate(v, 100) : v])
506
+ );
507
+ }
508
+ }
509
+ function parseContent(content) {
510
+ if (!content) return [];
511
+ if (typeof content === "string") return content.trim() ? [{ type: "text", text: content }] : [];
512
+ if (!Array.isArray(content)) return [];
513
+ const result = [];
514
+ for (const block of content) {
515
+ if (block.type === "text" && block.text?.trim()) {
516
+ result.push({ type: "text", text: block.text });
517
+ } else if (block.type === "thinking" && block.thinking?.trim()) {
518
+ result.push({ type: "thinking", text: block.thinking });
519
+ } else if (block.type === "tool_use") {
520
+ result.push({ type: "tool_use", toolName: block.name ?? "", toolInput: summarizeToolInput(block.name, block.input) });
521
+ } else if (block.type === "tool_result") {
522
+ const text = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.filter((c) => c.type === "text").map((c) => c.text).join("\n") : "";
523
+ if (text.trim()) result.push({ type: "tool_result", text: truncate(text, 500) });
524
+ }
525
+ }
526
+ return result;
527
+ }
528
+ function parseRecord(record) {
529
+ if (!record.type || !record.uuid) return null;
530
+ if (record.type === "user" && record.message) {
531
+ const content = parseContent(record.message.content);
532
+ if (!content.some((c) => c.type === "text" || c.type === "thinking")) return null;
533
+ const textOnly = content.filter((c) => c.type === "text");
534
+ const firstText = textOnly[0]?.text || "";
535
+ if (firstText.startsWith("This session is being continued") || firstText.startsWith("<system-reminder>") || firstText.startsWith("Conversation compacted") || firstText.startsWith("Base directory for this skill:") || firstText.startsWith("# ")) return null;
536
+ return { id: record.uuid, role: "user", timestamp: record.timestamp || "", content: textOnly };
537
+ }
538
+ if (record.type === "assistant" && record.message) {
539
+ const content = parseContent(record.message.content);
540
+ if (content.length === 0) return null;
541
+ return {
542
+ id: record.uuid,
543
+ role: "assistant",
544
+ timestamp: record.timestamp || "",
545
+ content,
546
+ model: record.message.model,
547
+ usage: record.message.usage ? { input_tokens: record.message.usage.input_tokens || 0, output_tokens: record.message.usage.output_tokens || 0 } : void 0
548
+ };
549
+ }
550
+ if (record.type === "system" && record.content && record.subtype !== "local_command") {
551
+ const text = typeof record.content === "string" ? record.content.replace(/<[^>]+>/g, "").trim() : "";
552
+ if (!text || text.length < 5) return null;
553
+ return { id: record.uuid, role: "system", timestamp: record.timestamp || "", content: [{ type: "text", text }] };
554
+ }
555
+ return null;
556
+ }
557
+ var claudeCodeAdapter2 = {
558
+ findFile(session, homeDir = homedir3()) {
559
+ const projectDirName = session.cwd.replace(/\/+$/, "").replace(/\//g, "-");
560
+ const projPath = join3(homeDir, ".claude", "projects", projectDirName);
561
+ if (!existsSync4(projPath)) return null;
562
+ try {
563
+ const files = readdirSync2(projPath).filter((f) => f.endsWith(".jsonl"));
564
+ let latest = null;
565
+ for (const file of files) {
566
+ const filePath = join3(projPath, file);
567
+ try {
568
+ const { mtimeMs } = statSync2(filePath);
569
+ if (!latest || mtimeMs > latest.mtime) latest = { path: filePath, mtime: mtimeMs };
570
+ } catch {
571
+ continue;
572
+ }
573
+ }
574
+ return latest?.path ?? null;
575
+ } catch {
576
+ return null;
577
+ }
578
+ },
579
+ parse(filePath, cursor, limit) {
580
+ try {
581
+ const content = readFileSync2(filePath, "utf-8");
582
+ const lines = content.split("\n").filter((l) => l.trim());
583
+ const messages = [];
584
+ for (const line of lines) {
585
+ try {
586
+ const record = JSON.parse(line);
587
+ const msg = parseRecord(record);
588
+ if (msg) messages.push(msg);
589
+ } catch {
590
+ }
591
+ }
592
+ const total = messages.length;
593
+ const page = messages.slice(cursor, cursor + limit);
594
+ const nextCursor = cursor + page.length;
595
+ return { messages: page, total, hasMore: nextCursor < total, nextCursor };
596
+ } catch {
597
+ return { messages: [], total: 0, hasMore: false, nextCursor: 0 };
598
+ }
599
+ }
600
+ };
601
+ registerAdapter("claude-code", claudeCodeAdapter2);
602
+
603
+ // src/transcriptAdapters/codex.ts
604
+ import { existsSync as existsSync5, readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as statSync3 } from "fs";
605
+ import { join as join4 } from "path";
606
+ import { homedir as homedir4 } from "os";
607
+ import { execSync as execSync4 } from "child_process";
608
+ function truncate2(s, max) {
609
+ if (!s) return "";
610
+ return s.length > max ? s.slice(0, max) + "..." : s;
611
+ }
612
+ function extractCodexText(content) {
613
+ if (!content) return "";
614
+ if (typeof content === "string") return content;
615
+ if (!Array.isArray(content)) return "";
616
+ return content.map((c) => c.text || "").filter((t) => t.trim()).join("\n");
617
+ }
618
+ function summarizeCodexToolInput(name, args) {
619
+ if (name === "exec_command" || name === "shell")
620
+ return { cmd: truncate2(args.cmd, 200) };
621
+ if (name === "read_file" || name === "write_file")
622
+ return {
623
+ path: args.path || args.file_path,
624
+ content: args.content ? truncate2(args.content, 100) : void 0
625
+ };
626
+ return Object.fromEntries(
627
+ Object.entries(args).slice(0, 3).map(([k, v]) => [k, typeof v === "string" ? truncate2(v, 150) : v])
628
+ );
629
+ }
630
+ function parseRecord2(record, counter) {
631
+ if (!record.type || !record.payload) return null;
632
+ const p = record.payload;
633
+ const ts = record.timestamp || "";
634
+ if (record.type !== "response_item") return null;
635
+ if (p.type === "message" && p.role === "user") {
636
+ const texts = Array.isArray(p.content) ? p.content.map((c) => typeof c === "string" ? c : c.text || "").filter(
637
+ (t) => t.trim() && !t.startsWith("#") && !t.startsWith("<permissions")
638
+ ) : (() => {
639
+ const t = extractCodexText(p.content);
640
+ return t && !t.startsWith("#") && !t.startsWith("<permissions") ? [t] : [];
641
+ })();
642
+ if (texts.length === 0) return null;
643
+ if (texts.length === 1)
644
+ return {
645
+ id: `codex-${++counter.value}`,
646
+ role: "user",
647
+ timestamp: ts,
648
+ content: [{ type: "text", text: texts[0] }]
649
+ };
650
+ return texts.map((text) => ({
651
+ id: `codex-${++counter.value}`,
652
+ role: "user",
653
+ timestamp: ts,
654
+ content: [{ type: "text", text }]
655
+ }));
656
+ }
657
+ if (p.type === "message" && p.role === "assistant") {
658
+ const text = extractCodexText(p.content);
659
+ if (!text) return null;
660
+ return {
661
+ id: `codex-${++counter.value}`,
662
+ role: "assistant",
663
+ timestamp: ts,
664
+ content: [{ type: "text", text }],
665
+ model: "codex"
666
+ };
667
+ }
668
+ if (p.type === "reasoning") {
669
+ const summaryText = Array.isArray(p.summary) && p.summary.length > 0 ? p.summary.map((s) => s.text || "").join("\n") : null;
670
+ if (!summaryText) return null;
671
+ return {
672
+ id: `codex-${++counter.value}`,
673
+ role: "assistant",
674
+ timestamp: ts,
675
+ content: [{ type: "thinking", text: summaryText }]
676
+ };
677
+ }
678
+ if (p.type === "function_call") {
679
+ let args = {};
680
+ try {
681
+ args = typeof p.arguments === "string" ? JSON.parse(p.arguments) : p.arguments || {};
682
+ } catch {
683
+ args = { raw: truncate2(p.arguments, 200) };
684
+ }
685
+ return {
686
+ id: `codex-${++counter.value}`,
687
+ role: "assistant",
688
+ timestamp: ts,
689
+ content: [
690
+ {
691
+ type: "tool_use",
692
+ toolName: p.name || "unknown",
693
+ toolInput: summarizeCodexToolInput(p.name, args)
694
+ }
695
+ ]
696
+ };
697
+ }
698
+ if (p.type === "function_call_output") {
699
+ const output = typeof p.output === "string" ? p.output : "";
700
+ if (!output.trim()) return null;
701
+ return {
702
+ id: `codex-${++counter.value}`,
703
+ role: "assistant",
704
+ timestamp: ts,
705
+ content: [{ type: "tool_result", text: truncate2(output, 500) }]
706
+ };
707
+ }
708
+ return null;
709
+ }
710
+ function safeReaddir(dir) {
711
+ try {
712
+ return readdirSync3(dir);
713
+ } catch {
714
+ return [];
715
+ }
716
+ }
717
+ function findLatestJsonl(dir) {
718
+ let latest = null;
719
+ try {
720
+ for (const year of readdirSync3(dir)) {
721
+ for (const month of safeReaddir(join4(dir, year))) {
722
+ for (const day of safeReaddir(join4(dir, year, month))) {
723
+ for (const file of safeReaddir(join4(dir, year, month, day))) {
724
+ if (!file.endsWith(".jsonl")) continue;
725
+ const fp = join4(dir, year, month, day, file);
726
+ try {
727
+ const { mtimeMs } = statSync3(fp);
728
+ if (!latest || mtimeMs > latest.mtime)
729
+ latest = { path: fp, mtime: mtimeMs };
730
+ } catch {
731
+ continue;
732
+ }
733
+ }
734
+ }
735
+ }
736
+ }
737
+ } catch {
738
+ }
739
+ return latest?.path ?? null;
740
+ }
741
+ var codexAdapter2 = {
742
+ findFile(session, homeDir = homedir4()) {
743
+ const s = session;
744
+ const dbPath = join4(homeDir, ".codex", "state_5.sqlite");
745
+ if (existsSync5(dbPath)) {
746
+ try {
747
+ const safeDb = dbPath.replace(/'/g, "'\\''");
748
+ if (s.codexThreadId) {
749
+ const safeId = s.codexThreadId.replace(/'/g, "'\\''");
750
+ const idOutput = execSync4(
751
+ `sqlite3 '${safeDb}' "SELECT rollout_path FROM threads WHERE id='${safeId}' LIMIT 1;"`,
752
+ { encoding: "utf-8", timeout: 3e3, stdio: "pipe" }
753
+ ).trim();
754
+ if (idOutput && existsSync5(idOutput)) return idOutput;
755
+ }
756
+ if (session.cwd && s.tmuxSession) {
757
+ try {
758
+ const safeTmux = s.tmuxSession.replace(/'/g, "'\\''");
759
+ const tmuxCreated = execSync4(
760
+ `tmux display-message -t '${safeTmux}' -p '#{session_created}'`,
761
+ { encoding: "utf-8", timeout: 2e3, stdio: "pipe" }
762
+ ).trim();
763
+ if (tmuxCreated && /^\d+$/.test(tmuxCreated)) {
764
+ const safeCwd = session.cwd.replace(/"/g, '\\"');
765
+ const tsOutput = execSync4(
766
+ `sqlite3 '${safeDb}' "SELECT rollout_path FROM threads WHERE cwd='${safeCwd}' AND archived=0 AND created_at >= ${tmuxCreated} ORDER BY created_at ASC LIMIT 1;"`,
767
+ { encoding: "utf-8", timeout: 3e3, stdio: "pipe" }
768
+ ).trim();
769
+ if (tsOutput && existsSync5(tsOutput)) return tsOutput;
770
+ }
771
+ } catch {
772
+ }
773
+ }
774
+ if (session.cwd) {
775
+ const safeCwd = session.cwd.replace(/"/g, '\\"');
776
+ const cwdOutput = execSync4(
777
+ `sqlite3 '${safeDb}' "SELECT rollout_path FROM threads WHERE cwd='${safeCwd}' AND archived=0 ORDER BY updated_at DESC LIMIT 1;"`,
778
+ { encoding: "utf-8", timeout: 3e3, stdio: "pipe" }
779
+ ).trim();
780
+ if (cwdOutput && existsSync5(cwdOutput)) return cwdOutput;
781
+ }
782
+ } catch {
783
+ }
784
+ }
785
+ const sessionsDir = join4(homeDir, ".codex", "sessions");
786
+ if (!existsSync5(sessionsDir)) return null;
787
+ return findLatestJsonl(sessionsDir);
788
+ },
789
+ parse(filePath, cursor, limit) {
790
+ try {
791
+ const content = readFileSync3(filePath, "utf-8");
792
+ const lines = content.split("\n").filter((l) => l.trim());
793
+ const counter = { value: 0 };
794
+ const messages = [];
795
+ for (const line of lines) {
796
+ try {
797
+ const record = JSON.parse(line);
798
+ const msg = parseRecord2(record, counter);
799
+ if (Array.isArray(msg)) messages.push(...msg);
800
+ else if (msg) messages.push(msg);
801
+ } catch {
802
+ }
803
+ }
804
+ const total = messages.length;
805
+ const page = messages.slice(cursor, cursor + limit);
806
+ const nextCursor = cursor + page.length;
807
+ return { messages: page, total, hasMore: nextCursor < total, nextCursor };
808
+ } catch {
809
+ return { messages: [], total: 0, hasMore: false, nextCursor: 0 };
810
+ }
811
+ }
812
+ };
813
+ registerAdapter("codex", codexAdapter2);
814
+
439
815
  // src/wsClient.ts
440
816
  var reconnectAttempts = 0;
441
817
  function connectToBackend(config) {
@@ -498,6 +874,87 @@ function connectToBackend(config) {
498
874
  }
499
875
  break;
500
876
  }
877
+ case "user-message": {
878
+ const tmuxAvail = isTmuxAvailable();
879
+ const sessionExists = tmuxSessionExists(msg.payload.tmuxSession);
880
+ let paneInfo = "n/a";
881
+ try {
882
+ paneInfo = tmuxExec(
883
+ `tmux list-panes -t '${sanitizeShellArg(msg.payload.tmuxSession)}' -F '#I:#P #{pane_current_command} #{pane_active}'`
884
+ ).trim();
885
+ } catch {
886
+ }
887
+ console.log(
888
+ `[user-message] session=${msg.payload.tmuxSession} exists=${sessionExists} available=${tmuxAvail} panes="${paneInfo}" msg="${msg.payload.message?.slice(0, 80)}"`
889
+ );
890
+ if (tmuxAvail && sessionExists) {
891
+ try {
892
+ let paneBefore = "";
893
+ try {
894
+ paneBefore = tmuxExec(
895
+ `tmux capture-pane -t '${sanitizeShellArg(msg.payload.tmuxSession)}' -p -S -10`
896
+ ).split("\n").slice(-5).join(" | ");
897
+ } catch {
898
+ }
899
+ console.log(`[user-message] pane-before: ${paneBefore}`);
900
+ sendKeys(msg.payload.tmuxSession, msg.payload.message);
901
+ await new Promise((r) => setTimeout(r, 80));
902
+ sendKeys(msg.payload.tmuxSession, "\r");
903
+ console.log(`[user-message] sent OK`);
904
+ await new Promise((r) => setTimeout(r, 500));
905
+ let paneAfter = "";
906
+ try {
907
+ paneAfter = tmuxExec(
908
+ `tmux capture-pane -t '${sanitizeShellArg(msg.payload.tmuxSession)}' -p -S -10`
909
+ ).split("\n").slice(-5).join(" | ");
910
+ } catch {
911
+ }
912
+ console.log(`[user-message] pane-after: ${paneAfter}`);
913
+ } catch (e) {
914
+ console.error(`[user-message] sendKeys failed:`, e);
915
+ }
916
+ }
917
+ break;
918
+ }
919
+ case "get-transcript": {
920
+ const {
921
+ requestId,
922
+ sessionId,
923
+ agentType,
924
+ cwd,
925
+ cursor = 0,
926
+ limit = 50,
927
+ codexThreadId = null,
928
+ tmuxSession = null
929
+ } = msg.payload;
930
+ const adapter = getAdapter2(agentType);
931
+ let result = {
932
+ messages: [],
933
+ total: 0,
934
+ hasMore: false,
935
+ nextCursor: 0
936
+ };
937
+ if (adapter) {
938
+ const fakeSession = {
939
+ id: sessionId,
940
+ cwd,
941
+ agentType,
942
+ codexThreadId,
943
+ tmuxSession
944
+ };
945
+ const filePath = adapter.findFile(fakeSession);
946
+ if (filePath) {
947
+ result = adapter.parse(filePath, cursor, limit);
948
+ }
949
+ }
950
+ ws.send(
951
+ JSON.stringify({
952
+ type: "transcript-response",
953
+ payload: { requestId, ...result }
954
+ })
955
+ );
956
+ break;
957
+ }
501
958
  }
502
959
  } catch (e) {
503
960
  console.error("Error handling WS message:", e);
@@ -514,29 +971,29 @@ function connectToBackend(config) {
514
971
  }
515
972
 
516
973
  // src/sessionScanner.ts
517
- import { execSync as execSync4 } from "child_process";
974
+ import { execSync as execSync6 } from "child_process";
518
975
  import {
519
- statSync as statSync2,
520
- readdirSync as readdirSync2,
521
- existsSync as existsSync4,
976
+ statSync as statSync4,
977
+ readdirSync as readdirSync4,
978
+ existsSync as existsSync7,
522
979
  openSync,
523
980
  readSync,
524
981
  closeSync
525
982
  } from "fs";
526
- import { join as join2, basename as basename2 } from "path";
527
- import { homedir as homedir2 } from "os";
983
+ import { join as join5, basename as basename2 } from "path";
984
+ import { homedir as homedir5 } from "os";
528
985
 
529
986
  // src/enrichers/git.ts
530
- import { execSync as execSync3 } from "child_process";
531
- import { existsSync as existsSync3 } from "fs";
987
+ import { execSync as execSync5 } from "child_process";
988
+ import { existsSync as existsSync6 } from "fs";
532
989
  function getGitInfo(cwd) {
533
- if (!existsSync3(`${cwd}/.git`)) return null;
990
+ if (!existsSync6(`${cwd}/.git`)) return null;
534
991
  try {
535
- const branch = execSync3("git rev-parse --abbrev-ref HEAD", { cwd, encoding: "utf-8", timeout: 3e3 }).trim();
536
- const repoName = execSync3("git rev-parse --show-toplevel", { cwd, encoding: "utf-8", timeout: 3e3 }).trim().split("/").pop() || "";
992
+ const branch = execSync5("git rev-parse --abbrev-ref HEAD", { cwd, encoding: "utf-8", timeout: 3e3 }).trim();
993
+ const repoName = execSync5("git rev-parse --show-toplevel", { cwd, encoding: "utf-8", timeout: 3e3 }).trim().split("/").pop() || "";
537
994
  let remoteUrl = "";
538
995
  try {
539
- remoteUrl = execSync3("git remote get-url origin", { cwd, encoding: "utf-8", timeout: 3e3 }).trim();
996
+ remoteUrl = execSync5("git remote get-url origin", { cwd, encoding: "utf-8", timeout: 3e3 }).trim();
540
997
  } catch {
541
998
  }
542
999
  return { repoName, branch, remoteUrl };
@@ -549,7 +1006,7 @@ function getGitInfo(cwd) {
549
1006
  function buildTmuxPidMap() {
550
1007
  const map = /* @__PURE__ */ new Map();
551
1008
  try {
552
- const output = execSync4(
1009
+ const output = execSync6(
553
1010
  "tmux list-panes -a -F '#{pane_pid} #{session_name}' 2>/dev/null",
554
1011
  { encoding: "utf-8", timeout: 3e3, stdio: "pipe" }
555
1012
  ).trim();
@@ -576,7 +1033,7 @@ function findTmuxSessionForPid(pid, tmuxPidMap) {
576
1033
  if (session) return session;
577
1034
  try {
578
1035
  const ppid = parseInt(
579
- execSync4(`ps -p ${currentPid} -o ppid=`, {
1036
+ execSync6(`ps -p ${currentPid} -o ppid=`, {
580
1037
  encoding: "utf-8",
581
1038
  timeout: 2e3,
582
1039
  stdio: "pipe"
@@ -596,7 +1053,7 @@ function pathToProjectDir(p) {
596
1053
  }
597
1054
  function getProcessCwd(pid) {
598
1055
  try {
599
- const output = execSync4(
1056
+ const output = execSync6(
600
1057
  `lsof -a -p ${pid} -d cwd -Fn 2>/dev/null | grep '^n'`,
601
1058
  { encoding: "utf-8", timeout: 3e3, stdio: "pipe" }
602
1059
  ).trim();
@@ -609,7 +1066,7 @@ function getProcessCwd(pid) {
609
1066
  function findAgentProcesses() {
610
1067
  const processes = [];
611
1068
  try {
612
- const psOutput = execSync4("ps aux", {
1069
+ const psOutput = execSync6("ps aux", {
613
1070
  encoding: "utf-8",
614
1071
  timeout: 5e3,
615
1072
  stdio: "pipe"
@@ -655,12 +1112,12 @@ function findAgentProcesses() {
655
1112
  }
656
1113
  function findLatestSessionFile(projPath) {
657
1114
  try {
658
- const files = readdirSync2(projPath).filter((f) => f.endsWith(".jsonl"));
1115
+ const files = readdirSync4(projPath).filter((f) => f.endsWith(".jsonl"));
659
1116
  let latest = null;
660
1117
  for (const file of files) {
661
- const filePath = join2(projPath, file);
1118
+ const filePath = join5(projPath, file);
662
1119
  try {
663
- const mtime = statSync2(filePath).mtimeMs;
1120
+ const mtime = statSync4(filePath).mtimeMs;
664
1121
  if (!latest || mtime > latest.mtimeMs) {
665
1122
  latest = { sessionId: basename2(file, ".jsonl"), mtimeMs: mtime };
666
1123
  }
@@ -675,8 +1132,8 @@ function findLatestSessionFile(projPath) {
675
1132
  }
676
1133
  function isAwaitingPermissionFromJsonl(projPath, sessionId) {
677
1134
  try {
678
- const filePath = join2(projPath, `${sessionId}.jsonl`);
679
- const size = statSync2(filePath).size;
1135
+ const filePath = join5(projPath, `${sessionId}.jsonl`);
1136
+ const size = statSync4(filePath).size;
680
1137
  if (size === 0) return false;
681
1138
  const TAIL_BYTES = 4096;
682
1139
  const start = Math.max(0, size - TAIL_BYTES);
@@ -715,7 +1172,7 @@ var AWAITING_INPUT_PATTERNS = [
715
1172
  function isAwaitingInputFromTmux(tmuxSession) {
716
1173
  if (!tmuxSession) return false;
717
1174
  try {
718
- const output = execSync4(
1175
+ const output = execSync6(
719
1176
  `tmux capture-pane -t '${tmuxSession.replace(/'/g, "'\\''")}' -p -S -20`,
720
1177
  {
721
1178
  encoding: "utf-8",
@@ -730,8 +1187,8 @@ function isAwaitingInputFromTmux(tmuxSession) {
730
1187
  }
731
1188
  function discoverClaudeSessions(processes, tmuxPidMap) {
732
1189
  const sessions = [];
733
- const claudeProjectsDir = join2(homedir2(), ".claude", "projects");
734
- if (!existsSync4(claudeProjectsDir)) return sessions;
1190
+ const claudeProjectsDir = join5(homedir5(), ".claude", "projects");
1191
+ if (!existsSync7(claudeProjectsDir)) return sessions;
735
1192
  const now = Date.now();
736
1193
  const WORKING_THRESHOLD_MS = 5 * 1e3;
737
1194
  const seen = /* @__PURE__ */ new Map();
@@ -741,14 +1198,14 @@ function discoverClaudeSessions(processes, tmuxPidMap) {
741
1198
  const cwd = proc.cwd;
742
1199
  if (!cwd) continue;
743
1200
  const projDirName = pathToProjectDir(cwd);
744
- const projPath = join2(claudeProjectsDir, projDirName);
745
- if (!existsSync4(projPath)) continue;
1201
+ const projPath = join5(claudeProjectsDir, projDirName);
1202
+ if (!existsSync7(projPath)) continue;
746
1203
  let sessionId = proc.sessionId;
747
1204
  let mtimeMs = null;
748
1205
  if (sessionId) {
749
- const filePath = join2(projPath, `${sessionId}.jsonl`);
1206
+ const filePath = join5(projPath, `${sessionId}.jsonl`);
750
1207
  try {
751
- mtimeMs = statSync2(filePath).mtimeMs;
1208
+ mtimeMs = statSync4(filePath).mtimeMs;
752
1209
  } catch {
753
1210
  sessionId = null;
754
1211
  }
@@ -807,10 +1264,10 @@ function discoverClaudeSessions(processes, tmuxPidMap) {
807
1264
  }
808
1265
  function getCodexSessionsFromDb() {
809
1266
  const map = /* @__PURE__ */ new Map();
810
- const dbPath = join2(homedir2(), ".codex", "state_5.sqlite");
811
- if (!existsSync4(dbPath)) return map;
1267
+ const dbPath = join5(homedir5(), ".codex", "state_5.sqlite");
1268
+ if (!existsSync7(dbPath)) return map;
812
1269
  try {
813
- const output = execSync4(
1270
+ const output = execSync6(
814
1271
  `sqlite3 "${dbPath}" "SELECT id, cwd, title, model_provider, updated_at FROM threads ORDER BY updated_at DESC LIMIT 50;"`,
815
1272
  { encoding: "utf-8", timeout: 3e3, stdio: "pipe" }
816
1273
  ).trim();