@rubytech/create-maxy 1.0.805 → 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.
Files changed (27) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/neo4j/migrations/004-project-admin-agent.ts +247 -0
  3. package/payload/platform/neo4j/migrations/004-prune-alien-accounts.ts +134 -0
  4. package/payload/platform/plugins/docs/references/cloudflare.md +1 -1
  5. package/payload/platform/plugins/docs/references/graph.md +42 -0
  6. package/payload/platform/plugins/docs/references/internals.md +11 -1
  7. package/payload/platform/plugins/docs/references/plugins-guide.md +1 -1
  8. package/payload/platform/plugins/whatsapp-import/PLUGIN.md +18 -5
  9. package/payload/platform/plugins/whatsapp-import/skills/whatsapp-import-enrich/SKILL.md +314 -0
  10. package/payload/platform/templates/agents/admin/IDENTITY.md +3 -1
  11. package/payload/platform/templates/specialists/agents/database-operator.md +5 -2
  12. package/payload/server/chunk-LSUMH6OF.js +9993 -0
  13. package/payload/server/chunk-LTIWPCUF.js +3477 -0
  14. package/payload/server/chunk-SC3ZSD7N.js +9993 -0
  15. package/payload/server/chunk-YULDSPAC.js +3484 -0
  16. package/payload/server/client-pool-CD7WHZIK.js +31 -0
  17. package/payload/server/client-pool-LXE7RIRT.js +31 -0
  18. package/payload/server/maxy-edge.js +2 -2
  19. package/payload/server/neo4j-migrations-HEECOAGK.js +128 -0
  20. package/payload/server/public/assets/admin-CTM9Vb-j.js +352 -0
  21. package/payload/server/public/assets/{graph-CBu0rtrP.js → graph-CDwy6Qw1.js} +1 -1
  22. package/payload/server/public/assets/page-DEyK-lSN.js +50 -0
  23. package/payload/server/public/graph.html +2 -2
  24. package/payload/server/public/index.html +2 -2
  25. package/payload/server/server.js +348 -202
  26. package/payload/server/public/assets/admin-BYsaXlDv.js +0 -352
  27. package/payload/server/public/assets/page-BNM63zsb.js +0 -50
@@ -50,7 +50,7 @@ import {
50
50
  vncLog,
51
51
  waitForExit,
52
52
  writeChromiumWrapper
53
- } from "./chunk-OH4OLVBN.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-5ABJJQ5K.js";
117
+ } from "./chunk-YULDSPAC.js";
117
118
  import {
118
119
  __commonJS,
119
120
  __toESM
@@ -923,6 +924,27 @@ function defaultRules() {
923
924
  // this rule it was written but never read (the review-detector had no
924
925
  // rule coverage for it). A single ERR line is worth surfacing; the
925
926
  // tee'd output is typically noise-free.
927
+ // Task 862: setup-tunnel.sh emits `[script:setup-tunnel]
928
+ // step=onboarding-persist result=skipped reason=no-account-dir` via
929
+ // phase_line when ACCOUNT_DIR is unset. Pre-Task-862, the form-driven
930
+ // action runner threaded STREAM_LOG_PATH but not ACCOUNT_DIR, so the
931
+ // line landed in the agent's stream log (system source) and the user
932
+ // looped on currentStep=6 indefinitely.
933
+ // The agent-via-Bash path also threads STREAM_LOG_PATH; operator-SSH
934
+ // does not — that's the disambiguator. If this pattern reappears in
935
+ // a system log, a future invocation surface forgot to declare
936
+ // ACCOUNT_DIR. Fix at action-runner.ts WHITELIST['cloudflare-setup'],
937
+ // not in the script.
938
+ id: "cloudflare-setup-account-dir-missing",
939
+ name: "cloudflare-setup ran without ACCOUNT_DIR \u2014 onboarding step-7 will not persist",
940
+ type: "silent-catch",
941
+ logSource: "system",
942
+ pattern: "\\[script:setup-tunnel\\] step=onboarding-persist result=skipped reason=no-account-dir",
943
+ thresholdCount: 0,
944
+ thresholdWindowMinutes: 0,
945
+ suggestedAction: "An invocation surface for setup-tunnel.sh failed to declare ACCOUNT_DIR. Without it the script's step-7 persist block (Task 562) skips, leaving OnboardingState.currentStep=6 forever. Inspect platform/ui/server/lib/action-runner.ts WHITELIST['cloudflare-setup'].build (Task 862 thread) and platform/ui/app/lib/claude-agent/spawn-env.ts buildSpawnEnv (Task 562 thread) \u2014 confirm ACCOUNT_DIR is in the env map for whichever surface the action-id prefix indicates. Do NOT change setup-tunnel.sh; the skipped branch is correct for operator-SSH reconfigure flows."
946
+ },
947
+ {
926
948
  id: "cloudflared-edge-errors",
927
949
  name: "cloudflared edge connectivity errors",
928
950
  type: "silent-catch",
@@ -3718,6 +3740,37 @@ function sanitizeReason(err) {
3718
3740
  return `${err.name}:${msg}`;
3719
3741
  }
3720
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
+
3721
3774
  // app/lib/whatsapp/inbound/media.ts
3722
3775
  import { randomUUID as randomUUID3 } from "crypto";
3723
3776
  import { writeFile, mkdir } from "fs/promises";
@@ -3727,7 +3780,7 @@ import {
3727
3780
  downloadContentFromMessage,
3728
3781
  normalizeMessageContent as normalizeMessageContent2
3729
3782
  } from "@whiskeysockets/baileys";
3730
- var TAG8 = "[whatsapp:media]";
3783
+ var TAG9 = "[whatsapp:media]";
3731
3784
  var MEDIA_DIR = "/tmp/maxy-media";
3732
3785
  function mimeToExt(mimetype) {
3733
3786
  const map = {
@@ -3783,25 +3836,25 @@ async function downloadInboundMedia(msg, sock, opts) {
3783
3836
  }
3784
3837
  );
3785
3838
  if (!buffer || buffer.length === 0) {
3786
- console.error(`${TAG8} primary download returned empty, trying direct fallback`);
3839
+ console.error(`${TAG9} primary download returned empty, trying direct fallback`);
3787
3840
  const downloadable = getDownloadableContent(content);
3788
3841
  if (downloadable) {
3789
3842
  try {
3790
3843
  const stream = await downloadContentFromMessage(downloadable.downloadable, downloadable.mediaType);
3791
3844
  buffer = await streamToBuffer(stream);
3792
3845
  } catch (fallbackErr) {
3793
- console.error(`${TAG8} direct download fallback failed: ${String(fallbackErr)}`);
3846
+ console.error(`${TAG9} direct download fallback failed: ${String(fallbackErr)}`);
3794
3847
  }
3795
3848
  }
3796
3849
  }
3797
3850
  if (!buffer || buffer.length === 0) {
3798
- console.error(`${TAG8} download failed: empty buffer for ${mimetype ?? "unknown"}`);
3851
+ console.error(`${TAG9} download failed: empty buffer for ${mimetype ?? "unknown"}`);
3799
3852
  return void 0;
3800
3853
  }
3801
3854
  if (buffer.length > maxBytes) {
3802
3855
  const sizeMB = (buffer.length / (1024 * 1024)).toFixed(1);
3803
3856
  const limitMB = (maxBytes / (1024 * 1024)).toFixed(0);
3804
- 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`);
3805
3858
  return void 0;
3806
3859
  }
3807
3860
  await mkdir(MEDIA_DIR, { recursive: true });
@@ -3810,20 +3863,20 @@ async function downloadInboundMedia(msg, sock, opts) {
3810
3863
  const filePath = join4(MEDIA_DIR, filename);
3811
3864
  await writeFile(filePath, buffer);
3812
3865
  const sizeKB = (buffer.length / 1024).toFixed(0);
3813
- 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}`);
3814
3867
  return {
3815
3868
  path: filePath,
3816
3869
  mimetype: mimetype ?? "application/octet-stream",
3817
3870
  size: buffer.length
3818
3871
  };
3819
3872
  } catch (err) {
3820
- 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)}`);
3821
3874
  return void 0;
3822
3875
  }
3823
3876
  }
3824
3877
 
3825
3878
  // app/lib/whatsapp/inbound/debounce.ts
3826
- var TAG9 = "[whatsapp:debounce]";
3879
+ var TAG10 = "[whatsapp:debounce]";
3827
3880
  var STT_TAG = "[whatsapp:stt-await]";
3828
3881
  function createInboundDebouncer(opts) {
3829
3882
  const { debounceMs, buildKey, onFlush, onError } = opts;
@@ -3854,7 +3907,7 @@ function createInboundDebouncer(opts) {
3854
3907
  pending.delete(key);
3855
3908
  const batchSize = batch.entries.length;
3856
3909
  try {
3857
- console.error(`${TAG9} debounce flush key=${key} batchSize=${batchSize}`);
3910
+ console.error(`${TAG10} debounce flush key=${key} batchSize=${batchSize}`);
3858
3911
  const result = onFlush(batch.entries);
3859
3912
  if (result && typeof result.catch === "function") {
3860
3913
  result.catch(onError);
@@ -3926,7 +3979,7 @@ function createInboundDebouncer(opts) {
3926
3979
  }
3927
3980
 
3928
3981
  // app/lib/whatsapp/opening-hours.ts
3929
- var TAG10 = "[whatsapp:hours]";
3982
+ var TAG11 = "[whatsapp:hours]";
3930
3983
  async function isBusinessOpen(accountId) {
3931
3984
  try {
3932
3985
  const timezone = await resolveTimezone(accountId);
@@ -3944,7 +3997,7 @@ async function isBusinessOpen(accountId) {
3944
3997
  { accountId, dayOfWeek, previousDayOfWeek }
3945
3998
  );
3946
3999
  if (result.records.length === 0) {
3947
- 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`);
3948
4001
  return { open: true, reason: "no opening hours configured" };
3949
4002
  }
