@sneub/pair 0.0.6 → 0.0.7

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 (2) hide show
  1. package/dist/cli.js +297 -170
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -7304,7 +7304,7 @@ function currentDateTime() {
7304
7304
  const tz = Intl.DateTimeFormat("en-US", { timeZoneName: "short" }).formatToParts(now).find((p) => p.type === "timeZoneName")?.value ?? "UTC";
7305
7305
  return `${date} ${time} ${tz}`;
7306
7306
  }
7307
- async function assembleSystemPrompt() {
7307
+ async function assembleSystemPrompt(options) {
7308
7308
  const config = getConfig();
7309
7309
  const workspaceDir = config.pair.workspaceDir;
7310
7310
  const claudePath = join9(workspaceDir, "CLAUDE.md");
@@ -7389,6 +7389,17 @@ ${heartbeatContent}`);
7389
7389
  ## Today's Notes
7390
7390
  ${notesContent}`);
7391
7391
  }
7392
+ if (options?.includeTranscript) {
7393
+ const maxEntries = config.pair.conversationLogMaxEntries;
7394
+ const transcript = await getRecentTranscript(maxEntries);
7395
+ if (transcript) {
7396
+ sections.push(`---
7397
+ ## Recent Conversation
7398
+ ` + `The following is a transcript of your recent conversation with the user, ` + `provided for context continuity. The previous session has expired, but ` + `this log captures what was discussed. Use it to maintain conversational ` + `context \u2014 the user may be responding to something you said earlier.
7399
+
7400
+ ` + transcript);
7401
+ }
7402
+ }
7392
7403
  const tools = await loadTools();
