@rubytech/create-maxy 1.0.806 → 1.0.807

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.
@@ -50,7 +50,7 @@ import {
50
50
  vncLog,
51
51
  waitForExit,
52
52
  writeChromiumWrapper
53
- } from "./chunk-SC3ZSD7N.js";
53
+ } from "./chunk-LSUMH6OF.js";
54
54
  import {
55
55
  ACCOUNTS_DIR,
56
56
  GREETING_DIRECTIVE,
@@ -65,6 +65,7 @@ import {
65
65
  deleteAgentProjection,
66
66
  deleteConversation,
67
67
  embed,
68
+ ensureConversation,
68
69
  fetchBranding,
69
70
  findGroupBySlug,
70
71
  findRecentConversation,
@@ -113,7 +114,7 @@ import {
113
114
  verifyAndGetConversationUpdatedAt,
114
115
  verifyConversationOwnership,
115
116
  writeAdminUserAndPerson
116
- } from "./chunk-LTIWPCUF.js";
117
+ } from "./chunk-YULDSPAC.js";
117
118
  import {
118
119
  __commonJS,
119
120
  __toESM
@@ -3739,6 +3740,37 @@ function sanitizeReason(err) {
3739
3740
  return `${err.name}:${msg}`;
3740
3741
  }
3741
3742
 
3743
+ // app/lib/whatsapp/ensure-conversation.ts
3744
+ var TAG8 = "[whatsapp-persist]";
3745
+ async function ensureWhatsAppConversation(input) {
3746
+ const t0 = Date.now();
3747
+ try {
3748
+ const result = await ensureConversation(
3749
+ input.accountId,
3750
+ input.agentType,
3751
+ input.sessionKey
3752
+ );
3753
+ const ms = Date.now() - t0;
3754
+ if (!result.conversationId) {
3755
+ console.error(
3756
+ `${TAG8} conversation-merged FAIL sessionKey=${input.sessionKey} reason=null-conversationId ms=${ms}`
3757
+ );
3758
+ return null;
3759
+ }
3760
+ console.error(
3761
+ `${TAG8} conversation-merged sessionKey=${input.sessionKey} agentType=${input.agentType} channel=whatsapp created=${result.created} ms=${ms}`
3762
+ );
3763
+ return { conversationId: result.conversationId, created: result.created };
3764
+ } catch (err) {
3765
+ const ms = Date.now() - t0;
3766
+ const reason = err instanceof Error ? `${err.name}:${err.message.slice(0, 200)}` : String(err).slice(0, 200);
3767
+ console.error(
3768
+ `${TAG8} conversation-merged FAIL sessionKey=${input.sessionKey} reason=${reason} ms=${ms}`
3769
+ );
3770
+ return null;
3771
+ }
3772
+ }
3773
+
3742
3774
  // app/lib/whatsapp/inbound/media.ts
3743
3775
  import { randomUUID as randomUUID3 } from "crypto";
3744
3776
  import { writeFile, mkdir } from "fs/promises";
@@ -3748,7 +3780,7 @@ import {
3748
3780
  downloadContentFromMessage,
3749
3781
  normalizeMessageContent as normalizeMessageContent2
3750
3782
  } from "@whiskeysockets/baileys";
3751
- var TAG8 = "[whatsapp:media]";
3783
+ var TAG9 = "[whatsapp:media]";
3752
3784
  var MEDIA_DIR = "/tmp/maxy-media";
3753
3785
  function mimeToExt(mimetype) {
3754
3786
  const map = {
@@ -3804,25 +3836,25 @@ async function downloadInboundMedia(msg, sock, opts) {
3804
3836
  }
3805
3837
  );
3806
3838
  if (!buffer || buffer.length === 0) {
3807
- console.error(`${TAG8} primary download returned empty, trying direct fallback`);
3839
+ console.error(`${TAG9} primary download returned empty, trying direct fallback`);
3808
3840
  const downloadable = getDownloadableContent(content);
3809
3841
  if (downloadable) {
3810
3842
  try {
3811
3843
  const stream = await downloadContentFromMessage(downloadable.downloadable, downloadable.mediaType);
3812
3844
  buffer = await streamToBuffer(stream);
3813
3845
  } catch (fallbackErr) {
3814
- console.error(`${TAG8} direct download fallback failed: ${String(fallbackErr)}`);
3846
+ console.error(`${TAG9} direct download fallback failed: ${String(fallbackErr)}`);
3815
3847
  }
3816
3848
  }
3817
3849
  }
3818
3850
  if (!buffer || buffer.length === 0) {
3819
- console.error(`${TAG8} download failed: empty buffer for ${mimetype ?? "unknown"}`);
3851
+ console.error(`${TAG9} download failed: empty buffer for ${mimetype ?? "unknown"}`);
3820
3852
  return void 0;
3821
3853
  }
3822
3854
  if (buffer.length > maxBytes) {
3823
3855
  const sizeMB = (buffer.length / (1024 * 1024)).toFixed(1);
3824
3856
  const limitMB = (maxBytes / (1024 * 1024)).toFixed(0);
3825
- console.error(`${TAG8} media too large type=${mimetype ?? "unknown"} size=${sizeMB}MB limit=${limitMB}MB`);
3857
+ console.error(`${TAG9} media too large type=${mimetype ?? "unknown"} size=${sizeMB}MB limit=${limitMB}MB`);
3826
3858
  return void 0;
3827
3859
  }
3828
3860
  await mkdir(MEDIA_DIR, { recursive: true });
@@ -3831,20 +3863,20 @@ async function downloadInboundMedia(msg, sock, opts) {
3831
3863
  const filePath = join4(MEDIA_DIR, filename);
3832
3864
  await writeFile(filePath, buffer);
3833
3865
  const sizeKB = (buffer.length / 1024).toFixed(0);
3834
- console.error(`${TAG8} media downloaded type=${mimetype ?? "unknown"} size=${sizeKB}KB path=${filePath}`);
3866
+ console.error(`${TAG9} media downloaded type=${mimetype ?? "unknown"} size=${sizeKB}KB path=${filePath}`);
3835
3867
  return {
3836
3868
  path: filePath,
3837
3869
  mimetype: mimetype ?? "application/octet-stream",
3838
3870
  size: buffer.length
3839
3871
  };
3840
3872
  } catch (err) {
3841
- console.error(`${TAG8} media download failed type=${mimetype ?? "unknown"} error=${String(err)}`);
3873
+ console.error(`${TAG9} media download failed type=${mimetype ?? "unknown"} error=${String(err)}`);
3842
3874
  return void 0;
3843
3875
  }
3844
3876
  }
3845
3877
 
3846
3878
  // app/lib/whatsapp/inbound/debounce.ts
3847
- var TAG9 = "[whatsapp:debounce]";
3879
+ var TAG10 = "[whatsapp:debounce]";
3848
3880
  var STT_TAG = "[whatsapp:stt-await]";
3849
3881
  function createInboundDebouncer(opts) {
3850
3882
  const { debounceMs, buildKey, onFlush, onError } = opts;
@@ -3875,7 +3907,7 @@ function createInboundDebouncer(opts) {
3875
3907
  pending.delete(key);
3876
3908
  const batchSize = batch.entries.length;
3877
3909
  try {
3878
- console.error(`${TAG9} debounce flush key=${key} batchSize=${batchSize}`);
3910
+ console.error(`${TAG10} debounce flush key=${key} batchSize=${batchSize}`);
3879
3911
  const result = onFlush(batch.entries);
3880
3912
  if (result && typeof result.catch === "function") {
3881
3913
  result.catch(onError);
@@ -3947,7 +3979,7 @@ function createInboundDebouncer(opts) {
3947
3979
  }
3948
3980
 
3949
3981
  // app/lib/whatsapp/opening-hours.ts
3950
- var TAG10 = "[whatsapp:hours]";
3982
+ var TAG11 = "[whatsapp:hours]";
3951
3983
  async function isBusinessOpen(accountId) {
3952
3984
  try {
3953
3985
  const timezone = await resolveTimezone(accountId);
@@ -3965,7 +3997,7 @@ async function isBusinessOpen(accountId) {
3965
3997
  { accountId, dayOfWeek, previousDayOfWeek }
3966
3998
  );
3967
3999
  if (result.records.length === 0) {
3968
- console.error(`${TAG10} [${accountId}] business hours check: no opening hours configured, treating as open`);
4000
+ console.error(`${TAG11} [${accountId}] business hours check: no opening hours configured, treating as open`);
3969
4001
  return { open: true, reason: "no opening hours configured" };
3970
4002
  }
3971
4003
  const specs = result.records.filter((r) => r.get("opens") != null && r.get("closes") != null).map((r) => ({
@@ -3974,7 +4006,7 @@ async function isBusinessOpen(accountId) {
3974
4006
  closes: String(r.get("closes")).trim()
3975
4007
  }));
3976
4008
  if (specs.length === 0) {
3977
- console.error(`${TAG10} [${accountId}] business hours check: no opening hours configured, treating as open`);
4009
+ console.error(`${TAG11} [${accountId}] business hours check: no opening hours configured, treating as open`);
3978
4010
  return { open: true, reason: "no opening hours configured" };
3979
4011
  }
3980
4012
  for (const spec of specs) {
@@ -3984,7 +4016,7 @@ async function isBusinessOpen(accountId) {
3984
4016
  if (spec.opens > spec.closes && currentTime < spec.closes) {
3985
4017
  const hoursStr = `${spec.opens}-${spec.closes} (${spec.day})`;
3986
4018
  console.error(
3987
- `${TAG10} [${accountId}] business hours check: open (day=${dayOfWeek}, time=${currentTime}, hours=${hoursStr})`
4019
+ `${TAG11} [${accountId}] business hours check: open (day=${dayOfWeek}, time=${currentTime}, hours=${hoursStr})`
3988
4020
  );
3989
4021
  return {
3990
4022
  open: true,
@@ -3997,7 +4029,7 @@ async function isBusinessOpen(accountId) {
3997
4029
  } else if (isTimeInRange(currentTime, spec.opens, spec.closes)) {
3998
4030
  const hoursStr = `${spec.opens}-${spec.closes}`;
3999
4031
  console.error(
4000
- `${TAG10} [${accountId}] business hours check: open (day=${dayOfWeek}, time=${currentTime}, hours=${hoursStr})`
4032
+ `${TAG11} [${accountId}] business hours check: open (day=${dayOfWeek}, time=${currentTime}, hours=${hoursStr})`
4001
4033
  );
4002
4034
  return {
4003
4035
  open: true,
@@ -4011,7 +4043,7 @@ async function isBusinessOpen(accountId) {
4011
4043
  const todaySpecs = specs.filter((s) => s.day === dayOfWeek);
4012
4044
  const allHours = todaySpecs.map((s) => `${s.opens}-${s.closes}`).join(", ") || "none today";
4013
4045
  console.error(
4014
- `${TAG10} [${accountId}] business hours check: closed (day=${dayOfWeek}, time=${currentTime}, hours=${allHours})`
4046
+ `${TAG11} [${accountId}] business hours check: closed (day=${dayOfWeek}, time=${currentTime}, hours=${allHours})`
4015
4047
  );
4016
4048
  return {
4017
4049
  open: false,
@@ -4025,7 +4057,7 @@ async function isBusinessOpen(accountId) {
4025
4057
  }
4026
4058
  } catch (err) {
4027
4059
  console.error(
4028
- `${TAG10} [${accountId}] business hours check failed, treating as open: ${err instanceof Error ? err.message : String(err)}`
4060
+ `${TAG11} [${accountId}] business hours check failed, treating as open: ${err instanceof Error ? err.message : String(err)}`
4029
4061
  );
4030
4062
  return { open: true, reason: "hours check failed (treating as open)" };
4031
4063
  }
@@ -4071,7 +4103,7 @@ import { execFile } from "child_process";
4071
4103
  import { unlink, stat } from "fs/promises";
4072
4104
  import { promisify } from "util";
4073
4105
  var execFileAsync = promisify(execFile);
4074
- var TAG11 = "[stt]";
4106
+ var TAG12 = "[stt]";
4075
4107
  var WHISPER_BINARY = process.env.WHISPER_BINARY ?? "/opt/whisper.cpp/build/bin/whisper-cli";
4076
4108
  var WHISPER_MODEL = process.env.WHISPER_MODEL ?? "/opt/whisper.cpp/models/ggml-base.bin";
4077
4109
  var WHISPER_TIMEOUT_MS = 20 * 60 * 1e3;
@@ -4082,11 +4114,11 @@ async function transcribe(audioPath, mimetype) {
4082
4114
  const s = await stat(audioPath);
4083
4115
  audioBytes = s.size;
4084
4116
  } catch {
4085
- console.error(`${TAG11} failed: file not readable path=${audioPath}`);
4117
+ console.error(`${TAG12} failed: file not readable path=${audioPath}`);
4086
4118
  return void 0;
4087
4119
  }
4088
4120
  console.error(
4089
- `${TAG11} start provider=whisper-local audio_bytes=${audioBytes} mimetype=${mimetype} path=${audioPath}`
4121
+ `${TAG12} start provider=whisper-local audio_bytes=${audioBytes} mimetype=${mimetype} path=${audioPath}`
4090
4122
  );
4091
4123
  const wavPath = audioPath.replace(/\.[^.]+$/, "") + ".wav";
4092
4124
  try {
@@ -4105,7 +4137,7 @@ async function transcribe(audioPath, mimetype) {
4105
4137
  ], { timeout: 3e4 });
4106
4138
  } catch (err) {
4107
4139
  const reason = err instanceof Error ? err.message : String(err);
4108
- console.error(`${TAG11} failed: ffmpeg conversion error=${reason}`);
4140
+ console.error(`${TAG12} failed: ffmpeg conversion error=${reason}`);
4109
4141
  return void 0;
4110
4142
  }
4111
4143
  try {
@@ -4123,20 +4155,20 @@ async function transcribe(audioPath, mimetype) {
4123
4155
  const text = stdout.trim();
4124
4156
  const durationMs = Date.now() - startMs;
4125
4157
  if (!text) {
4126
- console.error(`${TAG11} failed: whisper returned empty output duration_ms=${durationMs}`);
4158
+ console.error(`${TAG12} failed: whisper returned empty output duration_ms=${durationMs}`);
4127
4159
  return void 0;
4128
4160
  }
4129
4161
  const langMatch = stderr.match(/auto-detected language:\s*(\w+)/);
4130
4162
  const language = langMatch?.[1] ?? "unknown";
4131
4163
  const words = text.split(/\s+/).filter(Boolean).length;
4132
4164
  console.error(
4133
- `${TAG11} done provider=whisper-local duration_ms=${durationMs} words=${words} lang=${language}`
4165
+ `${TAG12} done provider=whisper-local duration_ms=${durationMs} words=${words} lang=${language}`
4134
4166
  );
4135
4167
  return { text, language, durationMs };
4136
4168
  } catch (err) {
4137
4169
  const durationMs = Date.now() - startMs;
4138
4170
  const reason = err instanceof Error ? err.message : String(err);
4139
- console.error(`${TAG11} failed provider=whisper-local duration_ms=${durationMs} error=${reason}`);
4171
+ console.error(`${TAG12} failed provider=whisper-local duration_ms=${durationMs} error=${reason}`);
4140
4172
  return void 0;
4141
4173
  } finally {
4142
4174
  unlink(wavPath).catch(() => {
@@ -4145,7 +4177,7 @@ async function transcribe(audioPath, mimetype) {
4145
4177
  }
4146
4178
 
4147
4179
  // app/lib/whatsapp/manager.ts
4148
- var TAG12 = "[whatsapp:manager]";
4180
+ var TAG13 = "[whatsapp:manager]";
4149
4181
  var MAX_RECONNECT_ATTEMPTS = 10;
4150
4182
  var MESSAGE_STORE_MAX = 500;
4151
4183
  var GROUP_META_TTL = 5 * 60 * 1e3;
@@ -4165,7 +4197,7 @@ function storeMessage(storeKey, entry) {
4165
4197
  if (entries.length > MESSAGE_STORE_MAX) {
4166
4198
  const trimmed = entries.length - MESSAGE_STORE_MAX;
4167
4199
  entries.splice(0, trimmed);
4168
- console.error(`${TAG12} message store trimmed for ${storeKey}: ${trimmed} oldest messages removed`);
4200
+ console.error(`${TAG13} message store trimmed for ${storeKey}: ${trimmed} oldest messages removed`);
4169
4201
  }
4170
4202
  }
4171
4203
  function deriveSessionKey(input) {
@@ -4184,7 +4216,7 @@ function deriveSessionKey(input) {
4184
4216
  }
4185
4217
  async function init(opts) {
4186
4218
  if (initialized) {
4187
- console.error(`${TAG12} already initialized`);
4219
+ console.error(`${TAG13} already initialized`);
4188
4220
  return;
4189
4221
  }
4190
4222
  configDir = opts.configDir;
@@ -4192,20 +4224,20 @@ async function init(opts) {
4192
4224
  loadConfig(opts.accountConfig);
4193
4225
  const accountIds = listCredentialAccountIds(configDir);
4194
4226
  if (accountIds.length === 0) {
4195
- console.error(`${TAG12} init: no stored WhatsApp credentials found`);
4227
+ console.error(`${TAG13} init: no stored WhatsApp credentials found`);
4196
4228
  initialized = true;
4197
4229
  return;
4198
4230
  }
4199
- console.error(`${TAG12} init: found ${accountIds.length} credentialed account(s): ${accountIds.join(", ")}`);
4231
+ console.error(`${TAG13} init: found ${accountIds.length} credentialed account(s): ${accountIds.join(", ")}`);
4200
4232
  initialized = true;
4201
4233
  for (const accountId of accountIds) {
4202
4234
  const accountCfg = whatsAppConfig.accounts?.[accountId];
4203
4235
  if (accountCfg?.enabled === false) {
4204
- console.error(`${TAG12} skipping disabled account=${accountId}`);
4236
+ console.error(`${TAG13} skipping disabled account=${accountId}`);
4205
4237
  continue;
4206
4238
  }
4207
4239
  startConnection(accountId).catch((err) => {
4208
- console.error(`${TAG12} failed to auto-start account=${accountId}: ${formatError(err)}`);
4240
+ console.error(`${TAG13} failed to auto-start account=${accountId}: ${formatError(err)}`);
4209
4241
  });
4210
4242
  }
4211
4243
  }
@@ -4214,7 +4246,7 @@ async function startConnection(accountId) {
4214
4246
  const authDir = resolveAuthDir(configDir, accountId);
4215
4247
  const hasAuth = await authExists(authDir);
4216
4248
  if (!hasAuth) {
4217
- console.error(`${TAG12} no credentials for account=${accountId}`);
4249
+ console.error(`${TAG13} no credentials for account=${accountId}`);
4218
4250
  return;
4219
4251
  }
4220
4252
  await stopConnection(accountId);
@@ -4250,11 +4282,11 @@ async function stopConnection(accountId) {
4250
4282
  conn.sock.ev.removeAllListeners("creds.update");
4251
4283
  conn.sock.ws?.close?.();
4252
4284
  } catch (err) {
4253
- console.warn(`${TAG12} socket cleanup error during stop account=${accountId}: ${String(err)}`);
4285
+ console.warn(`${TAG13} socket cleanup error during stop account=${accountId}: ${String(err)}`);
4254
4286
  }
4255
4287
  }
4256
4288
  connections.delete(accountId);
4257
- console.error(`${TAG12} stopped account=${accountId}`);
4289
+ console.error(`${TAG13} stopped account=${accountId}`);
4258
4290
  }
4259
4291
  function getStatus() {
4260
4292
  return Array.from(connections.values()).map((conn) => ({
@@ -4295,9 +4327,9 @@ async function registerLoginSocket(accountId, sock, authDir) {
4295
4327
  connections.set(accountId, conn);
4296
4328
  try {
4297
4329
  await sock.sendPresenceUpdate("available");
4298
- console.error(`${TAG12} presence set to available account=${accountId}`);
4330
+ console.error(`${TAG13} presence set to available account=${accountId}`);
4299
4331
  } catch (err) {
4300
- console.error(`${TAG12} presence update failed account=${accountId}: ${String(err)}`);
4332
+ console.error(`${TAG13} presence update failed account=${accountId}: ${String(err)}`);
4301
4333
  }
4302
4334
  await runInitQueries(sock, {
4303
4335
  accountId,
@@ -4308,7 +4340,7 @@ async function registerLoginSocket(accountId, sock, authDir) {
4308
4340
  });
4309
4341
  monitorInbound(conn);
4310
4342
  watchForDisconnect(conn);
4311
- console.error(`${TAG12} registered login socket for account=${accountId} phone=${selfId.e164 ?? "unknown"}`);
4343
+ console.error(`${TAG13} registered login socket for account=${accountId} phone=${selfId.e164 ?? "unknown"}`);
4312
4344
  }
4313
4345
  function reloadConfig(accountConfig) {
4314
4346
  loadConfig(accountConfig);
@@ -4332,7 +4364,7 @@ async function shutdown() {
4332
4364
  const ids = Array.from(connections.keys());
4333
4365
  await Promise.all(ids.map((id) => stopConnection(id)));
4334
4366
  initialized = false;
4335
- console.error(`${TAG12} shutdown complete`);
4367
+ console.error(`${TAG13} shutdown complete`);
4336
4368
  }
4337
4369
  function loadConfig(accountConfig) {
4338
4370
  try {
@@ -4344,12 +4376,12 @@ function loadConfig(accountConfig) {
4344
4376
  if (parsed.success) {
4345
4377
  whatsAppConfig = parsed.data;
4346
4378
  } else {
4347
- console.error(`${TAG12} config validation failed: ${parsed.error.message}`);
4379
+ console.error(`${TAG13} config validation failed: ${parsed.error.message}`);
4348
4380
  whatsAppConfig = {};
4349
4381
  }
4350
4382
  }
4351
4383
  } catch (err) {
4352
- console.error(`${TAG12} config load error: ${String(err)}`);
4384
+ console.error(`${TAG13} config load error: ${String(err)}`);
4353
4385
  whatsAppConfig = {};
4354
4386
  }
4355
4387
  }
@@ -4360,13 +4392,13 @@ async function connectWithReconnect(conn) {
4360
4392
  let cycleError = null;
4361
4393
  let connectedAt;
4362
4394
  try {
4363
- console.error(`${TAG12} connecting account=${conn.accountId} attempt=${conn.reconnectAttempts}`);
4395
+ console.error(`${TAG13} connecting account=${conn.accountId} attempt=${conn.reconnectAttempts}`);
4364
4396
  const sock = await createWaSocket({
4365
4397
  authDir: conn.authDir,
4366
4398
  silent: true
4367
4399
  });
4368
4400
  conn.sock = sock;
4369
- console.error(`${TAG12} socket created account=${conn.accountId} \u2014 waiting for connection`);
4401
+ console.error(`${TAG13} socket created account=${conn.accountId} \u2014 waiting for connection`);
4370
4402
  await waitForConnection(sock);
4371
4403
  const selfId = readSelfId(conn.authDir);
4372
4404
  connectedAt = Date.now();
@@ -4376,12 +4408,12 @@ async function connectWithReconnect(conn) {
4376
4408
  conn.lastConnectedAt = connectedAt;
4377
4409
  conn.lastError = void 0;
4378
4410
  conn.lidMapping = sock.signalRepository?.lidMapping ?? null;
4379
- console.error(`${TAG12} connected account=${conn.accountId} phone=${selfId.e164 ?? "unknown"}`);
4411
+ console.error(`${TAG13} connected account=${conn.accountId} phone=${selfId.e164 ?? "unknown"}`);
4380
4412
  try {
4381
4413
  await sock.sendPresenceUpdate("available");
4382
- console.error(`${TAG12} presence set to available account=${conn.accountId}`);
4414
+ console.error(`${TAG13} presence set to available account=${conn.accountId}`);
4383
4415
  } catch (err) {
4384
- console.error(`${TAG12} presence update failed account=${conn.accountId}: ${String(err)}`);
4416
+ console.error(`${TAG13} presence update failed account=${conn.accountId}: ${String(err)}`);
4385
4417
  }
4386
4418
  await runInitQueries(sock, {
4387
4419
  accountId: conn.accountId,
@@ -4408,9 +4440,9 @@ async function connectWithReconnect(conn) {
4408
4440
  }
4409
4441
  const classification = classifyDisconnect(err);
4410
4442
  conn.lastError = classification.message;
4411
- console.error(`${TAG12} disconnect account=${conn.accountId}: ${classification.kind} \u2014 ${classification.message}`);
4443
+ console.error(`${TAG13} disconnect account=${conn.accountId}: ${classification.kind} \u2014 ${classification.message}`);
4412
4444
  if (!classification.shouldRetry) {
4413
- console.error(`${TAG12} terminal disconnect for account=${conn.accountId}, stopping reconnection`);
4445
+ console.error(`${TAG13} terminal disconnect for account=${conn.accountId}, stopping reconnection`);
4414
4446
  return;
4415
4447
  }
4416
4448
  }
@@ -4423,7 +4455,7 @@ async function connectWithReconnect(conn) {
4423
4455
  if (decision.action === "abort") {
4424
4456
  const stuckReason = `GIVING UP account=${conn.accountId} attempts=${decision.finalAttempts}/${maxAttempts} uptimeMsLast=${uptimeMs} stableThresholdMs=${STABLE_UPTIME_MS} lastError=${conn.lastError ?? "(none)"}`;
4425
4457
  console.error(
4426
- `${TAG12} ${stuckReason} \u2014 re-pair via QR or restart required; WhatsApp will not reconnect automatically`
4458
+ `${TAG13} ${stuckReason} \u2014 re-pair via QR or restart required; WhatsApp will not reconnect automatically`
4427
4459
  );
4428
4460
  conn.sessionStuckReason = stuckReason;
4429
4461
  conn.lastError = stuckReason;
@@ -4432,17 +4464,17 @@ async function connectWithReconnect(conn) {
4432
4464
  conn.reconnectAttempts = decision.nextAttempts;
4433
4465
  if (decision.reason === "short-lived") {
4434
4466
  console.error(
4435
- `${TAG12} short-lived session account=${conn.accountId} uptimeMs=${uptimeMs} attempt=${decision.nextAttempts}/${maxAttempts} lastError=${conn.lastError ?? "(clean disconnect)"}`
4467
+ `${TAG13} short-lived session account=${conn.accountId} uptimeMs=${uptimeMs} attempt=${decision.nextAttempts}/${maxAttempts} lastError=${conn.lastError ?? "(clean disconnect)"}`
4436
4468
  );
4437
4469
  } else {
4438
4470
  console.error(
4439
- `${TAG12} session stable account=${conn.accountId} uptimeMs=${uptimeMs} \u2014 reconnect counter reset`
4471
+ `${TAG13} session stable account=${conn.accountId} uptimeMs=${uptimeMs} \u2014 reconnect counter reset`
4440
4472
  );
4441
4473
  }
4442
4474
  if (decision.reason === "short-lived" || cycleError) {
4443
4475
  const delay = computeBackoff(Math.max(1, decision.nextAttempts));
4444
4476
  console.error(
4445
- `${TAG12} reconnecting account=${conn.accountId} in ${delay}ms (attempt ${decision.nextAttempts}/${maxAttempts})`
4477
+ `${TAG13} reconnecting account=${conn.accountId} in ${delay}ms (attempt ${decision.nextAttempts}/${maxAttempts})`
4446
4478
  );
4447
4479
  await new Promise((resolve25) => {
4448
4480
  const timer2 = setTimeout(resolve25, delay);
@@ -4476,11 +4508,11 @@ function watchForDisconnect(conn) {
4476
4508
  conn.sock.ev.on("connection.update", (update) => {
4477
4509
  if (update.connection === "close") {
4478
4510
  if (connections.get(conn.accountId) !== conn) return;
4479
- console.error(`${TAG12} socket disconnected for account=${conn.accountId}`);
4511
+ console.error(`${TAG13} socket disconnected for account=${conn.accountId}`);
4480
4512
  conn.connected = false;
4481
4513
  conn.sock = null;
4482
4514
  connectWithReconnect(conn).catch((err) => {
4483
- console.error(`${TAG12} reconnection failed for account=${conn.accountId}: ${formatError(err)}`);
4515
+ console.error(`${TAG13} reconnection failed for account=${conn.accountId}: ${formatError(err)}`);
4484
4516
  });
4485
4517
  }
4486
4518
  });
@@ -4489,7 +4521,7 @@ async function getGroupMeta(conn, jid) {
4489
4521
  const cached = conn.groupMetaCache.get(jid);
4490
4522
  if (cached && cached.expires > Date.now()) return cached;
4491
4523
  if (!conn.sock) return null;
4492
- console.error(`${TAG12} group metadata cache miss for ${jid}, fetching from Baileys account=${conn.accountId}`);
4524
+ console.error(`${TAG13} group metadata cache miss for ${jid}, fetching from Baileys account=${conn.accountId}`);
4493
4525
  try {
4494
4526
  const meta = await conn.sock.groupMetadata(jid);
4495
4527
  const participants = await Promise.all(
@@ -4504,12 +4536,12 @@ async function getGroupMeta(conn, jid) {
4504
4536
  };
4505
4537
  conn.groupMetaCache.set(jid, entry);
4506
4538
  console.error(
4507
- `${TAG12} group metadata cached for ${jid}: "${meta.subject}", ${participants.length} participants, expires in ${GROUP_META_TTL}ms account=${conn.accountId}`
4539
+ `${TAG13} group metadata cached for ${jid}: "${meta.subject}", ${participants.length} participants, expires in ${GROUP_META_TTL}ms account=${conn.accountId}`
4508
4540
  );
4509
4541
  return entry;
4510
4542
  } catch (err) {
4511
4543
  console.error(
4512
- `${TAG12} group metadata fetch failed for ${jid}: ${err instanceof Error ? err.message : String(err)}, caching empty entry for ${GROUP_META_TTL}ms account=${conn.accountId}`
4544
+ `${TAG13} group metadata fetch failed for ${jid}: ${err instanceof Error ? err.message : String(err)}, caching empty entry for ${GROUP_META_TTL}ms account=${conn.accountId}`
4513
4545
  );
4514
4546
  const emptyEntry = { expires: Date.now() + GROUP_META_TTL };
4515
4547
  conn.groupMetaCache.set(jid, emptyEntry);
@@ -4520,7 +4552,7 @@ function monitorInbound(conn) {
4520
4552
  if (!conn.sock || !onInboundMessage) return;
4521
4553
  const sock = conn.sock;
4522
4554
  const debounceMs = whatsAppConfig.accounts?.[conn.accountId]?.debounceMs ?? whatsAppConfig.debounceMs ?? 0;
4523
- console.error(`${TAG12} monitorInbound started account=${conn.accountId} debounceMs=${debounceMs}`);
4555
+ console.error(`${TAG13} monitorInbound started account=${conn.accountId} debounceMs=${debounceMs}`);
4524
4556
  conn.debouncer = createInboundDebouncer({
4525
4557
  debounceMs,
4526
4558
  buildKey: (payload) => {
@@ -4533,7 +4565,7 @@ function monitorInbound(conn) {
4533
4565
  onInboundMessage(entries[0]);
4534
4566
  return;
4535
4567
  }
4536
- console.error(`${TAG12} debounce: combining ${entries.length} messages account=${conn.accountId} from=${entries[0].senderPhone}`);
4568
+ console.error(`${TAG13} debounce: combining ${entries.length} messages account=${conn.accountId} from=${entries[0].senderPhone}`);
4537
4569
  const last = entries[entries.length - 1];
4538
4570
  const mediaEntry = entries.find((e) => e.mediaPath);
4539
4571
  const combinedText = entries.map((e) => e.text).filter(Boolean).join("\n");
@@ -4546,7 +4578,7 @@ function monitorInbound(conn) {
4546
4578
  });
4547
4579
  },
4548
4580
  onError: (err) => {
4549
- console.error(`${TAG12} debounce flush error account=${conn.accountId}: ${String(err)}`);
4581
+ console.error(`${TAG13} debounce flush error account=${conn.accountId}: ${String(err)}`);
4550
4582
  }
4551
4583
  });
4552
4584
  sock.ev.on("messages.upsert", async (upsert) => {
@@ -4574,7 +4606,7 @@ function monitorInbound(conn) {
4574
4606
  });
4575
4607
  const entries = messageStore.get(storeKey);
4576
4608
  console.error(
4577
- `${TAG12} stored message ${msg.key.id ?? "?"} for ${remoteJid} (type: ${upsert.type}, store size: ${entries?.length ?? 0}/${MESSAGE_STORE_MAX}) account=${conn.accountId}`
4609
+ `${TAG13} stored message ${msg.key.id ?? "?"} for ${remoteJid} (type: ${upsert.type}, store size: ${entries?.length ?? 0}/${MESSAGE_STORE_MAX}) account=${conn.accountId}`
4578
4610
  );
4579
4611
  recordActivity({
4580
4612
  accountId: conn.accountId,
@@ -4597,23 +4629,31 @@ function monitorInbound(conn) {
4597
4629
  isOwnerMirror: fromMe && !isGroup
4598
4630
  });
4599
4631
  if (msg.key.id) {
4600
- await persistWhatsAppMessage({
4632
+ const merged = await ensureWhatsAppConversation({
4601
4633
  accountId: conn.accountId,
4602
- remoteJid,
4603
4634
  sessionKey,
4604
- msgKeyId: msg.key.id,
4605
- fromMe,
4606
- senderPhone,
4607
- selfPhone: conn.selfPhone,
4608
- body: extracted.text,
4609
- timestamp: ts,
4610
- pushName: msg.pushName ?? void 0,
4611
- quoted: extracted.quotedMessage ? {
4612
- id: extracted.quotedMessage.id,
4613
- body: extracted.quotedMessage.text,
4614
- sender: extracted.quotedMessage.sender
4615
- } : void 0
4635
+ agentType: sessionKeyAgentType,
4636
+ groupJid: isGroup ? remoteJid : void 0
4616
4637
  });
4638
+ if (merged) {
4639
+ await persistWhatsAppMessage({
4640
+ accountId: conn.accountId,
4641
+ remoteJid,
4642
+ sessionKey,
4643
+ msgKeyId: msg.key.id,
4644
+ fromMe,
4645
+ senderPhone,
4646
+ selfPhone: conn.selfPhone,
4647
+ body: extracted.text,
4648
+ timestamp: ts,
4649
+ pushName: msg.pushName ?? void 0,
4650
+ quoted: extracted.quotedMessage ? {
4651
+ id: extracted.quotedMessage.id,
4652
+ body: extracted.quotedMessage.text,
4653
+ sender: extracted.quotedMessage.sender
4654
+ } : void 0
4655
+ });
4656
+ }
4617
4657
  }
4618
4658
  }
4619
4659
  if (upsert.type === "append") {
@@ -4627,13 +4667,13 @@ function monitorInbound(conn) {
4627
4667
  );
4628
4668
  }
4629
4669
  console.error(
4630
- `${TAG12} append-type message ${msg.key.id ?? "?"} stored, dispatch skipped account=${conn.accountId}`
4670
+ `${TAG13} append-type message ${msg.key.id ?? "?"} stored, dispatch skipped account=${conn.accountId}`
4631
4671
  );
4632
4672
  continue;
4633
4673
  }
4634
4674
  await handleInboundMessage(conn, msg);
4635
4675
  } catch (err) {
4636
- console.error(`${TAG12} inbound handler error account=${conn.accountId}: ${String(err)}`);
4676
+ console.error(`${TAG13} inbound handler error account=${conn.accountId}: ${String(err)}`);
4637
4677
  }
4638
4678
  }
4639
4679
  });
@@ -4643,31 +4683,31 @@ async function handleInboundMessage(conn, msg) {
4643
4683
  const remoteJid = msg.key.remoteJid;
4644
4684
  if (!remoteJid) return;
4645
4685
  if (remoteJid === "status@broadcast") {
4646
- console.error(`${TAG12} drop: status broadcast account=${conn.accountId}`);
4686
+ console.error(`${TAG13} drop: status broadcast account=${conn.accountId}`);
4647
4687
  return;
4648
4688
  }
4649
4689
  if (!msg.message) {
4650
- console.error(`${TAG12} drop: empty message account=${conn.accountId} from=${remoteJid}`);
4690
+ console.error(`${TAG13} drop: empty message account=${conn.accountId} from=${remoteJid}`);
4651
4691
  return;
4652
4692
  }
4653
4693
  const dedupKey = `${conn.accountId}:${remoteJid}:${msg.key.id}`;
4654
4694
  if (isDuplicateInbound(dedupKey)) {
4655
- console.error(`${TAG12} drop: duplicate account=${conn.accountId} key=${dedupKey}`);
4695
+ console.error(`${TAG13} drop: duplicate account=${conn.accountId} key=${dedupKey}`);
4656
4696
  return;
4657
4697
  }
4658
4698
  if (msg.key.fromMe) {
4659
4699
  if (msg.key.id && isAgentSentMessage(msg.key.id)) {
4660
- console.error(`${TAG12} drop: echo suppression account=${conn.accountId} msgId=${msg.key.id}`);
4700
+ console.error(`${TAG13} drop: echo suppression account=${conn.accountId} msgId=${msg.key.id}`);
4661
4701
  return;
4662
4702
  }
4663
4703
  const extracted2 = extractMessage(msg);
4664
4704
  if (!extracted2.text) {
4665
- console.error(`${TAG12} owner reply skipped \u2014 no text content account=${conn.accountId}`);
4705
+ console.error(`${TAG13} owner reply skipped \u2014 no text content account=${conn.accountId}`);
4666
4706
  return;
4667
4707
  }
4668
4708
  const isGroup2 = isGroupJid(remoteJid);
4669
4709
  const senderPhone2 = conn.selfPhone ?? "owner";
4670
- console.error(`${TAG12} owner reply mirrored to session from=${senderPhone2} account=${conn.accountId}`);
4710
+ console.error(`${TAG13} owner reply mirrored to session from=${senderPhone2} account=${conn.accountId}`);
4671
4711
  const reply2 = async (text) => {
4672
4712
  const currentSock = conn.sock;
4673
4713
  if (!currentSock) throw new Error("WhatsApp disconnected \u2014 cannot reply");
@@ -4695,7 +4735,7 @@ async function handleInboundMessage(conn, msg) {
4695
4735
  }
4696
4736
  const extracted = extractMessage(msg);
4697
4737
  if (!extracted.text && !extracted.mediaType) {
4698
- console.error(`${TAG12} drop: no text or media account=${conn.accountId} from=${remoteJid}`);
4738
+ console.error(`${TAG13} drop: no text or media account=${conn.accountId} from=${remoteJid}`);
4699
4739
  return;
4700
4740
  }
4701
4741
  let mediaResult;
@@ -4705,7 +4745,7 @@ async function handleInboundMessage(conn, msg) {
4705
4745
  maxBytes: maxMb * 1024 * 1024
4706
4746
  });
4707
4747
  if (!mediaResult) {
4708
- console.error(`${TAG12} media download returned undefined account=${conn.accountId} type=${extracted.mediaType} from=${remoteJid}`);
4748
+ console.error(`${TAG13} media download returned undefined account=${conn.accountId} type=${extracted.mediaType} from=${remoteJid}`);
4709
4749
  }
4710
4750
  }
4711
4751
  const isGroup = isGroupJid(remoteJid);
@@ -4750,7 +4790,7 @@ async function handleInboundMessage(conn, msg) {
4750
4790
  });
4751
4791
  }
4752
4792
  console.error(
4753
- `${TAG12} 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}` : "")
4793
+ `${TAG13} 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}` : "")
4754
4794
  );
4755
4795
  if (!accessResult.allowed) return;
4756
4796
  let groupSubject;
@@ -4793,15 +4833,15 @@ async function handleInboundMessage(conn, msg) {
4793
4833
  if (accessResult.agentType === "public") {
4794
4834
  const hoursResult = await isBusinessOpen(conn.accountId);
4795
4835
  if (!hoursResult.open) {
4796
- console.error(`${TAG12} [${conn.accountId}] dispatch skipped: business closed`);
4836
+ console.error(`${TAG13} [${conn.accountId}] dispatch skipped: business closed`);
4797
4837
  const afterHoursMessage = whatsAppConfig.accounts?.[conn.accountId]?.afterHoursMessage ?? whatsAppConfig.afterHoursMessage;
4798
4838
  if (afterHoursMessage) {
4799
4839
  try {
4800
4840
  await reply(afterHoursMessage);
4801
- console.error(`${TAG12} [${conn.accountId}] after-hours auto-reply sent to ${remoteJid}`);
4841
+ console.error(`${TAG13} [${conn.accountId}] after-hours auto-reply sent to ${remoteJid}`);
4802
4842
  } catch (err) {
4803
4843
  console.error(
4804
- `${TAG12} [${conn.accountId}] after-hours auto-reply failed: ${err instanceof Error ? err.message : String(err)}`
4844
+ `${TAG13} [${conn.accountId}] after-hours auto-reply failed: ${err instanceof Error ? err.message : String(err)}`
4805
4845
  );
4806
4846
  }
4807
4847
  }
@@ -5387,7 +5427,7 @@ async function storeGeneratedFile(accountId, filePath) {
5387
5427
  import { writeFile as writeFile3, mkdtemp, rm } from "fs/promises";
5388
5428
  import { tmpdir } from "os";
5389
5429
  import { join as join5 } from "path";
5390
- var TAG13 = "[voice]";
5430
+ var TAG14 = "[voice]";
5391
5431
  var AUDIO_MIME_TYPES = /* @__PURE__ */ new Set([
5392
5432
  "audio/ogg",
5393
5433
  "audio/webm",
@@ -5419,7 +5459,7 @@ async function transcribeVoiceNote(file, source) {
5419
5459
  const bytes = file.size;
5420
5460
  const mimeType = file.type;
5421
5461
  console.error(
5422
- `${TAG13} recording send source=${source} duration_ms=unknown bytes=${bytes} format=${mimeType}`
5462
+ `${TAG14} recording send source=${source} duration_ms=unknown bytes=${bytes} format=${mimeType}`
5423
5463
  );
5424
5464
  let tempDir;
5425
5465
  let tempPath;
@@ -5431,7 +5471,7 @@ async function transcribeVoiceNote(file, source) {
5431
5471
  await writeFile3(tempPath, buffer);
5432
5472
  } catch (err) {
5433
5473
  const reason = err instanceof Error ? err.message : String(err);
5434
- console.error(`${TAG13} failed source=${source} error=temp-file-write: ${reason}`);
5474
+ console.error(`${TAG14} failed source=${source} error=temp-file-write: ${reason}`);
5435
5475
  return { ok: false, error: "Could not process voice note" };
5436
5476
  }
5437
5477
  try {
@@ -5439,7 +5479,7 @@ async function transcribeVoiceNote(file, source) {
5439
5479
  if (!sttResult) {
5440
5480
  const elapsed2 = Date.now() - startMs;
5441
5481
  console.error(
5442
- `${TAG13} failed source=${source} error=transcription-failed duration_ms=${elapsed2}`
5482
+ `${TAG14} failed source=${source} error=transcription-failed duration_ms=${elapsed2}`
5443
5483
  );
5444
5484
  return { ok: false, error: "Could not transcribe voice note. Please try again or type your message." };
5445
5485
  }
@@ -5447,7 +5487,7 @@ async function transcribeVoiceNote(file, source) {
5447
5487
  const elapsed = Date.now() - startMs;
5448
5488
  const words = rawText.split(/\s+/).filter(Boolean).length;
5449
5489
  console.error(
5450
- `${TAG13} transcribed source=${source} duration_ms=${elapsed} stt_ms=${sttResult.durationMs} words=${words}`
5490
+ `${TAG14} transcribed source=${source} duration_ms=${elapsed} stt_ms=${sttResult.durationMs} words=${words}`
5451
5491
  );
5452
5492
  return {
5453
5493
  ok: true,
@@ -5457,7 +5497,7 @@ async function transcribeVoiceNote(file, source) {
5457
5497
  const elapsed = Date.now() - startMs;
5458
5498
  const reason = err instanceof Error ? err.message : String(err);
5459
5499
  console.error(
5460
- `${TAG13} failed source=${source} error=${reason} duration_ms=${elapsed}`
5500
+ `${TAG14} failed source=${source} error=${reason} duration_ms=${elapsed}`
5461
5501
  );
5462
5502
  return { ok: false, error: "Voice note transcription failed. Please try again or type your message." };
5463
5503
  } finally {
@@ -5492,7 +5532,7 @@ var VERDICT_DEFINITIONS = {
5492
5532
  };
5493
5533
 
5494
5534
  // app/lib/inbound-gateway.ts
5495
- var TAG14 = "[inbound-gateway]";
5535
+ var TAG15 = "[inbound-gateway]";
5496
5536
  var GATEWAY_TIMEOUT_MS = 1e4;
5497
5537
  var MIN_WORDS_FOR_PROCESSING = 5;
5498
5538
  function defaultResult(rawText, latencyMs) {
@@ -5615,11 +5655,11 @@ async function processInbound(rawText, channel) {
5615
5655
  };
5616
5656
  result.fallthrough = false;
5617
5657
  console.warn(
5618
- `${TAG14} short-message-injection channel=${channel} words=${words.length} latency_ms=${result.latencyMs}`
5658
+ `${TAG15} short-message-injection channel=${channel} words=${words.length} latency_ms=${result.latencyMs}`
5619
5659
  );
5620
5660
  } else {
5621
5661
  console.log(
5622
- `${TAG14} passthrough channel=${channel} reason=short-message words=${words.length} latency_ms=${result.latencyMs}`
5662
+ `${TAG15} passthrough channel=${channel} reason=short-message words=${words.length} latency_ms=${result.latencyMs}`
5623
5663
  );
5624
5664
  }
5625
5665
  return result;
@@ -5636,13 +5676,13 @@ async function processInbound(rawText, channel) {
5636
5676
  const latencyMs = Date.now() - startMs;
5637
5677
  if (llmResult.kind === "fallback") {
5638
5678
  console.warn(
5639
- `${TAG14} fallthrough channel=${channel} reason=${llmResult.cause} detail=${llmResult.reason} latency_ms=${latencyMs}`
5679
+ `${TAG15} fallthrough channel=${channel} reason=${llmResult.cause} detail=${llmResult.reason} latency_ms=${latencyMs}`
5640
5680
  );
5641
5681
  return defaultResult(rawText.trim(), latencyMs);
5642
5682
  }
5643
5683
  if (llmResult.kind !== "ok-tool") {
5644
5684
  console.warn(
5645
- `${TAG14} fallthrough channel=${channel} reason=no-tool-response latency_ms=${latencyMs}`
5685
+ `${TAG15} fallthrough channel=${channel} reason=no-tool-response latency_ms=${latencyMs}`
5646
5686
  );
5647
5687
  return defaultResult(rawText.trim(), latencyMs);
5648
5688
  }
@@ -5652,7 +5692,7 @@ async function processInbound(rawText, channel) {
5652
5692
  const verdict = input.verdict;
5653
5693
  if (!processedText || !verdict || !["clean", "suspicious", "discard"].includes(verdict)) {
5654
5694
  console.warn(
5655
- `${TAG14} fallthrough channel=${channel} reason=mandatory-fields-missing latency_ms=${latencyMs} hasProcessedText=${!!processedText} verdict=${String(verdict)}`
5695
+ `${TAG15} fallthrough channel=${channel} reason=mandatory-fields-missing latency_ms=${latencyMs} hasProcessedText=${!!processedText} verdict=${String(verdict)}`
5656
5696
  );
5657
5697
  return defaultResult(rawText.trim(), latencyMs);
5658
5698
  }
@@ -5680,18 +5720,18 @@ async function processInbound(rawText, channel) {
5680
5720
  fallthrough: false
5681
5721
  };
5682
5722
  console.log(
5683
- `${TAG14} channel=${channel} verdict=${verdict} promptInjection=${promptInjectionRisk} intent=${intent} language=${language} complexity=${complexity} requiresHistory=${requiresHistory} rewrite=${!isAdmin && processedText !== rawText.trim()} searchQuery=${searchQuery ? "yes" : "null"} latency_ms=${latencyMs}`
5723
+ `${TAG15} channel=${channel} verdict=${verdict} promptInjection=${promptInjectionRisk} intent=${intent} language=${language} complexity=${complexity} requiresHistory=${requiresHistory} rewrite=${!isAdmin && processedText !== rawText.trim()} searchQuery=${searchQuery ? "yes" : "null"} latency_ms=${latencyMs}`
5684
5724
  );
5685
5725
  if (verdict !== "clean") {
5686
5726
  console.warn(
5687
- `${TAG14} ${verdict.toUpperCase()} channel=${channel} reason=${reason} promptInjection=${promptInjectionRisk}`
5727
+ `${TAG15} ${verdict.toUpperCase()} channel=${channel} reason=${reason} promptInjection=${promptInjectionRisk}`
5688
5728
  );
5689
5729
  }
5690
5730
  return result;
5691
5731
  } catch (err) {
5692
5732
  const reason = err instanceof Error ? err.message : String(err);
5693
5733
  console.warn(
5694
- `${TAG14} fallthrough channel=${channel} reason=parse-error: ${reason} latency_ms=${latencyMs}`
5734
+ `${TAG15} fallthrough channel=${channel} reason=parse-error: ${reason} latency_ms=${latencyMs}`
5695
5735
  );
5696
5736
  return defaultResult(rawText.trim(), latencyMs);
5697
5737
  }
@@ -6784,7 +6824,7 @@ function checkTelegramAccess(params) {
6784
6824
  }
6785
6825
 
6786
6826
  // server/routes/telegram.ts
6787
- var TAG15 = "[telegram-webhook]";
6827
+ var TAG16 = "[telegram-webhook]";
6788
6828
  var TELEGRAM_API = "https://api.telegram.org";
6789
6829
  function getWebhookSecret(botType) {
6790
6830
  const filePath = botType === "admin" ? TELEGRAM_ADMIN_WEBHOOK_SECRET_FILE : TELEGRAM_WEBHOOK_SECRET_FILE;
@@ -6808,12 +6848,12 @@ async function handleInbound(params) {
6808
6848
  const gatewayChannel = agentType === "admin" ? "telegram-admin" : "telegram-dm";
6809
6849
  registerSession(sessionKey, agentType, accountId);
6810
6850
  console.error(
6811
- `${TAG15} session registered: sessionKey=${sessionKey} agentType=${agentType} botType=${botType} senderId=${senderId} accountId=${accountId.slice(0, 8)}\u2026`
6851
+ `${TAG16} session registered: sessionKey=${sessionKey} agentType=${agentType} botType=${botType} senderId=${senderId} accountId=${accountId.slice(0, 8)}\u2026`
6812
6852
  );
6813
6853
  const gatewayResult = await processInbound(text, gatewayChannel);
6814
6854
  if (gatewayResult.screening.verdict === "discard") {
6815
6855
  console.error(
6816
- `${TAG15} discarded: senderId=${senderId} chatId=${chatId} reason=${gatewayResult.screening.reason}`
6856
+ `${TAG16} discarded: senderId=${senderId} chatId=${chatId} reason=${gatewayResult.screening.reason}`
6817
6857
  );
6818
6858
  return;
6819
6859
  }
@@ -6836,7 +6876,7 @@ async function handleInbound(params) {
6836
6876
  }
6837
6877
  } catch (err) {
6838
6878
  console.error(
6839
- `${TAG15} agent-error: chatId=${chatId} senderId=${senderId} error=${err instanceof Error ? err.message : String(err)}`
6879
+ `${TAG16} agent-error: chatId=${chatId} senderId=${senderId} error=${err instanceof Error ? err.message : String(err)}`
6840
6880
  );
6841
6881
  responseText = "I'm having trouble right now. Please try again in a moment.";
6842
6882
  }
@@ -6854,12 +6894,12 @@ async function handleInbound(params) {
6854
6894
  const data = await res.json();
6855
6895
  if (!data.ok) {
6856
6896
  console.error(
6857
- `${TAG15} send-error: chatId=${chatId} error=${data.description ?? "unknown"}`
6897
+ `${TAG16} send-error: chatId=${chatId} error=${data.description ?? "unknown"}`
6858
6898
  );
6859
6899
  }
6860
6900
  } catch (err) {
6861
6901
  console.error(
6862
- `${TAG15} send-error: chatId=${chatId} error=${err instanceof Error ? err.message : String(err)}`
6902
+ `${TAG16} send-error: chatId=${chatId} error=${err instanceof Error ? err.message : String(err)}`
6863
6903
  );
6864
6904
  }
6865
6905
  }
@@ -6867,28 +6907,28 @@ var app6 = new Hono();
6867
6907
  app6.post("/webhook", async (c) => {
6868
6908
  const botType = c.req.query("bot");
6869
6909
  if (!botType || botType !== "public" && botType !== "admin") {
6870
- console.error(`${TAG15} invalid-bot-type: received=${botType ?? "missing"}`);
6910
+ console.error(`${TAG16} invalid-bot-type: received=${botType ?? "missing"}`);
6871
6911
  return c.json({ error: "Missing or invalid bot type" }, 400);
6872
6912
  }
6873
6913
  const storedSecret = getWebhookSecret(botType);
6874
6914
  if (!storedSecret) {
6875
- console.error(`${TAG15} secret=missing botType=${botType}`);
6915
+ console.error(`${TAG16} secret=missing botType=${botType}`);
6876
6916
  return c.json({ error: "Webhook not configured" }, 401);
6877
6917
  }
6878
6918
  const headerSecret = c.req.header("x-telegram-bot-api-secret-token");
6879
6919
  if (!headerSecret || !verifyWebhookSecret(headerSecret, storedSecret)) {
6880
- console.error(`${TAG15} secret=invalid botType=${botType}`);
6920
+ console.error(`${TAG16} secret=invalid botType=${botType}`);
6881
6921
  return c.json({ error: "Unauthorized" }, 401);
6882
6922
  }
6883
6923
  const account = resolveAccount();
6884
6924
  if (!account) {
6885
- console.error(`${TAG15} config=no-account`);
6925
+ console.error(`${TAG16} config=no-account`);
6886
6926
  return c.json({ error: "No account configured" }, 500);
6887
6927
  }
6888
6928
  const tgConfig = account.config.telegram ?? {};
6889
6929
  const botToken = botType === "admin" ? tgConfig.adminBotToken : tgConfig.publicBotToken;
6890
6930
  if (!botToken) {
6891
- console.error(`${TAG15} config=no-token botType=${botType}`);
6931
+ console.error(`${TAG16} config=no-token botType=${botType}`);
6892
6932
  return c.json({ error: `No ${botType} bot token configured` }, 500);
6893
6933
  }
6894
6934
  let update;
@@ -6916,7 +6956,7 @@ app6.post("/webhook", async (c) => {
6916
6956
  }
6917
6957
  const accessResult = checkTelegramAccess({ senderId, botType, config: tgConfig });
6918
6958
  console.error(
6919
- `${TAG15} access: botType=${botType} senderId=${senderId} chatId=${chatId} allowed=${accessResult.allowed} reason=${accessResult.reason} agentType=${accessResult.agentType}`
6959
+ `${TAG16} access: botType=${botType} senderId=${senderId} chatId=${chatId} allowed=${accessResult.allowed} reason=${accessResult.reason} agentType=${accessResult.agentType}`
6920
6960
  );
6921
6961
  if (!accessResult.allowed) {
6922
6962
  return c.json({ ok: true });
@@ -6927,7 +6967,7 @@ app6.post("/webhook", async (c) => {
6927
6967
  headers: { "Content-Type": "application/json" },
6928
6968
  body: JSON.stringify({ callback_query_id: callbackId })
6929
6969
  }).catch((err) => {
6930
- console.error(`${TAG15} callback-ack-error: ${err instanceof Error ? err.message : String(err)}`);
6970
+ console.error(`${TAG16} callback-ack-error: ${err instanceof Error ? err.message : String(err)}`);
6931
6971
  });
6932
6972
  }
6933
6973
  handleInbound({
@@ -6940,7 +6980,7 @@ app6.post("/webhook", async (c) => {
6940
6980
  agentType: accessResult.agentType
6941
6981
  }).catch((err) => {
6942
6982
  console.error(
6943
- `${TAG15} unhandled-error: chatId=${chatId} senderId=${senderId} error=${err instanceof Error ? err.message : String(err)}`
6983
+ `${TAG16} unhandled-error: chatId=${chatId} senderId=${senderId} error=${err instanceof Error ? err.message : String(err)}`
6944
6984
  );
6945
6985
  });
6946
6986
  return c.json({ ok: true });
@@ -6954,14 +6994,14 @@ import { realpathSync as realpathSync2, readdirSync as readdirSync2, readFileSyn
6954
6994
 
6955
6995
  // app/lib/whatsapp/login.ts
6956
6996
  import { randomUUID as randomUUID6 } from "crypto";
6957
- var TAG16 = "[whatsapp:login]";
6997
+ var TAG17 = "[whatsapp:login]";
6958
6998
  var ACTIVE_LOGIN_TTL_MS = 3 * 6e4;
6959
6999
  var activeLogins = /* @__PURE__ */ new Map();
6960
7000
  function closeSocket(sock) {
6961
7001
  try {
6962
7002
  sock.ws?.close?.();
6963
7003
  } catch (err) {
6964
- console.warn(`${TAG16} socket close error during cleanup: ${String(err)}`);
7004
+ console.warn(`${TAG17} socket close error during cleanup: ${String(err)}`);
6965
7005
  }
6966
7006
  }
6967
7007
  function resetActiveLogin(accountId) {
@@ -6984,7 +7024,7 @@ async function loginConnectionLoop(accountId, login) {
6984
7024
  const current = activeLogins.get(accountId);
6985
7025
  if (current?.id === login.id) {
6986
7026
  current.connected = true;
6987
- console.error(`${TAG16} loginConnectionLoop: connected account=${accountId} attempt=${attempt}`);
7027
+ console.error(`${TAG17} loginConnectionLoop: connected account=${accountId} attempt=${attempt}`);
6988
7028
  }
6989
7029
  return;
6990
7030
  } catch (err) {
@@ -6994,7 +7034,7 @@ async function loginConnectionLoop(accountId, login) {
6994
7034
  if (!classification.shouldRetry || attempt >= LOGIN_MAX_RECONNECTS) {
6995
7035
  if (attempt >= LOGIN_MAX_RECONNECTS) {
6996
7036
  console.error(
6997
- `${TAG16} login reconnect attempts exhausted (${attempt}/${LOGIN_MAX_RECONNECTS}) \u2014 surfacing error to agent`
7037
+ `${TAG17} login reconnect attempts exhausted (${attempt}/${LOGIN_MAX_RECONNECTS}) \u2014 surfacing error to agent`
6998
7038
  );
6999
7039
  current.error = `Login failed after ${attempt} reconnect attempts: ${formatError(err)}`;
7000
7040
  } else {
@@ -7006,7 +7046,7 @@ async function loginConnectionLoop(accountId, login) {
7006
7046
  attempt++;
7007
7047
  const delay = LOGIN_RECONNECT_DELAYS[attempt - 1] ?? 8e3;
7008
7048
  console.error(
7009
- `${TAG16} status=${classification.statusCode ?? "unknown"} restart required \u2014 reconnecting with saved creds (attempt ${attempt}/${LOGIN_MAX_RECONNECTS}) delay=${delay}ms`
7049
+ `${TAG17} status=${classification.statusCode ?? "unknown"} restart required \u2014 reconnecting with saved creds (attempt ${attempt}/${LOGIN_MAX_RECONNECTS}) delay=${delay}ms`
7010
7050
  );
7011
7051
  closeSocket(current.sock);
7012
7052
  await new Promise((r) => setTimeout(r, delay));
@@ -7017,7 +7057,7 @@ async function loginConnectionLoop(accountId, login) {
7017
7057
  current.sock = newSock;
7018
7058
  } catch (sockErr) {
7019
7059
  console.error(
7020
- `${TAG16} reconnect socket creation failed (attempt ${attempt}/${LOGIN_MAX_RECONNECTS}): ${String(sockErr)}`
7060
+ `${TAG17} reconnect socket creation failed (attempt ${attempt}/${LOGIN_MAX_RECONNECTS}): ${String(sockErr)}`
7021
7061
  );
7022
7062
  current.error = `Reconnection failed: ${String(sockErr)}`;
7023
7063
  return;
@@ -7031,7 +7071,7 @@ async function startLogin(opts) {
7031
7071
  const hasAuth = await authExists(authDir);
7032
7072
  const selfId = readSelfId(authDir);
7033
7073
  console.error(
7034
- `${TAG16} startLogin account=${accountId} force=${!!force} hasAuth=${hasAuth}` + (existing0 ? ` activeLogin={id=${existing0.id.slice(0, 8)},age=${Math.round((Date.now() - existing0.startedAt) / 1e3)}s,hasQr=${!!existing0.qr}}` : " activeLogin=none")
7074
+ `${TAG17} startLogin account=${accountId} force=${!!force} hasAuth=${hasAuth}` + (existing0 ? ` activeLogin={id=${existing0.id.slice(0, 8)},age=${Math.round((Date.now() - existing0.startedAt) / 1e3)}s,hasQr=${!!existing0.qr}}` : " activeLogin=none")
7035
7075
  );
7036
7076
  if (hasAuth && !force) {
7037
7077
  const who = selfId.e164 ?? selfId.jid ?? "unknown";
@@ -7043,7 +7083,7 @@ async function startLogin(opts) {
7043
7083
  await clearAuth(authDir);
7044
7084
  const existing = activeLogins.get(accountId);
7045
7085
  if (existing && isLoginFresh(existing) && existing.qrDataUrl && !force) {
7046
- console.error(`${TAG16} startLogin account=${accountId} guard: returning existing QR (age=${Math.round((Date.now() - existing.startedAt) / 1e3)}s)`);
7086
+ console.error(`${TAG17} startLogin account=${accountId} guard: returning existing QR (age=${Math.round((Date.now() - existing.startedAt) / 1e3)}s)`);
7047
7087
  return {
7048
7088
  qrDataUrl: existing.qrDataUrl,
7049
7089
  qrRaw: existing.qr,
@@ -7051,7 +7091,7 @@ async function startLogin(opts) {
7051
7091
  };
7052
7092
  }
7053
7093
  if (existing) {
7054
- console.error(`${TAG16} startLogin account=${accountId} ${force ? "force override" : "stale/no-QR"}, resetting active login`);
7094
+ console.error(`${TAG17} startLogin account=${accountId} ${force ? "force override" : "stale/no-QR"}, resetting active login`);
7055
7095
  }
7056
7096
  resetActiveLogin(accountId);
7057
7097
  let resolveQr = null;
@@ -7073,14 +7113,14 @@ async function startLogin(opts) {
7073
7113
  onQr: (qr2) => {
7074
7114
  loginQrCount++;
7075
7115
  if (pendingQr) {
7076
- console.error(`${TAG16} QR rotation #${loginQrCount} received for account=${accountId} \u2014 not forwarded (initial QR already captured)`);
7116
+ console.error(`${TAG17} QR rotation #${loginQrCount} received for account=${accountId} \u2014 not forwarded (initial QR already captured)`);
7077
7117
  return;
7078
7118
  }
7079
7119
  pendingQr = qr2;
7080
7120
  const current = activeLogins.get(accountId);
7081
7121
  if (current && !current.qr) current.qr = qr2;
7082
7122
  clearTimeout(qrTimer);
7083
- console.error(`${TAG16} QR #${loginQrCount} received for account=${accountId} \u2014 forwarding to caller`);
7123
+ console.error(`${TAG17} QR #${loginQrCount} received for account=${accountId} \u2014 forwarding to caller`);
7084
7124
  resolveQr?.(qr2);
7085
7125
  }
7086
7126
  });
@@ -7100,7 +7140,7 @@ async function startLogin(opts) {
7100
7140
  activeLogins.set(accountId, login);
7101
7141
  if (pendingQr && !login.qr) login.qr = pendingQr;
7102
7142
  loginConnectionLoop(accountId, login).catch((err) => {
7103
- console.error(`${TAG16} loginConnectionLoop unexpected error: ${String(err)}`);
7143
+ console.error(`${TAG17} loginConnectionLoop unexpected error: ${String(err)}`);
7104
7144
  const current = activeLogins.get(accountId);
7105
7145
  if (current?.id === login.id) {
7106
7146
  current.error = `Unexpected login error: ${String(err)}`;
@@ -7125,7 +7165,7 @@ async function waitForLogin(opts) {
7125
7165
  const { accountId, timeoutMs = 6e4 } = opts;
7126
7166
  const login = activeLogins.get(accountId);
7127
7167
  console.error(
7128
- `${TAG16} waitForLogin account=${accountId} timeout=${timeoutMs}ms` + (login ? ` login={id=${login.id.slice(0, 8)},age=${Math.round((Date.now() - login.startedAt) / 1e3)}s,connected=${login.connected},hasQr=${!!login.qr}}` : " login=none")
7168
+ `${TAG17} waitForLogin account=${accountId} timeout=${timeoutMs}ms` + (login ? ` login={id=${login.id.slice(0, 8)},age=${Math.round((Date.now() - login.startedAt) / 1e3)}s,connected=${login.connected},hasQr=${!!login.qr}}` : " login=none")
7129
7169
  );
7130
7170
  if (!login) {
7131
7171
  return { connected: false, message: "No active WhatsApp login in progress." };
@@ -7138,7 +7178,7 @@ async function waitForLogin(opts) {
7138
7178
  while (Date.now() < deadline) {
7139
7179
  if (login.connected) {
7140
7180
  const selfId = readSelfId(login.authDir);
7141
- console.error(`${TAG16} login complete for account=${accountId} phone=${selfId.e164 ?? "unknown"}`);
7181
+ console.error(`${TAG17} login complete for account=${accountId} phone=${selfId.e164 ?? "unknown"}`);
7142
7182
  const sock = login.sock;
7143
7183
  const authDir = login.authDir;
7144
7184
  activeLogins.delete(accountId);
@@ -7158,7 +7198,7 @@ async function waitForLogin(opts) {
7158
7198
  await new Promise((r) => setTimeout(r, 1e3));
7159
7199
  }
7160
7200
  const elapsed = Math.round((Date.now() - (deadline - timeoutMs)) / 1e3);
7161
- console.error(`${TAG16} waitForLogin timeout account=${accountId} elapsed=${elapsed}s \u2014 cleaning up active login`);
7201
+ console.error(`${TAG17} waitForLogin timeout account=${accountId} elapsed=${elapsed}s \u2014 cleaning up active login`);
7162
7202
  resetActiveLogin(accountId);
7163
7203
  return { connected: false, message: "Login timed out. Try generating a new QR." };
7164
7204
  }
@@ -7272,17 +7312,17 @@ function serializeWhatsAppSchema() {
7272
7312
  }
7273
7313
 
7274
7314
  // server/routes/whatsapp.ts
7275
- var TAG17 = "[whatsapp:api]";
7315
+ var TAG18 = "[whatsapp:api]";
7276
7316
  var PLATFORM_ROOT4 = process.env.MAXY_PLATFORM_ROOT || "";
7277
7317
  var app7 = new Hono();
7278
7318
  app7.get("/status", (c) => {
7279
7319
  try {
7280
7320
  const status = getStatus();
7281
7321
  const summary = status.map((a) => `${a.accountId}:${a.connected ? "up" : "down"}`).join(", ");
7282
- console.error(`${TAG17} status accounts=${status.length} [${summary}]`);
7322
+ console.error(`${TAG18} status accounts=${status.length} [${summary}]`);
7283
7323
  return c.json({ accounts: status });
7284
7324
  } catch (err) {
7285
- console.error(`${TAG17} status error: ${String(err)}`);
7325
+ console.error(`${TAG18} status error: ${String(err)}`);
7286
7326
  return c.json({ error: String(err) }, 500);
7287
7327
  }
7288
7328
  });
@@ -7293,10 +7333,10 @@ app7.post("/login/start", async (c) => {
7293
7333
  const force = body.force ?? false;
7294
7334
  const authDir = join6(MAXY_DIR, "credentials", "whatsapp", accountId);
7295
7335
  const result = await startLogin({ accountId, authDir, force });
7296
- console.error(`${TAG17} login/start result account=${accountId} hasQr=${!!result.qrRaw}${result.selfPhone ? ` phone=${result.selfPhone}` : ""}`);
7336
+ console.error(`${TAG18} login/start result account=${accountId} hasQr=${!!result.qrRaw}${result.selfPhone ? ` phone=${result.selfPhone}` : ""}`);
7297
7337
  return c.json(result);
7298
7338
  } catch (err) {
7299
- console.error(`${TAG17} login/start error: ${String(err)}`);
7339
+ console.error(`${TAG18} login/start error: ${String(err)}`);
7300
7340
  return c.json({ error: String(err) }, 500);
7301
7341
  }
7302
7342
  });
@@ -7311,7 +7351,7 @@ app7.post("/login/wait", async (c) => {
7311
7351
  try {
7312
7352
  await registerLoginSocket(accountId, result.sock, result.authDir);
7313
7353
  } catch (regErr) {
7314
- console.error(`${TAG17} registerLoginSocket failed account=${accountId}: ${String(regErr)}`);
7354
+ console.error(`${TAG18} registerLoginSocket failed account=${accountId}: ${String(regErr)}`);
7315
7355
  }
7316
7356
  try {
7317
7357
  const account = resolveAccount();
@@ -7319,16 +7359,16 @@ app7.post("/login/wait", async (c) => {
7319
7359
  const persistResult = persistAfterPairing(account.accountDir, accountId, result.selfPhone ?? null);
7320
7360
  configPersisted = persistResult.ok;
7321
7361
  if (!persistResult.ok) {
7322
- console.error(`${TAG17} config persist failed account=${accountId}: ${persistResult.error}`);
7362
+ console.error(`${TAG18} config persist failed account=${accountId}: ${persistResult.error}`);
7323
7363
  }
7324
7364
  } else {
7325
- console.error(`${TAG17} config persist skipped \u2014 no account resolved`);
7365
+ console.error(`${TAG18} config persist skipped \u2014 no account resolved`);
7326
7366
  }
7327
7367
  } catch (persistErr) {
7328
- console.error(`${TAG17} config persist error account=${accountId}: ${String(persistErr)}`);
7368
+ console.error(`${TAG18} config persist error account=${accountId}: ${String(persistErr)}`);
7329
7369
  }
7330
7370
  }
7331
- console.error(`${TAG17} login/wait result account=${accountId} connected=${result.connected}${result.selfPhone ? ` phone=${result.selfPhone}` : ""} configPersisted=${configPersisted}`);
7371
+ console.error(`${TAG18} login/wait result account=${accountId} connected=${result.connected}${result.selfPhone ? ` phone=${result.selfPhone}` : ""} configPersisted=${configPersisted}`);
7332
7372
  return c.json({
7333
7373
  connected: result.connected,
7334
7374
  message: result.message,
@@ -7336,7 +7376,7 @@ app7.post("/login/wait", async (c) => {
7336
7376
  configPersisted
7337
7377
  });
7338
7378
  } catch (err) {
7339
- console.error(`${TAG17} login/wait error: ${String(err)}`);
7379
+ console.error(`${TAG18} login/wait error: ${String(err)}`);
7340
7380
  return c.json({ error: String(err) }, 500);
7341
7381
  }
7342
7382
  });
@@ -7347,7 +7387,7 @@ app7.post("/disconnect", async (c) => {
7347
7387
  await stopConnection(accountId);
7348
7388
  return c.json({ disconnected: true, accountId });
7349
7389
  } catch (err) {
7350
- console.error(`${TAG17} disconnect error: ${String(err)}`);
7390
+ console.error(`${TAG18} disconnect error: ${String(err)}`);
7351
7391
  return c.json({ error: String(err) }, 500);
7352
7392
  }
7353
7393
  });
@@ -7358,7 +7398,7 @@ app7.post("/reconnect", async (c) => {
7358
7398
  await startConnection(accountId);
7359
7399
  return c.json({ reconnecting: true, accountId });
7360
7400
  } catch (err) {
7361
- console.error(`${TAG17} reconnect error: ${String(err)}`);
7401
+ console.error(`${TAG18} reconnect error: ${String(err)}`);
7362
7402
  return c.json({ error: String(err) }, 500);
7363
7403
  }
7364
7404
  });
@@ -7377,7 +7417,7 @@ app7.post("/send", async (c) => {
7377
7417
  const result = await sendTextMessage(sock, to, text, { accountId });
7378
7418
  return c.json(result);
7379
7419
  } catch (err) {
7380
- console.error(`${TAG17} send error: ${String(err)}`);
7420
+ console.error(`${TAG18} send error: ${String(err)}`);
7381
7421
  return c.json({ error: String(err) }, 500);
7382
7422
  }
7383
7423
  });
@@ -7398,7 +7438,7 @@ app7.post("/config", async (c) => {
7398
7438
  return c.json({ ok: false, error: 'Missing required field "phone" (E.164 format, e.g. +441234567890).' }, 400);
7399
7439
  }
7400
7440
  const result = addAdminPhone(account.accountDir, phone);
7401
- console.error(`${TAG17} config action=add-admin-phone phone=${phone} ok=${result.ok}`);
7441
+ console.error(`${TAG18} config action=add-admin-phone phone=${phone} ok=${result.ok}`);
7402
7442
  return c.json(result, result.ok ? 200 : 400);
7403
7443
  }
7404
7444
  case "remove-admin-phone": {
@@ -7406,12 +7446,12 @@ app7.post("/config", async (c) => {
7406
7446
  return c.json({ ok: false, error: 'Missing required field "phone".' }, 400);
7407
7447
  }
7408
7448
  const result = removeAdminPhone(account.accountDir, phone);
7409
- console.error(`${TAG17} config action=remove-admin-phone phone=${phone} ok=${result.ok}`);
7449
+ console.error(`${TAG18} config action=remove-admin-phone phone=${phone} ok=${result.ok}`);
7410
7450
  return c.json(result, result.ok ? 200 : 400);
7411
7451
  }
7412
7452
  case "list-admin-phones": {
7413
7453
  const phones = readAdminPhones(account.accountDir);
7414
- console.error(`${TAG17} config action=list-admin-phones count=${phones.length}`);
7454
+ console.error(`${TAG18} config action=list-admin-phones count=${phones.length}`);
7415
7455
  return c.json({ ok: true, phones });
7416
7456
  }
7417
7457
  case "set-public-agent": {
@@ -7419,14 +7459,14 @@ app7.post("/config", async (c) => {
7419
7459
  return c.json({ ok: false, error: 'Missing required field "slug" (the agent directory name, e.g. "my-agent").' }, 400);
7420
7460
  }
7421
7461
  const result = setPublicAgent(account.accountDir, slug);
7422
- console.error(`${TAG17} config action=set-public-agent slug=${slug} ok=${result.ok}`);
7462
+ console.error(`${TAG18} config action=set-public-agent slug=${slug} ok=${result.ok}`);
7423
7463
  return c.json(result, result.ok ? 200 : 400);
7424
7464
  }
7425
7465
  case "get-public-agent": {
7426
7466
  const targetAccount = typeof accountId === "string" && accountId.trim() ? accountId.trim() : "default";
7427
7467
  const targetGroup = typeof groupJid === "string" && groupJid.trim() ? groupJid.trim() : void 0;
7428
7468
  const resolved = resolvePublicAgent(account.accountDir, { accountId: targetAccount, groupJid: targetGroup });
7429
- console.error(`${TAG17} config action=get-public-agent accountId=${targetAccount} groupJid=${targetGroup ?? "none"} slug=${resolved?.slug ?? "none"} source=${resolved?.source ?? "none"}`);
7469
+ console.error(`${TAG18} config action=get-public-agent accountId=${targetAccount} groupJid=${targetGroup ?? "none"} slug=${resolved?.slug ?? "none"} source=${resolved?.source ?? "none"}`);
7430
7470
  return c.json({ ok: true, slug: resolved?.slug ?? null, source: resolved?.source ?? null });
7431
7471
  }
7432
7472
  case "set-group-public-agent": {
@@ -7438,7 +7478,7 @@ app7.post("/config", async (c) => {
7438
7478
  }
7439
7479
  const targetAccount = typeof accountId === "string" && accountId.trim() ? accountId.trim() : "default";
7440
7480
  const result = setGroupPublicAgent(account.accountDir, targetAccount, groupJid, slug);
7441
- console.error(`${TAG17} config action=set-group-public-agent accountId=${targetAccount} groupJid=${groupJid} slug=${slug} ok=${result.ok}`);
7481
+ console.error(`${TAG18} config action=set-group-public-agent accountId=${targetAccount} groupJid=${groupJid} slug=${slug} ok=${result.ok}`);
7442
7482
  return c.json(result, result.ok ? 200 : 400);
7443
7483
  }
7444
7484
  case "unset-group-public-agent": {
@@ -7447,7 +7487,7 @@ app7.post("/config", async (c) => {
7447
7487
  }
7448
7488
  const targetAccount = typeof accountId === "string" && accountId.trim() ? accountId.trim() : "default";
7449
7489
  const result = unsetGroupPublicAgent(account.accountDir, targetAccount, groupJid);
7450
- console.error(`${TAG17} config action=unset-group-public-agent accountId=${targetAccount} groupJid=${groupJid} ok=${result.ok}`);
7490
+ console.error(`${TAG18} config action=unset-group-public-agent accountId=${targetAccount} groupJid=${groupJid} ok=${result.ok}`);
7451
7491
  return c.json(result, result.ok ? 200 : 400);
7452
7492
  }
7453
7493
  case "list-public-agents": {
@@ -7464,26 +7504,26 @@ app7.post("/config", async (c) => {
7464
7504
  const config = JSON.parse(readFileSync9(configPath2, "utf-8"));
7465
7505
  agents.push({ slug: entry.name, displayName: config.displayName ?? entry.name });
7466
7506
  } catch {
7467
- console.error(`${TAG17} config action=list-public-agents error="failed to parse config.json for agent ${entry.name}" \u2014 skipping`);
7507
+ console.error(`${TAG18} config action=list-public-agents error="failed to parse config.json for agent ${entry.name}" \u2014 skipping`);
7468
7508
  }
7469
7509
  }
7470
7510
  } catch (err) {
7471
- console.error(`${TAG17} config action=list-public-agents error="failed to scan agents directory: ${String(err)}"`);
7511
+ console.error(`${TAG18} config action=list-public-agents error="failed to scan agents directory: ${String(err)}"`);
7472
7512
  }
7473
7513
  }
7474
- console.error(`${TAG17} config action=list-public-agents count=${agents.length}`);
7514
+ console.error(`${TAG18} config action=list-public-agents count=${agents.length}`);
7475
7515
  return c.json({ ok: true, agents });
7476
7516
  }
7477
7517
  case "schema": {
7478
7518
  const text = serializeWhatsAppSchema();
7479
- console.error(`${TAG17} config action=schema`);
7519
+ console.error(`${TAG18} config action=schema`);
7480
7520
  return c.json({ ok: true, text });
7481
7521
  }
7482
7522
  case "list-groups": {
7483
7523
  const groupAccountId = accountId ?? "default";
7484
7524
  const sock = getSocket(groupAccountId);
7485
7525
  if (!sock) {
7486
- console.error(`${TAG17} config action=list-groups error="not connected" accountId=${groupAccountId}`);
7526
+ console.error(`${TAG18} config action=list-groups error="not connected" accountId=${groupAccountId}`);
7487
7527
  return c.json({ ok: false, error: `WhatsApp account "${groupAccountId}" is not connected. Connect first, then retry.` });
7488
7528
  }
7489
7529
  try {
@@ -7493,10 +7533,10 @@ app7.post("/config", async (c) => {
7493
7533
  name: g.subject ?? g.id,
7494
7534
  participantCount: Array.isArray(g.participants) ? g.participants.length : 0
7495
7535
  }));
7496
- console.error(`${TAG17} config action=list-groups count=${groups.length} accountId=${groupAccountId}`);
7536
+ console.error(`${TAG18} config action=list-groups count=${groups.length} accountId=${groupAccountId}`);
7497
7537
  return c.json({ ok: true, groups });
7498
7538
  } catch (err) {
7499
- console.error(`${TAG17} config action=list-groups error="${String(err)}" accountId=${groupAccountId}`);
7539
+ console.error(`${TAG18} config action=list-groups error="${String(err)}" accountId=${groupAccountId}`);
7500
7540
  return c.json({ ok: false, error: `Failed to fetch groups: ${String(err)}` });
7501
7541
  }
7502
7542
  }
@@ -7506,12 +7546,12 @@ app7.post("/config", async (c) => {
7506
7546
  }
7507
7547
  const result = updateConfig(account.accountDir, fields);
7508
7548
  const fieldNames = Object.keys(fields);
7509
- console.error(`${TAG17} config action=update-config fields=[${fieldNames.join(",")}] ok=${result.ok}`);
7549
+ console.error(`${TAG18} config action=update-config fields=[${fieldNames.join(",")}] ok=${result.ok}`);
7510
7550
  return c.json(result, result.ok ? 200 : 400);
7511
7551
  }
7512
7552
  case "get-config": {
7513
7553
  const waConfig = getConfig(account.accountDir);
7514
- console.error(`${TAG17} config action=get-config`);
7554
+ console.error(`${TAG18} config action=get-config`);
7515
7555
  return c.json({ ok: true, config: waConfig });
7516
7556
  }
7517
7557
  default:
@@ -7521,7 +7561,7 @@ app7.post("/config", async (c) => {
7521
7561
  );
7522
7562
  }
7523
7563
  } catch (err) {
7524
- console.error(`${TAG17} config error: ${String(err)}`);
7564
+ console.error(`${TAG18} config error: ${String(err)}`);
7525
7565
  return c.json({ ok: false, error: String(err) }, 500);
7526
7566
  }
7527
7567
  });
@@ -7543,16 +7583,16 @@ app7.post("/send-document", async (c) => {
7543
7583
  const accountResolved = realpathSync2(accountDir);
7544
7584
  if (!resolvedPath.startsWith(accountResolved + "/")) {
7545
7585
  const sanitised = filePath.replace(accountDir, "<account>/");
7546
- console.error(`${TAG17} send-document REJECTED path=${sanitised} reason=outside_account_directory`);
7586
+ console.error(`${TAG18} send-document REJECTED path=${sanitised} reason=outside_account_directory`);
7547
7587
  return c.json({ error: "Access denied: file is outside the account directory" }, 403);
7548
7588
  }
7549
7589
  } catch (err) {
7550
7590
  const code = err.code;
7551
7591
  if (code === "ENOENT") {
7552
- console.error(`${TAG17} send-document ENOENT path=${filePath}`);
7592
+ console.error(`${TAG18} send-document ENOENT path=${filePath}`);
7553
7593
  return c.json({ error: `File not found: ${filePath}` }, 404);
7554
7594
  }
7555
- console.error(`${TAG17} send-document path error: ${String(err)}`);
7595
+ console.error(`${TAG18} send-document path error: ${String(err)}`);
7556
7596
  return c.json({ error: String(err) }, 500);
7557
7597
  }
7558
7598
  const fileStat = await stat3(resolvedPath);
@@ -7574,11 +7614,11 @@ app7.post("/send-document", async (c) => {
7574
7614
  caption
7575
7615
  }, { accountId });
7576
7616
  console.error(
7577
- `${TAG17} send-document to=${to} size=${fileStat.size} mime=${mimetype} ok=${result.success}` + (result.messageId ? ` id=${result.messageId}` : "")
7617
+ `${TAG18} send-document to=${to} size=${fileStat.size} mime=${mimetype} ok=${result.success}` + (result.messageId ? ` id=${result.messageId}` : "")
7578
7618
  );
7579
7619
  return c.json(result);
7580
7620
  } catch (err) {
7581
- console.error(`${TAG17} send-document error: ${String(err)}`);
7621
+ console.error(`${TAG18} send-document error: ${String(err)}`);
7582
7622
  return c.json({ error: String(err) }, 500);
7583
7623
  }
7584
7624
  });
@@ -7588,11 +7628,11 @@ app7.get("/activity", (c) => {
7588
7628
  const result = getChannelActivity(accountId);
7589
7629
  const total = result.accounts.reduce((sum, a) => sum + a.total, 0);
7590
7630
  console.error(
7591
- `${TAG17} activity accounts=${result.accounts.length} total=${total} recentEvents=${result.recentEvents.length}` + (accountId ? ` filter=${accountId}` : "")
7631
+ `${TAG18} activity accounts=${result.accounts.length} total=${total} recentEvents=${result.recentEvents.length}` + (accountId ? ` filter=${accountId}` : "")
7592
7632
  );
7593
7633
  return c.json(result);
7594
7634
  } catch (err) {
7595
- console.error(`${TAG17} activity error: ${String(err)}`);
7635
+ console.error(`${TAG18} activity error: ${String(err)}`);
7596
7636
  return c.json({ error: String(err) }, 500);
7597
7637
  }
7598
7638
  });
@@ -7611,10 +7651,10 @@ app7.get("/conversations", (c) => {
7611
7651
  };
7612
7652
  });
7613
7653
  conversations.sort((a, b) => (b.lastMessageTimestamp ?? 0) - (a.lastMessageTimestamp ?? 0));
7614
- console.error(`${TAG17} conversations account=${accountId} count=${conversations.length}`);
7654
+ console.error(`${TAG18} conversations account=${accountId} count=${conversations.length}`);
7615
7655
  return c.json({ conversations });
7616
7656
  } catch (err) {
7617
- console.error(`${TAG17} conversations error: ${String(err)}`);
7657
+ console.error(`${TAG18} conversations error: ${String(err)}`);
7618
7658
  return c.json({ error: String(err) }, 500);
7619
7659
  }
7620
7660
  });
@@ -7629,10 +7669,10 @@ app7.get("/messages", (c) => {
7629
7669
  const limit = limitParam ? parseInt(limitParam, 10) : void 0;
7630
7670
  const effectiveLimit = limit && !Number.isNaN(limit) && limit > 0 ? limit : void 0;
7631
7671
  const messages = getMessages(accountId, jid, effectiveLimit);
7632
- console.error(`${TAG17} messages account=${accountId} jid=${jid} limit=${effectiveLimit ?? "all"} returned=${messages.length}`);
7672
+ console.error(`${TAG18} messages account=${accountId} jid=${jid} limit=${effectiveLimit ?? "all"} returned=${messages.length}`);
7633
7673
  return c.json({ messages });
7634
7674
  } catch (err) {
7635
- console.error(`${TAG17} messages error: ${String(err)}`);
7675
+ console.error(`${TAG18} messages error: ${String(err)}`);
7636
7676
  return c.json({ error: String(err) }, 500);
7637
7677
  }
7638
7678
  });
@@ -7644,12 +7684,12 @@ app7.get("/group-info", async (c) => {
7644
7684
  return c.json({ error: "Missing required parameter: jid" }, 400);
7645
7685
  }
7646
7686
  if (!isGroupJid(jid)) {
7647
- console.error(`${TAG17} group-info error="not a group JID" jid=${jid} account=${accountId}`);
7687
+ console.error(`${TAG18} group-info error="not a group JID" jid=${jid} account=${accountId}`);
7648
7688
  return c.json({ error: `"${jid}" is not a group JID. Group JIDs end with @g.us.` }, 400);
7649
7689
  }
7650
7690
  const sock = getSocket(accountId);
7651
7691
  if (!sock) {
7652
- console.error(`${TAG17} group-info error="not connected" account=${accountId}`);
7692
+ console.error(`${TAG18} group-info error="not connected" account=${accountId}`);
7653
7693
  return c.json({ error: `WhatsApp account "${accountId}" is not connected. Connect first, then retry.` }, 400);
7654
7694
  }
7655
7695
  const meta = await sock.groupMetadata(jid);
@@ -7662,10 +7702,10 @@ app7.get("/group-info", async (c) => {
7662
7702
  participantCount: meta.participants.length,
7663
7703
  participants: meta.participants.map((p) => ({ jid: p.id, admin: p.admin ?? null }))
7664
7704
  };
7665
- console.error(`${TAG17} group-info jid=${jid} subject="${meta.subject}" participants=${meta.participants.length} account=${accountId}`);
7705
+ console.error(`${TAG18} group-info jid=${jid} subject="${meta.subject}" participants=${meta.participants.length} account=${accountId}`);
7666
7706
  return c.json(result);
7667
7707
  } catch (err) {
7668
- console.error(`${TAG17} group-info error="${String(err)}" jid=${jid} account=${accountId}`);
7708
+ console.error(`${TAG18} group-info error="${String(err)}" jid=${jid} account=${accountId}`);
7669
7709
  return c.json({ error: `Failed to fetch group info: ${String(err)}` }, 500);
7670
7710
  }
7671
7711
  });
@@ -8546,7 +8586,7 @@ var app11 = new Hono();
8546
8586
  app11.post("/cancel", requireAdminSession, async (c) => {
8547
8587
  const session_key = c.var.sessionKey;
8548
8588
  try {
8549
- const { interruptClient: interruptClient2 } = await import("./client-pool-CD7WHZIK.js");
8589
+ const { interruptClient: interruptClient2 } = await import("./client-pool-LXE7RIRT.js");
8550
8590
  await interruptClient2(session_key);
8551
8591
  return c.json({ ok: true });
8552
8592
  } catch (err) {
@@ -9426,16 +9466,26 @@ app17.get("/", requireAdminSession, async (c) => {
9426
9466
  sessionKey: null,
9427
9467
  name: r.name,
9428
9468
  updatedAt: r.updatedAt,
9429
- phase: "flushed"
9469
+ phase: "flushed",
9470
+ channel: r.channel
9430
9471
  }));
9431
9472
  const inProgressRows = inProgressRaw.map((r) => ({
9432
9473
  conversationId: null,
9433
9474
  sessionKey: r.sessionKey,
9434
9475
  name: null,
9435
9476
  updatedAt: new Date(r.createdAt).toISOString(),
9436
- phase: "pre-flush"
9477
+ phase: "pre-flush",
9478
+ channel: "webchat"
9437
9479
  }));
9438
9480
  const sessions = [...flushedRows, ...inProgressRows].sort((a, b) => a.updatedAt < b.updatedAt ? 1 : a.updatedAt > b.updatedAt ? -1 : 0).slice(0, 20);
9481
+ const channelCounts = sessions.reduce((acc, s) => {
9482
+ const k = s.channel ?? "unknown";
9483
+ acc[k] = (acc[k] ?? 0) + 1;
9484
+ return acc;
9485
+ }, {});
9486
+ console.error(
9487
+ `[conversations-list] render rows=${sessions.length} channels=${JSON.stringify(channelCounts)}`
9488
+ );
9439
9489
  return c.json({ sessions });
9440
9490
  } catch (err) {
9441
9491
  console.error(`[sessions-list] Failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -9699,13 +9749,13 @@ async function cdpNavigateNewTab(url, opts = {}) {
9699
9749
  // server/routes/admin/device-browser.ts
9700
9750
  var app19 = new Hono();
9701
9751
  app19.post("/navigate", async (c) => {
9702
- const TAG18 = "[device-url:click]";
9752
+ const TAG19 = "[device-url:click]";
9703
9753
  let body;
9704
9754
  try {
9705
9755
  body = await c.req.json();
9706
9756
  } catch (err) {
9707
9757
  const detail = err instanceof Error ? err.message : String(err);
9708
- console.error(`${TAG18} reject reason=body-not-json detail=${detail} browser=fallback navigateResult=error`);
9758
+ console.error(`${TAG19} reject reason=body-not-json detail=${detail} browser=fallback navigateResult=error`);
9709
9759
  return c.json(
9710
9760
  { ok: false, navigateResult: "error", browser: "fallback", detail: "Request body was not valid JSON" },
9711
9761
  400
@@ -9715,7 +9765,7 @@ app19.post("/navigate", async (c) => {
9715
9765
  const intent = typeof body.intent === "string" ? body.intent : "";
9716
9766
  const hostname2 = typeof body.hostname === "string" ? body.hostname : "";
9717
9767
  if (!url) {
9718
- console.error(`${TAG18} reject reason=missing-url intent=${JSON.stringify(intent)} browser=fallback navigateResult=error`);
9768
+ console.error(`${TAG19} reject reason=missing-url intent=${JSON.stringify(intent)} browser=fallback navigateResult=error`);
9719
9769
  return c.json(
9720
9770
  { ok: false, navigateResult: "error", browser: "fallback", detail: "url field is required" },
9721
9771
  400
@@ -9725,7 +9775,7 @@ app19.post("/navigate", async (c) => {
9725
9775
  try {
9726
9776
  parsed = new URL(url);
9727
9777
  } catch {
9728
- console.error(`${TAG18} reject reason=url-malformed intent=${JSON.stringify(intent)} url=${url} browser=fallback navigateResult=error`);
9778
+ console.error(`${TAG19} reject reason=url-malformed intent=${JSON.stringify(intent)} url=${url} browser=fallback navigateResult=error`);
9729
9779
  return c.json(
9730
9780
  { ok: false, navigateResult: "error", browser: "fallback", detail: "url is not a valid URL" },
9731
9781
  400
@@ -9733,7 +9783,7 @@ app19.post("/navigate", async (c) => {
9733
9783
  }
9734
9784
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
9735
9785
  console.error(
9736
- `${TAG18} reject reason=scheme-not-allowed scheme=${parsed.protocol} intent=${JSON.stringify(intent)} browser=fallback navigateResult=error`
9786
+ `${TAG19} reject reason=scheme-not-allowed scheme=${parsed.protocol} intent=${JSON.stringify(intent)} browser=fallback navigateResult=error`
9737
9787
  );
9738
9788
  return c.json(
9739
9789
  {
@@ -9749,7 +9799,7 @@ app19.post("/navigate", async (c) => {
9749
9799
  const cdpOk = await ensureCdp(transport);
9750
9800
  if (!cdpOk) {
9751
9801
  console.error(
9752
- `${TAG18} intent=${JSON.stringify(intent)} browser=fallback navigateResult=cdp-unreachable hostname=${JSON.stringify(hostname2)}`
9802
+ `${TAG19} intent=${JSON.stringify(intent)} browser=fallback navigateResult=cdp-unreachable hostname=${JSON.stringify(hostname2)}`
9753
9803
  );
9754
9804
  return c.json(
9755
9805
  {
@@ -9765,7 +9815,7 @@ app19.post("/navigate", async (c) => {
9765
9815
  const browser = outcome.result === "ok" ? "vnc" : "fallback";
9766
9816
  const detailStr = outcome.detail ? ` detail=${JSON.stringify(outcome.detail.length > 230 ? outcome.detail.slice(0, 227) + "..." : outcome.detail)}` : "";
9767
9817
  console.error(
9768
- `${TAG18} intent=${JSON.stringify(intent)} browser=${browser} navigateResult=${outcome.result} hostname=${JSON.stringify(hostname2)} targetId=${outcome.targetId ?? "none"}${detailStr}`
9818
+ `${TAG19} intent=${JSON.stringify(intent)} browser=${browser} navigateResult=${outcome.result} hostname=${JSON.stringify(hostname2)} targetId=${outcome.targetId ?? "none"}${detailStr}`
9769
9819
  );
9770
9820
  if (outcome.result !== "ok") {
9771
9821
  return c.json(
@@ -9796,18 +9846,18 @@ var ALLOWED_EVENTS = /* @__PURE__ */ new Set([
9796
9846
  ]);
9797
9847
  var app20 = new Hono();
9798
9848
  app20.post("/", async (c) => {
9799
- const TAG18 = "[admin:events]";
9849
+ const TAG19 = "[admin:events]";
9800
9850
  let body;
9801
9851
  try {
9802
9852
  body = await c.req.json();
9803
9853
  } catch (err) {
9804
9854
  const detail = err instanceof Error ? err.message : String(err);
9805
- console.error(`${TAG18} reject reason=body-not-json detail=${detail}`);
9855
+ console.error(`${TAG19} reject reason=body-not-json detail=${detail}`);
9806
9856
  return c.json({ ok: false, detail: "Request body was not valid JSON" }, 400);
9807
9857
  }
9808
9858
  const event = typeof body.event === "string" ? body.event : "";
9809
9859
  if (!ALLOWED_EVENTS.has(event)) {
9810
- console.error(`${TAG18} reject reason=event-not-allowed event=${JSON.stringify(event)}`);
9860
+ console.error(`${TAG19} reject reason=event-not-allowed event=${JSON.stringify(event)}`);
9811
9861
  return c.json({ ok: false, detail: `Event "${event}" is not allowed` }, 400);
9812
9862
  }
9813
9863
  const rawFields = body.fields && typeof body.fields === "object" ? body.fields : {};
@@ -11157,6 +11207,8 @@ function plainProperties(properties) {
11157
11207
  // server/routes/admin/graph-search.ts
11158
11208
  var DEFAULT_LIMIT = 20;
11159
11209
  var MAX_LIMIT = 2e3;
11210
+ var MESSAGE_FAMILY_LABELS = ["Message", "UserMessage", "AssistantMessage", "WhatsAppMessage"];
11211
+ var CONVERSATION_PARENT_LABELS = /* @__PURE__ */ new Set(["AdminConversation", "PublicConversation"]);
11160
11212
  var app23 = new Hono();
11161
11213
  app23.get("/", requireAdminSession, async (c) => {
11162
11214
  const sessionKey = c.var.sessionKey;
@@ -11175,13 +11227,15 @@ app23.get("/", requireAdminSession, async (c) => {
11175
11227
  }
11176
11228
  const parsedLimit = rawLimit ? parseInt(rawLimit, 10) : DEFAULT_LIMIT;
11177
11229
  const limit = Number.isFinite(parsedLimit) && parsedLimit > 0 ? Math.min(parsedLimit, MAX_LIMIT) : DEFAULT_LIMIT;
11230
+ const wantsBodyFulltext = labels.some((l) => CONVERSATION_PARENT_LABELS.has(l));
11231
+ const expandedLabels = wantsBodyFulltext ? Array.from(/* @__PURE__ */ new Set([...labels, ...MESSAGE_FAMILY_LABELS])) : labels;
11178
11232
  const started = Date.now();
11179
11233
  const session = getSession();
11180
11234
  try {
11181
11235
  const res = await hybrid(session, embed, {
11182
11236
  query: q,
11183
11237
  accountId,
11184
- labels,
11238
+ labels: expandedLabels,
11185
11239
  limit,
11186
11240
  degradeOnEmbedFailure: true
11187
11241
  });
@@ -11190,11 +11244,59 @@ app23.get("/", requireAdminSession, async (c) => {
11190
11244
  console.error(`[graph-search] embed-unavailable err="${res.embedError}" \u2014 bm25-only`);
11191
11245
  console.error(`[graph-search] embed-degraded query="${q}" results=${res.results.length}`);
11192
11246
  }
11247
+ let labelMatchCount = 0;
11248
+ let bodyFulltextCount = 0;
11249
+ let resolvedResults = res.results;
11250
+ if (wantsBodyFulltext) {
11251
+ const userLabelSet = new Set(labels);
11252
+ const seen = /* @__PURE__ */ new Map();
11253
+ for (const r of res.results) {
11254
+ const isMessage = r.labels.some((l) => MESSAGE_FAMILY_LABELS.includes(l));
11255
+ if (isMessage) continue;
11256
+ if (r.labels.some((l) => userLabelSet.has(l))) {
11257
+ const existing = seen.get(r.nodeId);
11258
+ if (!existing || existing.score < r.score) {
11259
+ seen.set(r.nodeId, r);
11260
+ }
11261
+ labelMatchCount++;
11262
+ }
11263
+ }
11264
+ for (const r of res.results) {
11265
+ const isMessage = r.labels.some((l) => MESSAGE_FAMILY_LABELS.includes(l));
11266
+ if (!isMessage) continue;
11267
+ const parent = r.related.find(
11268
+ (rel) => rel.relationship === "PART_OF" && rel.direction === "outgoing" && rel.labels.some((l) => userLabelSet.has(l))
11269
+ );
11270
+ if (!parent) continue;
11271
+ const existing = seen.get(parent.nodeId);
11272
+ if (existing) {
11273
+ if (existing.score < r.score) {
11274
+ seen.set(parent.nodeId, { ...existing, score: r.score });
11275
+ }
11276
+ } else {
11277
+ seen.set(parent.nodeId, {
11278
+ nodeId: parent.nodeId,
11279
+ labels: parent.labels,
11280
+ properties: parent.properties,
11281
+ score: r.score,
11282
+ related: []
11283
+ });
11284
+ }
11285
+ bodyFulltextCount++;
11286
+ }
11287
+ resolvedResults = Array.from(seen.values()).sort((a, b) => b.score - a.score);
11288
+ console.error(
11289
+ `[graph-search] label-match query="${q}" hits=${labelMatchCount}`
11290
+ );
11291
+ console.error(
11292
+ `[graph-search] body-fulltext query="${q}" hits=${bodyFulltextCount} ms=${total}`
11293
+ );
11294
+ }
11193
11295
  console.error(
11194
- `[graph-search] query="${q}" labels=${labels.join(",")} limit=${limit} mode=${res.mode} results=${res.results.length} expand-ms=${res.expandMs} total-ms=${total}`
11296
+ `[graph-search] query="${q}" labels=${labels.join(",")} expanded=${expandedLabels.length > labels.length ? 1 : 0} limit=${limit} mode=${res.mode} raw-results=${res.results.length} resolved-results=${resolvedResults.length} expand-ms=${res.expandMs} total-ms=${total}`
11195
11297
  );
11196
11298
  return c.json({
11197
- results: res.results,
11299
+ results: resolvedResults,
11198
11300
  mode: res.mode,
11199
11301
  embedError: res.embedError,
11200
11302
  elapsedMs: total
@@ -11393,6 +11495,10 @@ async function handleDefault(c, accountId) {
11393
11495
  const includeTrashed = c.req.query("includeTrashed") === "1";
11394
11496
  const includeAgentActions = c.req.query("includeAgentActions") === "1";
11395
11497
  const agentActionLabels = includeAgentActions ? [] : [...AGENT_ACTION_LABELS];
11498
+ const rawChannel = c.req.query("channel") ?? "";
11499
+ const channel = rawChannel.split(",").map((s) => s.trim()).filter(Boolean);
11500
+ const rawMessageSublabel = c.req.query("messageSublabel") ?? "";
11501
+ const messageSublabel = rawMessageSublabel.split(",").map((s) => s.trim()).filter(Boolean);
11396
11502
  if (labels.length === 0) {
11397
11503
  console.error(
11398
11504
  `[graph-page] load rejected reason=missing-filter account=${accountId} labels=`
@@ -11424,7 +11530,7 @@ async function handleDefault(c, accountId) {
11424
11530
  try {
11425
11531
  const countCypher = includeTrashed ? DEFAULT_COUNT_CYPHER_INCLUDE_TRASHED : DEFAULT_COUNT_CYPHER;
11426
11532
  const fetchCypher = includeTrashed ? DEFAULT_FETCH_CYPHER_INCLUDE_TRASHED : DEFAULT_FETCH_CYPHER;
11427
- const cypherParams = { accountId, labels, agentActionLabels };
11533
+ const cypherParams = { accountId, labels, agentActionLabels, channel, messageSublabel };
11428
11534
  const result = await session.executeRead(async (tx) => {
11429
11535
  const countResult = await tx.run(countCypher, cypherParams);
11430
11536
  const matchedRaw = countResult.records[0]?.get("matched");
@@ -11456,8 +11562,10 @@ async function handleDefault(c, accountId) {
11456
11562
  const nodes = result.rawNodes.map((n) => pruneNode(n, warnedClasses, conversationWarnings));
11457
11563
  const edges = result.rawEdges.filter((e) => e && e.id != null).map((e) => pruneEdge(e, warnedClasses));
11458
11564
  const trashedSuffix = includeTrashed ? " includeTrashed=1" : "";
11565
+ const channelSuffix = channel.length > 0 ? ` channel=${channel.join(",")}` : "";
11566
+ const sublabelSuffix = messageSublabel.length > 0 ? ` messageSublabel=${messageSublabel.join(",")}` : "";
11459
11567
  console.error(
11460
- `[graph-page] load mode=default account=${accountId} agentActions=${includeAgentActions} labels=${labels.join(",")}${trashedSuffix} nodes=${nodes.length} edges=${edges.length} ms=${elapsed}`
11568
+ `[graph-page] load mode=default account=${accountId} agentActions=${includeAgentActions} labels=${labels.join(",")}${trashedSuffix}${channelSuffix}${sublabelSuffix} nodes=${nodes.length} edges=${edges.length} ms=${elapsed}`
11461
11569
  );
11462
11570
  return c.json({ nodes, edges });
11463
11571
  } catch (err) {
@@ -11553,13 +11661,22 @@ async function handleNeighbourhood(c, accountId) {
11553
11661
  }
11554
11662
  }
11555
11663
  }
11664
+ var SUBFACET_PREDICATE = `
11665
+ AND (size($channel) = 0
11666
+ OR NOT any(lbl IN labels(n) WHERE lbl IN ['Conversation','AdminConversation','PublicConversation'])
11667
+ OR n.channel IS NULL
11668
+ OR n.channel IN $channel)
11669
+ AND (size($messageSublabel) = 0
11670
+ OR NOT any(lbl IN labels(n) WHERE lbl IN ['Message','UserMessage','AssistantMessage','WhatsAppMessage'])
11671
+ OR any(lbl IN labels(n) WHERE lbl IN $messageSublabel))
11672
+ `;
11556
11673
  var DEFAULT_COUNT_CYPHER = `
11557
11674
  MATCH (n)
11558
11675
  WHERE n.accountId = $accountId
11559
11676
  AND any(lbl IN labels(n) WHERE lbl IN $labels)
11560
11677
  AND NOT any(lbl IN labels(n) WHERE lbl IN $agentActionLabels)
11561
11678
  AND NOT n:Trashed
11562
- AND n.deletedAt IS NULL
11679
+ AND n.deletedAt IS NULL${SUBFACET_PREDICATE}
11563
11680
  RETURN count(n) AS matched
11564
11681
  `;
11565
11682
  var CONVERSATION_PROPS_PROJECTION = `CASE
@@ -11573,7 +11690,7 @@ var DEFAULT_FETCH_CYPHER = `
11573
11690
  AND any(lbl IN labels(n) WHERE lbl IN $labels)
11574
11691
  AND NOT any(lbl IN labels(n) WHERE lbl IN $agentActionLabels)
11575
11692
  AND NOT n:Trashed
11576
- AND n.deletedAt IS NULL
11693
+ AND n.deletedAt IS NULL${SUBFACET_PREDICATE}
11577
11694
  WITH collect(n) AS nodes, collect(elementId(n)) AS nodeIds
11578
11695
  UNWIND nodes AS n
11579
11696
  OPTIONAL MATCH (n)-[r]-(m)
@@ -11595,7 +11712,7 @@ var DEFAULT_COUNT_CYPHER_INCLUDE_TRASHED = `
11595
11712
  WHERE n.accountId = $accountId
11596
11713
  AND any(lbl IN labels(n) WHERE lbl IN $labels)
11597
11714
  AND NOT any(lbl IN labels(n) WHERE lbl IN $agentActionLabels)
11598
- AND n.deletedAt IS NULL
11715
+ AND n.deletedAt IS NULL${SUBFACET_PREDICATE}
11599
11716
  RETURN count(n) AS matched
11600
11717
  `;
11601
11718
  var DEFAULT_FETCH_CYPHER_INCLUDE_TRASHED = `
@@ -11603,7 +11720,7 @@ var DEFAULT_FETCH_CYPHER_INCLUDE_TRASHED = `
11603
11720
  WHERE n.accountId = $accountId
11604
11721
  AND any(lbl IN labels(n) WHERE lbl IN $labels)
11605
11722
  AND NOT any(lbl IN labels(n) WHERE lbl IN $agentActionLabels)
11606
- AND n.deletedAt IS NULL
11723
+ AND n.deletedAt IS NULL${SUBFACET_PREDICATE}
11607
11724
  WITH collect(n) AS nodes, collect(elementId(n)) AS nodeIds
11608
11725
  UNWIND nodes AS n
11609
11726
  OPTIONAL MATCH (n)-[r]-(m)