3950
4003
  const specs = result.records.filter((r) => r.get("opens") != null && r.get("closes") != null).map((r) => ({
@@ -3953,7 +4006,7 @@ async function isBusinessOpen(accountId) {
3953
4006
  closes: String(r.get("closes")).trim()
3954
4007
  }));
3955
4008
  if (specs.length === 0) {
3956
- 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`);
3957
4010
  return { open: true, reason: "no opening hours configured" };
3958
4011
  }
3959
4012
  for (const spec of specs) {
@@ -3963,7 +4016,7 @@ async function isBusinessOpen(accountId) {
3963
4016
  if (spec.opens > spec.closes && currentTime < spec.closes) {
3964
4017
  const hoursStr = `${spec.opens}-${spec.closes} (${spec.day})`;
3965
4018
  console.error(
3966
- `${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})`
3967
4020
  );
3968
4021
  return {
3969
4022
  open: true,
@@ -3976,7 +4029,7 @@ async function isBusinessOpen(accountId) {
3976
4029
  } else if (isTimeInRange(currentTime, spec.opens, spec.closes)) {
3977
4030
  const hoursStr = `${spec.opens}-${spec.closes}`;
3978
4031
  console.error(
3979
- `${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})`
3980
4033
  );
3981
4034
  return {
3982
4035
  open: true,
@@ -3990,7 +4043,7 @@ async function isBusinessOpen(accountId) {
3990
4043
  const todaySpecs = specs.filter((s) => s.day === dayOfWeek);
3991
4044
  const allHours = todaySpecs.map((s) => `${s.opens}-${s.closes}`).join(", ") || "none today";
3992
4045
  console.error(
3993
- `${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})`
3994
4047
  );
3995
4048
  return {
3996
4049
  open: false,
@@ -4004,7 +4057,7 @@ async function isBusinessOpen(accountId) {
4004
4057
  }
4005
4058
  } catch (err) {
4006
4059
  console.error(
4007
- `${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)}`
4008
4061
  );
4009
4062
  return { open: true, reason: "hours check failed (treating as open)" };
4010
4063
  }
@@ -4050,7 +4103,7 @@ import { execFile } from "child_process";
4050
4103
  import { unlink, stat } from "fs/promises";
4051
4104
  import { promisify } from "util";
4052
4105
  var execFileAsync = promisify(execFile);
4053
- var TAG11 = "[stt]";
4106
+ var TAG12 = "[stt]";
4054
4107
  var WHISPER_BINARY = process.env.WHISPER_BINARY ?? "/opt/whisper.cpp/build/bin/whisper-cli";
4055
4108
  var WHISPER_MODEL = process.env.WHISPER_MODEL ?? "/opt/whisper.cpp/models/ggml-base.bin";
4056
4109
  var WHISPER_TIMEOUT_MS = 20 * 60 * 1e3;
@@ -4061,11 +4114,11 @@ async function transcribe(audioPath, mimetype) {
4061
4114
  const s = await stat(audioPath);
4062
4115
  audioBytes = s.size;
4063
4116
  } catch {
4064
- console.error(`${TAG11} failed: file not readable path=${audioPath}`);
4117
+ console.error(`${TAG12} failed: file not readable path=${audioPath}`);
4065
4118
  return void 0;
4066
4119
  }
4067
4120
  console.error(
4068
- `${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}`
4069
4122
  );
4070
4123
  const wavPath = audioPath.replace(/\.[^.]+$/, "") + ".wav";
4071
4124
  try {
@@ -4084,7 +4137,7 @@ async function transcribe(audioPath, mimetype) {
4084
4137
  ], { timeout: 3e4 });
4085
4138
  } catch (err) {
4086
4139
  const reason = err instanceof Error ? err.message : String(err);
4087
- console.error(`${TAG11} failed: ffmpeg conversion error=${reason}`);
4140
+ console.error(`${TAG12} failed: ffmpeg conversion error=${reason}`);
4088
4141
  return void 0;
4089
4142
  }
4090
4143
  try {
@@ -4102,20 +4155,20 @@ async function transcribe(audioPath, mimetype) {
4102
4155
  const text = stdout.trim();
4103
4156
  const durationMs = Date.now() - startMs;
4104
4157
  if (!text) {
4105
- console.error(`${TAG11} failed: whisper returned empty output duration_ms=${durationMs}`);
4158
+ console.error(`${TAG12} failed: whisper returned empty output duration_ms=${durationMs}`);
4106
4159
  return void 0;
4107
4160
  }
4108
4161
  const langMatch = stderr.match(/auto-detected language:\s*(\w+)/);
4109
4162
  const language = langMatch?.[1] ?? "unknown";
4110
4163
  const words = text.split(/\s+/).filter(Boolean).length;
4111
4164
  console.error(
4112
- `${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}`
4113
4166
  );
4114
4167
  return { text, language, durationMs };
4115
4168
  } catch (err) {
4116
4169
  const durationMs = Date.now() - startMs;
4117
4170
  const reason = err instanceof Error ? err.message : String(err);
4118
- 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}`);
4119
4172
  return void 0;
