@rubytech/create-maxy-code 0.1.480 → 0.1.482

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 (23) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/docs/superpowers/plans/2026-07-20-storage-pages-create.md +82 -0
  3. package/payload/platform/plugins/cloudflare/skills/calendar-site/SKILL.md +1 -1
  4. package/payload/platform/plugins/cloudflare/skills/site-deploy/SKILL.md +1 -1
  5. package/payload/platform/plugins/storage-broker/PLUGIN.md +6 -2
  6. package/payload/platform/plugins/storage-broker/mcp/dist/index.js +23 -0
  7. package/payload/platform/plugins/storage-broker/mcp/dist/index.js.map +1 -1
  8. package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-distil-profile.d.ts +26 -0
  9. package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-distil-profile.d.ts.map +1 -1
  10. package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-distil-profile.js +46 -4
  11. package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-distil-profile.js.map +1 -1
  12. package/payload/platform/plugins/voice-mirror/mcp/scripts/smoke.mjs +30 -0
  13. package/payload/platform/plugins/whatsapp/PLUGIN.md +3 -0
  14. package/payload/platform/plugins/whatsapp/mcp/dist/index.js +25 -3
  15. package/payload/platform/plugins/whatsapp/mcp/dist/index.js.map +1 -1
  16. package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +12 -0
  17. package/payload/platform/plugins/whatsapp/skills/connect-whatsapp/SKILL.md +6 -0
  18. package/payload/platform/plugins/whatsapp/skills/manage-whatsapp-config/SKILL.md +10 -0
  19. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
  20. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +2 -0
  21. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  22. package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
  23. package/payload/server/server.js +474 -219
