@rubytech/create-maxy 1.0.474 → 1.0.476

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.
@@ -5211,6 +5211,10 @@ var ADMIN_CORE_TOOLS = [
5211
5211
  "mcp__whatsapp__whatsapp-send",
5212
5212
  "mcp__whatsapp__whatsapp-send-document",
5213
5213
  "mcp__whatsapp__whatsapp-config",
5214
+ "mcp__whatsapp__whatsapp-activity",
5215
+ "mcp__whatsapp__whatsapp-conversations",
5216
+ "mcp__whatsapp__whatsapp-messages",
5217
+ "mcp__whatsapp__whatsapp-group-info",
5214
5218
  "mcp__admin__system-status",
5215
5219
  "mcp__admin__brand-settings",
5216
5220
  "mcp__admin__account-manage",
@@ -23724,7 +23728,8 @@ var WhatsAppAccountSchema = external_exports.object({
23724
23728
  group: external_exports.enum(["always", "mentions", "never"]).optional().default("mentions")
23725
23729
  }).strict().optional().describe("React to incoming messages with an emoji to acknowledge receipt before the agent responds."),
23726
23730
  debounceMs: external_exports.number().int().nonnegative().optional().default(2e3).describe("Wait this many milliseconds after the last message before processing, to batch rapid multi-message input into a single agent turn. Default 2000ms catches most multi-message bursts. 0 means process each message immediately."),
23727
- publicAgent: external_exports.string().optional().describe("Slug of the public agent that handles WhatsApp inbound from non-admin phones for this account. Overrides the top-level publicAgent when set.")
23731
+ publicAgent: external_exports.string().optional().describe("Slug of the public agent that handles WhatsApp inbound from non-admin phones for this account. Overrides the top-level publicAgent when set."),
23732
+ afterHoursMessage: external_exports.string().optional().describe("Auto-reply text sent to public senders when the business is closed (outside opening hours). If not set, messages received outside business hours are silently skipped with no reply.")
23728
23733
  }).strict().superRefine((value, ctx) => {
23729
23734
  if (value.dmPolicy !== "open") return;
23730
23735
  const allow = (value.allowFrom ?? []).map((v) => String(v).trim()).filter(Boolean);
@@ -23746,7 +23751,8 @@ var WhatsAppConfigSchema = external_exports.object({
23746
23751
  mediaMaxMb: external_exports.number().int().positive().optional().default(50).describe("Maximum file size in MB for media the agent will download and process."),
23747
23752
  textChunkLimit: external_exports.number().int().positive().optional().default(4e3).describe("Maximum characters per outbound message. Longer replies are split into multiple messages."),
23748
23753
  debounceMs: external_exports.number().int().nonnegative().optional().default(2e3).describe("Wait this many milliseconds after the last message before processing, to batch rapid multi-message input into a single agent turn. Default 2000ms catches most multi-message bursts. 0 means process each message immediately."),
23749
- publicAgent: external_exports.string().optional().describe("Slug of the public agent that handles WhatsApp inbound from non-admin phones. Per-account overrides take precedence.")
23754
+ publicAgent: external_exports.string().optional().describe("Slug of the public agent that handles WhatsApp inbound from non-admin phones. Per-account overrides take precedence."),
23755
+ afterHoursMessage: external_exports.string().optional().describe("Default auto-reply text sent to public senders when the business is closed (outside opening hours). Per-account overrides take precedence. If not set, messages received outside business hours are silently skipped.")
23750
23756
  }).strict().superRefine((value, ctx) => {
23751
23757
  if (value.dmPolicy !== "open") return;
23752
23758
  const allow = (value.allowFrom ?? []).map((v) => String(v).trim()).filter(Boolean);
@@ -24318,8 +24324,60 @@ function isDuplicateInbound(key) {
24318
24324
  return true;
24319
24325
  }
24320
24326
 
24327
+ // app/lib/whatsapp/activity.ts
24328
+ var TAG5 = "[whatsapp:activity]";
24329
+ var RECENT_EVENTS_MAX = 200;
24330
+ var counters = /* @__PURE__ */ new Map();
24331
+ var recentEvents = [];
24332
+ function recordActivity(event) {
24333
+ try {
24334
+ const now = Date.now();
24335
+ const { accountId, direction, jid, messageType } = event;
24336
+ let acct = counters.get(accountId);
24337
+ if (!acct) {
24338
+ acct = { inbound: 0, outbound: 0, firstEventAt: now, lastEventAt: now };
24339
+ counters.set(accountId, acct);
24340
+ }
24341
+ acct[direction]++;
24342
+ acct.lastEventAt = now;
24343
+ const entry = {
24344
+ channel: "whatsapp",
24345
+ accountId,
24346
+ direction,
24347
+ jid,
24348
+ timestamp: now,
24349
+ messageType
24350
+ };
24351
+ recentEvents.push(entry);
24352
+ if (recentEvents.length > RECENT_EVENTS_MAX) {
24353
+ recentEvents.splice(0, recentEvents.length - RECENT_EVENTS_MAX);
24354
+ }
24355
+ console.error(
24356
+ `${TAG5} channel-activity: direction=${direction} account=${accountId} jid=${jid}` + (messageType ? ` type=${messageType}` : "")
24357
+ );
24358
+ } catch (err) {
24359
+ console.error(`${TAG5} recording failed: ${String(err)}`);
24360
+ }
24361
+ }
24362
+ function getChannelActivity(accountId) {
24363
+ const accounts = [];
24364
+ for (const [id, acct] of counters) {
24365
+ if (accountId && id !== accountId) continue;
24366
+ accounts.push({
24367
+ accountId: id,
24368
+ inbound: acct.inbound,
24369
+ outbound: acct.outbound,
24370
+ total: acct.inbound + acct.outbound,
24371
+ firstEventAt: acct.firstEventAt,
24372
+ lastEventAt: acct.lastEventAt
24373
+ });
24374
+ }
24375
+ const events = accountId ? recentEvents.filter((e) => e.accountId === accountId) : [...recentEvents];
24376
+ return { accounts, recentEvents: events };
24377
+ }
24378
+
24321
24379
  // app/lib/whatsapp/outbound/send.ts
24322
- var TAG5 = "[whatsapp:outbound]";
24380
+ var TAG6 = "[whatsapp:outbound]";
24323
24381
  async function sendTextMessage(sock, to, text, opts) {
24324
24382
  try {
24325
24383
  const jid = to.includes("@") ? to : toWhatsappJid(to);
@@ -24331,11 +24389,14 @@ async function sendTextMessage(sock, to, text, opts) {
24331
24389
  const messageId = result?.key?.id;
24332
24390
  if (messageId) {
24333
24391
  trackAgentSentMessage(messageId);
24334
- console.error(`${TAG5} sent text to=${jid} id=${messageId}`);
24392
+ console.error(`${TAG6} sent text to=${jid} id=${messageId}`);
24393
+ }
24394
+ if (opts?.accountId) {
24395
+ recordActivity({ accountId: opts.accountId, direction: "outbound", jid, messageType: "text" });
24335
24396
  }
24336
24397
  return { success: true, messageId: messageId ?? void 0 };
24337
24398
  } catch (err) {
24338
- console.error(`${TAG5} send failed to=${to}: ${String(err)}`);
24399
+ console.error(`${TAG6} send failed to=${to}: ${String(err)}`);
24339
24400
  return { success: false, error: String(err) };
24340
24401
  }
24341
24402
  }
@@ -24351,7 +24412,7 @@ async function sendReadReceipt(sock, chatJid, messageIds, participant) {
24351
24412
  } catch {
24352
24413
  }
24353
24414
  }
24354
- async function sendMediaMessage(sock, to, media) {
24415
+ async function sendMediaMessage(sock, to, media, opts) {
24355
24416
  try {
24356
24417
  const jid = to.includes("@") ? to : toWhatsappJid(to);
24357
24418
  let payload;
@@ -24390,11 +24451,14 @@ async function sendMediaMessage(sock, to, media) {
24390
24451
  const messageId = result?.key?.id;
24391
24452
  if (messageId) {
24392
24453
  trackAgentSentMessage(messageId);
24393
- console.error(`${TAG5} sent ${media.type} to=${jid} id=${messageId}`);
24454
+ console.error(`${TAG6} sent ${media.type} to=${jid} id=${messageId}`);
24455
+ }
24456
+ if (opts?.accountId) {
24457
+ recordActivity({ accountId: opts.accountId, direction: "outbound", jid, messageType: media.type });
24394
24458
  }
24395
24459
  return { success: true, messageId: messageId ?? void 0 };
24396
24460
  } catch (err) {
24397
- console.error(`${TAG5} send media failed to=${to}: ${String(err)}`);
24461
+ console.error(`${TAG6} send media failed to=${to}: ${String(err)}`);
24398
24462
  return { success: false, error: String(err) };
24399
24463
  }
24400
24464
  }
@@ -24408,7 +24472,7 @@ import {
24408
24472
  downloadContentFromMessage,
24409
24473
  normalizeMessageContent as normalizeMessageContent2
24410
24474
  } from "@whiskeysockets/baileys";
24411
- var TAG6 = "[whatsapp:media]";
24475
+ var TAG7 = "[whatsapp:media]";
24412
24476
  var MEDIA_DIR = "/tmp/maxy-media";
24413
24477
  function mimeToExt(mimetype) {
24414
24478
  const map2 = {
@@ -24464,25 +24528,25 @@ async function downloadInboundMedia(msg, sock, opts) {
24464
24528
  }
24465
24529
  );
24466
24530
  if (!buffer || buffer.length === 0) {
24467
- console.error(`${TAG6} primary download returned empty, trying direct fallback`);
24531
+ console.error(`${TAG7} primary download returned empty, trying direct fallback`);
24468
24532
  const downloadable = getDownloadableContent(content);
24469
24533
  if (downloadable) {
24470
24534
  try {
24471
24535
  const stream = await downloadContentFromMessage(downloadable.downloadable, downloadable.mediaType);
24472
24536
  buffer = await streamToBuffer(stream);
24473
24537
  } catch (fallbackErr) {
24474
- console.error(`${TAG6} direct download fallback failed: ${String(fallbackErr)}`);
24538
+ console.error(`${TAG7} direct download fallback failed: ${String(fallbackErr)}`);
24475
24539
  }
24476
24540
  }
24477
24541
  }
24478
24542
  if (!buffer || buffer.length === 0) {
24479
- console.error(`${TAG6} download failed: empty buffer for ${mimetype ?? "unknown"}`);
24543
+ console.error(`${TAG7} download failed: empty buffer for ${mimetype ?? "unknown"}`);
24480
24544
  return void 0;
24481
24545
  }
24482
24546
  if (buffer.length > maxBytes) {
24483
24547
  const sizeMB = (buffer.length / (1024 * 1024)).toFixed(1);
24484
24548
  const limitMB = (maxBytes / (1024 * 1024)).toFixed(0);
24485
- console.error(`${TAG6} media too large type=${mimetype ?? "unknown"} size=${sizeMB}MB limit=${limitMB}MB`);
24549
+ console.error(`${TAG7} media too large type=${mimetype ?? "unknown"} size=${sizeMB}MB limit=${limitMB}MB`);
24486
24550
  return void 0;
24487
24551
  }
24488
24552
  await mkdir2(MEDIA_DIR, { recursive: true });
@@ -24491,20 +24555,20 @@ async function downloadInboundMedia(msg, sock, opts) {
24491
24555
  const filePath = join6(MEDIA_DIR, filename);
24492
24556
  await writeFile2(filePath, buffer);
24493
24557
  const sizeKB = (buffer.length / 1024).toFixed(0);
24494
- console.error(`${TAG6} media downloaded type=${mimetype ?? "unknown"} size=${sizeKB}KB path=${filePath}`);
24558
+ console.error(`${TAG7} media downloaded type=${mimetype ?? "unknown"} size=${sizeKB}KB path=${filePath}`);
24495
24559
  return {
24496
24560
  path: filePath,
24497
24561
  mimetype: mimetype ?? "application/octet-stream",
24498
24562
  size: buffer.length
24499
24563
  };
24500
24564
  } catch (err) {
24501
- console.error(`${TAG6} media download failed type=${mimetype ?? "unknown"} error=${String(err)}`);
24565
+ console.error(`${TAG7} media download failed type=${mimetype ?? "unknown"} error=${String(err)}`);
24502
24566
  return void 0;
24503
24567
  }
24504
24568
  }
24505
24569
 
24506
24570
  // app/lib/whatsapp/inbound/debounce.ts
24507
- var TAG7 = "[whatsapp:debounce]";
24571
+ var TAG8 = "[whatsapp:debounce]";
24508
24572
  function createInboundDebouncer(opts) {
24509
24573
  const { debounceMs, buildKey, onFlush, onError } = opts;
24510
24574
  const pending = /* @__PURE__ */ new Map();
@@ -24516,7 +24580,7 @@ function createInboundDebouncer(opts) {
24516
24580
  pending.delete(key);
24517
24581
  const batchSize = batch.entries.length;
24518
24582
  try {
24519
- console.error(`${TAG7} debounce flush key=${key} batchSize=${batchSize}`);
24583
+ console.error(`${TAG8} debounce flush key=${key} batchSize=${batchSize}`);
24520
24584
  const result = onFlush(batch.entries);
24521
24585
  if (result && typeof result.catch === "function") {
24522
24586
  result.catch(onError);
@@ -24573,17 +24637,153 @@ function createInboundDebouncer(opts) {
24573
24637
  return { enqueue, flush, destroy };
24574
24638
  }
24575
24639
 
24640
+ // app/lib/whatsapp/opening-hours.ts
24641
+ var TAG9 = "[whatsapp:hours]";
24642
+ async function isBusinessOpen(accountId) {
24643
+ try {
24644
+ const timezone = await resolveTimezone(accountId);
24645
+ const now = /* @__PURE__ */ new Date();
24646
+ const dayOfWeek = getDayOfWeek(now, timezone);
24647
+ const previousDayOfWeek = getPreviousDayOfWeek(now, timezone);
24648
+ const currentTime = getCurrentTime(now, timezone);
24649
+ const session = getSession();
24650
+ try {
24651
+ const result = await session.run(
24652
+ `MATCH (b:LocalBusiness {accountId: $accountId})
24653
+ OPTIONAL MATCH (b)-[:HAS_HOURS]->(hours:OpeningHoursSpecification)
24654
+ WHERE hours.dayOfWeek IN [$dayOfWeek, $previousDayOfWeek]
24655
+ RETURN hours.dayOfWeek AS dayOfWeek, hours.opens AS opens, hours.closes AS closes`,
24656
+ { accountId, dayOfWeek, previousDayOfWeek }
24657
+ );
24658
+ if (result.records.length === 0) {
24659
+ console.error(`${TAG9} [${accountId}] business hours check: no opening hours configured, treating as open`);
24660
+ return { open: true, reason: "no opening hours configured" };
24661
+ }
24662
+ const specs = result.records.filter((r) => r.get("opens") != null && r.get("closes") != null).map((r) => ({
24663
+ day: String(r.get("dayOfWeek")).trim(),
24664
+ opens: String(r.get("opens")).trim(),
24665
+ closes: String(r.get("closes")).trim()
24666
+ }));
24667
+ if (specs.length === 0) {
24668
+ console.error(`${TAG9} [${accountId}] business hours check: no opening hours configured, treating as open`);
24669
+ return { open: true, reason: "no opening hours configured" };
24670
+ }
24671
+ for (const spec of specs) {
24672
+ if (!spec.opens || !spec.closes || spec.opens === spec.closes) continue;
24673
+ const isYesterday = spec.day === previousDayOfWeek;
24674
+ if (isYesterday) {
24675
+ if (spec.opens > spec.closes && currentTime < spec.closes) {
24676
+ const hoursStr = `${spec.opens}-${spec.closes} (${spec.day})`;
24677
+ console.error(
24678
+ `${TAG9} [${accountId}] business hours check: open (day=${dayOfWeek}, time=${currentTime}, hours=${hoursStr})`
24679
+ );
24680
+ return {
24681
+ open: true,
24682
+ reason: "within business hours",
24683
+ dayOfWeek,
24684
+ currentTime,
24685
+ matchedHours: hoursStr
24686
+ };
24687
+ }
24688
+ } else if (isTimeInRange(currentTime, spec.opens, spec.closes)) {
24689
+ const hoursStr = `${spec.opens}-${spec.closes}`;
24690
+ console.error(
24691
+ `${TAG9} [${accountId}] business hours check: open (day=${dayOfWeek}, time=${currentTime}, hours=${hoursStr})`
24692
+ );
24693
+ return {
24694
+ open: true,
24695
+ reason: "within business hours",
24696
+ dayOfWeek,
24697
+ currentTime,
24698
+ matchedHours: hoursStr
24699
+ };
24700
+ }
24701
+ }
24702
+ const todaySpecs = specs.filter((s) => s.day === dayOfWeek);
24703
+ const allHours = todaySpecs.map((s) => `${s.opens}-${s.closes}`).join(", ") || "none today";
24704
+ console.error(
24705
+ `${TAG9} [${accountId}] business hours check: closed (day=${dayOfWeek}, time=${currentTime}, hours=${allHours})`
24706
+ );
24707
+ return {
24708
+ open: false,
24709
+ reason: "outside business hours",
24710
+ dayOfWeek,
24711
+ currentTime,
24712
+ matchedHours: allHours
24713
+ };
24714
+ } finally {
24715
+ await session.close();
24716
+ }
24717
+ } catch (err) {
24718
+ console.error(
24719
+ `${TAG9} [${accountId}] business hours check failed, treating as open: ${err instanceof Error ? err.message : String(err)}`
24720
+ );
24721
+ return { open: true, reason: "hours check failed (treating as open)" };
24722
+ }
24723
+ }
24724
+ async function resolveTimezone(accountId) {
24725
+ try {
24726
+ const tz = await getUserTimezone(accountId);
24727
+ if (tz) {
24728
+ Intl.DateTimeFormat("en-US", { timeZone: tz });
24729
+ return tz;
24730
+ }
24731
+ } catch {
24732
+ }
24733
+ return Intl.DateTimeFormat().resolvedOptions().timeZone;
24734
+ }
24735
+ function getDayOfWeek(date5, timezone) {
24736
+ return new Intl.DateTimeFormat("en-US", { weekday: "long", timeZone: timezone }).format(date5);
24737
+ }
24738
+ function getPreviousDayOfWeek(date5, timezone) {
24739
+ const yesterday = new Date(date5.getTime() - 24 * 60 * 60 * 1e3);
24740
+ return getDayOfWeek(yesterday, timezone);
24741
+ }
24742
+ function getCurrentTime(date5, timezone) {
24743
+ const parts = new Intl.DateTimeFormat("en-US", {
24744
+ hour: "2-digit",
24745
+ minute: "2-digit",
24746
+ hour12: false,
24747
+ timeZone: timezone
24748
+ }).formatToParts(date5);
24749
+ const hour = parts.find((p) => p.type === "hour")?.value ?? "00";
24750
+ const minute = parts.find((p) => p.type === "minute")?.value ?? "00";
24751
+ return `${hour}:${minute}`;
24752
+ }
24753
+ function isTimeInRange(time3, opens, closes) {
24754
+ if (opens <= closes) {
24755
+ return time3 >= opens && time3 < closes;
24756
+ }
24757
+ return time3 >= opens || time3 < closes;
24758
+ }
24759
+
24576
24760
  // app/lib/whatsapp/manager.ts
24577
- var TAG8 = "[whatsapp:manager]";
24761
+ var TAG10 = "[whatsapp:manager]";
24578
24762
  var MAX_RECONNECT_ATTEMPTS = 10;
24763
+ var MESSAGE_STORE_MAX = 500;
24764
+ var GROUP_META_TTL = 5 * 60 * 1e3;
24579
24765
  var connections = /* @__PURE__ */ new Map();
24580
24766
  var configDir = null;
24581
24767
  var whatsAppConfig = {};
24582
24768
  var onInboundMessage = null;
24583
24769
  var initialized = false;
24770
+ var messageStore = /* @__PURE__ */ new Map();
24771
+ function storeMessage(storeKey, entry) {
24772
+ let entries = messageStore.get(storeKey);
24773
+ if (!entries) {
24774
+ entries = [];
24775
+ messageStore.set(storeKey, entries);
24776
+ }
24777
+ entries.push(entry);
24778
+ if (entries.length > MESSAGE_STORE_MAX) {
24779
+ const trimmed = entries.length - MESSAGE_STORE_MAX;
24780
+ entries.splice(0, trimmed);
24781
+ console.error(`${TAG10} message store trimmed for ${storeKey}: ${trimmed} oldest messages removed`);
24782
+ }
24783
+ }
24584
24784
  async function init(opts) {
24585
24785
  if (initialized) {
24586
- console.error(`${TAG8} already initialized`);
24786
+ console.error(`${TAG10} already initialized`);
24587
24787
  return;
24588
24788
  }
24589
24789
  configDir = opts.configDir;
@@ -24591,20 +24791,20 @@ async function init(opts) {
24591
24791
  loadConfig(opts.accountConfig);
24592
24792
  const accountIds = listCredentialAccountIds(configDir);
24593
24793
  if (accountIds.length === 0) {
24594
- console.error(`${TAG8} init: no stored WhatsApp credentials found`);
24794
+ console.error(`${TAG10} init: no stored WhatsApp credentials found`);
24595
24795
  initialized = true;
24596
24796
  return;
24597
24797
  }
24598
- console.error(`${TAG8} init: found ${accountIds.length} credentialed account(s): ${accountIds.join(", ")}`);
24798
+ console.error(`${TAG10} init: found ${accountIds.length} credentialed account(s): ${accountIds.join(", ")}`);
24599
24799
  initialized = true;
24600
24800
  for (const accountId of accountIds) {
24601
24801
  const accountCfg = whatsAppConfig.accounts?.[accountId];
24602
24802
  if (accountCfg?.enabled === false) {
24603
- console.error(`${TAG8} skipping disabled account=${accountId}`);
24803
+ console.error(`${TAG10} skipping disabled account=${accountId}`);
24604
24804
  continue;
24605
24805
  }
24606
24806
  startConnection(accountId).catch((err) => {
24607
- console.error(`${TAG8} failed to auto-start account=${accountId}: ${formatError(err)}`);
24807
+ console.error(`${TAG10} failed to auto-start account=${accountId}: ${formatError(err)}`);
24608
24808
  });
24609
24809
  }
24610
24810
  }
@@ -24613,7 +24813,7 @@ async function startConnection(accountId) {
24613
24813
  const authDir = resolveAuthDir(configDir, accountId);
24614
24814
  const hasAuth = await authExists(authDir);
24615
24815
  if (!hasAuth) {
24616
- console.error(`${TAG8} no credentials for account=${accountId}`);
24816
+ console.error(`${TAG10} no credentials for account=${accountId}`);
24617
24817
  return;
24618
24818
  }
24619
24819
  await stopConnection(accountId);
@@ -24628,7 +24828,8 @@ async function startConnection(accountId) {
24628
24828
  reconnectAttempts: 0,
24629
24829
  abortController: new AbortController(),
24630
24830
  debouncer: null,
24631
- lidMapping: null
24831
+ lidMapping: null,
24832
+ groupMetaCache: /* @__PURE__ */ new Map()
24632
24833
  };
24633
24834
  connections.set(accountId, conn);
24634
24835
  await connectWithReconnect(conn);
@@ -24648,11 +24849,11 @@ async function stopConnection(accountId) {
24648
24849
  conn.sock.ev.removeAllListeners("creds.update");
24649
24850
  conn.sock.ws?.close?.();
24650
24851
  } catch (err) {
24651
- console.warn(`${TAG8} socket cleanup error during stop account=${accountId}: ${String(err)}`);
24852
+ console.warn(`${TAG10} socket cleanup error during stop account=${accountId}: ${String(err)}`);
24652
24853
  }
24653
24854
  }
24654
24855
  connections.delete(accountId);
24655
- console.error(`${TAG8} stopped account=${accountId}`);
24856
+ console.error(`${TAG10} stopped account=${accountId}`);
24656
24857
  }
24657
24858
  function getStatus() {
24658
24859
  return Array.from(connections.values()).map((conn) => ({
@@ -24686,18 +24887,19 @@ async function registerLoginSocket(accountId, sock, authDir) {
24686
24887
  lastConnectedAt: Date.now(),
24687
24888
  abortController: new AbortController(),
24688
24889
  debouncer: null,
24689
- lidMapping
24890
+ lidMapping,
24891
+ groupMetaCache: /* @__PURE__ */ new Map()
24690
24892
  };
24691
24893
  connections.set(accountId, conn);
24692
24894
  try {
24693
24895
  await sock.sendPresenceUpdate("available");
24694
- console.error(`${TAG8} presence set to available account=${accountId}`);
24896
+ console.error(`${TAG10} presence set to available account=${accountId}`);
24695
24897
  } catch (err) {
24696
- console.error(`${TAG8} presence update failed account=${accountId}: ${String(err)}`);
24898
+ console.error(`${TAG10} presence update failed account=${accountId}: ${String(err)}`);
24697
24899
  }
24698
24900
  monitorInbound(conn);
24699
24901
  watchForDisconnect(conn);
24700
- console.error(`${TAG8} registered login socket for account=${accountId} phone=${selfId.e164 ?? "unknown"}`);
24902
+ console.error(`${TAG10} registered login socket for account=${accountId} phone=${selfId.e164 ?? "unknown"}`);
24701
24903
  }
24702
24904
  function reloadConfig(accountConfig) {
24703
24905
  loadConfig(accountConfig);
@@ -24707,7 +24909,7 @@ async function shutdown() {
24707
24909
  const ids = Array.from(connections.keys());
24708
24910
  await Promise.all(ids.map((id) => stopConnection(id)));
24709
24911
  initialized = false;
24710
- console.error(`${TAG8} shutdown complete`);
24912
+ console.error(`${TAG10} shutdown complete`);
24711
24913
  }
24712
24914
  function loadConfig(accountConfig) {
24713
24915
  try {
@@ -24719,12 +24921,12 @@ function loadConfig(accountConfig) {
24719
24921
  if (parsed.success) {
24720
24922
  whatsAppConfig = parsed.data;
24721
24923
  } else {
24722
- console.error(`${TAG8} config validation failed: ${parsed.error.message}`);
24924
+ console.error(`${TAG10} config validation failed: ${parsed.error.message}`);
24723
24925
  whatsAppConfig = {};
24724
24926
  }
24725
24927
  }
24726
24928
  } catch (err) {
24727
- console.error(`${TAG8} config load error: ${String(err)}`);
24929
+ console.error(`${TAG10} config load error: ${String(err)}`);
24728
24930
  whatsAppConfig = {};
24729
24931
  }
24730
24932
  }
@@ -24732,13 +24934,13 @@ async function connectWithReconnect(conn) {
24732
24934
  const maxAttempts = MAX_RECONNECT_ATTEMPTS;
24733
24935
  while (!conn.abortController.signal.aborted) {
24734
24936
  try {
24735
- console.error(`${TAG8} connecting account=${conn.accountId} attempt=${conn.reconnectAttempts}`);
24937
+ console.error(`${TAG10} connecting account=${conn.accountId} attempt=${conn.reconnectAttempts}`);
24736
24938
  const sock = await createWaSocket({
24737
24939
  authDir: conn.authDir,
24738
24940
  silent: true
24739
24941
  });
24740
24942
  conn.sock = sock;
24741
- console.error(`${TAG8} socket created account=${conn.accountId} \u2014 waiting for connection`);
24943
+ console.error(`${TAG10} socket created account=${conn.accountId} \u2014 waiting for connection`);
24742
24944
  await waitForConnection(sock);
24743
24945
  const selfId = readSelfId(conn.authDir);
24744
24946
  conn.connected = true;
@@ -24748,12 +24950,12 @@ async function connectWithReconnect(conn) {
24748
24950
  conn.reconnectAttempts = 0;
24749
24951
  conn.lastError = void 0;
24750
24952
  conn.lidMapping = sock.signalRepository?.lidMapping ?? null;
24751
- console.error(`${TAG8} connected account=${conn.accountId} phone=${selfId.e164 ?? "unknown"}`);
24953
+ console.error(`${TAG10} connected account=${conn.accountId} phone=${selfId.e164 ?? "unknown"}`);
24752
24954
  try {
24753
24955
  await sock.sendPresenceUpdate("available");
24754
- console.error(`${TAG8} presence set to available account=${conn.accountId}`);
24956
+ console.error(`${TAG10} presence set to available account=${conn.accountId}`);
24755
24957
  } catch (err) {
24756
- console.error(`${TAG8} presence update failed account=${conn.accountId}: ${String(err)}`);
24958
+ console.error(`${TAG10} presence update failed account=${conn.accountId}: ${String(err)}`);
24757
24959
  }
24758
24960
  if (conn.debouncer) {
24759
24961
  conn.debouncer.destroy();
@@ -24768,19 +24970,19 @@ async function connectWithReconnect(conn) {
24768
24970
  conn.sock = null;
24769
24971
  const classification = classifyDisconnect(err);
24770
24972
  conn.lastError = classification.message;
24771
- console.error(`${TAG8} disconnect account=${conn.accountId}: ${classification.kind} \u2014 ${classification.message}`);
24973
+ console.error(`${TAG10} disconnect account=${conn.accountId}: ${classification.kind} \u2014 ${classification.message}`);
24772
24974
  if (!classification.shouldRetry) {
24773
- console.error(`${TAG8} terminal disconnect for account=${conn.accountId}, stopping reconnection`);
24975
+ console.error(`${TAG10} terminal disconnect for account=${conn.accountId}, stopping reconnection`);
24774
24976
  return;
24775
24977
  }
24776
24978
  conn.reconnectAttempts++;
24777
24979
  if (conn.reconnectAttempts > maxAttempts) {
24778
- console.error(`${TAG8} max retries exceeded for account=${conn.accountId}`);
24980
+ console.error(`${TAG10} max retries exceeded for account=${conn.accountId}`);
24779
24981
  conn.lastError = `Max reconnect attempts (${maxAttempts}) exceeded`;
24780
24982
  return;
24781
24983
  }
24782
24984
  const delay = computeBackoff(conn.reconnectAttempts);
24783
- console.error(`${TAG8} reconnecting account=${conn.accountId} in ${delay}ms (attempt ${conn.reconnectAttempts}/${maxAttempts})`);
24985
+ console.error(`${TAG10} reconnecting account=${conn.accountId} in ${delay}ms (attempt ${conn.reconnectAttempts}/${maxAttempts})`);
24784
24986
  await new Promise((resolve19) => {
24785
24987
  const timer = setTimeout(resolve19, delay);
24786
24988
  conn.abortController.signal.addEventListener("abort", () => {
@@ -24810,20 +25012,51 @@ function watchForDisconnect(conn) {
24810
25012
  conn.sock.ev.on("connection.update", (update) => {
24811
25013
  if (update.connection === "close") {
24812
25014
  if (connections.get(conn.accountId) !== conn) return;
24813
- console.error(`${TAG8} socket disconnected for account=${conn.accountId}`);
25015
+ console.error(`${TAG10} socket disconnected for account=${conn.accountId}`);
24814
25016
  conn.connected = false;
24815
25017
  conn.sock = null;
24816
25018
  connectWithReconnect(conn).catch((err) => {
24817
- console.error(`${TAG8} reconnection failed for account=${conn.accountId}: ${formatError(err)}`);
25019
+ console.error(`${TAG10} reconnection failed for account=${conn.accountId}: ${formatError(err)}`);
24818
25020
  });
24819
25021
  }
24820
25022
  });
24821
25023
  }
25024
+ async function getGroupMeta(conn, jid) {
25025
+ const cached2 = conn.groupMetaCache.get(jid);
25026
+ if (cached2 && cached2.expires > Date.now()) return cached2;
25027
+ if (!conn.sock) return null;
25028
+ console.error(`${TAG10} group metadata cache miss for ${jid}, fetching from Baileys account=${conn.accountId}`);
25029
+ try {
25030
+ const meta3 = await conn.sock.groupMetadata(jid);
25031
+ const participants = await Promise.all(
25032
+ (meta3.participants ?? []).map(async (p) => {
25033
+ return await resolveJidToE164(p.id, conn.lidMapping) ?? p.id;
25034
+ })
25035
+ );
25036
+ const entry = {
25037
+ subject: meta3.subject,
25038
+ participants,
25039
+ expires: Date.now() + GROUP_META_TTL
25040
+ };
25041
+ conn.groupMetaCache.set(jid, entry);
25042
+ console.error(
25043
+ `${TAG10} group metadata cached for ${jid}: "${meta3.subject}", ${participants.length} participants, expires in ${GROUP_META_TTL}ms account=${conn.accountId}`
25044
+ );
25045
+ return entry;
25046
+ } catch (err) {
25047
+ console.error(
25048
+ `${TAG10} group metadata fetch failed for ${jid}: ${err instanceof Error ? err.message : String(err)}, caching empty entry for ${GROUP_META_TTL}ms account=${conn.accountId}`
25049
+ );
25050
+ const emptyEntry = { expires: Date.now() + GROUP_META_TTL };
25051
+ conn.groupMetaCache.set(jid, emptyEntry);
25052
+ return null;
25053
+ }
25054
+ }
24822
25055
  function monitorInbound(conn) {
24823
25056
  if (!conn.sock || !onInboundMessage) return;
24824
25057
  const sock = conn.sock;
24825
25058
  const debounceMs = whatsAppConfig.accounts?.[conn.accountId]?.debounceMs ?? whatsAppConfig.debounceMs ?? 0;
24826
- console.error(`${TAG8} monitorInbound started account=${conn.accountId} debounceMs=${debounceMs}`);
25059
+ console.error(`${TAG10} monitorInbound started account=${conn.accountId} debounceMs=${debounceMs}`);
24827
25060
  conn.debouncer = createInboundDebouncer({
24828
25061
  debounceMs,
24829
25062
  buildKey: (payload) => {
@@ -24836,7 +25069,7 @@ function monitorInbound(conn) {
24836
25069
  onInboundMessage(entries[0]);
24837
25070
  return;
24838
25071
  }
24839
- console.error(`${TAG8} debounce: combining ${entries.length} messages account=${conn.accountId} from=${entries[0].senderPhone}`);
25072
+ console.error(`${TAG10} debounce: combining ${entries.length} messages account=${conn.accountId} from=${entries[0].senderPhone}`);
24840
25073
  const last = entries[entries.length - 1];
24841
25074
  const mediaEntry = entries.find((e) => e.mediaPath);
24842
25075
  const combinedText = entries.map((e) => e.text).filter(Boolean).join("\n");
@@ -24849,16 +25082,60 @@ function monitorInbound(conn) {
24849
25082
  });
24850
25083
  },
24851
25084
  onError: (err) => {
24852
- console.error(`${TAG8} debounce flush error account=${conn.accountId}: ${String(err)}`);
25085
+ console.error(`${TAG10} debounce flush error account=${conn.accountId}: ${String(err)}`);
24853
25086
  }
24854
25087
  });
24855
25088
  sock.ev.on("messages.upsert", async (upsert) => {
24856
- if (upsert.type !== "notify") return;
25089
+ if (upsert.type !== "notify" && upsert.type !== "append") return;
24857
25090
  for (const msg of upsert.messages) {
24858
25091
  try {
25092
+ const remoteJid = msg.key.remoteJid;
25093
+ if (!remoteJid || remoteJid === "status@broadcast") continue;
25094
+ if (!msg.message) continue;
25095
+ const extracted = extractMessage(msg);
25096
+ if (extracted.text) {
25097
+ const isGroup = isGroupJid(remoteJid);
25098
+ const senderJid = isGroup ? msg.key.participant ?? remoteJid : remoteJid;
25099
+ const senderPhone = await resolveJidToE164(senderJid, conn.lidMapping) ?? senderJid;
25100
+ const ts = msg.messageTimestamp ? Number(msg.messageTimestamp) : Math.floor(Date.now() / 1e3);
25101
+ const storeKey = `${conn.accountId}:${remoteJid}`;
25102
+ storeMessage(storeKey, {
25103
+ id: msg.key.id ?? `${remoteJid}-${ts}`,
25104
+ sender: senderPhone,
25105
+ senderName: msg.pushName ?? void 0,
25106
+ body: extracted.text,
25107
+ timestamp: ts,
25108
+ fromMe: Boolean(msg.key.fromMe),
25109
+ quoted: extracted.quotedMessage ? { id: extracted.quotedMessage.id, body: extracted.quotedMessage.text, sender: extracted.quotedMessage.sender } : void 0
25110
+ });
25111
+ const entries = messageStore.get(storeKey);
25112
+ console.error(
25113
+ `${TAG10} stored message ${msg.key.id ?? "?"} for ${remoteJid} (type: ${upsert.type}, store size: ${entries?.length ?? 0}/${MESSAGE_STORE_MAX}) account=${conn.accountId}`
25114
+ );
25115
+ recordActivity({
25116
+ accountId: conn.accountId,
25117
+ direction: "inbound",
25118
+ jid: remoteJid
25119
+ });
25120
+ }
25121
+ if (upsert.type === "append") {
25122
+ const sendReceipts = whatsAppConfig.accounts?.[conn.accountId]?.sendReadReceipts ?? whatsAppConfig.sendReadReceipts ?? true;
25123
+ if (sendReceipts && msg.key.id) {
25124
+ sendReadReceipt(
25125
+ sock,
25126
+ remoteJid,
25127
+ [msg.key.id],
25128
+ isGroupJid(remoteJid) ? msg.key.participant ?? void 0 : void 0
25129
+ );
25130
+ }
25131
+ console.error(
25132
+ `${TAG10} append-type message ${msg.key.id ?? "?"} stored, dispatch skipped account=${conn.accountId}`
25133
+ );
25134
+ continue;
25135
+ }
24859
25136
  await handleInboundMessage(conn, msg);
24860
25137
  } catch (err) {
24861
- console.error(`${TAG8} inbound handler error account=${conn.accountId}: ${String(err)}`);
25138
+ console.error(`${TAG10} inbound handler error account=${conn.accountId}: ${String(err)}`);
24862
25139
  }
24863
25140
  }
24864
25141
  });
@@ -24868,35 +25145,35 @@ async function handleInboundMessage(conn, msg) {
24868
25145
  const remoteJid = msg.key.remoteJid;
24869
25146
  if (!remoteJid) return;
24870
25147
  if (remoteJid === "status@broadcast") {
24871
- console.error(`${TAG8} drop: status broadcast account=${conn.accountId}`);
25148
+ console.error(`${TAG10} drop: status broadcast account=${conn.accountId}`);
24872
25149
  return;
24873
25150
  }
24874
25151
  if (!msg.message) {
24875
- console.error(`${TAG8} drop: empty message account=${conn.accountId} from=${remoteJid}`);
25152
+ console.error(`${TAG10} drop: empty message account=${conn.accountId} from=${remoteJid}`);
24876
25153
  return;
24877
25154
  }
24878
25155
  const dedupKey = `${conn.accountId}:${remoteJid}:${msg.key.id}`;
24879
25156
  if (isDuplicateInbound(dedupKey)) {
24880
- console.error(`${TAG8} drop: duplicate account=${conn.accountId} key=${dedupKey}`);
25157
+ console.error(`${TAG10} drop: duplicate account=${conn.accountId} key=${dedupKey}`);
24881
25158
  return;
24882
25159
  }
24883
25160
  if (msg.key.fromMe) {
24884
25161
  if (msg.key.id && isAgentSentMessage(msg.key.id)) {
24885
- console.error(`${TAG8} drop: echo suppression account=${conn.accountId} msgId=${msg.key.id}`);
25162
+ console.error(`${TAG10} drop: echo suppression account=${conn.accountId} msgId=${msg.key.id}`);
24886
25163
  return;
24887
25164
  }
24888
25165
  const extracted2 = extractMessage(msg);
24889
25166
  if (!extracted2.text) {
24890
- console.error(`${TAG8} owner reply skipped \u2014 no text content account=${conn.accountId}`);
25167
+ console.error(`${TAG10} owner reply skipped \u2014 no text content account=${conn.accountId}`);
24891
25168
  return;
24892
25169
  }
24893
25170
  const isGroup2 = isGroupJid(remoteJid);
24894
25171
  const senderPhone2 = conn.selfPhone ?? "owner";
24895
- console.error(`${TAG8} owner reply mirrored to session from=${senderPhone2} account=${conn.accountId}`);
25172
+ console.error(`${TAG10} owner reply mirrored to session from=${senderPhone2} account=${conn.accountId}`);
24896
25173
  const reply2 = async (text) => {
24897
25174
  const currentSock = conn.sock;
24898
25175
  if (!currentSock) throw new Error("WhatsApp disconnected \u2014 cannot reply");
24899
- await sendTextMessage(currentSock, remoteJid, text);
25176
+ await sendTextMessage(currentSock, remoteJid, text, { accountId: conn.accountId });
24900
25177
  };
24901
25178
  onInboundMessage({
24902
25179
  accountId: conn.accountId,
@@ -24913,7 +25190,7 @@ async function handleInboundMessage(conn, msg) {
24913
25190
  }
24914
25191
  const extracted = extractMessage(msg);
24915
25192
  if (!extracted.text && !extracted.mediaType) {
24916
- console.error(`${TAG8} drop: no text or media account=${conn.accountId} from=${remoteJid}`);
25193
+ console.error(`${TAG10} drop: no text or media account=${conn.accountId} from=${remoteJid}`);
24917
25194
  return;
24918
25195
  }
24919
25196
  let mediaResult;
@@ -24923,7 +25200,7 @@ async function handleInboundMessage(conn, msg) {
24923
25200
  maxBytes: maxMb * 1024 * 1024
24924
25201
  });
24925
25202
  if (!mediaResult) {
24926
- console.error(`${TAG8} media download returned undefined account=${conn.accountId} type=${extracted.mediaType} from=${remoteJid}`);
25203
+ console.error(`${TAG10} media download returned undefined account=${conn.accountId} type=${extracted.mediaType} from=${remoteJid}`);
24927
25204
  }
24928
25205
  }
24929
25206
  const isGroup = isGroupJid(remoteJid);
@@ -24951,9 +25228,14 @@ async function handleInboundMessage(conn, msg) {
24951
25228
  });
24952
25229
  }
24953
25230
  console.error(
24954
- `${TAG8} inbound account=${conn.accountId} from=${senderPhone} group=${isGroup} access=${accessResult.allowed ? "allowed" : "blocked"}(${accessResult.reason}) agent=${accessResult.agentType}` + (extracted.mediaType ? ` media=${extracted.mediaType}` : "") + (mediaResult ? ` mediaPath=${mediaResult.path}` : "") + (extracted.quotedMessage ? ` replyTo=${extracted.quotedMessage.id}` : "")
25231
+ `${TAG10} inbound account=${conn.accountId} from=${senderPhone} group=${isGroup} access=${accessResult.allowed ? "allowed" : "blocked"}(${accessResult.reason}) agent=${accessResult.agentType}` + (extracted.mediaType ? ` media=${extracted.mediaType}` : "") + (mediaResult ? ` mediaPath=${mediaResult.path}` : "") + (extracted.quotedMessage ? ` replyTo=${extracted.quotedMessage.id}` : "")
24955
25232
  );
24956
25233
  if (!accessResult.allowed) return;
25234
+ let groupSubject;
25235
+ if (isGroup) {
25236
+ const groupMeta = await getGroupMeta(conn, remoteJid);
25237
+ groupSubject = groupMeta?.subject;
25238
+ }
24957
25239
  const sendReceipts = whatsAppConfig.accounts?.[conn.accountId]?.sendReadReceipts ?? whatsAppConfig.sendReadReceipts ?? true;
24958
25240
  if (sendReceipts && msg.key.id) {
24959
25241
  sendReadReceipt(conn.sock, remoteJid, [msg.key.id], isGroup ? senderJid : void 0);
@@ -24961,7 +25243,7 @@ async function handleInboundMessage(conn, msg) {
24961
25243
  const reply = async (text) => {
24962
25244
  const currentSock = conn.sock;
24963
25245
  if (!currentSock) throw new Error("WhatsApp disconnected \u2014 cannot reply");
24964
- await sendTextMessage(currentSock, remoteJid, text);
25246
+ await sendTextMessage(currentSock, remoteJid, text, { accountId: conn.accountId });
24965
25247
  };
24966
25248
  const sessionKey = accessResult.agentType === "admin" ? `whatsapp:${conn.accountId}` : void 0;
24967
25249
  conn.lastMessageAt = Date.now();
@@ -24972,6 +25254,7 @@ async function handleInboundMessage(conn, msg) {
24972
25254
  text: extracted.text,
24973
25255
  isGroup,
24974
25256
  groupJid: isGroup ? remoteJid : void 0,
25257
+ groupSubject,
24975
25258
  reply,
24976
25259
  sessionKey,
24977
25260
  mediaPath: mediaResult?.path,
@@ -24979,6 +25262,24 @@ async function handleInboundMessage(conn, msg) {
24979
25262
  mediaType: extracted.mediaType,
24980
25263
  replyContext: extracted.quotedMessage
24981
25264
  };
25265
+ if (accessResult.agentType === "public") {
25266
+ const hoursResult = await isBusinessOpen(conn.accountId);
25267
+ if (!hoursResult.open) {
25268
+ console.error(`${TAG10} [${conn.accountId}] dispatch skipped: business closed`);
25269
+ const afterHoursMessage = whatsAppConfig.accounts?.[conn.accountId]?.afterHoursMessage ?? whatsAppConfig.afterHoursMessage;
25270
+ if (afterHoursMessage) {
25271
+ try {
25272
+ await reply(afterHoursMessage);
25273
+ console.error(`${TAG10} [${conn.accountId}] after-hours auto-reply sent to ${remoteJid}`);
25274
+ } catch (err) {
25275
+ console.error(
25276
+ `${TAG10} [${conn.accountId}] after-hours auto-reply failed: ${err instanceof Error ? err.message : String(err)}`
25277
+ );
25278
+ }
25279
+ }
25280
+ return;
25281
+ }
25282
+ }
24982
25283
  if (conn.debouncer) {
24983
25284
  conn.debouncer.enqueue(payload);
24984
25285
  } else {
@@ -25083,7 +25384,7 @@ async function POST13(req) {
25083
25384
  { status: 503 }
25084
25385
  );
25085
25386
  }
25086
- const result = await sendTextMessage(sock, to, text);
25387
+ const result = await sendTextMessage(sock, to, text, { accountId });
25087
25388
  return Response.json(result);
25088
25389
  } catch (err) {
25089
25390
  console.error(`[whatsapp:api] send error: ${String(err)}`);
@@ -25317,7 +25618,7 @@ async function POST14(req) {
25317
25618
  import { readFile as readFile2, stat as stat2 } from "fs/promises";
25318
25619
  import { realpathSync as realpathSync2 } from "fs";
25319
25620
  import { resolve as resolve11, basename as basename2 } from "path";
25320
- var TAG9 = "[whatsapp:api]";
25621
+ var TAG11 = "[whatsapp:api]";
25321
25622
  var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT || "";
25322
25623
  async function POST15(req) {
25323
25624
  try {
@@ -25340,16 +25641,16 @@ async function POST15(req) {
25340
25641
  const accountResolved = realpathSync2(accountDir);
25341
25642
  if (!resolvedPath.startsWith(accountResolved + "/")) {
25342
25643
  const sanitised = filePath.replace(accountDir, "<account>/");
25343
- console.error(`${TAG9} send-document REJECTED path=${sanitised} reason=outside_account_directory`);
25644
+ console.error(`${TAG11} send-document REJECTED path=${sanitised} reason=outside_account_directory`);
25344
25645
  return Response.json({ error: "Access denied: file is outside the account directory" }, { status: 403 });
25345
25646
  }
25346
25647
  } catch (err) {
25347
25648
  const code = err.code;
25348
25649
  if (code === "ENOENT") {
25349
- console.error(`${TAG9} send-document ENOENT path=${filePath}`);
25650
+ console.error(`${TAG11} send-document ENOENT path=${filePath}`);
25350
25651
  return Response.json({ error: `File not found: ${filePath}` }, { status: 404 });
25351
25652
  }
25352
- console.error(`${TAG9} send-document path error: ${String(err)}`);
25653
+ console.error(`${TAG11} send-document path error: ${String(err)}`);
25353
25654
  return Response.json({ error: String(err) }, { status: 500 });
25354
25655
  }
25355
25656
  const fileStat = await stat2(resolvedPath);
@@ -25375,13 +25676,30 @@ async function POST15(req) {
25375
25676
  mimetype,
25376
25677
  filename,
25377
25678
  caption
25378
- });
25679
+ }, { accountId });
25680
+ console.error(
25681
+ `${TAG11} send-document to=${to} size=${fileStat.size} mime=${mimetype} ok=${result.success}` + (result.messageId ? ` id=${result.messageId}` : "")
25682
+ );
25683
+ return Response.json(result);
25684
+ } catch (err) {
25685
+ console.error(`${TAG11} send-document error: ${String(err)}`);
25686
+ return Response.json({ error: String(err) }, { status: 500 });
25687
+ }
25688
+ }
25689
+
25690
+ // app/api/whatsapp/activity/route.ts
25691
+ async function GET3(req) {
25692
+ try {
25693
+ const url2 = new URL(req.url);
25694
+ const accountId = url2.searchParams.get("accountId") ?? void 0;
25695
+ const result = getChannelActivity(accountId);
25696
+ const total = result.accounts.reduce((sum, a) => sum + a.total, 0);
25379
25697
  console.error(
25380
- `${TAG9} send-document to=${to} size=${fileStat.size} mime=${mimetype} ok=${result.success}` + (result.messageId ? ` id=${result.messageId}` : "")
25698
+ `[whatsapp:api] activity accounts=${result.accounts.length} total=${total} recentEvents=${result.recentEvents.length}` + (accountId ? ` filter=${accountId}` : "")
25381
25699
  );
25382
25700
  return Response.json(result);
25383
25701
  } catch (err) {
25384
- console.error(`${TAG9} send-document error: ${String(err)}`);
25702
+ console.error(`[whatsapp:api] activity error: ${String(err)}`);
25385
25703
  return Response.json({ error: String(err) }, { status: 500 });
25386
25704
  }
25387
25705
  }
@@ -25441,7 +25759,7 @@ async function waitForAuthPage(timeoutMs = 2e4) {
25441
25759
  }
25442
25760
  return false;
25443
25761
  }
25444
- async function GET3() {
25762
+ async function GET4() {
25445
25763
  return Response.json({ authenticated: checkAuthStatus() });
25446
25764
  }
25447
25765
  async function POST17(req) {
@@ -25591,11 +25909,19 @@ async function POST19(req) {
25591
25909
  } catch (err) {
25592
25910
  console.error(`[session] onboarding query failed: ${err instanceof Error ? err.message : String(err)}`);
25593
25911
  }
25912
+ let businessName;
25913
+ try {
25914
+ const branding = await fetchBranding(accountId);
25915
+ businessName = branding?.name || void 0;
25916
+ } catch {
25917
+ }
25918
+ console.log(`[session] businessName=${businessName ?? "\u2013"}`);
25594
25919
  return Response.json({
25595
25920
  session_key: sessionKey,
25596
25921
  agent_id: "admin",
25597
25922
  thinkingView,
25598
- onboardingComplete
25923
+ onboardingComplete,
25924
+ businessName
25599
25925
  });
25600
25926
  }
25601
25927
 
@@ -25887,7 +26213,7 @@ async function POST21(req) {
25887
26213
  import { existsSync as existsSync14, readdirSync as readdirSync3, readFileSync as readFileSync14, statSync as statSync3 } from "fs";
25888
26214
  import { resolve as resolve12, basename as basename3 } from "path";
25889
26215
  var TAIL_BYTES = 8192;
25890
- async function GET4(request) {
26216
+ async function GET5(request) {
25891
26217
  const { searchParams } = new URL(request.url);
25892
26218
  const fileParam = searchParams.get("file");
25893
26219
  const typeParam = searchParams.get("type");
@@ -25961,7 +26287,7 @@ async function GET4(request) {
25961
26287
 
25962
26288
  // app/api/admin/claude-info/route.ts
25963
26289
  import { execFileSync as execFileSync3 } from "child_process";
25964
- async function GET5() {
26290
+ async function GET6() {
25965
26291
  let version2 = "unknown";
25966
26292
  try {
25967
26293
  const raw2 = execFileSync3("claude", ["--version"], { encoding: "utf-8", timeout: 5e3 });
@@ -25985,7 +26311,7 @@ async function GET5() {
25985
26311
  import { readFile as readFile3, readdir } from "fs/promises";
25986
26312
  import { existsSync as existsSync15 } from "fs";
25987
26313
  import { resolve as resolve13 } from "path";
25988
- async function GET6(req, attachmentId) {
26314
+ async function GET7(req, attachmentId) {
25989
26315
  const sessionKey = new URL(req.url).searchParams.get("session_key") ?? "";
25990
26316
  if (!validateSession(sessionKey, "admin")) {
25991
26317
  return new Response("Unauthorized", { status: 401 });
@@ -26069,7 +26395,7 @@ async function PATCH(req) {
26069
26395
  // app/api/admin/agents/route.ts
26070
26396
  import { resolve as resolve15 } from "path";
26071
26397
  import { readdirSync as readdirSync4, readFileSync as readFileSync16, existsSync as existsSync16 } from "fs";
26072
- async function GET7() {
26398
+ async function GET8() {
26073
26399
  const account = resolveAccount();
26074
26400
  if (!account) {
26075
26401
  return Response.json({ agents: [] });
@@ -26172,7 +26498,7 @@ function isNewer(latest, installed) {
26172
26498
  function invalidateVersionCache() {
26173
26499
  latestCache = null;
26174
26500
  }
26175
- async function GET8() {
26501
+ async function GET9() {
26176
26502
  const installed = readInstalled();
26177
26503
  const latest = await fetchLatest();
26178
26504
  const updateAvailable = installed !== "unknown" && latest !== null && isNewer(latest, installed);
@@ -26252,7 +26578,7 @@ async function POST22(req) {
26252
26578
  }
26253
26579
 
26254
26580
  // app/api/admin/sessions/route.ts
26255
- async function GET9(req) {
26581
+ async function GET10(req) {
26256
26582
  const url2 = new URL(req.url);
26257
26583
  const sessionKey = url2.searchParams.get("session_key");
26258
26584
  if (!sessionKey) {
@@ -26303,7 +26629,7 @@ async function DELETE2(req, { params }) {
26303
26629
  }
26304
26630
 
26305
26631
  // app/api/admin/sessions/[id]/messages/route.ts
26306
- async function GET10(req, { params }) {
26632
+ async function GET11(req, { params }) {
26307
26633
  const { id: conversationId } = await params;
26308
26634
  const url2 = new URL(req.url);
26309
26635
  const sessionKey = url2.searchParams.get("session_key");
@@ -26686,33 +27012,34 @@ app.post("/api/whatsapp/reconnect", (c) => POST12(c.req.raw));
26686
27012
  app.post("/api/whatsapp/send", (c) => POST13(c.req.raw));
26687
27013
  app.post("/api/whatsapp/config", (c) => POST14(c.req.raw));
26688
27014
  app.post("/api/whatsapp/send-document", (c) => POST15(c.req.raw));
26689
- app.get("/api/onboarding/claude-auth", () => GET3());
27015
+ app.get("/api/whatsapp/activity", (c) => GET3(c.req.raw));
27016
+ app.get("/api/onboarding/claude-auth", () => GET4());
26690
27017
  app.post("/api/onboarding/claude-auth", (c) => POST17(c.req.raw));
26691
27018
  app.post("/api/onboarding/set-pin", (c) => POST18(c.req.raw));
26692
27019
  app.delete("/api/onboarding/set-pin", (c) => DELETE(c.req.raw));
26693
27020
  app.post("/api/admin/session", (c) => POST19(c.req.raw));
26694
27021
  app.post("/api/admin/chat", (c) => POST20(c.req.raw));
26695
27022
  app.post("/api/admin/compact", (c) => POST21(c.req.raw));
26696
- app.get("/api/admin/logs", (c) => GET4(c.req.raw));
26697
- app.get("/api/admin/claude-info", () => GET5());
27023
+ app.get("/api/admin/logs", (c) => GET5(c.req.raw));
27024
+ app.get("/api/admin/claude-info", () => GET6());
26698
27025
  app.patch("/api/admin/account", (c) => PATCH(c.req.raw));
26699
27026
  app.get(
26700
27027
  "/api/admin/attachment/:attachmentId",
26701
- (c) => GET6(c.req.raw, c.req.param("attachmentId"))
27028
+ (c) => GET7(c.req.raw, c.req.param("attachmentId"))
26702
27029
  );
26703
27030
  app.post("/api/admin/file-attach", (c) => POST16(c.req.raw));
26704
- app.get("/api/admin/version", () => GET8());
27031
+ app.get("/api/admin/version", () => GET9());
26705
27032
  app.post("/api/admin/version/upgrade", (c) => POST22(c.req.raw));
26706
- app.get("/api/admin/agents", () => GET7());
27033
+ app.get("/api/admin/agents", () => GET8());
26707
27034
  app.post("/api/admin/browser/launch", () => POST23());
26708
- app.get("/api/admin/sessions", (c) => GET9(c.req.raw));
27035
+ app.get("/api/admin/sessions", (c) => GET10(c.req.raw));
26709
27036
  app.delete(
26710
27037
  "/api/admin/sessions/:id",
26711
27038
  (c) => DELETE2(c.req.raw, { params: Promise.resolve({ id: c.req.param("id") }) })
26712
27039
  );
26713
27040
  app.get(
26714
27041
  "/api/admin/sessions/:id/messages",
26715
- (c) => GET10(c.req.raw, { params: Promise.resolve({ id: c.req.param("id") }) })
27042
+ (c) => GET11(c.req.raw, { params: Promise.resolve({ id: c.req.param("id") }) })
26716
27043
  );
26717
27044
  var SAFE_SLUG_RE = /^[a-z][a-z0-9-]{2,49}$/;
26718
27045
  var SAFE_FILENAME_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;