4120
4173
  } finally {
4121
4174
  unlink(wavPath).catch(() => {
@@ -4124,7 +4177,7 @@ async function transcribe(audioPath, mimetype) {
4124
4177
  }
4125
4178
 
4126
4179
  // app/lib/whatsapp/manager.ts
4127
- var TAG12 = "[whatsapp:manager]";
4180
+ var TAG13 = "[whatsapp:manager]";
4128
4181
  var MAX_RECONNECT_ATTEMPTS = 10;
4129
4182
  var MESSAGE_STORE_MAX = 500;
4130
4183
  var GROUP_META_TTL = 5 * 60 * 1e3;
@@ -4144,7 +4197,7 @@ function storeMessage(storeKey, entry) {
4144
4197
  if (entries.length > MESSAGE_STORE_MAX) {
4145
4198
  const trimmed = entries.length - MESSAGE_STORE_MAX;
4146
4199
  entries.splice(0, trimmed);
4147
- 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`);
4148
4201
  }
4149
4202
  }
4150
4203
  function deriveSessionKey(input) {
@@ -4163,7 +4216,7 @@ function deriveSessionKey(input) {
4163
4216
  }
4164
4217
  async function init(opts) {
4165
4218
  if (initialized) {
4166
- console.error(`${TAG12} already initialized`);
4219
+ console.error(`${TAG13} already initialized`);
4167
4220
  return;
4168
4221
  }
4169
4222
  configDir = opts.configDir;
@@ -4171,20 +4224,20 @@ async function init(opts) {
4171
4224
  loadConfig(opts.accountConfig);
4172
4225
  const accountIds = listCredentialAccountIds(configDir);
4173
4226
  if (accountIds.length === 0) {
4174
- console.error(`${TAG12} init: no stored WhatsApp credentials found`);
4227
+ console.error(`${TAG13} init: no stored WhatsApp credentials found`);
4175
4228
  initialized = true;
4176
4229
  return;
4177
4230
  }
4178
- 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(", ")}`);
4179
4232
  initialized = true;
4180
4233
  for (const accountId of accountIds) {
4181
4234
  const accountCfg = whatsAppConfig.accounts?.[accountId];
4182
4235
  if (accountCfg?.enabled === false) {
4183
- console.error(`${TAG12} skipping disabled account=${accountId}`);
4236
+ console.error(`${TAG13} skipping disabled account=${accountId}`);
4184
4237
  continue;
4185
4238
  }
4186
4239
  startConnection(accountId).catch((err) => {
4187
- console.error(`${TAG12} failed to auto-start account=${accountId}: ${formatError(err)}`);
4240
+ console.error(`${TAG13} failed to auto-start account=${accountId}: ${formatError(err)}`);
4188
4241
  });
4189
4242
  }
4190
4243
  }
@@ -4193,7 +4246,7 @@ async function startConnection(accountId) {
4193
4246
  const authDir = resolveAuthDir(configDir, accountId);
4194
4247
  const hasAuth = await authExists(authDir);
4195
4248
  if (!hasAuth) {
4196
- console.error(`${TAG12} no credentials for account=${accountId}`);
4249
+ console.error(`${TAG13} no credentials for account=${accountId}`);
4197
4250
  return;
4198
4251
  }
4199
4252
  await stopConnection(accountId);
@@ -4229,11 +4282,11 @@ async function stopConnection(accountId) {
4229
4282
  conn.sock.ev.removeAllListeners("creds.update");
4230
4283
  conn.sock.ws?.close?.();
4231
4284
  } catch (err) {
4232
- 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)}`);
4233
4286
  }
4234
4287
  }
4235
4288
  connections.delete(accountId);
4236
- console.error(`${TAG12} stopped account=${accountId}`);
4289
+ console.error(`${TAG13} stopped account=${accountId}`);
4237
4290
  }
4238
4291
  function getStatus() {
4239
4292
  return Array.from(connections.values()).map((conn) => ({
@@ -4274,9 +4327,9 @@ async function registerLoginSocket(accountId, sock, authDir) {
4274
4327
  connections.set(accountId, conn);
4275
4328
  try {
4276
4329
  await sock.sendPresenceUpdate("available");
4277
- console.error(`${TAG12} presence set to available account=${accountId}`);
4330
+ console.error(`${TAG13} presence set to available account=${accountId}`);
4278
4331
  } catch (err) {
4279
- console.error(`${TAG12} presence update failed account=${accountId}: ${String(err)}`);
4332
+ console.error(`${TAG13} presence update failed account=${accountId}: ${String(err)}`);
4280
4333
  }
4281
4334
  await runInitQueries(sock, {
4282
4335
  accountId,
@@ -4287,7 +4340,7 @@ async function registerLoginSocket(accountId, sock, authDir) {
4287
4340
  });
4288
4341
  monitorInbound(conn);
4289
4342
  watchForDisconnect(conn);
4290
- 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"}`);
4291
4344
  }
4292
4345
  function reloadConfig(accountConfig) {
4293
4346
  loadConfig(accountConfig);
@@ -4311,7 +4364,7 @@ async function shutdown() {
4311
4364
  const ids = Array.from(connections.keys());
4312
4365
  await Promise.all(ids.map((id) => stopConnection(id)));
4313
4366
  initialized = false;
4314
- console.error(`${TAG12} shutdown complete`);
4367
+ console.error(`${TAG13} shutdown complete`);
4315
4368
  }
4316
4369
  function loadConfig(accountConfig) {
4317
4370
  try {
@@ -4323,12 +4376,12 @@ function loadConfig(accountConfig) {
4323
4376
  if (parsed.success) {
4324
4377
  whatsAppConfig = parsed.data;
4325
4378
  } else {
4326
- console.error(`${TAG12} config validation failed: ${parsed.error.message}`);
4379
+ console.error(`${TAG13} config validation failed: ${parsed.error.message}`);
4327
4380
  whatsAppConfig = {};
4328
4381
  }
4329
4382
  }
4330
4383
  } catch (err) {
4331
- console.error(`${TAG12} config load error: ${String(err)}`);
4384
+ console.error(`${TAG13} config load error: ${String(err)}`);
4332
4385
  whatsAppConfig = {};
4333
4386
  }
4334
4387
  }
@@ -4339,13 +4392,13 @@ async function connectWithReconnect(conn) {
4339
4392
  let cycleError = null;
4340
4393
  let connectedAt;
4341
4394
  try {
4342
- console.error(`${TAG12} connecting account=${conn.accountId} attempt=${conn.reconnectAttempts}`);
4395
+ console.error(`${TAG13} connecting account=${conn.accountId} attempt=${conn.reconnectAttempts}`);
4343
4396
  const sock = await createWaSocket({
4344
4397
  authDir: conn.authDir,
4345
4398
  silent: true
4346
4399
  });
4347
4400
  conn.sock = sock;
4348
- 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`);
4349
4402
  await waitForConnection(sock);
4350
4403
  const selfId = readSelfId(conn.authDir);
4351
4404
  connectedAt = Date.now();
@@ -4355,12 +4408,12 @@ async function connectWithReconnect(conn) {
4355
4408
  conn.lastConnectedAt = connectedAt;
4356
4409
  conn.lastError = void 0;
4357
4410
  conn.lidMapping = sock.signalRepository?.lidMapping ?? null;
4358
- console.error(`${TAG12} connected account=${conn.accountId} phone=${selfId.e164 ?? "unknown"}`);
4411
+ console.error(`${TAG13} connected account=${conn.accountId} phone=${selfId.e164 ?? "unknown"}`);
4359
4412
  try {
4360
4413
  await sock.sendPresenceUpdate("available");
4361
- console.error(`${TAG12} presence set to available account=${conn.accountId}`);
4414
+ console.error(`${TAG13} presence set to available account=${conn.accountId}`);
4362
4415
  } catch (err) {
4363
- console.error(`${TAG12} presence update failed account=${conn.accountId}: ${String(err)}`);
4416
+ console.error(`${TAG13} presence update failed account=${conn.accountId}: ${String(err)}`);
4364
4417
  }
4365
4418
  await runInitQueries(sock, {
4366
4419
  accountId: conn.accountId,
@@ -4387,9 +4440,9 @@ async function connectWithReconnect(conn) {
4387
4440
  }
4388
4441
  const classification = classifyDisconnect(err);
4389
4442
  conn.lastError = classification.message;
4390
- 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}`);
4391
4444
  if (!classification.shouldRetry) {
4392
- console.error(`${TAG12} terminal disconnect for account=${conn.accountId}, stopping reconnection`);
4445
+ console.error(`${TAG13} terminal disconnect for account=${conn.accountId}, stopping reconnection`);
4393
4446
  return;
4394
4447
  }
4395
4448
  }
@@ -4402,7 +4455,7 @@ async function connectWithReconnect(conn) {
4402
4455
  if (decision.action === "abort") {
4403
4456
  const stuckReason = `GIVING UP account=${conn.accountId} attempts=${decision.finalAttempts}/${maxAttempts} uptimeMsLast=${uptimeMs} stableThresholdMs=${STABLE_UPTIME_MS} lastError=${conn.lastError ?? "(none)"}`;
4404
4457
  console.error(
4405
- `${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`
4406
4459
  );
4407
4460
  conn.sessionStuckReason = stuckReason;
4408
4461
  conn.lastError = stuckReason;
@@ -4411,17 +4464,17 @@ async function connectWithReconnect(conn) {
4411
4464
  conn.reconnectAttempts = decision.nextAttempts;
4412
4465
  if (decision.reason === "short-lived") {
4413
4466
  console.error(
4414
- `${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)"}`
4415
4468
  );
4416
4469
  } else {
4417
4470
  console.error(
4418
- `${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`
4419
4472
  );
4420
4473
  }
4421
4474
  if (decision.reason === "short-lived" || cycleError) {
4422
4475
  const delay = computeBackoff(Math.max(1, decision.nextAttempts));
4423
4476
  console.error(
4424
- `${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})`
4425
4478
  );
4426
4479
  await new Promise((resolve25) => {
4427
4480
  const timer2 = setTimeout(resolve25, delay);
@@ -4455,11 +4508,11 @@ function watchForDisconnect(conn) {
4455
4508
  conn.sock.ev.on("connection.update", (update) => {
4456
4509
  if (update.connection === "close") {
4457
4510
  if (connections.get(conn.accountId) !== conn) return;
4458
- console.error(`${TAG12} socket disconnected for account=${conn.accountId}`);
4511
+ console.error(`${TAG13} socket disconnected for account=${conn.accountId}`);
4459
4512
  conn.connected = false;
4460
4513
  conn.sock = null;
4461
4514
  connectWithReconnect(conn).catch((err) => {
4462
- console.error(`${TAG12} reconnection failed for account=${conn.accountId}: ${formatError(err)}`);
4515
+ console.error(`${TAG13} reconnection failed for account=${conn.accountId}: ${formatError(err)}`);
4463
4516
  });
4464
4517
  }
4465
4518
  });
@@ -4468,7 +4521,7 @@ async function getGroupMeta(conn, jid) {
4468
4521
  const cached = conn.groupMetaCache.get(jid);
4469
4522
  if (cached && cached.expires > Date.now()) return cached;
4470
4523
  if (!conn.sock) return null;
4471
- 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}`);
4472
4525
  try {
4473
4526
  const meta = await conn.sock.groupMetadata(jid);
4474
4527
  const participants = await Promise.all(
@@ -4483,12 +4536,12 @@ async function getGroupMeta(conn, jid) {
4483
4536
  };
4484
4537
  conn.groupMetaCache.set(jid, entry);
4485
4538
  console.error(
4486
- `${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}`
4487
4540
  );
4488
4541
  return entry;
4489
4542
  } catch (err) {
4490
4543
  console.error(
4491
- `${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}`
4492
4545
  );
4493
4546
  const emptyEntry = { expires: Date.now() + GROUP_META_TTL };
4494
4547
  conn.groupMetaCache.set(jid, emptyEntry);
@@ -4499,7 +4552,7 @@ function monitorInbound(conn) {
4499
4552
  if (!conn.sock || !onInboundMessage) return;
4500
4553
  const sock = conn.sock;
4501
4554
  const debounceMs = whatsAppConfig.accounts?.[conn.accountId]?.debounceMs ?? whatsAppConfig.debounceMs ?? 0;
4502
- console.error(`${TAG12} monitorInbound started account=${conn.accountId} debounceMs=${debounceMs}`);
4555
+ console.error(`${TAG13} monitorInbound started account=${conn.accountId} debounceMs=${debounceMs}`);
4503
4556
  conn.debouncer = createInboundDebouncer({
4504
4557
  debounceMs,
4505
4558
  buildKey: (payload) => {
@@ -4512,7 +4565,7 @@ function monitorInbound(conn) {
4512
4565
  onInboundMessage(entries[0]);
4513
4566
  return;
4514
4567
  }
4515
- 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}`);
4516
4569
  const last = entries[entries.length - 1];
4517
4570
  const mediaEntry = entries.find((e) => e.mediaPath);
4518
4571
  const combinedText = entries.map((e) => e.text).filter(Boolean).join("\n");
@@ -4525,7 +4578,7 @@ function monitorInbound(conn) {
4525
4578
  });
4526
4579
  },
4527
4580
  onError: (err) => {
4528
- console.error(`${TAG12} debounce flush error account=${conn.accountId}: ${String(err)}`);
4581
+ console.error(`${TAG13} debounce flush error account=${conn.accountId}: ${String(err)}`);
4529
4582
  }
4530
4583
  });
4531
4584
  sock.ev.on("messages.upsert", async (upsert) => {
@@ -4553,7 +4606,7 @@ function monitorInbound(conn) {
4553
4606
  });
4554
4607
  const entries = messageStore.get(storeKey);
4555
4608
  console.error(
4556
- `${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}`
4557
4610
  );
4558
4611
  recordActivity({
4559
4612
  accountId: conn.accountId,
@@ -4576,23 +4629,31 @@ function monitorInbound(conn) {
4576
4629
  isOwnerMirror: fromMe && !isGroup
4577
4630
  });
4578
4631
  if (msg.key.id) {
4579
- await persistWhatsAppMessage({
4632
+ const merged = await ensureWhatsAppConversation({
4580
4633
  accountId: conn.accountId,
4581
- remoteJid,
4582
4634
  sessionKey,
4583
- msgKeyId: msg.key.id,
4584
- fromMe,
4585
- senderPhone,
4586
- selfPhone: conn.selfPhone,
4587
- body: extracted.text,
4588
- timestamp: ts,
4589
- pushName: msg.pushName ?? void 0,
4590
- quoted: extracted.quotedMessage ? {
4591
- id: extracted.quotedMessage.id,
4592
- body: extracted.quotedMessage.text,
4593
- sender: extracted.quotedMessage.sender
4594
- } : void 0
4635
+ agentType: sessionKeyAgentType,
4636
+ groupJid: isGroup ? remoteJid : void 0
4595
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
+ }
4596
4657
  }
4597
4658
  }
4598
4659
  if (upsert.type === "append") {
@@ -4606,13 +4667,13 @@ function monitorInbound(conn) {
4606
4667
  );
4607
4668
  }
4608
4669
  console.error(
4609
- `${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}`
4610
4671
  );
4611
4672
  continue;
4612
4673
  }
4613
4674
  await handleInboundMessage(conn, msg);
4614
4675
  } catch (err) {
4615
- console.error(`${TAG12} inbound handler error account=${conn.accountId}: ${String(err)}`);
4676
+ console.error(`${TAG13} inbound handler error account=${conn.accountId}: ${String(err)}`);
4616
4677
  }
4617
4678
  }
4618
4679
  });
@@ -4622,31 +4683,31 @@ async function handleInboundMessage(conn, msg) {
4622
4683
  const remoteJid = msg.key.remoteJid;
4623
4684
  if (!remoteJid) return;
4624
4685
  if (remoteJid === "status@broadcast") {
4625
- console.error(`${TAG12} drop: status broadcast account=${conn.accountId}`);
4686
+ console.error(`${TAG13} drop: status broadcast account=${conn.accountId}`);
4626
4687
  return;
4627
4688
  }
4628
4689
  if (!msg.message) {
4629
- console.error(`${TAG12} drop: empty message account=${conn.accountId} from=${remoteJid}`);
4690
+ console.error(`${TAG13} drop: empty message account=${conn.accountId} from=${remoteJid}`);
4630
4691
  return;
4631
4692
  }
4632
4693
  const dedupKey = `${conn.accountId}:${remoteJid}:${msg.key.id}`;
4633
4694
  if (isDuplicateInbound(dedupKey)) {
4634
- console.error(`${TAG12} drop: duplicate account=${conn.accountId} key=${dedupKey}`);
4695
+ console.error(`${TAG13} drop: duplicate account=${conn.accountId} key=${dedupKey}`);
4635
4696
  return;
4636
4697
  }
4637
4698
  if (msg.key.fromMe) {
4638
4699
  if (msg.key.id && isAgentSentMessage(msg.key.id)) {
4639
- 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}`);
4640
4701
  return;
4641
4702
  }
4642
4703
  const extracted2 = extractMessage(msg);
4643
4704
  if (!extracted2.text) {
4644
- 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}`);
4645
4706
  return;
4646
4707
  }
4647
4708
  const isGroup2 = isGroupJid(remoteJid);
4648
4709
  const senderPhone2 = conn.selfPhone ?? "owner";
4649
- 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}`);
4650
4711
  const reply2 = async (text) => {
4651
4712
  const currentSock = conn.sock;
4652
4713
  if (!currentSock) throw new Error("WhatsApp disconnected \u2014 cannot reply");
@@ -4674,7 +4735,7 @@ async function handleInboundMessage(conn, msg) {
4674
4735
  }
4675
4736
  const extracted = extractMessage(msg);
4676
4737
  if (!extracted.text && !extracted.mediaType) {
4677
- 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}`);
4678
4739
  return;
4679
4740
  }