@@ -1563,7 +1563,7 @@ var serveStatic = (options = { root: "" }) => {
1563
1563
  import { readFileSync as readFileSync42, existsSync as existsSync38, watchFile, readdirSync as readdirSync26, statSync as statSync17 } from "fs";
1564
1564
  import { spawn as spawn3 } from "child_process";
1565
1565
  import { createHash as createHash7 } from "crypto";
1566
- import { resolve as resolve37, join as join44, basename as basename12 } from "path";
1566
+ import { resolve as resolve38, join as join45, basename as basename12 } from "path";
1567
1567
  import { homedir as homedir4 } from "os";
1568
1568
  import { monitorEventLoopDelay } from "perf_hooks";
1569
1569
 
@@ -2472,7 +2472,8 @@ var WhatsAppAccountSchema = z.object({
2472
2472
  }).strict().optional().describe("React to incoming messages with an emoji to acknowledge receipt before the agent responds."),
2473
2473
  debounceMs: z.number().int().nonnegative().optional().default(2e3).describe("Wait this many milliseconds after the last message before processing, to batch rapid multi-message input into a single agent turn. Default 2000ms catches most multi-message bursts. 0 means process each message immediately."),
2474
2474
  publicAgent: z.string().optional().describe("Slug of the public agent that handles WhatsApp inbound from non-admin phones for this account. Overrides the top-level publicAgent when set."),
2475
- afterHoursMessage: z.string().optional().describe("Auto-reply text sent to public senders when the business is closed (outside opening hours). If not set, messages received outside business hours are silently skipped with no reply.")
2475
+ afterHoursMessage: z.string().optional().describe("Auto-reply text sent to public senders when the business is closed (outside opening hours). If not set, messages received outside business hours are silently skipped with no reply."),
2476
+ dispatchInbound: z.boolean().optional().describe("When false, this account's WhatsApp socket only observes: inbound is stored and readable but never dispatched to an agent. Default true. Set with set-dispatch-inbound.")
2476
2477
  }).strict().superRefine((value, ctx) => {
2477
2478
  if (value.dmPolicy !== "open") return;
2478
2479
  const allow = (value.allowFrom ?? []).map((v) => String(v).trim()).filter(Boolean);
@@ -2836,7 +2837,7 @@ var credsSaveQueue = Promise.resolve();
2836
2837
  async function drainCredsSaveQueue(timeoutMs = 5e3) {
2837
2838
  console.error(`${TAG6} draining credential save queue\u2026`);
2838
2839
  const timer = new Promise(
2839
- (resolve38) => setTimeout(() => resolve38("timeout"), timeoutMs)
2840
+ (resolve39) => setTimeout(() => resolve39("timeout"), timeoutMs)
2840
2841
  );
2841
2842
  const result = await Promise.race([
2842
2843
  credsSaveQueue.then(() => "drained"),
@@ -3013,11 +3014,11 @@ async function createWaSocket(opts) {
3013
3014
  return sock;
3014
3015
  }
3015
3016
  async function waitForConnection(sock) {
3016
- return new Promise((resolve38, reject) => {
3017
+ return new Promise((resolve39, reject) => {
3017
3018
  const handler = (update) => {
3018
3019
  if (update.connection === "open") {
3019
3020
  sock.ev.off("connection.update", handler);
3020
- resolve38();
3021
+ resolve39();
3021
3022
  }
3022
3023
  if (update.connection === "close") {
3023
3024
  sock.ev.off("connection.update", handler);
@@ -3125,7 +3126,7 @@ function isTimeoutError(err) {
3125
3126
  return Boolean(err?.__maxyTimeout);
3126
3127
  }
3127
3128
  function withTimeout(label, promise, timeoutMs) {
3128
- return new Promise((resolve38, reject) => {
3129
+ return new Promise((resolve39, reject) => {
3129
3130
  const timer = setTimeout(() => {
3130
3131
  const err = new Error(`${label} timed out after ${timeoutMs}ms`);
3131
3132
  err.__maxyTimeout = true;
@@ -3135,7 +3136,7 @@ function withTimeout(label, promise, timeoutMs) {
3135
3136
  promise.then(
3136
3137
  (value) => {
3137
3138
  clearTimeout(timer);
3138
- resolve38(value);
3139
+ resolve39(value);
3139
3140
  },
3140
3141
  (err) => {
3141
3142
  clearTimeout(timer);
@@ -3622,6 +3623,18 @@ function checkGroupAccess(_params) {
3622
3623
  return { allowed: false, reason: "group-observe-only", agentType: "public" };
3623
3624
  }
3624
3625
 
3626
+ // app/lib/whatsapp/inbound/dispatch-mode.ts
3627
+ function shouldDispatchInbound(config, accountId) {
3628
+ return config.accounts?.[accountId]?.dispatchInbound ?? true;
3629
+ }
3630
+ function decideDispatch(conn, payload, deps) {
3631
+ if (shouldDispatchInbound(deps.config, conn.accountId)) {
3632
+ deps.emit(payload);
3633
+ return;
3634
+ }
3635
+ deps.log(`[whatsapp:observe] op=dispatch-suppressed account=${conn.accountId} sender=${payload.senderPhone} agentType=${payload.agentType}`);
3636
+ }
3637
+
3625
3638
  // app/lib/access-gate.ts
3626
3639
  import neo4j from "neo4j-driver";
3627
3640
  import { readFileSync as readFileSync6 } from "fs";
@@ -4175,8 +4188,8 @@ async function persistWhatsAppMessage(input) {
4175
4188
  const { givenName, familyName } = splitName(input.pushName);
4176
4189
  const prev = sessionWriteLocks.get(input.cacheKey);
4177
4190
  let release;
4178
- const mine = new Promise((resolve38) => {
4179
- release = resolve38;
4191
+ const mine = new Promise((resolve39) => {
4192
+ release = resolve39;
4180
4193
  });
4181
4194
  const chained = (prev ?? Promise.resolve()).then(() => mine);
4182
4195
  sessionWriteLocks.set(input.cacheKey, chained);
@@ -5484,6 +5497,16 @@ var configDir = null;
5484
5497
  var whatsAppConfig = {};
5485
5498
  var onInboundMessage = null;
5486
5499
  var initialized = false;
5500
+ function dispatchInboundPayload(conn, payload) {
5501
+ if (!onInboundMessage) return;
5502
+ decideDispatch(conn, payload, { config: whatsAppConfig, emit: onInboundMessage, log: (l) => console.error(l) });
5503
+ }
5504
+ function isObserveAccount(accountId) {
5505
+ return !shouldDispatchInbound(whatsAppConfig, accountId);
5506
+ }
5507
+ function getWhatsAppConfig() {
5508
+ return whatsAppConfig;
5509
+ }
5487
5510
  var messageStore = /* @__PURE__ */ new Map();
5488
5511
  function messageStoreKey(accountId, remoteJid) {
5489
5512
  return `${accountId}:${remoteJid}`;
@@ -5903,11 +5926,11 @@ async function connectWithReconnect(conn) {
5903
5926
  console.error(
5904
5927
  `${TAG17} reconnecting account=${conn.accountId} in ${delay}ms (attempt ${decision.nextAttempts}/${maxAttempts})`
5905
5928
  );
5906
- await new Promise((resolve38) => {
5907
- const timer = setTimeout(resolve38, delay);
5929
+ await new Promise((resolve39) => {
5930
+ const timer = setTimeout(resolve39, delay);
5908
5931
  conn.abortController.signal.addEventListener("abort", () => {
5909
5932
  clearTimeout(timer);
5910
- resolve38();
5933
+ resolve39();
5911
5934
  }, { once: true });
5912
5935
  });
5913
5936
  }
@@ -5915,16 +5938,16 @@ async function connectWithReconnect(conn) {
5915
5938
  }
5916
5939
  }
5917
5940
  function waitForDisconnectEvent(conn) {
5918
- return new Promise((resolve38) => {
5941
+ return new Promise((resolve39) => {
5919
5942
  if (!conn.sock) {
5920
- resolve38();
5943
+ resolve39();
5921
5944
  return;
5922
5945
  }
5923
5946
  const sock = conn.sock;
5924
5947
  const handler = (update) => {
5925
5948
  if (update.connection === "close") {
5926
5949
  sock.ev.off("connection.update", handler);
5927
- resolve38();
5950
+ resolve39();
5928
5951
  }
5929
5952
  };
5930
5953
  sock.ev.on("connection.update", handler);
@@ -5977,7 +6000,7 @@ function monitorInbound(conn) {
5977
6000
  if (entries.length > 1) {
5978
6001
  console.error(`${TAG17} debounce: combining ${entries.length} messages account=${conn.accountId} from=${entries[0].senderPhone}`);
5979
6002
  }
5980
- onInboundMessage(combineInboundBatch(entries));
6003
+ dispatchInboundPayload(conn, combineInboundBatch(entries));
5981
6004
  },
5982
6005
  onError: (err) => {
5983
6006
  console.error(`${TAG17} debounce flush error account=${conn.accountId}: ${String(err)}`);
@@ -6202,7 +6225,7 @@ function monitorInbound(conn) {
6202
6225
  const currentSock = conn.sock;
6203
6226
  if (currentSock) await sendComposing(currentSock, remoteJid);
6204
6227
  };
6205
- onInboundMessage(buildSelfChatDispatch({
6228
+ dispatchInboundPayload(conn, buildSelfChatDispatch({
6206
6229
  accountId: conn.accountId,
6207
6230
  selfPhone: conn.selfPhone,
6208
6231
  remoteJid,
@@ -6311,7 +6334,7 @@ async function handleInboundMessage(conn, msg) {
6311
6334
  const currentSock = conn.sock;
6312
6335
  if (currentSock) await sendComposing(currentSock, remoteJid);
6313
6336
  };
6314
- onInboundMessage({
6337
+ dispatchInboundPayload(conn, {
6315
6338
  accountId: conn.accountId,
6316
6339
  agentType: "admin",
6317
6340
  senderPhone: senderPhone2,
@@ -6358,8 +6381,8 @@ async function handleInboundMessage(conn, msg) {
6358
6381
  const conversationKey = isGroup ? remoteJid : senderPhone;
6359
6382
  const debounceKey = `${conn.accountId}:${conversationKey}:${senderPhone}`;
6360
6383
  let resolvePending;
6361
- const sttPending = new Promise((resolve38) => {
6362
- resolvePending = resolve38;
6384
+ const sttPending = new Promise((resolve39) => {
6385
+ resolvePending = resolve39;
6363
6386
  });
6364
6387
  if (conn.debouncer) conn.debouncer.registerPending(debounceKey, sttPending);
6365
6388
  try {
@@ -6477,7 +6500,7 @@ async function handleInboundMessage(conn, msg) {
6477
6500
  if (conn.debouncer) {
6478
6501
  conn.debouncer.enqueue(payload);
6479
6502
  } else {
6480
- onInboundMessage(payload);
6503
+ dispatchInboundPayload(conn, payload);
6481
6504
  }
6482
6505
  }
6483
6506
 
@@ -6787,20 +6810,20 @@ function buildX11Env(chromiumWrapperPath, transport = "vnc") {
6787
6810
 
6788
6811
  // server/routes/health.ts
6789
6812
  function checkPort(port2, timeoutMs = 500) {
6790
- return new Promise((resolve38) => {
6813
+ return new Promise((resolve39) => {
6791
6814
  const socket = createConnection2(port2, "127.0.0.1");
6792
6815
  socket.setTimeout(timeoutMs);
6793
6816
  socket.once("connect", () => {
6794
6817
  socket.destroy();
6795
- resolve38(true);
6818
+ resolve39(true);
6796
6819
  });
6797
6820
  socket.once("error", () => {
6798
6821
  socket.destroy();
6799
- resolve38(false);
6822
+ resolve39(false);
6800
6823
  });
6801
6824
  socket.once("timeout", () => {
6802
6825
  socket.destroy();
6803
- resolve38(false);
6826
+ resolve39(false);
6804
6827
  });
6805
6828
  });
6806
6829
  }
@@ -7751,6 +7774,48 @@ function setAccountManager(accountDir, phone, subAccountId, mode = "active") {
7751
7774
  return { ok: false, error: msg };
7752
7775
  }
7753
7776
  }
7777
+ function setDispatchInbound(accountDir, accountId, value) {
7778
+ const id = accountId.trim();
7779
+ if (!id) return { ok: false, error: "Missing account id." };
7780
+ let validIds;
7781
+ try {
7782
+ validIds = listValidAccounts().map((a) => a.accountId);
7783
+ } catch (err) {
7784
+ return { ok: false, error: `Could not read the account registry to validate the account: ${err instanceof Error ? err.message : String(err)}. Try again.` };
7785
+ }
7786
+ if (!validIds.includes(id)) {
7787
+ return { ok: false, error: `Account "${id}" is not in this install's account registry.` };
7788
+ }
7789
+ try {
7790
+ const config = readConfig(accountDir);
7791
+ if (!config.whatsapp || typeof config.whatsapp !== "object") {
7792
+ config.whatsapp = {};
7793
+ }
7794
+ const wa = config.whatsapp;
7795
+ if (!wa.accounts || typeof wa.accounts !== "object") {
7796
+ wa.accounts = {};
7797
+ }
7798
+ const accounts = wa.accounts;
7799
+ if (!accounts[id] || typeof accounts[id] !== "object") {
7800
+ accounts[id] = { name: "Main" };
7801
+ }
7802
+ accounts[id].dispatchInbound = value;
7803
+ const parsed = WhatsAppConfigSchema.safeParse(wa);
7804
+ if (!parsed.success) {
7805
+ const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
7806
+ return { ok: false, error: `Validation failed: ${msg}` };
7807
+ }
7808
+ config.whatsapp = parsed.data;
7809
+ writeConfig(accountDir, config);
7810
+ console.error(`${TAG21} set dispatchInbound account=${id} value=${value}`);
7811
+ reloadManagerConfig(accountDir);
7812
+ return { ok: true };
7813
+ } catch (err) {
7814
+ const msg = err instanceof Error ? err.message : String(err);
7815
+ console.error(`${TAG21} setDispatchInbound failed: ${msg}`);
7816
+ return { ok: false, error: msg };
7817
+ }
7818
+ }
7754
7819
  function clearAccountManager(accountDir, phone) {
7755
7820
  const normalized = phone.trim();
7756
7821
  try {
@@ -8089,8 +8154,8 @@ async function startLogin(opts) {
8089
8154
  await clearAuth(authDir);
8090
8155
  let resolveCode = null;
8091
8156
  let rejectCode = null;
8092
- const codePromise = new Promise((resolve38, reject) => {
8093
- resolveCode = resolve38;
8157
+ const codePromise = new Promise((resolve39, reject) => {
8158
+ resolveCode = resolve39;
8094
8159
  rejectCode = reject;
8095
8160
  });
8096
8161
  const codeTimer = setTimeout(
@@ -8489,6 +8554,14 @@ function reconcileCredsOnDisk(opts) {
8489
8554
  return entries;
8490
8555
  }
8491
8556
 
8557
+ // app/lib/whatsapp/outbound/reply-guard.ts
8558
+ function admitReply(input) {
8559
+ if (isGroupJid(input.remoteJid)) return { ok: false, status: 403, reason: "group-send-blocked" };
8560
+ if (!isUserJid(input.remoteJid)) return { ok: false, status: 400, reason: "invalid-remote-jid" };
8561
+ if (!input.hasConversation) return { ok: false, status: 403, reason: "no-existing-conversation" };
8562
+ return { ok: true };
8563
+ }
8564
+
8492
8565
  // app/lib/whatsapp-reader/select-sessions.ts
8493
8566
  function isReaderChannelSession(role, channel) {
8494
8567
  return role === "admin" && (channel === "whatsapp" || channel === "telegram") || role === "public" && (channel === "whatsapp" || channel === "webchat");
@@ -8647,6 +8720,15 @@ function houseSocketScopeDenial(c, op) {
8647
8720
  console.error(`${TAG25} op=authz-deny route=${op} caller=${callerAccountId ?? "none"} houseAdmin=${houseAdminScope ? "y" : "n"}`);
8648
8721
  return c.json({ ok: false, error: "Not authorized. The WhatsApp connection and its settings are house-scoped; this session may only act on its own account." }, 403);
8649
8722
  }
8723
+ function selfOrHouseScopeDenial(c, targetAccountId, op) {
8724
+ const { decision, callerAccountId, houseAdminScope } = evaluateCallerScope(c, targetAccountId);
8725
+ if (decision.authorized) {
8726
+ console.error(`${TAG25} op=authz-ok route=${op} target=${targetAccountId} caller=${callerAccountId ?? "none"} houseAdmin=${houseAdminScope ? "y" : "n"}`);
8727
+ return null;
8728
+ }
8729
+ console.error(`${TAG25} op=authz-deny route=${op} target=${targetAccountId} caller=${callerAccountId ?? "none"} houseAdmin=${houseAdminScope ? "y" : "n"}`);
8730
+ return c.json({ ok: false, error: "Not authorized. This session may only pair or manage its own account." }, 403);
8731
+ }
8650
8732
  var app2 = new Hono();
8651
8733
  app2.get("/status", (c) => {
8652
8734
  try {
@@ -8658,8 +8740,12 @@ app2.get("/status", (c) => {
8658
8740
  const credsRoot = join12(MAXY_DIR, "credentials", "whatsapp");
8659
8741
  const accountDir = resolveAccount()?.accountDir ?? null;
8660
8742
  const reconciled = reconcileCredsOnDisk({ credsRoot, accountDir, liveAccountIds: liveIds, activeLoginAccountIds: activeLoginIds });
8661
- const accounts = [...live, ...reconciled];
8662
- const summary = accounts.map((a) => `${a.accountId}:${a.connected ? "up" : a.linkedUnconfigured ? "linked-unconfigured" : "down"}`).join(", ");
8743
+ const cfg = getWhatsAppConfig();
8744
+ const accounts = [...live, ...reconciled].map((a) => ({
8745
+ ...a,
8746
+ dispatchInbound: shouldDispatchInbound(cfg, a.accountId)
8747
+ }));
8748
+ const summary = accounts.map((a) => `${a.accountId}:${a.connected ? "up" : a.linkedUnconfigured ? "linked-unconfigured" : "down"}:${a.dispatchInbound ? "dispatch" : "observe"}`).join(", ");
8663
8749
  console.error(`${TAG25} status accounts=${accounts.length} [${summary}]`);
8664
8750
  const reachableAccountIds = [
8665
8751
  .../* @__PURE__ */ new Set([...listStoredAccountIds(), ...listConnectionPlatformAccountIds()])
@@ -8679,10 +8765,10 @@ app2.get("/status", (c) => {
8679
8765
  });
8680
8766
  app2.post("/login/start", async (c) => {
8681
8767
  try {
8682
- const denial = houseSocketScopeDenial(c, "login/start");
8683
- if (denial) return denial;
8684
8768
  const body = await c.req.json().catch(() => ({}));
8685
8769
  const accountId = validateAccountId(body.accountId);
8770
+ const denial = selfOrHouseScopeDenial(c, accountId, "login/start");
8771
+ if (denial) return denial;
8686
8772
  const force = body.force ?? false;
8687
8773
  const phone = typeof body.phone === "string" ? body.phone.trim() : "";
8688
8774
  if (!phone) {
@@ -8700,10 +8786,10 @@ app2.post("/login/start", async (c) => {
8700
8786
  });
8701
8787
  app2.post("/login/wait", async (c) => {
8702
8788
  try {
8703
- const denial = houseSocketScopeDenial(c, "login/wait");
8704
- if (denial) return denial;
8705
8789
  const body = await c.req.json().catch(() => ({}));
8706
8790
  const accountId = validateAccountId(body.accountId);
8791
+ const denial = selfOrHouseScopeDenial(c, accountId, "login/wait");
8792
+ if (denial) return denial;
8707
8793
  const timeoutMs = body.timeoutMs ?? 6e4;
8708
8794
  const result = await waitForLogin({ accountId, timeoutMs });
8709
8795
  console.error(`${TAG25} login/wait result account=${accountId} connected=${result.connected}${result.selfPhone ? ` phone=${result.selfPhone}` : ""} configPersisted=${result.configPersisted}`);
@@ -8720,10 +8806,10 @@ app2.post("/login/wait", async (c) => {
8720
8806
  });
8721
8807
  app2.post("/disconnect", async (c) => {
8722
8808
  try {
8723
- const denial = houseSocketScopeDenial(c, "disconnect");
8724
- if (denial) return denial;
8725
8809
  const body = await c.req.json().catch(() => ({}));
8726
8810
  const accountId = validateAccountId(body.accountId);
8811
+ const denial = selfOrHouseScopeDenial(c, accountId, "disconnect");
8812
+ if (denial) return denial;
8727
8813
  await stopConnection(accountId);
8728
8814
  return c.json({ disconnected: true, accountId });
8729
8815
  } catch (err) {
@@ -8733,10 +8819,10 @@ app2.post("/disconnect", async (c) => {
8733
8819
  });
8734
8820
  app2.post("/reconnect", async (c) => {
8735
8821
  try {
8736
- const denial = houseSocketScopeDenial(c, "reconnect");
8737
- if (denial) return denial;
8738
8822
  const body = await c.req.json().catch(() => ({}));
8739
8823
  const accountId = validateAccountId(body.accountId);
8824
+ const denial = selfOrHouseScopeDenial(c, accountId, "reconnect");
8825
+ if (denial) return denial;
8740
8826
  await startConnection(accountId);
8741
8827
  return c.json({ reconnecting: true, accountId });
8742
8828
  } catch (err) {
@@ -8751,6 +8837,22 @@ app2.post("/config", async (c) => {
8751
8837
  if (!action || typeof action !== "string") {
8752
8838
  return c.json({ ok: false, error: 'Missing required field "action".' }, 400);
8753
8839
  }
8840
+ if (action === "set-dispatch-inbound") {
8841
+ if (!accountId || typeof accountId !== "string") {
8842
+ return c.json({ ok: false, error: 'Missing required field "accountId" (the account whose socket to set).' }, 400);
8843
+ }
8844
+ const target = validateAccountId(accountId);
8845
+ const scopeDenial = selfOrHouseScopeDenial(c, target, `config action=set-dispatch-inbound target=${target}`);
8846
+ if (scopeDenial) return scopeDenial;
8847
+ if (typeof body.dispatchInbound !== "boolean") {
8848
+ return c.json({ ok: false, error: 'Missing required boolean field "dispatchInbound".' }, 400);
8849
+ }
8850
+ const acct = resolveAccount();
8851
+ if (!acct) return c.json({ ok: false, error: "No account configured." }, 500);
8852
+ const result = setDispatchInbound(acct.accountDir, target, body.dispatchInbound);
8853
+ console.error(`${TAG25} config action=set-dispatch-inbound account=${target} dispatchInbound=${body.dispatchInbound} ok=${result.ok}`);
8854
+ return c.json(result, result.ok ? 200 : 400);
8855
+ }
8754
8856
  const denial = houseSocketScopeDenial(c, `config action=${action}`);
8755
8857
  if (denial) return denial;
8756
8858
  const account = resolveAccount();
@@ -8961,6 +9063,65 @@ app2.post("/send-admin", async (c) => {
8961
9063
  return c.json({ error: String(err) }, 500);
8962
9064
  }
8963
9065
  });
9066
+ app2.post("/reply", async (c) => {
9067
+ const sendId = process.hrtime.bigint().toString(36).slice(-8);
9068
+ try {
9069
+ const body = await c.req.json().catch(() => ({}));
9070
+ if (!body.accountId || typeof body.accountId !== "string") {
9071
+ return c.json({ error: "accountId is required" }, 400);
9072
+ }
9073
+ const accountId = validateAccountId(body.accountId);
9074
+ const denial = selfOrHouseScopeDenial(c, accountId, "reply");
9075
+ if (denial) return denial;
9076
+ const remoteJid = typeof body.remoteJid === "string" ? body.remoteJid : "";
9077
+ const text = typeof body.text === "string" && body.text.trim().length ? body.text : "";
9078
+ if (!remoteJid || !text) {
9079
+ return c.json({ error: "accountId, remoteJid and text are required" }, 400);
9080
+ }
9081
+ const storeKey = resolveReadStoreKey(accountId);
9082
+ const convo = readConversation(storeKey, remoteJid);
9083
+ const admit = admitReply({ remoteJid, hasConversation: convo.length > 0 });
9084
+ if (!admit.ok) {
9085
+ console.error(`[whatsapp:observe] op=reply-refused sendId=${sendId} account=${accountId} remoteJid=${remoteJid} reason=${admit.reason}`);
9086
+ return c.json({ error: admit.reason, reason: admit.reason }, admit.status);
9087
+ }
9088
+ const res = resolveSocket(accountId);
9089
+ if (!res.ok) {
9090
+ console.error(`[whatsapp:observe] op=reply-socket sendId=${sendId} account=${accountId} result=${res.reason} presentKeys=${res.presentKeys.join(",") || "none"}`);
9091
+ return c.json({ error: formatSocketResolutionMessage(res, accountId), reason: res.reason }, 503);
9092
+ }
9093
+ const sent = await sendTextMessage(res.sock, remoteJid, text, { accountId: res.accountId });
9094
+ if (!sent.success) {
9095
+ console.error(`[whatsapp:observe] op=reply-send-fail sendId=${sendId} account=${accountId} error=${sent.error ?? ""}`);
9096
+ return c.json({ error: sent.error ?? "send failed" }, 500);
9097
+ }
9098
+ if (sent.messageId) {
9099
+ const template = convo[convo.length - 1];
9100
+ const nowIso = (/* @__PURE__ */ new Date()).toISOString();
9101
+ const rec = {
9102
+ messageId: `whatsapp-live:${res.accountId}:${remoteJid}:${sent.messageId}`,
9103
+ sessionId: template.sessionId,
9104
+ dateSent: nowIso,
9105
+ body: text,
9106
+ fromMe: true,
9107
+ senderTelephone: template.fromMe ? template.senderTelephone : "",
9108
+ senderName: null,
9109
+ remoteJid,
9110
+ msgKeyId: sent.messageId,
9111
+ quotedId: null,
9112
+ quotedSender: null,
9113
+ scope: template.scope,
9114
+ createdAt: nowIso
9115
+ };
9116
+ appendMessage(storeKey, rec);
9117
+ }
9118
+ console.error(`[whatsapp:observe] op=reply-sent sendId=${sendId} account=${accountId} socket=${res.accountId} waMessageId=${sent.messageId ?? ""}`);
9119
+ return c.json({ success: true, messageId: sent.messageId });
9120
+ } catch (err) {
9121
+ console.error(`${TAG25} reply error: ${String(err)}`);
9122
+ return c.json({ error: String(err) }, 500);
9123
+ }
9124
+ });
8964
9125
  app2.get("/activity", (c) => {
8965
9126
  try {
8966
9127
  const accountId = c.req.query("accountId") ?? void 0;
@@ -9439,6 +9600,58 @@ app3.post("/pages/adopt", nameBodyLimit("pages"), async (c) => {
9439
9600
  await session.close();
9440
9601
  }
9441
9602
  });
9603
+ app3.post("/pages/create", nameBodyLimit("pages"), async (c) => {
9604
+ const pub = randomUUID7().slice(0, 8);
9605
+ const account = caller(c);
9606
+ if (!account) {
9607
+ log("write", "pages", "none", "*", null, "deny", "no-caller");
9608
+ return c.json({ error: "no-caller" }, 403);
9609
+ }
9610
+ const parsed = await readJsonBody(c);
9611
+ if (!parsed.ok) return parsed.res;
9612
+ const { project } = parsed.body;
9613
+ if (!project || typeof project !== "string") return c.json({ error: "project-required" }, 400);
9614
+ console.error(`[pages-broker] op=request pub=${pub} caller=${account} project=${project}`);
9615
+ const session = getSession();
9616
+ try {
9617
+ const owner = await resolveOwner(session, "pages", project);
9618
+ if (owner !== null && owner !== account) {
9619
+ log("write", "pages", account, project, owner, "deny");
9620
+ console.error(
9621
+ `[pages-broker] op=create pub=${pub} project=${project} existedOnCf=unchecked created=false decision=deny reason=cross-account`
9622
+ );
9623
+ return c.json({ error: "cross-account" }, 403);
9624
+ }
9625
+ if (owner === account) {
9626
+ log("write", "pages", account, project, account, "allow");
9627
+ console.error(
9628
+ `[pages-broker] op=create pub=${pub} project=${project} existedOnCf=unchecked created=false decision=allow reason=already-owned`
9629
+ );
9630
+ return c.json({ project, created: false });
9631
+ }
9632
+ const exec = await makeHousePagesExec(platformRoot());
9633
+ const existedOnCf = (await exec.pagesProjectList()).some((p) => p.name === project);
9634
+ if (existedOnCf) {
9635
+ log("write", "pages", account, project, null, "deny", "exists-unregistered");
9636
+ console.error(
9637
+ `[pages-broker] op=create pub=${pub} project=${project} existedOnCf=true created=false decision=deny reason=exists-unregistered`
9638
+ );
9639
+ return c.json({ error: "exists-unregistered" }, 409);
9640
+ }
9641
+ await exec.pagesProjectCreate(project, "main");
9642
+ await registerResource(session, { accountId: account, kind: "pages", name: project, cfResourceId: project });
9643
+ log("write", "pages", account, project, account, "allow");
9644
+ console.error(
9645
+ `[pages-broker] op=create pub=${pub} project=${project} existedOnCf=false created=true decision=allow reason=owner`
9646
+ );
9647
+ return c.json({ project, created: true });
9648
+ } catch (err) {
9649
+ console.error(`[pages-broker] op=exit pub=${pub} result=error detail="${String(err)}"`);
9650
+ return c.json({ error: String(err) }, 500);
9651
+ } finally {
9652
+ await session.close();
9653
+ }
9654
+ });
9442
9655
  app3.get("/d1/list", async (c) => {
9443
9656
  const account = caller(c);
9444
9657
  if (!account) return c.json({ error: "no-caller" }, 403);
@@ -14290,15 +14503,15 @@ function operatorRoleFor(config, userId) {
14290
14503
  async function buildAccountOptionList(accounts, userId) {
14291
14504
  const houseAccount = accounts.find((a) => a.config.role === "house");
14292
14505
  console.log(`[admin-accounts] op=list count=${accounts.length} house=${houseAccount?.accountId ?? "none"}`);
14293
- const resolve38 = async (a) => {
14506
+ const resolve39 = async (a) => {
14294
14507
  const businessName = await fetchAccountName(a.accountId) || void 0;
14295
14508
  return { accountId: a.accountId, businessName, role: operatorRoleFor(a.config, userId), isHouse: a.config.role === "house" };
14296
14509
  };
14297
14510
  const houseOptions = await Promise.all(
14298
- accounts.filter((a) => a.config.role === "house").map(resolve38)
14511
+ accounts.filter((a) => a.config.role === "house").map(resolve39)
14299
14512
  );
14300
14513
  const nonHouseOptions = await Promise.all(
14301
- accounts.filter((a) => a.config.role !== "house").map(resolve38)
14514
+ accounts.filter((a) => a.config.role !== "house").map(resolve39)
14302
14515
  );
14303
14516
  const sortKey = (o) => (o.businessName || o.accountId).toLowerCase();
14304
14517
  nonHouseOptions.sort((x, y) => {
@@ -20315,6 +20528,76 @@ function isWithin(target, root) {
20315
20528
  }
20316
20529
  var sidebar_artefacts_default = app29;
20317
20530
 
20531
+ // server/channel-session-override.ts
20532
+ import { readFileSync as readFileSync34, writeFileSync as writeFileSync12, renameSync as renameSync9 } from "fs";
20533
+ import { join as join35 } from "path";
20534
+ var FILE2 = "channel-session-override.json";
20535
+ var UUID2 = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
20536
+ function channelOverridePath(accountDir) {
20537
+ return join35(accountDir, FILE2);
20538
+ }
20539
+ function senderKey(channel, senderId) {
20540
+ return `${channel}:${senderId}`;
20541
+ }
20542
+ function readRaw2(accountDir) {
20543
+ try {
20544
+ return JSON.parse(readFileSync34(channelOverridePath(accountDir), "utf8"));
20545
+ } catch {
20546
+ return null;
20547
+ }
20548
+ }
20549
+ function readChannelOverrideId(accountDir, channel, senderId) {
20550
+ const raw = readRaw2(accountDir);
20551
+ if (!raw || !raw.bySender || typeof raw.bySender !== "object") return null;
20552
+ const v = raw.bySender[senderKey(channel, senderId)];
20553
+ return typeof v === "string" && UUID2.test(v) ? v : null;
20554
+ }
20555
+ function writeChannelOverrideId(accountDir, channel, senderId, sessionId) {
20556
+ const raw = readRaw2(accountDir);
20557
+ const bySender = {};
20558
+ if (raw?.bySender && typeof raw.bySender === "object") {
20559
+ for (const [k, v] of Object.entries(raw.bySender)) {
20560
+ if (typeof v === "string") bySender[k] = v;
20561
+ }
20562
+ }
20563
+ bySender[senderKey(channel, senderId)] = sessionId;
20564
+ const path3 = channelOverridePath(accountDir);
20565
+ const tmp = `${path3}.${process.pid}.tmp`;
20566
+ writeFileSync12(tmp, JSON.stringify({ bySender }, null, 2), "utf8");
20567
+ renameSync9(tmp, path3);
20568
+ }
20569
+ function clearChannelOverrideForSession(accountDir, sessionId) {
20570
+ const raw = readRaw2(accountDir);
20571
+ if (!raw?.bySender || typeof raw.bySender !== "object") return [];
20572
+ const kept = {};
20573
+ const cleared = [];
20574
+ for (const [k, v] of Object.entries(raw.bySender)) {
20575
+ if (typeof v !== "string") continue;
20576
+ if (v === sessionId) cleared.push(k);
20577
+ else kept[k] = v;
20578
+ }
20579
+ if (cleared.length === 0) return [];
20580
+ const path3 = channelOverridePath(accountDir);
20581
+ const tmp = `${path3}.${process.pid}.tmp`;
20582
+ writeFileSync12(tmp, JSON.stringify({ bySender: kept }, null, 2), "utf8");
20583
+ renameSync9(tmp, path3);
20584
+ return cleared;
20585
+ }
20586
+
20587
+ // server/routes/admin/clear-forward-pointers.ts
20588
+ function clearForwardPointers(sessionId) {
20589
+ try {
20590
+ for (const a of listValidAccounts()) {
20591
+ const cleared = clearChannelOverrideForSession(a.accountDir, sessionId);
20592
+ if (cleared.length > 0) {
20593
+ console.log(`[session-lifecycle] op=channel-forward-cleared sessionId=${sessionId.slice(0, 8)} account=${a.accountId} senderKeys=${cleared.join(",")}`);
20594
+ }
20595
+ }
20596
+ } catch (err) {
20597
+ console.error(`[session-lifecycle] op=channel-forward-clear-failed sessionId=${sessionId.slice(0, 8)} err=${err instanceof Error ? err.message : String(err)}`);
20598
+ }
20599
+ }
20600
+
20318
20601
  // server/routes/admin/session-delete.ts
20319
20602
  var app30 = new Hono();
20320
20603
  app30.post("/", requireAdminSession, async (c) => {
@@ -20347,6 +20630,7 @@ app30.post("/", requireAdminSession, async (c) => {
20347
20630
  return c.json({ error: "manager-unreachable" }, 502);
20348
20631
  }
20349
20632
  if (del.status === 204) {
20633
+ clearForwardPointers(sessionId);
20350
20634
  console.log(`[session-delete] sessionId=${sessionId} deleted`);
20351
20635
  return c.json({ deleted: true });
20352
20636
  }
@@ -20415,6 +20699,7 @@ app32.post("/", requireAdminSession, async (c) => {
20415
20699
  return c.json({ error: "manager-unreachable" }, 502);
20416
20700
  }
20417
20701
  if (res.status === 200) {
20702
+ if (mode === "archive") clearForwardPointers(sessionId);
20418
20703
  const dir = mode === "archive" ? "top\u2192archive" : "archive\u2192top";
20419
20704
  console.log(`[session-archive] sessionId=${sessionId.slice(0, 8)} moved ${dir}`);
20420
20705
  return c.json({ archived: mode === "archive" });
@@ -20644,49 +20929,40 @@ var session_rc_spawn_default = app35;
20644
20929
  // server/routes/admin/session-reseat.ts
20645
20930
  import { randomUUID as randomUUID12 } from "crypto";
20646
20931
 
20647
- // server/channel-session-override.ts
20648
- import { readFileSync as readFileSync34, writeFileSync as writeFileSync12, renameSync as renameSync9 } from "fs";
20649
- import { join as join35 } from "path";
20650
- var FILE2 = "channel-session-override.json";
20651
- var UUID2 = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
20652
- function channelOverridePath(accountDir) {
20653
- return join35(accountDir, FILE2);
20932
+ // server/wa-descriptor.ts
20933
+ import { resolve as resolve26, join as join36 } from "path";
20934
+ function waGatewayUrl() {
20935
+ return `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`;
20654
20936
  }
20655
- function senderKey(channel, senderId) {
20656
- return `${channel}:${senderId}`;
20937
+ function waServerPath() {
20938
+ return process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve26(process.env.MAXY_PLATFORM_ROOT ?? join36(__dirname, ".."), "services/whatsapp-channel/dist/server.js");
20657
20939
  }
20658
- function readRaw2(accountDir) {
20659
- try {
20660
- return JSON.parse(readFileSync34(channelOverridePath(accountDir), "utf8"));
20661
- } catch {
20662
- return null;
20663
- }
20940
+ function waChannelDescriptor(senderId) {
20941
+ return { senderId, gatewayUrl: waGatewayUrl(), serverPath: waServerPath() };
20664
20942
  }
20665
- function readChannelOverrideId(accountDir, channel, senderId) {
20666
- const raw = readRaw2(accountDir);
20667
- if (!raw || !raw.bySender || typeof raw.bySender !== "object") return null;
20668
- const v = raw.bySender[senderKey(channel, senderId)];
20669
- return typeof v === "string" && UUID2.test(v) ? v : null;
20943
+
20944
+ // server/telegram-descriptor.ts
20945
+ import { resolve as resolve27, join as join37 } from "path";
20946
+ function telegramGatewayUrl() {
20947
+ return `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`;
20670
20948
  }
20671
- function writeChannelOverrideId(accountDir, channel, senderId, sessionId) {
20672
- const raw = readRaw2(accountDir);
20673
- const bySender = {};
20674
- if (raw?.bySender && typeof raw.bySender === "object") {
20675
- for (const [k, v] of Object.entries(raw.bySender)) {
20676
- if (typeof v === "string") bySender[k] = v;
20677
- }
20678
- }
20679
- bySender[senderKey(channel, senderId)] = sessionId;
20680
- const path3 = channelOverridePath(accountDir);
20681
- const tmp = `${path3}.${process.pid}.tmp`;
20682
- writeFileSync12(tmp, JSON.stringify({ bySender }, null, 2), "utf8");
20683
- renameSync9(tmp, path3);
20949
+ function telegramServerPath() {
20950
+ return process.env.MAXY_TELEGRAM_CHANNEL_SERVER_PATH ?? resolve27(process.env.MAXY_PLATFORM_ROOT ?? join37(__dirname, ".."), "services/telegram-channel/dist/server.js");
20951
+ }
20952
+ function telegramChannelDescriptor(senderId) {
20953
+ return { senderId, gatewayUrl: telegramGatewayUrl(), serverPath: telegramServerPath() };
20684
20954
  }
20685
20955
 
20686
20956
  // server/routes/admin/session-reseat.ts
20687
20957
  function isAdminChannelSource(source) {
20688
20958
  return !!source && source.role === "admin" && (source.channel === "whatsapp" || source.channel === "telegram") && typeof source.senderId === "string" && source.senderId.length > 0;
20689
20959
  }
20960
+ function shouldWriteChannelForward(source) {
20961
+ return isAdminChannelSource(source) && !!source.accountId;
20962
+ }
20963
+ function isPublicChannelSource(source) {
20964
+ return !!source && source.role === "public" && (source.channel === "whatsapp" || source.channel === "telegram");
20965
+ }
20690
20966
  function resolveReseatPlan(body, mintId = randomUUID12, adminUserId, authenticatedName, source) {
20691
20967
  const sessionId = mintId();
20692
20968
  const key = `session:${sessionId}`;
@@ -20700,6 +20976,8 @@ function resolveReseatPlan(body, mintId = randomUUID12, adminUserId, authenticat
20700
20976
  payload.channel = source.channel;
20701
20977
  payload.senderId = source.senderId;
20702
20978
  if (source.accountId) payload.targetAccountId = source.accountId;
20979
+ if (source.channel === "whatsapp") payload.waChannel = waChannelDescriptor(source.senderId);
20980
+ else payload.telegramChannel = telegramChannelDescriptor(source.senderId);
20703
20981
  } else {
20704
20982
  payload.channel = "webchat";
20705
20983
  payload.senderId = key;
@@ -20747,6 +21025,10 @@ app36.post("/", requireAdminSession, async (c) => {
20747
21025
  const authenticatedName = accountId ? await resolveAuthenticatedName(accountId, adminUserId) : void 0;
20748
21026
  const sourceMeta = readSourceRowMeta(fromSessionId);
20749
21027
  const source = sourceMeta ? { channel: sourceMeta.channel, senderId: sourceMeta.senderId, role: sourceMeta.role, accountId: sourceMeta.accountId } : null;
21028
+ if (isPublicChannelSource(source)) {
21029
+ console.error(`[chat-reseat] op=reseat-refused from=${fromSessionId} reason=public-channel channel=${source?.channel ?? "none"}`);
21030
+ return c.json({ error: "public channel re-seat not supported (would detach the seat)" }, 400);
21031
+ }
20750
21032
  const plan = resolveReseatPlan(
20751
21033
  { fromSessionId, model, ...permissionMode ? { permissionMode } : {}, ...effort ? { effort } : {} },
20752
21034
  randomUUID12,
@@ -20799,19 +21081,21 @@ app36.post("/", requireAdminSession, async (c) => {
20799
21081
  if (isChannelFork) {
20800
21082
  const channel = plan.payload.channel;
20801
21083
  const senderId = plan.payload.senderId;
20802
- try {
20803
- const provenance = source?.accountId ? "source-account" : "boot-account-fallback";
20804
- const dir = source?.accountId ? listValidAccounts().find((a) => a.accountId === source.accountId)?.accountDir ?? null : resolveAccount()?.accountDir ?? null;
20805
- if (dir) {
20806
- writeChannelOverrideId(dir, channel, senderId, plan.sessionId);
20807
- console.log(`[chat-reseat] op=channel-forward reseatReqId=${reseatReqId} senderId=${senderId} from=${fromSessionId} to=${plan.sessionId} keyedOn=${provenance}`);
20808
- } else {
20809
- console.error(`[session-reseat] channel-forward-failed reason=no-account-dir accountId=${source?.accountId ?? "none"} senderId=${senderId}`);
21084
+ if (!shouldWriteChannelForward(source)) {
21085
+ console.error(`[chat-reseat] op=channel-forward-skipped reseatReqId=${reseatReqId} senderId=${senderId} from=${fromSessionId} reason=no-source-account`);
21086
+ } else {
21087
+ try {
21088
+ const dir = listValidAccounts().find((a) => a.accountId === source.accountId)?.accountDir ?? null;
21089
+ if (dir) {
21090
+ writeChannelOverrideId(dir, channel, senderId, plan.sessionId);
21091
+ console.log(`[chat-reseat] op=channel-forward reseatReqId=${reseatReqId} senderId=${senderId} from=${fromSessionId} to=${plan.sessionId} account=${source.accountId}`);
21092
+ } else {
21093
+ console.error(`[session-reseat] channel-forward-failed reason=no-account-dir accountId=${source.accountId} senderId=${senderId}`);
21094
+ }
21095
+ } catch (err) {
21096
+ console.error(`[session-reseat] channel-forward-failed err=${err instanceof Error ? err.message : String(err)} senderId=${senderId}`);
20810
21097
  }
20811
- } catch (err) {
20812
- console.error(`[session-reseat] channel-forward-failed err=${err instanceof Error ? err.message : String(err)} senderId=${senderId}`);
20813
21098
  }
20814
- console.log(`[chat-reseat] op=channel-fork-unbound reseatReqId=${reseatReqId} senderId=${senderId} session=${plan.sessionId} note=binds-on-next-inbound`);
20815
21099
  }
20816
21100
  const target = `/chat?session=${plan.sessionId}`;
20817
21101
  console.log(`[chat-reseat] op=acquired reseatReqId=${reseatReqId} pid=${parsed?.spawnedPid ?? "none"} new=${plan.sessionId}`);
@@ -20948,10 +21232,10 @@ var system_stats_default = app37;
20948
21232
 
20949
21233
  // server/routes/admin/health.ts
20950
21234
  import { existsSync as existsSync31, readFileSync as readFileSync35 } from "fs";
20951
- import { resolve as resolve26, join as join36 } from "path";
20952
- var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT ?? resolve26(process.cwd(), "..");
21235
+ import { resolve as resolve28, join as join38 } from "path";
21236
+ var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT ?? resolve28(process.cwd(), "..");
20953
21237
  var brandHostname = "maxy";
20954
- var brandJsonPath = join36(PLATFORM_ROOT6, "config", "brand.json");
21238
+ var brandJsonPath = join38(PLATFORM_ROOT6, "config", "brand.json");
20955
21239
  if (existsSync31(brandJsonPath)) {
20956
21240
  try {
20957
21241
  const brand = JSON.parse(readFileSync35(brandJsonPath, "utf-8"));
@@ -20959,7 +21243,7 @@ if (existsSync31(brandJsonPath)) {
20959
21243
  } catch {
20960
21244
  }
20961
21245
  }
20962
- var VERSION_FILE = resolve26(PLATFORM_ROOT6, `config/.${brandHostname}-version`);
21246
+ var VERSION_FILE = resolve28(PLATFORM_ROOT6, `config/.${brandHostname}-version`);
20963
21247
  var PROCESS_STARTED_AT = (/* @__PURE__ */ new Date()).toISOString();
20964
21248
  var PROBE_TIMEOUT_MS = 1e3;
20965
21249
  function readVersion() {
@@ -21741,7 +22025,7 @@ var browser_default = app45;
21741
22025
 
21742
22026
  // server/routes/admin/calendar.ts
21743
22027
  import { existsSync as existsSync32 } from "fs";
21744
- import { join as join38 } from "path";
22028
+ import { join as join40 } from "path";
21745
22029
 
21746
22030
  // server/lib/calendar-ics.ts
21747
22031
  var CRLF = "\r\n";
@@ -21841,7 +22125,7 @@ function countVEvents(ics) {
21841
22125
 
21842
22126
  // server/lib/calendar-availability.ts
21843
22127
  import { readFileSync as readFileSync36 } from "fs";
21844
- import { join as join37 } from "path";
22128
+ import { join as join39 } from "path";
21845
22129
  var AVAILABILITY_FILENAME = "calendar-availability.json";
21846
22130
  var REQUIRED_KEYS = [
21847
22131
  "timezone",
@@ -21850,7 +22134,7 @@ var REQUIRED_KEYS = [
21850
22134
  "weekly"
21851
22135
  ];
21852
22136
  function readAvailabilityConfig(accountDir) {
21853
- const path3 = join37(accountDir, AVAILABILITY_FILENAME);
22137
+ const path3 = join39(accountDir, AVAILABILITY_FILENAME);
21854
22138
  let raw;
21855
22139
  try {
21856
22140
  raw = readFileSync36(path3, "utf-8");
@@ -22212,7 +22496,7 @@ app46.get("/booking-link", requireAdminSession, (c) => {
22212
22496
  console.error('[calendar] op=booking-link-fail err="no account configured"');
22213
22497
  return c.json({ bookingDomain: null });
22214
22498
  }
22215
- if (!existsSync32(join38(account.accountDir, AVAILABILITY_FILENAME))) {
22499
+ if (!existsSync32(join40(account.accountDir, AVAILABILITY_FILENAME))) {
22216
22500
  console.log("[calendar] op=booking-link bookingDomain=none source=availability-config");
22217
22501
  return c.json({ bookingDomain: null });
22218
22502
  }
@@ -22871,9 +23155,9 @@ var verify_token_default = app54;
22871
23155
 
22872
23156
  // app/lib/access-email.ts
22873
23157
  import { spawn as spawn2 } from "child_process";
22874
- import { resolve as resolve27 } from "path";
22875
- var PLATFORM_ROOT7 = process.env.MAXY_PLATFORM_ROOT ?? resolve27(process.cwd(), "..");
22876
- var SEND_SCRIPT = resolve27(
23158
+ import { resolve as resolve29 } from "path";
23159
+ var PLATFORM_ROOT7 = process.env.MAXY_PLATFORM_ROOT ?? resolve29(process.cwd(), "..");
23160
+ var SEND_SCRIPT = resolve29(
22877
23161
  PLATFORM_ROOT7,
22878
23162
  "plugins",
22879
23163
  "email",
@@ -23016,7 +23300,7 @@ var access_default = app56;
23016
23300
 
23017
23301
  // server/routes/sites.ts
23018
23302
  import { existsSync as existsSync33, readFileSync as readFileSync37, realpathSync as realpathSync7, statSync as statSync14 } from "fs";
23019
- import { resolve as resolve28 } from "path";
23303
+ import { resolve as resolve30 } from "path";
23020
23304
  var SAFE_SEG_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;
23021
23305
  var MIME = {
23022
23306
  ".html": "text/html; charset=utf-8",
@@ -23073,8 +23357,8 @@ app57.get("/:rel{.*}", (c) => {
23073
23357
  }
23074
23358
  segments.push(seg);
23075
23359
  }
23076
- const rootDir = resolve28(account.accountDir, "sites");
23077
- let filePath = segments.length === 0 ? rootDir : resolve28(rootDir, ...segments);
23360
+ const rootDir = resolve30(account.accountDir, "sites");
23361
+ let filePath = segments.length === 0 ? rootDir : resolve30(rootDir, ...segments);
23078
23362
  if (filePath !== rootDir && !filePath.startsWith(rootDir + "/")) {
23079
23363
  console.error(`[sites] path-traversal-rejected path=${reqPath} reason=escape status=403`);
23080
23364
  return c.text("Forbidden", 403);
@@ -23094,7 +23378,7 @@ app57.get("/:rel{.*}", (c) => {
23094
23378
  return c.redirect(target, 301);
23095
23379
  }
23096
23380
  if (stat9?.isDirectory()) {
23097
- filePath = resolve28(filePath, "index.html");
23381
+ filePath = resolve30(filePath, "index.html");
23098
23382
  }
23099
23383
  if (!filePath.startsWith(rootDir + "/")) {
23100
23384
  console.error(`[sites] path-traversal-rejected path=${reqPath} reason=escape status=403`);
@@ -23232,7 +23516,7 @@ var VISITOR_COOKIE_MAX_AGE_SECONDS = Math.floor(DEFAULT_TTL_MS / 1e3);
23232
23516
 
23233
23517
  // app/lib/brand-config.ts
23234
23518
  import { existsSync as existsSync34, readFileSync as readFileSync39 } from "fs";
23235
- import { join as join39 } from "path";
23519
+ import { join as join41 } from "path";
23236
23520
  var cached2 = null;
23237
23521
  var cachedAttempted = false;
23238
23522
  function readBrandConfig() {
@@ -23240,7 +23524,7 @@ function readBrandConfig() {
23240
23524
  cachedAttempted = true;
23241
23525
  const platformRoot5 = process.env.MAXY_PLATFORM_ROOT;
23242
23526
  if (!platformRoot5) return null;
23243
- const brandPath = join39(platformRoot5, "config", "brand.json");
23527
+ const brandPath = join41(platformRoot5, "config", "brand.json");
23244
23528
  if (!existsSync34(brandPath)) return null;
23245
23529
  try {
23246
23530
  cached2 = JSON.parse(readFileSync39(brandPath, "utf-8"));
@@ -23740,14 +24024,14 @@ async function writeEvent(opts) {
23740
24024
  var visitor_event_default = app60;
23741
24025
 
23742
24026
  // server/routes/session.ts
23743
- import { resolve as resolve29 } from "path";
24027
+ import { resolve as resolve31 } from "path";
23744
24028
  import { existsSync as existsSync35, writeFileSync as writeFileSync14, mkdirSync as mkdirSync9 } from "fs";
23745
24029
  var UUID_RE5 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
23746
24030
  function writeBrandingCache(accountId, agentSlug, branding) {
23747
24031
  try {
23748
- const cacheDir = resolve29(MAXY_DIR, "branding-cache", accountId);
24032
+ const cacheDir = resolve31(MAXY_DIR, "branding-cache", accountId);
23749
24033
  mkdirSync9(cacheDir, { recursive: true });
23750
- writeFileSync14(resolve29(cacheDir, `${agentSlug}.json`), JSON.stringify(branding), "utf-8");
24034
+ writeFileSync14(resolve31(cacheDir, `${agentSlug}.json`), JSON.stringify(branding), "utf-8");
23751
24035
  } catch (err) {
23752
24036
  console.error(`[branding] cache write failed: ${err instanceof Error ? err.message : String(err)}`);
23753
24037
  }
@@ -23824,8 +24108,8 @@ app61.post("/", async (c) => {
23824
24108
  }
23825
24109
  let agentConfig = null;
23826
24110
  if (account) {
23827
- const agentDir = resolve29(account.accountDir, "agents", agentSlug);
23828
- if (!existsSync35(agentDir) || !existsSync35(resolve29(agentDir, "config.json"))) {
24111
+ const agentDir = resolve31(account.accountDir, "agents", agentSlug);
24112
+ if (!existsSync35(agentDir) || !existsSync35(resolve31(agentDir, "config.json"))) {
23829
24113
  return c.json({ error: "Agent not found" }, 404);
23830
24114
  }
23831
24115
  agentConfig = resolveAgentConfig(account.accountDir, agentSlug);
@@ -24075,7 +24359,7 @@ var calendar_public_default = app62;
24075
24359
  // server/routes/portal-fetch.ts
24076
24360
  import { createReadStream as createReadStream3 } from "fs";
24077
24361
  import { stat as stat6, readFile as readFile6, realpath } from "fs/promises";
24078
- import { join as join40, resolve as resolve30, sep as sep8 } from "path";
24362
+ import { join as join42, resolve as resolve32, sep as sep8 } from "path";
24079
24363
  import { Readable as Readable3 } from "stream";
24080
24364
 
24081
24365
  // ../plugins/cloudflare/bin/schema-exposed-dirs.mjs
@@ -24176,7 +24460,7 @@ ${expiresAtMs}`)
24176
24460
  return diff === 0;
24177
24461
  }
24178
24462
  async function readAccountSecret(accountId) {
24179
- const path3 = resolve30(DATA_ROOT, "accounts", accountId, "secrets", "data-portal.env");
24463
+ const path3 = resolve32(DATA_ROOT, "accounts", accountId, "secrets", "data-portal.env");
24180
24464
  let text;
24181
24465
  try {
24182
24466
  text = await readFile6(path3, "utf8");
@@ -24197,13 +24481,13 @@ function isValidAccountId2(value) {
24197
24481
  async function resolveFetchTarget(accountDir, relPath) {
24198
24482
  let schemaText = null;
24199
24483
  try {
24200
- schemaText = await readFile6(join40(accountDir, "SCHEMA.md"), "utf8");
24484
+ schemaText = await readFile6(join42(accountDir, "SCHEMA.md"), "utf8");
24201
24485
  } catch {
24202
24486
  schemaText = null;
24203
24487
  }
24204
24488
  const { exposed } = resolveExposedDirs(schemaText);
24205
24489
  if (exposed.length === 0) return { ok: false, reason: "not-exposed" };
24206
- const lexical = resolve30(accountDir, relPath);
24490
+ const lexical = resolve32(accountDir, relPath);
24207
24491
  let root;
24208
24492
  let absolute;
24209
24493
  try {
@@ -24241,7 +24525,7 @@ async function handle(c, headOnly) {
24241
24525
  console.error(`${TAG45} op=denied account=${q(accountId)} reason=secret`);
24242
24526
  return c.json({ ok: false, error: "denied" }, 401);
24243
24527
  }
24244
- const target = await resolveFetchTarget(resolve30(DATA_ROOT, "accounts", accountId), relPath);
24528
+ const target = await resolveFetchTarget(resolve32(DATA_ROOT, "accounts", accountId), relPath);
24245
24529
  if (!target.ok) {
24246
24530
  const op = target.reason === "enoent" ? "miss" : "denied";
24247
24531
  console.error(`${TAG45} op=${op} account=${accountId} reason=${target.reason}`);
@@ -24353,7 +24637,7 @@ function startTimeEntryCensus(openSession, opts = {}) {
24353
24637
 
24354
24638
  // app/lib/ledger-census.ts
24355
24639
  import { readdirSync as readdirSync25, readFileSync as readFileSync40, statSync as statSync15 } from "fs";
24356
- import { join as join41 } from "path";
24640
+ import { join as join43 } from "path";
24357
24641
 
24358
24642
  // ../lib/ledger-core/dist/reconcile.js
24359
24643
  var LEDGER_INDEX_NAMES = [
@@ -24407,7 +24691,7 @@ function countWriteRejects24h(logDir, now) {
24407
24691
  }
24408
24692
  let total = 0;
24409
24693
  for (const name of entries) {
24410
- const path3 = join41(logDir, name);
24694
+ const path3 = join43(logDir, name);
24411
24695
  try {
24412
24696
  if (now - statSync15(path3).mtimeMs > DAY_MS) continue;
24413
24697
  for (const line of readFileSync40(path3, "utf8").split("\n")) {
@@ -24679,7 +24963,7 @@ function startGraphHealthTimer() {
24679
24963
 
24680
24964
  // app/lib/file-watcher.ts
24681
24965
  import * as fsp2 from "fs/promises";
24682
- import { resolve as resolve31, sep as sep9 } from "path";
24966
+ import { resolve as resolve33, sep as sep9 } from "path";
24683
24967
  var ACCOUNT_UUID_RE4 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
24684
24968
  var DEFAULT_COALESCE_MS = 500;
24685
24969
  var ROOTS = ["accounts"];
@@ -24698,7 +24982,7 @@ async function startFileWatcher(opts = {}) {
24698
24982
  const dropFn = opts.drop ?? dropFileIndex;
24699
24983
  for (const r of ROOTS) {
24700
24984
  try {
24701
- await fsp2.mkdir(resolve31(dataRoot, r), { recursive: true });
24985
+ await fsp2.mkdir(resolve33(dataRoot, r), { recursive: true });
24702
24986
  } catch (err) {
24703
24987
  console.error(
24704
24988
  `[file-watcher] start-failed root="${r}" err="${err.message}" \u2014 index will be maintained by the 5-min reconcile backstop only`
@@ -24719,7 +25003,7 @@ async function startFileWatcher(opts = {}) {
24719
25003
  timers.set(relativePath, t);
24720
25004
  }
24721
25005
  async function runHook(relativePath, accountId) {
24722
- const absolute = resolve31(dataRoot, relativePath);
25006
+ const absolute = resolve33(dataRoot, relativePath);
24723
25007
  let exists = false;
24724
25008
  try {
24725
25009
  const st = await fsp2.stat(absolute);
@@ -24743,7 +25027,7 @@ async function startFileWatcher(opts = {}) {
24743
25027
  }
24744
25028
  }
24745
25029
  async function watchRoot(rootName) {
24746
- const absRoot = resolve31(dataRoot, rootName);
25030
+ const absRoot = resolve33(dataRoot, rootName);
24747
25031
  try {
24748
25032
  const iter = fsp2.watch(absRoot, { recursive: true, signal: controller.signal });
24749
25033
  for await (const event of iter) {
@@ -24782,7 +25066,7 @@ async function startFileWatcher(opts = {}) {
24782
25066
 
24783
25067
  // app/lib/migrate-uploads.ts
24784
25068
  import { mkdir as mkdir5, readdir as readdir5, rename as rename2, rm as rm3 } from "fs/promises";
24785
- import { dirname as dirname13, relative as relative6, resolve as resolve32 } from "path";
25069
+ import { dirname as dirname13, relative as relative6, resolve as resolve34 } from "path";
24786
25070
  var ACCOUNT_UUID_RE5 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
24787
25071
  async function walkFiles(dir) {
24788
25072
  const out = [];
@@ -24793,7 +25077,7 @@ async function walkFiles(dir) {
24793
25077
  return out;
24794
25078
  }
24795
25079
  for (const e of entries) {
24796
- const abs = resolve32(dir, e.name);
25080
+ const abs = resolve34(dir, e.name);
24797
25081
  if (e.isDirectory()) {
24798
25082
  out.push(...await walkFiles(abs));
24799
25083
  } else if (e.isFile()) {
@@ -24813,7 +25097,7 @@ async function relocateTree(srcDir, destDir, dataRoot, accountId, session) {
24813
25097
  let moved = 0;
24814
25098
  for (const oldAbs of files) {
24815
25099
  const suffix = relative6(srcDir, oldAbs);
24816
- const newAbs = resolve32(destDir, suffix);
25100
+ const newAbs = resolve34(destDir, suffix);
24817
25101
  await mkdir5(dirname13(newAbs), { recursive: true });
24818
25102
  await rename2(oldAbs, newAbs);
24819
25103
  moved++;
@@ -24840,7 +25124,7 @@ async function relocateTree(srcDir, destDir, dataRoot, accountId, session) {
24840
25124
  }
24841
25125
  async function migrateUploads(opts = {}) {
24842
25126
  const dataRoot = opts.dataRoot ?? DATA_ROOT;
24843
- const oldRoot = resolve32(dataRoot, "uploads");
25127
+ const oldRoot = resolve34(dataRoot, "uploads");
24844
25128
  let topEntries;
24845
25129
  try {
24846
25130
  topEntries = await readdir5(oldRoot, { withFileTypes: true });
@@ -24861,8 +25145,8 @@ async function migrateUploads(opts = {}) {
24861
25145
  const name = entry.name;
24862
25146
  if (ACCOUNT_UUID_RE5.test(name)) {
24863
25147
  moved += await relocateTree(
24864
- resolve32(oldRoot, name),
24865
- resolve32(dataRoot, "accounts", name, "uploads"),
25148
+ resolve34(oldRoot, name),
25149
+ resolve34(dataRoot, "accounts", name, "uploads"),
24866
25150
  dataRoot,
24867
25151
  name,
24868
25152
  session
@@ -24874,8 +25158,8 @@ async function migrateUploads(opts = {}) {
24874
25158
  continue;
24875
25159
  }
24876
25160
  moved += await relocateTree(
24877
- resolve32(oldRoot, "public"),
24878
- resolve32(dataRoot, "accounts", installAccountId, "uploads", "public"),
25161
+ resolve34(oldRoot, "public"),
25162
+ resolve34(dataRoot, "accounts", installAccountId, "uploads", "public"),
24879
25163
  dataRoot,
24880
25164
  null,
24881
25165
  // public uploads carry no graph nodes
@@ -24906,7 +25190,7 @@ async function migrateUploads(opts = {}) {
24906
25190
  // app/lib/migrate-admin-webchat-sidecars.ts
24907
25191
  import { readdir as readdir6, readFile as readFile7, rename as rename3, writeFile as writeFile4, unlink as unlink3 } from "fs/promises";
24908
25192
  import { existsSync as existsSync36 } from "fs";
24909
- import { join as join42 } from "path";
25193
+ import { join as join44 } from "path";
24910
25194
  var ADMIN_ROLE2 = "admin";
24911
25195
  var WEBCHAT_CHANNEL2 = "webchat";
24912
25196
  var SESSION_META_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.meta\.json$/i;
@@ -24951,7 +25235,7 @@ async function collectLiveSidecars(projectsRoot) {
24951
25235
  }
24952
25236
  for (const slug of slugs) {
24953
25237
  if (!slug.isDirectory()) continue;
24954
- const slugDir = join42(projectsRoot, slug.name);
25238
+ const slugDir = join44(projectsRoot, slug.name);
24955
25239
  let entries;
24956
25240
  try {
24957
25241
  entries = await readdir6(slugDir, { withFileTypes: true });
@@ -24960,9 +25244,9 @@ async function collectLiveSidecars(projectsRoot) {
24960
25244
  }
24961
25245
  for (const entry of entries) {
24962
25246
  if (entry.isFile() && SESSION_META_RE.test(entry.name)) {
24963
- out.push(join42(slugDir, entry.name));
25247
+ out.push(join44(slugDir, entry.name));
24964
25248
  } else if (entry.isDirectory() && entry.name === "subagents") {
24965
- const subDir = join42(slugDir, entry.name);
25249
+ const subDir = join44(slugDir, entry.name);
24966
25250
  let subs;
24967
25251
  try {
24968
25252
  subs = await readdir6(subDir, { withFileTypes: true });
@@ -24970,7 +25254,7 @@ async function collectLiveSidecars(projectsRoot) {
24970
25254
  continue;
24971
25255
  }
24972
25256
  for (const s of subs) {
24973
- if (s.isFile() && SESSION_META_RE.test(s.name)) out.push(join42(subDir, s.name));
25257
+ if (s.isFile() && SESSION_META_RE.test(s.name)) out.push(join44(subDir, s.name));
24974
25258
  }
24975
25259
  }
24976
25260
  }
@@ -24982,7 +25266,7 @@ function shortId(path3) {
24982
25266
  return base.slice(0, 8);
24983
25267
  }
24984
25268
  async function migrateAdminWebchatSidecars(opts = {}) {
24985
- const projectsRoot = opts.projectsRoot ?? (process.env.CLAUDE_CONFIG_DIR ? join42(process.env.CLAUDE_CONFIG_DIR, "projects") : null) ?? "";
25269
+ const projectsRoot = opts.projectsRoot ?? (process.env.CLAUDE_CONFIG_DIR ? join44(process.env.CLAUDE_CONFIG_DIR, "projects") : null) ?? "";
24986
25270
  const usersFile = opts.usersFile ?? USERS_FILE;
24987
25271
  const accountId = opts.accountId ?? resolveAccount()?.accountId ?? null;
24988
25272
  const resolveName = opts.resolveName ?? defaultResolveName;
@@ -25336,11 +25620,9 @@ function createWaChannelRoutes(deps) {
25336
25620
  void stream2.writeSSE({ data: JSON.stringify(payload) });
25337
25621
  });
25338
25622
  console.error(`[whatsapp-native] op=channel-attached senderId=${senderId}`);
25339
- deps.onAttached?.(senderId);
25340
25623
  stream2.onAbort(() => {
25341
25624
  deps.hub.detach(senderId);
25342
25625
  console.error(`[whatsapp-native] op=channel-detached senderId=${senderId}`);
25343
- deps.onDetached?.(senderId);
25344
25626
  });
25345
25627
  while (!stream2.aborted) {
25346
25628
  await stream2.sleep(15e3);
@@ -25481,31 +25763,16 @@ var WaGateway = class {
25481
25763
  docContexts = /* @__PURE__ */ new Map();
25482
25764
  spawning = /* @__PURE__ */ new Set();
25483
25765
  seq = 0;
25484
- /** Hono sub-app exposing the loopback channel routes; mount on the UI app. */
25485
- /** Task 1789senderId -> the ms at which its channel server last detached,
25486
- * cleared on attach. The standing audit reads this; nothing else does. */
25487
- detachedAt = /* @__PURE__ */ new Map();
25488
- /** Seat lost. `atMs` is injected so the audit's accounting is testable. */
25489
- recordDetached(senderId, atMs) {
25490
- this.detachedAt.set(senderId, atMs);
25491
- }
25492
- /** Seat regained — the sender is no longer unseated. */
25493
- recordAttached(senderId) {
25494
- this.detachedAt.delete(senderId);
25495
- }
25496
- /** Senders with no channel server for longer than `olderThanMs`. */
25497
- unseatedSenders(nowMs, olderThanMs) {
25498
- const out = [];
25499
- for (const [senderId, sinceMs] of this.detachedAt) {
25500
- if (nowMs - sinceMs > olderThanMs) out.push({ senderId, sinceMs });
25501
- }
25502
- return out.sort((a, b) => a.senderId.localeCompare(b.senderId));
25503
- }
25766
+ /** Hono sub-app exposing the loopback channel routes; mount on the UI app.
25767
+ * Task 1836 — the process-local unseated-sender census that used to live here
25768
+ * was retired. Its quiet state was ambiguous (a UI restart emptied it, so
25769
+ * silence meant "no detach seen since boot", not "every sender is seated") and
25770
+ * it grew unbounded. The genuine "no seat with pending work" signal is
25771
+ * InboundHub.sweep -> spawn-undelivered, which is driven by live queue state
25772
+ * and self-prunes. */
25504
25773
  routes() {
25505
25774
  return createWaChannelRoutes({
25506
25775
  hub: this.hub,
25507
- onAttached: (senderId) => this.recordAttached(senderId),
25508
- onDetached: (senderId) => this.recordDetached(senderId, Date.now()),
25509
25776
  sendOutbound: (senderId, text) => this.sendOutbound(senderId, text),
25510
25777
  sendDocument: (senderId, filePath, caption) => this.sendDocument(senderId, filePath, caption),
25511
25778
  onReady: (senderId) => {
@@ -25684,12 +25951,6 @@ function logDuplicateSenderGroups(groups) {
25684
25951
  );
25685
25952
  }
25686
25953
  }
25687
- function logUnseatedSenders(entries, nowMs) {
25688
- for (const e of entries) {
25689
- const secs = Math.round((nowMs - e.sinceMs) / 1e3);
25690
- console.error(`[whatsapp-native] op=sender-unseated senderId=${e.senderId} since=${new Date(e.sinceMs).toISOString()} forSeconds=${secs}`);
25691
- }
25692
- }
25693
25954
 
25694
25955
  // app/lib/whatsapp/gateway/spawn-request.ts
25695
25956
  function buildWaSpawnRequest(input) {
@@ -26045,16 +26306,16 @@ var WebchatGateway = class _WebchatGateway {
26045
26306
  * The public /api/chat route awaits this to return the reply on the same SSE
26046
26307
  * response, preserving the pre-756 POST→single-blob browser contract. */
26047
26308
  awaitReply(key, timeoutMs) {
26048
- return new Promise((resolve38) => {
26309
+ return new Promise((resolve39) => {
26049
26310
  const timer = setTimeout(() => {
26050
26311
  this.replyAwaiters.delete(key);
26051
- resolve38({ timeout: true });
26312
+ resolve39({ timeout: true });
26052
26313
  }, timeoutMs);
26053
26314
  if (timer.unref) timer.unref();
26054
26315
  this.replyAwaiters.set(key, (r) => {
26055
26316
  clearTimeout(timer);
26056
26317
  this.replyAwaiters.delete(key);
26057
- resolve38(r);
26318
+ resolve39(r);
26058
26319
  });
26059
26320
  });
26060
26321
  }
@@ -26065,11 +26326,11 @@ var WebchatGateway = class _WebchatGateway {
26065
26326
  * terminal dialog stays answerable). */
26066
26327
  awaitPermissionVerdict(p, timeoutMs) {
26067
26328
  const k = _WebchatGateway.promptKey(p.key, p.requestId);
26068
- return new Promise((resolve38) => {
26329
+ return new Promise((resolve39) => {
26069
26330
  this.pendingPrompts.get(k)?.resolve({ timeout: true });
26070
26331
  const timer = setTimeout(() => {
26071
26332
  this.pendingPrompts.delete(k);
26072
- resolve38({ timeout: true });
26333
+ resolve39({ timeout: true });
26073
26334
  }, timeoutMs);
26074
26335
  if (timer.unref) timer.unref();
26075
26336
  this.pendingPrompts.set(k, {
@@ -26078,7 +26339,7 @@ var WebchatGateway = class _WebchatGateway {
26078
26339
  resolve: (r) => {
26079
26340
  clearTimeout(timer);
26080
26341
  this.pendingPrompts.delete(k);
26081
- resolve38(r);
26342
+ resolve39(r);
26082
26343
  }
26083
26344
  });
26084
26345
  console.error(`[webchat:perm] op=open key=${keyDisplay(p.key)} id=${p.requestId} tool=${p.toolName}`);
@@ -26643,7 +26904,7 @@ async function fanOut(subscribers, text, onError, tag) {
26643
26904
 
26644
26905
  // app/lib/webchat/file-delivery.ts
26645
26906
  import { realpathSync as realpathSync8 } from "fs";
26646
- import { resolve as resolve33 } from "path";
26907
+ import { resolve as resolve35 } from "path";
26647
26908
 
26648
26909
  // app/lib/channel-pty-bridge/file-delivery.ts
26649
26910
  var SEND_USER_FILE = "SendUserFile";
@@ -26744,7 +27005,7 @@ function makeWebchatSendFile(entry) {
26744
27005
  console.error(`${TAG49} file-delivery reject reason=no-account sender=${entry.senderId}`);
26745
27006
  return { ok: false, error: "no-account" };
26746
27007
  }
26747
- const accountDir = resolve33(platformRoot2(), "..", "data/accounts", entry.accountId);
27008
+ const accountDir = resolve35(platformRoot2(), "..", "data/accounts", entry.accountId);
26748
27009
  try {
26749
27010
  const resolved = realpathSync8(filePath);
26750
27011
  const accountResolved = realpathSync8(accountDir);
@@ -27311,7 +27572,7 @@ function buildTelegramSpawnRequest(input) {
27311
27572
  // app/lib/telegram/outbound/send-document.ts
27312
27573
  import { realpathSync as realpathSync9 } from "fs";
27313
27574
  import { readFile as readFile8, stat as stat8 } from "fs/promises";
27314
- import { resolve as resolve34, basename as basename11 } from "path";
27575
+ import { resolve as resolve36, basename as basename11 } from "path";
27315
27576
  var TAG51 = "[telegram:outbound]";
27316
27577
  var TELEGRAM_DOCUMENT_MAX_BYTES = 50 * 1024 * 1024;
27317
27578
  async function sendTelegramDocument(input) {
@@ -27322,7 +27583,7 @@ async function sendTelegramDocument(input) {
27322
27583
  if (!maxyAccountId || !platformRoot5) {
27323
27584
  return { ok: false, status: 400, error: "Cannot validate file path: missing account or platform context" };
27324
27585
  }
27325
- const accountDir = resolve34(platformRoot5, "..", "data/accounts", maxyAccountId);
27586
+ const accountDir = resolve36(platformRoot5, "..", "data/accounts", maxyAccountId);
27326
27587
  let resolvedPath;
27327
27588
  try {
27328
27589
  resolvedPath = realpathSync9(filePath);
@@ -27448,15 +27709,6 @@ function startTelegramNativeFileFollower(input) {
27448
27709
  });
27449
27710
  }
27450
27711
 
27451
- // server/telegram-descriptor.ts
27452
- import { resolve as resolve35, join as join43 } from "path";
27453
- function telegramGatewayUrl() {
27454
- return `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`;
27455
- }
27456
- function telegramServerPath() {
27457
- return process.env.MAXY_TELEGRAM_CHANNEL_SERVER_PATH ?? resolve35(process.env.MAXY_PLATFORM_ROOT ?? join43(__dirname, ".."), "services/telegram-channel/dist/server.js");
27458
- }
27459
-
27460
27712
  // app/lib/channel-pty-bridge/public-session-end-review.ts
27461
27713
  import { randomUUID as randomUUID16 } from "crypto";
27462
27714
  var TAG53 = "[public-session-review]";
@@ -27742,7 +27994,7 @@ function broadcastAdminShutdown(reason) {
27742
27994
  // ../lib/entitlement/src/index.ts
27743
27995
  import { createPublicKey, createHash as createHash6, verify as cryptoVerify } from "crypto";
27744
27996
  import { existsSync as existsSync37, readFileSync as readFileSync41, statSync as statSync16 } from "fs";
27745
- import { resolve as resolve36 } from "path";
27997
+ import { resolve as resolve37 } from "path";
27746
27998
 
27747
27999
  // ../lib/entitlement/src/canonicalize.ts
27748
28000
  function canonicalize(value) {
@@ -27777,7 +28029,7 @@ var PUBKEY_SHA256 = "8eee6bcb33545fd13b16d3199a5735ca5db5062834c7b49dfe4f23801d9
27777
28029
  var GRACE_DAYS = 7;
27778
28030
  var GRACE_MS = GRACE_DAYS * 24 * 60 * 60 * 1e3;
27779
28031
  function pubkeyPath(brand) {
27780
- return resolve36(brand.platformRoot, "lib", "entitlement", "rubytech-pubkey.pem");
28032
+ return resolve37(brand.platformRoot, "lib", "entitlement", "rubytech-pubkey.pem");
27781
28033
  }
27782
28034
  var memo = null;
27783
28035
  function memoKey(mtimeMs, account) {
@@ -27789,7 +28041,7 @@ function resolveEntitlement(brand, account) {
27789
28041
  if (brand.commercialMode !== true) {
27790
28042
  return logResolved(implicitTrust(account), null);
27791
28043
  }
27792
- const entitlementPath = resolve36(brand.configDir, "entitlement.json");
28044
+ const entitlementPath = resolve37(brand.configDir, "entitlement.json");
27793
28045
  if (!existsSync37(entitlementPath)) {
27794
28046
  return logResolved(anonymousFallback("missing"), { reason: "missing" });
27795
28047
  }
@@ -27981,7 +28233,7 @@ function clientFrom(c) {
27981
28233
  }
27982
28234
  installMediaDownloadGuard();
27983
28235
  var PLATFORM_ROOT8 = process.env.MAXY_PLATFORM_ROOT || "";
27984
- var BRAND_JSON_PATH = PLATFORM_ROOT8 ? join44(PLATFORM_ROOT8, "config", "brand.json") : "";
28236
+ var BRAND_JSON_PATH = PLATFORM_ROOT8 ? join45(PLATFORM_ROOT8, "config", "brand.json") : "";
27985
28237
  var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", marketingUrl: "https://getmaxy.com" };
27986
28238
  if (BRAND_JSON_PATH && !existsSync38(BRAND_JSON_PATH)) {
27987
28239
  console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);
@@ -28020,7 +28272,7 @@ var brandLoginOpts = {
28020
28272
  appleTouchIconPath: brandAppIcon512Path,
28021
28273
  themeColor: BRAND.defaultColors?.primary
28022
28274
  };
28023
- var ALIAS_DOMAINS_PATH = join44(homedir4(), BRAND.configDir, "alias-domains.json");
28275
+ var ALIAS_DOMAINS_PATH = join45(homedir4(), BRAND.configDir, "alias-domains.json");
28024
28276
  function loadAliasDomains() {
28025
28277
  try {
28026
28278
  if (!existsSync38(ALIAS_DOMAINS_PATH)) return null;
@@ -28097,7 +28349,7 @@ var waGateway = new WaGateway({
28097
28349
  fetchStandingRules: fetchAccountStandingRules,
28098
28350
  resolveSessionOverride: (accountId, senderId) => resolveChannelOverride(accountId, "whatsapp", senderId),
28099
28351
  gatewayUrl: `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`,
28100
- serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve37(process.env.MAXY_PLATFORM_ROOT ?? join44(__dirname, ".."), "services/whatsapp-channel/dist/server.js"),
28352
+ serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve38(process.env.MAXY_PLATFORM_ROOT ?? join45(__dirname, ".."), "services/whatsapp-channel/dist/server.js"),
28101
28353
  // Task 751 / 1390 — file delivery on the native channel. `maxyAccountId` (the
28102
28354
  // path-validation scope) is the sender's effective SESSION account, resolved
28103
28355
  // once at the inbound gate and threaded here via the gateway's per-sender doc
@@ -28112,7 +28364,7 @@ var waGateway = new WaGateway({
28112
28364
  caption,
28113
28365
  accountId,
28114
28366
  maxyAccountId,
28115
- platformRoot: resolve37(process.env.MAXY_PLATFORM_ROOT ?? join44(__dirname, ".."))
28367
+ platformRoot: resolve38(process.env.MAXY_PLATFORM_ROOT ?? join45(__dirname, ".."))
28116
28368
  });
28117
28369
  return result.ok ? { ok: true, messageId: result.messageId } : { ok: false, error: result.error };
28118
28370
  },
@@ -28226,7 +28478,7 @@ function runDuplicateSenderAudit() {
28226
28478
  try {
28227
28479
  const configDir2 = process.env.CLAUDE_CONFIG_DIR;
28228
28480
  if (!configDir2) return;
28229
- const projectsRoot = join44(configDir2, "projects");
28481
+ const projectsRoot = join45(configDir2, "projects");
28230
28482
  const records = [];
28231
28483
  for (const { path: jsonlPath, isSubagent, archived } of enumerateJsonls(projectsRoot)) {
28232
28484
  if (isSubagent || archived) continue;
@@ -28236,8 +28488,6 @@ function runDuplicateSenderAudit() {
28236
28488
  records.push({ sessionId, channel: meta.channel, senderId: meta.senderId, accountId: meta.accountId });
28237
28489
  }
28238
28490
  logDuplicateSenderGroups(findDuplicateSenderGroups(records));
28239
- const now = Date.now();
28240
- logUnseatedSenders(waGateway.unseatedSenders(now, 15 * 6e4), now);
28241
28491
  } catch (err) {
28242
28492
  console.error(`[whatsapp-native] op=duplicate-sender-audit-error error=${err instanceof Error ? err.message : String(err)}`);
28243
28493
  }
@@ -28248,6 +28498,7 @@ registerLoop({
28248
28498
  firstRunDelayMs: 0,
28249
28499
  run: runDuplicateSenderAudit
28250
28500
  });
28501
+ console.log("[1789-sequel] op=cluster-complete items=[1835,1836,1837,1839]");
28251
28502
  var webchatGateway = new WebchatGateway({
28252
28503
  fetchStandingRules: fetchAccountStandingRules,
28253
28504
  gatewayUrl: webchatGatewayUrl(),
@@ -28789,8 +29040,8 @@ app64.get("/agent-assets/:slug/:filename", (c) => {
28789
29040
  console.error(`[agent-assets] no-account slug=${slug} file=${filename}`);
28790
29041
  return c.text("Not found", 404);
28791
29042
  }
28792
- const filePath = resolve37(account.accountDir, "agents", slug, "assets", filename);
28793
- const expectedDir = resolve37(account.accountDir, "agents", slug, "assets");
29043
+ const filePath = resolve38(account.accountDir, "agents", slug, "assets", filename);
29044
+ const expectedDir = resolve38(account.accountDir, "agents", slug, "assets");
28794
29045
  if (!filePath.startsWith(expectedDir + "/")) {
28795
29046
  console.error(`[agent-assets] path-traversal-rejected slug=${slug} file=${filename}`);
28796
29047
  return c.text("Forbidden", 403);
@@ -28819,8 +29070,8 @@ app64.get("/generated/:filename", (c) => {
28819
29070
  console.error(`[generated] serve file=${filename} status=404`);
28820
29071
  return c.text("Not found", 404);
28821
29072
  }
28822
- const filePath = resolve37(account.accountDir, "generated", filename);
28823
- const expectedDir = resolve37(account.accountDir, "generated");
29073
+ const filePath = resolve38(account.accountDir, "generated", filename);
29074
+ const expectedDir = resolve38(account.accountDir, "generated");
28824
29075
  if (!filePath.startsWith(expectedDir + "/")) {
28825
29076
  console.error(`[generated] serve file=${filename} status=403`);
28826
29077
  return c.text("Forbidden", 403);
@@ -28874,11 +29125,11 @@ var brandScript = `<script>window.__BRAND__=${JSON.stringify({
28874
29125
  var brandThemeColor = BRAND.defaultColors?.primary ?? "#000000";
28875
29126
  var brandBackgroundColor = BRAND.defaultColors?.background ?? "#ffffff";
28876
29127
  var brandAppIconsExist = [brandAppIcon192Path, brandAppIcon512Path, brandMaskableIconPath].every(
28877
- (p) => existsSync38(resolve37(process.cwd(), "public", p.replace(/^\//, "")))
29128
+ (p) => existsSync38(resolve38(process.cwd(), "public", p.replace(/^\//, "")))
28878
29129
  );
28879
29130
  var SW_SOURCE = (() => {
28880
29131
  try {
28881
- return readFileSync42(resolve37(process.cwd(), "public", "sw.js"), "utf-8");
29132
+ return readFileSync42(resolve38(process.cwd(), "public", "sw.js"), "utf-8");
28882
29133
  } catch {
28883
29134
  return null;
28884
29135
  }
@@ -28886,7 +29137,7 @@ var SW_SOURCE = (() => {
28886
29137
  function readInstalledVersion() {
28887
29138
  try {
28888
29139
  if (!PLATFORM_ROOT8) return "unknown";
28889
- const versionFile = join44(PLATFORM_ROOT8, "config", `.${BRAND.hostname}-version`);
29140
+ const versionFile = join45(PLATFORM_ROOT8, "config", `.${BRAND.hostname}-version`);
28890
29141
  if (!existsSync38(versionFile)) return "unknown";
28891
29142
  const content = readFileSync42(versionFile, "utf-8").trim();
28892
29143
  return content || "unknown";
@@ -28929,7 +29180,7 @@ var clientErrorReporterScript = `<script>
28929
29180
  function cachedHtml(file) {
28930
29181
  let html = htmlCache.get(file);
28931
29182
  if (!html) {
28932
- html = readFileSync42(resolve37(process.cwd(), "public", file), "utf-8");
29183
+ html = readFileSync42(resolve38(process.cwd(), "public", file), "utf-8");
28933
29184
  const productNameEsc = escapeHtml(BRAND.productName);
28934
29185
  html = html.replace(/<title>([^<]*)<\/title>/, (_match, inner) => `<title>${escapeHtml(inner).replace(/Maxy/g, productNameEsc)}</title>`);
28935
29186
  html = html.replace('href="/favicon.ico"', `href="${escapeHtml(brandFaviconPath)}"`);
@@ -28946,11 +29197,11 @@ ${clientErrorReporterScript}
28946
29197
  return html;
28947
29198
  }
28948
29199
  function loadBrandingCache(agentSlug) {
28949
- const configDir2 = join44(homedir4(), BRAND.configDir);
29200
+ const configDir2 = join45(homedir4(), BRAND.configDir);
28950
29201
  try {
28951
29202
  const accountId = getDefaultAccountId();
28952
29203
  if (!accountId) return null;
28953
- const cachePath = join44(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
29204
+ const cachePath = join45(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
28954
29205
  if (!existsSync38(cachePath)) return null;
28955
29206
  return JSON.parse(readFileSync42(cachePath, "utf-8"));
28956
29207
  } catch {
@@ -29047,7 +29298,7 @@ app64.use("/vnc-popout.html", logViewerFetch);
29047
29298
  app64.get("/vnc-popout.html", (c) => {
29048
29299
  let html = htmlCache.get("vnc-popout.html");
29049
29300
  if (!html) {
29050
- html = readFileSync42(resolve37(process.cwd(), "public", "vnc-popout.html"), "utf-8");
29301
+ html = readFileSync42(resolve38(process.cwd(), "public", "vnc-popout.html"), "utf-8");
29051
29302
  const name = escapeHtml(BRAND.productName);
29052
29303
  html = html.replace("<title>Browser \u2014 Maxy</title>", `<title>${name}</title>`);
29053
29304
  html = html.replace("</head>", ` ${brandScript}
@@ -29184,8 +29435,8 @@ var hostname = process.env.HOSTNAME ?? "127.0.0.1";
29184
29435
  var httpServer = serve({ fetch: app64.fetch, port, hostname });
29185
29436
  console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
29186
29437
  {
29187
- const reconcilePlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join44(__dirname, "..");
29188
- const reconcileScript = resolve37(reconcilePlatformRoot, "plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js");
29438
+ const reconcilePlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join45(__dirname, "..");
29439
+ const reconcileScript = resolve38(reconcilePlatformRoot, "plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js");
29189
29440
  const RECONCILE_INTERVAL_MS = 12e4;
29190
29441
  const runReconcile = (ctx) => {
29191
29442
  if (!existsSync38(reconcileScript)) return;
@@ -29210,8 +29461,8 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
29210
29461
  });
29211
29462
  }
29212
29463
  {
29213
- const outlookPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join44(__dirname, "..");
29214
- const outlookScript = resolve37(outlookPlatformRoot, "plugins/outlook/mcp/dist/scripts/complete-registration.js");
29464
+ const outlookPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join45(__dirname, "..");
29465
+ const outlookScript = resolve38(outlookPlatformRoot, "plugins/outlook/mcp/dist/scripts/complete-registration.js");
29215
29466
  const OUTLOOK_COMPLETE_INTERVAL_MS = 3e4;
29216
29467
  const runOutlookComplete = (ctx) => {
29217
29468
  if (!existsSync38(outlookScript)) return;
@@ -29236,8 +29487,8 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
29236
29487
  });
29237
29488
  }
29238
29489
  {
29239
- const auditRoot = process.env.MAXY_PLATFORM_ROOT ?? join44(__dirname, "..");
29240
- const strandedAccountsDir = resolve37(auditRoot, "..", "data/accounts");
29490
+ const auditRoot = process.env.MAXY_PLATFORM_ROOT ?? join45(__dirname, "..");
29491
+ const strandedAccountsDir = resolve38(auditRoot, "..", "data/accounts");
29241
29492
  const STRANDED_AUDIT_INTERVAL_MS = 3e5;
29242
29493
  const STRANDED_AGE_MS = 16 * 6e4;
29243
29494
  const STRANDED_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -29247,7 +29498,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
29247
29498
  const now = Date.now();
29248
29499
  for (const name of readdirSync26(strandedAccountsDir)) {
29249
29500
  if (!STRANDED_UUID_RE.test(name)) continue;
29250
- const pendingPath2 = resolve37(strandedAccountsDir, name, "secrets/outlook/pending-devicecode.enc");
29501
+ const pendingPath2 = resolve38(strandedAccountsDir, name, "secrets/outlook/pending-devicecode.enc");
29251
29502
  if (!existsSync38(pendingPath2)) continue;
29252
29503
  const ageMs = now - statSync17(pendingPath2).mtimeMs;
29253
29504
  if (ageMs > STRANDED_AGE_MS) {
@@ -29266,8 +29517,8 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
29266
29517
  });
29267
29518
  }
29268
29519
  {
29269
- const googleRoot = process.env.MAXY_PLATFORM_ROOT ?? join44(__dirname, "..");
29270
- const googleAccountsDir = resolve37(googleRoot, "..", "data/accounts");
29520
+ const googleRoot = process.env.MAXY_PLATFORM_ROOT ?? join45(__dirname, "..");
29521
+ const googleAccountsDir = resolve38(googleRoot, "..", "data/accounts");
29271
29522
  const GOOGLE_PENDING_AUDIT_INTERVAL_MS = 3e5;
29272
29523
  const runGooglePendingAuditSafe = () => {
29273
29524
  try {
@@ -29282,7 +29533,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
29282
29533
  firstRunDelayMs: 27e3,
29283
29534
  run: runGooglePendingAuditSafe
29284
29535
  });
29285
- const googleAuditScript = resolve37(googleRoot, "plugins/google/mcp/dist/scripts/account-audit.js");
29536
+ const googleAuditScript = resolve38(googleRoot, "plugins/google/mcp/dist/scripts/account-audit.js");
29286
29537
  const GOOGLE_ACCOUNT_AUDIT_INTERVAL_MS = 36e5;
29287
29538
  const runGoogleAccountAudit = (ctx) => {
29288
29539
  if (!existsSync38(googleAuditScript)) return;
@@ -29310,7 +29561,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
29310
29561
  });
29311
29562
  }
29312
29563
  {
29313
- const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join44(__dirname, "..");
29564
+ const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join45(__dirname, "..");
29314
29565
  const STORAGE_AUDIT_INTERVAL_MS = 3e5;
29315
29566
  const runStorageAuditSafe = () => runStorageAudit(auditPlatformRoot).catch((err) => {
29316
29567
  console.error(`[storage-audit] error="${err.message}"`);
@@ -29344,8 +29595,8 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
29344
29595
  });
29345
29596
  }
29346
29597
  {
29347
- const publishPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join44(__dirname, "..");
29348
- const publishScript = resolve37(publishPlatformRoot, "plugins/scheduling/mcp/dist/scripts/publish-availability.js");
29598
+ const publishPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join45(__dirname, "..");
29599
+ const publishScript = resolve38(publishPlatformRoot, "plugins/scheduling/mcp/dist/scripts/publish-availability.js");
29349
29600
  const PUBLISH_INTERVAL_MS = 3e5;
29350
29601
  const currentAccount = () => {
29351
29602
  try {
@@ -29375,7 +29626,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
29375
29626
  }
29376
29627
  };
29377
29628
  const auditSnapshotAge = (account) => {
29378
- const availPath = resolve37(account.accountDir, "calendar-availability.json");
29629
+ const availPath = resolve38(account.accountDir, "calendar-availability.json");
29379
29630
  let hasBookingSite = false;
29380
29631
  try {
29381
29632
  if (existsSync38(availPath)) {
@@ -29385,7 +29636,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
29385
29636
  } catch {
29386
29637
  }
29387
29638
  if (!hasBookingSite) return;
29388
- const statePath = resolve37(account.accountDir, "state", "booking-availability", "last-publish.json");
29639
+ const statePath = resolve38(account.accountDir, "state", "booking-availability", "last-publish.json");
29389
29640
  let lastSuccessAt = null;
29390
29641
  if (existsSync38(statePath)) {
29391
29642
  try {
@@ -29417,8 +29668,8 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
29417
29668
  startTimeEntryCensus(() => getSession());
29418
29669
  startLedgerCensus(() => getSession());
29419
29670
  {
29420
- const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join44(__dirname, "..");
29421
- const auditScript = resolve37(auditPlatformRoot, "plugins/connector/mcp/dist/scripts/audit-connectors.js");
29671
+ const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join45(__dirname, "..");
29672
+ const auditScript = resolve38(auditPlatformRoot, "plugins/connector/mcp/dist/scripts/audit-connectors.js");
29422
29673
  const auditLogDir = process.env.LOG_DIR ?? LOG_DIR;
29423
29674
  const CONNECTOR_AUDIT_INTERVAL_MS = 3e5;
29424
29675
  const runConnectorAudit = (ctx) => {
@@ -29727,9 +29978,13 @@ if (bootAccountConfig?.whatsapp) {
29727
29978
  }
29728
29979
  init({
29729
29980
  configDir: configDirForWhatsApp,
29730
- platformRoot: resolve37(process.env.MAXY_PLATFORM_ROOT ?? join44(__dirname, "..")),
29981
+ platformRoot: resolve38(process.env.MAXY_PLATFORM_ROOT ?? join45(__dirname, "..")),
29731
29982
  accountConfig: bootAccountConfig,
29732
29983
  onMessage: async (msg) => {
29984
+ if (isObserveAccount(msg.accountId)) {
29985
+ console.error(`[whatsapp:observe] op=BREACH account=${msg.accountId} sender=${msg.senderPhone} \u2014 dispatch reached the router from an observe socket`);
29986
+ return;
29987
+ }
29733
29988
  if (msg.isOwnerMirror) {
29734
29989
  console.error(`[whatsapp:route] skipped reason=owner-mirror senderId=${msg.senderPhone}`);
29735
29990
  return;