7393
7404
  if (tools.systemPromptBlock) {
7394
7405
  sections.push(tools.systemPromptBlock);
@@ -7423,27 +7434,131 @@ var log2, fileStates;
7423
7434
  var init_memory = __esm(() => {
7424
7435
  init_config();
7425
7436
  init_logger();
7437
+ init_conversation_log();
7426
7438
  init_tools();
7427
7439
  log2 = createLogger("memory");
7428
7440
  fileStates = new Map;
7429
7441
  });
7430
7442
 
7443
+ // src/core/conversation-log.ts
7444
+ import { existsSync as existsSync10 } from "fs";
7445
+ import { readFile as readFile8, writeFile, appendFile as appendFile2, mkdir } from "fs/promises";
7446
+ import { join as join10, dirname as dirname2 } from "path";
7447
+ function getLogPath() {
7448
+ return join10(getWorkspaceDir2(), LOG_FILENAME);
7449
+ }
7450
+ async function readEntries() {
7451
+ const logPath = getLogPath();
7452
+ if (!existsSync10(logPath))
7453
+ return [];
7454
+ try {
7455
+ const content = await readFile8(logPath, "utf-8");
7456
+ const entries = [];
7457
+ for (const line of content.split(`
7458
+ `)) {
7459
+ const trimmed = line.trim();
7460
+ if (!trimmed)
7461
+ continue;
7462
+ try {
7463
+ entries.push(JSON.parse(trimmed));
7464
+ } catch {}
7465
+ }
7466
+ return entries;
7467
+ } catch (err) {
7468
+ log3.warn(`Failed to read conversation log: ${err}`);
7469
+ return [];
7470
+ }
7471
+ }
7472
+ async function rotateIfNeeded2(maxEntries) {
7473
+ const entries = await readEntries();
7474
+ if (entries.length <= maxEntries * 2)
7475
+ return;
7476
+ const trimmed = entries.slice(-maxEntries);
7477
+ const logPath = getLogPath();
7478
+ const content = trimmed.map((e) => JSON.stringify(e)).join(`
7479
+ `) + `
7480
+ `;
7481
+ try {
7482
+ await writeFile(logPath, content, "utf-8");
7483
+ log3.info(`Conversation log rotated: ${entries.length} \u2192 ${trimmed.length} entries`);
7484
+ } catch (err) {
7485
+ log3.warn(`Failed to rotate conversation log: ${err}`);
7486
+ }
7487
+ }
7488
+ function formatTimestamp(iso) {
7489
+ try {
7490
+ const d = new Date(iso);
7491
+ const pad = (n) => String(n).padStart(2, "0");
7492
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
7493
+ } catch {
7494
+ return iso;
7495
+ }
7496
+ }
7497
+ async function logMessage(role, text, options) {
7498
+ const entry = {
7499
+ ts: new Date().toISOString(),
7500
+ role,
7501
+ text,
7502
+ ...options?.name ? { name: options.name } : {},
7503
+ ...options?.channel ? { channel: options.channel } : {}
7504
+ };
7505
+ const logPath = getLogPath();
7506
+ try {
7507
+ const dir = dirname2(logPath);
7508
+ if (!existsSync10(dir)) {
7509
+ await mkdir(dir, { recursive: true });
7510
+ }
7511
+ await appendFile2(logPath, JSON.stringify(entry) + `
7512
+ `, "utf-8");
7513
+ const maxEntries = options?.maxEntries ?? DEFAULT_MAX_ENTRIES;
7514
+ rotateIfNeeded2(maxEntries).catch((err) => {
7515
+ log3.warn(`Rotation check failed: ${err}`);
7516
+ });
7517
+ } catch (err) {
7518
+ log3.warn(`Failed to write conversation log entry: ${err}`);
7519
+ }
7520
+ }
7521
+ async function getRecentTranscript(maxEntries) {
7522
+ const limit = maxEntries ?? DEFAULT_MAX_ENTRIES;
7523
+ const entries = await readEntries();
7524
+ if (entries.length === 0)
7525
+ return null;
7526
+ const recent = entries.slice(-limit);
7527
+ const lines = recent.map((entry) => {
7528
+ const time = formatTimestamp(entry.ts);
7529
+ const speaker = entry.role === "user" ? `${entry.name ?? "User"}${entry.channel ? ` (${entry.channel})` : ""}` : "Assistant";
7530
+ let text = entry.text;
7531
+ if (text.length > MAX_TRANSCRIPT_CHARS_PER_ENTRY) {
7532
+ text = text.slice(0, MAX_TRANSCRIPT_CHARS_PER_ENTRY) + " [...]";
7533
+ }
7534
+ return `[${time}] ${speaker}: ${text}`;
7535
+ });
7536
+ return lines.join(`
7537
+ `);
7538
+ }
7539
+ var log3, LOG_FILENAME = "conversation-log.jsonl", DEFAULT_MAX_ENTRIES = 50, MAX_TRANSCRIPT_CHARS_PER_ENTRY = 800;
7540
+ var init_conversation_log = __esm(() => {
7541
+ init_logger();
7542
+ init_memory();
7543
+ log3 = createLogger("conversation-log");
7544
+ });
7545
+
7431
7546
  // src/core/engine.ts
7432
7547
  import { query } from "@anthropic-ai/claude-agent-sdk";
7433
7548
  import { EventEmitter } from "events";
7434
- var log3, AUTH_ERRORS, Engine;
7549
+ var log4, AUTH_ERRORS, Engine;
7435
7550
  var init_engine = __esm(() => {
7436
7551
  init_config();
7437
7552
  init_logger();
7438
7553
  init_memory();
7439
- log3 = createLogger("engine");
7554
+ log4 = createLogger("engine");
7440
7555
  AUTH_ERRORS = new Set(["authentication_failed", "billing_error"]);
7441
7556
  Engine = class Engine extends EventEmitter {
7442
7557
  async runTurn(prompt, options) {
7443
7558
  const config = getConfig();
7444
7559
  const model = options.model ?? config.pair.model ?? "claude-sonnet-4-6";
7445
7560
  const workspaceDir = getWorkspaceDir2();
7446
- log3.info(`Starting turn (model: ${model}, resume: ${options.sdkSessionId ?? "new"})`);
7561
+ log4.info(`Starting turn (model: ${model}, resume: ${options.sdkSessionId ?? "new"})`);
7447
7562
  const startTime = Date.now();
7448
7563
  const settings = config.pair.apiKeyHelper ? { apiKeyHelper: config.pair.apiKeyHelper } : undefined;
7449
7564
  const stderrLines = [];
@@ -7482,9 +7597,9 @@ var init_engine = __esm(() => {
7482
7597
  const errorMsg = err instanceof Error ? err.message : String(err);
7483
7598
  const stderrTail = stderrLines.slice(-15).join(`
7484
7599
  `);
7485
- log3.error(`Turn failed after ${elapsed}s: ${errorMsg}`);
7600
+ log4.error(`Turn failed after ${elapsed}s: ${errorMsg}`);
7486
7601
  if (stderrTail) {
7487
- log3.error(`claude stderr (last ${Math.min(stderrLines.length, 15)} lines):
7602
+ log4.error(`claude stderr (last ${Math.min(stderrLines.length, 15)} lines):
7488
7603
  ${stderrTail}`);
7489
7604
  }
7490
7605
  const isAuthError = errorMsg.includes("401") || errorMsg.includes("auth") || errorMsg.includes("Unauthorized") || /\b(invalid[_ ]api[_ ]key|not authenticated|credentials)\b/i.test(stderrTail);
@@ -7503,7 +7618,7 @@ ${stderrTail}`);
7503
7618
  }
7504
7619
  handleMessage(message) {
7505
7620
  if (message.type === "system" && message.subtype === "init") {
7506
- log3.info(`SDK session initialized: ${message.session_id}`);
7621
+ log4.info(`SDK session initialized: ${message.session_id}`);
7507
7622
  this.emit("started", { sessionId: message.session_id });
7508
7623
  return;
7509
7624
  }
@@ -7511,7 +7626,7 @@ ${stderrTail}`);
7511
7626
  const authMsg = message;
7512
7627
  if (authMsg.error) {
7513
7628
  const config = getConfig();
7514
- log3.error(`Auth error: ${authMsg.error}`);
7629
+ log4.error(`Auth error: ${authMsg.error}`);
7515
7630
  if (config.pair.authMode === "oauth") {
7516
7631
  this.emit("error", {
7517
7632
  message: "Authentication failed \u2014 your OAuth session may have expired. Run `claude login` to re-authenticate.",
@@ -7519,14 +7634,14 @@ ${stderrTail}`);
7519
7634
  });
7520
7635
  }
7521
7636
  } else if (authMsg.isAuthenticating) {
7522
- log3.info("Authenticating...");
7637
+ log4.info("Authenticating...");
7523
7638
  }
7524
7639
  return;
7525
7640
  }
7526
7641
  if (message.type === "assistant") {
7527
7642
  if (message.error && AUTH_ERRORS.has(message.error)) {
7528
7643
  const config = getConfig();
7529
- log3.error(`Assistant auth error: ${message.error}`);
7644
+ log4.error(`Assistant auth error: ${message.error}`);
7530
7645
  if (config.pair.authMode === "oauth") {
7531
7646
  this.emit("error", {
7532
7647
  message: "Authentication failed \u2014 your OAuth session may have expired. Run `claude login` to re-authenticate.",
@@ -7538,7 +7653,7 @@ ${stderrTail}`);
7538
7653
  for (const block of message.message.content) {
7539
7654
  if (typeof block === "object" && "type" in block && block.type === "tool_use") {
7540
7655
  const toolName = "name" in block ? String(block.name) : "unknown";
7541
- log3.info(`Tool use: ${toolName}`);
7656
+ log4.info(`Tool use: ${toolName}`);
7542
7657
  this.emit("tool_use", { toolName });
7543
7658
  }
7544
7659
  }
@@ -7548,7 +7663,7 @@ ${stderrTail}`);
7548
7663
  if (message.subtype === "success") {
7549
7664
  const cost = message.total_cost_usd ?? 0;
7550
7665
  const elapsed = (message.duration_ms / 1000).toFixed(1);
7551
- log3.info(`Turn complete (${elapsed}s, $${cost.toFixed(4)}, ` + `${message.num_turns} turns)`);
7666
+ log4.info(`Turn complete (${elapsed}s, $${cost.toFixed(4)}, ` + `${message.num_turns} turns)`);
7552
7667
  this.emit("done", {
7553
7668
  response: message.result,
7554
7669
  sessionId: message.session_id,
@@ -7556,7 +7671,7 @@ ${stderrTail}`);
7556
7671
  });
7557
7672
  } else {
7558
7673
  const errorDetails = message.errors?.join("; ") ?? message.subtype;
7559
- log3.error(`Turn error: ${message.subtype} \u2014 ${errorDetails}`);
7674
+ log4.error(`Turn error: ${message.subtype} \u2014 ${errorDetails}`);
7560
7675
  this.emit("error", {
7561
7676
  message: `Turn failed: ${message.subtype}`,
7562
7677
  details: errorDetails
@@ -21451,7 +21566,7 @@ function createPairMcpServer(adapters) {
21451
21566
  const channelId = args.channelId ?? config2.pair.defaultChannelId;
21452
21567
  if (!channel) {
21453
21568
  const msg = "No channel specified and no defaultChannel configured. " + "Set pair.defaultChannel in config.json or pass channel explicitly.";
21454
- log4.warn(`send_message: ${msg}`);
21569
+ log5.warn(`send_message: ${msg}`);
21455
21570
  return {
21456
21571
  content: [{ type: "text", text: `Error: ${msg}` }],
21457
21572
  isError: true
@@ -21459,7 +21574,7 @@ function createPairMcpServer(adapters) {
21459
21574
  }
21460
21575
  if (!channelId) {
21461
21576
  const msg = "No channelId specified and no defaultChannelId configured. " + "Set pair.defaultChannelId in config.json or pass channelId explicitly.";
21462
- log4.warn(`send_message: ${msg}`);
21577
+ log5.warn(`send_message: ${msg}`);
21463
21578
  return {
21464
21579
  content: [{ type: "text", text: `Error: ${msg}` }],
21465
21580
  isError: true
@@ -21468,7 +21583,7 @@ function createPairMcpServer(adapters) {
21468
21583
  const adapter = adapters.get(channel);
21469
21584
  if (!adapter) {
21470
21585
  const msg = `No ${channel} adapter is registered \u2014 is it configured and started?`;
21471
- log4.warn(`send_message: ${msg}`);
21586
+ log5.warn(`send_message: ${msg}`);
21472
21587
  return {
21473
21588
  content: [{ type: "text", text: `Error: ${msg}` }],
21474
21589
  isError: true
@@ -21477,7 +21592,8 @@ function createPairMcpServer(adapters) {
21477
21592
  try {
21478
21593
  await adapter.sendMessage(channelId, args.text);
21479
21594
  const preview = args.text.length > 60 ? `${args.text.slice(0, 60)}...` : args.text;
21480
- log4.info(`send_message \u2192 ${channel}:${channelId} (${preview})`);
21595
+ log5.info(`send_message \u2192 ${channel}:${channelId} (${preview})`);
21596
+ logMessage("assistant", args.text, { channel }).catch(() => {});
21481
21597
  return {
21482
21598
  content: [
21483
21599
  {
@@ -21488,7 +21604,7 @@ function createPairMcpServer(adapters) {
21488
21604
  };
21489
21605
  } catch (err) {
21490
21606
  const errMsg = err instanceof Error ? err.message : String(err);
21491
- log4.error(`send_message failed for ${channel}:${channelId}: ${errMsg}`);
21607
+ log5.error(`send_message failed for ${channel}:${channelId}: ${errMsg}`);
21492
21608
  return {
21493
21609
  content: [
21494
21610
  {
@@ -21503,12 +21619,13 @@ function createPairMcpServer(adapters) {
21503
21619
  ]
21504
21620
  });
21505
21621
  }
21506
- var log4;
21622
+ var log5;
21507
21623
  var init_pair_mcp = __esm(() => {
21508
21624
  init_zod();
21509
21625
  init_config();
21510
21626
  init_logger();
21511
- log4 = createLogger("pair-mcp");
21627
+ init_conversation_log();
21628
+ log5 = createLogger("pair-mcp");
21512
21629
  });
21513
21630
 
21514
21631
  // src/core/session.ts
@@ -21522,7 +21639,7 @@ class SessionManager {
21522
21639
  const existing = this.sessions.get(key);
21523
21640
  if (existing) {
21524
21641
  if (this.isExpired(existing)) {
21525
- log5.info(`Session expired for ${key} (idle ${this.idleMinutes(existing).toFixed(1)} min) \u2014 creating new`);
21642
+ log6.info(`Session expired for ${key} (idle ${this.idleMinutes(existing).toFixed(1)} min) \u2014 creating new`);
21526
21643
  this.expire(key);
21527
21644
  } else {
21528
21645
  existing.lastActiveAt = new Date;
@@ -21542,7 +21659,7 @@ class SessionManager {
21542
21659
  isProcessing: false
21543
21660
  };
21544
21661
  this.sessions.set(key, session);
21545
- log5.info(`New session created: ${key} (id: ${session.id})`);
21662
+ log6.info(`New session created: ${key} (id: ${session.id})`);
21546
21663
  return session;
21547
21664
  }
21548
21665
  get(sessionKey) {
@@ -21551,7 +21668,7 @@ class SessionManager {
21551
21668
  expire(sessionKey) {
21552
21669
  const session = this.sessions.get(sessionKey);
21553
21670
  if (session) {
21554
- log5.info(`Session expired: ${sessionKey} (id: ${session.id}, ` + `sdk: ${session.sdkSessionId ?? "none"}, ` + `created: ${session.createdAt.toISOString()})`);
21671
+ log6.info(`Session expired: ${sessionKey} (id: ${session.id}, ` + `sdk: ${session.sdkSessionId ?? "none"}, ` + `created: ${session.createdAt.toISOString()})`);
21555
21672
  this.sessions.delete(sessionKey);
21556
21673
  }
21557
21674
  }
@@ -21562,13 +21679,13 @@ class SessionManager {
21562
21679
  this.sweepTimer = setInterval(() => {
21563
21680
  this.sweep();
21564
21681
  }, SWEEP_INTERVAL_MS);
21565
- log5.info("Session sweep started (every 5 minutes)");
21682
+ log6.info("Session sweep started (every 5 minutes)");
21566
21683
  }
21567
21684
  stopSweep() {
21568
21685
  if (this.sweepTimer) {
21569
21686
  clearInterval(this.sweepTimer);
21570
21687
  this.sweepTimer = null;
21571
- log5.info("Session sweep stopped");
21688
+ log6.info("Session sweep stopped");
21572
21689
  }
21573
21690
  }
21574
21691
  get size() {
@@ -21585,7 +21702,7 @@ class SessionManager {
21585
21702
  this.expire(key);
21586
21703
  }
21587
21704
  if (expired.length > 0) {
21588
- log5.info(`Sweep expired ${expired.length} session(s): ${expired.join(", ")}`);
21705
+ log6.info(`Sweep expired ${expired.length} session(s): ${expired.join(", ")}`);
21589
21706
  }
21590
21707
  }
21591
21708
  isExpired(session) {
@@ -21597,11 +21714,11 @@ class SessionManager {
21597
21714
  return (Date.now() - session.lastActiveAt.getTime()) / 60000;
21598
21715
  }
21599
21716
  }
21600
- var log5;
21717
+ var log6;
21601
21718
  var init_session = __esm(() => {
21602
21719
  init_config();
21603
21720
  init_logger();
21604
- log5 = createLogger("session");
21721
+ log6 = createLogger("session");
21605
21722
  });
21606
21723
 
21607
21724
  // src/core/router.ts
@@ -21621,10 +21738,10 @@ class Router {
21621
21738
  this.adapters.set(adapter.name, adapter);
21622
21739
  adapter.onMessage((msg) => {
21623
21740
  this.handleMessage(msg).catch((err) => {
21624
- log6.error(`Unhandled error in handleMessage for ${msg.channel}:${msg.userId}: ${err}`);
21741
+ log7.error(`Unhandled error in handleMessage for ${msg.channel}:${msg.userId}: ${err}`);
21625
21742
  });
21626
21743
  });
21627
- log6.info(`Adapter registered: ${adapter.name}`);
21744
+ log7.info(`Adapter registered: ${adapter.name}`);
21628
21745
  }
21629
21746
  isAuthorized(msg) {
21630
21747
  const config2 = getConfig();
@@ -21639,10 +21756,10 @@ class Router {
21639
21756
  }
21640
21757
  async handleMessage(msg) {
21641
21758
  if (!this.isAuthorized(msg)) {
21642
- log6.warn(`Unauthorized message from ${msg.channel}:${msg.userId} (${msg.userName}) \u2014 ignoring`);
21759
+ log7.warn(`Unauthorized message from ${msg.channel}:${msg.userId} (${msg.userName}) \u2014 ignoring`);
21643
21760
  return;
21644
21761
  }
21645
- log6.info(`Message from ${msg.channel}:${msg.userId} (${msg.userName}): ` + `${msg.text.slice(0, 100)}${msg.text.length > 100 ? "..." : ""}`);
21762
+ log7.info(`Message from ${msg.channel}:${msg.userId} (${msg.userName}): ` + `${msg.text.slice(0, 100)}${msg.text.length > 100 ? "..." : ""}`);
21646
21763
  const session = this.sessionManager.getOrCreate(msg.channel, msg.userId, msg.userName);
21647
21764
  session.pendingMessages.push(msg.text);
21648
21765
  const existingTimer = this.debounceTimers.get(session.key);
@@ -21652,14 +21769,14 @@ class Router {
21652
21769
  const timer = setTimeout(() => {
21653
21770
  this.debounceTimers.delete(session.key);
21654
21771
  this.drainIfReady(session, msg).catch((err) => {
21655
- log6.error(`Failed to drain messages for ${session.key}: ${err}`);
21772
+ log7.error(`Failed to drain messages for ${session.key}: ${err}`);
21656
21773
  });
21657
21774
  }, DEBOUNCE_MS);
21658
21775
  this.debounceTimers.set(session.key, timer);
21659
21776
  }
21660
21777
  async drainIfReady(session, msg) {
21661
21778
  if (session.isProcessing) {
21662
- log6.info(`Session ${session.key} is processing \u2014 ${session.pendingMessages.length} message(s) queued`);
21779
+ log7.info(`Session ${session.key} is processing \u2014 ${session.pendingMessages.length} message(s) queued`);
21663
21780
  return;
21664
21781
  }
21665
21782
  if (session.pendingMessages.length === 0)
@@ -21674,13 +21791,18 @@ class Router {
21674
21791
  const startTime = Date.now();
21675
21792
  const adapter = this.adapters.get(msg.channel);
21676
21793
  if (!adapter) {
21677
- log6.error(`No adapter registered for channel: ${msg.channel}`);
21794
+ log7.error(`No adapter registered for channel: ${msg.channel}`);
21678
21795
  return;
21679
21796
  }
21680
21797
  session.isProcessing = true;
21681
21798
  await adapter.sendTypingIndicator(msg.channelId).catch((err) => {
21682
- log6.warn(`Failed to send typing indicator: ${err}`);
21799
+ log7.warn(`Failed to send typing indicator: ${err}`);
21683
21800
  });
21801
+ logMessage("user", prompt, {
21802
+ name: session.userName,
21803
+ channel: msg.channel,
21804
+ maxEntries: getConfig().pair.conversationLogMaxEntries
21805
+ }).catch(() => {});
21684
21806
  let systemPrompt;
21685
21807
  let enginePrompt = prompt;
21686
21808
  if (session.sdkSessionId) {
@@ -21698,7 +21820,7 @@ ${prompt}`;
21698
21820
  }
21699
21821
  systemPrompt = await assembleSystemPrompt();
21700
21822
  } else {
21701
- systemPrompt = await assembleSystemPrompt();
21823
+ systemPrompt = await assembleSystemPrompt({ includeTranscript: true });
21702
21824
  }
21703
21825
  const config2 = getConfig();
21704
21826
  if (!this.pairMcpServer) {
@@ -21715,7 +21837,7 @@ ${prompt}`;
21715
21837
  return;
21716
21838
  acknowledged = true;
21717
21839
  await adapter.sendMessage(msg.channelId, "Working on that...").catch((err) => {
21718
- log6.warn(`Failed to send acknowledge: ${err}`);
21840
+ log7.warn(`Failed to send acknowledge: ${err}`);
21719
21841
  });
21720
21842
  };
21721
21843
  const ackTimer = setTimeout(() => {
@@ -21729,19 +21851,23 @@ ${prompt}`;
21729
21851
  session.sdkSessionId = sessionId;
21730
21852
  session.lastActiveAt = new Date;
21731
21853
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
21732
- log6.info(`Response for ${msg.channel}:${msg.userId} (${elapsed}s, $${cost.toFixed(4)}): ` + `${response.slice(0, 100)}${response.length > 100 ? "..." : ""}`);
21854
+ log7.info(`Response for ${msg.channel}:${msg.userId} (${elapsed}s, $${cost.toFixed(4)}): ` + `${response.slice(0, 100)}${response.length > 100 ? "..." : ""}`);
21855
+ logMessage("assistant", response, {
21856
+ channel: msg.channel,
21857
+ maxEntries: getConfig().pair.conversationLogMaxEntries
21858
+ }).catch(() => {});
21733
21859
  adapter.sendMessage(msg.channelId, response).catch((err) => {
21734
- log6.error(`Failed to send response: ${err}`);
21860
+ log7.error(`Failed to send response: ${err}`);
21735
21861
  });
21736
21862
  };
21737
21863
  const onError = ({ message, details }) => {
21738
21864
  clearTimeout(ackTimer);
21739
21865
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
21740
- log6.error(`Error for ${msg.channel}:${msg.userId} (${elapsed}s): ${message}` + (details ? ` \u2014 ${details}` : ""));
21866
+ log7.error(`Error for ${msg.channel}:${msg.userId} (${elapsed}s): ${message}` + (details ? ` \u2014 ${details}` : ""));
21741
21867
  const friendlyMessage = `Something went wrong: ${message}
21742
21868
  ` + "Please try again, or rephrase your request.";
21743
21869
  adapter.sendMessage(msg.channelId, friendlyMessage).catch((err) => {
21744
- log6.error(`Failed to send error message: ${err}`);
21870
+ log7.error(`Failed to send error message: ${err}`);
21745
21871
  });
21746
21872
  };
21747
21873
  this.engine.on("tool_use", onToolUse);
@@ -21761,40 +21887,41 @@ ${prompt}`;
21761
21887
  this.engine.removeListener("error", onError);
21762
21888
  session.isProcessing = false;
21763
21889
  if (session.pendingMessages.length > 0) {
21764
- log6.info(`Draining ${session.pendingMessages.length} queued message(s) for ${session.key}`);
21890
+ log7.info(`Draining ${session.pendingMessages.length} queued message(s) for ${session.key}`);
21765
21891
  setTimeout(() => {
21766
21892
  this.drainIfReady(session, msg).catch((err) => {
21767
- log6.error(`Failed to drain queued messages for ${session.key}: ${err}`);
21893
+ log7.error(`Failed to drain queued messages for ${session.key}: ${err}`);
21768
21894
  });
21769
21895
  }, 0);
21770
21896
  }
21771
21897
  }
21772
21898
  }
21773
21899
  }
21774
- var log6, DEBOUNCE_MS = 2000;
21900
+ var log7, DEBOUNCE_MS = 2000;
21775
21901
  var init_router = __esm(() => {
21776
21902
  init_config();
21777
21903
  init_logger();
21904
+ init_conversation_log();
21778
21905
  init_engine();
21779
21906
  init_memory();
21780
21907
  init_pair_mcp();
21781
21908
  init_session();
21782
- log6 = createLogger("router");
21909
+ log7 = createLogger("router");
21783
21910
  });
21784
21911
 
21785
21912
  // src/core/cleanup.ts
21786
- import { existsSync as existsSync10, readdirSync as readdirSync3, statSync as statSync7, unlinkSync as unlinkSync2 } from "fs";
21787
- import { join as join10 } from "path";
21913
+ import { existsSync as existsSync11, readdirSync as readdirSync3, statSync as statSync7, unlinkSync as unlinkSync2 } from "fs";
21914
+ import { join as join11 } from "path";
21788
21915
  function cleanupSessions(workspaceDir) {
21789
- const dir = join10(workspaceDir, "sessions");
21916
+ const dir = join11(workspaceDir, "sessions");
21790
21917
  cleanupDir(dir, ".jsonl", SEVEN_DAYS_MS, "session");
21791
21918
  }
21792
21919
  function cleanupUploads(workspaceDir) {
21793
- const dir = join10(workspaceDir, "uploads");
21920
+ const dir = join11(workspaceDir, "uploads");
21794
21921
  cleanupDir(dir, null, TWENTY_FOUR_HOURS_MS, "upload");
21795
21922
  }
21796
21923
  function cleanupNotes(workspaceDir) {
21797
- const dir = join10(workspaceDir, "notes");
21924
+ const dir = join11(workspaceDir, "notes");
21798
21925
  cleanupDir(dir, ".md", SEVEN_DAYS_MS, "note");
21799
21926
  }
21800
21927
  function startCleanupJobs(workspaceDir) {
@@ -21802,31 +21929,31 @@ function startCleanupJobs(workspaceDir) {
21802
21929
  const timer = setInterval(() => {
21803
21930
  runAllCleanups(workspaceDir);
21804
21931
  }, CLEANUP_INTERVAL_MS);
21805
- log7.info("Cleanup jobs started (every hour)");
21932
+ log8.info("Cleanup jobs started (every hour)");
21806
21933
  return () => {
21807
21934
  clearInterval(timer);
21808
- log7.info("Cleanup jobs stopped");
21935
+ log8.info("Cleanup jobs stopped");
21809
21936
  };
21810
21937
  }
21811
21938
  function runAllCleanups(workspaceDir) {
21812
21939
  try {
21813
21940
  cleanupSessions(workspaceDir);
21814
21941
  } catch (err) {
21815
- log7.error(`Session cleanup failed: ${err}`);
21942
+ log8.error(`Session cleanup failed: ${err}`);
21816
21943
  }
21817
21944
  try {
21818
21945
  cleanupUploads(workspaceDir);
21819
21946
  } catch (err) {
21820
- log7.error(`Upload cleanup failed: ${err}`);
21947
+ log8.error(`Upload cleanup failed: ${err}`);
21821
21948
  }
21822
21949
  try {
21823
21950
  cleanupNotes(workspaceDir);
21824
21951
  } catch (err) {
21825
- log7.error(`Note cleanup failed: ${err}`);
21952
+ log8.error(`Note cleanup failed: ${err}`);
21826
21953
  }
21827
21954
  }
21828
21955
  function cleanupDir(dir, extension, maxAgeMs, label) {
21829
- if (!existsSync10(dir))
21956
+ if (!existsSync11(dir))
21830
21957
  return;
21831
21958
  const now = Date.now();
21832
21959
  let deleted = 0;
@@ -21835,7 +21962,7 @@ function cleanupDir(dir, extension, maxAgeMs, label) {
21835
21962
  for (const entry of entries) {
21836
21963
  if (extension && !entry.endsWith(extension))
21837
21964
  continue;
21838
- const filePath = join10(dir, entry);
21965
+ const filePath = join11(dir, entry);
21839
21966
  try {
21840
21967
  const stats = statSync7(filePath);
21841
21968
  if (!stats.isFile())
@@ -21844,31 +21971,31 @@ function cleanupDir(dir, extension, maxAgeMs, label) {
21844
21971
  if (ageMs > maxAgeMs) {
21845
21972
  unlinkSync2(filePath);
21846
21973
  deleted++;
21847
- log7.info(`Deleted old ${label}: ${entry} (age: ${(ageMs / 86400000).toFixed(1)} days)`);
21974
+ log8.info(`Deleted old ${label}: ${entry} (age: ${(ageMs / 86400000).toFixed(1)} days)`);
21848
21975
  }
21849
21976
  } catch (err) {
21850
- log7.warn(`Failed to clean up ${filePath}: ${err}`);
21977
+ log8.warn(`Failed to clean up ${filePath}: ${err}`);
21851
21978
  }
21852
21979
  }
21853
21980
  } catch (err) {
21854
- log7.warn(`Failed to read directory ${dir}: ${err}`);
21981
+ log8.warn(`Failed to read directory ${dir}: ${err}`);
21855
21982
  }
21856
21983
  if (deleted > 0) {
21857
- log7.info(`Cleaned up ${deleted} ${label} file(s) from ${dir}`);
21984
+ log8.info(`Cleaned up ${deleted} ${label} file(s) from ${dir}`);
21858
21985
  }
21859
21986
  }
21860
- var log7, SEVEN_DAYS_MS, TWENTY_FOUR_HOURS_MS, CLEANUP_INTERVAL_MS;
21987
+ var log8, SEVEN_DAYS_MS, TWENTY_FOUR_HOURS_MS, CLEANUP_INTERVAL_MS;
21861
21988
  var init_cleanup = __esm(() => {
21862
21989
  init_logger();
21863
- log7 = createLogger("cleanup");
21990
+ log8 = createLogger("cleanup");
21864
21991
  SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
21865
21992
  TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1000;
21866
21993
  CLEANUP_INTERVAL_MS = 60 * 60 * 1000;
21867
21994
  });
21868
21995
 
21869
21996
  // src/core/heartbeat.ts
21870
- import { existsSync as existsSync11 } from "fs";
21871
- import { join as join11 } from "path";
21997
+ import { existsSync as existsSync12 } from "fs";
21998
+ import { join as join12 } from "path";
21872
21999
 
21873
22000
  class Heartbeat {
21874
22001
  timer = null;
@@ -21887,10 +22014,10 @@ class Heartbeat {
21887
22014
  if (this.timer)
21888
22015
  return;
21889
22016
  const minutes = this.intervalMs / 60000;
21890
- log8.info(`Heartbeat started (every ${minutes} minute${minutes === 1 ? "" : "s"})`);
22017
+ log9.info(`Heartbeat started (every ${minutes} minute${minutes === 1 ? "" : "s"})`);
21891
22018
  this.timer = setInterval(() => {
21892
22019
  this.fire().catch((err) => {
21893
- log8.error(`Heartbeat fire failed unexpectedly: ${err}`);
22020
+ log9.error(`Heartbeat fire failed unexpectedly: ${err}`);
21894
22021
  });
21895
22022
  }, this.intervalMs);
21896
22023
  }
@@ -21898,7 +22025,7 @@ class Heartbeat {
21898
22025
  if (this.timer) {
21899
22026
  clearInterval(this.timer);
21900
22027
  this.timer = null;
21901
- log8.info("Heartbeat stopped");
22028
+ log9.info("Heartbeat stopped");
21902
22029
  }
21903
22030
  }
21904
22031
  get processing() {
@@ -21906,7 +22033,7 @@ class Heartbeat {
21906
22033
  }
21907
22034
  async fire() {
21908
22035
  if (this.isProcessing) {
21909
- log8.info("Heartbeat tick skipped \u2014 previous fire still in progress");
22036
+ log9.info("Heartbeat tick skipped \u2014 previous fire still in progress");
21910
22037
  return;
21911
22038
  }
21912
22039
  this.isProcessing = true;
@@ -21914,8 +22041,8 @@ class Heartbeat {
21914
22041
  try {
21915
22042
  const config2 = getConfig();
21916
22043
  const workspaceDir = getWorkspaceDir2();
21917
- const heartbeatPath = join11(workspaceDir, "HEARTBEAT.md");
21918
- const heartbeatExists = existsSync11(heartbeatPath);
22044
+ const heartbeatPath = join12(workspaceDir, "HEARTBEAT.md");
22045
+ const heartbeatExists = existsSync12(heartbeatPath);
21919
22046
  const systemPrompt = await assembleSystemPrompt();
21920
22047
  const prompt = this.buildPrompt(heartbeatExists);
21921
22048
  const userMcpServers = config2.mcp.servers;
@@ -21928,11 +22055,11 @@ class Heartbeat {
21928
22055
  const onDone = ({ cost }) => {
21929
22056
  completed = true;
21930
22057
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
21931
- log8.info(`Heartbeat fire complete (${elapsed}s, $${cost.toFixed(4)})`);
22058
+ log9.info(`Heartbeat fire complete (${elapsed}s, $${cost.toFixed(4)})`);
21932
22059
  };
21933
22060
  const onError = ({ message, details }) => {
21934
22061
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
21935
- log8.error(`Heartbeat fire error (${elapsed}s): ${message}` + (details ? ` \u2014 ${details}` : ""));
22062
+ log9.error(`Heartbeat fire error (${elapsed}s): ${message}` + (details ? ` \u2014 ${details}` : ""));
21936
22063
  };
21937
22064
  engine.on("done", onDone);
21938
22065
  engine.on("error", onError);
@@ -21947,10 +22074,10 @@ class Heartbeat {
21947
22074
  engine.removeListener("error", onError);
21948
22075
  }
21949
22076
  if (!completed) {
21950
- log8.warn("Heartbeat fire returned without a done event");
22077
+ log9.warn("Heartbeat fire returned without a done event");
21951
22078
  }
21952
22079
  } catch (err) {
21953
- log8.error(`Heartbeat fire threw: ${err instanceof Error ? err.message : String(err)}`);
22080
+ log9.error(`Heartbeat fire threw: ${err instanceof Error ? err.message : String(err)}`);
21954
22081
  } finally {
21955
22082
  this.isProcessing = false;
21956
22083
  }
@@ -21977,14 +22104,14 @@ class Heartbeat {
21977
22104
  `);
21978
22105
  }
21979
22106
  }
21980
- var log8;
22107
+ var log9;
21981
22108
  var init_heartbeat = __esm(() => {
21982
22109
  init_config();
21983
22110
  init_logger();
21984
22111
  init_engine();
21985
22112
  init_memory();
21986
22113
  init_pair_mcp();
21987
- log8 = createLogger("heartbeat");
22114
+ log9 = createLogger("heartbeat");
21988
22115
  });
21989
22116
 
21990
22117
  // src/core/health.ts
@@ -22011,26 +22138,26 @@ function startHealthCheck(port, adapters) {
22011
22138
  return new Response("Not Found", { status: 404 });
22012
22139
  }
22013
22140
  });
22014
- log9.info(`Health check server listening on port ${port}`);
22141
+ log10.info(`Health check server listening on port ${port}`);
22015
22142
  }
22016
22143
  function stopHealthCheck() {
22017
22144
  if (server) {
22018
22145
  server.stop();
22019
22146
  server = null;
22020
- log9.info("Health check server stopped");
22147
+ log10.info("Health check server stopped");
22021
22148
  }
22022
22149
  }
22023
- var log9, server = null, startedAt;
22150
+ var log10, server = null, startedAt;
22024
22151
  var init_health = __esm(() => {
22025
22152
  init_logger();
22026
- log9 = createLogger("health");
22153
+ log10 = createLogger("health");
22027
22154
  startedAt = Date.now();
22028
22155
  });
22029
22156
 
22030
22157
  // src/adapters/telegram.ts
22031
- import { existsSync as existsSync12 } from "fs";
22032
- import { mkdir, readFile as readFile8 } from "fs/promises";
22033
- import { join as join12 } from "path";
22158
+ import { existsSync as existsSync13 } from "fs";
22159
+ import { mkdir as mkdir2, readFile as readFile9 } from "fs/promises";
22160
+ import { join as join13 } from "path";
22034
22161
  function escapeHtml(text) {
22035
22162
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
22036
22163
  }
@@ -22134,13 +22261,13 @@ class TelegramAdapter {
22134
22261
  await ctx.reply("Hi! I'm Pair, your personal AI assistant. Send me a message and I'll help.");
22135
22262
  });