4680
4741
  let mediaResult;
@@ -4684,7 +4745,7 @@ async function handleInboundMessage(conn, msg) {
4684
4745
  maxBytes: maxMb * 1024 * 1024
4685
4746
  });
4686
4747
  if (!mediaResult) {
4687
- 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}`);
4688
4749
  }
4689
4750
  }
4690
4751
  const isGroup = isGroupJid(remoteJid);
@@ -4729,7 +4790,7 @@ async function handleInboundMessage(conn, msg) {
4729
4790
  });
4730
4791
  }
4731
4792
  console.error(
4732
- `${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}` : "")
4733
4794
  );
4734
4795
  if (!accessResult.allowed) return;
4735
4796
  let groupSubject;
@@ -4772,15 +4833,15 @@ async function handleInboundMessage(conn, msg) {
4772
4833
  if (accessResult.agentType === "public") {
4773
4834
  const hoursResult = await isBusinessOpen(conn.accountId);
4774
4835
  if (!hoursResult.open) {
4775
- console.error(`${TAG12} [${conn.accountId}] dispatch skipped: business closed`);
4836
+ console.error(`${TAG13} [${conn.accountId}] dispatch skipped: business closed`);
4776
4837
  const afterHoursMessage = whatsAppConfig.accounts?.[conn.accountId]?.afterHoursMessage ?? whatsAppConfig.afterHoursMessage;
4777
4838
  if (afterHoursMessage) {
4778
4839
  try {
4779
4840
  await reply(afterHoursMessage);
4780
- 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}`);
4781
4842
  } catch (err) {
4782
4843
  console.error(
4783
- `${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)}`
4784
4845
  );
4785
4846
  }
4786
4847
  }
