@rubytech/create-maxy 1.0.460 → 1.0.461

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-maxy",
3
- "version": "1.0.460",
3
+ "version": "1.0.461",
4
4
  "description": "Install Maxy — AI for Productive People",
5
5
  "bin": {
6
6
  "create-maxy": "./dist/index.js"
@@ -23691,6 +23691,282 @@ var WhatsAppConfigSchema = external_exports.object({
23691
23691
  });
23692
23692
  });
23693
23693
 
23694
+ // app/lib/whatsapp/config-persist.ts
23695
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, existsSync as existsSync10 } from "fs";
23696
+ import { resolve as resolve9, join as join5 } from "path";
23697
+ var TAG4 = "[whatsapp:config]";
23698
+ function configPath(accountDir) {
23699
+ return resolve9(accountDir, "account.json");
23700
+ }
23701
+ function readConfig(accountDir) {
23702
+ const path2 = configPath(accountDir);
23703
+ if (!existsSync10(path2)) throw new Error(`account.json not found at ${path2}`);
23704
+ return JSON.parse(readFileSync10(path2, "utf-8"));
23705
+ }
23706
+ function writeConfig(accountDir, config2) {
23707
+ const path2 = configPath(accountDir);
23708
+ writeFileSync6(path2, JSON.stringify(config2, null, 2) + "\n", "utf-8");
23709
+ }
23710
+ function reloadManagerConfig(accountDir) {
23711
+ try {
23712
+ const config2 = readConfig(accountDir);
23713
+ reloadConfig(config2);
23714
+ console.error(`${TAG4} reloaded manager config`);
23715
+ } catch (err) {
23716
+ console.error(`${TAG4} manager config reload failed: ${String(err)}`);
23717
+ }
23718
+ }
23719
+ var E164_PATTERN = /^\+\d{7,15}$/;
23720
+ function migrateAdminPhones(wa) {
23721
+ if (wa.adminPhones !== void 0) return 0;
23722
+ if (!Array.isArray(wa.allowFrom)) return 0;
23723
+ const allowFrom = wa.allowFrom;
23724
+ const phones = [];
23725
+ const remaining = [];
23726
+ for (const entry of allowFrom) {
23727
+ if (E164_PATTERN.test(entry)) {
23728
+ phones.push(entry);
23729
+ } else {
23730
+ remaining.push(entry);
23731
+ }
23732
+ }
23733
+ if (phones.length === 0) return 0;
23734
+ wa.adminPhones = phones;
23735
+ wa.allowFrom = remaining;
23736
+ console.error(`${TAG4} migrated ${phones.length} phone(s) from allowFrom to adminPhones`);
23737
+ return phones.length;
23738
+ }
23739
+ function persistAfterPairing(accountDir, accountId, selfPhone) {
23740
+ try {
23741
+ const config2 = readConfig(accountDir);
23742
+ if (!config2.whatsapp || typeof config2.whatsapp !== "object") {
23743
+ config2.whatsapp = {};
23744
+ }
23745
+ const wa = config2.whatsapp;
23746
+ if (!wa.accounts || typeof wa.accounts !== "object") {
23747
+ wa.accounts = {};
23748
+ }
23749
+ const accounts = wa.accounts;
23750
+ if (!accounts[accountId] || typeof accounts[accountId] !== "object") {
23751
+ accounts[accountId] = { name: "Main" };
23752
+ }
23753
+ if (selfPhone) {
23754
+ const normalized = selfPhone.startsWith("+") ? selfPhone : `+${selfPhone}`;
23755
+ if (!Array.isArray(wa.adminPhones)) {
23756
+ wa.adminPhones = [];
23757
+ }
23758
+ const adminPhones = wa.adminPhones;
23759
+ if (!adminPhones.includes(normalized)) {
23760
+ adminPhones.push(normalized);
23761
+ console.error(`${TAG4} added selfPhone=${normalized} to adminPhones`);
23762
+ }
23763
+ } else {
23764
+ console.error(`${TAG4} skipping adminPhones \u2014 selfPhone is null account=${accountId}`);
23765
+ }
23766
+ const parsed = WhatsAppConfigSchema.safeParse(wa);
23767
+ if (!parsed.success) {
23768
+ const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
23769
+ console.error(`${TAG4} validation failed after pairing: ${msg}`);
23770
+ return { ok: false, error: `Validation failed: ${msg}` };
23771
+ }
23772
+ config2.whatsapp = parsed.data;
23773
+ writeConfig(accountDir, config2);
23774
+ console.error(`${TAG4} persisted after pairing account=${accountId} phone=${selfPhone ?? "null"}`);
23775
+ reloadManagerConfig(accountDir);
23776
+ return { ok: true };
23777
+ } catch (err) {
23778
+ const msg = err instanceof Error ? err.message : String(err);
23779
+ console.error(`${TAG4} persist failed account=${accountId}: ${msg}`);
23780
+ return { ok: false, error: msg };
23781
+ }
23782
+ }
23783
+ function addAdminPhone(accountDir, phone) {
23784
+ const normalized = phone.trim();
23785
+ if (!E164_PATTERN.test(normalized)) {
23786
+ return { ok: false, error: `Invalid phone format "${normalized}". Expected E.164 (e.g. +441234567890).` };
23787
+ }
23788
+ try {
23789
+ const config2 = readConfig(accountDir);
23790
+ if (!config2.whatsapp || typeof config2.whatsapp !== "object") {
23791
+ config2.whatsapp = {};
23792
+ }
23793
+ const wa = config2.whatsapp;
23794
+ migrateAdminPhones(wa);
23795
+ if (!Array.isArray(wa.adminPhones)) {
23796
+ wa.adminPhones = [];
23797
+ }
23798
+ const adminPhones = wa.adminPhones;
23799
+ if (adminPhones.includes(normalized)) {
23800
+ return { ok: true, message: `Phone ${normalized} is already in the admin list.` };
23801
+ }
23802
+ adminPhones.push(normalized);
23803
+ const parsed = WhatsAppConfigSchema.safeParse(wa);
23804
+ if (!parsed.success) {
23805
+ const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
23806
+ return { ok: false, error: `Validation failed: ${msg}` };
23807
+ }
23808
+ config2.whatsapp = parsed.data;
23809
+ writeConfig(accountDir, config2);
23810
+ console.error(`${TAG4} added admin phone=${normalized}`);
23811
+ reloadManagerConfig(accountDir);
23812
+ return { ok: true, message: `Added ${normalized} as admin phone. Messages from this number will route to the admin agent.` };
23813
+ } catch (err) {
23814
+ const msg = err instanceof Error ? err.message : String(err);
23815
+ console.error(`${TAG4} addAdminPhone failed: ${msg}`);
23816
+ return { ok: false, error: msg };
23817
+ }
23818
+ }
23819
+ function removeAdminPhone(accountDir, phone) {
23820
+ const normalized = phone.trim();
23821
+ try {
23822
+ const config2 = readConfig(accountDir);
23823
+ if (!config2.whatsapp || typeof config2.whatsapp !== "object") {
23824
+ return { ok: true, message: `No WhatsApp config exists \u2014 nothing to remove.` };
23825
+ }
23826
+ const wa = config2.whatsapp;
23827
+ migrateAdminPhones(wa);
23828
+ if (!Array.isArray(wa.adminPhones)) {
23829
+ return { ok: true, message: `No admin phones configured \u2014 nothing to remove.` };
23830
+ }
23831
+ const adminPhones = wa.adminPhones;
23832
+ const idx = adminPhones.indexOf(normalized);
23833
+ if (idx === -1) {
23834
+ return { ok: true, message: `Phone ${normalized} is not in the admin list.` };
23835
+ }
23836
+ adminPhones.splice(idx, 1);
23837
+ const parsed = WhatsAppConfigSchema.safeParse(wa);
23838
+ if (!parsed.success) {
23839
+ const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
23840
+ return { ok: false, error: `Validation failed: ${msg}` };
23841
+ }
23842
+ config2.whatsapp = parsed.data;
23843
+ writeConfig(accountDir, config2);
23844
+ console.error(`${TAG4} removed admin phone=${normalized}`);
23845
+ reloadManagerConfig(accountDir);
23846
+ return { ok: true, message: `Removed ${normalized} from admin phones. Messages from this number will now route to the public agent.` };
23847
+ } catch (err) {
23848
+ const msg = err instanceof Error ? err.message : String(err);
23849
+ console.error(`${TAG4} removeAdminPhone failed: ${msg}`);
23850
+ return { ok: false, error: msg };
23851
+ }
23852
+ }
23853
+ function readAdminPhones(accountDir) {
23854
+ try {
23855
+ const config2 = readConfig(accountDir);
23856
+ const wa = config2.whatsapp;
23857
+ if (!wa) return [];
23858
+ const migrated = migrateAdminPhones(wa);
23859
+ if (migrated > 0) {
23860
+ const parsed = WhatsAppConfigSchema.safeParse(wa);
23861
+ if (parsed.success) {
23862
+ config2.whatsapp = parsed.data;
23863
+ writeConfig(accountDir, config2);
23864
+ reloadManagerConfig(accountDir);
23865
+ }
23866
+ }
23867
+ if (!Array.isArray(wa.adminPhones)) return [];
23868
+ return wa.adminPhones.filter((p) => typeof p === "string");
23869
+ } catch {
23870
+ return [];
23871
+ }
23872
+ }
23873
+ function setPublicAgent(accountDir, slug) {
23874
+ const trimmed = slug.trim();
23875
+ if (!trimmed) {
23876
+ return { ok: false, error: "Agent slug cannot be empty." };
23877
+ }
23878
+ const agentConfigPath = join5(accountDir, "agents", trimmed, "config.json");
23879
+ if (!existsSync10(agentConfigPath)) {
23880
+ return { ok: false, error: `Agent "${trimmed}" not found \u2014 no config.json at ${agentConfigPath}. Check the agent slug and try again.` };
23881
+ }
23882
+ try {
23883
+ const config2 = readConfig(accountDir);
23884
+ if (!config2.whatsapp || typeof config2.whatsapp !== "object") {
23885
+ config2.whatsapp = {};
23886
+ }
23887
+ const wa = config2.whatsapp;
23888
+ wa.publicAgent = trimmed;
23889
+ const parsed = WhatsAppConfigSchema.safeParse(wa);
23890
+ if (!parsed.success) {
23891
+ const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
23892
+ return { ok: false, error: `Validation failed: ${msg}` };
23893
+ }
23894
+ config2.whatsapp = parsed.data;
23895
+ writeConfig(accountDir, config2);
23896
+ console.error(`${TAG4} publicAgent set to ${trimmed}`);
23897
+ reloadManagerConfig(accountDir);
23898
+ return { ok: true, message: `Public agent set to "${trimmed}". WhatsApp messages from non-admin phones will be handled by this agent.` };
23899
+ } catch (err) {
23900
+ const msg = err instanceof Error ? err.message : String(err);
23901
+ console.error(`${TAG4} setPublicAgent failed: ${msg}`);
23902
+ return { ok: false, error: msg };
23903
+ }
23904
+ }
23905
+ function getPublicAgent(accountDir) {
23906
+ try {
23907
+ const config2 = readConfig(accountDir);
23908
+ const wa = config2.whatsapp;
23909
+ if (!wa) return null;
23910
+ const slug = wa.publicAgent;
23911
+ if (typeof slug === "string" && slug.trim()) {
23912
+ return slug.trim();
23913
+ }
23914
+ return null;
23915
+ } catch {
23916
+ return null;
23917
+ }
23918
+ }
23919
+ function updateConfig(accountDir, fields) {
23920
+ const fieldNames = Object.keys(fields);
23921
+ if (fieldNames.length === 0) {
23922
+ return { ok: false, error: "No fields provided to update." };
23923
+ }
23924
+ try {
23925
+ const config2 = readConfig(accountDir);
23926
+ if (!config2.whatsapp || typeof config2.whatsapp !== "object") {
23927
+ config2.whatsapp = {};
23928
+ }
23929
+ const wa = config2.whatsapp;
23930
+ migrateAdminPhones(wa);
23931
+ for (const [key, value] of Object.entries(fields)) {
23932
+ wa[key] = value;
23933
+ }
23934
+ const parsed = WhatsAppConfigSchema.safeParse(wa);
23935
+ if (!parsed.success) {
23936
+ const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
23937
+ console.error(`${TAG4} update validation failed: ${msg}`);
23938
+ return { ok: false, error: `Validation failed: ${msg}` };
23939
+ }
23940
+ config2.whatsapp = parsed.data;
23941
+ writeConfig(accountDir, config2);
23942
+ console.error(`${TAG4} updated fields=[${fieldNames.join(",")}]`);
23943
+ reloadManagerConfig(accountDir);
23944
+ return { ok: true, message: `Updated WhatsApp config: ${fieldNames.join(", ")}.` };
23945
+ } catch (err) {
23946
+ const msg = err instanceof Error ? err.message : String(err);
23947
+ console.error(`${TAG4} updateConfig failed: ${msg}`);
23948
+ return { ok: false, error: msg };
23949
+ }
23950
+ }
23951
+ function getConfig(accountDir) {
23952
+ try {
23953
+ const config2 = readConfig(accountDir);
23954
+ const wa = config2.whatsapp;
23955
+ if (!wa) return null;
23956
+ const migrated = migrateAdminPhones(wa);
23957
+ const parsed = WhatsAppConfigSchema.safeParse(wa);
23958
+ if (!parsed.success) return wa;
23959
+ if (migrated > 0) {
23960
+ config2.whatsapp = parsed.data;
23961
+ writeConfig(accountDir, config2);
23962
+ reloadManagerConfig(accountDir);
23963
+ }
23964
+ return parsed.data;
23965
+ } catch {
23966
+ return null;
23967
+ }
23968
+ }
23969
+
23694
23970
  // app/lib/whatsapp/normalize.ts
