@rubytech/create-maxy 1.0.806 → 1.0.808

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 (30) 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/admin/skills/onboarding/SKILL.md +2 -0
  5. package/payload/platform/plugins/docs/references/cloudflare.md +1 -0
  6. package/payload/platform/plugins/docs/references/graph.md +42 -0
  7. package/payload/platform/plugins/docs/references/internals.md +3 -1
  8. package/payload/platform/plugins/docs/references/memory-guide.md +4 -0
  9. package/payload/platform/plugins/docs/references/troubleshooting.md +19 -1
  10. package/payload/platform/plugins/memory/mcp/dist/tools/profile-read.d.ts.map +1 -1
  11. package/payload/platform/plugins/memory/mcp/dist/tools/profile-read.js +19 -0
  12. package/payload/platform/plugins/memory/mcp/dist/tools/profile-read.js.map +1 -1
  13. package/payload/platform/templates/agents/admin/IDENTITY.md +4 -1
  14. package/payload/platform/templates/agents/admin/SOUL.md +2 -0
  15. package/payload/server/chunk-CRWLE6BZ.js +3511 -0
  16. package/payload/server/chunk-LSUMH6OF.js +9993 -0
  17. package/payload/server/chunk-V3VLAL7N.js +10009 -0
  18. package/payload/server/chunk-YULDSPAC.js +3484 -0
  19. package/payload/server/client-pool-LXE7RIRT.js +31 -0
  20. package/payload/server/client-pool-N2Y57223.js +31 -0
  21. package/payload/server/maxy-edge.js +5 -4
  22. package/payload/server/neo4j-migrations-HEECOAGK.js +128 -0
  23. package/payload/server/public/assets/admin-MxaCgGHZ.js +352 -0
  24. package/payload/server/public/assets/{graph-CBu0rtrP.js → graph-CDwy6Qw1.js} +1 -1
  25. package/payload/server/public/assets/page-DEyK-lSN.js +50 -0
  26. package/payload/server/public/graph.html +2 -2
  27. package/payload/server/public/index.html +2 -2
  28. package/payload/server/server.js +658 -278
  29. package/payload/server/public/assets/admin-BYsaXlDv.js +0 -352
  30. package/payload/server/public/assets/page-BNM63zsb.js +0 -50