@@ -5366,7 +5427,7 @@ async function storeGeneratedFile(accountId, filePath) {
5366
5427
  import { writeFile as writeFile3, mkdtemp, rm } from "fs/promises";
5367
5428
  import { tmpdir } from "os";
5368
5429
  import { join as join5 } from "path";
5369
- var TAG13 = "[voice]";
5430
+ var TAG14 = "[voice]";
5370
5431
  var AUDIO_MIME_TYPES = /* @__PURE__ */ new Set([
5371
5432
  "audio/ogg",
5372
5433
  "audio/webm",
@@ -5398,7 +5459,7 @@ async function transcribeVoiceNote(file, source) {
5398
5459
  const bytes = file.size;
5399
5460
  const mimeType = file.type;
5400
5461
  console.error(
5401
- `${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}`
5402
5463
  );
5403
5464
  let tempDir;
5404
5465
  let tempPath;
@@ -5410,7 +5471,7 @@ async function transcribeVoiceNote(file, source) {
5410
5471
  await writeFile3(tempPath, buffer);
5411
5472
  } catch (err) {
5412
5473
  const reason = err instanceof Error ? err.message : String(err);
5413
- console.error(`${TAG13} failed source=${source} error=temp-file-write: ${reason}`);
5474
+ console.error(`${TAG14} failed source=${source} error=temp-file-write: ${reason}`);
5414
5475
  return { ok: false, error: "Could not process voice note" };
5415
5476
  }
5416
5477
  try {
@@ -5418,7 +5479,7 @@ async function transcribeVoiceNote(file, source) {
5418
5479
  if (!sttResult) {
5419
5480
  const elapsed2 = Date.now() - startMs;
5420
5481
  console.error(
5421
- `${TAG13} failed source=${source} error=transcription-failed duration_ms=${elapsed2}`
5482
+ `${TAG14} failed source=${source} error=transcription-failed duration_ms=${elapsed2}`
5422
5483
  );
5423
5484
  return { ok: false, error: "Could not transcribe voice note. Please try again or type your message." };
5424
5485
  }
@@ -5426,7 +5487,7 @@ async function transcribeVoiceNote(file, source) {
5426
5487
  const elapsed = Date.now() - startMs;
5427
5488
  const words = rawText.split(/\s+/).filter(Boolean).length;
5428
5489
  console.error(
5429
- `${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}`
5430
5491
  );
5431
5492
  return {
5432
5493
  ok: true,
@@ -5436,7 +5497,7 @@ async function transcribeVoiceNote(file, source) {
5436
5497
  const elapsed = Date.now() - startMs;
5437
5498
  const reason = err instanceof Error ? err.message : String(err);
5438
5499
  console.error(
5439
- `${TAG13} failed source=${source} error=${reason} duration_ms=${elapsed}`
5500
+ `${TAG14} failed source=${source} error=${reason} duration_ms=${elapsed}`
5440
5501
  );
5441
5502
  return { ok: false, error: "Voice note transcription failed. Please try again or type your message." };
5442
5503
  } finally {
@@ -5471,7 +5532,7 @@ var VERDICT_DEFINITIONS = {
5471
5532
  };
5472
5533
 
5473
5534
  // app/lib/inbound-gateway.ts
5474
- var TAG14 = "[inbound-gateway]";
5535
+ var TAG15 = "[inbound-gateway]";
5475
5536
  var GATEWAY_TIMEOUT_MS = 1e4;
5476
5537
  var MIN_WORDS_FOR_PROCESSING = 5;
5477
5538
  function defaultResult(rawText, latencyMs) {
@@ -5594,11 +5655,11 @@ async function processInbound(rawText, channel) {
5594
5655
  };
5595
5656
  result.fallthrough = false;
5596
5657
  console.warn(
5597
- `${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}`
5598
5659
  );
5599
5660
  } else {
5600
5661
  console.log(
5601
- `${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}`
5602
5663
  );
5603
5664
  }
5604
5665
  return result;
@@ -5615,13 +5676,13 @@ async function processInbound(rawText, channel) {
5615
5676
  const latencyMs = Date.now() - startMs;
5616
5677
  if (llmResult.kind === "fallback") {
5617
5678
  console.warn(
5618
- `${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}`
5619
5680
  );
5620
5681
  return defaultResult(rawText.trim(), latencyMs);
5621
5682
  }
5622
5683
  if (llmResult.kind !== "ok-tool") {
5623
5684
  console.warn(
5624
- `${TAG14} fallthrough channel=${channel} reason=no-tool-response latency_ms=${latencyMs}`
5685
+ `${TAG15} fallthrough channel=${channel} reason=no-tool-response latency_ms=${latencyMs}`
5625
5686
  );
5626
5687
  return defaultResult(rawText.trim(), latencyMs);
5627
5688
  }
@@ -5631,7 +5692,7 @@ async function processInbound(rawText, channel) {
5631
5692
  const verdict = input.verdict;
5632
5693
  if (!processedText || !verdict || !["clean", "suspicious", "discard"].includes(verdict)) {
5633
5694
  console.warn(
5634
- `${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)}`
5635
5696
  );
5636
5697
  return defaultResult(rawText.trim(), latencyMs);
5637
5698
  }
@@ -5659,18 +5720,18 @@ async function processInbound(rawText, channel) {
5659
5720
  fallthrough: false
5660
5721
  };
5661
5722
  console.log(
5662
- `${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}`
5663
5724
  );
5664
5725
  if (verdict !== "clean") {
5665
5726
  console.warn(
5666
- `${TAG14} ${verdict.toUpperCase()} channel=${channel} reason=${reason} promptInjection=${promptInjectionRisk}`
5727
+ `${TAG15} ${verdict.toUpperCase()} channel=${channel} reason=${reason} promptInjection=${promptInjectionRisk}`
5667
5728
  );
5668
5729
  }
5669
5730
  return result;
5670
5731
  } catch (err) {
5671
5732
  const reason = err instanceof Error ? err.message : String(err);
5672
5733
  console.warn(
5673
- `${TAG14} fallthrough channel=${channel} reason=parse-error: ${reason} latency_ms=${latencyMs}`
5734
+ `${TAG15} fallthrough channel=${channel} reason=parse-error: ${reason} latency_ms=${latencyMs}`
5674
5735
  );
5675
5736
  return defaultResult(rawText.trim(), latencyMs);
5676
5737
  }
@@ -6763,7 +6824,7 @@ function checkTelegramAccess(params) {
6763
6824
  }
6764
6825
 
6765
6826
  // server/routes/telegram.ts
6766
- var TAG15 = "[telegram-webhook]";
6827
+ var TAG16 = "[telegram-webhook]";
6767
6828
  var TELEGRAM_API = "https://api.telegram.org";
6768
6829
  function getWebhookSecret(botType) {
6769
6830
  const filePath = botType === "admin" ? TELEGRAM_ADMIN_WEBHOOK_SECRET_FILE : TELEGRAM_WEBHOOK_SECRET_FILE;
@@ -6787,12 +6848,12 @@ async function handleInbound(params) {
6787
6848
  const gatewayChannel = agentType === "admin" ? "telegram-admin" : "telegram-dm";
6788
6849
  registerSession(sessionKey, agentType, accountId);
6789
6850
  console.error(
6790
- `${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`
6791
6852
  );
6792
6853
  const gatewayResult = await processInbound(text, gatewayChannel);
6793
6854
  if (gatewayResult.screening.verdict === "discard") {
6794
6855
  console.error(
6795
- `${TAG15} discarded: senderId=${senderId} chatId=${chatId} reason=${gatewayResult.screening.reason}`
6856
+ `${TAG16} discarded: senderId=${senderId} chatId=${chatId} reason=${gatewayResult.screening.reason}`
6796
6857
  );
6797
6858
  return;
6798
6859
  }
@@ -6815,7 +6876,7 @@ async function handleInbound(params) {
6815
6876
  }
6816
6877
  } catch (err) {
6817
6878
  console.error(
6818
- `${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)}`
6819
6880
  );
6820
6881
  responseText = "I'm having trouble right now. Please try again in a moment.";
6821
6882
  }
@@ -6833,12 +6894,12 @@ async function handleInbound(params) {
6833
6894
  const data = await res.json();
6834
6895
  if (!data.ok) {
6835
6896
  console.error(
6836
- `${TAG15} send-error: chatId=${chatId} error=${data.description ?? "unknown"}`
6897
+ `${TAG16} send-error: chatId=${chatId} error=${data.description ?? "unknown"}`
6837
6898
  );
6838
6899
  }
6839
6900
  } catch (err) {
6840
6901
  console.error(
6841
- `${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)}`
6842
6903
  );
6843
6904
  }
6844
6905
  }
@@ -6846,28 +6907,28 @@ var app6 = new Hono();
6846
6907
  app6.post("/webhook", async (c) => {
6847
6908
  const botType = c.req.query("bot");
6848
6909
  if (!botType || botType !== "public" && botType !== "admin") {
6849
- console.error(`${TAG15} invalid-bot-type: received=${botType ?? "missing"}`);
6910
+ console.error(`${TAG16} invalid-bot-type: received=${botType ?? "missing"}`);
6850
6911
  return c.json({ error: "Missing or invalid bot type" }, 400);
6851
6912
  }
6852
6913
  const storedSecret = getWebhookSecret(botType);
6853
6914
  if (!storedSecret) {
6854
- console.error(`${TAG15} secret=missing botType=${botType}`);
6915
+ console.error(`${TAG16} secret=missing botType=${botType}`);
6855
6916
  return c.json({ error: "Webhook not configured" }, 401);
6856
6917
  }
6857
6918
  const headerSecret = c.req.header("x-telegram-bot-api-secret-token");
6858
6919
  if (!headerSecret || !verifyWebhookSecret(headerSecret, storedSecret)) {
6859
- console.error(`${TAG15} secret=invalid botType=${botType}`);
6920
+ console.error(`${TAG16} secret=invalid botType=${botType}`);
6860
6921
  return c.json({ error: "Unauthorized" }, 401);
6861
6922
  }
6862
6923
  const account = resolveAccount();
6863
6924
  if (!account) {
6864
- console.error(`${TAG15} config=no-account`);
6925
+ console.error(`${TAG16} config=no-account`);
6865
6926
  return c.json({ error: "No account configured" }, 500);
6866
6927
  }
6867
6928
  const tgConfig = account.config.telegram ?? {};
6868
6929
  const botToken = botType === "admin" ? tgConfig.adminBotToken : tgConfig.publicBotToken;
6869
6930
  if (!botToken) {
6870
- console.error(`${TAG15} config=no-token botType=${botType}`);
6931
+ console.error(`${TAG16} config=no-token botType=${botType}`);
6871
6932
  return c.json({ error: `No ${botType} bot token configured` }, 500);
6872
6933
  }
6873
6934
  let update;
@@ -6895,7 +6956,7 @@ app6.post("/webhook", async (c) => {
6895
6956
  }
6896
6957
  const accessResult = checkTelegramAccess({ senderId, botType, config: tgConfig });
6897
6958
  console.error(
6898
- `${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}`
6899
6960
  );
6900
6961
  if (!accessResult.allowed) {
6901
6962
  return c.json({ ok: true });
@@ -6906,7 +6967,7 @@ app6.post("/webhook", async (c) => {
6906
6967
  headers: { "Content-Type": "application/json" },
6907
6968
  body: JSON.stringify({ callback_query_id: callbackId })
6908
6969
  }).catch((err) => {
6909
- 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)}`);
6910
6971
  });
6911
6972
  }
6912
6973
  handleInbound({
@@ -6919,7 +6980,7 @@ app6.post("/webhook", async (c) => {
6919
6980
  agentType: accessResult.agentType
6920
6981
  }).catch((err) => {
6921
6982
  console.error(
6922
- `${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)}`
6923
6984
  );
6924
6985
  });
6925
6986
  return c.json({ ok: true });
@@ -6933,14 +6994,14 @@ import { realpathSync as realpathSync2, readdirSync as readdirSync2, readFileSyn
6933
6994
 
6934
6995
  // app/lib/whatsapp/login.ts
6935
6996
  import { randomUUID as randomUUID6 } from "crypto";
6936
- var TAG16 = "[whatsapp:login]";
6997
+ var TAG17 = "[whatsapp:login]";
6937
6998
  var ACTIVE_LOGIN_TTL_MS = 3 * 6e4;
6938
6999
  var activeLogins = /* @__PURE__ */ new Map();
6939
7000
  function closeSocket(sock) {
6940
7001
  try {
6941
7002
  sock.ws?.close?.();
6942
7003
  } catch (err) {
6943
- console.warn(`${TAG16} socket close error during cleanup: ${String(err)}`);
7004
+ console.warn(`${TAG17} socket close error during cleanup: ${String(err)}`);
6944
7005
  }
6945
7006
  }
6946
7007
  function resetActiveLogin(accountId) {
@@ -6963,7 +7024,7 @@ async function loginConnectionLoop(accountId, login) {
6963
7024
  const current = activeLogins.get(accountId);
6964
7025
  if (current?.id === login.id) {
6965
7026
  current.connected = true;
6966
- console.error(`${TAG16} loginConnectionLoop: connected account=${accountId} attempt=${attempt}`);
7027
+ console.error(`${TAG17} loginConnectionLoop: connected account=${accountId} attempt=${attempt}`);
6967
7028
  }
6968
7029
  return;
6969
7030
  } catch (err) {
@@ -6973,7 +7034,7 @@ async function loginConnectionLoop(accountId, login) {
6973
7034
  if (!classification.shouldRetry || attempt >= LOGIN_MAX_RECONNECTS) {
6974
7035
  if (attempt >= LOGIN_MAX_RECONNECTS) {
6975
7036
  console.error(
6976
- `${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`
6977
7038
  );
6978
7039
  current.error = `Login failed after ${attempt} reconnect attempts: ${formatError(err)}`;
6979
7040
  } else {
@@ -6985,7 +7046,7 @@ async function loginConnectionLoop(accountId, login) {
6985
7046
  attempt++;
6986
7047
  const delay = LOGIN_RECONNECT_DELAYS[attempt - 1] ?? 8e3;
6987
7048
  console.error(
6988
- `${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`
6989
7050
  );
6990
7051
  closeSocket(current.sock);
6991
7052
  await new Promise((r) => setTimeout(r, delay));
@@ -6996,7 +7057,7 @@ async function loginConnectionLoop(accountId, login) {
6996
7057
  current.sock = newSock;
6997
7058
  } catch (sockErr) {
6998
7059
  console.error(
6999
- `${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)}`
7000
7061
  );
7001
7062
  current.error = `Reconnection failed: ${String(sockErr)}`;
7002
7063
  return;
@@ -7010,7 +7071,7 @@ async function startLogin(opts) {
7010
7071
  const hasAuth = await authExists(authDir);
7011
7072
  const selfId = readSelfId(authDir);
7012
7073
  console.error(
7013
- `${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")
7014
7075
  );
7015
7076
  if (hasAuth && !force) {
7016
7077
  const who = selfId.e164 ?? selfId.jid ?? "unknown";
@@ -7022,7 +7083,7 @@ async function startLogin(opts) {
7022
7083
  await clearAuth(authDir);
7023
7084
  const existing = activeLogins.get(accountId);
7024
7085
  if (existing && isLoginFresh(existing) && existing.qrDataUrl && !force) {
7025
- 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)`);
7026
7087
  return {
7027
7088
  qrDataUrl: existing.qrDataUrl,
7028
7089
  qrRaw: existing.qr,
@@ -7030,7 +7091,7 @@ async function startLogin(opts) {
7030
7091
  };
7031
7092
  }
7032
7093
  if (existing) {
7033
- 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`);
7034
7095
  }
7035
7096
  resetActiveLogin(accountId);
7036
7097
  let resolveQr = null;
@@ -7052,14 +7113,14 @@ async function startLogin(opts) {
7052
7113
  onQr: (qr2) => {
7053
7114
  loginQrCount++;
7054
7115
  if (pendingQr) {
7055
- 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)`);
7056
7117
  return;
7057
7118
  }
7058
7119
  pendingQr = qr2;
7059
7120
  const current = activeLogins.get(accountId);
7060
7121
  if (current && !current.qr) current.qr = qr2;
7061
7122
  clearTimeout(qrTimer);
7062
- 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`);
7063
7124
  resolveQr?.(qr2);
7064
7125
  }
7065
7126
  });
@@ -7079,7 +7140,7 @@ async function startLogin(opts) {
7079
7140
  activeLogins.set(accountId, login);
7080
7141
  if (pendingQr && !login.qr) login.qr = pendingQr;
7081
7142
  loginConnectionLoop(accountId, login).catch((err) => {
7082
- console.error(`${TAG16} loginConnectionLoop unexpected error: ${String(err)}`);
7143
+ console.error(`${TAG17} loginConnectionLoop unexpected error: ${String(err)}`);
7083
7144
  const current = activeLogins.get(accountId);
7084
7145
  if (current?.id === login.id) {
7085
7146
  current.error = `Unexpected login error: ${String(err)}`;
@@ -7104,7 +7165,7 @@ async function waitForLogin(opts) {
7104
7165
  const { accountId, timeoutMs = 6e4 } = opts;
7105
7166
  const login = activeLogins.get(accountId);
7106
7167
  console.error(
7107
- `${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")
7108
7169
  );
7109
7170
  if (!login) {
7110
7171
  return { connected: false, message: "No active WhatsApp login in progress." };
@@ -7117,7 +7178,7 @@ async function waitForLogin(opts) {
7117
7178
  while (Date.now() < deadline) {
7118
7179
  if (login.connected) {
7119
7180
  const selfId = readSelfId(login.authDir);
7120
- 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"}`);
7121
7182
  const sock = login.sock;
7122
7183
  const authDir = login.authDir;
7123
7184
  activeLogins.delete(accountId);
@@ -7137,7 +7198,7 @@ async function waitForLogin(opts) {
7137
7198
  await new Promise((r) => setTimeout(r, 1e3));
7138
7199
  }
7139
7200
  const elapsed = Math.round((Date.now() - (deadline - timeoutMs)) / 1e3);
7140
- 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`);
7141
7202
  resetActiveLogin(accountId);
7142
7203
  return { connected: false, message: "Login timed out. Try generating a new QR." };
7143
7204
  }
@@ -7251,17 +7312,17 @@ function serializeWhatsAppSchema() {
7251
7312
  }
7252
7313
 
7253
7314
  // server/routes/whatsapp.ts
7254
- var TAG17 = "[whatsapp:api]";
7315
+ var TAG18 = "[whatsapp:api]";
7255
7316
  var PLATFORM_ROOT4 = process.env.MAXY_PLATFORM_ROOT || "";
7256
7317
  var app7 = new Hono();
7257
7318
  app7.get("/status", (c) => {
7258
7319
  try {
7259
7320
  const status = getStatus();
7260
7321
  const summary = status.map((a) => `${a.accountId}:${a.connected ? "up" : "down"}`).join(", ");
7261
- console.error(`${TAG17} status accounts=${status.length} [${summary}]`);
7322
+ console.error(`${TAG18} status accounts=${status.length} [${summary}]`);
7262
7323
  return c.json({ accounts: status });
7263
7324
  } catch (err) {
7264
- console.error(`${TAG17} status error: ${String(err)}`);
7325
+ console.error(`${TAG18} status error: ${String(err)}`);
7265
7326
  return c.json({ error: String(err) }, 500);
7266
7327
  }
7267
7328
  });
@@ -7272,10 +7333,10 @@ app7.post("/login/start", async (c) => {
7272
7333
  const force = body.force ?? false;
7273
7334
  const authDir = join6(MAXY_DIR, "credentials", "whatsapp", accountId);
7274
7335
  const result = await startLogin({ accountId, authDir, force });
7275
- 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}` : ""}`);
7276
7337
  return c.json(result);
7277
7338
  } catch (err) {
7278
- console.error(`${TAG17} login/start error: ${String(err)}`);
7339
+ console.error(`${TAG18} login/start error: ${String(err)}`);
7279
7340
  return c.json({ error: String(err) }, 500);
7280
7341
  }
7281
7342
  });
@@ -7290,7 +7351,7 @@ app7.post("/login/wait", async (c) => {
7290
7351
  try {
7291
7352
  await registerLoginSocket(accountId, result.sock, result.authDir);
7292
7353
  } catch (regErr) {
7293
- console.error(`${TAG17} registerLoginSocket failed account=${accountId}: ${String(regErr)}`);
7354
+ console.error(`${TAG18} registerLoginSocket failed account=${accountId}: ${String(regErr)}`);
7294
7355
  }
7295
7356
  try {
7296
7357
  const account = resolveAccount();
@@ -7298,16 +7359,16 @@ app7.post("/login/wait", async (c) => {
7298
7359
  const persistResult = persistAfterPairing(account.accountDir, accountId, result.selfPhone ?? null);
7299
7360
  configPersisted = persistResult.ok;
7300
7361
  if (!persistResult.ok) {
7301
- console.error(`${TAG17} config persist failed account=${accountId}: ${persistResult.error}`);
7362
+ console.error(`${TAG18} config persist failed account=${accountId}: ${persistResult.error}`);
7302
7363
  }
7303
7364
  } else {
7304
- console.error(`${TAG17} config persist skipped \u2014 no account resolved`);
7365
+ console.error(`${TAG18} config persist skipped \u2014 no account resolved`);
7305
7366
  }
7306
7367
  } catch (persistErr) {
7307
- console.error(`${TAG17} config persist error account=${accountId}: ${String(persistErr)}`);
7368
+ console.error(`${TAG18} config persist error account=${accountId}: ${String(persistErr)}`);
7308
7369
  }
7309
7370
  }
7310
- 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}`);
7311
7372
  return c.json({
7312
7373
  connected: result.connected,
7313
7374
  message: result.message,
@@ -7315,7 +7376,7 @@ app7.post("/login/wait", async (c) => {
7315
7376
  configPersisted
7316
7377
  });
7317
7378
  } catch (err) {
7318
- console.error(`${TAG17} login/wait error: ${String(err)}`);
7379
+ console.error(`${TAG18} login/wait error: ${String(err)}`);
7319
7380
  return c.json({ error: String(err) }, 500);
7320
7381
  }
7321
7382
  });
@@ -7326,7 +7387,7 @@ app7.post("/disconnect", async (c) => {
7326
7387
  await stopConnection(accountId);
7327
7388
  return c.json({ disconnected: true, accountId });
7328
7389
  } catch (err) {
7329
- console.error(`${TAG17} disconnect error: ${String(err)}`);
7390
+ console.error(`${TAG18} disconnect error: ${String(err)}`);
7330
7391
  return c.json({ error: String(err) }, 500);
7331
7392
  }
7332
7393
  });
@@ -7337,7 +7398,7 @@ app7.post("/reconnect", async (c) => {
7337
7398
  await startConnection(accountId);
7338
7399
  return c.json({ reconnecting: true, accountId });
7339
7400
  } catch (err) {
7340
- console.error(`${TAG17} reconnect error: ${String(err)}`);
7401
+ console.error(`${TAG18} reconnect error: ${String(err)}`);
7341
7402
  return c.json({ error: String(err) }, 500);
7342
7403
  }
7343
7404
  });
@@ -7356,7 +7417,7 @@ app7.post("/send", async (c) => {
7356
7417
  const result = await sendTextMessage(sock, to, text, { accountId });
7357
7418
  return c.json(result);
7358
7419
  } catch (err) {
7359
- console.error(`${TAG17} send error: ${String(err)}`);
7420
+ console.error(`${TAG18} send error: ${String(err)}`);
7360
7421
  return c.json({ error: String(err) }, 500);
7361
7422
  }
7362
7423
  });
@@ -7377,7 +7438,7 @@ app7.post("/config", async (c) => {
7377
7438
  return c.json({ ok: false, error: 'Missing required field "phone" (E.164 format, e.g. +441234567890).' }, 400);
7378
7439
  }
7379
7440
  const result = addAdminPhone(account.accountDir, phone);
7380
- 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}`);
7381
7442
  return c.json(result, result.ok ? 200 : 400);
7382
7443
  }
7383
7444
  case "remove-admin-phone": {
@@ -7385,12 +7446,12 @@ app7.post("/config", async (c) => {
7385
7446
  return c.json({ ok: false, error: 'Missing required field "phone".' }, 400);
7386
7447
  }
7387
7448
  const result = removeAdminPhone(account.accountDir, phone);
7388
- 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}`);
7389
7450
  return c.json(result, result.ok ? 200 : 400);
7390
7451
  }
7391
7452
  case "list-admin-phones": {
7392
7453
  const phones = readAdminPhones(account.accountDir);
7393
- console.error(`${TAG17} config action=list-admin-phones count=${phones.length}`);
7454
+ console.error(`${TAG18} config action=list-admin-phones count=${phones.length}`);
7394
7455
  return c.json({ ok: true, phones });
7395
7456
  }
7396
7457
  case "set-public-agent": {
@@ -7398,14 +7459,14 @@ app7.post("/config", async (c) => {
7398
7459
  return c.json({ ok: false, error: 'Missing required field "slug" (the agent directory name, e.g. "my-agent").' }, 400);
7399
7460
  }
7400
7461
  const result = setPublicAgent(account.accountDir, slug);
7401
- 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}`);
7402
7463
  return c.json(result, result.ok ? 200 : 400);
7403
7464
  }
7404
7465
  case "get-public-agent": {
7405
7466
  const targetAccount = typeof accountId === "string" && accountId.trim() ? accountId.trim() : "default";
7406
7467
  const targetGroup = typeof groupJid === "string" && groupJid.trim() ? groupJid.trim() : void 0;
7407
7468
  const resolved = resolvePublicAgent(account.accountDir, { accountId: targetAccount, groupJid: targetGroup });
7408
- 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"}`);
7409
7470
  return c.json({ ok: true, slug: resolved?.slug ?? null, source: resolved?.source ?? null });
7410
7471
  }
7411
7472
  case "set-group-public-agent": {
@@ -7417,7 +7478,7 @@ app7.post("/config", async (c) => {
7417
7478
  }
7418
7479
  const targetAccount = typeof accountId === "string" && accountId.trim() ? accountId.trim() : "default";
7419
7480
  const result = setGroupPublicAgent(account.accountDir, targetAccount, groupJid, slug);
7420
- 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}`);
7421
7482
  return c.json(result, result.ok ? 200 : 400);
7422
7483
  }
7423
7484
  case "unset-group-public-agent": {
@@ -7426,7 +7487,7 @@ app7.post("/config", async (c) => {
7426
7487
  }
7427
7488
  const targetAccount = typeof accountId === "string" && accountId.trim() ? accountId.trim() : "default";
7428
7489
  const result = unsetGroupPublicAgent(account.accountDir, targetAccount, groupJid);
7429
- 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}`);
7430
7491
  return c.json(result, result.ok ? 200 : 400);
7431
7492
  }
7432
7493
  case "list-public-agents": {
@@ -7443,26 +7504,26 @@ app7.post("/config", async (c) => {
7443
7504
  const config = JSON.parse(readFileSync9(configPath2, "utf-8"));
7444
7505
  agents.push({ slug: entry.name, displayName: config.displayName ?? entry.name });
7445
7506
  } catch {
7446
- 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`);
7447
7508
  }
7448
7509
  }
7449
7510
  } catch (err) {
7450
- 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)}"`);
7451
7512
  }
7452
7513
  }
7453
- console.error(`${TAG17} config action=list-public-agents count=${agents.length}`);
7514
+ console.error(`${TAG18} config action=list-public-agents count=${agents.length}`);
7454
7515
  return c.json({ ok: true, agents });
7455
7516
  }
7456
7517
  case "schema": {
7457
7518
  const text = serializeWhatsAppSchema();
7458
- console.error(`${TAG17} config action=schema`);
7519
+ console.error(`${TAG18} config action=schema`);
7459
7520
  return c.json({ ok: true, text });
7460
7521
  }
7461
7522
  case "list-groups": {
7462
7523
  const groupAccountId = accountId ?? "default";
7463
7524
  const sock = getSocket(groupAccountId);
7464
7525
  if (!sock) {
7465
- 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}`);
7466
7527
  return c.json({ ok: false, error: `WhatsApp account "${groupAccountId}" is not connected. Connect first, then retry.` });
7467
7528
  }
7468
7529
  try {
@@ -7472,10 +7533,10 @@ app7.post("/config", async (c) => {
7472
7533
  name: g.subject ?? g.id,
7473
7534
  participantCount: Array.isArray(g.participants) ? g.participants.length : 0
7474
7535
  }));
7475
- 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}`);
7476
7537
  return c.json({ ok: true, groups });
7477
7538
  } catch (err) {
7478
- 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}`);
7479
7540
  return c.json({ ok: false, error: `Failed to fetch groups: ${String(err)}` });
7480
7541
  }
7481
7542
  }
@@ -7485,12 +7546,12 @@ app7.post("/config", async (c) => {
7485
7546
  }
7486
7547
  const result = updateConfig(account.accountDir, fields);
7487
7548
  const fieldNames = Object.keys(fields);
7488
- 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}`);
7489
7550
  return c.json(result, result.ok ? 200 : 400);
7490
7551
  }
7491
7552
  case "get-config": {
7492
7553
  const waConfig = getConfig(account.accountDir);
7493
- console.error(`${TAG17} config action=get-config`);
7554
+ console.error(`${TAG18} config action=get-config`);
7494
7555
  return c.json({ ok: true, config: waConfig });
7495
7556
  }
7496
7557
  default:
@@ -7500,7 +7561,7 @@ app7.post("/config", async (c) => {
7500
7561
  );
7501
7562
  }
7502
7563
  } catch (err) {
7503
- console.error(`${TAG17} config error: ${String(err)}`);
7564
+ console.error(`${TAG18} config error: ${String(err)}`);
7504
7565
  return c.json({ ok: false, error: String(err) }, 500);
7505
7566
  }
7506
7567
  });