22136
22263
  this.bot.command("tasks", async (ctx) => {
22137
- const tasksPath = join12(this.workspaceDir, "tasks.md");
22264
+ const tasksPath = join13(this.workspaceDir, "tasks.md");
22138
22265
  try {
22139
- if (!existsSync12(tasksPath)) {
22266
+ if (!existsSync13(tasksPath)) {
22140
22267
  await ctx.reply("No tasks yet.");
22141
22268
  return;
22142
22269
  }
22143
- const content = await readFile8(tasksPath, "utf-8");
22270
+ const content = await readFile9(tasksPath, "utf-8");
22144
22271
  if (!content.trim()) {
22145
22272
  await ctx.reply("No tasks yet.");
22146
22273
  return;
@@ -22189,10 +22316,10 @@ class TelegramAdapter {
22189
22316
  const url2 = `https://api.telegram.org/file/bot${this.token}/${file2.file_path}`;
22190
22317
  const response = await fetch(url2);
22191
22318
  const buffer = Buffer.from(await response.arrayBuffer());
22192
- const uploadsDir = join12(this.workspaceDir, "uploads");
22193
- await mkdir(uploadsDir, { recursive: true });
22319
+ const uploadsDir = join13(this.workspaceDir, "uploads");
22320
+ await mkdir2(uploadsDir, { recursive: true });
22194
22321
  const filename = `photo_${Date.now()}.jpg`;
22195
- const uploadPath = join12(uploadsDir, filename);
22322
+ const uploadPath = join13(uploadsDir, filename);
22196
22323
  await Bun.write(uploadPath, buffer);
22197
22324
  this.log.info(`Photo saved: ${uploadPath} (${buffer.length} bytes)`);
22198
22325
  const caption = ctx.message.caption ? `
@@ -22221,10 +22348,10 @@ Please analyze this image using the Read tool.`
22221
22348
  const url2 = `https://api.telegram.org/file/bot${this.token}/${file2.file_path}`;
22222
22349
  const response = await fetch(url2);
22223
22350
  const buffer = Buffer.from(await response.arrayBuffer());
22224
- const uploadsDir = join12(this.workspaceDir, "uploads");
22225
- await mkdir(uploadsDir, { recursive: true });
22351
+ const uploadsDir = join13(this.workspaceDir, "uploads");
22352
+ await mkdir2(uploadsDir, { recursive: true });
22226
22353
  const filename = doc2.file_name ?? `document_${Date.now()}`;
22227
- const uploadPath = join12(uploadsDir, filename);
22354
+ const uploadPath = join13(uploadsDir, filename);
22228
22355
  await Bun.write(uploadPath, buffer);
22229
22356
  this.log.info(`Document saved: ${uploadPath} (${buffer.length} bytes, ${doc2.mime_type ?? "unknown type"})`);
22230
22357
  const caption = ctx.message.caption ? `
@@ -26366,22 +26493,22 @@ __export(exports_start, {
26366
26493
  default: () => start_default
26367
26494
  });
26368
26495
  import {
26369
- existsSync as existsSync13,
26496
+ existsSync as existsSync14,
26370
26497
  mkdirSync as mkdirSync4,
26371
26498
  readdirSync as readdirSync4,
26372
26499
  statSync as statSync8,
26373
26500
  unlinkSync as unlinkSync3,
26374
26501
  writeFileSync
26375
26502
  } from "fs";
26376
- import { readFile as readFile9 } from "fs/promises";
26377
- import { join as join13 } from "path";
26503
+ import { readFile as readFile10 } from "fs/promises";
26504
+ import { join as join14 } from "path";
26378
26505
  function getPidFile() {
26379
- return join13(getPairHome(), "pair.pid");
26506
+ return join14(getPairHome(), "pair.pid");
26380
26507
  }
26381
26508
  function getLogDir2() {
26382
- return join13(getPairHome(), "logs");
26509
+ return join14(getPairHome(), "logs");
26383
26510
  }
26384
- async function validateOAuth(log10) {
26511
+ async function validateOAuth(log11) {
26385
26512
  try {
26386
26513
  const proc = Bun.spawn(["claude", "auth", "status"], {
26387
26514
  stdout: "pipe",
@@ -26390,11 +26517,11 @@ async function validateOAuth(log10) {
26390
26517
  const exitCode = await proc.exited;
26391
26518
  const stdout = await new Response(proc.stdout).text();
26392
26519
  if (exitCode === 0) {
26393
- log10.info(`OAuth: authenticated \u2014 ${stdout.trim().split(`
26520
+ log11.info(`OAuth: authenticated \u2014 ${stdout.trim().split(`
26394
26521
  `)[0]}`);
26395
26522
  return true;
26396
26523
  }
26397
- log10.warn("OAuth: not authenticated \u2014 run `claude login`");
26524
+ log11.warn("OAuth: not authenticated \u2014 run `claude login`");
26398
26525
  return false;
26399
26526
  } catch {
26400
26527
  try {
@@ -26404,11 +26531,11 @@ async function validateOAuth(log10) {
26404
26531
  });
26405
26532
  const exitCode = await proc.exited;
26406
26533
  if (exitCode === 0) {
26407
- log10.info("OAuth: Claude CLI found (could not verify auth status)");
26534
+ log11.info("OAuth: Claude CLI found (could not verify auth status)");
26408
26535
  return true;
26409
26536
  }
26410
26537
  } catch {}
26411
- log10.warn("OAuth: Claude CLI not found \u2014 install and run `claude login`");
26538
+ log11.warn("OAuth: Claude CLI not found \u2014 install and run `claude login`");
26412
26539
  return false;
26413
26540
  }
26414
26541
  }
@@ -26416,7 +26543,7 @@ function spawnDaemon() {
26416
26543
  const pairHome = getPairHome();
26417
26544
  const logDir = getLogDir2();
26418
26545
  const pidFile = getPidFile();
26419
- if (!existsSync13(logDir)) {
26546
+ if (!existsSync14(logDir)) {
26420
26547
  mkdirSync4(logDir, { recursive: true });
26421
26548
  }
26422
26549
  const args = process.argv.slice(1).filter((a) => a !== "-d" && a !== "--daemon");
@@ -26426,19 +26553,19 @@ function spawnDaemon() {
26426
26553
  env: { ...process.env, PAIR_HOME: pairHome }
26427
26554
  });
26428
26555
  const pid = child.pid;
26429
- if (!existsSync13(pairHome)) {
26556
+ if (!existsSync14(pairHome)) {
26430
26557
  mkdirSync4(pairHome, { recursive: true });
26431
26558
  }
26432
26559
  writeFileSync(pidFile, String(pid), "utf-8");
26433
26560
  child.unref();
26434
26561
  console.log(` Pair started in background (PID: ${pid})`);
26435
26562
  console.log(` PID file: ${pidFile}`);
26436
- console.log(` Logs: ${join13(logDir, "pair.log")}
26563
+ console.log(` Logs: ${join14(logDir, "pair.log")}
26437
26564
  `);
26438
26565
  }
26439
26566
  function writePidFile() {
26440
26567
  const pairHome = getPairHome();
26441
- if (!existsSync13(pairHome)) {
26568
+ if (!existsSync14(pairHome)) {
26442
26569
  mkdirSync4(pairHome, { recursive: true });
26443
26570
  }
26444
26571
  writeFileSync(getPidFile(), String(process.pid), "utf-8");
@@ -26446,42 +26573,42 @@ function writePidFile() {
26446
26573
  function removePidFile() {
26447
26574
  try {
26448
26575
  const pidFile = getPidFile();
26449
- if (existsSync13(pidFile)) {
26576
+ if (existsSync14(pidFile)) {
26450
26577
  unlinkSync3(pidFile);
26451
26578
  }
26452
26579
  } catch {}
26453
26580
  }
26454
- async function ensureWorkspaceInitialized(workspaceDir, log10) {
26455
- if (existsSync13(workspaceDir))
26581
+ async function ensureWorkspaceInitialized(workspaceDir, log11) {
26582
+ if (existsSync14(workspaceDir))
26456
26583
  return;
26457
- const templatesDir = join13(getPackageRoot(), "workspace-init");
26458
- if (!existsSync13(templatesDir)) {
26459
- log10.warn(`Workspace dir ${workspaceDir} does not exist and templates not found at ${templatesDir} \u2014 creating empty workspace`);
26584
+ const templatesDir = join14(getPackageRoot(), "workspace-init");
26585
+ if (!existsSync14(templatesDir)) {
26586
+ log11.warn(`Workspace dir ${workspaceDir} does not exist and templates not found at ${templatesDir} \u2014 creating empty workspace`);
26460
26587
  mkdirSync4(workspaceDir, { recursive: true });
26461
26588
  return;
26462
26589
  }
26463
- log10.info(`Initializing workspace at ${workspaceDir} from templates`);
26590
+ log11.info(`Initializing workspace at ${workspaceDir} from templates`);
26464
26591
  mkdirSync4(workspaceDir, { recursive: true });
26465
- mkdirSync4(join13(workspaceDir, "notes"), { recursive: true });
26466
- mkdirSync4(join13(workspaceDir, "uploads"), { recursive: true });
26467
- mkdirSync4(join13(workspaceDir, "sessions"), { recursive: true });
26592
+ mkdirSync4(join14(workspaceDir, "notes"), { recursive: true });
26593
+ mkdirSync4(join14(workspaceDir, "uploads"), { recursive: true });
26594
+ mkdirSync4(join14(workspaceDir, "sessions"), { recursive: true });
26468
26595
  for (const file2 of readdirSync4(templatesDir)) {
26469
- const src = join13(templatesDir, file2);
26596
+ const src = join14(templatesDir, file2);
26470
26597
  try {
26471
26598
  if (statSync8(src).isDirectory())
26472
26599
  continue;
26473
26600
  } catch {
26474
26601
  continue;
26475
26602
  }
26476
- const dest = join13(workspaceDir, file2);
26477
- if (existsSync13(dest))
26603
+ const dest = join14(workspaceDir, file2);
26604
+ if (existsSync14(dest))
26478
26605
  continue;
26479
26606
  try {
26480
- const content = await readFile9(src, "utf-8");
26607
+ const content = await readFile10(src, "utf-8");
26481
26608
  await Bun.write(dest, content);
26482
- log10.info(` Copied ${file2}`);
26609
+ log11.info(` Copied ${file2}`);
26483
26610
  } catch (err) {
26484
- log10.warn(` Failed to copy ${file2}: ${String(err)}`);
26611
+ log11.warn(` Failed to copy ${file2}: ${String(err)}`);
26485
26612
  }
26486
26613
  }
26487
26614
  }
@@ -26525,22 +26652,22 @@ var init_start = __esm(() => {
26525
26652
  level: config2.pair.logLevel,
26526
26653
  stdout: !isDaemonChild
26527
26654
  });
26528
- const log10 = createLogger("main");
26529
- log10.info("Pair is starting up...");
26530
- log10.info(`Home: ${getPairHome()} (${getPairMode()} mode)`);
26531
- log10.info(`Model: ${config2.pair.model}`);
26532
- log10.info(`Auth: ${config2.pair.authMode}${config2.pair.apiKeyHelper ? ` (helper: ${config2.pair.apiKeyHelper})` : ""}`);
26533
- log10.info(`Workspace: ${config2.pair.workspaceDir}`);
26534
- await ensureWorkspaceInitialized(config2.pair.workspaceDir, log10);
26655
+ const log11 = createLogger("main");
26656
+ log11.info("Pair is starting up...");
26657
+ log11.info(`Home: ${getPairHome()} (${getPairMode()} mode)`);
26658
+ log11.info(`Model: ${config2.pair.model}`);
26659
+ log11.info(`Auth: ${config2.pair.authMode}${config2.pair.apiKeyHelper ? ` (helper: ${config2.pair.apiKeyHelper})` : ""}`);
26660
+ log11.info(`Workspace: ${config2.pair.workspaceDir}`);
26661
+ await ensureWorkspaceInitialized(config2.pair.workspaceDir, log11);
26535
26662
  if (config2.pair.authMode === "oauth") {
26536
- const authOk = await validateOAuth(log10);
26663
+ const authOk = await validateOAuth(log11);
26537
26664
  if (!authOk && !isDaemonChild) {
26538
26665
  console.log(`
26539
26666
  Warning: Could not verify OAuth credentials.
26540
26667
  ` + " Run `claude login` to authenticate with your Claude Max/Pro subscription.\n");
26541
26668
  }
26542
26669
  } else if (!process.env.ANTHROPIC_API_KEY) {
26543
- log10.warn("ANTHROPIC_API_KEY is not set \u2014 queries will fail");
26670
+ log11.warn("ANTHROPIC_API_KEY is not set \u2014 queries will fail");
26544
26671
  if (!isDaemonChild) {
26545
26672
  console.log(`
26546
26673
  Warning: ANTHROPIC_API_KEY is not set. Run 'pair setup' to configure.
@@ -26549,7 +26676,7 @@ var init_start = __esm(() => {
26549
26676
  }
26550
26677
  if (isDaemonChild) {
26551
26678
  writePidFile();
26552
- log10.info(`Daemon started (PID: ${process.pid})`);
26679
+ log11.info(`Daemon started (PID: ${process.pid})`);
26553
26680
  }
26554
26681
  const sessionManager = new SessionManager;
26555
26682
  sessionManager.startSweep();
@@ -26558,32 +26685,32 @@ var init_start = __esm(() => {
26558
26685
  const stopCleanup = startCleanupJobs(config2.pair.workspaceDir);
26559
26686
  const telegramToken = config2.telegram.botToken;
26560
26687
  if (telegramToken) {
26561
- log10.info("Telegram adapter enabled \u2014 connecting...");
26688
+ log11.info("Telegram adapter enabled \u2014 connecting...");
26562
26689
  const telegram = new TelegramAdapter(telegramToken, config2.pair.workspaceDir);
26563
26690
  router.registerAdapter(telegram);
26564
26691
  adapters.push(telegram);
26565
26692
  try {
26566
26693
  await telegram.start();
26567
26694
  } catch (err) {
26568
- log10.error(`Failed to start Telegram adapter: ${String(err)}`);
26695
+ log11.error(`Failed to start Telegram adapter: ${String(err)}`);
26569
26696
  }
26570
26697
  } else {
26571
- log10.warn("Telegram adapter disabled \u2014 no bot token configured");
26698
+ log11.warn("Telegram adapter disabled \u2014 no bot token configured");
26572
26699
  }
26573
26700
  const slackBotToken = config2.slack.botToken;
26574
26701
  const slackAppToken = config2.slack.appToken;
26575
26702
  if (slackBotToken && slackAppToken) {
26576
- log10.info("Slack adapter enabled \u2014 connecting...");
26703
+ log11.info("Slack adapter enabled \u2014 connecting...");
26577
26704
  const slack = new SlackAdapter(slackBotToken, slackAppToken);
26578
26705
  router.registerAdapter(slack);
26579
26706
  adapters.push(slack);
26580
26707
  try {
26581
26708
  await slack.start();
26582
26709
  } catch (err) {
26583
- log10.error(`Failed to start Slack adapter: ${String(err)}`);
26710
+ log11.error(`Failed to start Slack adapter: ${String(err)}`);
26584
26711
  }
26585
26712
  } else {
26586
- log10.warn("Slack adapter disabled \u2014 no bot/app tokens configured");
26713
+ log11.warn("Slack adapter disabled \u2014 no bot/app tokens configured");
26587
26714
  }
26588
26715
  if (config2.pair.healthCheckPort) {
26589
26716
  startHealthCheck(config2.pair.healthCheckPort, adapters);
@@ -26592,19 +26719,19 @@ var init_start = __esm(() => {
26592
26719
  const heartbeatInterval = config2.pair.heartbeatIntervalMinutes;
26593
26720
  if (heartbeatInterval && heartbeatInterval > 0) {
26594
26721
  if (adapters.length === 0) {
26595
- log10.warn("Heartbeat is configured but no adapters are running \u2014 " + "the agent will wake up but `send_message` calls will fail.");
26722
+ log11.warn("Heartbeat is configured but no adapters are running \u2014 " + "the agent will wake up but `send_message` calls will fail.");
26596
26723
  }
26597
26724
  if (!config2.pair.defaultChannel || !config2.pair.defaultChannelId) {
26598
- log10.warn("Heartbeat is configured but pair.defaultChannel/defaultChannelId are not set \u2014 " + "the agent must pass channel + channelId explicitly to every send_message call.");
26725
+ log11.warn("Heartbeat is configured but pair.defaultChannel/defaultChannelId are not set \u2014 " + "the agent must pass channel + channelId explicitly to every send_message call.");
26599
26726
  }
26600
26727
  heartbeat = new Heartbeat({
26601
26728
  adapters: router.getAdapters(),
26602
26729
  intervalMinutes: heartbeatInterval
26603
26730
  });
26604
26731
  heartbeat.start();
26605
- log10.info(`Heartbeat enabled: every ${heartbeatInterval} min` + (config2.pair.defaultChannel ? `, default target ${config2.pair.defaultChannel}:${config2.pair.defaultChannelId}` : ""));
26732
+ log11.info(`Heartbeat enabled: every ${heartbeatInterval} min` + (config2.pair.defaultChannel ? `, default target ${config2.pair.defaultChannel}:${config2.pair.defaultChannelId}` : ""));
26606
26733
  } else {
26607
- log10.info("Heartbeat disabled (pair.heartbeatIntervalMinutes not set)");
26734
+ log11.info("Heartbeat disabled (pair.heartbeatIntervalMinutes not set)");
26608
26735
  }
26609
26736
  if (!isDaemonChild) {
26610
26737
  const channels = adapters.map((a) => a.name);
@@ -26614,7 +26741,7 @@ var init_start = __esm(() => {
26614
26741
  console.log(` Channels: ${channels.length > 0 ? channels.join(", ") : "(none)"}`);
26615
26742
  console.log();
26616
26743
  if (channels.length === 0) {
26617
- console.log(` No adapters are enabled. Configure a bot token in ${join13(getPairHome(), ".env")}`);
26744
+ console.log(` No adapters are enabled. Configure a bot token in ${join14(getPairHome(), ".env")}`);
26618
26745
  console.log(` and run 'pair setup' to get started.
26619
26746
  `);
26620
26747
  }
@@ -26626,7 +26753,7 @@ var init_start = __esm(() => {
26626
26753
  if (shuttingDown)
26627
26754
  return;
26628
26755
  shuttingDown = true;
26629
- log10.info("Shutdown signal received \u2014 shutting down...");
26756
+ log11.info("Shutdown signal received \u2014 shutting down...");
26630
26757
  if (!isDaemonChild) {
26631
26758
  console.log(`
26632
26759
  Shutting down...`);
@@ -26639,17 +26766,17 @@ var init_start = __esm(() => {
26639
26766
  sessionManager.stopSweep();
26640
26767
  for (const adapter of adapters) {
26641
26768
  try {
26642
- log10.info(`Stopping ${adapter.name} adapter...`);
26769
+ log11.info(`Stopping ${adapter.name} adapter...`);
26643
26770
  await adapter.stop();
26644
- log10.info(`${adapter.name} adapter stopped`);
26771
+ log11.info(`${adapter.name} adapter stopped`);
26645
26772
  } catch (err) {
26646
- log10.error(`Error stopping ${adapter.name}: ${String(err)}`);
26773
+ log11.error(`Error stopping ${adapter.name}: ${String(err)}`);
26647
26774
  }
26648
26775
  }
26649
26776
  if (isDaemonChild) {
26650
26777
  removePidFile();
26651
26778
  }
26652
- log10.info("Pair stopped");
26779
+ log11.info("Pair stopped");
26653
26780
  if (!isDaemonChild) {
26654
26781
  console.log(` Pair stopped.
26655
26782
  `);
@@ -26671,10 +26798,10 @@ var exports_stop = {};
26671
26798
  __export(exports_stop, {
26672
26799
  default: () => stop_default
26673
26800
  });
26674
- import { existsSync as existsSync14, readFileSync, unlinkSync as unlinkSync4 } from "fs";
26675
- import { join as join14 } from "path";
26801
+ import { existsSync as existsSync15, readFileSync, unlinkSync as unlinkSync4 } from "fs";
26802
+ import { join as join15 } from "path";
26676
26803
  function getPidFile2() {
26677
- return join14(getPairHome(), "pair.pid");
26804
+ return join15(getPairHome(), "pair.pid");
26678
26805
  }
26679
26806
  function isProcessRunning(pid) {
26680
26807
  try {
@@ -26697,7 +26824,7 @@ async function waitForExit(pid, timeoutMs) {
26697
26824
  function removePidFile2() {
26698
26825
  try {
26699
26826
  const pidFile = getPidFile2();
26700
- if (existsSync14(pidFile)) {
26827
+ if (existsSync15(pidFile)) {
26701
26828
  unlinkSync4(pidFile);
26702
26829
  }
26703
26830
  } catch {}
@@ -26713,7 +26840,7 @@ var init_stop = __esm(() => {
26713
26840
  },
26714
26841
  async run() {
26715
26842
  const pidFile = getPidFile2();
26716
- if (!existsSync14(pidFile)) {
26843
+ if (!existsSync15(pidFile)) {
26717
26844
  console.log(` Pair is not running (no PID file found).
26718
26845
  `);
26719
26846
  return;
@@ -26763,8 +26890,8 @@ var exports_status = {};
26763
26890
  __export(exports_status, {
26764
26891
  default: () => status_default
26765
26892
  });
26766
- import { existsSync as existsSync15, readFileSync as readFileSync2, readdirSync as readdirSync5, statSync as statSync9 } from "fs";
26767
- import { join as join15 } from "path";
26893
+ import { existsSync as existsSync16, readFileSync as readFileSync2, readdirSync as readdirSync5, statSync as statSync9 } from "fs";
26894
+ import { join as join16 } from "path";
26768
26895
  function isProcessRunning2(pid) {
26769
26896
  try {
26770
26897
  process.kill(pid, 0);
@@ -26790,12 +26917,12 @@ function formatUptime(seconds) {
26790
26917
  return parts.join(" ");
26791
26918
  }
26792
26919
  function countFiles(dir) {
26793
- if (!existsSync15(dir))
26920
+ if (!existsSync16(dir))
26794
26921
  return 0;
26795
26922
  try {
26796
26923
  return readdirSync5(dir).filter((entry) => {
26797
26924
  try {
26798
- return statSync9(join15(dir, entry)).isFile();
26925
+ return statSync9(join16(dir, entry)).isFile();
26799
26926
  } catch {
26800
26927
  return false;
26801
26928
  }
@@ -26805,7 +26932,7 @@ function countFiles(dir) {
26805
26932
  }
26806
26933
  }
26807
26934
  function countLines(filePath) {
26808
- if (!existsSync15(filePath))
26935
+ if (!existsSync16(filePath))
26809
26936
  return 0;
26810
26937
  try {
26811
26938
  const content = readFileSync2(filePath, "utf-8");
@@ -26816,7 +26943,7 @@ function countLines(filePath) {
26816
26943
  }
26817
26944
  }
26818
26945
  function readFilePreview(filePath, maxLines = 10) {
26819
- if (!existsSync15(filePath))
26946
+ if (!existsSync16(filePath))
26820
26947
  return null;
26821
26948
  try {
26822
26949
  const content = readFileSync2(filePath, "utf-8").trim();
@@ -26848,13 +26975,13 @@ var init_status = __esm(() => {
26848
26975
  `);
26849
26976
  const pairHome = getPairHome();
26850
26977
  const pairMode = getPairMode();
26851
- const pidFile = join15(pairHome, "pair.pid");
26852
- const workspaceDir = join15(pairHome, "workspace");
26978
+ const pidFile = join16(pairHome, "pair.pid");
26979
+ const workspaceDir = join16(pairHome, "workspace");
26853
26980
  console.log(` Home: ${pairHome} (${pairMode} mode)`);
26854
26981
  let pid = null;
26855
26982
  let running = false;
26856
26983
  let uptime = null;
26857
- if (existsSync15(pidFile)) {
26984
+ if (existsSync16(pidFile)) {
26858
26985
  const pidStr = readFileSync2(pidFile, "utf-8").trim();
26859
26986
  pid = Number(pidStr);
26860
26987
  if (pid && !Number.isNaN(pid) && isProcessRunning2(pid)) {
@@ -26877,11 +27004,11 @@ var init_status = __esm(() => {
26877
27004
  }
26878
27005
  console.log();
26879
27006
  console.log(" Workspace");
26880
- const notesDir = join15(workspaceDir, "notes");
26881
- const uploadsDir = join15(workspaceDir, "uploads");
26882
- const sessionsDir = join15(workspaceDir, "sessions");
26883
- const memoryPath = join15(workspaceDir, "MEMORY.md");
26884
- const tasksPath = join15(workspaceDir, "tasks.md");
27007
+ const notesDir = join16(workspaceDir, "notes");
27008
+ const uploadsDir = join16(workspaceDir, "uploads");
27009
+ const sessionsDir = join16(workspaceDir, "sessions");
27010
+ const memoryPath = join16(workspaceDir, "MEMORY.md");
27011
+ const tasksPath = join16(workspaceDir, "tasks.md");
26885
27012
  const noteCount = countFiles(notesDir);
26886
27013
  const uploadCount = countFiles(uploadsDir);
26887
27014
  const sessionCount = countFiles(sessionsDir);
@@ -26927,7 +27054,7 @@ init_dist();
26927
27054
  // package.json
26928
27055
  var package_default = {
26929
27056
  name: "@sneub/pair",
26930
- version: "0.0.6",
27057
+ version: "0.0.7",
26931
27058
  type: "module",
26932
27059
  description: "A personal AI assistant powered by Claude Code \u2014 connects Telegram and Slack to Claude with persistent memory and custom tools",
26933
27060
  bin: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sneub/pair",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "type": "module",
5
5
  "description": "A personal AI assistant powered by Claude Code — connects Telegram and Slack to Claude with persistent memory and custom tools",
6
6
  "bin": {