@@ -6,6 +6,7 @@ import {
6
6
  TELEGRAM_ADMIN_WEBHOOK_SECRET_FILE,
7
7
  TELEGRAM_WEBHOOK_SECRET_FILE,
8
8
  USERS_FILE,
9
+ actionLogPath,
9
10
  autoDeliverPremiumPlugins,
10
11
  buildX11Env,
11
12
  callOauthLlm,
@@ -29,6 +30,7 @@ import {
29
30
  launchAction,
30
31
  load,
31
32
  logPath,
33
+ reconcileCloudflareSetupFromLog,
32
34
  recordFailedAttempt,
33
35
  render,
34
36
  renderLoginPage,
@@ -50,7 +52,7 @@ import {
50
52
  vncLog,
51
53
  waitForExit,
52
54
  writeChromiumWrapper
53
- } from "./chunk-SC3ZSD7N.js";
55
+ } from "./chunk-V3VLAL7N.js";
54
56
  import {
55
57
  ACCOUNTS_DIR,
56
58
  GREETING_DIRECTIVE,
@@ -65,6 +67,7 @@ import {
65
67
  deleteAgentProjection,
66
68
  deleteConversation,
67
69
  embed,
70
+ ensureConversation,
68
71
  fetchBranding,
69
72
  findGroupBySlug,
70
73
  findRecentConversation,
@@ -113,7 +116,7 @@ import {
113
116
  verifyAndGetConversationUpdatedAt,
114
117
  verifyConversationOwnership,
115
118
  writeAdminUserAndPerson
116
- } from "./chunk-LTIWPCUF.js";
119
+ } from "./chunk-CRWLE6BZ.js";
117
120
  import {
118
121
  __commonJS,
119
122
  __toESM
@@ -616,8 +619,8 @@ var serveStatic = (options = { root: "" }) => {
616
619
  };
617
620
 
618
621
  // server/index.ts
619
- import { readFileSync as readFileSync17, existsSync as existsSync24, watchFile } from "fs";
620
- import { resolve as resolve24, join as join10, basename as basename7 } from "path";
622
+ import { readFileSync as readFileSync18, existsSync as existsSync25, watchFile } from "fs";
623
+ import { resolve as resolve24, join as join11, basename as basename7 } from "path";
621
624
  import { homedir as homedir2 } from "os";
622
625
 
623
626
  // app/lib/agent-slug-pattern.ts
@@ -803,6 +806,28 @@ function defaultRules() {
803
806
  scope: "session",
804
807
  suggestedAction: "The WebFetch SPA preflight has fired more than once in this conversation. Either the agent is ignoring the loud-failure directive (retrying WebFetch after seeing WEBFETCH_CANNOT_READ_JS_SPA), or multiple SPA URLs are being asked about. Read the conversation's stream log for the [tool-use] / [tool-result] sequence around each occurrence \u2014 if the agent dispatched WebFetch on the same URL or substituted Playwright silently, revisit the IDENTITY.md `Tool Failure Discipline` paragraph that names structured-error handling."
805
808
  },
809
+ {
810
+ // Task 867 — fires when setup-tunnel.sh emits step=done but no
811
+ // `[persist] role=user … Cloudflare setup completed (actionId: <id>)`
812
+ // line appears within 60s. Covers the three failure modes of the
813
+ // action-relay-queue plumbing: queue-write failed, boot-drain consumer
814
+ // skipped the record, or the agent's hoisted persist threw. The
815
+ // followup pattern is intentionally narrow to the cloudflare-setup
816
+ // relay shape (not a general "any user persist") so a benign user
817
+ // typing in chat does not satisfy the followup. logSource=any so
818
+ // the rule sees both the script tee (in stream logs / server.log)
819
+ // and the [persist] line (server.log).
820
+ id: "cloudflare-setup-relay-not-acknowledged",
821
+ name: "Cloudflare-setup completed but the chat relay never acknowledged",
822
+ type: "absent-followup",
823
+ logSource: "any",
824
+ pattern: "\\[script:setup-tunnel\\] step=done",
825
+ followupPattern: "\\[persist\\] .*role=user.* Cloudflare setup completed \\(actionId:",
826
+ followupWindowMs: 6e4,
827
+ thresholdCount: 0,
828
+ thresholdWindowMinutes: 0,
829
+ suggestedAction: "[Task 867] cloudflare-setup completed but the post-action relay never reached the chat history. Check `[action-relay-queue] phase=enqueued` (queue write), `[action-completion-relay] phase=consumed` (boot-drain ran), and `[persist] role=user \u2026 Cloudflare setup completed` (graph write). One of those is missing; the matching grep recipe is in the Task 867 brief Observability section."
830
+ },
806
831
  {
807
832
  // Task 538: fires when a [spawn] line appears in a conversation's stream
808
833
  // log but no subprocess-lifecycle marker follows within 10s. The three
@@ -3739,6 +3764,37 @@ function sanitizeReason(err) {
3739
3764
  return `${err.name}:${msg}`;
3740
3765
  }
3741
3766
 
3767
+ // app/lib/whatsapp/ensure-conversation.ts
3768
+ var TAG8 = "[whatsapp-persist]";
3769
+ async function ensureWhatsAppConversation(input) {
3770
+ const t0 = Date.now();
3771
+ try {
3772
+ const result = await ensureConversation(
3773
+ input.accountId,
3774
+ input.agentType,
3775
+ input.sessionKey
3776
+ );
3777
+ const ms = Date.now() - t0;
3778
+ if (!result.conversationId) {
3779
+ console.error(
3780
+ `${TAG8} conversation-merged FAIL sessionKey=${input.sessionKey} reason=null-conversationId ms=${ms}`
3781
+ );
3782
+ return null;
3783
+ }
3784
+ console.error(
3785
+ `${TAG8} conversation-merged sessionKey=${input.sessionKey} agentType=${input.agentType} channel=whatsapp created=${result.created} ms=${ms}`
3786
+ );
3787
+ return { conversationId: result.conversationId, created: result.created };
3788
+ } catch (err) {
3789
+ const ms = Date.now() - t0;
3790
+ const reason = err instanceof Error ? `${err.name}:${err.message.slice(0, 200)}` : String(err).slice(0, 200);
3791
+ console.error(
3792
+ `${TAG8} conversation-merged FAIL sessionKey=${input.sessionKey} reason=${reason} ms=${ms}`
3793
+ );
3794
+ return null;
3795
+ }
3796
+ }
3797
+
3742
3798
  // app/lib/whatsapp/inbound/media.ts
3743
3799
  import { randomUUID as randomUUID3 } from "crypto";
3744
3800
  import { writeFile, mkdir } from "fs/promises";
@@ -3748,7 +3804,7 @@ import {
3748
3804
  downloadContentFromMessage,
3749
3805
  normalizeMessageContent as normalizeMessageContent2
3750
3806
  } from "@whiskeysockets/baileys";
3751
- var TAG8 = "[whatsapp:media]";
3807
+ var TAG9 = "[whatsapp:media]";
3752
3808
  var MEDIA_DIR = "/tmp/maxy-media";
3753
3809
  function mimeToExt(mimetype) {
3754
3810
  const map = {
@@ -3804,25 +3860,25 @@ async function downloadInboundMedia(msg, sock, opts) {
3804
3860
  }
3805
3861
  );
3806
3862
  if (!buffer || buffer.length === 0) {
3807
- console.error(`${TAG8} primary download returned empty, trying direct fallback`);
3863
+ console.error(`${TAG9} primary download returned empty, trying direct fallback`);
3808
3864
  const downloadable = getDownloadableContent(content);
3809
3865
  if (downloadable) {
3810
3866
  try {
3811
3867
  const stream = await downloadContentFromMessage(downloadable.downloadable, downloadable.mediaType);
3812
3868
  buffer = await streamToBuffer(stream);
3813
3869
  } catch (fallbackErr) {
3814
- console.error(`${TAG8} direct download fallback failed: ${String(fallbackErr)}`);
3870
+ console.error(`${TAG9} direct download fallback failed: ${String(fallbackErr)}`);
3815
3871
  }
3816
3872
  }
3817
3873
  }
3818
3874
  if (!buffer || buffer.length === 0) {
3819
- console.error(`${TAG8} download failed: empty buffer for ${mimetype ?? "unknown"}`);
3875
+ console.error(`${TAG9} download failed: empty buffer for ${mimetype ?? "unknown"}`);
3820
3876
  return void 0;
3821
3877
  }
3822
3878
  if (buffer.length > maxBytes) {
3823
3879
  const sizeMB = (buffer.length / (1024 * 1024)).toFixed(1);
3824
3880
  const limitMB = (maxBytes / (1024 * 1024)).toFixed(0);
3825
- console.error(`${TAG8} media too large type=${mimetype ?? "unknown"} size=${sizeMB}MB limit=${limitMB}MB`);
3881
+ console.error(`${TAG9} media too large type=${mimetype ?? "unknown"} size=${sizeMB}MB limit=${limitMB}MB`);
3826
3882
  return void 0;
3827
3883
  }
3828
3884
  await mkdir(MEDIA_DIR, { recursive: true });
@@ -3831,20 +3887,20 @@ async function downloadInboundMedia(msg, sock, opts) {
3831
3887
  const filePath = join4(MEDIA_DIR, filename);
3832
3888
  await writeFile(filePath, buffer);
3833
3889
  const sizeKB = (buffer.length / 1024).toFixed(0);
3834
- console.error(`${TAG8} media downloaded type=${mimetype ?? "unknown"} size=${sizeKB}KB path=${filePath}`);
3890
+ console.error(`${TAG9} media downloaded type=${mimetype ?? "unknown"} size=${sizeKB}KB path=${filePath}`);
3835
3891
  return {
3836
3892
  path: filePath,
3837
3893
  mimetype: mimetype ?? "application/octet-stream",
3838
3894
  size: buffer.length
3839
3895
  };
3840
3896
  } catch (err) {
3841
- console.error(`${TAG8} media download failed type=${mimetype ?? "unknown"} error=${String(err)}`);
3897
+ console.error(`${TAG9} media download failed type=${mimetype ?? "unknown"} error=${String(err)}`);
3842
3898
  return void 0;
3843
3899
  }
3844
3900
  }
3845
3901
 
3846
3902
  // app/lib/whatsapp/inbound/debounce.ts
3847
- var TAG9 = "[whatsapp:debounce]";
3903
+ var TAG10 = "[whatsapp:debounce]";
3848
3904
  var STT_TAG = "[whatsapp:stt-await]";
3849
3905
  function createInboundDebouncer(opts) {
3850
3906
  const { debounceMs, buildKey, onFlush, onError } = opts;
@@ -3875,7 +3931,7 @@ function createInboundDebouncer(opts) {
3875
3931
  pending.delete(key);
3876
3932
  const batchSize = batch.entries.length;
3877
3933
  try {
3878
- console.error(`${TAG9} debounce flush key=${key} batchSize=${batchSize}`);
3934
+ console.error(`${TAG10} debounce flush key=${key} batchSize=${batchSize}`);
3879
3935
  const result = onFlush(batch.entries);
3880
3936
  if (result && typeof result.catch === "function") {
3881
3937
  result.catch(onError);
@@ -3947,7 +4003,7 @@ function createInboundDebouncer(opts) {
3947
4003
  }
3948
4004
 
3949
4005
  // app/lib/whatsapp/opening-hours.ts
3950
- var TAG10 = "[whatsapp:hours]";
4006
+ var TAG11 = "[whatsapp:hours]";
3951
4007
  async function isBusinessOpen(accountId) {
3952
4008
  try {
3953
4009
  const timezone = await resolveTimezone(accountId);
@@ -3965,7 +4021,7 @@ async function isBusinessOpen(accountId) {
3965
4021
  { accountId, dayOfWeek, previousDayOfWeek }
3966
4022
  );
3967
4023
  if (result.records.length === 0) {
3968
- console.error(`${TAG10} [${accountId}] business hours check: no opening hours configured, treating as open`);
4024
+ console.error(`${TAG11} [${accountId}] business hours check: no opening hours configured, treating as open`);
3969
4025
  return { open: true, reason: "no opening hours configured" };
3970
4026
  }
3971
4027
  const specs = result.records.filter((r) => r.get("opens") != null && r.get("closes") != null).map((r) => ({
@@ -3974,7 +4030,7 @@ async function isBusinessOpen(accountId) {
3974
4030
  closes: String(r.get("closes")).trim()
3975
4031
  }));
3976
4032
  if (specs.length === 0) {
3977
- console.error(`${TAG10} [${accountId}] business hours check: no opening hours configured, treating as open`);
4033
+ console.error(`${TAG11} [${accountId}] business hours check: no opening hours configured, treating as open`);
3978
4034
  return { open: true, reason: "no opening hours configured" };
3979
4035
  }
3980
4036
  for (const spec of specs) {
@@ -3984,7 +4040,7 @@ async function isBusinessOpen(accountId) {
3984
4040
  if (spec.opens > spec.closes && currentTime < spec.closes) {
3985
4041
  const hoursStr = `${spec.opens}-${spec.closes} (${spec.day})`;
3986
4042
  console.error(
3987
- `${TAG10} [${accountId}] business hours check: open (day=${dayOfWeek}, time=${currentTime}, hours=${hoursStr})`
4043
+ `${TAG11} [${accountId}] business hours check: open (day=${dayOfWeek}, time=${currentTime}, hours=${hoursStr})`
3988
4044
  );
3989
4045
  return {
3990
4046
  open: true,
@@ -3997,7 +4053,7 @@ async function isBusinessOpen(accountId) {
3997
4053
  } else if (isTimeInRange(currentTime, spec.opens, spec.closes)) {
3998
4054
  const hoursStr = `${spec.opens}-${spec.closes}`;
3999
4055
  console.error(
4000
- `${TAG10} [${accountId}] business hours check: open (day=${dayOfWeek}, time=${currentTime}, hours=${hoursStr})`
4056
+ `${TAG11} [${accountId}] business hours check: open (day=${dayOfWeek}, time=${currentTime}, hours=${hoursStr})`
4001
4057
  );
4002
4058
  return {
4003
4059
  open: true,
@@ -4011,7 +4067,7 @@ async function isBusinessOpen(accountId) {
4011
4067
  const todaySpecs = specs.filter((s) => s.day === dayOfWeek);
4012
4068
  const allHours = todaySpecs.map((s) => `${s.opens}-${s.closes}`).join(", ") || "none today";
4013
4069
  console.error(
4014
- `${TAG10} [${accountId}] business hours check: closed (day=${dayOfWeek}, time=${currentTime}, hours=${allHours})`
4070
+ `${TAG11} [${accountId}] business hours check: closed (day=${dayOfWeek}, time=${currentTime}, hours=${allHours})`
4015
4071
  );
4016
4072
  return {
4017
4073
  open: false,
@@ -4025,7 +4081,7 @@ async function isBusinessOpen(accountId) {
4025
4081
  }
4026
4082
  } catch (err) {
4027
4083
  console.error(
4028
- `${TAG10} [${accountId}] business hours check failed, treating as open: ${err instanceof Error ? err.message : String(err)}`
4084
+ `${TAG11} [${accountId}] business hours check failed, treating as open: ${err instanceof Error ? err.message : String(err)}`
4029
4085
  );
4030
4086
  return { open: true, reason: "hours check failed (treating as open)" };
4031
4087
  }
@@ -4071,7 +4127,7 @@ import { execFile } from "child_process";
4071
4127
  import { unlink, stat } from "fs/promises";
4072
4128
  import { promisify } from "util";
4073
4129
  var execFileAsync = promisify(execFile);
4074
- var TAG11 = "[stt]";
4130
+ var TAG12 = "[stt]";
4075
4131
  var WHISPER_BINARY = process.env.WHISPER_BINARY ?? "/opt/whisper.cpp/build/bin/whisper-cli";
4076
4132
  var WHISPER_MODEL = process.env.WHISPER_MODEL ?? "/opt/whisper.cpp/models/ggml-base.bin";
4077
4133
  var WHISPER_TIMEOUT_MS = 20 * 60 * 1e3;
@@ -4082,11 +4138,11 @@ async function transcribe(audioPath, mimetype) {
4082
4138
  const s = await stat(audioPath);
4083
4139
  audioBytes = s.size;
4084
4140
  } catch {
4085
- console.error(`${TAG11} failed: file not readable path=${audioPath}`);
4141
+ console.error(`${TAG12} failed: file not readable path=${audioPath}`);
4086
4142
  return void 0;
4087
4143
  }
4088
4144
  console.error(
4089
- `${TAG11} start provider=whisper-local audio_bytes=${audioBytes} mimetype=${mimetype} path=${audioPath}`
4145
+ `${TAG12} start provider=whisper-local audio_bytes=${audioBytes} mimetype=${mimetype} path=${audioPath}`
4090
4146
  );
4091
4147
  const wavPath = audioPath.replace(/\.[^.]+$/, "") + ".wav";
4092
4148
  try {
@@ -4105,7 +4161,7 @@ async function transcribe(audioPath, mimetype) {
4105
4161
  ], { timeout: 3e4 });
4106
4162
  } catch (err) {
4107
4163
  const reason = err instanceof Error ? err.message : String(err);
4108
- console.error(`${TAG11} failed: ffmpeg conversion error=${reason}`);
4164
+ console.error(`${TAG12} failed: ffmpeg conversion error=${reason}`);
4109
4165
  return void 0;
4110
4166
  }
4111
4167
  try {
@@ -4123,20 +4179,20 @@ async function transcribe(audioPath, mimetype) {
4123
4179
  const text = stdout.trim();
4124
4180
  const durationMs = Date.now() - startMs;
4125
4181
  if (!text) {
4126
- console.error(`${TAG11} failed: whisper returned empty output duration_ms=${durationMs}`);
4182
+ console.error(`${TAG12} failed: whisper returned empty output duration_ms=${durationMs}`);
4127
4183
  return void 0;
4128
4184
  }
4129
4185
  const langMatch = stderr.match(/auto-detected language:\s*(\w+)/);
4130
4186
  const language = langMatch?.[1] ?? "unknown";
4131
4187
  const words = text.split(/\s+/).filter(Boolean).length;
4132
4188
  console.error(
4133
- `${TAG11} done provider=whisper-local duration_ms=${durationMs} words=${words} lang=${language}`
4189
+ `${TAG12} done provider=whisper-local duration_ms=${durationMs} words=${words} lang=${language}`
4134
4190
  );
4135
4191
  return { text, language, durationMs };
4136
4192
  } catch (err) {
4137
4193
  const durationMs = Date.now() - startMs;
4138
4194
  const reason = err instanceof Error ? err.message : String(err);
4139
- console.error(`${TAG11} failed provider=whisper-local duration_ms=${durationMs} error=${reason}`);
4195
+ console.error(`${TAG12} failed provider=whisper-local duration_ms=${durationMs} error=${reason}`);
4140
4196
  return void 0;
4141
4197
  } finally {
4142
4198
  unlink(wavPath).catch(() => {
@@ -4145,7 +4201,7 @@ async function transcribe(audioPath, mimetype) {
4145
4201
  }
4146
4202
 
4147
4203
  // app/lib/whatsapp/manager.ts
4148
- var TAG12 = "[whatsapp:manager]";
4204
+ var TAG13 = "[whatsapp:manager]";
4149
4205
  var MAX_RECONNECT_ATTEMPTS = 10;
4150
4206
  var MESSAGE_STORE_MAX = 500;
4151
4207
  var GROUP_META_TTL = 5 * 60 * 1e3;
@@ -4165,7 +4221,7 @@ function storeMessage(storeKey, entry) {
4165
4221
  if (entries.length > MESSAGE_STORE_MAX) {
4166
4222
  const trimmed = entries.length - MESSAGE_STORE_MAX;
4167
4223
  entries.splice(0, trimmed);
4168
- console.error(`${TAG12} message store trimmed for ${storeKey}: ${trimmed} oldest messages removed`);
4224
+ console.error(`${TAG13} message store trimmed for ${storeKey}: ${trimmed} oldest messages removed`);
4169
4225
  }
4170
4226
  }
4171
4227
  function deriveSessionKey(input) {
@@ -4184,7 +4240,7 @@ function deriveSessionKey(input) {
4184
4240
  }
4185
4241
  async function init(opts) {
4186
4242
  if (initialized) {
4187
- console.error(`${TAG12} already initialized`);
4243
+ console.error(`${TAG13} already initialized`);
4188
4244
  return;
4189
4245
  }
4190
4246
  configDir = opts.configDir;
@@ -4192,20 +4248,20 @@ async function init(opts) {
4192
4248
  loadConfig(opts.accountConfig);
4193
4249
  const accountIds = listCredentialAccountIds(configDir);
4194
4250
  if (accountIds.length === 0) {
4195
- console.error(`${TAG12} init: no stored WhatsApp credentials found`);
4251
+ console.error(`${TAG13} init: no stored WhatsApp credentials found`);
4196
4252
  initialized = true;
4197
4253
  return;
4198
4254
  }
4199
- console.error(`${TAG12} init: found ${accountIds.length} credentialed account(s): ${accountIds.join(", ")}`);
4255
+ console.error(`${TAG13} init: found ${accountIds.length} credentialed account(s): ${accountIds.join(", ")}`);
4200
4256
  initialized = true;
4201
4257
  for (const accountId of accountIds) {
4202
4258
  const accountCfg = whatsAppConfig.accounts?.[accountId];
4203
4259
  if (accountCfg?.enabled === false) {
4204
- console.error(`${TAG12} skipping disabled account=${accountId}`);
4260
+ console.error(`${TAG13} skipping disabled account=${accountId}`);
4205
4261
  continue;
4206
4262
  }
4207
4263
  startConnection(accountId).catch((err) => {
4208
- console.error(`${TAG12} failed to auto-start account=${accountId}: ${formatError(err)}`);
4264
+ console.error(`${TAG13} failed to auto-start account=${accountId}: ${formatError(err)}`);
4209
4265
  });
4210
4266
  }
4211
4267
  }
@@ -4214,7 +4270,7 @@ async function startConnection(accountId) {
4214
4270
  const authDir = resolveAuthDir(configDir, accountId);
4215
4271
  const hasAuth = await authExists(authDir);
4216
4272
  if (!hasAuth) {
4217
- console.error(`${TAG12} no credentials for account=${accountId}`);
4273
+ console.error(`${TAG13} no credentials for account=${accountId}`);
4218
4274
  return;
4219
4275
  }
4220
4276
  await stopConnection(accountId);
@@ -4250,11 +4306,11 @@ async function stopConnection(accountId) {
4250
4306
  conn.sock.ev.removeAllListeners("creds.update");
4251
4307
  conn.sock.ws?.close?.();
4252
4308
  } catch (err) {
4253
- console.warn(`${TAG12} socket cleanup error during stop account=${accountId}: ${String(err)}`);
4309
+ console.warn(`${TAG13} socket cleanup error during stop account=${accountId}: ${String(err)}`);
4254
4310
  }
4255
4311
  }
4256
4312
  connections.delete(accountId);
4257
- console.error(`${TAG12} stopped account=${accountId}`);
4313
+ console.error(`${TAG13} stopped account=${accountId}`);
4258
4314
  }
4259
4315
  function getStatus() {
4260
4316
  return Array.from(connections.values()).map((conn) => ({
@@ -4295,9 +4351,9 @@ async function registerLoginSocket(accountId, sock, authDir) {
4295
4351
  connections.set(accountId, conn);
4296
4352
  try {
4297
4353
  await sock.sendPresenceUpdate("available");
4298
- console.error(`${TAG12} presence set to available account=${accountId}`);
4354
+ console.error(`${TAG13} presence set to available account=${accountId}`);
4299
4355
  } catch (err) {
4300
- console.error(`${TAG12} presence update failed account=${accountId}: ${String(err)}`);
4356
+ console.error(`${TAG13} presence update failed account=${accountId}: ${String(err)}`);
4301
4357
  }
4302
4358
  await runInitQueries(sock, {
4303
4359
  accountId,
@@ -4308,7 +4364,7 @@ async function registerLoginSocket(accountId, sock, authDir) {
4308
4364
  });
4309
4365
  monitorInbound(conn);
4310
4366
  watchForDisconnect(conn);
4311
- console.error(`${TAG12} registered login socket for account=${accountId} phone=${selfId.e164 ?? "unknown"}`);
4367
+ console.error(`${TAG13} registered login socket for account=${accountId} phone=${selfId.e164 ?? "unknown"}`);
4312
4368
  }
4313
4369
  function reloadConfig(accountConfig) {
4314
4370
  loadConfig(accountConfig);
@@ -4332,7 +4388,7 @@ async function shutdown() {
4332
4388
  const ids = Array.from(connections.keys());
4333
4389
  await Promise.all(ids.map((id) => stopConnection(id)));
4334
4390
  initialized = false;
4335
- console.error(`${TAG12} shutdown complete`);
4391
+ console.error(`${TAG13} shutdown complete`);
4336
4392
  }
4337
4393
  function loadConfig(accountConfig) {
4338
4394
  try {
@@ -4344,12 +4400,12 @@ function loadConfig(accountConfig) {
4344
4400
  if (parsed.success) {
4345
4401
  whatsAppConfig = parsed.data;
4346
4402
  } else {
4347
- console.error(`${TAG12} config validation failed: ${parsed.error.message}`);
4403
+ console.error(`${TAG13} config validation failed: ${parsed.error.message}`);
4348
4404
  whatsAppConfig = {};
4349
4405
  }
4350
4406
  }
4351
4407
  } catch (err) {
4352
- console.error(`${TAG12} config load error: ${String(err)}`);
4408
+ console.error(`${TAG13} config load error: ${String(err)}`);
4353
4409
  whatsAppConfig = {};
4354
4410
  }
4355
4411
  }
@@ -4360,13 +4416,13 @@ async function connectWithReconnect(conn) {
4360
4416
  let cycleError = null;
4361
4417
  let connectedAt;
4362
4418
  try {
4363
- console.error(`${TAG12} connecting account=${conn.accountId} attempt=${conn.reconnectAttempts}`);
4419
+ console.error(`${TAG13} connecting account=${conn.accountId} attempt=${conn.reconnectAttempts}`);
4364
4420
  const sock = await createWaSocket({
4365
4421
  authDir: conn.authDir,
4366
4422
  silent: true
4367
4423
  });
4368
4424
  conn.sock = sock;
4369
- console.error(`${TAG12} socket created account=${conn.accountId} \u2014 waiting for connection`);
4425
+ console.error(`${TAG13} socket created account=${conn.accountId} \u2014 waiting for connection`);
4370
4426
  await waitForConnection(sock);
4371
4427
  const selfId = readSelfId(conn.authDir);
4372
4428
  connectedAt = Date.now();
@@ -4376,12 +4432,12 @@ async function connectWithReconnect(conn) {
4376
4432
  conn.lastConnectedAt = connectedAt;
4377
4433
  conn.lastError = void 0;
4378
4434
  conn.lidMapping = sock.signalRepository?.lidMapping ?? null;
4379
- console.error(`${TAG12} connected account=${conn.accountId} phone=${selfId.e164 ?? "unknown"}`);
4435
+ console.error(`${TAG13} connected account=${conn.accountId} phone=${selfId.e164 ?? "unknown"}`);
4380
4436
  try {
4381
4437
  await sock.sendPresenceUpdate("available");
4382
- console.error(`${TAG12} presence set to available account=${conn.accountId}`);
4438
+ console.error(`${TAG13} presence set to available account=${conn.accountId}`);
4383
4439
  } catch (err) {
4384
- console.error(`${TAG12} presence update failed account=${conn.accountId}: ${String(err)}`);
4440
+ console.error(`${TAG13} presence update failed account=${conn.accountId}: ${String(err)}`);
4385
4441
  }
4386
4442
  await runInitQueries(sock, {
4387
4443
  accountId: conn.accountId,
@@ -4408,9 +4464,9 @@ async function connectWithReconnect(conn) {
4408
4464
  }
4409
4465
  const classification = classifyDisconnect(err);
4410
4466
  conn.lastError = classification.message;
4411
- console.error(`${TAG12} disconnect account=${conn.accountId}: ${classification.kind} \u2014 ${classification.message}`);
4467
+ console.error(`${TAG13} disconnect account=${conn.accountId}: ${classification.kind} \u2014 ${classification.message}`);
4412
4468
  if (!classification.shouldRetry) {
4413
- console.error(`${TAG12} terminal disconnect for account=${conn.accountId}, stopping reconnection`);
4469
+ console.error(`${TAG13} terminal disconnect for account=${conn.accountId}, stopping reconnection`);
4414
4470
  return;
4415
4471
  }
4416
4472
  }
@@ -4423,7 +4479,7 @@ async function connectWithReconnect(conn) {
4423
4479
  if (decision.action === "abort") {
4424
4480
  const stuckReason = `GIVING UP account=${conn.accountId} attempts=${decision.finalAttempts}/${maxAttempts} uptimeMsLast=${uptimeMs} stableThresholdMs=${STABLE_UPTIME_MS} lastError=${conn.lastError ?? "(none)"}`;
4425
4481
  console.error(
4426
- `${TAG12} ${stuckReason} \u2014 re-pair via QR or restart required; WhatsApp will not reconnect automatically`
4482
+ `${TAG13} ${stuckReason} \u2014 re-pair via QR or restart required; WhatsApp will not reconnect automatically`
4427
4483
  );
4428
4484
  conn.sessionStuckReason = stuckReason;
4429
4485
  conn.lastError = stuckReason;
@@ -4432,17 +4488,17 @@ async function connectWithReconnect(conn) {
4432
4488
  conn.reconnectAttempts = decision.nextAttempts;
4433
4489
  if (decision.reason === "short-lived") {
4434
4490
  console.error(
4435
- `${TAG12} short-lived session account=${conn.accountId} uptimeMs=${uptimeMs} attempt=${decision.nextAttempts}/${maxAttempts} lastError=${conn.lastError ?? "(clean disconnect)"}`
4491
+ `${TAG13} short-lived session account=${conn.accountId} uptimeMs=${uptimeMs} attempt=${decision.nextAttempts}/${maxAttempts} lastError=${conn.lastError ?? "(clean disconnect)"}`
4436
4492
  );
4437
4493
  } else {
4438
4494
  console.error(
4439
- `${TAG12} session stable account=${conn.accountId} uptimeMs=${uptimeMs} \u2014 reconnect counter reset`
4495
+ `${TAG13} session stable account=${conn.accountId} uptimeMs=${uptimeMs} \u2014 reconnect counter reset`
4440
4496
  );
4441
4497
  }
4442
4498
  if (decision.reason === "short-lived" || cycleError) {
4443
4499
  const delay = computeBackoff(Math.max(1, decision.nextAttempts));
4444
4500
  console.error(
4445
- `${TAG12} reconnecting account=${conn.accountId} in ${delay}ms (attempt ${decision.nextAttempts}/${maxAttempts})`
4501
+ `${TAG13} reconnecting account=${conn.accountId} in ${delay}ms (attempt ${decision.nextAttempts}/${maxAttempts})`
4446
4502
  );
4447
4503
  await new Promise((resolve25) => {
4448
4504
  const timer2 = setTimeout(resolve25, delay);
@@ -4476,11 +4532,11 @@ function watchForDisconnect(conn) {
4476
4532
  conn.sock.ev.on("connection.update", (update) => {
4477
4533
  if (update.connection === "close") {
4478
4534
  if (connections.get(conn.accountId) !== conn) return;
4479
- console.error(`${TAG12} socket disconnected for account=${conn.accountId}`);
4535
+ console.error(`${TAG13} socket disconnected for account=${conn.accountId}`);
4480
4536
  conn.connected = false;
4481
4537
  conn.sock = null;
4482
4538
  connectWithReconnect(conn).catch((err) => {
4483
- console.error(`${TAG12} reconnection failed for account=${conn.accountId}: ${formatError(err)}`);
4539
+ console.error(`${TAG13} reconnection failed for account=${conn.accountId}: ${formatError(err)}`);
4484
4540
  });
4485
4541
  }
4486
4542
  });
@@ -4489,7 +4545,7 @@ async function getGroupMeta(conn, jid) {
4489
4545
  const cached = conn.groupMetaCache.get(jid);
4490
4546
  if (cached && cached.expires > Date.now()) return cached;
4491
4547
  if (!conn.sock) return null;
4492
- console.error(`${TAG12} group metadata cache miss for ${jid}, fetching from Baileys account=${conn.accountId}`);
4548
+ console.error(`${TAG13} group metadata cache miss for ${jid}, fetching from Baileys account=${conn.accountId}`);
4493
4549
  try {
4494
4550
  const meta = await conn.sock.groupMetadata(jid);
4495
4551
  const participants = await Promise.all(
@@ -4504,12 +4560,12 @@ async function getGroupMeta(conn, jid) {
4504
4560
  };
4505
4561
  conn.groupMetaCache.set(jid, entry);
4506
4562
  console.error(
4507
- `${TAG12} group metadata cached for ${jid}: "${meta.subject}", ${participants.length} participants, expires in ${GROUP_META_TTL}ms account=${conn.accountId}`
4563
+ `${TAG13} group metadata cached for ${jid}: "${meta.subject}", ${participants.length} participants, expires in ${GROUP_META_TTL}ms account=${conn.accountId}`
4508
4564
  );
4509
4565
  return entry;
4510
4566
  } catch (err) {
4511
4567
  console.error(
4512
- `${TAG12} group metadata fetch failed for ${jid}: ${err instanceof Error ? err.message : String(err)}, caching empty entry for ${GROUP_META_TTL}ms account=${conn.accountId}`
4568
+ `${TAG13} group metadata fetch failed for ${jid}: ${err instanceof Error ? err.message : String(err)}, caching empty entry for ${GROUP_META_TTL}ms account=${conn.accountId}`
4513
4569
  );
4514
4570
  const emptyEntry = { expires: Date.now() + GROUP_META_TTL };
4515
4571
  conn.groupMetaCache.set(jid, emptyEntry);
@@ -4520,7 +4576,7 @@ function monitorInbound(conn) {
4520
4576
  if (!conn.sock || !onInboundMessage) return;
4521
4577
  const sock = conn.sock;
4522
4578
  const debounceMs = whatsAppConfig.accounts?.[conn.accountId]?.debounceMs ?? whatsAppConfig.debounceMs ?? 0;
4523
- console.error(`${TAG12} monitorInbound started account=${conn.accountId} debounceMs=${debounceMs}`);
4579
+ console.error(`${TAG13} monitorInbound started account=${conn.accountId} debounceMs=${debounceMs}`);
4524
4580
  conn.debouncer = createInboundDebouncer({
4525
4581
  debounceMs,
4526
4582
  buildKey: (payload) => {
@@ -4533,7 +4589,7 @@ function monitorInbound(conn) {
4533
4589
  onInboundMessage(entries[0]);
4534
4590
  return;
4535
4591
  }
4536
- console.error(`${TAG12} debounce: combining ${entries.length} messages account=${conn.accountId} from=${entries[0].senderPhone}`);
4592
+ console.error(`${TAG13} debounce: combining ${entries.length} messages account=${conn.accountId} from=${entries[0].senderPhone}`);
4537
4593
  const last = entries[entries.length - 1];
4538
4594
  const mediaEntry = entries.find((e) => e.mediaPath);
4539
4595
  const combinedText = entries.map((e) => e.text).filter(Boolean).join("\n");
@@ -4546,7 +4602,7 @@ function monitorInbound(conn) {
4546
4602
  });
4547
4603
  },
4548
4604
  onError: (err) => {
4549
- console.error(`${TAG12} debounce flush error account=${conn.accountId}: ${String(err)}`);
4605
+ console.error(`${TAG13} debounce flush error account=${conn.accountId}: ${String(err)}`);
4550
4606
  }
4551
4607
  });
4552
4608
  sock.ev.on("messages.upsert", async (upsert) => {
@@ -4574,7 +4630,7 @@ function monitorInbound(conn) {
4574
4630
  });
4575
4631
  const entries = messageStore.get(storeKey);
4576
4632
  console.error(
4577
- `${TAG12} stored message ${msg.key.id ?? "?"} for ${remoteJid} (type: ${upsert.type}, store size: ${entries?.length ?? 0}/${MESSAGE_STORE_MAX}) account=${conn.accountId}`
4633
+ `${TAG13} stored message ${msg.key.id ?? "?"} for ${remoteJid} (type: ${upsert.type}, store size: ${entries?.length ?? 0}/${MESSAGE_STORE_MAX}) account=${conn.accountId}`
4578
4634
  );
4579
4635
  recordActivity({
4580
4636
  accountId: conn.accountId,
@@ -4597,23 +4653,31 @@ function monitorInbound(conn) {
4597
4653
  isOwnerMirror: fromMe && !isGroup
4598
4654
  });
4599
4655
  if (msg.key.id) {
4600
- await persistWhatsAppMessage({
4656
+ const merged = await ensureWhatsAppConversation({
4601
4657
  accountId: conn.accountId,
4602
- remoteJid,
4603
4658
  sessionKey,
4604
- msgKeyId: msg.key.id,
4605
- fromMe,
4606
- senderPhone,
4607
- selfPhone: conn.selfPhone,
4608
- body: extracted.text,
4609
- timestamp: ts,
4610
- pushName: msg.pushName ?? void 0,
4611
- quoted: extracted.quotedMessage ? {
4612
- id: extracted.quotedMessage.id,
4613
- body: extracted.quotedMessage.text,
4614
- sender: extracted.quotedMessage.sender
4615
- } : void 0
4659
+ agentType: sessionKeyAgentType,
4660
+ groupJid: isGroup ? remoteJid : void 0
4616
4661
  });
4662
+ if (merged) {
4663
+ await persistWhatsAppMessage({
4664
+ accountId: conn.accountId,
4665
+ remoteJid,
4666
+ sessionKey,
4667
+ msgKeyId: msg.key.id,
4668
+ fromMe,
4669
+ senderPhone,
4670
+ selfPhone: conn.selfPhone,
4671
+ body: extracted.text,
4672
+ timestamp: ts,
4673
+ pushName: msg.pushName ?? void 0,
4674
+ quoted: extracted.quotedMessage ? {
4675
+ id: extracted.quotedMessage.id,
4676
+ body: extracted.quotedMessage.text,
4677
+ sender: extracted.quotedMessage.sender
4678
+ } : void 0
4679
+ });
4680
+ }
4617
4681
  }
4618
4682
  }
4619
4683
  if (upsert.type === "append") {
@@ -4627,13 +4691,13 @@ function monitorInbound(conn) {
4627
4691
  );
4628
4692
  }
4629
4693
  console.error(
4630
- `${TAG12} append-type message ${msg.key.id ?? "?"} stored, dispatch skipped account=${conn.accountId}`
4694
+ `${TAG13} append-type message ${msg.key.id ?? "?"} stored, dispatch skipped account=${conn.accountId}`
4631
4695
  );
4632
4696
  continue;
4633
4697
  }
4634
4698
  await handleInboundMessage(conn, msg);
4635
4699
  } catch (err) {
4636
- console.error(`${TAG12} inbound handler error account=${conn.accountId}: ${String(err)}`);
4700
+ console.error(`${TAG13} inbound handler error account=${conn.accountId}: ${String(err)}`);
4637
4701
  }
4638
4702
  }
4639
4703
  });
@@ -4643,31 +4707,31 @@ async function handleInboundMessage(conn, msg) {
4643
4707
  const remoteJid = msg.key.remoteJid;
4644
4708
  if (!remoteJid) return;
4645
4709
  if (remoteJid === "status@broadcast") {
4646
- console.error(`${TAG12} drop: status broadcast account=${conn.accountId}`);
4710
+ console.error(`${TAG13} drop: status broadcast account=${conn.accountId}`);
4647
4711
  return;
4648
4712
  }
4649
4713
  if (!msg.message) {
4650
- console.error(`${TAG12} drop: empty message account=${conn.accountId} from=${remoteJid}`);
4714
+ console.error(`${TAG13} drop: empty message account=${conn.accountId} from=${remoteJid}`);
4651
4715
  return;
4652
4716
  }
4653
4717
  const dedupKey = `${conn.accountId}:${remoteJid}:${msg.key.id}`;
4654
4718
  if (isDuplicateInbound(dedupKey)) {
4655
- console.error(`${TAG12} drop: duplicate account=${conn.accountId} key=${dedupKey}`);
4719
+ console.error(`${TAG13} drop: duplicate account=${conn.accountId} key=${dedupKey}`);
4656
4720
  return;
4657
4721
  }
4658
4722
  if (msg.key.fromMe) {
4659
4723
  if (msg.key.id && isAgentSentMessage(msg.key.id)) {
4660
- console.error(`${TAG12} drop: echo suppression account=${conn.accountId} msgId=${msg.key.id}`);
4724
+ console.error(`${TAG13} drop: echo suppression account=${conn.accountId} msgId=${msg.key.id}`);
4661
4725
  return;
4662
4726
  }
4663
4727
  const extracted2 = extractMessage(msg);
4664
4728
  if (!extracted2.text) {
4665
- console.error(`${TAG12} owner reply skipped \u2014 no text content account=${conn.accountId}`);
4729
+ console.error(`${TAG13} owner reply skipped \u2014 no text content account=${conn.accountId}`);
4666
4730
  return;
4667
4731
  }
4668
4732
  const isGroup2 = isGroupJid(remoteJid);
4669
4733
  const senderPhone2 = conn.selfPhone ?? "owner";
4670
- console.error(`${TAG12} owner reply mirrored to session from=${senderPhone2} account=${conn.accountId}`);
4734
+ console.error(`${TAG13} owner reply mirrored to session from=${senderPhone2} account=${conn.accountId}`);
4671
4735
  const reply2 = async (text) => {
4672
4736
  const currentSock = conn.sock;
4673
4737
  if (!currentSock) throw new Error("WhatsApp disconnected \u2014 cannot reply");
@@ -4695,7 +4759,7 @@ async function handleInboundMessage(conn, msg) {
4695
4759
  }
4696
4760
  const extracted = extractMessage(msg);
4697
4761
  if (!extracted.text && !extracted.mediaType) {
4698
- console.error(`${TAG12} drop: no text or media account=${conn.accountId} from=${remoteJid}`);
4762
+ console.error(`${TAG13} drop: no text or media account=${conn.accountId} from=${remoteJid}`);
4699
4763
  return;
4700
4764
  }
4701
4765
  let mediaResult;
@@ -4705,7 +4769,7 @@ async function handleInboundMessage(conn, msg) {
4705
4769
  maxBytes: maxMb * 1024 * 1024
4706
4770
  });
4707
4771
  if (!mediaResult) {
4708
- console.error(`${TAG12} media download returned undefined account=${conn.accountId} type=${extracted.mediaType} from=${remoteJid}`);
4772
+ console.error(`${TAG13} media download returned undefined account=${conn.accountId} type=${extracted.mediaType} from=${remoteJid}`);
4709
4773
  }
4710
4774
  }
4711
4775
  const isGroup = isGroupJid(remoteJid);
@@ -4750,7 +4814,7 @@ async function handleInboundMessage(conn, msg) {
4750
4814
  });
4751
4815
  }
4752
4816
  console.error(
4753
- `${TAG12} inbound account=${conn.accountId} from=${senderPhone} group=${isGroup} access=${accessResult.allowed ? "allowed" : "blocked"}(${accessResult.reason}) agent=${accessResult.agentType}` + (extracted.mediaType ? ` media=${extracted.mediaType}` : "") + (mediaResult ? ` mediaPath=${mediaResult.path}` : "") + (extracted.quotedMessage ? ` replyTo=${extracted.quotedMessage.id}` : "")
4817
+ `${TAG13} inbound account=${conn.accountId} from=${senderPhone} group=${isGroup} access=${accessResult.allowed ? "allowed" : "blocked"}(${accessResult.reason}) agent=${accessResult.agentType}` + (extracted.mediaType ? ` media=${extracted.mediaType}` : "") + (mediaResult ? ` mediaPath=${mediaResult.path}` : "") + (extracted.quotedMessage ? ` replyTo=${extracted.quotedMessage.id}` : "")
4754
4818
  );
4755
4819
  if (!accessResult.allowed) return;
4756
4820
  let groupSubject;
@@ -4793,15 +4857,15 @@ async function handleInboundMessage(conn, msg) {
4793
4857
  if (accessResult.agentType === "public") {
4794
4858
  const hoursResult = await isBusinessOpen(conn.accountId);
4795
4859
  if (!hoursResult.open) {
4796
- console.error(`${TAG12} [${conn.accountId}] dispatch skipped: business closed`);
4860
+ console.error(`${TAG13} [${conn.accountId}] dispatch skipped: business closed`);
4797
4861
  const afterHoursMessage = whatsAppConfig.accounts?.[conn.accountId]?.afterHoursMessage ?? whatsAppConfig.afterHoursMessage;
4798
4862
  if (afterHoursMessage) {
4799
4863
  try {
4800
4864
  await reply(afterHoursMessage);
4801
- console.error(`${TAG12} [${conn.accountId}] after-hours auto-reply sent to ${remoteJid}`);
4865
+ console.error(`${TAG13} [${conn.accountId}] after-hours auto-reply sent to ${remoteJid}`);
4802
4866
  } catch (err) {
4803
4867
  console.error(
4804
- `${TAG12} [${conn.accountId}] after-hours auto-reply failed: ${err instanceof Error ? err.message : String(err)}`
4868
+ `${TAG13} [${conn.accountId}] after-hours auto-reply failed: ${err instanceof Error ? err.message : String(err)}`
4805
4869
  );
4806
4870
  }
4807
4871
  }
@@ -5387,7 +5451,7 @@ async function storeGeneratedFile(accountId, filePath) {
5387
5451
  import { writeFile as writeFile3, mkdtemp, rm } from "fs/promises";
5388
5452
  import { tmpdir } from "os";
5389
5453
  import { join as join5 } from "path";
5390
- var TAG13 = "[voice]";
5454
+ var TAG14 = "[voice]";
5391
5455
  var AUDIO_MIME_TYPES = /* @__PURE__ */ new Set([
5392
5456
  "audio/ogg",
5393
5457
  "audio/webm",
@@ -5419,7 +5483,7 @@ async function transcribeVoiceNote(file, source) {
5419
5483
  const bytes = file.size;
5420
5484
  const mimeType = file.type;
5421
5485
  console.error(
5422
- `${TAG13} recording send source=${source} duration_ms=unknown bytes=${bytes} format=${mimeType}`
5486
+ `${TAG14} recording send source=${source} duration_ms=unknown bytes=${bytes} format=${mimeType}`
5423
5487
  );
5424
5488
  let tempDir;
5425
5489
  let tempPath;
@@ -5431,7 +5495,7 @@ async function transcribeVoiceNote(file, source) {
5431
5495
  await writeFile3(tempPath, buffer);
5432
5496
  } catch (err) {
5433
5497
  const reason = err instanceof Error ? err.message : String(err);
5434
- console.error(`${TAG13} failed source=${source} error=temp-file-write: ${reason}`);
5498
+ console.error(`${TAG14} failed source=${source} error=temp-file-write: ${reason}`);
5435
5499
  return { ok: false, error: "Could not process voice note" };
5436
5500
  }
5437
5501
  try {
@@ -5439,7 +5503,7 @@ async function transcribeVoiceNote(file, source) {
5439
5503
  if (!sttResult) {
5440
5504
  const elapsed2 = Date.now() - startMs;
5441
5505
  console.error(
5442
- `${TAG13} failed source=${source} error=transcription-failed duration_ms=${elapsed2}`
5506
+ `${TAG14} failed source=${source} error=transcription-failed duration_ms=${elapsed2}`
5443
5507
  );
5444
5508
  return { ok: false, error: "Could not transcribe voice note. Please try again or type your message." };
5445
5509
  }
@@ -5447,7 +5511,7 @@ async function transcribeVoiceNote(file, source) {
5447
5511
  const elapsed = Date.now() - startMs;
5448
5512
  const words = rawText.split(/\s+/).filter(Boolean).length;
5449
5513
  console.error(
5450
- `${TAG13} transcribed source=${source} duration_ms=${elapsed} stt_ms=${sttResult.durationMs} words=${words}`
5514
+ `${TAG14} transcribed source=${source} duration_ms=${elapsed} stt_ms=${sttResult.durationMs} words=${words}`
5451
5515
  );
5452
5516
  return {
5453
5517
  ok: true,
@@ -5457,7 +5521,7 @@ async function transcribeVoiceNote(file, source) {
5457
5521
  const elapsed = Date.now() - startMs;
5458
5522
  const reason = err instanceof Error ? err.message : String(err);
5459
5523
  console.error(
5460
- `${TAG13} failed source=${source} error=${reason} duration_ms=${elapsed}`
5524
+ `${TAG14} failed source=${source} error=${reason} duration_ms=${elapsed}`
5461
5525
  );
5462
5526
  return { ok: false, error: "Voice note transcription failed. Please try again or type your message." };
5463
5527
  } finally {
@@ -5492,7 +5556,7 @@ var VERDICT_DEFINITIONS = {
5492
5556
  };
5493
5557
 
5494
5558
  // app/lib/inbound-gateway.ts
5495
- var TAG14 = "[inbound-gateway]";
5559
+ var TAG15 = "[inbound-gateway]";
5496
5560
  var GATEWAY_TIMEOUT_MS = 1e4;
5497
5561
  var MIN_WORDS_FOR_PROCESSING = 5;
5498
5562
  function defaultResult(rawText, latencyMs) {
@@ -5615,11 +5679,11 @@ async function processInbound(rawText, channel) {
5615
5679
  };
5616
5680
  result.fallthrough = false;
5617
5681
  console.warn(
5618
- `${TAG14} short-message-injection channel=${channel} words=${words.length} latency_ms=${result.latencyMs}`
5682
+ `${TAG15} short-message-injection channel=${channel} words=${words.length} latency_ms=${result.latencyMs}`
5619
5683
  );
5620
5684
  } else {
5621
5685
  console.log(
5622
- `${TAG14} passthrough channel=${channel} reason=short-message words=${words.length} latency_ms=${result.latencyMs}`
5686
+ `${TAG15} passthrough channel=${channel} reason=short-message words=${words.length} latency_ms=${result.latencyMs}`
5623
5687
  );
5624
5688
  }
5625
5689
  return result;
@@ -5636,13 +5700,13 @@ async function processInbound(rawText, channel) {
5636
5700
  const latencyMs = Date.now() - startMs;
5637
5701
  if (llmResult.kind === "fallback") {
5638
5702
  console.warn(
5639
- `${TAG14} fallthrough channel=${channel} reason=${llmResult.cause} detail=${llmResult.reason} latency_ms=${latencyMs}`
5703
+ `${TAG15} fallthrough channel=${channel} reason=${llmResult.cause} detail=${llmResult.reason} latency_ms=${latencyMs}`
5640
5704
  );
5641
5705
  return defaultResult(rawText.trim(), latencyMs);
5642
5706
  }
5643
5707
  if (llmResult.kind !== "ok-tool") {
5644
5708
  console.warn(
5645
- `${TAG14} fallthrough channel=${channel} reason=no-tool-response latency_ms=${latencyMs}`
5709
+ `${TAG15} fallthrough channel=${channel} reason=no-tool-response latency_ms=${latencyMs}`
5646
5710
  );
5647
5711
  return defaultResult(rawText.trim(), latencyMs);
5648
5712
  }
@@ -5652,7 +5716,7 @@ async function processInbound(rawText, channel) {
5652
5716
  const verdict = input.verdict;
5653
5717
  if (!processedText || !verdict || !["clean", "suspicious", "discard"].includes(verdict)) {
5654
5718
  console.warn(
5655
- `${TAG14} fallthrough channel=${channel} reason=mandatory-fields-missing latency_ms=${latencyMs} hasProcessedText=${!!processedText} verdict=${String(verdict)}`
5719
+ `${TAG15} fallthrough channel=${channel} reason=mandatory-fields-missing latency_ms=${latencyMs} hasProcessedText=${!!processedText} verdict=${String(verdict)}`
5656
5720
  );
5657
5721
  return defaultResult(rawText.trim(), latencyMs);
5658
5722
  }
@@ -5680,18 +5744,18 @@ async function processInbound(rawText, channel) {
5680
5744
  fallthrough: false
5681
5745
  };
5682
5746
  console.log(
5683
- `${TAG14} channel=${channel} verdict=${verdict} promptInjection=${promptInjectionRisk} intent=${intent} language=${language} complexity=${complexity} requiresHistory=${requiresHistory} rewrite=${!isAdmin && processedText !== rawText.trim()} searchQuery=${searchQuery ? "yes" : "null"} latency_ms=${latencyMs}`
5747
+ `${TAG15} channel=${channel} verdict=${verdict} promptInjection=${promptInjectionRisk} intent=${intent} language=${language} complexity=${complexity} requiresHistory=${requiresHistory} rewrite=${!isAdmin && processedText !== rawText.trim()} searchQuery=${searchQuery ? "yes" : "null"} latency_ms=${latencyMs}`
5684
5748
  );
5685
5749
  if (verdict !== "clean") {
5686
5750
  console.warn(
5687
- `${TAG14} ${verdict.toUpperCase()} channel=${channel} reason=${reason} promptInjection=${promptInjectionRisk}`
5751
+ `${TAG15} ${verdict.toUpperCase()} channel=${channel} reason=${reason} promptInjection=${promptInjectionRisk}`
5688
5752
  );
5689
5753
  }
5690
5754
  return result;
5691
5755
  } catch (err) {
5692
5756
  const reason = err instanceof Error ? err.message : String(err);
5693
5757
  console.warn(
5694
- `${TAG14} fallthrough channel=${channel} reason=parse-error: ${reason} latency_ms=${latencyMs}`
5758
+ `${TAG15} fallthrough channel=${channel} reason=parse-error: ${reason} latency_ms=${latencyMs}`
5695
5759
  );
5696
5760
  return defaultResult(rawText.trim(), latencyMs);
5697
5761
  }
@@ -6784,7 +6848,7 @@ function checkTelegramAccess(params) {
6784
6848
  }
6785
6849
 
6786
6850
  // server/routes/telegram.ts
6787
- var TAG15 = "[telegram-webhook]";
6851
+ var TAG16 = "[telegram-webhook]";
6788
6852
  var TELEGRAM_API = "https://api.telegram.org";
6789
6853
  function getWebhookSecret(botType) {
6790
6854
  const filePath = botType === "admin" ? TELEGRAM_ADMIN_WEBHOOK_SECRET_FILE : TELEGRAM_WEBHOOK_SECRET_FILE;
@@ -6808,12 +6872,12 @@ async function handleInbound(params) {
6808
6872
  const gatewayChannel = agentType === "admin" ? "telegram-admin" : "telegram-dm";
6809
6873
  registerSession(sessionKey, agentType, accountId);
6810
6874
  console.error(
6811
- `${TAG15} session registered: sessionKey=${sessionKey} agentType=${agentType} botType=${botType} senderId=${senderId} accountId=${accountId.slice(0, 8)}\u2026`
6875
+ `${TAG16} session registered: sessionKey=${sessionKey} agentType=${agentType} botType=${botType} senderId=${senderId} accountId=${accountId.slice(0, 8)}\u2026`
6812
6876
  );
6813
6877
  const gatewayResult = await processInbound(text, gatewayChannel);
6814
6878
  if (gatewayResult.screening.verdict === "discard") {
6815
6879
  console.error(
6816
- `${TAG15} discarded: senderId=${senderId} chatId=${chatId} reason=${gatewayResult.screening.reason}`
6880
+ `${TAG16} discarded: senderId=${senderId} chatId=${chatId} reason=${gatewayResult.screening.reason}`
6817
6881
  );
6818
6882
  return;
6819
6883
  }
@@ -6836,7 +6900,7 @@ async function handleInbound(params) {
6836
6900
  }
6837
6901
  } catch (err) {
6838
6902
  console.error(
6839
- `${TAG15} agent-error: chatId=${chatId} senderId=${senderId} error=${err instanceof Error ? err.message : String(err)}`
6903
+ `${TAG16} agent-error: chatId=${chatId} senderId=${senderId} error=${err instanceof Error ? err.message : String(err)}`
6840
6904
  );
6841
6905
  responseText = "I'm having trouble right now. Please try again in a moment.";
6842
6906
  }
@@ -6854,12 +6918,12 @@ async function handleInbound(params) {
6854
6918
  const data = await res.json();
6855
6919
  if (!data.ok) {
6856
6920
  console.error(
6857
- `${TAG15} send-error: chatId=${chatId} error=${data.description ?? "unknown"}`
6921
+ `${TAG16} send-error: chatId=${chatId} error=${data.description ?? "unknown"}`
6858
6922
  );
6859
6923
  }
6860
6924
  } catch (err) {
6861
6925
  console.error(
6862
- `${TAG15} send-error: chatId=${chatId} error=${err instanceof Error ? err.message : String(err)}`
6926
+ `${TAG16} send-error: chatId=${chatId} error=${err instanceof Error ? err.message : String(err)}`
6863
6927
  );
6864
6928
  }
6865
6929
  }
@@ -6867,28 +6931,28 @@ var app6 = new Hono();
6867
6931
  app6.post("/webhook", async (c) => {
6868
6932
  const botType = c.req.query("bot");
6869
6933
  if (!botType || botType !== "public" && botType !== "admin") {
6870
- console.error(`${TAG15} invalid-bot-type: received=${botType ?? "missing"}`);
6934
+ console.error(`${TAG16} invalid-bot-type: received=${botType ?? "missing"}`);
6871
6935
  return c.json({ error: "Missing or invalid bot type" }, 400);
6872
6936
  }
6873
6937
  const storedSecret = getWebhookSecret(botType);
6874
6938
  if (!storedSecret) {
6875
- console.error(`${TAG15} secret=missing botType=${botType}`);
6939
+ console.error(`${TAG16} secret=missing botType=${botType}`);
6876
6940
  return c.json({ error: "Webhook not configured" }, 401);
6877
6941
  }
6878
6942
  const headerSecret = c.req.header("x-telegram-bot-api-secret-token");
6879
6943
  if (!headerSecret || !verifyWebhookSecret(headerSecret, storedSecret)) {
6880
- console.error(`${TAG15} secret=invalid botType=${botType}`);
6944
+ console.error(`${TAG16} secret=invalid botType=${botType}`);
6881
6945
  return c.json({ error: "Unauthorized" }, 401);
6882
6946
  }
6883
6947
  const account = resolveAccount();
6884
6948
  if (!account) {
6885
- console.error(`${TAG15} config=no-account`);
6949
+ console.error(`${TAG16} config=no-account`);
6886
6950
  return c.json({ error: "No account configured" }, 500);
6887
6951
  }
6888
6952
  const tgConfig = account.config.telegram ?? {};
6889
6953
  const botToken = botType === "admin" ? tgConfig.adminBotToken : tgConfig.publicBotToken;
6890
6954
  if (!botToken) {
6891
- console.error(`${TAG15} config=no-token botType=${botType}`);
6955
+ console.error(`${TAG16} config=no-token botType=${botType}`);
6892
6956
  return c.json({ error: `No ${botType} bot token configured` }, 500);
6893
6957
  }
6894
6958
  let update;
@@ -6916,7 +6980,7 @@ app6.post("/webhook", async (c) => {
6916
6980
  }
6917
6981
  const accessResult = checkTelegramAccess({ senderId, botType, config: tgConfig });
6918
6982
  console.error(
6919
- `${TAG15} access: botType=${botType} senderId=${senderId} chatId=${chatId} allowed=${accessResult.allowed} reason=${accessResult.reason} agentType=${accessResult.agentType}`
6983
+ `${TAG16} access: botType=${botType} senderId=${senderId} chatId=${chatId} allowed=${accessResult.allowed} reason=${accessResult.reason} agentType=${accessResult.agentType}`
6920
6984
  );
6921
6985
  if (!accessResult.allowed) {
6922
6986
  return c.json({ ok: true });
@@ -6927,7 +6991,7 @@ app6.post("/webhook", async (c) => {
6927
6991
  headers: { "Content-Type": "application/json" },
6928
6992
  body: JSON.stringify({ callback_query_id: callbackId })
6929
6993
  }).catch((err) => {
6930
- console.error(`${TAG15} callback-ack-error: ${err instanceof Error ? err.message : String(err)}`);
6994
+ console.error(`${TAG16} callback-ack-error: ${err instanceof Error ? err.message : String(err)}`);
6931
6995
  });
6932
6996
  }
6933
6997
  handleInbound({
@@ -6940,7 +7004,7 @@ app6.post("/webhook", async (c) => {
6940
7004
  agentType: accessResult.agentType
6941
7005
  }).catch((err) => {
6942
7006
  console.error(
6943
- `${TAG15} unhandled-error: chatId=${chatId} senderId=${senderId} error=${err instanceof Error ? err.message : String(err)}`
7007
+ `${TAG16} unhandled-error: chatId=${chatId} senderId=${senderId} error=${err instanceof Error ? err.message : String(err)}`
6944
7008
  );
6945
7009
  });
6946
7010
  return c.json({ ok: true });
@@ -6954,14 +7018,14 @@ import { realpathSync as realpathSync2, readdirSync as readdirSync2, readFileSyn
6954
7018
 
6955
7019
  // app/lib/whatsapp/login.ts
6956
7020
  import { randomUUID as randomUUID6 } from "crypto";
6957
- var TAG16 = "[whatsapp:login]";
7021
+ var TAG17 = "[whatsapp:login]";
6958
7022
  var ACTIVE_LOGIN_TTL_MS = 3 * 6e4;
6959
7023
  var activeLogins = /* @__PURE__ */ new Map();
6960
7024
  function closeSocket(sock) {
6961
7025
  try {
6962
7026
  sock.ws?.close?.();
6963
7027
  } catch (err) {
6964
- console.warn(`${TAG16} socket close error during cleanup: ${String(err)}`);
7028
+ console.warn(`${TAG17} socket close error during cleanup: ${String(err)}`);
6965
7029
  }
6966
7030
  }
6967
7031
  function resetActiveLogin(accountId) {
@@ -6984,7 +7048,7 @@ async function loginConnectionLoop(accountId, login) {
6984
7048
  const current = activeLogins.get(accountId);
6985
7049
  if (current?.id === login.id) {
6986
7050
  current.connected = true;
6987
- console.error(`${TAG16} loginConnectionLoop: connected account=${accountId} attempt=${attempt}`);
7051
+ console.error(`${TAG17} loginConnectionLoop: connected account=${accountId} attempt=${attempt}`);
6988
7052
  }
6989
7053
  return;
6990
7054
  } catch (err) {
@@ -6994,7 +7058,7 @@ async function loginConnectionLoop(accountId, login) {
6994
7058
  if (!classification.shouldRetry || attempt >= LOGIN_MAX_RECONNECTS) {
6995
7059
  if (attempt >= LOGIN_MAX_RECONNECTS) {
6996
7060
  console.error(
6997
- `${TAG16} login reconnect attempts exhausted (${attempt}/${LOGIN_MAX_RECONNECTS}) \u2014 surfacing error to agent`
7061
+ `${TAG17} login reconnect attempts exhausted (${attempt}/${LOGIN_MAX_RECONNECTS}) \u2014 surfacing error to agent`
6998
7062
  );
6999
7063
  current.error = `Login failed after ${attempt} reconnect attempts: ${formatError(err)}`;
7000
7064
  } else {
@@ -7006,7 +7070,7 @@ async function loginConnectionLoop(accountId, login) {
7006
7070
  attempt++;
7007
7071
  const delay = LOGIN_RECONNECT_DELAYS[attempt - 1] ?? 8e3;
7008
7072
  console.error(
7009
- `${TAG16} status=${classification.statusCode ?? "unknown"} restart required \u2014 reconnecting with saved creds (attempt ${attempt}/${LOGIN_MAX_RECONNECTS}) delay=${delay}ms`
7073
+ `${TAG17} status=${classification.statusCode ?? "unknown"} restart required \u2014 reconnecting with saved creds (attempt ${attempt}/${LOGIN_MAX_RECONNECTS}) delay=${delay}ms`
7010
7074
  );
7011
7075
  closeSocket(current.sock);
7012
7076
  await new Promise((r) => setTimeout(r, delay));
@@ -7017,7 +7081,7 @@ async function loginConnectionLoop(accountId, login) {
7017
7081
  current.sock = newSock;
7018
7082
  } catch (sockErr) {
7019
7083
  console.error(
7020
- `${TAG16} reconnect socket creation failed (attempt ${attempt}/${LOGIN_MAX_RECONNECTS}): ${String(sockErr)}`
7084
+ `${TAG17} reconnect socket creation failed (attempt ${attempt}/${LOGIN_MAX_RECONNECTS}): ${String(sockErr)}`
7021
7085
  );
7022
7086
  current.error = `Reconnection failed: ${String(sockErr)}`;
7023
7087
  return;
@@ -7031,7 +7095,7 @@ async function startLogin(opts) {
7031
7095
  const hasAuth = await authExists(authDir);
7032
7096
  const selfId = readSelfId(authDir);
7033
7097
  console.error(
7034
- `${TAG16} startLogin account=${accountId} force=${!!force} hasAuth=${hasAuth}` + (existing0 ? ` activeLogin={id=${existing0.id.slice(0, 8)},age=${Math.round((Date.now() - existing0.startedAt) / 1e3)}s,hasQr=${!!existing0.qr}}` : " activeLogin=none")
7098
+ `${TAG17} startLogin account=${accountId} force=${!!force} hasAuth=${hasAuth}` + (existing0 ? ` activeLogin={id=${existing0.id.slice(0, 8)},age=${Math.round((Date.now() - existing0.startedAt) / 1e3)}s,hasQr=${!!existing0.qr}}` : " activeLogin=none")
7035
7099
  );
7036
7100
  if (hasAuth && !force) {
7037
7101
  const who = selfId.e164 ?? selfId.jid ?? "unknown";
@@ -7043,7 +7107,7 @@ async function startLogin(opts) {
7043
7107
  await clearAuth(authDir);
7044
7108
  const existing = activeLogins.get(accountId);
7045
7109
  if (existing && isLoginFresh(existing) && existing.qrDataUrl && !force) {
7046
- console.error(`${TAG16} startLogin account=${accountId} guard: returning existing QR (age=${Math.round((Date.now() - existing.startedAt) / 1e3)}s)`);
7110
+ console.error(`${TAG17} startLogin account=${accountId} guard: returning existing QR (age=${Math.round((Date.now() - existing.startedAt) / 1e3)}s)`);
7047
7111
  return {
7048
7112
  qrDataUrl: existing.qrDataUrl,
7049
7113
  qrRaw: existing.qr,
@@ -7051,7 +7115,7 @@ async function startLogin(opts) {
7051
7115
  };
7052
7116
  }
7053
7117
  if (existing) {
7054
- console.error(`${TAG16} startLogin account=${accountId} ${force ? "force override" : "stale/no-QR"}, resetting active login`);
7118
+ console.error(`${TAG17} startLogin account=${accountId} ${force ? "force override" : "stale/no-QR"}, resetting active login`);
7055
7119
  }
7056
7120
  resetActiveLogin(accountId);
7057
7121
  let resolveQr = null;
@@ -7073,14 +7137,14 @@ async function startLogin(opts) {
7073
7137
  onQr: (qr2) => {
7074
7138
  loginQrCount++;
7075
7139
  if (pendingQr) {
7076
- console.error(`${TAG16} QR rotation #${loginQrCount} received for account=${accountId} \u2014 not forwarded (initial QR already captured)`);
7140
+ console.error(`${TAG17} QR rotation #${loginQrCount} received for account=${accountId} \u2014 not forwarded (initial QR already captured)`);
7077
7141
  return;
7078
7142
  }
7079
7143
  pendingQr = qr2;
7080
7144
  const current = activeLogins.get(accountId);
7081
7145
  if (current && !current.qr) current.qr = qr2;
7082
7146
  clearTimeout(qrTimer);
7083
- console.error(`${TAG16} QR #${loginQrCount} received for account=${accountId} \u2014 forwarding to caller`);
7147
+ console.error(`${TAG17} QR #${loginQrCount} received for account=${accountId} \u2014 forwarding to caller`);
7084
7148
  resolveQr?.(qr2);
7085
7149
  }
7086
7150
  });
@@ -7100,7 +7164,7 @@ async function startLogin(opts) {
7100
7164
  activeLogins.set(accountId, login);
7101
7165
  if (pendingQr && !login.qr) login.qr = pendingQr;
7102
7166
  loginConnectionLoop(accountId, login).catch((err) => {
7103
- console.error(`${TAG16} loginConnectionLoop unexpected error: ${String(err)}`);
7167
+ console.error(`${TAG17} loginConnectionLoop unexpected error: ${String(err)}`);
7104
7168
  const current = activeLogins.get(accountId);
7105
7169
  if (current?.id === login.id) {
7106
7170
  current.error = `Unexpected login error: ${String(err)}`;
@@ -7125,7 +7189,7 @@ async function waitForLogin(opts) {
7125
7189
  const { accountId, timeoutMs = 6e4 } = opts;
7126
7190
  const login = activeLogins.get(accountId);
7127
7191
  console.error(
7128
- `${TAG16} waitForLogin account=${accountId} timeout=${timeoutMs}ms` + (login ? ` login={id=${login.id.slice(0, 8)},age=${Math.round((Date.now() - login.startedAt) / 1e3)}s,connected=${login.connected},hasQr=${!!login.qr}}` : " login=none")
7192
+ `${TAG17} waitForLogin account=${accountId} timeout=${timeoutMs}ms` + (login ? ` login={id=${login.id.slice(0, 8)},age=${Math.round((Date.now() - login.startedAt) / 1e3)}s,connected=${login.connected},hasQr=${!!login.qr}}` : " login=none")
7129
7193
  );
7130
7194
  if (!login) {
7131
7195
  return { connected: false, message: "No active WhatsApp login in progress." };
@@ -7138,7 +7202,7 @@ async function waitForLogin(opts) {
7138
7202
  while (Date.now() < deadline) {
7139
7203
  if (login.connected) {
7140
7204
  const selfId = readSelfId(login.authDir);
7141
- console.error(`${TAG16} login complete for account=${accountId} phone=${selfId.e164 ?? "unknown"}`);
7205
+ console.error(`${TAG17} login complete for account=${accountId} phone=${selfId.e164 ?? "unknown"}`);
7142
7206
  const sock = login.sock;
7143
7207
  const authDir = login.authDir;
7144
7208
  activeLogins.delete(accountId);
@@ -7158,7 +7222,7 @@ async function waitForLogin(opts) {
7158
7222
  await new Promise((r) => setTimeout(r, 1e3));
7159
7223
  }
7160
7224
  const elapsed = Math.round((Date.now() - (deadline - timeoutMs)) / 1e3);
7161
- console.error(`${TAG16} waitForLogin timeout account=${accountId} elapsed=${elapsed}s \u2014 cleaning up active login`);
7225
+ console.error(`${TAG17} waitForLogin timeout account=${accountId} elapsed=${elapsed}s \u2014 cleaning up active login`);
7162
7226
  resetActiveLogin(accountId);
7163
7227
  return { connected: false, message: "Login timed out. Try generating a new QR." };
7164
7228
  }
@@ -7272,17 +7336,17 @@ function serializeWhatsAppSchema() {
7272
7336
  }
7273
7337
 
7274
7338
  // server/routes/whatsapp.ts
7275
- var TAG17 = "[whatsapp:api]";
7339
+ var TAG18 = "[whatsapp:api]";
7276
7340
  var PLATFORM_ROOT4 = process.env.MAXY_PLATFORM_ROOT || "";
7277
7341
  var app7 = new Hono();
7278
7342
  app7.get("/status", (c) => {
7279
7343
  try {
7280
7344
  const status = getStatus();
7281
7345
  const summary = status.map((a) => `${a.accountId}:${a.connected ? "up" : "down"}`).join(", ");
7282
- console.error(`${TAG17} status accounts=${status.length} [${summary}]`);
7346
+ console.error(`${TAG18} status accounts=${status.length} [${summary}]`);
7283
7347
  return c.json({ accounts: status });
7284
7348
  } catch (err) {
7285
- console.error(`${TAG17} status error: ${String(err)}`);
7349
+ console.error(`${TAG18} status error: ${String(err)}`);
7286
7350
  return c.json({ error: String(err) }, 500);
7287
7351
  }
7288
7352
  });
@@ -7293,10 +7357,10 @@ app7.post("/login/start", async (c) => {
7293
7357
  const force = body.force ?? false;
7294
7358
  const authDir = join6(MAXY_DIR, "credentials", "whatsapp", accountId);
7295
7359
  const result = await startLogin({ accountId, authDir, force });
7296
- console.error(`${TAG17} login/start result account=${accountId} hasQr=${!!result.qrRaw}${result.selfPhone ? ` phone=${result.selfPhone}` : ""}`);
7360
+ console.error(`${TAG18} login/start result account=${accountId} hasQr=${!!result.qrRaw}${result.selfPhone ? ` phone=${result.selfPhone}` : ""}`);
7297
7361
  return c.json(result);
7298
7362
  } catch (err) {
7299
- console.error(`${TAG17} login/start error: ${String(err)}`);
7363
+ console.error(`${TAG18} login/start error: ${String(err)}`);
7300
7364
  return c.json({ error: String(err) }, 500);
7301
7365
  }
7302
7366
  });
@@ -7311,7 +7375,7 @@ app7.post("/login/wait", async (c) => {
7311
7375
  try {
7312
7376
  await registerLoginSocket(accountId, result.sock, result.authDir);
7313
7377
  } catch (regErr) {
7314
- console.error(`${TAG17} registerLoginSocket failed account=${accountId}: ${String(regErr)}`);
7378
+ console.error(`${TAG18} registerLoginSocket failed account=${accountId}: ${String(regErr)}`);
7315
7379
  }
7316
7380
  try {
7317
7381
  const account = resolveAccount();
@@ -7319,16 +7383,16 @@ app7.post("/login/wait", async (c) => {
7319
7383
  const persistResult = persistAfterPairing(account.accountDir, accountId, result.selfPhone ?? null);
7320
7384
  configPersisted = persistResult.ok;
7321
7385
  if (!persistResult.ok) {
7322
- console.error(`${TAG17} config persist failed account=${accountId}: ${persistResult.error}`);
7386
+ console.error(`${TAG18} config persist failed account=${accountId}: ${persistResult.error}`);
7323
7387
  }
7324
7388
  } else {
7325
- console.error(`${TAG17} config persist skipped \u2014 no account resolved`);
7389
+ console.error(`${TAG18} config persist skipped \u2014 no account resolved`);
7326
7390
  }
7327
7391
  } catch (persistErr) {
7328
- console.error(`${TAG17} config persist error account=${accountId}: ${String(persistErr)}`);
7392
+ console.error(`${TAG18} config persist error account=${accountId}: ${String(persistErr)}`);
7329
7393
  }
7330
7394
  }
7331
- console.error(`${TAG17} login/wait result account=${accountId} connected=${result.connected}${result.selfPhone ? ` phone=${result.selfPhone}` : ""} configPersisted=${configPersisted}`);
7395
+ console.error(`${TAG18} login/wait result account=${accountId} connected=${result.connected}${result.selfPhone ? ` phone=${result.selfPhone}` : ""} configPersisted=${configPersisted}`);
7332
7396
  return c.json({
7333
7397
  connected: result.connected,
7334
7398
  message: result.message,
@@ -7336,7 +7400,7 @@ app7.post("/login/wait", async (c) => {
7336
7400
  configPersisted
7337
7401
  });
7338
7402
  } catch (err) {
7339
- console.error(`${TAG17} login/wait error: ${String(err)}`);
7403
+ console.error(`${TAG18} login/wait error: ${String(err)}`);
7340
7404
  return c.json({ error: String(err) }, 500);
7341
7405
  }
7342
7406
  });
@@ -7347,7 +7411,7 @@ app7.post("/disconnect", async (c) => {
7347
7411
  await stopConnection(accountId);
7348
7412
  return c.json({ disconnected: true, accountId });
7349
7413
  } catch (err) {
7350
- console.error(`${TAG17} disconnect error: ${String(err)}`);
7414
+ console.error(`${TAG18} disconnect error: ${String(err)}`);
7351
7415
  return c.json({ error: String(err) }, 500);
7352
7416
  }
7353
7417
  });
@@ -7358,7 +7422,7 @@ app7.post("/reconnect", async (c) => {
7358
7422
  await startConnection(accountId);
7359
7423
  return c.json({ reconnecting: true, accountId });
7360
7424
  } catch (err) {
7361
- console.error(`${TAG17} reconnect error: ${String(err)}`);
7425
+ console.error(`${TAG18} reconnect error: ${String(err)}`);
7362
7426
  return c.json({ error: String(err) }, 500);
7363
7427
  }
7364
7428
  });
@@ -7377,7 +7441,7 @@ app7.post("/send", async (c) => {
7377
7441
  const result = await sendTextMessage(sock, to, text, { accountId });
7378
7442
  return c.json(result);
7379
7443
  } catch (err) {
7380
- console.error(`${TAG17} send error: ${String(err)}`);
7444
+ console.error(`${TAG18} send error: ${String(err)}`);
7381
7445
  return c.json({ error: String(err) }, 500);
7382
7446
  }
7383
7447
  });
@@ -7398,7 +7462,7 @@ app7.post("/config", async (c) => {
7398
7462
  return c.json({ ok: false, error: 'Missing required field "phone" (E.164 format, e.g. +441234567890).' }, 400);
7399
7463
  }
7400
7464
  const result = addAdminPhone(account.accountDir, phone);
7401
- console.error(`${TAG17} config action=add-admin-phone phone=${phone} ok=${result.ok}`);
7465
+ console.error(`${TAG18} config action=add-admin-phone phone=${phone} ok=${result.ok}`);
7402
7466
  return c.json(result, result.ok ? 200 : 400);
7403
7467
  }
7404
7468
  case "remove-admin-phone": {
@@ -7406,12 +7470,12 @@ app7.post("/config", async (c) => {
7406
7470
  return c.json({ ok: false, error: 'Missing required field "phone".' }, 400);
7407
7471
  }
7408
7472
  const result = removeAdminPhone(account.accountDir, phone);
7409
- console.error(`${TAG17} config action=remove-admin-phone phone=${phone} ok=${result.ok}`);
7473
+ console.error(`${TAG18} config action=remove-admin-phone phone=${phone} ok=${result.ok}`);
7410
7474
  return c.json(result, result.ok ? 200 : 400);
7411
7475
  }
7412
7476
  case "list-admin-phones": {
7413
7477
  const phones = readAdminPhones(account.accountDir);
7414
- console.error(`${TAG17} config action=list-admin-phones count=${phones.length}`);
7478
+ console.error(`${TAG18} config action=list-admin-phones count=${phones.length}`);
7415
7479
  return c.json({ ok: true, phones });
7416
7480
  }
7417
7481
  case "set-public-agent": {
@@ -7419,14 +7483,14 @@ app7.post("/config", async (c) => {
7419
7483
  return c.json({ ok: false, error: 'Missing required field "slug" (the agent directory name, e.g. "my-agent").' }, 400);
7420
7484
  }
7421
7485
  const result = setPublicAgent(account.accountDir, slug);
7422
- console.error(`${TAG17} config action=set-public-agent slug=${slug} ok=${result.ok}`);
7486
+ console.error(`${TAG18} config action=set-public-agent slug=${slug} ok=${result.ok}`);
7423
7487
  return c.json(result, result.ok ? 200 : 400);
7424
7488
  }
7425
7489
  case "get-public-agent": {
7426
7490
  const targetAccount = typeof accountId === "string" && accountId.trim() ? accountId.trim() : "default";
7427
7491
  const targetGroup = typeof groupJid === "string" && groupJid.trim() ? groupJid.trim() : void 0;
7428
7492
  const resolved = resolvePublicAgent(account.accountDir, { accountId: targetAccount, groupJid: targetGroup });
7429
- console.error(`${TAG17} config action=get-public-agent accountId=${targetAccount} groupJid=${targetGroup ?? "none"} slug=${resolved?.slug ?? "none"} source=${resolved?.source ?? "none"}`);
7493
+ console.error(`${TAG18} config action=get-public-agent accountId=${targetAccount} groupJid=${targetGroup ?? "none"} slug=${resolved?.slug ?? "none"} source=${resolved?.source ?? "none"}`);
7430
7494
  return c.json({ ok: true, slug: resolved?.slug ?? null, source: resolved?.source ?? null });
7431
7495
  }
7432
7496
  case "set-group-public-agent": {
@@ -7438,7 +7502,7 @@ app7.post("/config", async (c) => {
7438
7502
  }
7439
7503
  const targetAccount = typeof accountId === "string" && accountId.trim() ? accountId.trim() : "default";
7440
7504
  const result = setGroupPublicAgent(account.accountDir, targetAccount, groupJid, slug);
7441
- console.error(`${TAG17} config action=set-group-public-agent accountId=${targetAccount} groupJid=${groupJid} slug=${slug} ok=${result.ok}`);
7505
+ console.error(`${TAG18} config action=set-group-public-agent accountId=${targetAccount} groupJid=${groupJid} slug=${slug} ok=${result.ok}`);
7442
7506
  return c.json(result, result.ok ? 200 : 400);
7443
7507
  }
7444
7508
  case "unset-group-public-agent": {
@@ -7447,7 +7511,7 @@ app7.post("/config", async (c) => {
7447
7511
  }
7448
7512
  const targetAccount = typeof accountId === "string" && accountId.trim() ? accountId.trim() : "default";
7449
7513
  const result = unsetGroupPublicAgent(account.accountDir, targetAccount, groupJid);
7450
- console.error(`${TAG17} config action=unset-group-public-agent accountId=${targetAccount} groupJid=${groupJid} ok=${result.ok}`);
7514
+ console.error(`${TAG18} config action=unset-group-public-agent accountId=${targetAccount} groupJid=${groupJid} ok=${result.ok}`);
7451
7515
  return c.json(result, result.ok ? 200 : 400);
7452
7516
  }
7453
7517
  case "list-public-agents": {
@@ -7464,26 +7528,26 @@ app7.post("/config", async (c) => {
7464
7528
  const config = JSON.parse(readFileSync9(configPath2, "utf-8"));
7465
7529
  agents.push({ slug: entry.name, displayName: config.displayName ?? entry.name });
7466
7530
  } catch {
7467
- console.error(`${TAG17} config action=list-public-agents error="failed to parse config.json for agent ${entry.name}" \u2014 skipping`);
7531
+ console.error(`${TAG18} config action=list-public-agents error="failed to parse config.json for agent ${entry.name}" \u2014 skipping`);
7468
7532
  }
7469
7533
  }
7470
7534
  } catch (err) {
7471
- console.error(`${TAG17} config action=list-public-agents error="failed to scan agents directory: ${String(err)}"`);
7535
+ console.error(`${TAG18} config action=list-public-agents error="failed to scan agents directory: ${String(err)}"`);
7472
7536
  }
7473
7537
  }
7474
- console.error(`${TAG17} config action=list-public-agents count=${agents.length}`);
7538
+ console.error(`${TAG18} config action=list-public-agents count=${agents.length}`);
7475
7539
  return c.json({ ok: true, agents });
7476
7540
  }
7477
7541
  case "schema": {
7478
7542
  const text = serializeWhatsAppSchema();
7479
- console.error(`${TAG17} config action=schema`);
7543
+ console.error(`${TAG18} config action=schema`);
7480
7544
  return c.json({ ok: true, text });
7481
7545
  }
7482
7546
  case "list-groups": {
7483
7547
  const groupAccountId = accountId ?? "default";
7484
7548
  const sock = getSocket(groupAccountId);
7485
7549
  if (!sock) {
7486
- console.error(`${TAG17} config action=list-groups error="not connected" accountId=${groupAccountId}`);
7550
+ console.error(`${TAG18} config action=list-groups error="not connected" accountId=${groupAccountId}`);
7487
7551
  return c.json({ ok: false, error: `WhatsApp account "${groupAccountId}" is not connected. Connect first, then retry.` });
7488
7552
  }
7489
7553
  try {
@@ -7493,10 +7557,10 @@ app7.post("/config", async (c) => {
7493
7557
  name: g.subject ?? g.id,
7494
7558
  participantCount: Array.isArray(g.participants) ? g.participants.length : 0
7495
7559
  }));
7496
- console.error(`${TAG17} config action=list-groups count=${groups.length} accountId=${groupAccountId}`);
7560
+ console.error(`${TAG18} config action=list-groups count=${groups.length} accountId=${groupAccountId}`);
7497
7561
  return c.json({ ok: true, groups });
7498
7562
  } catch (err) {
7499
- console.error(`${TAG17} config action=list-groups error="${String(err)}" accountId=${groupAccountId}`);
7563
+ console.error(`${TAG18} config action=list-groups error="${String(err)}" accountId=${groupAccountId}`);
7500
7564
  return c.json({ ok: false, error: `Failed to fetch groups: ${String(err)}` });
7501
7565
  }
7502
7566
  }
@@ -7506,12 +7570,12 @@ app7.post("/config", async (c) => {
7506
7570
  }
7507
7571
  const result = updateConfig(account.accountDir, fields);
7508
7572
  const fieldNames = Object.keys(fields);
7509
- console.error(`${TAG17} config action=update-config fields=[${fieldNames.join(",")}] ok=${result.ok}`);
7573
+ console.error(`${TAG18} config action=update-config fields=[${fieldNames.join(",")}] ok=${result.ok}`);
7510
7574
  return c.json(result, result.ok ? 200 : 400);
7511
7575
  }
7512
7576
  case "get-config": {
7513
7577
  const waConfig = getConfig(account.accountDir);
7514
- console.error(`${TAG17} config action=get-config`);
7578
+ console.error(`${TAG18} config action=get-config`);
7515
7579
  return c.json({ ok: true, config: waConfig });
7516
7580
  }
7517
7581
  default:
@@ -7521,7 +7585,7 @@ app7.post("/config", async (c) => {
7521
7585
  );
7522
7586
  }
7523
7587
  } catch (err) {
7524
- console.error(`${TAG17} config error: ${String(err)}`);
7588
+ console.error(`${TAG18} config error: ${String(err)}`);
7525
7589
  return c.json({ ok: false, error: String(err) }, 500);
7526
7590
  }
7527
7591
  });
@@ -7543,16 +7607,16 @@ app7.post("/send-document", async (c) => {
7543
7607
  const accountResolved = realpathSync2(accountDir);
7544
7608
  if (!resolvedPath.startsWith(accountResolved + "/")) {
7545
7609
  const sanitised = filePath.replace(accountDir, "<account>/");
7546
- console.error(`${TAG17} send-document REJECTED path=${sanitised} reason=outside_account_directory`);
7610
+ console.error(`${TAG18} send-document REJECTED path=${sanitised} reason=outside_account_directory`);
7547
7611
  return c.json({ error: "Access denied: file is outside the account directory" }, 403);
7548
7612
  }
7549
7613
  } catch (err) {
7550
7614
  const code = err.code;
7551
7615
  if (code === "ENOENT") {
7552
- console.error(`${TAG17} send-document ENOENT path=${filePath}`);
7616
+ console.error(`${TAG18} send-document ENOENT path=${filePath}`);
7553
7617
  return c.json({ error: `File not found: ${filePath}` }, 404);
7554
7618
  }
7555
- console.error(`${TAG17} send-document path error: ${String(err)}`);
7619
+ console.error(`${TAG18} send-document path error: ${String(err)}`);
7556
7620
  return c.json({ error: String(err) }, 500);
7557
7621
  }
7558
7622
  const fileStat = await stat3(resolvedPath);
@@ -7574,11 +7638,11 @@ app7.post("/send-document", async (c) => {
7574
7638
  caption
7575
7639
  }, { accountId });
7576
7640
  console.error(
7577
- `${TAG17} send-document to=${to} size=${fileStat.size} mime=${mimetype} ok=${result.success}` + (result.messageId ? ` id=${result.messageId}` : "")
7641
+ `${TAG18} send-document to=${to} size=${fileStat.size} mime=${mimetype} ok=${result.success}` + (result.messageId ? ` id=${result.messageId}` : "")
7578
7642
  );
7579
7643
  return c.json(result);
7580
7644
  } catch (err) {
7581
- console.error(`${TAG17} send-document error: ${String(err)}`);
7645
+ console.error(`${TAG18} send-document error: ${String(err)}`);
7582
7646
  return c.json({ error: String(err) }, 500);
7583
7647
  }
7584
7648
  });
@@ -7588,11 +7652,11 @@ app7.get("/activity", (c) => {
7588
7652
  const result = getChannelActivity(accountId);
7589
7653
  const total = result.accounts.reduce((sum, a) => sum + a.total, 0);
7590
7654
  console.error(
7591
- `${TAG17} activity accounts=${result.accounts.length} total=${total} recentEvents=${result.recentEvents.length}` + (accountId ? ` filter=${accountId}` : "")
7655
+ `${TAG18} activity accounts=${result.accounts.length} total=${total} recentEvents=${result.recentEvents.length}` + (accountId ? ` filter=${accountId}` : "")
7592
7656
  );
7593
7657
  return c.json(result);
7594
7658
  } catch (err) {
7595
- console.error(`${TAG17} activity error: ${String(err)}`);
7659
+ console.error(`${TAG18} activity error: ${String(err)}`);
7596
7660
  return c.json({ error: String(err) }, 500);
7597
7661
  }
7598
7662
  });
@@ -7611,10 +7675,10 @@ app7.get("/conversations", (c) => {
7611
7675
  };
7612
7676
  });
7613
7677
  conversations.sort((a, b) => (b.lastMessageTimestamp ?? 0) - (a.lastMessageTimestamp ?? 0));
7614
- console.error(`${TAG17} conversations account=${accountId} count=${conversations.length}`);
7678
+ console.error(`${TAG18} conversations account=${accountId} count=${conversations.length}`);
7615
7679
  return c.json({ conversations });
7616
7680
  } catch (err) {
7617
- console.error(`${TAG17} conversations error: ${String(err)}`);
7681
+ console.error(`${TAG18} conversations error: ${String(err)}`);
7618
7682
  return c.json({ error: String(err) }, 500);
7619
7683
  }
7620
7684
  });
@@ -7629,10 +7693,10 @@ app7.get("/messages", (c) => {
7629
7693
  const limit = limitParam ? parseInt(limitParam, 10) : void 0;
7630
7694
  const effectiveLimit = limit && !Number.isNaN(limit) && limit > 0 ? limit : void 0;
7631
7695
  const messages = getMessages(accountId, jid, effectiveLimit);
7632
- console.error(`${TAG17} messages account=${accountId} jid=${jid} limit=${effectiveLimit ?? "all"} returned=${messages.length}`);
7696
+ console.error(`${TAG18} messages account=${accountId} jid=${jid} limit=${effectiveLimit ?? "all"} returned=${messages.length}`);
7633
7697
  return c.json({ messages });
7634
7698
  } catch (err) {
7635
- console.error(`${TAG17} messages error: ${String(err)}`);
7699
+ console.error(`${TAG18} messages error: ${String(err)}`);
7636
7700
  return c.json({ error: String(err) }, 500);
7637
7701
  }
7638
7702
  });
@@ -7644,12 +7708,12 @@ app7.get("/group-info", async (c) => {
7644
7708
  return c.json({ error: "Missing required parameter: jid" }, 400);
7645
7709
  }
7646
7710
  if (!isGroupJid(jid)) {
7647
- console.error(`${TAG17} group-info error="not a group JID" jid=${jid} account=${accountId}`);
7711
+ console.error(`${TAG18} group-info error="not a group JID" jid=${jid} account=${accountId}`);
7648
7712
  return c.json({ error: `"${jid}" is not a group JID. Group JIDs end with @g.us.` }, 400);
7649
7713
  }
7650
7714
  const sock = getSocket(accountId);
7651
7715
  if (!sock) {
7652
- console.error(`${TAG17} group-info error="not connected" account=${accountId}`);
7716
+ console.error(`${TAG18} group-info error="not connected" account=${accountId}`);
7653
7717
  return c.json({ error: `WhatsApp account "${accountId}" is not connected. Connect first, then retry.` }, 400);
7654
7718
  }
7655
7719
  const meta = await sock.groupMetadata(jid);
@@ -7662,10 +7726,10 @@ app7.get("/group-info", async (c) => {
7662
7726
  participantCount: meta.participants.length,
7663
7727
  participants: meta.participants.map((p) => ({ jid: p.id, admin: p.admin ?? null }))
7664
7728
  };
7665
- console.error(`${TAG17} group-info jid=${jid} subject="${meta.subject}" participants=${meta.participants.length} account=${accountId}`);
7729
+ console.error(`${TAG18} group-info jid=${jid} subject="${meta.subject}" participants=${meta.participants.length} account=${accountId}`);
7666
7730
  return c.json(result);
7667
7731
  } catch (err) {
7668
- console.error(`${TAG17} group-info error="${String(err)}" jid=${jid} account=${accountId}`);
7732
+ console.error(`${TAG18} group-info error="${String(err)}" jid=${jid} account=${accountId}`);
7669
7733
  return c.json({ error: `Failed to fetch group info: ${String(err)}` }, 500);
7670
7734
  }
7671
7735
  });
@@ -8030,7 +8094,8 @@ app9.post("/", async (c) => {
8030
8094
  "unknown",
8031
8095
  "graph-labels-in-graph",
8032
8096
  "graph-default-view",
8033
- "graph-nav"
8097
+ "graph-nav",
8098
+ "event"
8034
8099
  ]);
8035
8100
  const kind = allowedKinds.has(kindRaw) ? kindRaw : "unknown";
8036
8101
  const msg = truncate(body.msg, MAX_MSG_LEN);
@@ -8044,29 +8109,42 @@ app9.post("/", async (c) => {
8044
8109
  const stackTrunc = truncate(body.stack, MAX_STACK_LEN);
8045
8110
  const head = stackHead(body.stack);
8046
8111
  const ts = (/* @__PURE__ */ new Date()).toISOString();
8047
- console.error(
8048
- `[client-error] ts=${ts} ip=${ip} kind=${kind} url=${JSON.stringify(url)} version=${version || "unknown"} ua=${JSON.stringify(ua)} msg=${JSON.stringify(msg)} stack-head=${JSON.stringify(head)} file-line-col=${JSON.stringify(fileLineCol)}`
8049
- );
8050
- rotateIfNeeded();
8051
- try {
8052
- const payload = {
8053
- ts,
8054
- ip,
8055
- kind,
8056
- url,
8057
- version,
8058
- ua,
8059
- msg,
8060
- stack: stackTrunc,
8061
- filename: file,
8062
- lineno: line,
8063
- colno: col,
8064
- tag: typeof body.tag === "string" ? truncate(body.tag, 32) : void 0,
8065
- status: typeof body.status === "number" ? body.status : void 0
8066
- };
8067
- appendFileSync2(CLIENT_ERRORS_LOG, JSON.stringify(payload) + "\n", "utf-8");
8068
- } catch (err) {
8069
- console.error(`[client-error] append failed: ${err instanceof Error ? err.message : String(err)}`);
8112
+ if (kind === "event") {
8113
+ const source = typeof body.source === "string" ? truncate(body.source, 64) : "unknown";
8114
+ const extra = Object.entries(body).filter(([k]) => !["kind", "source", "msg", "url", "ua", "version", "stack", "filename", "lineno", "colno", "tag", "status"].includes(k)).map(([k, v]) => {
8115
+ const safe = typeof v === "string" ? truncate(v, 200) : typeof v === "number" || typeof v === "boolean" ? String(v) : JSON.stringify(v).slice(0, 200);
8116
+ return `${k}=${safe}`;
8117
+ }).join(" ");
8118
+ console.log(
8119
+ `[client-event] ts=${ts} ip=${ip} source=${source} version=${version || "unknown"}${extra ? " " + extra : ""}`
8120
+ );
8121
+ } else {
8122
+ console.error(
8123
+ `[client-error] ts=${ts} ip=${ip} kind=${kind} url=${JSON.stringify(url)} version=${version || "unknown"} ua=${JSON.stringify(ua)} msg=${JSON.stringify(msg)} stack-head=${JSON.stringify(head)} file-line-col=${JSON.stringify(fileLineCol)}`
8124
+ );
8125
+ }
8126
+ if (kind !== "event") {
8127
+ rotateIfNeeded();
8128
+ try {
8129
+ const payload = {
8130
+ ts,
8131
+ ip,
8132
+ kind,
8133
+ url,
8134
+ version,
8135
+ ua,
8136
+ msg,
8137
+ stack: stackTrunc,
8138
+ filename: file,
8139
+ lineno: line,
8140
+ colno: col,
8141
+ tag: typeof body.tag === "string" ? truncate(body.tag, 32) : void 0,
8142
+ status: typeof body.status === "number" ? body.status : void 0
8143
+ };
8144
+ appendFileSync2(CLIENT_ERRORS_LOG, JSON.stringify(payload) + "\n", "utf-8");
8145
+ } catch (err) {
8146
+ console.error(`[client-error] append failed: ${err instanceof Error ? err.message : String(err)}`);
8147
+ }
8070
8148
  }
8071
8149
  return c.json({ ok: true });
8072
8150
  });
@@ -8546,7 +8624,7 @@ var app11 = new Hono();
8546
8624
  app11.post("/cancel", requireAdminSession, async (c) => {
8547
8625
  const session_key = c.var.sessionKey;
8548
8626
  try {
8549
- const { interruptClient: interruptClient2 } = await import("./client-pool-CD7WHZIK.js");
8627
+ const { interruptClient: interruptClient2 } = await import("./client-pool-N2Y57223.js");
8550
8628
  await interruptClient2(session_key);
8551
8629
  return c.json({ ok: true });
8552
8630
  } catch (err) {
@@ -9426,16 +9504,26 @@ app17.get("/", requireAdminSession, async (c) => {
9426
9504
  sessionKey: null,
9427
9505
  name: r.name,
9428
9506
  updatedAt: r.updatedAt,
9429
- phase: "flushed"
9507
+ phase: "flushed",
9508
+ channel: r.channel
9430
9509
  }));
9431
9510
  const inProgressRows = inProgressRaw.map((r) => ({
9432
9511
  conversationId: null,
9433
9512
  sessionKey: r.sessionKey,
9434
9513
  name: null,
9435
9514
  updatedAt: new Date(r.createdAt).toISOString(),
9436
- phase: "pre-flush"
9515
+ phase: "pre-flush",
9516
+ channel: "webchat"
9437
9517
  }));
9438
9518
  const sessions = [...flushedRows, ...inProgressRows].sort((a, b) => a.updatedAt < b.updatedAt ? 1 : a.updatedAt > b.updatedAt ? -1 : 0).slice(0, 20);
9519
+ const channelCounts = sessions.reduce((acc, s) => {
9520
+ const k = s.channel ?? "unknown";
9521
+ acc[k] = (acc[k] ?? 0) + 1;
9522
+ return acc;
9523
+ }, {});
9524
+ console.error(
9525
+ `[conversations-list] render rows=${sessions.length} channels=${JSON.stringify(channelCounts)}`
9526
+ );
9439
9527
  return c.json({ sessions });
9440
9528
  } catch (err) {
9441
9529
  console.error(`[sessions-list] Failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -9699,13 +9787,13 @@ async function cdpNavigateNewTab(url, opts = {}) {
9699
9787
  // server/routes/admin/device-browser.ts
9700
9788
  var app19 = new Hono();
9701
9789
  app19.post("/navigate", async (c) => {
9702
- const TAG18 = "[device-url:click]";
9790
+ const TAG20 = "[device-url:click]";
9703
9791
  let body;
9704
9792
  try {
9705
9793
  body = await c.req.json();
9706
9794
  } catch (err) {
9707
9795
  const detail = err instanceof Error ? err.message : String(err);
9708
- console.error(`${TAG18} reject reason=body-not-json detail=${detail} browser=fallback navigateResult=error`);
9796
+ console.error(`${TAG20} reject reason=body-not-json detail=${detail} browser=fallback navigateResult=error`);
9709
9797
  return c.json(
9710
9798
  { ok: false, navigateResult: "error", browser: "fallback", detail: "Request body was not valid JSON" },
9711
9799
  400
@@ -9715,7 +9803,7 @@ app19.post("/navigate", async (c) => {
9715
9803
  const intent = typeof body.intent === "string" ? body.intent : "";
9716
9804
  const hostname2 = typeof body.hostname === "string" ? body.hostname : "";
9717
9805
  if (!url) {
9718
- console.error(`${TAG18} reject reason=missing-url intent=${JSON.stringify(intent)} browser=fallback navigateResult=error`);
9806
+ console.error(`${TAG20} reject reason=missing-url intent=${JSON.stringify(intent)} browser=fallback navigateResult=error`);
9719
9807
  return c.json(
9720
9808
  { ok: false, navigateResult: "error", browser: "fallback", detail: "url field is required" },
9721
9809
  400
@@ -9725,7 +9813,7 @@ app19.post("/navigate", async (c) => {
9725
9813
  try {
9726
9814
  parsed = new URL(url);
9727
9815
  } catch {
9728
- console.error(`${TAG18} reject reason=url-malformed intent=${JSON.stringify(intent)} url=${url} browser=fallback navigateResult=error`);
9816
+ console.error(`${TAG20} reject reason=url-malformed intent=${JSON.stringify(intent)} url=${url} browser=fallback navigateResult=error`);
9729
9817
  return c.json(
9730
9818
  { ok: false, navigateResult: "error", browser: "fallback", detail: "url is not a valid URL" },
9731
9819
  400
@@ -9733,7 +9821,7 @@ app19.post("/navigate", async (c) => {
9733
9821
  }
9734
9822
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
9735
9823
  console.error(
9736
- `${TAG18} reject reason=scheme-not-allowed scheme=${parsed.protocol} intent=${JSON.stringify(intent)} browser=fallback navigateResult=error`
9824
+ `${TAG20} reject reason=scheme-not-allowed scheme=${parsed.protocol} intent=${JSON.stringify(intent)} browser=fallback navigateResult=error`
9737
9825
  );
9738
9826
  return c.json(
9739
9827
  {
@@ -9749,7 +9837,7 @@ app19.post("/navigate", async (c) => {
9749
9837
  const cdpOk = await ensureCdp(transport);
9750
9838
  if (!cdpOk) {
9751
9839
  console.error(
9752
- `${TAG18} intent=${JSON.stringify(intent)} browser=fallback navigateResult=cdp-unreachable hostname=${JSON.stringify(hostname2)}`
9840
+ `${TAG20} intent=${JSON.stringify(intent)} browser=fallback navigateResult=cdp-unreachable hostname=${JSON.stringify(hostname2)}`
9753
9841
  );
9754
9842
  return c.json(
9755
9843
  {
@@ -9765,7 +9853,7 @@ app19.post("/navigate", async (c) => {
9765
9853
  const browser = outcome.result === "ok" ? "vnc" : "fallback";
9766
9854
  const detailStr = outcome.detail ? ` detail=${JSON.stringify(outcome.detail.length > 230 ? outcome.detail.slice(0, 227) + "..." : outcome.detail)}` : "";
9767
9855
  console.error(
9768
- `${TAG18} intent=${JSON.stringify(intent)} browser=${browser} navigateResult=${outcome.result} hostname=${JSON.stringify(hostname2)} targetId=${outcome.targetId ?? "none"}${detailStr}`
9856
+ `${TAG20} intent=${JSON.stringify(intent)} browser=${browser} navigateResult=${outcome.result} hostname=${JSON.stringify(hostname2)} targetId=${outcome.targetId ?? "none"}${detailStr}`
9769
9857
  );
9770
9858
  if (outcome.result !== "ok") {
9771
9859
  return c.json(
@@ -9796,18 +9884,18 @@ var ALLOWED_EVENTS = /* @__PURE__ */ new Set([
9796
9884
  ]);
9797
9885
  var app20 = new Hono();
9798
9886
  app20.post("/", async (c) => {
9799
- const TAG18 = "[admin:events]";
9887
+ const TAG20 = "[admin:events]";
9800
9888
  let body;
9801
9889
  try {
9802
9890
  body = await c.req.json();
9803
9891
  } catch (err) {
9804
9892
  const detail = err instanceof Error ? err.message : String(err);
9805
- console.error(`${TAG18} reject reason=body-not-json detail=${detail}`);
9893
+ console.error(`${TAG20} reject reason=body-not-json detail=${detail}`);
9806
9894
  return c.json({ ok: false, detail: "Request body was not valid JSON" }, 400);
9807
9895
  }
9808
9896
  const event = typeof body.event === "string" ? body.event : "";
9809
9897
  if (!ALLOWED_EVENTS.has(event)) {
9810
- console.error(`${TAG18} reject reason=event-not-allowed event=${JSON.stringify(event)}`);
9898
+ console.error(`${TAG20} reject reason=event-not-allowed event=${JSON.stringify(event)}`);
9811
9899
  return c.json({ ok: false, detail: `Event "${event}" is not allowed` }, 400);
9812
9900
  }
9813
9901
  const rawFields = body.fields && typeof body.fields === "object" ? body.fields : {};
@@ -9831,7 +9919,7 @@ var events_default = app20;
9831
9919
  // server/routes/admin/cloudflare.ts
9832
9920
  import { homedir } from "os";
9833
9921
  import { resolve as resolve17 } from "path";
9834
- import { readFileSync as readFileSync15 } from "fs";
9922
+ import { readFileSync as readFileSync16 } from "fs";
9835
9923
 
9836
9924
  // app/lib/dns-label.ts
9837
9925
  var VALID_LABEL = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/;
@@ -9869,14 +9957,131 @@ function addAliasDomain(hostname2) {
9869
9957
  writeFileSync9(ALIAS_DOMAINS_PATH, JSON.stringify([...existing], null, 2) + "\n", "utf-8");
9870
9958
  }
9871
9959
 
9960
+ // app/lib/action-relay-queue.ts
9961
+ import {
9962
+ existsSync as existsSync20,
9963
+ mkdirSync as mkdirSync9,
9964
+ readdirSync as readdirSync5,
9965
+ readFileSync as readFileSync15,
9966
+ unlinkSync as unlinkSync2,
9967
+ writeFileSync as writeFileSync10
9968
+ } from "fs";
9969
+ import { join as join9 } from "path";
9970
+ var TAG19 = "[action-relay-queue]";
9971
+ var FILE_PREFIX = "action-completion-relay-";
9972
+ var FILE_SUFFIX = ".json";
9973
+ function queueDir(accountDir) {
9974
+ return join9(accountDir, "queue");
9975
+ }
9976
+ function relayFilePath(accountDir, actionId) {
9977
+ return join9(queueDir(accountDir), `${FILE_PREFIX}${actionId}${FILE_SUFFIX}`);
9978
+ }
9979
+ function enqueueActionCompletionRelay(opts) {
9980
+ const dir = queueDir(opts.accountDir);
9981
+ mkdirSync9(dir, { recursive: true });
9982
+ const filePath = relayFilePath(opts.accountDir, opts.actionId);
9983
+ const record = {
9984
+ actionId: opts.actionId,
9985
+ conversationId: opts.conversationId,
9986
+ message: opts.message,
9987
+ queuedAt: (/* @__PURE__ */ new Date()).toISOString()
9988
+ };
9989
+ const body = JSON.stringify(record);
9990
+ try {
9991
+ writeFileSync10(filePath, body, { flag: "wx", encoding: "utf-8" });
9992
+ console.log(
9993
+ `${TAG19} phase=enqueued actionId=${opts.actionId} conversationId=${opts.conversationId} path=${filePath} bytes=${Buffer.byteLength(body, "utf-8")}`
9994
+ );
9995
+ return { enqueued: true, path: filePath };
9996
+ } catch (e) {
9997
+ if (e.code === "EEXIST") {
9998
+ console.log(
9999
+ `${TAG19} phase=enqueue-skipped actionId=${opts.actionId} reason=already-queued`
10000
+ );
10001
+ return { enqueued: false, reason: "already-queued", path: filePath };
10002
+ }
10003
+ throw e;
10004
+ }
10005
+ }
10006
+ function consumeActionCompletionRelays(accountDir) {
10007
+ const dir = queueDir(accountDir);
10008
+ if (!existsSync20(dir)) return [];
10009
+ let entries;
10010
+ try {
10011
+ entries = readdirSync5(dir);
10012
+ } catch (e) {
10013
+ console.error(
10014
+ `${TAG19} phase=readdir-failed dir=${dir} error=${e instanceof Error ? e.message : String(e)}`
10015
+ );
10016
+ return [];
10017
+ }
10018
+ const records = [];
10019
+ for (const name of entries) {
10020
+ if (!name.startsWith(FILE_PREFIX) || !name.endsWith(FILE_SUFFIX)) continue;
10021
+ const filePath = join9(dir, name);
10022
+ let raw;
10023
+ try {
10024
+ raw = readFileSync15(filePath, "utf-8");
10025
+ } catch (e) {
10026
+ console.error(
10027
+ `${TAG19} phase=read-failed file=${name} error=${e instanceof Error ? e.message : String(e)}`
10028
+ );
10029
+ continue;
10030
+ }
10031
+ let parsed;
10032
+ try {
10033
+ parsed = JSON.parse(raw);
10034
+ } catch (e) {
10035
+ console.error(
10036
+ `${TAG19} phase=parse-failed file=${name} error=${e instanceof Error ? e.message : String(e)}`
10037
+ );
10038
+ continue;
10039
+ }
10040
+ if (!isActionCompletionRelay(parsed)) {
10041
+ console.error(
10042
+ `${TAG19} phase=shape-invalid file=${name} keys=${parsed && typeof parsed === "object" ? Object.keys(parsed).join(",") : "non-object"}`
10043
+ );
10044
+ continue;
10045
+ }
10046
+ records.push({ rec: parsed, filePath });
10047
+ }
10048
+ records.sort((a, b) => a.rec.queuedAt.localeCompare(b.rec.queuedAt));
10049
+ const out = [];
10050
+ const now = Date.now();
10051
+ for (const { rec, filePath } of records) {
10052
+ const queuedAtMs = Date.parse(rec.queuedAt);
10053
+ const ageMs = Number.isFinite(queuedAtMs) ? now - queuedAtMs : 0;
10054
+ out.push({ ...rec, ageMs, filePath });
10055
+ }
10056
+ return out;
10057
+ }
10058
+ function deleteConsumedRelay(filePath) {
10059
+ try {
10060
+ unlinkSync2(filePath);
10061
+ } catch (e) {
10062
+ const code = e.code;
10063
+ if (code !== "ENOENT") {
10064
+ console.error(
10065
+ `${TAG19} phase=unlink-failed file=${filePath} error=${e instanceof Error ? e.message : String(e)}`
10066
+ );
10067
+ }
10068
+ }
10069
+ }
10070
+ function isActionCompletionRelay(value) {
10071
+ if (!value || typeof value !== "object") return false;
10072
+ const v = value;
10073
+ return typeof v.actionId === "string" && typeof v.conversationId === "string" && typeof v.message === "string" && typeof v.queuedAt === "string";
10074
+ }
10075
+
9872
10076
  // server/routes/admin/cloudflare.ts
10077
+ import { existsSync as existsSyncFs, readFileSync as readFileSyncFs } from "fs";
9873
10078
  var SETUP_TIMEOUT_MS = 10 * 60 * 1e3;
9874
10079
  var DOMAINS_TIMEOUT_MS = 40 * 1e3;
9875
10080
  function loadBrandInfo() {
9876
10081
  const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ?? resolve17(process.cwd(), "..");
9877
10082
  const brandPath = resolve17(platformRoot2, "config", "brand.json");
9878
10083
  try {
9879
- const parsed = JSON.parse(readFileSync15(brandPath, "utf-8"));
10084
+ const parsed = JSON.parse(readFileSync16(brandPath, "utf-8"));
9880
10085
  const hostname2 = typeof parsed.hostname === "string" && parsed.hostname ? parsed.hostname : "maxy";
9881
10086
  const configDir2 = typeof parsed.configDir === "string" && parsed.configDir ? parsed.configDir : ".maxy";
9882
10087
  return { hostname: hostname2, configDir: configDir2 };
@@ -10172,13 +10377,79 @@ actionId: ${actionId}`,
10172
10377
  };
10173
10378
  return ok(success);
10174
10379
  });
10380
+ var RELAY_MAX_BODY = 8 * 1024;
10381
+ var RELAY_MAX_MESSAGE = 2048;
10382
+ var ACTION_ID_RE = /^[a-z0-9-]+$/;
10383
+ app21.post("/relay-completion", requireAdminSession, async (c) => {
10384
+ const sessionKey = c.var.sessionKey;
10385
+ let raw;
10386
+ try {
10387
+ raw = await c.req.text();
10388
+ } catch {
10389
+ return c.json({ ok: false, reason: "invalid-body" }, 400);
10390
+ }
10391
+ if (Buffer.byteLength(raw, "utf-8") > RELAY_MAX_BODY) {
10392
+ return c.json({ ok: false, reason: "body-too-large" }, 413);
10393
+ }
10394
+ let body;
10395
+ try {
10396
+ body = JSON.parse(raw);
10397
+ } catch {
10398
+ return c.json({ ok: false, reason: "invalid-json" }, 400);
10399
+ }
10400
+ if (!body || typeof body !== "object") {
10401
+ return c.json({ ok: false, reason: "invalid-body" }, 400);
10402
+ }
10403
+ const { actionId, message } = body;
10404
+ if (typeof actionId !== "string" || !ACTION_ID_RE.test(actionId) || actionId.length > 200) {
10405
+ console.error(`[cloudflare-relay-completion] phase=enqueue-skipped reason=invalid-actionid sessionKey=${sessionKey.slice(-8)}`);
10406
+ return c.json({ ok: false, reason: "invalid-actionid" }, 400);
10407
+ }
10408
+ if (typeof message !== "string" || message.length === 0 || message.length > RELAY_MAX_MESSAGE) {
10409
+ return c.json({ ok: false, reason: "invalid-message" }, 400);
10410
+ }
10411
+ const conversationId = getConversationIdForSession(sessionKey);
10412
+ if (!conversationId) {
10413
+ console.error(`[cloudflare-relay-completion] phase=enqueue-skipped reason=missing-conversation-id sessionKey=${sessionKey.slice(-8)}`);
10414
+ return c.json({ ok: false, reason: "missing-conversation-id" }, 400);
10415
+ }
10416
+ const account = resolveAccount();
10417
+ if (!account) {
10418
+ console.error(`[cloudflare-relay-completion] phase=enqueue-skipped reason=missing-account-dir actionId=${actionId}`);
10419
+ return c.json({ ok: false, reason: "missing-account-dir" }, 500);
10420
+ }
10421
+ const logPath2 = actionLogPath(actionId);
10422
+ let auditOutcome = "log-absent";
10423
+ if (existsSyncFs(logPath2)) {
10424
+ try {
10425
+ const lines = readFileSyncFs(logPath2, "utf-8").split("\n");
10426
+ const reconciled = reconcileCloudflareSetupFromLog(lines);
10427
+ auditOutcome = reconciled ? reconciled.kind : "log-incomplete";
10428
+ } catch (e) {
10429
+ auditOutcome = `read-failed:${e instanceof Error ? e.message : String(e)}`;
10430
+ }
10431
+ }
10432
+ console.log(`[cloudflare-relay-completion] phase=enqueue-attempt actionId=${actionId} auditOutcome=${auditOutcome} conversationId=${conversationId}`);
10433
+ try {
10434
+ enqueueActionCompletionRelay({
10435
+ accountDir: account.accountDir,
10436
+ actionId,
10437
+ conversationId,
10438
+ message
10439
+ });
10440
+ } catch (e) {
10441
+ console.error(`[cloudflare-relay-completion] phase=enqueue-failed actionId=${actionId} error=${e instanceof Error ? e.message : String(e)}`);
10442
+ return c.json({ ok: false, reason: "enqueue-failed" }, 500);
10443
+ }
10444
+ return c.body(null, 204);
10445
+ });
10175
10446
  var cloudflare_default = app21;
10176
10447
 
10177
10448
  // server/routes/admin/files.ts
10178
10449
  import { createReadStream as createReadStream3 } from "fs";
10179
10450
  import { readdir as readdir2, readFile as readFile4, stat as stat4, mkdir as mkdir3, writeFile as writeFile4, unlink as unlink2 } from "fs/promises";
10180
10451
  import { realpathSync as realpathSync4 } from "fs";
10181
- import { basename as basename6, dirname as dirname8, join as join9, resolve as resolve19, sep as sep2 } from "path";
10452
+ import { basename as basename6, dirname as dirname8, join as join10, resolve as resolve19, sep as sep2 } from "path";
10182
10453
  import { Readable as Readable2 } from "stream";
10183
10454
 
10184
10455
  // app/lib/data-path.ts
@@ -10536,7 +10807,7 @@ async function cascadeDeleteDocument(params) {
10536
10807
  var UUID_RE3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
10537
10808
  async function readMeta(absDir, baseName) {
10538
10809
  try {
10539
- const raw = await readFile4(join9(absDir, `${baseName}.meta.json`), "utf8");
10810
+ const raw = await readFile4(join10(absDir, `${baseName}.meta.json`), "utf8");
10540
10811
  const parsed = JSON.parse(raw);
10541
10812
  if (typeof parsed?.filename === "string") {
10542
10813
  return { filename: parsed.filename, mimeType: typeof parsed.mimeType === "string" ? parsed.mimeType : void 0 };
@@ -10574,7 +10845,7 @@ async function readAccountNames() {
10574
10845
  }
10575
10846
  async function enrich(absolute, entry, accountNames) {
10576
10847
  if (entry.kind === "directory" && UUID_RE3.test(entry.name)) {
10577
- const meta = await readMeta(join9(absolute, entry.name), entry.name);
10848
+ const meta = await readMeta(join10(absolute, entry.name), entry.name);
10578
10849
  if (meta?.filename) {
10579
10850
  entry.displayName = meta.filename;
10580
10851
  entry.mimeType = meta.mimeType;
@@ -10633,7 +10904,7 @@ app22.get("/", requireAdminSession, async (c) => {
10633
10904
  continue;
10634
10905
  }
10635
10906
  try {
10636
- const entryPath = join9(absolute, name);
10907
+ const entryPath = join10(absolute, name);
10637
10908
  const s = await stat4(entryPath);
10638
10909
  entries.push({
10639
10910
  name,
@@ -10806,7 +11077,7 @@ app22.delete("/", requireAdminSession, async (c) => {
10806
11077
  }
10807
11078
  const dot = base.lastIndexOf(".");
10808
11079
  const stem = dot === -1 ? base : base.slice(0, dot);
10809
- const sidecarPath = UUID_RE3.test(stem) && base !== `${stem}.meta.json` ? join9(dirname8(absolute), `${stem}.meta.json`) : null;
11080
+ const sidecarPath = UUID_RE3.test(stem) && base !== `${stem}.meta.json` ? join10(dirname8(absolute), `${stem}.meta.json`) : null;
10810
11081
  await unlink2(absolute);
10811
11082
  if (sidecarPath) {
10812
11083
  try {
@@ -11157,6 +11428,8 @@ function plainProperties(properties) {
11157
11428
  // server/routes/admin/graph-search.ts
11158
11429
  var DEFAULT_LIMIT = 20;
11159
11430
  var MAX_LIMIT = 2e3;
11431
+ var MESSAGE_FAMILY_LABELS = ["Message", "UserMessage", "AssistantMessage", "WhatsAppMessage"];
11432
+ var CONVERSATION_PARENT_LABELS = /* @__PURE__ */ new Set(["AdminConversation", "PublicConversation"]);
11160
11433
  var app23 = new Hono();
11161
11434
  app23.get("/", requireAdminSession, async (c) => {
11162
11435
  const sessionKey = c.var.sessionKey;
@@ -11175,13 +11448,15 @@ app23.get("/", requireAdminSession, async (c) => {
11175
11448
  }
11176
11449
  const parsedLimit = rawLimit ? parseInt(rawLimit, 10) : DEFAULT_LIMIT;
11177
11450
  const limit = Number.isFinite(parsedLimit) && parsedLimit > 0 ? Math.min(parsedLimit, MAX_LIMIT) : DEFAULT_LIMIT;
11451
+ const wantsBodyFulltext = labels.some((l) => CONVERSATION_PARENT_LABELS.has(l));
11452
+ const expandedLabels = wantsBodyFulltext ? Array.from(/* @__PURE__ */ new Set([...labels, ...MESSAGE_FAMILY_LABELS])) : labels;
11178
11453
  const started = Date.now();
11179
11454
  const session = getSession();
11180
11455
  try {
11181
11456
  const res = await hybrid(session, embed, {
11182
11457
  query: q,
11183
11458
  accountId,
11184
- labels,
11459
+ labels: expandedLabels,
11185
11460
  limit,
11186
11461
  degradeOnEmbedFailure: true
11187
11462
  });
@@ -11190,11 +11465,59 @@ app23.get("/", requireAdminSession, async (c) => {
11190
11465
  console.error(`[graph-search] embed-unavailable err="${res.embedError}" \u2014 bm25-only`);
11191
11466
  console.error(`[graph-search] embed-degraded query="${q}" results=${res.results.length}`);
11192
11467
  }
11468
+ let labelMatchCount = 0;
11469
+ let bodyFulltextCount = 0;
11470
+ let resolvedResults = res.results;
11471
+ if (wantsBodyFulltext) {
11472
+ const userLabelSet = new Set(labels);
11473
+ const seen = /* @__PURE__ */ new Map();
11474
+ for (const r of res.results) {
11475
+ const isMessage = r.labels.some((l) => MESSAGE_FAMILY_LABELS.includes(l));
11476
+ if (isMessage) continue;
11477
+ if (r.labels.some((l) => userLabelSet.has(l))) {
11478
+ const existing = seen.get(r.nodeId);
11479
+ if (!existing || existing.score < r.score) {
11480
+ seen.set(r.nodeId, r);
11481
+ }
11482
+ labelMatchCount++;
11483
+ }
11484
+ }
11485
+ for (const r of res.results) {
11486
+ const isMessage = r.labels.some((l) => MESSAGE_FAMILY_LABELS.includes(l));
11487
+ if (!isMessage) continue;
11488
+ const parent = r.related.find(
11489
+ (rel) => rel.relationship === "PART_OF" && rel.direction === "outgoing" && rel.labels.some((l) => userLabelSet.has(l))
11490
+ );
11491
+ if (!parent) continue;
11492
+ const existing = seen.get(parent.nodeId);
11493
+ if (existing) {
11494
+ if (existing.score < r.score) {
11495
+ seen.set(parent.nodeId, { ...existing, score: r.score });
11496
+ }
11497
+ } else {
11498
+ seen.set(parent.nodeId, {
11499
+ nodeId: parent.nodeId,
11500
+ labels: parent.labels,
11501
+ properties: parent.properties,
11502
+ score: r.score,
11503
+ related: []
11504
+ });
11505
+ }
11506
+ bodyFulltextCount++;
11507
+ }
11508
+ resolvedResults = Array.from(seen.values()).sort((a, b) => b.score - a.score);
11509
+ console.error(
11510
+ `[graph-search] label-match query="${q}" hits=${labelMatchCount}`
11511
+ );
11512
+ console.error(
11513
+ `[graph-search] body-fulltext query="${q}" hits=${bodyFulltextCount} ms=${total}`
11514
+ );
11515
+ }
11193
11516
  console.error(
11194
- `[graph-search] query="${q}" labels=${labels.join(",")} limit=${limit} mode=${res.mode} results=${res.results.length} expand-ms=${res.expandMs} total-ms=${total}`
11517
+ `[graph-search] query="${q}" labels=${labels.join(",")} expanded=${expandedLabels.length > labels.length ? 1 : 0} limit=${limit} mode=${res.mode} raw-results=${res.results.length} resolved-results=${resolvedResults.length} expand-ms=${res.expandMs} total-ms=${total}`
11195
11518
  );
11196
11519
  return c.json({
11197
- results: res.results,
11520
+ results: resolvedResults,
11198
11521
  mode: res.mode,
11199
11522
  embedError: res.embedError,
11200
11523
  elapsedMs: total
@@ -11393,6 +11716,10 @@ async function handleDefault(c, accountId) {
11393
11716
  const includeTrashed = c.req.query("includeTrashed") === "1";
11394
11717
  const includeAgentActions = c.req.query("includeAgentActions") === "1";
11395
11718
  const agentActionLabels = includeAgentActions ? [] : [...AGENT_ACTION_LABELS];
11719
+ const rawChannel = c.req.query("channel") ?? "";
11720
+ const channel = rawChannel.split(",").map((s) => s.trim()).filter(Boolean);
11721
+ const rawMessageSublabel = c.req.query("messageSublabel") ?? "";
11722
+ const messageSublabel = rawMessageSublabel.split(",").map((s) => s.trim()).filter(Boolean);
11396
11723
  if (labels.length === 0) {
11397
11724
  console.error(
11398
11725
  `[graph-page] load rejected reason=missing-filter account=${accountId} labels=`
@@ -11424,7 +11751,7 @@ async function handleDefault(c, accountId) {
11424
11751
  try {
11425
11752
  const countCypher = includeTrashed ? DEFAULT_COUNT_CYPHER_INCLUDE_TRASHED : DEFAULT_COUNT_CYPHER;
11426
11753
  const fetchCypher = includeTrashed ? DEFAULT_FETCH_CYPHER_INCLUDE_TRASHED : DEFAULT_FETCH_CYPHER;
11427
- const cypherParams = { accountId, labels, agentActionLabels };
11754
+ const cypherParams = { accountId, labels, agentActionLabels, channel, messageSublabel };
11428
11755
  const result = await session.executeRead(async (tx) => {
11429
11756
  const countResult = await tx.run(countCypher, cypherParams);
11430
11757
  const matchedRaw = countResult.records[0]?.get("matched");
@@ -11456,8 +11783,10 @@ async function handleDefault(c, accountId) {
11456
11783
  const nodes = result.rawNodes.map((n) => pruneNode(n, warnedClasses, conversationWarnings));
11457
11784
  const edges = result.rawEdges.filter((e) => e && e.id != null).map((e) => pruneEdge(e, warnedClasses));
11458
11785
  const trashedSuffix = includeTrashed ? " includeTrashed=1" : "";
11786
+ const channelSuffix = channel.length > 0 ? ` channel=${channel.join(",")}` : "";
11787
+ const sublabelSuffix = messageSublabel.length > 0 ? ` messageSublabel=${messageSublabel.join(",")}` : "";
11459
11788
  console.error(
11460
- `[graph-page] load mode=default account=${accountId} agentActions=${includeAgentActions} labels=${labels.join(",")}${trashedSuffix} nodes=${nodes.length} edges=${edges.length} ms=${elapsed}`
11789
+ `[graph-page] load mode=default account=${accountId} agentActions=${includeAgentActions} labels=${labels.join(",")}${trashedSuffix}${channelSuffix}${sublabelSuffix} nodes=${nodes.length} edges=${edges.length} ms=${elapsed}`
11461
11790
  );
11462
11791
  return c.json({ nodes, edges });
11463
11792
  } catch (err) {
@@ -11553,13 +11882,22 @@ async function handleNeighbourhood(c, accountId) {
11553
11882
  }
11554
11883
  }
11555
11884
  }
11885
+ var SUBFACET_PREDICATE = `
11886
+ AND (size($channel) = 0
11887
+ OR NOT any(lbl IN labels(n) WHERE lbl IN ['Conversation','AdminConversation','PublicConversation'])
11888
+ OR n.channel IS NULL
11889
+ OR n.channel IN $channel)
11890
+ AND (size($messageSublabel) = 0
11891
+ OR NOT any(lbl IN labels(n) WHERE lbl IN ['Message','UserMessage','AssistantMessage','WhatsAppMessage'])
11892
+ OR any(lbl IN labels(n) WHERE lbl IN $messageSublabel))
11893
+ `;
11556
11894
  var DEFAULT_COUNT_CYPHER = `
11557
11895
  MATCH (n)
11558
11896
  WHERE n.accountId = $accountId
11559
11897
  AND any(lbl IN labels(n) WHERE lbl IN $labels)
11560
11898
  AND NOT any(lbl IN labels(n) WHERE lbl IN $agentActionLabels)
11561
11899
  AND NOT n:Trashed
11562
- AND n.deletedAt IS NULL
11900
+ AND n.deletedAt IS NULL${SUBFACET_PREDICATE}
11563
11901
  RETURN count(n) AS matched
11564
11902
  `;
11565
11903
  var CONVERSATION_PROPS_PROJECTION = `CASE
@@ -11573,7 +11911,7 @@ var DEFAULT_FETCH_CYPHER = `
11573
11911
  AND any(lbl IN labels(n) WHERE lbl IN $labels)
11574
11912
  AND NOT any(lbl IN labels(n) WHERE lbl IN $agentActionLabels)
11575
11913
  AND NOT n:Trashed
11576
- AND n.deletedAt IS NULL
11914
+ AND n.deletedAt IS NULL${SUBFACET_PREDICATE}
11577
11915
  WITH collect(n) AS nodes, collect(elementId(n)) AS nodeIds
11578
11916
  UNWIND nodes AS n
11579
11917
  OPTIONAL MATCH (n)-[r]-(m)
@@ -11595,7 +11933,7 @@ var DEFAULT_COUNT_CYPHER_INCLUDE_TRASHED = `
11595
11933
  WHERE n.accountId = $accountId
11596
11934
  AND any(lbl IN labels(n) WHERE lbl IN $labels)
11597
11935
  AND NOT any(lbl IN labels(n) WHERE lbl IN $agentActionLabels)
11598
- AND n.deletedAt IS NULL
11936
+ AND n.deletedAt IS NULL${SUBFACET_PREDICATE}
11599
11937
  RETURN count(n) AS matched
11600
11938
  `;
11601
11939
  var DEFAULT_FETCH_CYPHER_INCLUDE_TRASHED = `
@@ -11603,7 +11941,7 @@ var DEFAULT_FETCH_CYPHER_INCLUDE_TRASHED = `
11603
11941
  WHERE n.accountId = $accountId
11604
11942
  AND any(lbl IN labels(n) WHERE lbl IN $labels)
11605
11943
  AND NOT any(lbl IN labels(n) WHERE lbl IN $agentActionLabels)
11606
- AND n.deletedAt IS NULL
11944
+ AND n.deletedAt IS NULL${SUBFACET_PREDICATE}
11607
11945
  WITH collect(n) AS nodes, collect(elementId(n)) AS nodeIds
11608
11946
  UNWIND nodes AS n
11609
11947
  OPTIONAL MATCH (n)-[r]-(m)
@@ -12197,7 +12535,7 @@ var adherence_default = app30;
12197
12535
  import neo4j3 from "neo4j-driver";
12198
12536
  import { readFile as readFile5, readdir as readdir3, stat as stat5 } from "fs/promises";
12199
12537
  import { resolve as resolve20, relative as relative2, isAbsolute } from "path";
12200
- import { existsSync as existsSync20 } from "fs";
12538
+ import { existsSync as existsSync21 } from "fs";
12201
12539
  var LIMIT = 50;
12202
12540
  var TEXT_MIME_PREFIXES = ["text/", "application/json", "application/markdown"];
12203
12541
  var ADMIN_AGENT_FILES = ["IDENTITY.md", "SOUL.md", "KNOWLEDGE.md"];
@@ -12345,7 +12683,7 @@ async function fetchAgentTemplateRows(accountDir) {
12345
12683
  async function unionSpecialistFilenames(overrideDir, bundledDir) {
12346
12684
  const names = /* @__PURE__ */ new Set();
12347
12685
  for (const dir of [overrideDir, bundledDir]) {
12348
- if (!existsSync20(dir)) continue;
12686
+ if (!existsSync21(dir)) continue;
12349
12687
  try {
12350
12688
  const entries = await readdir3(dir);
12351
12689
  for (const entry of entries) {
@@ -12360,7 +12698,7 @@ async function unionSpecialistFilenames(overrideDir, bundledDir) {
12360
12698
  }
12361
12699
  async function readAgentTemplateRow(inp) {
12362
12700
  let chosenPath = null;
12363
- if (existsSync20(inp.overridePath)) {
12701
+ if (existsSync21(inp.overridePath)) {
12364
12702
  try {
12365
12703
  validateFilePathInAccount(inp.overridePath, inp.overrideRoot);
12366
12704
  chosenPath = inp.overridePath;
@@ -12371,7 +12709,7 @@ async function readAgentTemplateRow(inp) {
12371
12709
  );
12372
12710
  return null;
12373
12711
  }
12374
- } else if (existsSync20(inp.bundledPath)) {
12712
+ } else if (existsSync21(inp.bundledPath)) {
12375
12713
  if (!isWithin(inp.bundledPath, inp.bundledRoot)) {
12376
12714
  console.error(
12377
12715
  `[admin/sidebar-artefacts] agent-template-read-failed agent=${inp.displayName} kind=${inp.logName} error="bundled path outside PLATFORM_ROOT"`
@@ -12413,7 +12751,7 @@ var sidebar_artefacts_default = app31;
12413
12751
  // server/routes/admin/sidebar-artefact-save.ts
12414
12752
  import { mkdir as mkdir4, readdir as readdir4, stat as stat6, writeFile as writeFile5 } from "fs/promises";
12415
12753
  import { resolve as resolve21 } from "path";
12416
- import { existsSync as existsSync21 } from "fs";
12754
+ import { existsSync as existsSync22 } from "fs";
12417
12755
  var ADMIN_AGENT_FILES2 = /* @__PURE__ */ new Set(["IDENTITY.md", "SOUL.md", "KNOWLEDGE.md"]);
12418
12756
  var UUID_RE4 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
12419
12757
  var app32 = new Hono();
@@ -12477,7 +12815,7 @@ async function resolveSavePath(id, accountId, accountDir) {
12477
12815
  }
12478
12816
  if (UUID_RE4.test(id)) {
12479
12817
  const dir = resolve21(ATTACHMENTS_ROOT, accountId, id);
12480
- if (!existsSync21(dir)) {
12818
+ if (!existsSync22(dir)) {
12481
12819
  return { kind: "reject", status: 400, reason: "not-found" };
12482
12820
  }
12483
12821
  try {
@@ -12501,7 +12839,7 @@ var sidebar_artefact_save_default = app32;
12501
12839
 
12502
12840
  // server/routes/admin/sidebar-artefact-content.ts
12503
12841
  import { readFile as readFile6, readdir as readdir5 } from "fs/promises";
12504
- import { existsSync as existsSync22 } from "fs";
12842
+ import { existsSync as existsSync23 } from "fs";
12505
12843
  import { resolve as resolve22 } from "path";
12506
12844
  var UUID_RE5 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
12507
12845
  var app33 = new Hono();
@@ -12515,7 +12853,7 @@ app33.get("/", requireAdminSession, async (c) => {
12515
12853
  return new Response("Not found", { status: 404 });
12516
12854
  }
12517
12855
  const dir = resolve22(ATTACHMENTS_ROOT, accountId, id);
12518
- if (!existsSync22(dir)) {
12856
+ if (!existsSync23(dir)) {
12519
12857
  console.error(`[admin/sidebar-artefact-content] not-found id=${id.slice(0, 8)}`);
12520
12858
  return new Response("Not found", { status: 404 });
12521
12859
  }
@@ -12577,7 +12915,7 @@ app34.route("/sidebar-artefact-content", sidebar_artefact_content_default);
12577
12915
  var admin_default = app34;
12578
12916
 
12579
12917
  // server/routes/sites.ts
12580
- import { existsSync as existsSync23, readFileSync as readFileSync16, realpathSync as realpathSync5, statSync as statSync8 } from "fs";
12918
+ import { existsSync as existsSync24, readFileSync as readFileSync17, realpathSync as realpathSync5, statSync as statSync8 } from "fs";
12581
12919
  import { resolve as resolve23 } from "path";
12582
12920
  var SAFE_SEG_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;
12583
12921
  var MIME = {
@@ -12644,7 +12982,7 @@ app35.get("/:rel{.*}", (c) => {
12644
12982
  }
12645
12983
  let stat7;
12646
12984
  try {
12647
- stat7 = existsSync23(filePath) ? statSync8(filePath) : null;
12985
+ stat7 = existsSync24(filePath) ? statSync8(filePath) : null;
12648
12986
  } catch {
12649
12987
  stat7 = null;
12650
12988
  }
@@ -12657,7 +12995,7 @@ app35.get("/:rel{.*}", (c) => {
12657
12995
  console.error(`[sites] path-traversal-rejected path=${reqPath} reason=escape status=403`);
12658
12996
  return c.text("Forbidden", 403);
12659
12997
  }
12660
- if (!existsSync23(filePath)) {
12998
+ if (!existsSync24(filePath)) {
12661
12999
  console.error(`[sites] not-found path=${reqPath} status=404`);
12662
13000
  return c.text("Not found", 404);
12663
13001
  }
@@ -12676,7 +13014,7 @@ app35.get("/:rel{.*}", (c) => {
12676
13014
  }
12677
13015
  let body;
12678
13016
  try {
12679
- body = readFileSync16(realPath);
13017
+ body = readFileSync17(realPath);
12680
13018
  } catch (err) {
12681
13019
  const code = err?.code;
12682
13020
  if (code === "EISDIR") {
@@ -12808,14 +13146,14 @@ function clientFrom(c) {
12808
13146
  );
12809
13147
  }
12810
13148
  var PLATFORM_ROOT7 = process.env.MAXY_PLATFORM_ROOT || "";
12811
- var BRAND_JSON_PATH = PLATFORM_ROOT7 ? join10(PLATFORM_ROOT7, "config", "brand.json") : "";
13149
+ var BRAND_JSON_PATH = PLATFORM_ROOT7 ? join11(PLATFORM_ROOT7, "config", "brand.json") : "";
12812
13150
  var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", domain: "getmaxy.com" };
12813
- if (BRAND_JSON_PATH && !existsSync24(BRAND_JSON_PATH)) {
13151
+ if (BRAND_JSON_PATH && !existsSync25(BRAND_JSON_PATH)) {
12814
13152
  console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);
12815
13153
  }
12816
- if (BRAND_JSON_PATH && existsSync24(BRAND_JSON_PATH)) {
13154
+ if (BRAND_JSON_PATH && existsSync25(BRAND_JSON_PATH)) {
12817
13155
  try {
12818
- const parsed = JSON.parse(readFileSync17(BRAND_JSON_PATH, "utf-8"));
13156
+ const parsed = JSON.parse(readFileSync18(BRAND_JSON_PATH, "utf-8"));
12819
13157
  BRAND = { ...BRAND, ...parsed };
12820
13158
  } catch (err) {
12821
13159
  console.error(`[brand] Failed to parse brand.json: ${err.message}`);
@@ -12834,11 +13172,11 @@ var brandLoginOpts = {
12834
13172
  bodyFont: BRAND.defaultFonts?.body,
12835
13173
  logoContainsName: !!BRAND.logoContainsName
12836
13174
  };
12837
- var ALIAS_DOMAINS_PATH2 = join10(homedir2(), BRAND.configDir, "alias-domains.json");
13175
+ var ALIAS_DOMAINS_PATH2 = join11(homedir2(), BRAND.configDir, "alias-domains.json");
12838
13176
  function loadAliasDomains() {
12839
13177
  try {
12840
- if (!existsSync24(ALIAS_DOMAINS_PATH2)) return null;
12841
- const parsed = JSON.parse(readFileSync17(ALIAS_DOMAINS_PATH2, "utf-8"));
13178
+ if (!existsSync25(ALIAS_DOMAINS_PATH2)) return null;
13179
+ const parsed = JSON.parse(readFileSync18(ALIAS_DOMAINS_PATH2, "utf-8"));
12842
13180
  if (!Array.isArray(parsed)) {
12843
13181
  console.error("[alias-domains] malformed alias-domains.json \u2014 expected array");
12844
13182
  return null;
@@ -13184,14 +13522,14 @@ app36.get("/agent-assets/:slug/:filename", (c) => {
13184
13522
  console.error(`[agent-assets] path-traversal-rejected slug=${slug} file=${filename}`);
13185
13523
  return c.text("Forbidden", 403);
13186
13524
  }
13187
- if (!existsSync24(filePath)) {
13525
+ if (!existsSync25(filePath)) {
13188
13526
  console.error(`[agent-assets] serve slug=${slug} file=${filename} status=404`);
13189
13527
  return c.text("Not found", 404);
13190
13528
  }
13191
13529
  const ext = "." + filename.split(".").pop()?.toLowerCase();
13192
13530
  const contentType = IMAGE_MIME[ext] || "application/octet-stream";
13193
13531
  console.log(`[agent-assets] serve slug=${slug} file=${filename} status=200`);
13194
- const body = readFileSync17(filePath);
13532
+ const body = readFileSync18(filePath);
13195
13533
  return c.body(body, 200, {
13196
13534
  "Content-Type": contentType,
13197
13535
  "Cache-Control": "public, max-age=3600"
@@ -13214,14 +13552,14 @@ app36.get("/generated/:filename", (c) => {
13214
13552
  console.error(`[generated] serve file=${filename} status=403`);
13215
13553
  return c.text("Forbidden", 403);
13216
13554
  }
13217
- if (!existsSync24(filePath)) {
13555
+ if (!existsSync25(filePath)) {
13218
13556
  console.error(`[generated] serve file=${filename} status=404`);
13219
13557
  return c.text("Not found", 404);
13220
13558
  }
13221
13559
  const ext = "." + filename.split(".").pop()?.toLowerCase();
13222
13560
  const contentType = IMAGE_MIME[ext] || "application/octet-stream";
13223
13561
  console.log(`[generated] serve file=${filename} status=200`);
13224
- const body = readFileSync17(filePath);
13562
+ const body = readFileSync18(filePath);
13225
13563
  return c.body(body, 200, {
13226
13564
  "Content-Type": contentType,
13227
13565
  "Cache-Control": "public, max-age=86400"
@@ -13231,9 +13569,9 @@ app36.route("/sites", sites_default);
13231
13569
  var htmlCache = /* @__PURE__ */ new Map();
13232
13570
  var brandLogoPath = "/brand/maxy-monochrome.png";
13233
13571
  var brandIconPath = "/brand/maxy-monochrome.png";
13234
- if (BRAND_JSON_PATH && existsSync24(BRAND_JSON_PATH)) {
13572
+ if (BRAND_JSON_PATH && existsSync25(BRAND_JSON_PATH)) {
13235
13573
  try {
13236
- const fullBrand = JSON.parse(readFileSync17(BRAND_JSON_PATH, "utf-8"));
13574
+ const fullBrand = JSON.parse(readFileSync18(BRAND_JSON_PATH, "utf-8"));
13237
13575
  if (fullBrand.assets?.logo) brandLogoPath = `/brand/${fullBrand.assets.logo}`;
13238
13576
  brandIconPath = fullBrand.assets?.icon ? `/brand/${fullBrand.assets.icon}` : brandLogoPath;
13239
13577
  } catch {
@@ -13250,9 +13588,9 @@ var brandScript = `<script>window.__BRAND__=${JSON.stringify({
13250
13588
  function readInstalledVersion() {
13251
13589
  try {
13252
13590
  if (!PLATFORM_ROOT7) return "unknown";
13253
- const versionFile = join10(PLATFORM_ROOT7, "config", `.${BRAND.hostname}-version`);
13254
- if (!existsSync24(versionFile)) return "unknown";
13255
- const content = readFileSync17(versionFile, "utf-8").trim();
13591
+ const versionFile = join11(PLATFORM_ROOT7, "config", `.${BRAND.hostname}-version`);
13592
+ if (!existsSync25(versionFile)) return "unknown";
13593
+ const content = readFileSync18(versionFile, "utf-8").trim();
13256
13594
  return content || "unknown";
13257
13595
  } catch {
13258
13596
  return "unknown";
@@ -13293,7 +13631,7 @@ var clientErrorReporterScript = `<script>
13293
13631
  function cachedHtml(file) {
13294
13632
  let html = htmlCache.get(file);
13295
13633
  if (!html) {
13296
- html = readFileSync17(resolve24(process.cwd(), "public", file), "utf-8");
13634
+ html = readFileSync18(resolve24(process.cwd(), "public", file), "utf-8");
13297
13635
  const productNameEsc = escapeHtml(BRAND.productName);
13298
13636
  html = html.replace(/<title>([^<]*)<\/title>/, (_match, inner) => `<title>${escapeHtml(inner).replace(/Maxy/g, productNameEsc)}</title>`);
13299
13637
  html = html.replace('href="/favicon.ico"', `href="${escapeHtml(brandFaviconPath)}"`);
@@ -13309,26 +13647,26 @@ ${clientErrorReporterScript}
13309
13647
  }
13310
13648
  var brandedHtmlCache = /* @__PURE__ */ new Map();
13311
13649
  function loadBrandingCache(agentSlug) {
13312
- const configDir2 = join10(homedir2(), BRAND.configDir);
13650
+ const configDir2 = join11(homedir2(), BRAND.configDir);
13313
13651
  try {
13314
- const accountJsonPath = join10(configDir2, "account.json");
13315
- if (!existsSync24(accountJsonPath)) return null;
13316
- const account = JSON.parse(readFileSync17(accountJsonPath, "utf-8"));
13652
+ const accountJsonPath = join11(configDir2, "account.json");
13653
+ if (!existsSync25(accountJsonPath)) return null;
13654
+ const account = JSON.parse(readFileSync18(accountJsonPath, "utf-8"));
13317
13655
  const accountId = account.accountId;
13318
13656
  if (!accountId) return null;
13319
- const cachePath = join10(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
13320
- if (!existsSync24(cachePath)) return null;
13321
- return JSON.parse(readFileSync17(cachePath, "utf-8"));
13657
+ const cachePath = join11(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
13658
+ if (!existsSync25(cachePath)) return null;
13659
+ return JSON.parse(readFileSync18(cachePath, "utf-8"));
13322
13660
  } catch {
13323
13661
  return null;
13324
13662
  }
13325
13663
  }
13326
13664
  function resolveDefaultSlug() {
13327
13665
  try {
13328
- const configDir2 = join10(homedir2(), BRAND.configDir);
13329
- const accountJsonPath = join10(configDir2, "account.json");
13330
- if (!existsSync24(accountJsonPath)) return null;
13331
- const account = JSON.parse(readFileSync17(accountJsonPath, "utf-8"));
13666
+ const configDir2 = join11(homedir2(), BRAND.configDir);
13667
+ const accountJsonPath = join11(configDir2, "account.json");
13668
+ if (!existsSync25(accountJsonPath)) return null;
13669
+ const account = JSON.parse(readFileSync18(accountJsonPath, "utf-8"));
13332
13670
  return account.defaultAgent || null;
13333
13671
  } catch {
13334
13672
  return null;
@@ -13401,7 +13739,7 @@ app36.use("/vnc-popout.html", logViewerFetch);
13401
13739
  app36.get("/vnc-popout.html", (c) => {
13402
13740
  let html = htmlCache.get("vnc-popout.html");
13403
13741
  if (!html) {
13404
- html = readFileSync17(resolve24(process.cwd(), "public", "vnc-popout.html"), "utf-8");
13742
+ html = readFileSync18(resolve24(process.cwd(), "public", "vnc-popout.html"), "utf-8");
13405
13743
  const name = escapeHtml(BRAND.productName);
13406
13744
  html = html.replace("<title>Browser \u2014 Maxy</title>", `<title>${name}</title>`);
13407
13745
  html = html.replace("</head>", ` ${brandScript}
@@ -13491,8 +13829,8 @@ try {
13491
13829
  (async () => {
13492
13830
  try {
13493
13831
  let userId = "";
13494
- if (existsSync24(USERS_FILE)) {
13495
- const users = JSON.parse(readFileSync17(USERS_FILE, "utf-8").trim() || "[]");
13832
+ if (existsSync25(USERS_FILE)) {
13833
+ const users = JSON.parse(readFileSync18(USERS_FILE, "utf-8").trim() || "[]");
13496
13834
  userId = users[0]?.userId ?? "";
13497
13835
  }
13498
13836
  await backfillNullUserIdConversations(userId);
@@ -13552,6 +13890,48 @@ for (const dir of bootEnabled) {
13552
13890
  bootDelivered.push(dir);
13553
13891
  }
13554
13892
  console.error(`[plugins] readiness enabled=${bootEnabled.length} delivered=${bootDelivered.length} dist-missing=[${bootDistMissing.join(",")}] missing=[${bootMissing.join(",")}]`);
13893
+ (async () => {
13894
+ if (!bootAccount) {
13895
+ console.log("[action-completion-relay] phase=consumed-skip reason=no-account");
13896
+ return;
13897
+ }
13898
+ let records;
13899
+ try {
13900
+ records = consumeActionCompletionRelays(bootAccount.accountDir);
13901
+ } catch (err) {
13902
+ console.error(`[action-completion-relay] phase=consume-failed error=${err instanceof Error ? err.message : String(err)}`);
13903
+ return;
13904
+ }
13905
+ if (records.length === 0) {
13906
+ console.log(`[action-completion-relay] phase=consumed-empty accountId=${bootAccount.accountId.slice(0, 8)}\u2026`);
13907
+ return;
13908
+ }
13909
+ console.log(`[action-completion-relay] phase=consume-batch count=${records.length} accountId=${bootAccount.accountId.slice(0, 8)}\u2026`);
13910
+ for (const rec of records) {
13911
+ const sessionKey = `cloudflare-relay-boot:${rec.actionId}`;
13912
+ let outcome = "injected";
13913
+ let dispatchError;
13914
+ try {
13915
+ registerSession(sessionKey, "admin", bootAccount.accountId);
13916
+ setConversationIdForSession(sessionKey, rec.conversationId);
13917
+ for await (const _ev of invokeAgent({ type: "admin" }, rec.message, sessionKey)) {
13918
+ }
13919
+ } catch (err) {
13920
+ outcome = "dispatch-failed";
13921
+ dispatchError = err instanceof Error ? err.message : String(err);
13922
+ } finally {
13923
+ unregisterSession(sessionKey);
13924
+ }
13925
+ if (outcome === "injected") {
13926
+ deleteConsumedRelay(rec.filePath);
13927
+ console.log(`[action-completion-relay] phase=consumed actionId=${rec.actionId} conversationId=${rec.conversationId} ageMs=${rec.ageMs} outcome=injected`);
13928
+ } else {
13929
+ console.error(`[action-completion-relay] phase=consumed actionId=${rec.actionId} conversationId=${rec.conversationId} ageMs=${rec.ageMs} outcome=${outcome} reason=${JSON.stringify(dispatchError ?? "")} fileRetained=${JSON.stringify(rec.filePath)}`);
13930
+ }
13931
+ }
13932
+ })().catch((err) => {
13933
+ console.error(`[action-completion-relay] boot-drain rejected: ${err instanceof Error ? err.message : String(err)}`);
13934
+ });
13555
13935
  if (bootAccountConfig?.whatsapp) {
13556
13936
  console.error(`[whatsapp:boot] loading whatsapp config from account.json publicAgent=${bootPublicAgent ?? "none"}`);
13557
13937
  } else {
@@ -13559,7 +13939,7 @@ if (bootAccountConfig?.whatsapp) {
13559
13939
  }
13560
13940
  init({
13561
13941
  configDir: configDirForWhatsApp,
13562
- platformRoot: resolve24(process.env.MAXY_PLATFORM_ROOT ?? join10(__dirname, "..")),
13942
+ platformRoot: resolve24(process.env.MAXY_PLATFORM_ROOT ?? join11(__dirname, "..")),
13563
13943
  accountConfig: bootAccountConfig,
13564
13944
  onMessage: async (msg) => {
13565
13945
  try {