@@ -7522,16 +7583,16 @@ app7.post("/send-document", async (c) => {
7522
7583
  const accountResolved = realpathSync2(accountDir);
7523
7584
  if (!resolvedPath.startsWith(accountResolved + "/")) {
7524
7585
  const sanitised = filePath.replace(accountDir, "<account>/");
7525
- 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`);
7526
7587
  return c.json({ error: "Access denied: file is outside the account directory" }, 403);
7527
7588
  }
7528
7589
  } catch (err) {
7529
7590
  const code = err.code;
7530
7591
  if (code === "ENOENT") {
7531
- console.error(`${TAG17} send-document ENOENT path=${filePath}`);
7592
+ console.error(`${TAG18} send-document ENOENT path=${filePath}`);
7532
7593
  return c.json({ error: `File not found: ${filePath}` }, 404);
7533
7594
  }
7534
- console.error(`${TAG17} send-document path error: ${String(err)}`);
7595
+ console.error(`${TAG18} send-document path error: ${String(err)}`);
7535
7596
  return c.json({ error: String(err) }, 500);
7536
7597
  }
7537
7598
  const fileStat = await stat3(resolvedPath);
@@ -7553,11 +7614,11 @@ app7.post("/send-document", async (c) => {
7553
7614
  caption
7554
7615
  }, { accountId });
7555
7616
  console.error(
7556
- `${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}` : "")
7557
7618
  );
7558
7619
  return c.json(result);
7559
7620
  } catch (err) {
7560
- console.error(`${TAG17} send-document error: ${String(err)}`);
7621
+ console.error(`${TAG18} send-document error: ${String(err)}`);
7561
7622
  return c.json({ error: String(err) }, 500);
7562
7623
  }
7563
7624
  });
@@ -7567,11 +7628,11 @@ app7.get("/activity", (c) => {
7567
7628
  const result = getChannelActivity(accountId);
7568
7629
  const total = result.accounts.reduce((sum, a) => sum + a.total, 0);
7569
7630
  console.error(
7570
- `${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}` : "")
7571
7632
  );
7572
7633
  return c.json(result);
7573
7634
  } catch (err) {
7574
- console.error(`${TAG17} activity error: ${String(err)}`);
7635
+ console.error(`${TAG18} activity error: ${String(err)}`);
7575
7636
  return c.json({ error: String(err) }, 500);
7576
7637
  }
7577
7638
  });
@@ -7590,10 +7651,10 @@ app7.get("/conversations", (c) => {
7590
7651
  };
7591
7652
  });
7592
7653
  conversations.sort((a, b) => (b.lastMessageTimestamp ?? 0) - (a.lastMessageTimestamp ?? 0));
7593
- console.error(`${TAG17} conversations account=${accountId} count=${conversations.length}`);
7654
+ console.error(`${TAG18} conversations account=${accountId} count=${conversations.length}`);
7594
7655
  return c.json({ conversations });
7595
7656
  } catch (err) {
7596
- console.error(`${TAG17} conversations error: ${String(err)}`);
7657
+ console.error(`${TAG18} conversations error: ${String(err)}`);
7597
7658
  return c.json({ error: String(err) }, 500);
7598
7659
  }
7599
7660
  });
@@ -7608,10 +7669,10 @@ app7.get("/messages", (c) => {
7608
7669
  const limit = limitParam ? parseInt(limitParam, 10) : void 0;
7609
7670
  const effectiveLimit = limit && !Number.isNaN(limit) && limit > 0 ? limit : void 0;
7610
7671
  const messages = getMessages(accountId, jid, effectiveLimit);
7611
- 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}`);
7612
7673
  return c.json({ messages });
7613
7674
  } catch (err) {
7614
- console.error(`${TAG17} messages error: ${String(err)}`);
7675
+ console.error(`${TAG18} messages error: ${String(err)}`);
7615
7676
  return c.json({ error: String(err) }, 500);
7616
7677
  }
7617
7678
  });