23695
23971
  var WHATSAPP_USER_JID_RE = /^(\d+)(?::\d+)?@s\.whatsapp\.net$/i;
23696
23972
  var WHATSAPP_LID_RE = /^(\d+)@lid$/i;
@@ -23976,7 +24252,7 @@ function isDuplicateInbound(key) {
23976
24252
  }
23977
24253
 
23978
24254
  // app/lib/whatsapp/outbound/send.ts
23979
- var TAG4 = "[whatsapp:outbound]";
24255
+ var TAG5 = "[whatsapp:outbound]";
23980
24256
  async function sendTextMessage(sock, to, text, opts) {
23981
24257
  try {
23982
24258
  const jid = to.includes("@") ? to : toWhatsappJid(to);
@@ -23988,11 +24264,11 @@ async function sendTextMessage(sock, to, text, opts) {
23988
24264
  const messageId = result?.key?.id;
23989
24265
  if (messageId) {
23990
24266
  trackAgentSentMessage(messageId);
23991
- console.error(`${TAG4} sent text to=${jid} id=${messageId}`);
24267
+ console.error(`${TAG5} sent text to=${jid} id=${messageId}`);
23992
24268
  }
23993
24269
  return { success: true, messageId: messageId ?? void 0 };
23994
24270
  } catch (err) {
23995
- console.error(`${TAG4} send failed to=${to}: ${String(err)}`);
24271
+ console.error(`${TAG5} send failed to=${to}: ${String(err)}`);
23996
24272
  return { success: false, error: String(err) };
23997
24273
  }
23998
24274
  }
@@ -24012,13 +24288,13 @@ async function sendReadReceipt(sock, chatJid, messageIds, participant) {
24012
24288
  // app/lib/whatsapp/inbound/media.ts
24013
24289
  import { randomUUID as randomUUID6 } from "crypto";
24014
24290
  import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
24015
- import { join as join5 } from "path";
24291
+ import { join as join6 } from "path";
24016
24292
  import {
24017
24293
  downloadMediaMessage,
24018
24294
  downloadContentFromMessage,
24019
24295
  normalizeMessageContent as normalizeMessageContent2
24020
24296
  } from "@whiskeysockets/baileys";
24021
- var TAG5 = "[whatsapp:media]";
24297
+ var TAG6 = "[whatsapp:media]";
24022
24298
  var MEDIA_DIR = "/tmp/maxy-media";
24023
24299
  function mimeToExt(mimetype) {
24024
24300
  const map2 = {
@@ -24074,47 +24350,47 @@ async function downloadInboundMedia(msg, sock, opts) {
24074
24350
  }
24075
24351
  );
24076
24352
  if (!buffer || buffer.length === 0) {
24077
- console.error(`${TAG5} primary download returned empty, trying direct fallback`);
24353
+ console.error(`${TAG6} primary download returned empty, trying direct fallback`);
24078
24354
  const downloadable = getDownloadableContent(content);
24079
24355
  if (downloadable) {
24080
24356
  try {
24081
24357
  const stream = await downloadContentFromMessage(downloadable.downloadable, downloadable.mediaType);
24082
24358
  buffer = await streamToBuffer(stream);
24083
24359
  } catch (fallbackErr) {
24084
- console.error(`${TAG5} direct download fallback failed: ${String(fallbackErr)}`);
24360
+ console.error(`${TAG6} direct download fallback failed: ${String(fallbackErr)}`);
24085
24361
  }
24086
24362
  }
24087
24363
  }
24088
24364
  if (!buffer || buffer.length === 0) {
24089
- console.error(`${TAG5} download failed: empty buffer for ${mimetype ?? "unknown"}`);
24365
+ console.error(`${TAG6} download failed: empty buffer for ${mimetype ?? "unknown"}`);
24090
24366
  return void 0;
24091
24367
  }
24092
24368
  if (buffer.length > maxBytes) {
24093
24369
  const sizeMB = (buffer.length / (1024 * 1024)).toFixed(1);
24094
24370
  const limitMB = (maxBytes / (1024 * 1024)).toFixed(0);
24095
- console.error(`${TAG5} media too large type=${mimetype ?? "unknown"} size=${sizeMB}MB limit=${limitMB}MB`);
24371
+ console.error(`${TAG6} media too large type=${mimetype ?? "unknown"} size=${sizeMB}MB limit=${limitMB}MB`);
24096
24372
  return void 0;
24097
24373
  }
24098
24374
  await mkdir2(MEDIA_DIR, { recursive: true });
24099
24375
  const ext = mimeToExt(mimetype ?? "application/octet-stream");
24100
24376
  const filename = `${randomUUID6()}.${ext}`;
24101
- const filePath = join5(MEDIA_DIR, filename);
24377
+ const filePath = join6(MEDIA_DIR, filename);
24102
24378
  await writeFile2(filePath, buffer);
24103
24379
  const sizeKB = (buffer.length / 1024).toFixed(0);
24104
- console.error(`${TAG5} media downloaded type=${mimetype ?? "unknown"} size=${sizeKB}KB path=${filePath}`);
24380
+ console.error(`${TAG6} media downloaded type=${mimetype ?? "unknown"} size=${sizeKB}KB path=${filePath}`);
24105
24381
  return {
24106
24382
  path: filePath,
24107
24383
  mimetype: mimetype ?? "application/octet-stream",
24108
24384
  size: buffer.length
24109
24385
  };
24110
24386
  } catch (err) {
24111
- console.error(`${TAG5} media download failed type=${mimetype ?? "unknown"} error=${String(err)}`);
24387
+ console.error(`${TAG6} media download failed type=${mimetype ?? "unknown"} error=${String(err)}`);
24112
24388
  return void 0;
24113
24389
  }
24114
24390
  }
24115
24391
 
24116
24392
  // app/lib/whatsapp/inbound/debounce.ts
24117
- var TAG6 = "[whatsapp:debounce]";
24393
+ var TAG7 = "[whatsapp:debounce]";
24118
24394
  function createInboundDebouncer(opts) {
24119
24395
  const { debounceMs, buildKey, onFlush, onError } = opts;
24120
24396
  const pending = /* @__PURE__ */ new Map();
@@ -24126,7 +24402,7 @@ function createInboundDebouncer(opts) {
24126
24402
  pending.delete(key);
24127
24403
  const batchSize = batch.entries.length;
24128
24404
  try {
24129
- console.error(`${TAG6} debounce flush key=${key} batchSize=${batchSize}`);
24405
+ console.error(`${TAG7} debounce flush key=${key} batchSize=${batchSize}`);
24130
24406
  const result = onFlush(batch.entries);
24131
24407
  if (result && typeof result.catch === "function") {
24132
24408
  result.catch(onError);
@@ -24184,7 +24460,7 @@ function createInboundDebouncer(opts) {
24184
24460
  }
24185
24461
 
24186
24462
  // app/lib/whatsapp/manager.ts
24187
- var TAG7 = "[whatsapp:manager]";
24463
+ var TAG8 = "[whatsapp:manager]";
24188
24464
  var MAX_RECONNECT_ATTEMPTS = 10;
24189
24465
  var connections = /* @__PURE__ */ new Map();
24190
24466
  var configDir = null;
@@ -24193,7 +24469,7 @@ var onInboundMessage = null;
24193
24469
  var initialized = false;
24194
24470
  async function init(opts) {
24195
24471
  if (initialized) {
24196
- console.error(`${TAG7} already initialized`);
24472
+ console.error(`${TAG8} already initialized`);
24197
24473
  return;
24198
24474
  }
24199
24475
  configDir = opts.configDir;
@@ -24201,20 +24477,20 @@ async function init(opts) {
24201
24477
  loadConfig(opts.accountConfig);
24202
24478
  const accountIds = listCredentialAccountIds(configDir);
24203
24479
  if (accountIds.length === 0) {
24204
- console.error(`${TAG7} init: no stored WhatsApp credentials found`);
24480
+ console.error(`${TAG8} init: no stored WhatsApp credentials found`);
24205
24481
  initialized = true;
24206
24482
  return;
24207
24483
  }
24208
- console.error(`${TAG7} init: found ${accountIds.length} credentialed account(s): ${accountIds.join(", ")}`);
24484
+ console.error(`${TAG8} init: found ${accountIds.length} credentialed account(s): ${accountIds.join(", ")}`);
24209
24485
  initialized = true;
24210
24486
  for (const accountId of accountIds) {
24211
24487
  const accountCfg = whatsAppConfig.accounts?.[accountId];
24212
24488
  if (accountCfg?.enabled === false) {
24213
- console.error(`${TAG7} skipping disabled account=${accountId}`);
24489
+ console.error(`${TAG8} skipping disabled account=${accountId}`);
24214
24490
  continue;
24215
24491
  }
24216
24492
  startConnection(accountId).catch((err) => {
24217
- console.error(`${TAG7} failed to auto-start account=${accountId}: ${formatError(err)}`);
24493
+ console.error(`${TAG8} failed to auto-start account=${accountId}: ${formatError(err)}`);
24218
24494
  });
24219
24495
  }
24220
24496
  }
@@ -24223,7 +24499,7 @@ async function startConnection(accountId) {
24223
24499
  const authDir = resolveAuthDir(configDir, accountId);
24224
24500
  const hasAuth = await authExists(authDir);
24225
24501
  if (!hasAuth) {
24226
- console.error(`${TAG7} no credentials for account=${accountId}`);
24502
+ console.error(`${TAG8} no credentials for account=${accountId}`);
24227
24503
  return;
24228
24504
  }
24229
24505
  await stopConnection(accountId);
@@ -24258,11 +24534,11 @@ async function stopConnection(accountId) {
24258
24534
  conn.sock.ev.removeAllListeners("creds.update");
24259
24535
  conn.sock.ws?.close?.();
24260
24536
  } catch (err) {
24261
- console.warn(`${TAG7} socket cleanup error during stop account=${accountId}: ${String(err)}`);
24537
+ console.warn(`${TAG8} socket cleanup error during stop account=${accountId}: ${String(err)}`);
24262
24538
  }
24263
24539
  }
24264
24540
  connections.delete(accountId);
24265
- console.error(`${TAG7} stopped account=${accountId}`);
24541
+ console.error(`${TAG8} stopped account=${accountId}`);
24266
24542
  }
24267
24543
  function getStatus() {
24268
24544
  return Array.from(connections.values()).map((conn) => ({
@@ -24301,13 +24577,13 @@ async function registerLoginSocket(accountId, sock, authDir) {
24301
24577
  connections.set(accountId, conn);
24302
24578
  try {
24303
24579
  await sock.sendPresenceUpdate("available");
24304
- console.error(`${TAG7} presence set to available account=${accountId}`);
24580
+ console.error(`${TAG8} presence set to available account=${accountId}`);
24305
24581
  } catch (err) {
24306
- console.error(`${TAG7} presence update failed account=${accountId}: ${String(err)}`);
24582
+ console.error(`${TAG8} presence update failed account=${accountId}: ${String(err)}`);
24307
24583
  }
24308
24584
  monitorInbound(conn);
24309
24585
  watchForDisconnect(conn);
24310
- console.error(`${TAG7} registered login socket for account=${accountId} phone=${selfId.e164 ?? "unknown"}`);
24586
+ console.error(`${TAG8} registered login socket for account=${accountId} phone=${selfId.e164 ?? "unknown"}`);
24311
24587
  }
24312
24588
  function reloadConfig(accountConfig) {
24313
24589
  loadConfig(accountConfig);
@@ -24317,22 +24593,24 @@ async function shutdown() {
24317
24593
  const ids = Array.from(connections.keys());
24318
24594
  await Promise.all(ids.map((id) => stopConnection(id)));
24319
24595
  initialized = false;
24320
- console.error(`${TAG7} shutdown complete`);
24596
+ console.error(`${TAG8} shutdown complete`);
24321
24597
  }
24322
24598
  function loadConfig(accountConfig) {
24323
24599
  try {
24324
24600
  const raw2 = accountConfig?.whatsapp;
24325
24601
  if (raw2 && typeof raw2 === "object") {
24326
- const parsed = WhatsAppConfigSchema.safeParse(raw2);
24602
+ const wa = raw2;
24603
+ migrateAdminPhones(wa);
24604
+ const parsed = WhatsAppConfigSchema.safeParse(wa);
24327
24605
  if (parsed.success) {
24328
24606
  whatsAppConfig = parsed.data;
24329
24607
  } else {
24330
- console.error(`${TAG7} config validation failed: ${parsed.error.message}`);
24608
+ console.error(`${TAG8} config validation failed: ${parsed.error.message}`);
24331
24609
  whatsAppConfig = {};
24332
24610
  }
24333
24611
  }
24334
24612
  } catch (err) {
24335
- console.error(`${TAG7} config load error: ${String(err)}`);
24613
+ console.error(`${TAG8} config load error: ${String(err)}`);
24336
24614
  whatsAppConfig = {};
24337
24615
  }
24338
24616
  }
@@ -24340,13 +24618,13 @@ async function connectWithReconnect(conn) {
24340
24618
  const maxAttempts = MAX_RECONNECT_ATTEMPTS;
24341
24619
  while (!conn.abortController.signal.aborted) {
24342
24620
  try {
24343
- console.error(`${TAG7} connecting account=${conn.accountId} attempt=${conn.reconnectAttempts}`);
24621
+ console.error(`${TAG8} connecting account=${conn.accountId} attempt=${conn.reconnectAttempts}`);
24344
24622
  const sock = await createWaSocket({
24345
24623
  authDir: conn.authDir,
24346
24624
  silent: true
24347
24625
  });
24348
24626
  conn.sock = sock;
24349
- console.error(`${TAG7} socket created account=${conn.accountId} \u2014 waiting for connection`);
24627
+ console.error(`${TAG8} socket created account=${conn.accountId} \u2014 waiting for connection`);
24350
24628
  await waitForConnection(sock);
24351
24629
  const selfId = readSelfId(conn.authDir);
24352
24630
  conn.connected = true;
@@ -24356,12 +24634,12 @@ async function connectWithReconnect(conn) {
24356
24634
  conn.reconnectAttempts = 0;
24357
24635
  conn.lastError = void 0;
24358
24636
  conn.lidMapping = sock.signalRepository?.lidMapping ?? null;
24359
- console.error(`${TAG7} connected account=${conn.accountId} phone=${selfId.e164 ?? "unknown"}`);
24637
+ console.error(`${TAG8} connected account=${conn.accountId} phone=${selfId.e164 ?? "unknown"}`);
24360
24638
  try {
24361
24639
  await sock.sendPresenceUpdate("available");
24362
- console.error(`${TAG7} presence set to available account=${conn.accountId}`);
24640
+ console.error(`${TAG8} presence set to available account=${conn.accountId}`);
24363
24641
  } catch (err) {
24364
- console.error(`${TAG7} presence update failed account=${conn.accountId}: ${String(err)}`);
24642
+ console.error(`${TAG8} presence update failed account=${conn.accountId}: ${String(err)}`);
24365
24643
  }
24366
24644
  if (conn.debouncer) {
24367
24645
  conn.debouncer.destroy();
@@ -24376,19 +24654,19 @@ async function connectWithReconnect(conn) {
24376
24654
  conn.sock = null;
24377
24655
  const classification = classifyDisconnect(err);
24378
24656
  conn.lastError = classification.message;
24379
- console.error(`${TAG7} disconnect account=${conn.accountId}: ${classification.kind} \u2014 ${classification.message}`);
24657
+ console.error(`${TAG8} disconnect account=${conn.accountId}: ${classification.kind} \u2014 ${classification.message}`);
24380
24658
  if (!classification.shouldRetry) {
24381
- console.error(`${TAG7} terminal disconnect for account=${conn.accountId}, stopping reconnection`);
24659
+ console.error(`${TAG8} terminal disconnect for account=${conn.accountId}, stopping reconnection`);
24382
24660
  return;
24383
24661
  }
24384
24662
  conn.reconnectAttempts++;
24385
24663
  if (conn.reconnectAttempts > maxAttempts) {
24386
- console.error(`${TAG7} max retries exceeded for account=${conn.accountId}`);
24664
+ console.error(`${TAG8} max retries exceeded for account=${conn.accountId}`);
24387
24665
  conn.lastError = `Max reconnect attempts (${maxAttempts}) exceeded`;
24388
24666
  return;
24389
24667
  }
24390
24668
  const delay = computeBackoff(conn.reconnectAttempts);
24391
- console.error(`${TAG7} reconnecting account=${conn.accountId} in ${delay}ms (attempt ${conn.reconnectAttempts}/${maxAttempts})`);
24669
+ console.error(`${TAG8} reconnecting account=${conn.accountId} in ${delay}ms (attempt ${conn.reconnectAttempts}/${maxAttempts})`);
24392
24670
  await new Promise((resolve17) => {
24393
24671
  const timer = setTimeout(resolve17, delay);
24394
24672
  conn.abortController.signal.addEventListener("abort", () => {
@@ -24418,11 +24696,11 @@ function watchForDisconnect(conn) {
24418
24696
  conn.sock.ev.on("connection.update", (update) => {
24419
24697
  if (update.connection === "close") {
24420
24698
  if (connections.get(conn.accountId) !== conn) return;
24421
- console.error(`${TAG7} socket disconnected for account=${conn.accountId}`);
24699
+ console.error(`${TAG8} socket disconnected for account=${conn.accountId}`);
24422
24700
  conn.connected = false;
24423
24701
  conn.sock = null;
24424
24702
  connectWithReconnect(conn).catch((err) => {
24425
- console.error(`${TAG7} reconnection failed for account=${conn.accountId}: ${formatError(err)}`);
24703
+ console.error(`${TAG8} reconnection failed for account=${conn.accountId}: ${formatError(err)}`);
24426
24704
  });
24427
24705
  }
24428
24706
  });
@@ -24431,7 +24709,7 @@ function monitorInbound(conn) {
24431
24709
  if (!conn.sock || !onInboundMessage) return;
24432
24710
  const sock = conn.sock;
24433
24711
  const debounceMs = whatsAppConfig.accounts?.[conn.accountId]?.debounceMs ?? whatsAppConfig.debounceMs ?? 0;
24434
- console.error(`${TAG7} monitorInbound started account=${conn.accountId} debounceMs=${debounceMs}`);
24712
+ console.error(`${TAG8} monitorInbound started account=${conn.accountId} debounceMs=${debounceMs}`);
24435
24713
  conn.debouncer = createInboundDebouncer({
24436
24714
  debounceMs,
24437
24715
  buildKey: (payload) => {
@@ -24444,7 +24722,7 @@ function monitorInbound(conn) {
24444
24722
  onInboundMessage(entries[0]);
24445
24723
  return;
24446
24724
  }
24447
- console.error(`${TAG7} debounce: combining ${entries.length} messages account=${conn.accountId} from=${entries[0].senderPhone}`);
24725
+ console.error(`${TAG8} debounce: combining ${entries.length} messages account=${conn.accountId} from=${entries[0].senderPhone}`);
24448
24726
  const last = entries[entries.length - 1];
24449
24727
  const mediaEntry = entries.find((e) => e.mediaPath);
24450
24728
  const combinedText = entries.map((e) => e.text).filter(Boolean).join("\n");
@@ -24457,7 +24735,7 @@ function monitorInbound(conn) {
24457
24735
  });
24458
24736
  },
24459
24737
  onError: (err) => {
24460
- console.error(`${TAG7} debounce flush error account=${conn.accountId}: ${String(err)}`);
24738
+ console.error(`${TAG8} debounce flush error account=${conn.accountId}: ${String(err)}`);
24461
24739
  }
24462
24740
  });
24463
24741
  sock.ev.on("messages.upsert", async (upsert) => {
@@ -24466,7 +24744,7 @@ function monitorInbound(conn) {
24466
24744
  try {
24467
24745
  await handleInboundMessage(conn, msg);
24468
24746
  } catch (err) {
24469
- console.error(`${TAG7} inbound handler error account=${conn.accountId}: ${String(err)}`);
24747
+ console.error(`${TAG8} inbound handler error account=${conn.accountId}: ${String(err)}`);
24470
24748
  }
24471
24749
  }
24472
24750
  });
@@ -24476,31 +24754,31 @@ async function handleInboundMessage(conn, msg) {
24476
24754
  const remoteJid = msg.key.remoteJid;
24477
24755
  if (!remoteJid) return;
24478
24756
  if (remoteJid === "status@broadcast") {
24479
- console.error(`${TAG7} drop: status broadcast account=${conn.accountId}`);
24757
+ console.error(`${TAG8} drop: status broadcast account=${conn.accountId}`);
24480
24758
  return;
24481
24759
  }
24482
24760
  if (!msg.message) {
24483
- console.error(`${TAG7} drop: empty message account=${conn.accountId} from=${remoteJid}`);
24761
+ console.error(`${TAG8} drop: empty message account=${conn.accountId} from=${remoteJid}`);
24484
24762
  return;
24485
24763
  }
24486
24764
  const dedupKey = `${conn.accountId}:${remoteJid}:${msg.key.id}`;
24487
24765
  if (isDuplicateInbound(dedupKey)) {
24488
- console.error(`${TAG7} drop: duplicate account=${conn.accountId} key=${dedupKey}`);
24766
+ console.error(`${TAG8} drop: duplicate account=${conn.accountId} key=${dedupKey}`);
24489
24767
  return;
24490
24768
  }
24491
24769
  if (msg.key.fromMe) {
24492
24770
  if (msg.key.id && isAgentSentMessage(msg.key.id)) {
24493
- console.error(`${TAG7} drop: echo suppression account=${conn.accountId} msgId=${msg.key.id}`);
24771
+ console.error(`${TAG8} drop: echo suppression account=${conn.accountId} msgId=${msg.key.id}`);
24494
24772
  return;
24495
24773
  }
24496
24774
  const extracted2 = extractMessage(msg);
24497
24775
  if (!extracted2.text) {
24498
- console.error(`${TAG7} owner reply skipped \u2014 no text content account=${conn.accountId}`);
24776
+ console.error(`${TAG8} owner reply skipped \u2014 no text content account=${conn.accountId}`);
24499
24777
  return;
24500
24778
  }
24501
24779
  const isGroup2 = isGroupJid(remoteJid);
24502
24780
  const senderPhone2 = conn.selfPhone ?? "owner";
24503
- console.error(`${TAG7} owner reply mirrored to session from=${senderPhone2} account=${conn.accountId}`);
24781
+ console.error(`${TAG8} owner reply mirrored to session from=${senderPhone2} account=${conn.accountId}`);
24504
24782
  const reply2 = async (text) => {
24505
24783
  const currentSock = conn.sock;
24506
24784
  if (!currentSock) throw new Error("WhatsApp disconnected \u2014 cannot reply");
@@ -24521,7 +24799,7 @@ async function handleInboundMessage(conn, msg) {
24521
24799
  }
24522
24800
  const extracted = extractMessage(msg);
24523
24801
  if (!extracted.text && !extracted.mediaType) {
24524
- console.error(`${TAG7} drop: no text or media account=${conn.accountId} from=${remoteJid}`);
24802
+ console.error(`${TAG8} drop: no text or media account=${conn.accountId} from=${remoteJid}`);
24525
24803
  return;
24526
24804
  }
24527
24805
  let mediaResult;
@@ -24531,7 +24809,7 @@ async function handleInboundMessage(conn, msg) {
24531
24809
  maxBytes: maxMb * 1024 * 1024
24532
24810
  });
24533
24811
  if (!mediaResult) {
24534
- console.error(`${TAG7} media download returned undefined account=${conn.accountId} type=${extracted.mediaType} from=${remoteJid}`);
24812
+ console.error(`${TAG8} media download returned undefined account=${conn.accountId} type=${extracted.mediaType} from=${remoteJid}`);
24535
24813
  }
24536
24814
  }
24537
24815
  const isGroup = isGroupJid(remoteJid);
@@ -24559,7 +24837,7 @@ async function handleInboundMessage(conn, msg) {
24559
24837
  });
24560
24838
  }
24561
24839
  console.error(
24562
- `${TAG7} 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}` : "")
24840
+ `${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}` : "")
24563
24841
  );
24564
24842
  if (!accessResult.allowed) return;
24565
24843
  const sendReceipts = whatsAppConfig.accounts?.[conn.accountId]?.sendReadReceipts ?? whatsAppConfig.sendReadReceipts ?? true;
@@ -24594,273 +24872,6 @@ async function handleInboundMessage(conn, msg) {
24594
24872
  }
24595
24873
  }
24596
24874
 
24597
- // app/lib/whatsapp/config-persist.ts
24598
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, existsSync as existsSync10 } from "fs";
24599
- import { resolve as resolve9, join as join6 } from "path";
24600
- var TAG8 = "[whatsapp:config]";
24601
- function configPath(accountDir) {
24602
- return resolve9(accountDir, "account.json");
24603
- }
24604
- function readConfig(accountDir) {
24605
- const path2 = configPath(accountDir);
24606
- if (!existsSync10(path2)) throw new Error(`account.json not found at ${path2}`);
24607
- return JSON.parse(readFileSync10(path2, "utf-8"));
24608
- }
24609
- function writeConfig(accountDir, config2) {
24610
- const path2 = configPath(accountDir);
24611
- writeFileSync6(path2, JSON.stringify(config2, null, 2) + "\n", "utf-8");
24612
- }
24613
- function reloadManagerConfig(accountDir) {
24614
- try {
24615
- const config2 = readConfig(accountDir);
24616
- reloadConfig(config2);
24617
- console.error(`${TAG8} reloaded manager config`);
24618
- } catch (err) {
24619
- console.error(`${TAG8} manager config reload failed: ${String(err)}`);
24620
- }
24621
- }
24622
- var E164_PATTERN = /^\+\d{7,15}$/;
24623
- function migrateAdminPhones(wa) {
24624
- if (wa.adminPhones !== void 0) return 0;
24625
- if (!Array.isArray(wa.allowFrom)) return 0;
24626
- const allowFrom = wa.allowFrom;
24627
- const phones = [];
24628
- const remaining = [];
24629
- for (const entry of allowFrom) {
24630
- if (E164_PATTERN.test(entry)) {
24631
- phones.push(entry);
24632
- } else {
24633
- remaining.push(entry);
24634
- }
24635
- }
24636
- if (phones.length === 0) return 0;
24637
- wa.adminPhones = phones;
24638
- wa.allowFrom = remaining;
24639
- console.error(`${TAG8} migrated ${phones.length} phone(s) from allowFrom to adminPhones`);
24640
- return phones.length;
24641
- }
24642
- function persistAfterPairing(accountDir, accountId, selfPhone) {
24643
- try {
24644
- const config2 = readConfig(accountDir);
24645
- if (!config2.whatsapp || typeof config2.whatsapp !== "object") {
24646
- config2.whatsapp = {};
24647
- }
24648
- const wa = config2.whatsapp;
24649
- if (!wa.accounts || typeof wa.accounts !== "object") {
24650
- wa.accounts = {};
24651
- }
24652
- const accounts = wa.accounts;
24653
- if (!accounts[accountId] || typeof accounts[accountId] !== "object") {
24654
- accounts[accountId] = { name: "Main" };
24655
- }
24656
- if (selfPhone) {
24657
- const normalized = selfPhone.startsWith("+") ? selfPhone : `+${selfPhone}`;
24658
- if (!Array.isArray(wa.adminPhones)) {
24659
- wa.adminPhones = [];
24660
- }
24661
- const adminPhones = wa.adminPhones;
24662
- if (!adminPhones.includes(normalized)) {
24663
- adminPhones.push(normalized);
24664
- console.error(`${TAG8} added selfPhone=${normalized} to adminPhones`);
24665
- }
24666
- } else {
24667
- console.error(`${TAG8} skipping adminPhones \u2014 selfPhone is null account=${accountId}`);
24668
- }
24669
- const parsed = WhatsAppConfigSchema.safeParse(wa);
24670
- if (!parsed.success) {
24671
- const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
24672
- console.error(`${TAG8} validation failed after pairing: ${msg}`);
24673
- return { ok: false, error: `Validation failed: ${msg}` };
24674
- }
24675
- config2.whatsapp = parsed.data;
24676
- writeConfig(accountDir, config2);
24677
- console.error(`${TAG8} persisted after pairing account=${accountId} phone=${selfPhone ?? "null"}`);
24678
- reloadManagerConfig(accountDir);
24679
- return { ok: true };
24680
- } catch (err) {
24681
- const msg = err instanceof Error ? err.message : String(err);
24682
- console.error(`${TAG8} persist failed account=${accountId}: ${msg}`);
24683
- return { ok: false, error: msg };
24684
- }
24685
- }
24686
- function addAdminPhone(accountDir, phone) {
24687
- const normalized = phone.trim();
24688
- if (!E164_PATTERN.test(normalized)) {
24689
- return { ok: false, error: `Invalid phone format "${normalized}". Expected E.164 (e.g. +441234567890).` };
24690
- }
24691
- try {
24692
- const config2 = readConfig(accountDir);
24693
- if (!config2.whatsapp || typeof config2.whatsapp !== "object") {
24694
- config2.whatsapp = {};
24695
- }
24696
- const wa = config2.whatsapp;
24697
- if (!Array.isArray(wa.adminPhones)) {
24698
- wa.adminPhones = [];
24699
- }
24700
- const adminPhones = wa.adminPhones;
24701
- if (adminPhones.includes(normalized)) {
24702
- return { ok: true, message: `Phone ${normalized} is already in the admin list.` };
24703
- }
24704
- adminPhones.push(normalized);
24705
- const parsed = WhatsAppConfigSchema.safeParse(wa);
24706
- if (!parsed.success) {
24707
- const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
24708
- return { ok: false, error: `Validation failed: ${msg}` };
24709
- }
24710
- config2.whatsapp = parsed.data;
24711
- writeConfig(accountDir, config2);
24712
- console.error(`${TAG8} added admin phone=${normalized}`);
24713
- reloadManagerConfig(accountDir);
24714
- return { ok: true, message: `Added ${normalized} as admin phone. Messages from this number will route to the admin agent.` };
24715
- } catch (err) {
24716
- const msg = err instanceof Error ? err.message : String(err);
24717
- console.error(`${TAG8} addAdminPhone failed: ${msg}`);
24718
- return { ok: false, error: msg };
24719
- }
24720
- }
24721
- function removeAdminPhone(accountDir, phone) {
24722
- const normalized = phone.trim();
24723
- try {
24724
- const config2 = readConfig(accountDir);
24725
- if (!config2.whatsapp || typeof config2.whatsapp !== "object") {
24726
- return { ok: true, message: `No WhatsApp config exists \u2014 nothing to remove.` };
24727
- }
24728
- const wa = config2.whatsapp;
24729
- if (!Array.isArray(wa.adminPhones)) {
24730
- return { ok: true, message: `No admin phones configured \u2014 nothing to remove.` };
24731
- }
24732
- const adminPhones = wa.adminPhones;
24733
- const idx = adminPhones.indexOf(normalized);
24734
- if (idx === -1) {
24735
- return { ok: true, message: `Phone ${normalized} is not in the admin list.` };
24736
- }
24737
- adminPhones.splice(idx, 1);
24738
- const parsed = WhatsAppConfigSchema.safeParse(wa);
24739
- if (!parsed.success) {
24740
- const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
24741
- return { ok: false, error: `Validation failed: ${msg}` };
24742
- }
24743
- config2.whatsapp = parsed.data;
24744
- writeConfig(accountDir, config2);
24745
- console.error(`${TAG8} removed admin phone=${normalized}`);
24746
- reloadManagerConfig(accountDir);
24747
- return { ok: true, message: `Removed ${normalized} from admin phones. Messages from this number will now route to the public agent.` };
24748
- } catch (err) {
24749
- const msg = err instanceof Error ? err.message : String(err);
24750
- console.error(`${TAG8} removeAdminPhone failed: ${msg}`);
24751
- return { ok: false, error: msg };
24752
- }
24753
- }
24754
- function readAdminPhones(accountDir) {
24755
- try {
24756
- const config2 = readConfig(accountDir);
24757
- const wa = config2.whatsapp;
24758
- if (!wa) return [];
24759
- migrateAdminPhones(wa);
24760
- if (!Array.isArray(wa.adminPhones)) return [];
24761
- return wa.adminPhones.filter((p) => typeof p === "string");
24762
- } catch {
24763
- return [];
24764
- }
24765
- }
24766
- function setPublicAgent(accountDir, slug) {
24767
- const trimmed = slug.trim();
24768
- if (!trimmed) {
24769
- return { ok: false, error: "Agent slug cannot be empty." };
24770
- }
24771
- const agentConfigPath = join6(accountDir, "agents", trimmed, "config.json");
24772
- if (!existsSync10(agentConfigPath)) {
24773
- return { ok: false, error: `Agent "${trimmed}" not found \u2014 no config.json at ${agentConfigPath}. Check the agent slug and try again.` };
24774
- }
24775
- try {
24776
- const config2 = readConfig(accountDir);
24777
- if (!config2.whatsapp || typeof config2.whatsapp !== "object") {
24778
- config2.whatsapp = {};
24779
- }
24780
- const wa = config2.whatsapp;
24781
- wa.publicAgent = trimmed;
24782
- const parsed = WhatsAppConfigSchema.safeParse(wa);
24783
- if (!parsed.success) {
24784
- const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
24785
- return { ok: false, error: `Validation failed: ${msg}` };
24786
- }
24787
- config2.whatsapp = parsed.data;
24788
- writeConfig(accountDir, config2);
24789
- console.error(`${TAG8} publicAgent set to ${trimmed}`);
24790
- reloadManagerConfig(accountDir);
24791
- return { ok: true, message: `Public agent set to "${trimmed}". WhatsApp messages from non-admin phones will be handled by this agent.` };
24792
- } catch (err) {
24793
- const msg = err instanceof Error ? err.message : String(err);
24794
- console.error(`${TAG8} setPublicAgent failed: ${msg}`);
24795
- return { ok: false, error: msg };
24796
- }
24797
- }
24798
- function getPublicAgent(accountDir) {
24799
- try {
24800
- const config2 = readConfig(accountDir);
24801
- const wa = config2.whatsapp;
24802
- if (!wa) return null;
24803
- const slug = wa.publicAgent;
24804
- if (typeof slug === "string" && slug.trim()) {
24805
- return slug.trim();
24806
- }
24807
- return null;
24808
- } catch {
24809
- return null;
24810
- }
24811
- }
24812
- function updateConfig(accountDir, fields) {
24813
- const fieldNames = Object.keys(fields);
24814
- if (fieldNames.length === 0) {
24815
- return { ok: false, error: "No fields provided to update." };
24816
- }
24817
- try {
24818
- const config2 = readConfig(accountDir);
24819
- if (!config2.whatsapp || typeof config2.whatsapp !== "object") {
24820
- config2.whatsapp = {};
24821
- }
24822
- const wa = config2.whatsapp;
24823
- migrateAdminPhones(wa);
24824
- for (const [key, value] of Object.entries(fields)) {
24825
- wa[key] = value;
24826
- }
24827
- const parsed = WhatsAppConfigSchema.safeParse(wa);
24828
- if (!parsed.success) {
24829
- const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
24830
- console.error(`${TAG8} update validation failed: ${msg}`);
24831
- return { ok: false, error: `Validation failed: ${msg}` };
24832
- }
24833
- config2.whatsapp = parsed.data;
24834
- writeConfig(accountDir, config2);
24835
- console.error(`${TAG8} updated fields=[${fieldNames.join(",")}]`);
24836
- reloadManagerConfig(accountDir);
24837
- return { ok: true, message: `Updated WhatsApp config: ${fieldNames.join(", ")}.` };
24838
- } catch (err) {
24839
- const msg = err instanceof Error ? err.message : String(err);
24840
- console.error(`${TAG8} updateConfig failed: ${msg}`);
24841
- return { ok: false, error: msg };
24842
- }
24843
- }
24844
- function getConfig(accountDir) {
24845
- try {
24846
- const config2 = readConfig(accountDir);
24847
- const wa = config2.whatsapp;
24848
- if (!wa) return null;
24849
- const migrated = migrateAdminPhones(wa);
24850
- if (migrated > 0) {
24851
- const parsed = WhatsAppConfigSchema.safeParse(wa);
24852
- if (parsed.success) {
24853
- config2.whatsapp = parsed.data;
24854
- writeConfig(accountDir, config2);
24855
- reloadManagerConfig(accountDir);
24856
- }
24857
- }
24858
- return wa;
24859
- } catch {
24860
- return null;
24861
- }
24862
- }
24863
-
24864
24875
  // app/api/whatsapp/login/wait/route.ts
24865
24876
  async function POST10(req) {
24866
24877
  try {
@@ -42,7 +42,7 @@ Accounts are linked via QR in the Channels UI. Outbound routing (`src/web/outbou
42
42
 
43
43
  | Target | Account | Reason |
44
44
  |--------|---------|--------|
45
- | Admin/allowFrom phones | Account with admin binding | Admin's registered number |
45
+ | Admin phones | Account with admin binding | Admin's registered number |
46
46
  | Self-chat | Account with selfChatMode | Admin talking to themselves |
47
47
  | Group messages | Account that is a group member | Whichever is in the group |
48
48
  | Customer DMs | Account with open DM policy | Dedicated agent number |
@@ -68,7 +68,7 @@ Admin phone registration and public agent selection persist across relinks — n
68
68
  - **QR expired** — generate a new one with `whatsapp-login-start`
69
69
  - **Phone not scanning** — ensure the phone has internet, WhatsApp is updated, and the user is in Linked Devices
70
70
  - **Keeps disconnecting** — check `whatsapp-status` for error details. A 401 means re-link is needed. Repeated transient errors suggest network issues on the device.
71
- - **Messages routing as public** — the user's personal phone is not in `allowFrom`. Call `whatsapp-config` with `action: "add-admin-phone"` and the user's phone number to register it.
71
+ - **Messages routing as public** — the user's personal phone is not in `adminPhones`. Call `whatsapp-config` with `action: "add-admin-phone"` and the user's phone number to register it.
72
72
 
73
73
  ## Language
74
74