@@ -7623,12 +7684,12 @@ app7.get("/group-info", async (c) => {
7623
7684
  return c.json({ error: "Missing required parameter: jid" }, 400);
7624
7685
  }
7625
7686
  if (!isGroupJid(jid)) {
7626
- 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}`);
7627
7688
  return c.json({ error: `"${jid}" is not a group JID. Group JIDs end with @g.us.` }, 400);
7628
7689
  }
7629
7690
  const sock = getSocket(accountId);
7630
7691
  if (!sock) {
7631
- console.error(`${TAG17} group-info error="not connected" account=${accountId}`);
7692
+ console.error(`${TAG18} group-info error="not connected" account=${accountId}`);
7632
7693
  return c.json({ error: `WhatsApp account "${accountId}" is not connected. Connect first, then retry.` }, 400);
7633
7694
  }
7634
7695
  const meta = await sock.groupMetadata(jid);
@@ -7641,10 +7702,10 @@ app7.get("/group-info", async (c) => {
7641
7702
  participantCount: meta.participants.length,
7642
7703
  participants: meta.participants.map((p) => ({ jid: p.id, admin: p.admin ?? null }))
7643
7704
  };
7644
- 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}`);
7645
7706
  return c.json(result);
7646
7707
  } catch (err) {
7647
- 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}`);
7648
7709
  return c.json({ error: `Failed to fetch group info: ${String(err)}` }, 500);
7649
7710
  }
7650
7711
  });
@@ -8525,7 +8586,7 @@ var app11 = new Hono();
8525
8586
  app11.post("/cancel", requireAdminSession, async (c) => {
8526
8587
  const session_key = c.var.sessionKey;
8527
8588
  try {
8528
- const { interruptClient: interruptClient2 } = await import("./client-pool-S4UZCYDJ.js");
8589
+ const { interruptClient: interruptClient2 } = await import("./client-pool-LXE7RIRT.js");
8529
8590
  await interruptClient2(session_key);
8530
8591
  return c.json({ ok: true });
8531
8592
  } catch (err) {
@@ -9405,16 +9466,26 @@ app17.get("/", requireAdminSession, async (c) => {
9405
9466
  sessionKey: null,
9406
9467
  name: r.name,
9407
9468
  updatedAt: r.updatedAt,
9408
- phase: "flushed"
9469
+ phase: "flushed",
9470
+ channel: r.channel
9409
9471
  }));
9410
9472
  const inProgressRows = inProgressRaw.map((r) => ({
9411
9473
  conversationId: null,
9412
9474
  sessionKey: r.sessionKey,
9413
9475
  name: null,
9414
9476
  updatedAt: new Date(r.createdAt).toISOString(),
9415
- phase: "pre-flush"
9477
+ phase: "pre-flush",
9478
+ channel: "webchat"
9416
9479
  }));
9417
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
+ );
9418
9489
  return c.json({ sessions });
9419
9490
  } catch (err) {
9420
9491
  console.error(`[sessions-list] Failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -9678,13 +9749,13 @@ async function cdpNavigateNewTab(url, opts = {}) {
9678
9749
  // server/routes/admin/device-browser.ts
9679
9750
  var app19 = new Hono();
9680
9751
  app19.post("/navigate", async (c) => {
9681
- const TAG18 = "[device-url:click]";
9752
+ const TAG19 = "[device-url:click]";
9682
9753
  let body;
9683
9754
  try {
9684
9755
  body = await c.req.json();
9685
9756
  } catch (err) {
9686
9757
  const detail = err instanceof Error ? err.message : String(err);
9687
- 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`);
9688
9759
  return c.json(
9689
9760
  { ok: false, navigateResult: "error", browser: "fallback", detail: "Request body was not valid JSON" },
9690
9761
  400
@@ -9694,7 +9765,7 @@ app19.post("/navigate", async (c) => {
9694
9765
  const intent = typeof body.intent === "string" ? body.intent : "";
9695
9766
  const hostname2 = typeof body.hostname === "string" ? body.hostname : "";
9696
9767
  if (!url) {
9697
- 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`);
9698
9769
  return c.json(
9699
9770
  { ok: false, navigateResult: "error", browser: "fallback", detail: "url field is required" },
9700
9771
  400
@@ -9704,7 +9775,7 @@ app19.post("/navigate", async (c) => {
9704
9775
  try {
9705
9776
  parsed = new URL(url);
9706
9777
  } catch {
9707
- 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`);
9708
9779
  return c.json(
9709
9780
  { ok: false, navigateResult: "error", browser: "fallback", detail: "url is not a valid URL" },
9710
9781
  400
@@ -9712,7 +9783,7 @@ app19.post("/navigate", async (c) => {
9712
9783
  }
9713
9784
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
9714
9785
  console.error(
9715
- `${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`
9716
9787
  );
9717
9788
  return c.json(
9718
9789
  {
@@ -9728,7 +9799,7 @@ app19.post("/navigate", async (c) => {
9728
9799
  const cdpOk = await ensureCdp(transport);
9729
9800
  if (!cdpOk) {
9730
9801
  console.error(
9731
- `${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)}`
9732
9803
  );
9733
9804
  return c.json(
9734
9805
  {
@@ -9744,7 +9815,7 @@ app19.post("/navigate", async (c) => {
9744
9815
  const browser = outcome.result === "ok" ? "vnc" : "fallback";
9745
9816
  const detailStr = outcome.detail ? ` detail=${JSON.stringify(outcome.detail.length > 230 ? outcome.detail.slice(0, 227) + "..." : outcome.detail)}` : "";
9746
9817
  console.error(
9747
- `${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}`
9748
9819
  );
9749
9820
  if (outcome.result !== "ok") {
9750
9821
  return c.json(
@@ -9775,18 +9846,18 @@ var ALLOWED_EVENTS = /* @__PURE__ */ new Set([
9775
9846
  ]);
9776
9847
  var app20 = new Hono();
9777
9848
  app20.post("/", async (c) => {
9778
- const TAG18 = "[admin:events]";
9849
+ const TAG19 = "[admin:events]";
9779
9850
  let body;
9780
9851
  try {
9781
9852
  body = await c.req.json();
9782
9853
  } catch (err) {
9783
9854
  const detail = err instanceof Error ? err.message : String(err);
9784
- console.error(`${TAG18} reject reason=body-not-json detail=${detail}`);
9855
+ console.error(`${TAG19} reject reason=body-not-json detail=${detail}`);
9785
9856
  return c.json({ ok: false, detail: "Request body was not valid JSON" }, 400);
9786
9857
  }
9787
9858
  const event = typeof body.event === "string" ? body.event : "";
9788
9859
  if (!ALLOWED_EVENTS.has(event)) {
9789
- 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)}`);
9790
9861
  return c.json({ ok: false, detail: `Event "${event}" is not allowed` }, 400);
9791
9862
  }
9792
9863
  const rawFields = body.fields && typeof body.fields === "object" ? body.fields : {};
@@ -10067,6 +10138,14 @@ app21.post("/setup", requireAdminSession, async (c) => {
10067
10138
  const publicFqdn = body.publicLabel ? `${body.publicLabel}.${body.publicDomain}` : void 0;
10068
10139
  const apex = body.apex && body.apex.length > 0 ? body.apex : void 0;
10069
10140
  log(`phase=submit-received admin=${adminFqdn} public=${publicFqdn ?? "absent"} apex=${apex ?? "absent"}`);
10141
+ const account = resolveAccount();
10142
+ if (!account) {
10143
+ return err("script", "No account on disk for this device \u2014 re-run installer.");
10144
+ }
10145
+ if (account.accountId !== accountId) {
10146
+ return err("script", `Session/device account drift: session=${accountId.slice(0, 8)} device=${account.accountId.slice(0, 8)}`);
10147
+ }
10148
+ const accountDir = account.accountDir;
10070
10149
  try {
10071
10150
  await setRemotePassword(body.password);
10072
10151
  log(`phase=password-set`);
@@ -10092,7 +10171,7 @@ app21.post("/setup", requireAdminSession, async (c) => {
10092
10171
  let actionId;
10093
10172
  let unit;
10094
10173
  try {
10095
- const launched = await launchAction("cloudflare-setup", { positional: args, streamLogPath });
10174
+ const launched = await launchAction("cloudflare-setup", { positional: args, streamLogPath, accountDir });
10096
10175
  actionId = launched.actionId;
10097
10176
  unit = launched.unit;
10098
10177
  } catch (e) {
@@ -11128,6 +11207,8 @@ function plainProperties(properties) {
11128
11207
  // server/routes/admin/graph-search.ts
11129
11208
  var DEFAULT_LIMIT = 20;
11130
11209
  var MAX_LIMIT = 2e3;
11210
+ var MESSAGE_FAMILY_LABELS = ["Message", "UserMessage", "AssistantMessage", "WhatsAppMessage"];
11211
+ var CONVERSATION_PARENT_LABELS = /* @__PURE__ */ new Set(["AdminConversation", "PublicConversation"]);
11131
11212
  var app23 = new Hono();
11132
11213
  app23.get("/", requireAdminSession, async (c) => {
11133
11214
  const sessionKey = c.var.sessionKey;
@@ -11146,13 +11227,15 @@ app23.get("/", requireAdminSession, async (c) => {
11146
11227
  }
11147
11228
  const parsedLimit = rawLimit ? parseInt(rawLimit, 10) : DEFAULT_LIMIT;
11148
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;
11149
11232
  const started = Date.now();
11150
11233
  const session = getSession();
11151
11234
  try {
11152
11235
  const res = await hybrid(session, embed, {
11153
11236
  query: q,
11154
11237
  accountId,
11155
- labels,
11238
+ labels: expandedLabels,
11156
11239
  limit,
11157
11240
  degradeOnEmbedFailure: true
11158
11241
  });
@@ -11161,11 +11244,59 @@ app23.get("/", requireAdminSession, async (c) => {
11161
11244
  console.error(`[graph-search] embed-unavailable err="${res.embedError}" \u2014 bm25-only`);
11162
11245
  console.error(`[graph-search] embed-degraded query="${q}" results=${res.results.length}`);
11163
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
+ }
11164
11295
  console.error(
11165
- `[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}`
11166
11297
  );
11167
11298
  return c.json({
11168
- results: res.results,
11299
+ results: resolvedResults,
11169
11300
  mode: res.mode,
11170
11301
  embedError: res.embedError,
11171
11302
  elapsedMs: total
@@ -11364,6 +11495,10 @@ async function handleDefault(c, accountId) {
11364
11495
  const includeTrashed = c.req.query("includeTrashed") === "1";
11365
11496
  const includeAgentActions = c.req.query("includeAgentActions") === "1";
11366
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);
11367
11502
  if (labels.length === 0) {
11368
11503
  console.error(
11369
11504
  `[graph-page] load rejected reason=missing-filter account=${accountId} labels=`
@@ -11395,7 +11530,7 @@ async function handleDefault(c, accountId) {
11395
11530
  try {
11396
11531
  const countCypher = includeTrashed ? DEFAULT_COUNT_CYPHER_INCLUDE_TRASHED : DEFAULT_COUNT_CYPHER;
11397
11532
  const fetchCypher = includeTrashed ? DEFAULT_FETCH_CYPHER_INCLUDE_TRASHED : DEFAULT_FETCH_CYPHER;
11398
- const cypherParams = { accountId, labels, agentActionLabels };
11533
+ const cypherParams = { accountId, labels, agentActionLabels, channel, messageSublabel };
11399
11534
  const result = await session.executeRead(async (tx) => {
11400
11535
  const countResult = await tx.run(countCypher, cypherParams);
11401
11536
  const matchedRaw = countResult.records[0]?.get("matched");
@@ -11427,8 +11562,10 @@ async function handleDefault(c, accountId) {
11427
11562
  const nodes = result.rawNodes.map((n) => pruneNode(n, warnedClasses, conversationWarnings));
11428
11563
  const edges = result.rawEdges.filter((e) => e && e.id != null).map((e) => pruneEdge(e, warnedClasses));
11429
11564
  const trashedSuffix = includeTrashed ? " includeTrashed=1" : "";
11565
+ const channelSuffix = channel.length > 0 ? ` channel=${channel.join(",")}` : "";
11566
+ const sublabelSuffix = messageSublabel.length > 0 ? ` messageSublabel=${messageSublabel.join(",")}` : "";
11430
11567
  console.error(
11431
- `[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}`
11432
11569
  );
11433
11570
  return c.json({ nodes, edges });
11434
11571
  } catch (err) {
@@ -11524,13 +11661,22 @@ async function handleNeighbourhood(c, accountId) {
11524
11661
  }
11525
11662
  }
11526
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
+ `;
11527
11673
  var DEFAULT_COUNT_CYPHER = `
11528
11674
  MATCH (n)
11529
11675
  WHERE n.accountId = $accountId
11530
11676
  AND any(lbl IN labels(n) WHERE lbl IN $labels)
11531
11677
  AND NOT any(lbl IN labels(n) WHERE lbl IN $agentActionLabels)
11532
11678
  AND NOT n:Trashed
11533
- AND n.deletedAt IS NULL
11679
+ AND n.deletedAt IS NULL${SUBFACET_PREDICATE}
11534
11680
  RETURN count(n) AS matched
11535
11681
  `;
11536
11682
  var CONVERSATION_PROPS_PROJECTION = `CASE
@@ -11544,7 +11690,7 @@ var DEFAULT_FETCH_CYPHER = `
11544
11690
  AND any(lbl IN labels(n) WHERE lbl IN $labels)
11545
11691
  AND NOT any(lbl IN labels(n) WHERE lbl IN $agentActionLabels)
11546
11692
  AND NOT n:Trashed
11547
- AND n.deletedAt IS NULL
11693
+ AND n.deletedAt IS NULL${SUBFACET_PREDICATE}
11548
11694
  WITH collect(n) AS nodes, collect(elementId(n)) AS nodeIds
11549
11695
  UNWIND nodes AS n
11550
11696
  OPTIONAL MATCH (n)-[r]-(m)
@@ -11566,7 +11712,7 @@ var DEFAULT_COUNT_CYPHER_INCLUDE_TRASHED = `
11566
11712
  WHERE n.accountId = $accountId
11567
11713
  AND any(lbl IN labels(n) WHERE lbl IN $labels)
11568
11714
  AND NOT any(lbl IN labels(n) WHERE lbl IN $agentActionLabels)
11569
- AND n.deletedAt IS NULL
11715
+ AND n.deletedAt IS NULL${SUBFACET_PREDICATE}
11570
11716
  RETURN count(n) AS matched
11571
11717
  `;
11572
11718
  var DEFAULT_FETCH_CYPHER_INCLUDE_TRASHED = `
@@ -11574,7 +11720,7 @@ var DEFAULT_FETCH_CYPHER_INCLUDE_TRASHED = `
11574
11720
  WHERE n.accountId = $accountId
11575
11721
  AND any(lbl IN labels(n) WHERE lbl IN $labels)
11576
11722
  AND NOT any(lbl IN labels(n) WHERE lbl IN $agentActionLabels)
11577
- AND n.deletedAt IS NULL
11723
+ AND n.deletedAt IS NULL${SUBFACET_PREDICATE}
11578
11724
  WITH collect(n) AS nodes, collect(elementId(n)) AS nodeIds
11579
11725
  UNWIND nodes AS n
11580
11726
  OPTIONAL MATCH (n)-[r]-(m)