@rubytech/create-maxy-code 0.1.386 → 0.1.388

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 (110) hide show
  1. package/dist/__tests__/cron-heartbeat-registration.test.js +5 -0
  2. package/dist/cron-registration.js +1 -0
  3. package/dist/index.js +4 -0
  4. package/package.json +1 -1
  5. package/payload/platform/neo4j/schema.cypher +36 -1
  6. package/payload/platform/plugins/.claude-plugin/marketplace.json +5 -0
  7. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +61 -1
  8. package/payload/platform/plugins/docs/references/graph.md +11 -0
  9. package/payload/platform/plugins/docs/references/plugins-guide.md +1 -0
  10. package/payload/platform/plugins/docs/references/sweep-guide.md +43 -0
  11. package/payload/platform/plugins/memory/references/schema-estate-agent.md +34 -0
  12. package/payload/platform/plugins/scheduling/PLUGIN.md +17 -1
  13. package/payload/platform/plugins/scheduling/mcp/dist/index.js +12 -0
  14. package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
  15. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/agent-dispatch.test.d.ts +2 -0
  16. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/agent-dispatch.test.d.ts.map +1 -0
  17. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/agent-dispatch.test.js +101 -0
  18. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/agent-dispatch.test.js.map +1 -0
  19. package/payload/platform/plugins/scheduling/mcp/dist/lib/agent-dispatch.d.ts +54 -0
  20. package/payload/platform/plugins/scheduling/mcp/dist/lib/agent-dispatch.d.ts.map +1 -0
  21. package/payload/platform/plugins/scheduling/mcp/dist/lib/agent-dispatch.js +117 -0
  22. package/payload/platform/plugins/scheduling/mcp/dist/lib/agent-dispatch.js.map +1 -0
  23. package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-classify.d.ts +7 -0
  24. package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-classify.d.ts.map +1 -1
  25. package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-classify.js +6 -3
  26. package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-classify.js.map +1 -1
  27. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/agent-turn-dispatch.test.d.ts +2 -0
  28. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/agent-turn-dispatch.test.d.ts.map +1 -0
  29. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/agent-turn-dispatch.test.js +88 -0
  30. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/agent-turn-dispatch.test.js.map +1 -0
  31. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/standing-audit.test.d.ts +2 -0
  32. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/standing-audit.test.d.ts.map +1 -0
  33. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/standing-audit.test.js +77 -0
  34. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/standing-audit.test.js.map +1 -0
  35. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts +59 -0
  36. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts.map +1 -0
  37. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js +131 -0
  38. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js.map +1 -0
  39. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +62 -15
  40. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
  41. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-event-agent.test.d.ts +2 -0
  42. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-event-agent.test.d.ts.map +1 -0
  43. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-event-agent.test.js +110 -0
  44. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-event-agent.test.js.map +1 -0
  45. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-get-agent.test.d.ts +2 -0
  46. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-get-agent.test.d.ts.map +1 -0
  47. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-get-agent.test.js +35 -0
  48. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-get-agent.test.js.map +1 -0
  49. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-agent.test.d.ts +2 -0
  50. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-agent.test.d.ts.map +1 -0
  51. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-agent.test.js +81 -0
  52. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-update-agent.test.js.map +1 -0
  53. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts +7 -0
  54. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts.map +1 -1
  55. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js +21 -4
  56. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js.map +1 -1
  57. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.d.ts +4 -0
  58. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.d.ts.map +1 -1
  59. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.js +6 -0
  60. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.js.map +1 -1
  61. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.d.ts +2 -1
  62. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.d.ts.map +1 -1
  63. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js +32 -1
  64. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js.map +1 -1
  65. package/payload/platform/plugins/sweep/.claude-plugin/plugin.json +8 -0
  66. package/payload/platform/plugins/sweep/PLUGIN.md +15 -0
  67. package/payload/platform/plugins/sweep/skills/sweep-workflow/SKILL.md +113 -0
  68. package/payload/platform/plugins/whatsapp/PLUGIN.md +1 -1
  69. package/payload/platform/plugins/whatsapp/mcp/dist/index.js +19 -4
  70. package/payload/platform/plugins/whatsapp/mcp/dist/index.js.map +1 -1
  71. package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +9 -4
  72. package/payload/platform/plugins/whatsapp/skills/manage-whatsapp-config/SKILL.md +12 -0
  73. package/payload/platform/scripts/seed-neo4j.sh +38 -0
  74. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +5 -0
  75. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
  76. package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
  77. package/payload/platform/services/whatsapp-channel/dist/server.js +1 -1
  78. package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
  79. package/payload/server/public/assets/{AdminLoginScreens-oxqHKL3C.js → AdminLoginScreens-BejIjbmU.js} +1 -1
  80. package/payload/server/public/assets/{AdminShell-DBD6ruS1.js → AdminShell-D2-uBSB5.js} +1 -1
  81. package/payload/server/public/assets/{Checkbox-yr8-ftrE.js → Checkbox-1F1tzqca.js} +1 -1
  82. package/payload/server/public/assets/Transcript-DkGa4CQH.js +2 -0
  83. package/payload/server/public/assets/{admin-CQfZnuFy.js → admin-DqQARkjy.js} +1 -1
  84. package/payload/server/public/assets/{browser-DbdPeDK1.js → browser-nDtEK6RC.js} +1 -1
  85. package/payload/server/public/assets/{calendar-CFdOhe7Q.js → calendar-CO4Bwmho.js} +1 -1
  86. package/payload/server/public/assets/chat-DeIge_bC.js +1 -0
  87. package/payload/server/public/assets/chevron-left-DhVdq0aN.js +1 -0
  88. package/payload/server/public/assets/data-B1GIdzHk.js +1 -0
  89. package/payload/server/public/assets/{graph-Bluocsf_.js → graph-DFyicKd7.js} +1 -1
  90. package/payload/server/public/assets/{graph-labels-BcLJmY-v.js → graph-labels-D3BQdcpD.js} +1 -1
  91. package/payload/server/public/assets/{operator-DjK3MBSS.js → operator-BxrjWdT9.js} +1 -1
  92. package/payload/server/public/assets/{page-DQNYTb2d.js → page-ByDLq_o1.js} +1 -1
  93. package/payload/server/public/assets/{page-zJQTRXB2.js → page-D-Ep4bXd.js} +1 -1
  94. package/payload/server/public/assets/{public-CHCniosp.js → public-DbdqfdYq.js} +1 -1
  95. package/payload/server/public/assets/{rotate-ccw-JDfwjfIL.js → rotate-ccw-BkX8HODe.js} +1 -1
  96. package/payload/server/public/assets/{useSubAccountSwitcher-CfI3J2IX.css → useSubAccountSwitcher-DS0iqSkP.css} +1 -1
  97. package/payload/server/public/browser.html +4 -4
  98. package/payload/server/public/calendar.html +5 -5
  99. package/payload/server/public/chat.html +8 -8
  100. package/payload/server/public/data.html +7 -7
  101. package/payload/server/public/graph.html +8 -8
  102. package/payload/server/public/index.html +10 -10
  103. package/payload/server/public/operator.html +10 -10
  104. package/payload/server/public/public.html +8 -8
  105. package/payload/server/server.js +713 -331
  106. package/payload/server/public/assets/Transcript-FX_w6no6.js +0 -2
  107. package/payload/server/public/assets/chat-BunxcNDk.js +0 -1
  108. package/payload/server/public/assets/chevron-left-Cngmu0Py.js +0 -1
  109. package/payload/server/public/assets/data-IqZlMSwG.js +0 -1
  110. /package/payload/server/public/assets/{useSubAccountSwitcher-BpQ3oKiJ.js → useSubAccountSwitcher-DMbRhLPv.js} +0 -0
@@ -1307,7 +1307,7 @@ var serveStatic = (options = { root: "" }) => {
1307
1307
  // server/index.ts
1308
1308
  import { readFileSync as readFileSync35, existsSync as existsSync33, watchFile } from "fs";
1309
1309
  import { spawn as spawn3 } from "child_process";
1310
- import { resolve as resolve33, join as join34, basename as basename12 } from "path";
1310
+ import { resolve as resolve33, join as join34, basename as basename13 } from "path";
1311
1311
  import { homedir as homedir3 } from "os";
1312
1312
  import { monitorEventLoopDelay } from "perf_hooks";
1313
1313
 
@@ -1726,7 +1726,8 @@ var WhatsAppAccountSchema = z.object({
1726
1726
  var WhatsAppConfigSchema = z.object({
1727
1727
  accounts: z.record(z.string(), WhatsAppAccountSchema.optional()).optional().describe("Per-account config keyed by account ID."),
1728
1728
  dmPolicy: DmPolicySchema.optional().default("disabled").describe("Who can message your public agent via WhatsApp DM. 'Open' lets anyone message. 'Allowlist' restricts to approved phone numbers. 'Disabled' blocks all public messages."),
1729
- adminPhones: z.array(z.string()).optional().describe("Phone numbers (E.164) that route to the admin agent. Managed via add-admin-phone / remove-admin-phone."),
1729
+ adminPhones: z.array(z.string()).optional().describe("Phone numbers (E.164) that route to the admin agent. Managed via add-admin-phone / remove-admin-phone. Authoritative only on the account that owns the WhatsApp paired socket; a list on any other account is inert and purged at boot \u2014 use accountManagers for sub-account routing."),
1730
+ accountManagers: z.record(z.string(), z.string()).optional().describe("Account-manager bindings: E.164 phone \u2192 sub-account UUID. A designated phone spawns an admin session scoped to that sub-account rather than the house. Managed via add-account-manager / remove-account-manager. A phone here must not also be in adminPhones."),
1730
1731
  allowFrom: z.array(z.string()).optional().describe("Phone numbers allowed to message the public agent when DM policy is 'allowlist'. Add '*' for open access. Does not affect admin routing \u2014 use adminPhones for that."),
1731
1732
  sendReadReceipts: z.boolean().optional().default(true).describe("Whether to send read receipts (blue ticks) when messages are received. Disabling may feel less responsive but preserves privacy."),
1732
1733
  mediaMaxMb: z.number().int().positive().optional().default(50).describe("Maximum file size in MB for media the agent will download and process."),
@@ -2601,13 +2602,24 @@ function extractMessage(msg) {
2601
2602
  return result;
2602
2603
  }
2603
2604
 
2605
+ // app/lib/whatsapp/managed-account.ts
2606
+ function managedAccountFor(accountManagers, phone) {
2607
+ for (const [managerPhone, subAccountId] of Object.entries(accountManagers)) {
2608
+ if (subAccountId && phonesMatch(managerPhone, phone)) return subAccountId;
2609
+ }
2610
+ return null;
2611
+ }
2612
+
2604
2613
  // app/lib/whatsapp/inbound/access-control.ts
2605
2614
  function resolveAccountConfig(config, accountConfig) {
2606
2615
  return {
2607
2616
  dmPolicy: accountConfig?.dmPolicy ?? config.dmPolicy ?? "disabled",
2608
2617
  adminPhones: accountConfig?.adminPhones ?? config.adminPhones ?? [],
2609
2618
  allowFrom: accountConfig?.allowFrom ?? config.allowFrom ?? [],
2610
- selfChatMode: accountConfig?.selfChatMode ?? false
2619
+ selfChatMode: accountConfig?.selfChatMode ?? false,
2620
+ // Account-manager bindings are a house-level routing table only — never a
2621
+ // per-account override, so this reads the top-level map unconditionally.
2622
+ accountManagers: config.accountManagers ?? {}
2611
2623
  };
2612
2624
  }
2613
2625
  function isAdminPhone(phone, adminPhones) {
@@ -2616,24 +2628,31 @@ function isAdminPhone(phone, adminPhones) {
2616
2628
  return adminPhones.some((entry) => phonesMatch(entry, normalized));
2617
2629
  }
2618
2630
  function checkDmAccess(params) {
2619
- const { senderPhone, selfPhone } = params;
2631
+ const { senderPhone, selfPhone, accountId } = params;
2620
2632
  const cfg = resolveAccountConfig(params.config, params.accountConfig);
2621
2633
  if (phonesMatch(senderPhone, selfPhone)) {
2622
- return { allowed: true, reason: "self-phone", agentType: "admin" };
2634
+ return { allowed: true, reason: "self-phone", agentType: "admin", effectiveAccountId: accountId };
2623
2635
  }
2624
2636
  if (isAdminPhone(senderPhone, cfg.adminPhones)) {
2625
- return { allowed: true, reason: "admin-binding", agentType: "admin" };
2637
+ return { allowed: true, reason: "admin-binding", agentType: "admin", effectiveAccountId: accountId };
2638
+ }
2639
+ const managedSub = managedAccountFor(cfg.accountManagers, senderPhone);
2640
+ if (managedSub) {
2641
+ if (params.isValidAccount && !params.isValidAccount(managedSub)) {
2642
+ return { allowed: false, reason: "account-manager-unresolved", agentType: "admin" };
2643
+ }
2644
+ return { allowed: true, reason: "account-manager", agentType: "admin", effectiveAccountId: managedSub };
2626
2645
  }
2627
2646
  switch (cfg.dmPolicy) {
2628
2647
  case "open":
2629
- return { allowed: true, reason: "dm-policy-open", agentType: "public" };
2648
+ return { allowed: true, reason: "dm-policy-open", agentType: "public", effectiveAccountId: accountId };
2630
2649
  case "allowlist": {
2631
2650
  const inList = cfg.allowFrom.some((entry) => {
2632
2651
  if (entry === "*") return true;
2633
2652
  return phonesMatch(entry, senderPhone);
2634
2653
  });
2635
2654
  if (inList) {
2636
- return { allowed: true, reason: "allowlist-match", agentType: "public" };
2655
+ return { allowed: true, reason: "allowlist-match", agentType: "public", effectiveAccountId: accountId };
2637
2656
  }
2638
2657
  return { allowed: false, reason: "not-in-allowlist", agentType: "public" };
2639
2658
  }
@@ -4085,7 +4104,9 @@ function buildSelfChatDispatch(input) {
4085
4104
  senderPhone,
4086
4105
  isGroup: false
4087
4106
  }),
4088
- media: []
4107
+ media: [],
4108
+ // Self-chat is the owner: the session scopes to the house account (Task 1383).
4109
+ effectiveAccountId: input.accountId
4089
4110
  };
4090
4111
  }
4091
4112
  async function init(opts) {
@@ -4733,12 +4754,24 @@ async function handleInboundMessage(conn, msg) {
4733
4754
  config: whatsAppConfig,
4734
4755
  accountConfig: whatsAppConfig.accounts?.[conn.accountId],
4735
4756
  accountId: conn.accountId,
4757
+ isValidAccount: (id) => listValidAccounts().some((a) => a.accountId === id),
4736
4758
  resolvePersonByPhone
4737
4759
  });
4738
4760
  }
4739
4761
  console.error(
4740
4762
  `${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}` : "")
4741
4763
  );
4764
+ if (!accessResult.allowed && accessResult.reason === "account-manager-unresolved") {
4765
+ console.error(
4766
+ `[whatsapp-native] op=account-manager-reject senderId=${senderPhone} reason=unresolved-effective-account`
4767
+ );
4768
+ }
4769
+ if (accessResult.allowed && accessResult.reason === "account-manager" && accessResult.effectiveAccountId === conn.accountId) {
4770
+ console.error(
4771
+ `[whatsapp-native] op=escalation-tripwire senderId=${senderPhone} reason=account-manager-routed-to-house effectiveAccount=${accessResult.effectiveAccountId}`
4772
+ );
4773
+ return;
4774
+ }
4742
4775
  if (!accessResult.allowed) return;
4743
4776
  if (isCustomerTurnSuppressed(accessResult.agentType, isHouseManagedService())) {
4744
4777
  console.error(
@@ -4786,7 +4819,10 @@ async function handleInboundMessage(conn, msg) {
4786
4819
  composing,
4787
4820
  cacheKey,
4788
4821
  media: mediaResult?.path ? [{ path: mediaResult.path, type: extracted.mediaType, mimetype: mediaResult.mimetype }] : [],
4789
- replyContext: extracted.quotedMessage
4822
+ replyContext: extracted.quotedMessage,
4823
+ // Task 1383 — the gate's single resolution feeds the spawn (house or the
4824
+ // bound sub-account); the router never re-resolves.
4825
+ effectiveAccountId: accessResult.effectiveAccountId
4790
4826
  };
4791
4827
  if (accessResult.agentType === "public") {
4792
4828
  const hoursResult = await isBusinessOpen(conn.accountId);
@@ -5971,6 +6007,137 @@ ${result.result.text}` : result.result.text;
5971
6007
  return app56;
5972
6008
  }
5973
6009
 
6010
+ // app/lib/channel-pty-bridge/admin-session-id.ts
6011
+ import { createHash } from "crypto";
6012
+ function adminSessionIdFor(accountId, senderId, personId) {
6013
+ const subject = personId && personId.length > 0 ? `person:${personId}` : senderId;
6014
+ const b = createHash("sha256").update(`${accountId}:admin:${subject}`).digest().subarray(0, 16);
6015
+ const v = Buffer.from(b);
6016
+ v[6] = v[6] & 15 | 64;
6017
+ v[8] = v[8] & 63 | 128;
6018
+ const h = v.toString("hex");
6019
+ return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20, 32)}`;
6020
+ }
6021
+
6022
+ // server/routes/channel/schedule-inject.ts
6023
+ var TAG16 = "[schedule-inject]";
6024
+ function isLoopbackAddr(addr) {
6025
+ return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
6026
+ }
6027
+ function createScheduleInjectRoutes(deps) {
6028
+ const app56 = new Hono();
6029
+ app56.post("/", async (c) => {
6030
+ const env = c.env;
6031
+ const remoteAddr = env?.incoming?.socket?.remoteAddress ?? "";
6032
+ if (!isLoopbackAddr(remoteAddr)) {
6033
+ console.error(`${TAG16} reject reason=non-loopback remoteAddr=${remoteAddr}`);
6034
+ return c.json({ ok: false, error: "schedule-inject-loopback-only" }, 403);
6035
+ }
6036
+ const xffHeader = env?.incoming?.headers?.["x-forwarded-for"];
6037
+ const xffRaw = Array.isArray(xffHeader) ? xffHeader.join(",") : typeof xffHeader === "string" ? xffHeader : "";
6038
+ if (xffRaw.length > 0) {
6039
+ const offender = xffRaw.split(",").map((t) => t.trim()).filter(Boolean).find((t) => !isLoopbackAddr(t));
6040
+ if (offender !== void 0) {
6041
+ console.error(`${TAG16} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
6042
+ return c.json({ ok: false, error: "schedule-inject-loopback-only" }, 403);
6043
+ }
6044
+ }
6045
+ let body;
6046
+ try {
6047
+ body = await c.req.json();
6048
+ } catch {
6049
+ return c.json({ ok: false, error: "invalid-json" }, 400);
6050
+ }
6051
+ const channel = body.channel;
6052
+ const destination = typeof body.destination === "string" ? body.destination : "";
6053
+ const prompt = typeof body.prompt === "string" ? body.prompt : "";
6054
+ const eventId = typeof body.eventId === "string" ? body.eventId : "";
6055
+ if (channel !== "whatsapp" && channel !== "telegram" || !destination || !prompt) {
6056
+ console.error(`${TAG16} reject reason=bad-request channel=${String(channel)} hasDestination=${Boolean(destination)} hasPrompt=${Boolean(prompt)}`);
6057
+ return c.json({ ok: false, error: "schedule-inject-bad-request" }, 400);
6058
+ }
6059
+ const account = deps.resolveAccount();
6060
+ if (!account) {
6061
+ console.error(`${TAG16} reject reason=no-account eventId=${eventId}`);
6062
+ return c.json({ ok: false, error: "no-account" }, 500);
6063
+ }
6064
+ const houseAccountId = account.accountId;
6065
+ console.error(`${TAG16} op=received eventId=${eventId} channel=${channel} destination=${destination} account=${houseAccountId}`);
6066
+ if (channel === "whatsapp") {
6067
+ const effectiveAccount2 = deps.effectiveAccountFor(houseAccountId, account.accountDir, destination);
6068
+ console.error(`${TAG16} op=effective-account eventId=${eventId} effectiveAccount=${effectiveAccount2}`);
6069
+ const sessionId2 = adminSessionIdFor(effectiveAccount2, destination);
6070
+ const reply2 = (text) => deps.sendWhatsAppText(houseAccountId, destination, text);
6071
+ try {
6072
+ await deps.waHandleInbound({
6073
+ accountId: houseAccountId,
6074
+ // Task 1383 — the router (ensureChannelSession) no longer re-resolves
6075
+ // the accountManagers map; it consumes this value. The scheduler
6076
+ // supplies its own resolution (effectiveAccountFor, computed above) so
6077
+ // a manager destination scopes the spawned session to the sub-account,
6078
+ // exactly as a real inbound now does via the gate.
6079
+ effectiveAccountId: effectiveAccount2,
6080
+ senderId: destination,
6081
+ role: "admin",
6082
+ personId: null,
6083
+ text: prompt,
6084
+ media: [],
6085
+ source: "schedule",
6086
+ reply: reply2
6087
+ });
6088
+ } catch (err) {
6089
+ console.error(`${TAG16} op=inject-spawn eventId=${eventId} sessionId=${sessionId2} result=inject-error status=${err instanceof Error ? err.message : String(err)}`);
6090
+ return c.json({ ok: false, error: "inject-error" }, 502);
6091
+ }
6092
+ console.error(`${TAG16} op=inject-spawn eventId=${eventId} sessionId=${sessionId2} result=ok status=-`);
6093
+ return c.json({ ok: true }, 200);
6094
+ }
6095
+ const effectiveAccount = houseAccountId;
6096
+ console.error(`${TAG16} op=effective-account eventId=${eventId} effectiveAccount=${effectiveAccount}`);
6097
+ const botToken = account.config.telegram?.adminBotToken;
6098
+ if (!botToken) {
6099
+ console.error(`${TAG16} reject reason=no-admin-bot-token eventId=${eventId}`);
6100
+ return c.json({ ok: false, error: "no-admin-bot-token" }, 400);
6101
+ }
6102
+ const chatId = Number(destination);
6103
+ const sessionId = adminSessionIdFor(effectiveAccount, destination);
6104
+ const reply = (text) => deps.sendTelegramText(botToken, chatId, text);
6105
+ try {
6106
+ await deps.tgHandleInbound({
6107
+ accountId: houseAccountId,
6108
+ senderId: destination,
6109
+ chatId: destination,
6110
+ role: "admin",
6111
+ personId: null,
6112
+ text: prompt,
6113
+ source: "schedule",
6114
+ reply
6115
+ });
6116
+ } catch (err) {
6117
+ console.error(`${TAG16} op=inject-spawn eventId=${eventId} sessionId=${sessionId} result=inject-error status=${err instanceof Error ? err.message : String(err)}`);
6118
+ return c.json({ ok: false, error: "inject-error" }, 502);
6119
+ }
6120
+ console.error(`${TAG16} op=inject-spawn eventId=${eventId} sessionId=${sessionId} result=ok status=-`);
6121
+ return c.json({ ok: true }, 200);
6122
+ });
6123
+ return app56;
6124
+ }
6125
+
6126
+ // app/lib/telegram/outbound/send-text.ts
6127
+ async function sendTelegramText(botToken, chatId, text) {
6128
+ try {
6129
+ const res = await fetch(`https://api.telegram.org/bot${botToken}/sendMessage`, {
6130
+ method: "POST",
6131
+ headers: { "Content-Type": "application/json" },
6132
+ body: JSON.stringify({ chat_id: chatId, text })
6133
+ });
6134
+ const data = await res.json();
6135
+ return data.ok ? { ok: true } : { ok: false, error: data.description ?? "send failed" };
6136
+ } catch (e) {
6137
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
6138
+ }
6139
+ }
6140
+
5974
6141
  // server/routes/whatsapp.ts
5975
6142
  import { join as join10, resolve as resolve10 } from "path";
5976
6143
  import { readdirSync as readdirSync4, readFileSync as readFileSync11, existsSync as existsSync6 } from "fs";
@@ -5980,8 +6147,8 @@ import { randomUUID as randomUUID6 } from "crypto";
5980
6147
 
5981
6148
  // app/lib/whatsapp/config-persist.ts
5982
6149
  import { readFileSync as readFileSync10, writeFileSync as writeFileSync4, existsSync as existsSync5 } from "fs";
5983
- import { resolve as resolve8, join as join8 } from "path";
5984
- var TAG16 = "[whatsapp:config]";
6150
+ import { resolve as resolve8, join as join8, dirname as dirname2, basename as basename2 } from "path";
6151
+ var TAG17 = "[whatsapp:config]";
5985
6152
  function configPath(accountDir) {
5986
6153
  return resolve8(accountDir, "account.json");
5987
6154
  }
@@ -5998,9 +6165,9 @@ function reloadManagerConfig(accountDir) {
5998
6165
  try {
5999
6166
  const config = readConfig(accountDir);
6000
6167
  reloadConfig(config);
6001
- console.error(`${TAG16} reloaded manager config`);
6168
+ console.error(`${TAG17} reloaded manager config`);
6002
6169
  } catch (err) {
6003
- console.error(`${TAG16} manager config reload failed: ${String(err)}`);
6170
+ console.error(`${TAG17} manager config reload failed: ${String(err)}`);
6004
6171
  }
6005
6172
  }
6006
6173
  var E164_PATTERN = /^\+\d{7,15}$/;
@@ -6026,25 +6193,25 @@ function persistAfterPairing(accountDir, accountId, selfPhone) {
6026
6193
  const adminPhones = wa.adminPhones;
6027
6194
  if (!adminPhones.includes(normalized)) {
6028
6195
  adminPhones.push(normalized);
6029
- console.error(`${TAG16} added selfPhone=${normalized} to adminPhones`);
6196
+ console.error(`${TAG17} added selfPhone=${normalized} to adminPhones`);
6030
6197
  }
6031
6198
  } else {
6032
- console.error(`${TAG16} skipping adminPhones \u2014 selfPhone is null account=${accountId}`);
6199
+ console.error(`${TAG17} skipping adminPhones \u2014 selfPhone is null account=${accountId}`);
6033
6200
  }
6034
6201
  const parsed = WhatsAppConfigSchema.safeParse(wa);
6035
6202
  if (!parsed.success) {
6036
6203
  const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
6037
- console.error(`${TAG16} validation failed after pairing: ${msg}`);
6204
+ console.error(`${TAG17} validation failed after pairing: ${msg}`);
6038
6205
  return { ok: false, error: `Validation failed: ${msg}` };
6039
6206
  }
6040
6207
  config.whatsapp = parsed.data;
6041
6208
  writeConfig(accountDir, config);
6042
- console.error(`${TAG16} persisted after pairing account=${accountId} phone=${selfPhone ?? "null"}`);
6209
+ console.error(`${TAG17} persisted after pairing account=${accountId} phone=${selfPhone ?? "null"}`);
6043
6210
  reloadManagerConfig(accountDir);
6044
6211
  return { ok: true };
6045
6212
  } catch (err) {
6046
6213
  const msg = err instanceof Error ? err.message : String(err);
6047
- console.error(`${TAG16} persist failed account=${accountId}: ${msg}`);
6214
+ console.error(`${TAG17} persist failed account=${accountId}: ${msg}`);
6048
6215
  return { ok: false, error: msg };
6049
6216
  }
6050
6217
  }
@@ -6055,6 +6222,16 @@ function addAdminPhone(accountDir, phone) {
6055
6222
  }
6056
6223
  try {
6057
6224
  const config = readConfig(accountDir);
6225
+ const targetId = typeof config.accountId === "string" && config.accountId ? config.accountId : basename2(resolve8(accountDir));
6226
+ const socketOwner = resolveHouseOrSoleAccountId(dirname2(resolve8(accountDir)));
6227
+ if (targetId !== socketOwner) {
6228
+ console.error(`${TAG17} add-admin-phone rejected accountId=${targetId} reason=not-socket-account`);
6229
+ return {
6230
+ ok: false,
6231
+ reason: "not-socket-account",
6232
+ error: `${targetId} does not own the WhatsApp paired socket, so its admin phones are never consulted. To route a phone to a sub-account, bind it as an account manager (add-account-manager) instead.`
6233
+ };
6234
+ }
6058
6235
  if (!config.whatsapp || typeof config.whatsapp !== "object") {
6059
6236
  config.whatsapp = {};
6060
6237
  }
@@ -6066,6 +6243,9 @@ function addAdminPhone(accountDir, phone) {
6066
6243
  if (adminPhones.includes(normalized)) {
6067
6244
  return { ok: true, message: `Phone ${normalized} is already in the admin list.` };
6068
6245
  }
6246
+ if (managedAccountFor(readAccountManagers(accountDir), normalized)) {
6247
+ return { ok: false, error: `${normalized} is bound as an account manager. Remove the account-manager binding before adding it as a house admin \u2014 a phone is a house admin or a sub-account manager, never both.` };
6248
+ }
6069
6249
  adminPhones.push(normalized);
6070
6250
  const parsed = WhatsAppConfigSchema.safeParse(wa);
6071
6251
  if (!parsed.success) {
@@ -6074,12 +6254,12 @@ function addAdminPhone(accountDir, phone) {
6074
6254
  }
6075
6255
  config.whatsapp = parsed.data;
6076
6256
  writeConfig(accountDir, config);
6077
- console.error(`${TAG16} added admin phone=${normalized}`);
6257
+ console.error(`${TAG17} added admin phone=${normalized}`);
6078
6258
  reloadManagerConfig(accountDir);
6079
6259
  return { ok: true, message: `Added ${normalized} as admin phone. Messages from this number will route to the admin agent.` };
6080
6260
  } catch (err) {
6081
6261
  const msg = err instanceof Error ? err.message : String(err);
6082
- console.error(`${TAG16} addAdminPhone failed: ${msg}`);
6262
+ console.error(`${TAG17} addAdminPhone failed: ${msg}`);
6083
6263
  return { ok: false, error: msg };
6084
6264
  }
6085
6265
  }
@@ -6107,12 +6287,12 @@ function removeAdminPhone(accountDir, phone) {
6107
6287
  }
6108
6288
  config.whatsapp = parsed.data;
6109
6289
  writeConfig(accountDir, config);
6110
- console.error(`${TAG16} removed admin phone=${normalized}`);
6290
+ console.error(`${TAG17} removed admin phone=${normalized}`);
6111
6291
  reloadManagerConfig(accountDir);
6112
6292
  return { ok: true, message: `Removed ${normalized} from admin phones. Messages from this number will now route to the public agent.` };
6113
6293
  } catch (err) {
6114
6294
  const msg = err instanceof Error ? err.message : String(err);
6115
- console.error(`${TAG16} removeAdminPhone failed: ${msg}`);
6295
+ console.error(`${TAG17} removeAdminPhone failed: ${msg}`);
6116
6296
  return { ok: false, error: msg };
6117
6297
  }
6118
6298
  }
@@ -6127,6 +6307,139 @@ function readAdminPhones(accountDir) {
6127
6307
  return [];
6128
6308
  }
6129
6309
  }
6310
+ function purgeNonSocketAdminPhones(accountDir, socketOwnerId) {
6311
+ try {
6312
+ const config = readConfig(accountDir);
6313
+ const wa = config.whatsapp;
6314
+ if (!wa || typeof wa !== "object") return { removed: [] };
6315
+ const targetId = typeof config.accountId === "string" && config.accountId ? config.accountId : basename2(resolve8(accountDir));
6316
+ if (targetId === socketOwnerId) return { removed: [] };
6317
+ const adminPhones = Array.isArray(wa.adminPhones) ? wa.adminPhones.filter((p) => typeof p === "string") : [];
6318
+ if (adminPhones.length === 0) return { removed: [] };
6319
+ delete wa.adminPhones;
6320
+ writeConfig(accountDir, config);
6321
+ for (const phone of adminPhones) {
6322
+ console.error(`[admin-identity] op=purge-nonsocket-adminphone accountId=${targetId} phone=${phone} removed=true`);
6323
+ }
6324
+ return { removed: adminPhones };
6325
+ } catch (err) {
6326
+ console.error(`[admin-identity] purge-nonsocket-adminphone failed dir=${accountDir}: ${String(err)}`);
6327
+ return { removed: [] };
6328
+ }
6329
+ }
6330
+ function purgeNonSocketAdminPhonesAtBoot() {
6331
+ let socketOwner;
6332
+ try {
6333
+ socketOwner = resolveHouseOrSoleAccountId(ACCOUNTS_DIR);
6334
+ } catch (err) {
6335
+ console.error(`[admin-identity] purge-nonsocket-adminphone skipped \u2014 cannot resolve socket owner: ${String(err)}`);
6336
+ return;
6337
+ }
6338
+ let accounts;
6339
+ try {
6340
+ accounts = listValidAccounts();
6341
+ } catch {
6342
+ return;
6343
+ }
6344
+ for (const acct of accounts) {
6345
+ purgeNonSocketAdminPhones(acct.accountDir, socketOwner);
6346
+ }
6347
+ }
6348
+ function setAccountManager(accountDir, phone, subAccountId) {
6349
+ const normalized = phone.trim();
6350
+ if (!E164_PATTERN.test(normalized)) {
6351
+ return { ok: false, error: `Invalid phone format "${normalized}". Expected E.164 (e.g. +441234567890).` };
6352
+ }
6353
+ const sub = subAccountId.trim();
6354
+ if (!sub) {
6355
+ return { ok: false, error: "Missing sub-account id." };
6356
+ }
6357
+ let validIds;
6358
+ try {
6359
+ validIds = listValidAccounts().map((a) => a.accountId);
6360
+ } catch (err) {
6361
+ return { ok: false, error: `Could not read the account registry to validate the sub-account: ${err instanceof Error ? err.message : String(err)}. Try again.` };
6362
+ }
6363
+ if (!validIds.includes(sub)) {
6364
+ return { ok: false, error: `Sub-account "${sub}" is not in this install's account registry. Check the sub-account id and try again.` };
6365
+ }
6366
+ try {
6367
+ const config = readConfig(accountDir);
6368
+ if (!config.whatsapp || typeof config.whatsapp !== "object") {
6369
+ config.whatsapp = {};
6370
+ }
6371
+ const wa = config.whatsapp;
6372
+ const adminPhones = Array.isArray(wa.adminPhones) ? wa.adminPhones.filter((p) => typeof p === "string") : [];
6373
+ if (isAdminPhone(normalized, adminPhones)) {
6374
+ return {
6375
+ ok: false,
6376
+ error: `${normalized} is a house admin phone. Remove it from admin phones before binding it as an account manager \u2014 a phone is a house admin or a sub-account manager, never both.`
6377
+ };
6378
+ }
6379
+ if (!wa.accountManagers || typeof wa.accountManagers !== "object") {
6380
+ wa.accountManagers = {};
6381
+ }
6382
+ wa.accountManagers[normalized] = sub;
6383
+ const parsed = WhatsAppConfigSchema.safeParse(wa);
6384
+ if (!parsed.success) {
6385
+ const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
6386
+ return { ok: false, error: `Validation failed: ${msg}` };
6387
+ }
6388
+ config.whatsapp = parsed.data;
6389
+ writeConfig(accountDir, config);
6390
+ console.error(`${TAG17} bound account manager phone=${normalized} managesAccount=${sub}`);
6391
+ reloadManagerConfig(accountDir);
6392
+ return { ok: true, message: `Bound ${normalized} as account manager for sub-account ${sub}. Their WhatsApp messages will reach that sub-account's admin agent; replies still go out over this account's WhatsApp device.` };
6393
+ } catch (err) {
6394
+ const msg = err instanceof Error ? err.message : String(err);
6395
+ console.error(`${TAG17} setAccountManager failed: ${msg}`);
6396
+ return { ok: false, error: msg };
6397
+ }
6398
+ }
6399
+ function clearAccountManager(accountDir, phone) {
6400
+ const normalized = phone.trim();
6401
+ try {
6402
+ const config = readConfig(accountDir);
6403
+ const wa = config.whatsapp;
6404
+ if (!wa || typeof wa !== "object" || !wa.accountManagers || typeof wa.accountManagers !== "object") {
6405
+ return { ok: true, message: `No account-manager binding for ${normalized}.` };
6406
+ }
6407
+ const map = wa.accountManagers;
6408
+ const key = Object.keys(map).find((k) => phonesMatch(k, normalized));
6409
+ if (!key) {
6410
+ return { ok: true, message: `No account-manager binding for ${normalized}.` };
6411
+ }
6412
+ delete map[key];
6413
+ const parsed = WhatsAppConfigSchema.safeParse(wa);
6414
+ if (!parsed.success) {
6415
+ const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
6416
+ return { ok: false, error: `Validation failed: ${msg}` };
6417
+ }
6418
+ config.whatsapp = parsed.data;
6419
+ writeConfig(accountDir, config);
6420
+ console.error(`${TAG17} cleared account manager phone=${normalized}`);
6421
+ reloadManagerConfig(accountDir);
6422
+ return { ok: true, message: `Cleared the account-manager binding for ${normalized}.` };
6423
+ } catch (err) {
6424
+ const msg = err instanceof Error ? err.message : String(err);
6425
+ console.error(`${TAG17} clearAccountManager failed: ${msg}`);
6426
+ return { ok: false, error: msg };
6427
+ }
6428
+ }
6429
+ function readAccountManagers(accountDir) {
6430
+ try {
6431
+ const config = readConfig(accountDir);
6432
+ const wa = config.whatsapp;
6433
+ if (!wa || typeof wa.accountManagers !== "object" || wa.accountManagers === null || Array.isArray(wa.accountManagers)) return {};
6434
+ const out = {};
6435
+ for (const [k, v] of Object.entries(wa.accountManagers)) {
6436
+ if (typeof v === "string") out[k] = v;
6437
+ }
6438
+ return out;
6439
+ } catch {
6440
+ return {};
6441
+ }
6442
+ }
6130
6443
  function setPublicAgent(accountDir, slug) {
6131
6444
  const trimmed = slug.trim();
6132
6445
  if (!trimmed) {
@@ -6150,12 +6463,12 @@ function setPublicAgent(accountDir, slug) {
6150
6463
  }
6151
6464
  config.whatsapp = parsed.data;
6152
6465
  writeConfig(accountDir, config);
6153
- console.error(`${TAG16} publicAgent set to ${trimmed}`);
6466
+ console.error(`${TAG17} publicAgent set to ${trimmed}`);
6154
6467
  reloadManagerConfig(accountDir);
6155
6468
  return { ok: true, message: `Public agent set to "${trimmed}". WhatsApp messages from non-admin phones will be handled by this agent.` };
6156
6469
  } catch (err) {
6157
6470
  const msg = err instanceof Error ? err.message : String(err);
6158
- console.error(`${TAG16} setPublicAgent failed: ${msg}`);
6471
+ console.error(`${TAG17} setPublicAgent failed: ${msg}`);
6159
6472
  return { ok: false, error: msg };
6160
6473
  }
6161
6474
  }
@@ -6196,17 +6509,17 @@ function updateConfig(accountDir, fields) {
6196
6509
  const parsed = WhatsAppConfigSchema.safeParse(wa);
6197
6510
  if (!parsed.success) {
6198
6511
  const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
6199
- console.error(`${TAG16} update validation failed: ${msg}`);
6512
+ console.error(`${TAG17} update validation failed: ${msg}`);
6200
6513
  return { ok: false, error: `Validation failed: ${msg}` };
6201
6514
  }
6202
6515
  config.whatsapp = parsed.data;
6203
6516
  writeConfig(accountDir, config);
6204
- console.error(`${TAG16} updated fields=[${fieldNames.join(",")}]`);
6517
+ console.error(`${TAG17} updated fields=[${fieldNames.join(",")}]`);
6205
6518
  reloadManagerConfig(accountDir);
6206
6519
  return { ok: true, message: `Updated WhatsApp config: ${fieldNames.join(", ")}.` };
6207
6520
  } catch (err) {
6208
6521
  const msg = err instanceof Error ? err.message : String(err);
6209
- console.error(`${TAG16} updateConfig failed: ${msg}`);
6522
+ console.error(`${TAG17} updateConfig failed: ${msg}`);
6210
6523
  return { ok: false, error: msg };
6211
6524
  }
6212
6525
  }
@@ -6243,17 +6556,17 @@ function migrateRemovedConfigKeys(accountDir) {
6243
6556
  }
6244
6557
  writeConfig(accountDir, config);
6245
6558
  console.error(
6246
- `${TAG16} migration: stripped unknown keys=[${result.droppedKeys.join(",")}] from account.json`
6559
+ `${TAG17} migration: stripped unknown keys=[${result.droppedKeys.join(",")}] from account.json`
6247
6560
  );
6248
6561
  return { dropped: result.droppedKeys };
6249
6562
  } catch (err) {
6250
- console.error(`${TAG16} migration failed: ${String(err)}`);
6563
+ console.error(`${TAG17} migration failed: ${String(err)}`);
6251
6564
  return { dropped: [] };
6252
6565
  }
6253
6566
  }
6254
6567
 
6255
6568
  // app/lib/whatsapp/login.ts
6256
- var TAG17 = "[whatsapp:login]";
6569
+ var TAG18 = "[whatsapp:login]";
6257
6570
  function maskPhone(digits) {
6258
6571
  if (digits.length <= 4) return "*".repeat(digits.length);
6259
6572
  return digits.slice(0, 2) + "*".repeat(digits.length - 4) + digits.slice(-2);
@@ -6264,7 +6577,7 @@ function closeSocket(sock) {
6264
6577
  try {
6265
6578
  sock.ws?.close?.();
6266
6579
  } catch (err) {
6267
- console.warn(`${TAG17} socket close error during cleanup: ${String(err)}`);
6580
+ console.warn(`${TAG18} socket close error during cleanup: ${String(err)}`);
6268
6581
  }
6269
6582
  }
6270
6583
  function resetActiveLogin(accountId) {
@@ -6291,18 +6604,18 @@ async function runPostPairing(login) {
6291
6604
  const masked = selfPhone ? `${selfPhone.slice(0, 4)}***` : "null";
6292
6605
  console.error(`[whatsapp-persist] wa-persist-on-connect account=${login.accountId} phone=${masked}`);
6293
6606
  } else {
6294
- console.error(`${TAG17} wa-persist-on-connect FAILED account=${login.accountId}: ${result.error}`);
6607
+ console.error(`${TAG18} wa-persist-on-connect FAILED account=${login.accountId}: ${result.error}`);
6295
6608
  }
6296
6609
  } catch (err) {
6297
- console.error(`${TAG17} wa-persist-on-connect error account=${login.accountId}: ${String(err)}`);
6610
+ console.error(`${TAG18} wa-persist-on-connect error account=${login.accountId}: ${String(err)}`);
6298
6611
  }
6299
6612
  } else {
6300
- console.error(`${TAG17} wa-persist-on-connect skipped \u2014 no accountDir account=${login.accountId}`);
6613
+ console.error(`${TAG18} wa-persist-on-connect skipped \u2014 no accountDir account=${login.accountId}`);
6301
6614
  }
6302
6615
  try {
6303
6616
  await registerLoginSocket(login.accountId, login.sock, login.authDir);
6304
6617
  } catch (err) {
6305
- console.error(`${TAG17} registerLoginSocket failed account=${login.accountId}: ${String(err)}`);
6618
+ console.error(`${TAG18} registerLoginSocket failed account=${login.accountId}: ${String(err)}`);
6306
6619
  }
6307
6620
  }
6308
6621
  async function loginConnectionLoop(accountId, login) {
@@ -6311,7 +6624,7 @@ async function loginConnectionLoop(accountId, login) {
6311
6624
  const logTerminal = (outcome, errMsg) => {
6312
6625
  const fc = credsCensus(login.authDir);
6313
6626
  console.error(
6314
- `${TAG17} op=terminal cid=${cidShort} outcome=${outcome} credsFinal={registered=${fc.registered},account=${fc.hasAccountSignature},me=${fc.e164 ?? "null"}}` + (errMsg ? ` error="${errMsg}"` : "")
6627
+ `${TAG18} op=terminal cid=${cidShort} outcome=${outcome} credsFinal={registered=${fc.registered},account=${fc.hasAccountSignature},me=${fc.e164 ?? "null"}}` + (errMsg ? ` error="${errMsg}"` : "")
6315
6628
  );
6316
6629
  };
6317
6630
  while (true) {
@@ -6321,7 +6634,7 @@ async function loginConnectionLoop(accountId, login) {
6321
6634
  if (current?.id === login.id) {
6322
6635
  await runPostPairing(current);
6323
6636
  current.connected = true;
6324
- console.error(`${TAG17} loginConnectionLoop: connected account=${accountId} attempt=${attempt} configPersisted=${current.configPersisted}`);
6637
+ console.error(`${TAG18} loginConnectionLoop: connected account=${accountId} attempt=${attempt} configPersisted=${current.configPersisted}`);
6325
6638
  logTerminal("connected");
6326
6639
  }
6327
6640
  return;
@@ -6333,7 +6646,7 @@ async function loginConnectionLoop(accountId, login) {
6333
6646
  if (!preRetryCensus.hasAccountSignature) {
6334
6647
  current.error = "WhatsApp accepted the code but did not finish linking. This usually means the number has reached its linked-device limit or has a stale linked device. On the phone, open WhatsApp, Linked Devices, remove existing devices, then ask for a new code.";
6335
6648
  console.error(
6336
- `${TAG17} op=pairing-incomplete cid=${cidShort} closeKind=${closeKind(getStatusCode(err), false)} reason=no-account-signature`
6649
+ `${TAG18} op=pairing-incomplete cid=${cidShort} closeKind=${closeKind(getStatusCode(err), false)} reason=no-account-signature`
6337
6650
  );
6338
6651
  return;
6339
6652
  }
@@ -6341,7 +6654,7 @@ async function loginConnectionLoop(accountId, login) {
6341
6654
  if (!classification.shouldRetry || attempt >= LOGIN_MAX_RECONNECTS) {
6342
6655
  if (attempt >= LOGIN_MAX_RECONNECTS) {
6343
6656
  console.error(
6344
- `${TAG17} login reconnect attempts exhausted (${attempt}/${LOGIN_MAX_RECONNECTS}) \u2014 surfacing error to agent`
6657
+ `${TAG18} login reconnect attempts exhausted (${attempt}/${LOGIN_MAX_RECONNECTS}) \u2014 surfacing error to agent`
6345
6658
  );
6346
6659
  current.error = `Login failed after ${attempt} reconnect attempts: ${formatError(err)}`;
6347
6660
  } else {
@@ -6354,7 +6667,7 @@ async function loginConnectionLoop(accountId, login) {
6354
6667
  attempt++;
6355
6668
  const delay = LOGIN_RECONNECT_DELAYS[attempt - 1] ?? 8e3;
6356
6669
  console.error(
6357
- `${TAG17} status=${classification.statusCode ?? "unknown"} restart required \u2014 reconnecting with saved creds (attempt ${attempt}/${LOGIN_MAX_RECONNECTS}) delay=${delay}ms`
6670
+ `${TAG18} status=${classification.statusCode ?? "unknown"} restart required \u2014 reconnecting with saved creds (attempt ${attempt}/${LOGIN_MAX_RECONNECTS}) delay=${delay}ms`
6358
6671
  );
6359
6672
  closeSocket(current.sock);
6360
6673
  await new Promise((r) => setTimeout(r, delay));
@@ -6362,14 +6675,14 @@ async function loginConnectionLoop(accountId, login) {
6362
6675
  if (afterDelay?.id !== login.id) return;
6363
6676
  const rc = credsCensus(login.authDir);
6364
6677
  console.error(
6365
- `${TAG17} op=reconnect cid=${cidShort} attempt=${attempt}/${LOGIN_MAX_RECONNECTS} withRegistered=${rc.registered} withAccount=${rc.hasAccountSignature}`
6678
+ `${TAG18} op=reconnect cid=${cidShort} attempt=${attempt}/${LOGIN_MAX_RECONNECTS} withRegistered=${rc.registered} withAccount=${rc.hasAccountSignature}`
6366
6679
  );
6367
6680
  try {
6368
6681
  const newSock = await createWaSocket({ authDir: login.authDir, correlationId: cidShort, account: accountId });
6369
6682
  current.sock = newSock;
6370
6683
  } catch (sockErr) {
6371
6684
  console.error(
6372
- `${TAG17} reconnect socket creation failed (attempt ${attempt}/${LOGIN_MAX_RECONNECTS}): ${String(sockErr)}`
6685
+ `${TAG18} reconnect socket creation failed (attempt ${attempt}/${LOGIN_MAX_RECONNECTS}): ${String(sockErr)}`
6373
6686
  );
6374
6687
  current.error = `Reconnection failed: ${String(sockErr)}`;
6375
6688
  logTerminal("error", current.error);
@@ -6386,7 +6699,7 @@ async function startLogin(opts) {
6386
6699
  const hasAuth = await authExists(authDir);
6387
6700
  const selfId = readSelfId(authDir);
6388
6701
  console.error(
6389
- `${TAG17} op=start cid=${cid} account=${accountId} force=${!!force} hasAuth=${hasAuth}` + (existing0 ? ` activeLogin={id=${existing0.id.slice(0, 8)},age=${Math.round((Date.now() - existing0.startedAt) / 1e3)}s,hasCode=${!!existing0.pairingCode}}` : " activeLogin=none")
6702
+ `${TAG18} op=start cid=${cid} account=${accountId} force=${!!force} hasAuth=${hasAuth}` + (existing0 ? ` activeLogin={id=${existing0.id.slice(0, 8)},age=${Math.round((Date.now() - existing0.startedAt) / 1e3)}s,hasCode=${!!existing0.pairingCode}}` : " activeLogin=none")
6390
6703
  );
6391
6704
  if (hasAuth && !force) {
6392
6705
  const who = selfId.e164 ?? selfId.jid ?? "unknown";
@@ -6401,7 +6714,7 @@ async function startLogin(opts) {
6401
6714
  }
6402
6715
  const existing = activeLogins.get(accountId);
6403
6716
  if (existing && isLoginFresh(existing) && existing.pairingCode && !force) {
6404
- console.error(`${TAG17} startLogin account=${accountId} guard: returning existing pairing code (age=${Math.round((Date.now() - existing.startedAt) / 1e3)}s)`);
6717
+ console.error(`${TAG18} startLogin account=${accountId} guard: returning existing pairing code (age=${Math.round((Date.now() - existing.startedAt) / 1e3)}s)`);
6405
6718
  return {
6406
6719
  pairingCode: existing.pairingCode,
6407
6720
  phone: existing.phone,
@@ -6409,7 +6722,7 @@ async function startLogin(opts) {
6409
6722
  };
6410
6723
  }
6411
6724
  if (existing) {
6412
- console.error(`${TAG17} startLogin account=${accountId} ${force ? "force override" : "stale/no-code"}, resetting active login`);
6725
+ console.error(`${TAG18} startLogin account=${accountId} ${force ? "force override" : "stale/no-code"}, resetting active login`);
6413
6726
  }
6414
6727
  resetActiveLogin(accountId);
6415
6728
  await clearAuth(authDir);
@@ -6440,7 +6753,7 @@ async function startLogin(opts) {
6440
6753
  if (requested) return;
6441
6754
  requested = true;
6442
6755
  console.error(
6443
- `${TAG17} op=pairing-request cid=${cid} account=${accountId} qrRef=#1 sinceOpenMs=${socketOpenTs ? Date.now() - socketOpenTs : 0}`
6756
+ `${TAG18} op=pairing-request cid=${cid} account=${accountId} qrRef=#1 sinceOpenMs=${socketOpenTs ? Date.now() - socketOpenTs : 0}`
6444
6757
  );
6445
6758
  void sock.requestPairingCode(digits).then((code) => {
6446
6759
  clearTimeout(codeTimer);
@@ -6449,7 +6762,7 @@ async function startLogin(opts) {
6449
6762
  if (current?.id !== login.id) return;
6450
6763
  if (!current.pairingCode) current.pairingCode = code;
6451
6764
  console.error(
6452
- `${TAG17} op=code-issued cid=${cid} account=${accountId} phone=${maskPhone(digits)} codeLen=${code.length} sinceOpenMs=${socketOpenTs ? codeIssuedTs - socketOpenTs : 0}`
6765
+ `${TAG18} op=code-issued cid=${cid} account=${accountId} phone=${maskPhone(digits)} codeLen=${code.length} sinceOpenMs=${socketOpenTs ? codeIssuedTs - socketOpenTs : 0}`
6453
6766
  );
6454
6767
  resolveCode?.(code);
6455
6768
  }).catch((err) => {
@@ -6480,7 +6793,7 @@ async function startLogin(opts) {
6480
6793
  const ttlTimer = setTimeout(() => {
6481
6794
  const current = activeLogins.get(accountId);
6482
6795
  if (current?.id === login.id && !current.connected) {
6483
- console.error(`${TAG17} pairing-window-elapsed account=${accountId} (ttl sweep)`);
6796
+ console.error(`${TAG18} pairing-window-elapsed account=${accountId} (ttl sweep)`);
6484
6797
  resetActiveLogin(accountId);
6485
6798
  }
6486
6799
  }, ACTIVE_LOGIN_TTL_MS);
@@ -6488,7 +6801,7 @@ async function startLogin(opts) {
6488
6801
  ttlTimer.unref();
6489
6802
  }
6490
6803
  loginConnectionLoop(accountId, login).catch((err) => {
6491
- console.error(`${TAG17} loginConnectionLoop unexpected error: ${String(err)}`);
6804
+ console.error(`${TAG18} loginConnectionLoop unexpected error: ${String(err)}`);
6492
6805
  const current = activeLogins.get(accountId);
6493
6806
  if (current?.id === login.id) {
6494
6807
  current.error = `Unexpected login error: ${String(err)}`;
@@ -6513,13 +6826,13 @@ async function waitForLogin(opts) {
6513
6826
  const { accountId, timeoutMs = 6e4 } = opts;
6514
6827
  const login = activeLogins.get(accountId);
6515
6828
  console.error(
6516
- `${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},hasCode=${!!login.pairingCode}}` : " login=none")
6829
+ `${TAG18} 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},hasCode=${!!login.pairingCode}}` : " login=none")
6517
6830
  );
6518
6831
  if (!login) {
6519
6832
  return { connected: false, message: "No active WhatsApp login in progress.", configPersisted: false };
6520
6833
  }
6521
6834
  if (!isLoginFresh(login)) {
6522
- console.error(`${TAG17} pairing-window-elapsed account=${accountId}`);
6835
+ console.error(`${TAG18} pairing-window-elapsed account=${accountId}`);
6523
6836
  resetActiveLogin(accountId);
6524
6837
  return { connected: false, message: "The pairing window expired. Ask me to generate a new code.", configPersisted: false };
6525
6838
  }
@@ -6527,8 +6840,8 @@ async function waitForLogin(opts) {
6527
6840
  while (Date.now() < deadline) {
6528
6841
  if (login.connected) {
6529
6842
  const selfId = readSelfId(login.authDir);
6530
- console.error(`${TAG17} pairing-connected account=${accountId} phone=${selfId.e164 ?? "unknown"}`);
6531
- console.error(`${TAG17} login complete for account=${accountId} phone=${selfId.e164 ?? "unknown"} configPersisted=${login.configPersisted}`);
6843
+ console.error(`${TAG18} pairing-connected account=${accountId} phone=${selfId.e164 ?? "unknown"}`);
6844
+ console.error(`${TAG18} login complete for account=${accountId} phone=${selfId.e164 ?? "unknown"} configPersisted=${login.configPersisted}`);
6532
6845
  const configPersisted = login.configPersisted;
6533
6846
  activeLogins.delete(accountId);
6534
6847
  return {
@@ -6546,7 +6859,7 @@ async function waitForLogin(opts) {
6546
6859
  await new Promise((r) => setTimeout(r, 1e3));
6547
6860
  }
6548
6861
  const elapsed = Math.round((Date.now() - (deadline - timeoutMs)) / 1e3);
6549
- console.error(`${TAG17} waitForLogin poll timeout account=${accountId} elapsed=${elapsed}s \u2014 login kept alive (pairing code still valid)`);
6862
+ console.error(`${TAG18} waitForLogin poll timeout account=${accountId} elapsed=${elapsed}s \u2014 login kept alive (pairing code still valid)`);
6550
6863
  return { connected: false, message: "Still waiting for you to enter the pairing code. The code is still valid \u2014 ask me to keep waiting, or request a new code.", configPersisted: false };
6551
6864
  }
6552
6865
  function listActiveLoginAccountIds() {
@@ -6560,8 +6873,8 @@ function listActiveLoginAccountIds() {
6560
6873
  // app/lib/whatsapp/outbound/send-document.ts
6561
6874
  import { realpathSync as realpathSync3 } from "fs";
6562
6875
  import { readFile, stat as stat2 } from "fs/promises";
6563
- import { resolve as resolve9, basename as basename2 } from "path";
6564
- var TAG18 = "[whatsapp:outbound]";
6876
+ import { resolve as resolve9, basename as basename3 } from "path";
6877
+ var TAG19 = "[whatsapp:outbound]";
6565
6878
  var WHATSAPP_DOCUMENT_MAX_BYTES = 100 * 1024 * 1024;
6566
6879
  var lastDocumentOutboundAt = /* @__PURE__ */ new Map();
6567
6880
  var lastRouteDocumentOutboundAt = /* @__PURE__ */ new Map();
@@ -6595,16 +6908,16 @@ async function sendWhatsAppDocument(input) {
6595
6908
  const accountResolved = realpathSync3(accountDir);
6596
6909
  if (!resolvedPath.startsWith(accountResolved + "/")) {
6597
6910
  const sanitised = filePath.replace(accountDir, "<account>/");
6598
- console.error(`${TAG18} document REJECTED path=${sanitised} reason=outside_account_directory`);
6911
+ console.error(`${TAG19} document REJECTED path=${sanitised} reason=outside_account_directory`);
6599
6912
  return { ok: false, status: 403, error: "Access denied: file is outside the account directory" };
6600
6913
  }
6601
6914
  } catch (err) {
6602
6915
  const code = err.code;
6603
6916
  if (code === "ENOENT") {
6604
- console.error(`${TAG18} document ENOENT path=${filePath}`);
6917
+ console.error(`${TAG19} document ENOENT path=${filePath}`);
6605
6918
  return { ok: false, status: 404, error: `File not found: ${filePath}` };
6606
6919
  }
6607
- console.error(`${TAG18} document path error: ${String(err)}`);
6920
+ console.error(`${TAG19} document path error: ${String(err)}`);
6608
6921
  return { ok: false, status: 500, error: String(err) };
6609
6922
  }
6610
6923
  const fileStat = await stat2(resolvedPath);
@@ -6615,11 +6928,11 @@ async function sendWhatsAppDocument(input) {
6615
6928
  error: `File exceeds 100 MB limit (${(fileStat.size / 1024 / 1024).toFixed(1)} MB)`
6616
6929
  };
6617
6930
  }
6618
- const filename = basename2(resolvedPath);
6931
+ const filename = basename3(resolvedPath);
6619
6932
  const jid = normalizeJid2(to);
6620
6933
  const sock = getSocket(accountId);
6621
6934
  if (!sock) {
6622
- console.error(`${TAG18} sent document to=${jid} file=${filename} bytes=${fileStat.size} ok=false reason=not-connected`);
6935
+ console.error(`${TAG19} sent document to=${jid} file=${filename} bytes=${fileStat.size} ok=false reason=not-connected`);
6623
6936
  return { ok: false, status: 503, error: `WhatsApp account "${accountId}" is not connected` };
6624
6937
  }
6625
6938
  const buffer = Buffer.from(await readFile(resolvedPath));
@@ -6631,7 +6944,7 @@ async function sendWhatsAppDocument(input) {
6631
6944
  { accountId }
6632
6945
  );
6633
6946
  console.error(
6634
- `${TAG18} sent document to=${jid} file=${filename} bytes=${fileStat.size} ok=${result.success}` + (result.messageId ? ` id=${result.messageId}` : "")
6947
+ `${TAG19} sent document to=${jid} file=${filename} bytes=${fileStat.size} ok=${result.success}` + (result.messageId ? ` id=${result.messageId}` : "")
6635
6948
  );
6636
6949
  if (result.success) {
6637
6950
  recordDocumentOutbound(to);
@@ -6751,7 +7064,7 @@ function serializeWhatsAppSchema() {
6751
7064
  // app/lib/whatsapp/status-reconcile.ts
6752
7065
  import { readdirSync as readdirSync3 } from "fs";
6753
7066
  import { join as join9 } from "path";
6754
- var TAG19 = "[whatsapp:reconcile]";
7067
+ var TAG20 = "[whatsapp:reconcile]";
6755
7068
  var HALF_REGISTERED_MIN_AGE_MS = 5 * 6e4;
6756
7069
  function listCredsAccountIds(credsRoot) {
6757
7070
  try {
@@ -6777,14 +7090,14 @@ function reconcileCredsOnDisk(opts) {
6777
7090
  const ageMs = getAuthAgeMs(authDir);
6778
7091
  const abandoned = !inFlight && ageMs !== null && ageMs >= HALF_REGISTERED_MIN_AGE_MS;
6779
7092
  if (!abandoned) {
6780
- console.error(`${TAG19} op=half-registered account=${accountId} registered=true hasAccount=false inFlight=${inFlight} ageMs=${ageMs ?? "null"}`);
7093
+ console.error(`${TAG20} op=half-registered account=${accountId} registered=true hasAccount=false inFlight=${inFlight} ageMs=${ageMs ?? "null"}`);
6781
7094
  entries.push({ accountId, connected: false, linkedUnconfigured: true });
6782
7095
  continue;
6783
7096
  }
6784
- console.error(`${TAG19} op=discard-half-registered account=${accountId} registered=true hasAccount=false ageMs=${ageMs}`);
7097
+ console.error(`${TAG20} op=discard-half-registered account=${accountId} registered=true hasAccount=false ageMs=${ageMs}`);
6785
7098
  const removed = discardAuthDirSync(authDir);
6786
7099
  if (!removed) {
6787
- console.error(`${TAG19} op=discard-half-registered-failed account=${accountId} \u2014 residue persists`);
7100
+ console.error(`${TAG20} op=discard-half-registered-failed account=${accountId} \u2014 residue persists`);
6788
7101
  entries.push({ accountId, connected: false, linkedUnconfigured: true });
6789
7102
  }
6790
7103
  continue;
@@ -6792,14 +7105,14 @@ function reconcileCredsOnDisk(opts) {
6792
7105
  const selfPhone = readSelfId(authDir).e164 ?? void 0;
6793
7106
  const configured = accountDir ? isAccountConfigured(accountDir, accountId) : false;
6794
7107
  console.error(
6795
- `${TAG19} wa-link-unconfigured account=${accountId} creds=present config=${configured ? "present" : "absent"}`
7108
+ `${TAG20} wa-link-unconfigured account=${accountId} creds=present config=${configured ? "present" : "absent"}`
6796
7109
  );
6797
7110
  if (!configured && accountDir) {
6798
7111
  const result = persistAfterPairing(accountDir, accountId, selfPhone ?? null);
6799
7112
  if (result.ok) {
6800
- console.error(`${TAG19} self-healed account=${accountId}`);
7113
+ console.error(`${TAG20} self-healed account=${accountId}`);
6801
7114
  } else {
6802
- console.error(`${TAG19} self-heal FAILED account=${accountId}: ${result.error}`);
7115
+ console.error(`${TAG20} self-heal FAILED account=${accountId}: ${result.error}`);
6803
7116
  }
6804
7117
  }
6805
7118
  entries.push({ accountId, selfPhone, connected: false, linkedUnconfigured: !configured });
@@ -6808,7 +7121,7 @@ function reconcileCredsOnDisk(opts) {
6808
7121
  }
6809
7122
 
6810
7123
  // server/routes/whatsapp.ts
6811
- var TAG20 = "[whatsapp:api]";
7124
+ var TAG21 = "[whatsapp:api]";
6812
7125
  var PLATFORM_ROOT5 = process.env.MAXY_PLATFORM_ROOT || "";
6813
7126
  var app2 = new Hono();
6814
7127
  app2.get("/status", (c) => {
@@ -6821,10 +7134,10 @@ app2.get("/status", (c) => {
6821
7134
  const reconciled = reconcileCredsOnDisk({ credsRoot, accountDir, liveAccountIds: liveIds, activeLoginAccountIds: activeLoginIds });
6822
7135
  const accounts = [...live, ...reconciled];
6823
7136
  const summary = accounts.map((a) => `${a.accountId}:${a.connected ? "up" : a.linkedUnconfigured ? "linked-unconfigured" : "down"}`).join(", ");
6824
- console.error(`${TAG20} status accounts=${accounts.length} [${summary}]`);
7137
+ console.error(`${TAG21} status accounts=${accounts.length} [${summary}]`);
6825
7138
  return c.json({ accounts });
6826
7139
  } catch (err) {
6827
- console.error(`${TAG20} status error: ${String(err)}`);
7140
+ console.error(`${TAG21} status error: ${String(err)}`);
6828
7141
  return c.json({ error: String(err) }, 500);
6829
7142
  }
6830
7143
  });
@@ -6840,10 +7153,10 @@ app2.post("/login/start", async (c) => {
6840
7153
  const authDir = join10(MAXY_DIR, "credentials", "whatsapp", accountId);
6841
7154
  const accountDir = resolveAccount()?.accountDir ?? null;
6842
7155
  const result = await startLogin({ accountId, authDir, phone, accountDir, force });
6843
- console.error(`${TAG20} login/start result account=${accountId} hasCode=${!!result.pairingCode}${result.selfPhone ? ` phone=${result.selfPhone}` : ""}`);
7156
+ console.error(`${TAG21} login/start result account=${accountId} hasCode=${!!result.pairingCode}${result.selfPhone ? ` phone=${result.selfPhone}` : ""}`);
6844
7157
  return c.json(result);
6845
7158
  } catch (err) {
6846
- console.error(`${TAG20} login/start error: ${String(err)}`);
7159
+ console.error(`${TAG21} login/start error: ${String(err)}`);
6847
7160
  return c.json({ error: String(err) }, 500);
6848
7161
  }
6849
7162
  });
@@ -6853,7 +7166,7 @@ app2.post("/login/wait", async (c) => {
6853
7166
  const accountId = validateAccountId(body.accountId);
6854
7167
  const timeoutMs = body.timeoutMs ?? 6e4;
6855
7168
  const result = await waitForLogin({ accountId, timeoutMs });
6856
- console.error(`${TAG20} login/wait result account=${accountId} connected=${result.connected}${result.selfPhone ? ` phone=${result.selfPhone}` : ""} configPersisted=${result.configPersisted}`);
7169
+ console.error(`${TAG21} login/wait result account=${accountId} connected=${result.connected}${result.selfPhone ? ` phone=${result.selfPhone}` : ""} configPersisted=${result.configPersisted}`);
6857
7170
  return c.json({
6858
7171
  connected: result.connected,
6859
7172
  message: result.message,
@@ -6861,7 +7174,7 @@ app2.post("/login/wait", async (c) => {
6861
7174
  configPersisted: result.configPersisted
6862
7175
  });
6863
7176
  } catch (err) {
6864
- console.error(`${TAG20} login/wait error: ${String(err)}`);
7177
+ console.error(`${TAG21} login/wait error: ${String(err)}`);
6865
7178
  return c.json({ error: String(err) }, 500);
6866
7179
  }
6867
7180
  });
@@ -6872,7 +7185,7 @@ app2.post("/disconnect", async (c) => {
6872
7185
  await stopConnection(accountId);
6873
7186
  return c.json({ disconnected: true, accountId });
6874
7187
  } catch (err) {
6875
- console.error(`${TAG20} disconnect error: ${String(err)}`);
7188
+ console.error(`${TAG21} disconnect error: ${String(err)}`);
6876
7189
  return c.json({ error: String(err) }, 500);
6877
7190
  }
6878
7191
  });
@@ -6883,14 +7196,14 @@ app2.post("/reconnect", async (c) => {
6883
7196
  await startConnection(accountId);
6884
7197
  return c.json({ reconnecting: true, accountId });
6885
7198
  } catch (err) {
6886
- console.error(`${TAG20} reconnect error: ${String(err)}`);
7199
+ console.error(`${TAG21} reconnect error: ${String(err)}`);
6887
7200
  return c.json({ error: String(err) }, 500);
6888
7201
  }
6889
7202
  });
6890
7203
  app2.post("/config", async (c) => {
6891
7204
  try {
6892
7205
  const body = await c.req.json().catch(() => ({}));
6893
- const { action, phone, slug, fields, accountId } = body;
7206
+ const { action, phone, slug, fields, accountId, managesAccount } = body;
6894
7207
  if (!action || typeof action !== "string") {
6895
7208
  return c.json({ ok: false, error: 'Missing required field "action".' }, 400);
6896
7209
  }
@@ -6904,7 +7217,8 @@ app2.post("/config", async (c) => {
6904
7217
  return c.json({ ok: false, error: 'Missing required field "phone" (E.164 format, e.g. +441234567890).' }, 400);
6905
7218
  }
6906
7219
  const result = addAdminPhone(account.accountDir, phone);
6907
- console.error(`${TAG20} config action=add-admin-phone phone=${phone} ok=${result.ok}`);
7220
+ const reasonTail = !result.ok && result.reason ? ` reason=${result.reason}` : "";
7221
+ console.error(`${TAG21} config action=add-admin-phone accountId=${account.accountId} phone=${phone} ok=${result.ok}${reasonTail}`);
6908
7222
  return c.json(result, result.ok ? 200 : 400);
6909
7223
  }
6910
7224
  case "remove-admin-phone": {
@@ -6912,26 +7226,50 @@ app2.post("/config", async (c) => {
6912
7226
  return c.json({ ok: false, error: 'Missing required field "phone".' }, 400);
6913
7227
  }
6914
7228
  const result = removeAdminPhone(account.accountDir, phone);
6915
- console.error(`${TAG20} config action=remove-admin-phone phone=${phone} ok=${result.ok}`);
7229
+ console.error(`${TAG21} config action=remove-admin-phone phone=${phone} ok=${result.ok}`);
6916
7230
  return c.json(result, result.ok ? 200 : 400);
6917
7231
  }
6918
7232
  case "list-admin-phones": {
6919
7233
  const phones = readAdminPhones(account.accountDir);
6920
- console.error(`${TAG20} config action=list-admin-phones count=${phones.length}`);
7234
+ console.error(`${TAG21} config action=list-admin-phones count=${phones.length}`);
6921
7235
  return c.json({ ok: true, phones });
6922
7236
  }
7237
+ case "add-account-manager": {
7238
+ if (!phone || typeof phone !== "string") {
7239
+ return c.json({ ok: false, error: 'Missing required field "phone" (E.164 format, e.g. +441234567890).' }, 400);
7240
+ }
7241
+ if (!managesAccount || typeof managesAccount !== "string") {
7242
+ return c.json({ ok: false, error: 'Missing required field "managesAccount" (the sub-account UUID this phone should manage).' }, 400);
7243
+ }
7244
+ const result = setAccountManager(account.accountDir, phone, managesAccount);
7245
+ console.error(`${TAG21} config action=add-account-manager phone=${phone} managesAccount=${managesAccount} ok=${result.ok}`);
7246
+ return c.json(result, result.ok ? 200 : 400);
7247
+ }
7248
+ case "remove-account-manager": {
7249
+ if (!phone || typeof phone !== "string") {
7250
+ return c.json({ ok: false, error: 'Missing required field "phone".' }, 400);
7251
+ }
7252
+ const result = clearAccountManager(account.accountDir, phone);
7253
+ console.error(`${TAG21} config action=remove-account-manager phone=${phone} ok=${result.ok}`);
7254
+ return c.json(result, result.ok ? 200 : 400);
7255
+ }
7256
+ case "list-account-managers": {
7257
+ const accountManagers = readAccountManagers(account.accountDir);
7258
+ console.error(`${TAG21} config action=list-account-managers count=${Object.keys(accountManagers).length}`);
7259
+ return c.json({ ok: true, accountManagers });
7260
+ }
6923
7261
  case "set-public-agent": {
6924
7262
  if (!slug || typeof slug !== "string") {
6925
7263
  return c.json({ ok: false, error: 'Missing required field "slug" (the agent directory name, e.g. "my-agent").' }, 400);
6926
7264
  }
6927
7265
  const result = setPublicAgent(account.accountDir, slug);
6928
- console.error(`${TAG20} config action=set-public-agent slug=${slug} ok=${result.ok}`);
7266
+ console.error(`${TAG21} config action=set-public-agent slug=${slug} ok=${result.ok}`);
6929
7267
  return c.json(result, result.ok ? 200 : 400);
6930
7268
  }
6931
7269
  case "get-public-agent": {
6932
7270
  const targetAccount = typeof accountId === "string" && accountId.trim() ? accountId.trim() : "default";
6933
7271
  const resolved = resolvePublicAgent(account.accountDir, { accountId: targetAccount });
6934
- console.error(`${TAG20} config action=get-public-agent accountId=${targetAccount} slug=${resolved?.slug ?? "none"} source=${resolved?.source ?? "none"}`);
7272
+ console.error(`${TAG21} config action=get-public-agent accountId=${targetAccount} slug=${resolved?.slug ?? "none"} source=${resolved?.source ?? "none"}`);
6935
7273
  return c.json({ ok: true, slug: resolved?.slug ?? null, source: resolved?.source ?? null });
6936
7274
  }
6937
7275
  case "list-public-agents": {
@@ -6948,26 +7286,26 @@ app2.post("/config", async (c) => {
6948
7286
  const config = JSON.parse(readFileSync11(configPath2, "utf-8"));
6949
7287
  agents.push({ slug: entry.name, displayName: config.displayName ?? entry.name });
6950
7288
  } catch {
6951
- console.error(`${TAG20} config action=list-public-agents error="failed to parse config.json for agent ${entry.name}" \u2014 skipping`);
7289
+ console.error(`${TAG21} config action=list-public-agents error="failed to parse config.json for agent ${entry.name}" \u2014 skipping`);
6952
7290
  }
6953
7291
  }
6954
7292
  } catch (err) {
6955
- console.error(`${TAG20} config action=list-public-agents error="failed to scan agents directory: ${String(err)}"`);
7293
+ console.error(`${TAG21} config action=list-public-agents error="failed to scan agents directory: ${String(err)}"`);
6956
7294
  }
6957
7295
  }
6958
- console.error(`${TAG20} config action=list-public-agents count=${agents.length}`);
7296
+ console.error(`${TAG21} config action=list-public-agents count=${agents.length}`);
6959
7297
  return c.json({ ok: true, agents });
6960
7298
  }
6961
7299
  case "schema": {
6962
7300
  const text = serializeWhatsAppSchema();
6963
- console.error(`${TAG20} config action=schema`);
7301
+ console.error(`${TAG21} config action=schema`);
6964
7302
  return c.json({ ok: true, text });
6965
7303
  }
6966
7304
  case "list-groups": {
6967
7305
  const groupAccountId = accountId ?? "default";
6968
7306
  const sock = getSocket(groupAccountId);
6969
7307
  if (!sock) {
6970
- console.error(`${TAG20} config action=list-groups error="not connected" accountId=${groupAccountId}`);
7308
+ console.error(`${TAG21} config action=list-groups error="not connected" accountId=${groupAccountId}`);
6971
7309
  return c.json({ ok: false, error: `WhatsApp account "${groupAccountId}" is not connected. Connect first, then retry.` });
6972
7310
  }
6973
7311
  try {
@@ -6977,10 +7315,10 @@ app2.post("/config", async (c) => {
6977
7315
  name: g.subject ?? g.id,
6978
7316
  participantCount: Array.isArray(g.participants) ? g.participants.length : 0
6979
7317
  }));
6980
- console.error(`${TAG20} config action=list-groups count=${groups.length} accountId=${groupAccountId}`);
7318
+ console.error(`${TAG21} config action=list-groups count=${groups.length} accountId=${groupAccountId}`);
6981
7319
  return c.json({ ok: true, groups });
6982
7320
  } catch (err) {
6983
- console.error(`${TAG20} config action=list-groups error="${String(err)}" accountId=${groupAccountId}`);
7321
+ console.error(`${TAG21} config action=list-groups error="${String(err)}" accountId=${groupAccountId}`);
6984
7322
  return c.json({ ok: false, error: `Failed to fetch groups: ${String(err)}` });
6985
7323
  }
6986
7324
  }
@@ -6990,22 +7328,22 @@ app2.post("/config", async (c) => {
6990
7328
  }
6991
7329
  const result = updateConfig(account.accountDir, fields);
6992
7330
  const fieldNames = Object.keys(fields);
6993
- console.error(`${TAG20} config action=update-config fields=[${fieldNames.join(",")}] ok=${result.ok}`);
7331
+ console.error(`${TAG21} config action=update-config fields=[${fieldNames.join(",")}] ok=${result.ok}`);
6994
7332
  return c.json(result, result.ok ? 200 : 400);
6995
7333
  }
6996
7334
  case "get-config": {
6997
7335
  const waConfig = getConfig(account.accountDir);
6998
- console.error(`${TAG20} config action=get-config`);
7336
+ console.error(`${TAG21} config action=get-config`);
6999
7337
  return c.json({ ok: true, config: waConfig });
7000
7338
  }
7001
7339
  default:
7002
7340
  return c.json(
7003
- { ok: false, error: `Unknown action "${action}". Valid actions: add-admin-phone, remove-admin-phone, list-admin-phones, set-public-agent, get-public-agent, list-public-agents, update-config, get-config, schema, list-groups.` },
7341
+ { ok: false, error: `Unknown action "${action}". Valid actions: add-admin-phone, remove-admin-phone, list-admin-phones, add-account-manager, remove-account-manager, list-account-managers, set-public-agent, get-public-agent, list-public-agents, update-config, get-config, schema, list-groups.` },
7004
7342
  400
7005
7343
  );
7006
7344
  }
7007
7345
  } catch (err) {
7008
- console.error(`${TAG20} config error: ${String(err)}`);
7346
+ console.error(`${TAG21} config error: ${String(err)}`);
7009
7347
  return c.json({ ok: false, error: String(err) }, 500);
7010
7348
  }
7011
7349
  });
@@ -7026,7 +7364,7 @@ app2.post("/send-document", async (c) => {
7026
7364
  recordRouteDocumentOutbound(to, filePath);
7027
7365
  return c.json({ success: true, messageId: result.messageId });
7028
7366
  } catch (err) {
7029
- console.error(`${TAG20} send-document error: ${String(err)}`);
7367
+ console.error(`${TAG21} send-document error: ${String(err)}`);
7030
7368
  return c.json({ error: String(err) }, 500);
7031
7369
  }
7032
7370
  });
@@ -7063,7 +7401,7 @@ app2.post("/send-admin", async (c) => {
7063
7401
  }
7064
7402
  return c.json({ success: true, messageId: result.messageId });
7065
7403
  } catch (err) {
7066
- console.error(`${TAG20} send-admin error: ${String(err)}`);
7404
+ console.error(`${TAG21} send-admin error: ${String(err)}`);
7067
7405
  return c.json({ error: String(err) }, 500);
7068
7406
  }
7069
7407
  });
@@ -7073,11 +7411,11 @@ app2.get("/activity", (c) => {
7073
7411
  const result = getChannelActivity(accountId);
7074
7412
  const total = result.accounts.reduce((sum, a) => sum + a.total, 0);
7075
7413
  console.error(
7076
- `${TAG20} activity accounts=${result.accounts.length} total=${total} recentEvents=${result.recentEvents.length}` + (accountId ? ` filter=${accountId}` : "")
7414
+ `${TAG21} activity accounts=${result.accounts.length} total=${total} recentEvents=${result.recentEvents.length}` + (accountId ? ` filter=${accountId}` : "")
7077
7415
  );
7078
7416
  return c.json(result);
7079
7417
  } catch (err) {
7080
- console.error(`${TAG20} activity error: ${String(err)}`);
7418
+ console.error(`${TAG21} activity error: ${String(err)}`);
7081
7419
  return c.json({ error: String(err) }, 500);
7082
7420
  }
7083
7421
  });
@@ -7096,10 +7434,10 @@ app2.get("/conversations", (c) => {
7096
7434
  };
7097
7435
  });
7098
7436
  conversations.sort((a, b) => (b.lastMessageTimestamp ?? 0) - (a.lastMessageTimestamp ?? 0));
7099
- console.error(`${TAG20} conversations account=${accountId} count=${conversations.length}`);
7437
+ console.error(`${TAG21} conversations account=${accountId} count=${conversations.length}`);
7100
7438
  return c.json({ conversations });
7101
7439
  } catch (err) {
7102
- console.error(`${TAG20} conversations error: ${String(err)}`);
7440
+ console.error(`${TAG21} conversations error: ${String(err)}`);
7103
7441
  return c.json({ error: String(err) }, 500);
7104
7442
  }
7105
7443
  });
@@ -7114,10 +7452,10 @@ app2.get("/messages", (c) => {
7114
7452
  const limit = limitParam ? parseInt(limitParam, 10) : void 0;
7115
7453
  const effectiveLimit = limit && !Number.isNaN(limit) && limit > 0 ? limit : void 0;
7116
7454
  const messages = getMessages(accountId, jid, effectiveLimit);
7117
- console.error(`${TAG20} messages account=${accountId} jid=${jid} limit=${effectiveLimit ?? "all"} returned=${messages.length}`);
7455
+ console.error(`${TAG21} messages account=${accountId} jid=${jid} limit=${effectiveLimit ?? "all"} returned=${messages.length}`);
7118
7456
  return c.json({ messages });
7119
7457
  } catch (err) {
7120
- console.error(`${TAG20} messages error: ${String(err)}`);
7458
+ console.error(`${TAG21} messages error: ${String(err)}`);
7121
7459
  return c.json({ error: String(err) }, 500);
7122
7460
  }
7123
7461
  });
@@ -7198,7 +7536,7 @@ app2.get("/conversation-graph-state", async (c) => {
7198
7536
  ms
7199
7537
  });
7200
7538
  } catch (err) {
7201
- console.error(`${TAG20} conversation-graph-state error: ${String(err)}`);
7539
+ console.error(`${TAG21} conversation-graph-state error: ${String(err)}`);
7202
7540
  return c.json({ error: String(err) }, 500);
7203
7541
  }
7204
7542
  });
@@ -7210,12 +7548,12 @@ app2.get("/group-info", async (c) => {
7210
7548
  return c.json({ error: "Missing required parameter: jid" }, 400);
7211
7549
  }
7212
7550
  if (!isGroupJid(jid)) {
7213
- console.error(`${TAG20} group-info error="not a group JID" jid=${jid} account=${accountId}`);
7551
+ console.error(`${TAG21} group-info error="not a group JID" jid=${jid} account=${accountId}`);
7214
7552
  return c.json({ error: `"${jid}" is not a group JID. Group JIDs end with @g.us.` }, 400);
7215
7553
  }
7216
7554
  const sock = getSocket(accountId);
7217
7555
  if (!sock) {
7218
- console.error(`${TAG20} group-info error="not connected" account=${accountId}`);
7556
+ console.error(`${TAG21} group-info error="not connected" account=${accountId}`);
7219
7557
  return c.json({ error: `WhatsApp account "${accountId}" is not connected. Connect first, then retry.` }, 400);
7220
7558
  }
7221
7559
  const meta = await sock.groupMetadata(jid);
@@ -7228,10 +7566,10 @@ app2.get("/group-info", async (c) => {
7228
7566
  participantCount: meta.participants.length,
7229
7567
  participants: meta.participants.map((p) => ({ jid: p.id, admin: p.admin ?? null }))
7230
7568
  };
7231
- console.error(`${TAG20} group-info jid=${jid} subject="${meta.subject}" participants=${meta.participants.length} account=${accountId}`);
7569
+ console.error(`${TAG21} group-info jid=${jid} subject="${meta.subject}" participants=${meta.participants.length} account=${accountId}`);
7232
7570
  return c.json(result);
7233
7571
  } catch (err) {
7234
- console.error(`${TAG20} group-info error="${String(err)}" jid=${jid} account=${accountId}`);
7572
+ console.error(`${TAG21} group-info error="${String(err)}" jid=${jid} account=${accountId}`);
7235
7573
  return c.json({ error: `Failed to fetch group info: ${String(err)}` }, 500);
7236
7574
  }
7237
7575
  });
@@ -7239,11 +7577,11 @@ var whatsapp_default = app2;
7239
7577
 
7240
7578
  // server/routes/whatsapp-reader.ts
7241
7579
  import { readFileSync as readFileSync15, watch, statSync as statSync5, openSync, readSync, closeSync, existsSync as existsSync8, readdirSync as readdirSync8, realpathSync as realpathSync4 } from "fs";
7242
- import { basename as basename4, dirname as dirname3, isAbsolute, join as join14, relative as relative2, resolve as resolve12, sep as sep3 } from "path";
7580
+ import { basename as basename5, dirname as dirname4, isAbsolute, join as join14, relative as relative2, resolve as resolve12, sep as sep3 } from "path";
7243
7581
 
7244
7582
  // server/routes/admin/sidebar-sessions.ts
7245
7583
  import { readdirSync as readdirSync5, readFileSync as readFileSync12, statSync as statSync3 } from "fs";
7246
- import { basename as basename3, dirname as dirname2, join as join11, resolve as resolve11 } from "path";
7584
+ import { basename as basename4, dirname as dirname3, join as join11, resolve as resolve11 } from "path";
7247
7585
 
7248
7586
  // app/lib/whatsapp-reader/select-sessions.ts
7249
7587
  function isReaderChannelSession(role, channel) {
@@ -7324,7 +7662,7 @@ function findSessionProjectDir(sessionId) {
7324
7662
  const cfg = claudeConfigDir();
7325
7663
  if (!cfg) return null;
7326
7664
  for (const { path: path2 } of enumerateJsonls(join11(cfg, "projects"))) {
7327
- if (basename3(path2) === `${sessionId}.jsonl`) return dirname2(path2);
7665
+ if (basename4(path2) === `${sessionId}.jsonl`) return dirname3(path2);
7328
7666
  }
7329
7667
  return null;
7330
7668
  }
@@ -7649,11 +7987,11 @@ app3.get("/", requireAdminSession, async (c) => {
7649
7987
  var sidebar_sessions_default = app3;
7650
7988
 
7651
7989
  // app/lib/admin-identity/pin-validator.ts
7652
- import { createHash } from "crypto";
7990
+ import { createHash as createHash2 } from "crypto";
7653
7991
  import { existsSync as existsSync7, readFileSync as readFileSync13, readdirSync as readdirSync6, statSync as statSync4 } from "fs";
7654
7992
  import { join as join12 } from "path";
7655
7993
  function hashPin(pin) {
7656
- return createHash("sha256").update(pin).digest("hex");
7994
+ return createHash2("sha256").update(pin).digest("hex");
7657
7995
  }
7658
7996
  function readUsersFile(usersFilePath) {
7659
7997
  if (!existsSync7(usersFilePath)) return null;
@@ -8053,8 +8391,8 @@ app4.get("/conversations", requireAdminSession, async (c) => {
8053
8391
  const rows = [];
8054
8392
  let sessionRowsExcludedUntagged = 0;
8055
8393
  for (const { path: path2 } of enumerateJsonls(projectsRoot)) {
8056
- const sessionId = basename4(path2).replace(/\.jsonl$/, "");
8057
- const projectDir = dirname3(path2);
8394
+ const sessionId = basename5(path2).replace(/\.jsonl$/, "");
8395
+ const projectDir = dirname4(path2);
8058
8396
  const meta = readSidecarMeta(join14(projectDir, `${sessionId}.meta.json`));
8059
8397
  if (!isReaderChannelSession(meta.role, meta.channel)) continue;
8060
8398
  if (multiAccount) {
@@ -8584,7 +8922,7 @@ var whatsapp_reader_default = app4;
8584
8922
 
8585
8923
  // server/routes/public-reader.ts
8586
8924
  import { statSync as statSync6, openSync as openSync2, readSync as readSync2, closeSync as closeSync2, watch as watch2, readFileSync as readFileSync16, readdirSync as readdirSync9 } from "fs";
8587
- import { basename as basename5, dirname as dirname4, join as join15, resolve as resolve13, sep as sep4 } from "path";
8925
+ import { basename as basename6, dirname as dirname5, join as join15, resolve as resolve13, sep as sep4 } from "path";
8588
8926
 
8589
8927
  // app/lib/whatsapp-reader/delivered-kinds.ts
8590
8928
  var PUBLIC_DELIVERED_KINDS = /* @__PURE__ */ new Set([
@@ -8631,7 +8969,7 @@ function resolveOpenVisitorSession(rows, visitorId, agentSlug) {
8631
8969
 
8632
8970
  // server/routes/public-reader.ts
8633
8971
  var app5 = new Hono();
8634
- var TAG21 = "[public-webchat]";
8972
+ var TAG22 = "[public-webchat]";
8635
8973
  function parseAccessSessionId(cookieHeader) {
8636
8974
  if (!cookieHeader) return null;
8637
8975
  const part = cookieHeader.split(";").map((p) => p.trim()).find((p) => p.startsWith("__access_session="));
@@ -8659,8 +8997,8 @@ function enumeratePublicRows() {
8659
8997
  if (!cfg) return [];
8660
8998
  const rows = [];
8661
8999
  for (const { path: path2 } of enumerateJsonls(join15(cfg, "projects"))) {
8662
- const sessionId = basename5(path2).replace(/\.jsonl$/, "");
8663
- const projectDir = dirname4(path2);
9000
+ const sessionId = basename6(path2).replace(/\.jsonl$/, "");
9001
+ const projectDir = dirname5(path2);
8664
9002
  const meta = readSidecarMeta(join15(projectDir, `${sessionId}.meta.json`));
8665
9003
  rows.push({
8666
9004
  sessionId,
@@ -8679,7 +9017,7 @@ function enumeratePublicRows() {
8679
9017
  app5.get("/session", (c) => {
8680
9018
  const visitor = resolveVisitor(c);
8681
9019
  if (!visitor) {
8682
- console.error(`${TAG21} op=reader-refused reason=no-anchor path=/session`);
9020
+ console.error(`${TAG22} op=reader-refused reason=no-anchor path=/session`);
8683
9021
  return c.json({ error: "gate-required" }, 401);
8684
9022
  }
8685
9023
  const rows = enumeratePublicRows();
@@ -8698,11 +9036,11 @@ app5.get("/session", (c) => {
8698
9036
  found = effectiveSlug ? resolveOpenVisitorSession(rows, visitor.visitorId, effectiveSlug) : null;
8699
9037
  }
8700
9038
  if (found) {
8701
- console.log(`${TAG21} op=session-resume anchor=${visitor.kind} key=${(found.senderId ?? "").slice(0, 8)} sessionId=${found.sessionId.slice(0, 8)}`);
9039
+ console.log(`${TAG22} op=session-resume anchor=${visitor.kind} key=${(found.senderId ?? "").slice(0, 8)} sessionId=${found.sessionId.slice(0, 8)}`);
8702
9040
  return c.json({ sessionId: found.sessionId, projectDir: found.projectDir, sessionKey: found.senderId });
8703
9041
  }
8704
9042
  const sessionKey = crypto.randomUUID();
8705
- console.log(`${TAG21} op=session-new anchor=${visitor.kind} key=${sessionKey.slice(0, 8)}`);
9043
+ console.log(`${TAG22} op=session-new anchor=${visitor.kind} key=${sessionKey.slice(0, 8)}`);
8706
9044
  return c.json({ sessionId: null, projectDir: null, sessionKey });
8707
9045
  });
8708
9046
  function publicUploadsDir(accountId, sessionId) {
@@ -8731,7 +9069,7 @@ function readFrom2(path2, from) {
8731
9069
  app5.get("/stream", (c) => {
8732
9070
  const visitor = resolveVisitor(c);
8733
9071
  if (!visitor) {
8734
- console.error(`${TAG21} op=reader-refused reason=no-anchor path=/stream`);
9072
+ console.error(`${TAG22} op=reader-refused reason=no-anchor path=/stream`);
8735
9073
  return c.json({ error: "gate-required" }, 401);
8736
9074
  }
8737
9075
  const sessionId = c.req.query("sessionId") ?? "";
@@ -8749,12 +9087,12 @@ app5.get("/stream", (c) => {
8749
9087
  const isPublicWebchat = meta.role === "public" && meta.channel === "webchat";
8750
9088
  const owns = visitor.kind === "person" ? meta.personId === visitor.personId : meta.personId === null && meta.visitorId === visitor.visitorId;
8751
9089
  if (!(isPublicWebchat && owns)) {
8752
- console.error(`${TAG21} op=reader-refused reason=not-owner anchor=${visitor.kind} sessionId=${sessionId.slice(0, 8)}`);
9090
+ console.error(`${TAG22} op=reader-refused reason=not-owner anchor=${visitor.kind} sessionId=${sessionId.slice(0, 8)}`);
8753
9091
  return c.json({ error: "forbidden" }, 403);
8754
9092
  }
8755
9093
  const senderShort = (meta.senderId ?? "").slice(0, 8);
8756
9094
  const encoder = new TextEncoder();
8757
- console.log(`${TAG21} op=reader-open anchor=${visitor.kind} key=${senderShort} mode=delivered-only sessionId=${sessionId.slice(0, 8)}`);
9095
+ console.log(`${TAG22} op=reader-open anchor=${visitor.kind} key=${senderShort} mode=delivered-only sessionId=${sessionId.slice(0, 8)}`);
8758
9096
  const send = (controller, turn, id) => controller.enqueue(encoder.encode(`id: ${id}
8759
9097
  data: ${JSON.stringify(turn)}
8760
9098
 
@@ -8769,7 +9107,7 @@ data: ${JSON.stringify(turn)}
8769
9107
  const { turns, dropped } = filterDeliveredFrames(lines);
8770
9108
  if (uploadsDir) enrichPublicAttachments(turns, listSessionAttachmentsInDir(uploadsDir), attachmentCursor);
8771
9109
  for (const turn of turns) send(controller, turn, offset);
8772
- if (dropped > 0) console.log(`${TAG21} op=reader-filtered key=${senderShort} dropped=${dropped}`);
9110
+ if (dropped > 0) console.log(`${TAG22} op=reader-filtered key=${senderShort} dropped=${dropped}`);
8773
9111
  };
8774
9112
  try {
8775
9113
  const { buf, end } = readFrom2(jsonlPath, 0);
@@ -8804,7 +9142,7 @@ data: ${JSON.stringify(turn)}
8804
9142
  watcher?.close();
8805
9143
  clearInterval(poll);
8806
9144
  clearInterval(heartbeat);
8807
- console.log(`${TAG21} op=reader-close key=${senderShort}`);
9145
+ console.log(`${TAG22} op=reader-close key=${senderShort}`);
8808
9146
  try {
8809
9147
  controller.close();
8810
9148
  } catch {
@@ -8878,7 +9216,7 @@ app5.get("/attachment/:attachmentId", (c) => {
8878
9216
  var public_reader_default = app5;
8879
9217
 
8880
9218
  // server/routes/webchat.ts
8881
- import { basename as basename6, dirname as dirname5 } from "path";
9219
+ import { basename as basename7, dirname as dirname6 } from "path";
8882
9220
  import { join as join18 } from "path";
8883
9221
  import { existsSync as existsSync10, readdirSync as readdirSync11, readFileSync as readFileSync19, renameSync as renameSync3, statSync as statSync7, writeFileSync as writeFileSync7 } from "fs";
8884
9222
 
@@ -8924,18 +9262,6 @@ function writeCanonicalOverrideId(accountDir, userId, sessionId) {
8924
9262
  renameSync(tmp, path2);
8925
9263
  }
8926
9264
 
8927
- // app/lib/channel-pty-bridge/admin-session-id.ts
8928
- import { createHash as createHash2 } from "crypto";
8929
- function adminSessionIdFor(accountId, senderId, personId) {
8930
- const subject = personId && personId.length > 0 ? `person:${personId}` : senderId;
8931
- const b = createHash2("sha256").update(`${accountId}:admin:${subject}`).digest().subarray(0, 16);
8932
- const v = Buffer.from(b);
8933
- v[6] = v[6] & 15 | 64;
8934
- v[8] = v[8] & 63 | 128;
8935
- const h = v.toString("hex");
8936
- return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20, 32)}`;
8937
- }
8938
-
8939
9265
  // ../lib/models/src/index.ts
8940
9266
  var OPUS_MODEL = "claude-opus-4-8[1m]";
8941
9267
  var SONNET_MODEL = "claude-sonnet-5";
@@ -9288,8 +9614,8 @@ function latestAdminWebchatSessionId(requesterUserId, primaryUserId, scopeAccoun
9288
9614
  let best = null;
9289
9615
  for (const { path: path2, isSubagent, archived } of enumerateJsonls(join18(cfg, "projects"))) {
9290
9616
  if (isSubagent || archived) continue;
9291
- const id = basename6(path2).slice(0, -".jsonl".length);
9292
- const meta = readSidecarMeta(join18(dirname5(path2), `${id}.meta.json`));
9617
+ const id = basename7(path2).slice(0, -".jsonl".length);
9618
+ const meta = readSidecarMeta(join18(dirname6(path2), `${id}.meta.json`));
9293
9619
  if (meta.role !== "admin" || meta.channel !== "webchat") continue;
9294
9620
  if (multiAccount && meta.accountId !== scopeAccountId) continue;
9295
9621
  let mtimeMs;
@@ -9312,7 +9638,7 @@ function latestAdminWebchatSessionId(requesterUserId, primaryUserId, scopeAccoun
9312
9638
  function overrideKnownNonArchived(id) {
9313
9639
  const { projectDir } = locateSession(id);
9314
9640
  if (projectDir === null) return sessionSidecarExists(id);
9315
- return basename6(projectDir) !== "archive";
9641
+ return basename7(projectDir) !== "archive";
9316
9642
  }
9317
9643
  function resolveCanonical(accountId, accountDir, requesterUserId, primaryUserId, scopeAccountId, multiAccount) {
9318
9644
  const bootstrapAccountId = scopeAccountId ?? accountId;
@@ -9680,8 +10006,8 @@ ${note}` : note;
9680
10006
  let projectDir = null;
9681
10007
  if (cfg) {
9682
10008
  for (const { path: path2 } of enumerateJsonls(join18(cfg, "projects"))) {
9683
- if (basename6(path2) === `${sessionId}.jsonl`) {
9684
- projectDir = dirname5(path2);
10009
+ if (basename7(path2) === `${sessionId}.jsonl`) {
10010
+ projectDir = dirname6(path2);
9685
10011
  break;
9686
10012
  }
9687
10013
  }
@@ -9913,7 +10239,7 @@ function routeTelegramUpdate(input) {
9913
10239
  }
9914
10240
 
9915
10241
  // server/routes/telegram.ts
9916
- var TAG22 = "[telegram-inbound]";
10242
+ var TAG23 = "[telegram-inbound]";
9917
10243
  function configDirName() {
9918
10244
  const platformRoot3 = process.env.MAXY_PLATFORM_ROOT ?? resolve15(process.cwd(), "..");
9919
10245
  const brandPath = join20(platformRoot3, "config", "brand.json");
@@ -9929,36 +10255,23 @@ function secretPath(botType) {
9929
10255
  const filename = botType === "admin" ? ".telegram-admin-webhook-secret" : ".telegram-webhook-secret";
9930
10256
  return resolve15(homedir(), configDirName(), filename);
9931
10257
  }
9932
- async function sendTelegram(botToken, chatId, text) {
9933
- try {
9934
- const res = await fetch(`https://api.telegram.org/bot${botToken}/sendMessage`, {
9935
- method: "POST",
9936
- headers: { "Content-Type": "application/json" },
9937
- body: JSON.stringify({ chat_id: chatId, text })
9938
- });
9939
- const data = await res.json();
9940
- return data.ok ? { ok: true } : { ok: false, error: data.description ?? "send failed" };
9941
- } catch (e) {
9942
- return { ok: false, error: e instanceof Error ? e.message : String(e) };
9943
- }
9944
- }
9945
10258
  var app7 = new Hono();
9946
10259
  app7.post("/", async (c) => {
9947
10260
  const botParam = c.req.query("bot");
9948
10261
  const botType = botParam === "admin" ? "admin" : botParam === "public" ? "public" : null;
9949
10262
  if (!botType) {
9950
- console.error(`${TAG22} op=reject reason=missing-bot-param`);
10263
+ console.error(`${TAG23} op=reject reason=missing-bot-param`);
9951
10264
  return c.json({ ok: false }, 400);
9952
10265
  }
9953
10266
  const sp = secretPath(botType);
9954
10267
  if (!existsSync12(sp)) {
9955
- console.error(`${TAG22} op=reject reason=no-secret-file botType=${botType}`);
10268
+ console.error(`${TAG23} op=reject reason=no-secret-file botType=${botType}`);
9956
10269
  return c.json({ ok: false }, 401);
9957
10270
  }
9958
10271
  const expected = readFileSync21(sp, "utf-8").trim();
9959
10272
  const got = c.req.header("x-telegram-bot-api-secret-token") ?? "";
9960
10273
  if (got !== expected) {
9961
- console.error(`${TAG22} op=reject reason=bad-secret botType=${botType}`);
10274
+ console.error(`${TAG23} op=reject reason=bad-secret botType=${botType}`);
9962
10275
  return c.json({ ok: false }, 401);
9963
10276
  }
9964
10277
  let update;
@@ -9969,40 +10282,40 @@ app7.post("/", async (c) => {
9969
10282
  }
9970
10283
  const account = resolveAccount();
9971
10284
  if (!account) {
9972
- console.error(`${TAG22} op=reject reason=no-account`);
10285
+ console.error(`${TAG23} op=reject reason=no-account`);
9973
10286
  return c.json({ ok: true }, 200);
9974
10287
  }
9975
10288
  const decision = routeTelegramUpdate({ update, botType, config: account.config.telegram ?? {} });
9976
10289
  if (decision.kind === "ignore") {
9977
- console.error(`${TAG22} op=ignore reason=${decision.reason}`);
10290
+ console.error(`${TAG23} op=ignore reason=${decision.reason}`);
9978
10291
  return c.json({ ok: true }, 200);
9979
10292
  }
9980
10293
  if (decision.kind === "denied") {
9981
- console.error(`${TAG22} op=denied reason=${decision.reason} agentType=${decision.agentType}`);
10294
+ console.error(`${TAG23} op=denied reason=${decision.reason} agentType=${decision.agentType}`);
9982
10295
  return c.json({ ok: true }, 200);
9983
10296
  }
9984
10297
  const role = decision.agentType === "admin" ? "admin" : "public";
9985
10298
  const agentSlug = role === "admin" ? "admin" : resolveDefaultAgentSlug(account.accountDir);
9986
10299
  if (!agentSlug) {
9987
- console.error(`${TAG22} op=reject reason=no-default-agent`);
10300
+ console.error(`${TAG23} op=reject reason=no-default-agent`);
9988
10301
  return c.json({ ok: true }, 200);
9989
10302
  }
9990
- console.error(`${TAG22} op=update accountId=${account.accountId} from=${decision.senderId}`);
10303
+ console.error(`${TAG23} op=update accountId=${account.accountId} from=${decision.senderId}`);
9991
10304
  const botToken = botType === "admin" ? account.config.telegram?.adminBotToken : account.config.telegram?.publicBotToken;
9992
10305
  const { senderId, chatId, text } = decision;
9993
10306
  const gateway = getTelegramGateway();
9994
10307
  if (!gateway) {
9995
- console.error(`${TAG22} op=reject reason=gateway-not-ready from=${senderId}`);
10308
+ console.error(`${TAG23} op=reject reason=gateway-not-ready from=${senderId}`);
9996
10309
  return c.json({ ok: true }, 200);
9997
10310
  }
9998
10311
  const reply = async (replyText) => {
9999
10312
  if (!botToken) {
10000
10313
  const reason = account.config.telegram ? "no-bot-token" : "no-telegram-config";
10001
- console.error(`${TAG22} op=reply-dropped reason=${reason} botType=${botType} from=${senderId}`);
10314
+ console.error(`${TAG23} op=reply-dropped reason=${reason} botType=${botType} from=${senderId}`);
10002
10315
  return;
10003
10316
  }
10004
- const sent = await sendTelegram(botToken, chatId, replyText);
10005
- console.error(`${TAG22} op=reply-sent from=${senderId} ok=${sent.ok}${sent.ok ? "" : ` error=${sent.error}`}`);
10317
+ const sent = await sendTelegramText(botToken, chatId, replyText);
10318
+ console.error(`${TAG23} op=reply-sent from=${senderId} ok=${sent.ok}${sent.ok ? "" : ` error=${sent.error}`}`);
10006
10319
  };
10007
10320
  void gateway.handleInbound({
10008
10321
  accountId: account.accountId,
@@ -10020,7 +10333,7 @@ var telegram_default = app7;
10020
10333
  // server/routes/quickbooks.ts
10021
10334
  import { join as join21 } from "path";
10022
10335
  import { existsSync as existsSync13, readFileSync as readFileSync22, writeFileSync as writeFileSync8, mkdirSync as mkdirSync4, rmSync, renameSync as renameSync4 } from "fs";
10023
- var TAG23 = "[quickbooks]";
10336
+ var TAG24 = "[quickbooks]";
10024
10337
  var INTUIT_TOKEN_URL = "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer";
10025
10338
  var STATE_RE = /^[A-Za-z0-9_-]+$/;
10026
10339
  function pendingPath(accountDir, state) {
@@ -10033,14 +10346,14 @@ async function completeConsent(args) {
10033
10346
  const { accountDir, code, realmId } = args;
10034
10347
  const state = args.state;
10035
10348
  if (!state || !STATE_RE.test(state) || !existsSync13(pendingPath(accountDir, state))) {
10036
- console.error(`${TAG23} op=callback state=${state ?? ""} stateValid=false realmId=${realmId ?? ""}`);
10349
+ console.error(`${TAG24} op=callback state=${state ?? ""} stateValid=false realmId=${realmId ?? ""}`);
10037
10350
  return { ok: false, status: 400, message: "Invalid or unknown authorization state.", stateValid: false };
10038
10351
  }
10039
10352
  const claimFile = `${pendingPath(accountDir, state)}.claimed`;
10040
10353
  try {
10041
10354
  renameSync4(pendingPath(accountDir, state), claimFile);
10042
10355
  } catch {
10043
- console.error(`${TAG23} op=callback state=${state} stateValid=false realmId=${realmId ?? ""}`);
10356
+ console.error(`${TAG24} op=callback state=${state} stateValid=false realmId=${realmId ?? ""}`);
10044
10357
  return { ok: false, status: 400, message: "Invalid or unknown authorization state.", stateValid: false };
10045
10358
  }
10046
10359
  try {
@@ -10048,14 +10361,14 @@ async function completeConsent(args) {
10048
10361
  try {
10049
10362
  pending = JSON.parse(readFileSync22(claimFile, "utf-8"));
10050
10363
  } catch {
10051
- console.error(`${TAG23} op=callback state=${state} stateValid=false realmId=${realmId ?? ""}`);
10364
+ console.error(`${TAG24} op=callback state=${state} stateValid=false realmId=${realmId ?? ""}`);
10052
10365
  return { ok: false, status: 400, message: "Authorization state could not be read.", stateValid: false };
10053
10366
  }
10054
10367
  if (Date.now() > pending.expiresAt) {
10055
- console.error(`${TAG23} op=callback state=${state} stateValid=false realmId=${realmId ?? ""}`);
10368
+ console.error(`${TAG24} op=callback state=${state} stateValid=false realmId=${realmId ?? ""}`);
10056
10369
  return { ok: false, status: 400, message: "Authorization request expired. Generate a new consent link.", stateValid: false };
10057
10370
  }
10058
- console.error(`${TAG23} op=callback state=${state} stateValid=true realmId=${realmId ?? ""}`);
10371
+ console.error(`${TAG24} op=callback state=${state} stateValid=true realmId=${realmId ?? ""}`);
10059
10372
  if (!code || !realmId) {
10060
10373
  return { ok: false, status: 400, message: "Missing code or realmId in the callback.", stateValid: true };
10061
10374
  }
@@ -10079,12 +10392,12 @@ async function completeConsent(args) {
10079
10392
  });
10080
10393
  const text = await res.text();
10081
10394
  if (!res.ok) {
10082
- console.error(`${TAG23} op=token-exchange realmId=${realmId} persisted=false`);
10395
+ console.error(`${TAG24} op=token-exchange realmId=${realmId} persisted=false`);
10083
10396
  return { ok: false, status: 502, message: `Token exchange failed (${res.status}). Generate a new consent link to retry.`, stateValid: true };
10084
10397
  }
10085
10398
  const body = JSON.parse(text);
10086
10399
  if (!body.refresh_token) {
10087
- console.error(`${TAG23} op=token-exchange realmId=${realmId} persisted=false`);
10400
+ console.error(`${TAG24} op=token-exchange realmId=${realmId} persisted=false`);
10088
10401
  return { ok: false, status: 502, message: "Token exchange returned no refresh token.", stateValid: true };
10089
10402
  }
10090
10403
  const now = Date.now();
@@ -10097,7 +10410,7 @@ async function completeConsent(args) {
10097
10410
  mkdirSync4(join21(accountDir, "secrets"), { recursive: true });
10098
10411
  writeFileSync8(storePath(accountDir), JSON.stringify(store2, null, 2), { mode: 384 });
10099
10412
  const persisted = JSON.parse(readFileSync22(storePath(accountDir), "utf-8")).connections?.[realmId]?.refreshToken === body.refresh_token;
10100
- console.error(`${TAG23} op=token-exchange realmId=${realmId} persisted=${persisted}`);
10413
+ console.error(`${TAG24} op=token-exchange realmId=${realmId} persisted=${persisted}`);
10101
10414
  return { ok: persisted, status: persisted ? 200 : 500, message: persisted ? `Connected company ${realmId}.` : "Failed to persist the connection.", stateValid: true };
10102
10415
  } finally {
10103
10416
  rmSync(claimFile, { force: true });
@@ -10110,7 +10423,7 @@ var app8 = new Hono();
10110
10423
  app8.get("/callback", async (c) => {
10111
10424
  const account = resolveAccount();
10112
10425
  if (!account) {
10113
- console.error(`${TAG23} op=callback stateValid=false realmId= reason=no-account`);
10426
+ console.error(`${TAG24} op=callback stateValid=false realmId= reason=no-account`);
10114
10427
  return c.html(page("QuickBooks", "This install has no account configured."), 500);
10115
10428
  }
10116
10429
  const result = await completeConsent({
@@ -10132,7 +10445,7 @@ import { createHash as createHash3, randomUUID as randomUUID7 } from "crypto";
10132
10445
 
10133
10446
  // ../lib/admins-write/src/index.ts
10134
10447
  import { existsSync as existsSync14, readFileSync as readFileSync23, writeFileSync as writeFileSync9, renameSync as renameSync5, mkdirSync as mkdirSync5, readdirSync as readdirSync13, statSync as statSync8, appendFileSync as appendFileSync2 } from "fs";
10135
- import { dirname as dirname6, join as join22 } from "path";
10448
+ import { dirname as dirname7, join as join22 } from "path";
10136
10449
  function id8(value) {
10137
10450
  return value.slice(0, 8);
10138
10451
  }
@@ -10145,7 +10458,7 @@ function appendUsersAuditLine(audit, fields) {
10145
10458
  const line = `[users-audit] action=${fields.action} actor=${actor}${sess} field=${fields.field} rowsBefore=${fields.rowsBefore} rowsAfter=${fields.rowsAfter} ts=${(/* @__PURE__ */ new Date()).toISOString()}
10146
10459
  `;
10147
10460
  try {
10148
- mkdirSync5(dirname6(audit.logFile), { recursive: true, mode: 448 });
10461
+ mkdirSync5(dirname7(audit.logFile), { recursive: true, mode: 448 });
10149
10462
  appendFileSync2(audit.logFile, line, { mode: 384 });
10150
10463
  } catch (err) {
10151
10464
  console.error(
@@ -10163,7 +10476,7 @@ function logLine(input, result) {
10163
10476
  );
10164
10477
  }
10165
10478
  function writeFileAtomic(filePath, contents, mode) {
10166
- mkdirSync5(dirname6(filePath), { recursive: true });
10479
+ mkdirSync5(dirname7(filePath), { recursive: true });
10167
10480
  const tempPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
10168
10481
  writeFileSync9(tempPath, contents, mode !== void 0 ? { mode } : void 0);
10169
10482
  renameSync5(tempPath, filePath);
@@ -11012,7 +11325,7 @@ var accounts_default = app12;
11012
11325
 
11013
11326
  // server/routes/admin/logs.ts
11014
11327
  import { existsSync as existsSync18, readdirSync as readdirSync14, readFileSync as readFileSync25, statSync as statSync10 } from "fs";
11015
- import { resolve as resolve16, basename as basename7 } from "path";
11328
+ import { resolve as resolve16, basename as basename8 } from "path";
11016
11329
 
11017
11330
  // app/lib/logs-read-resolve.ts
11018
11331
  import { existsSync as existsSync17 } from "fs";
@@ -11044,7 +11357,7 @@ app13.get("/", async (c) => {
11044
11357
  if (accountLogDir) logDirs.push(accountLogDir);
11045
11358
  logDirs.push(LOG_DIR);
11046
11359
  if (fileParam) {
11047
- const safe = basename7(fileParam);
11360
+ const safe = basename8(fileParam);
11048
11361
  const searched = [];
11049
11362
  for (const dir of logDirs) {
11050
11363
  const filePath = resolve16(dir, safe);
@@ -11114,7 +11427,7 @@ app13.get("/", async (c) => {
11114
11427
  if (hit) {
11115
11428
  console.info(`[admin/logs] resolved cacheKey=${cacheKeySlice} sessionId=${sessionIdSlice} via=${primaryId === cacheKey ? "cacheKey" : primaryId === sessionKeyFromConv ? "reverse-lookup" : "sessionId-fallback"}`);
11116
11429
  try {
11117
- const filename = basename7(hit.path);
11430
+ const filename = basename8(hit.path);
11118
11431
  const buffer = readFileSync25(hit.path);
11119
11432
  const onDiskBytes = statSync10(hit.path).size;
11120
11433
  const headers = {
@@ -12048,7 +12361,7 @@ var sessions_default = app17;
12048
12361
 
12049
12362
  // app/lib/claude-agent/spawn-context.ts
12050
12363
  import { existsSync as existsSync22, readFileSync as readFileSync27, readdirSync as readdirSync16, statSync as statSync11 } from "fs";
12051
- import { dirname as dirname7, resolve as resolve19, join as join25 } from "path";
12364
+ import { dirname as dirname8, resolve as resolve19, join as join25 } from "path";
12052
12365
  async function resolveOwnerProfileBlock(accountId, userId) {
12053
12366
  if (!userId) return { ok: false, reason: "missing-user-id" };
12054
12367
  try {
@@ -12155,7 +12468,7 @@ function listPluginDirs() {
12155
12468
  }
12156
12469
  }
12157
12470
  }
12158
- const premiumRoot = resolve19(dirname7(PLATFORM_ROOT), "premium-plugins");
12471
+ const premiumRoot = resolve19(dirname8(PLATFORM_ROOT), "premium-plugins");
12159
12472
  if (existsSync22(premiumRoot)) {
12160
12473
  for (const bundle of readdirSync16(premiumRoot)) {
12161
12474
  const bundlePlugins = join25(premiumRoot, bundle, "plugins");
@@ -12369,7 +12682,7 @@ function resolveTunnelUrl() {
12369
12682
  if (!state) return null;
12370
12683
  return `https://${hostname2}.${state.domain}`;
12371
12684
  }
12372
- var TAG24 = "[claude-session-manager:wrapper]";
12685
+ var TAG25 = "[claude-session-manager:wrapper]";
12373
12686
  async function refuseIfClaudeAuthDead(c, route, sessionId) {
12374
12687
  const auth = await ensureAuth();
12375
12688
  if (auth.status !== "dead" && auth.status !== "missing") return null;
@@ -12387,12 +12700,12 @@ async function performSpawnWithInitialMessage(args) {
12387
12700
  const aboutOwner = await resolveOwnerProfileBlock(args.senderId, args.userId);
12388
12701
  const ownerMs = Date.now() - ownerStart;
12389
12702
  const aboutOwnerStatus = aboutOwner == null ? "absent" : "ok" in aboutOwner && aboutOwner.ok === false ? `unresolved:${aboutOwner.reason}` : "ok";
12390
- console.log(`${TAG24} about-owner-resolved status=${aboutOwnerStatus} ms=${ownerMs}`);
12703
+ console.log(`${TAG25} about-owner-resolved status=${aboutOwnerStatus} ms=${ownerMs}`);
12391
12704
  const dormantPlugins = computeDormantPlugins(args.senderId);
12392
12705
  const activePlugins = computeActivePlugins(args.senderId);
12393
12706
  const specialistDomains = computeSpecialistDomains(args.senderId);
12394
12707
  const tunnelUrl = resolveTunnelUrl();
12395
- console.log(`${TAG24} tunnel-url-resolved value=${tunnelUrl ?? "null"}`);
12708
+ console.log(`${TAG25} tunnel-url-resolved value=${tunnelUrl ?? "null"}`);
12396
12709
  const upstreamPayload = JSON.stringify({
12397
12710
  senderId: args.senderId,
12398
12711
  // Task 205 — pass userId through to the manager so it lands as
@@ -12419,24 +12732,24 @@ async function performSpawnWithInitialMessage(args) {
12419
12732
  // unshapely values.
12420
12733
  conversationNodeId: args.conversationNodeId
12421
12734
  });
12422
- console.log(`${TAG24} forward-spawn-start managerBase=${managerBase("claude-session-manager:wrapper")} bytes=${upstreamPayload.length} hidden=${args.hidden} specialist=${args.specialist ?? "none"}`);
12735
+ console.log(`${TAG25} forward-spawn-start managerBase=${managerBase("claude-session-manager:wrapper")} bytes=${upstreamPayload.length} hidden=${args.hidden} specialist=${args.specialist ?? "none"}`);
12423
12736
  const forwardStart = Date.now();
12424
12737
  const upstream = await fetch(`${managerBase("claude-session-manager:wrapper")}/public-spawn`, {
12425
12738
  method: "POST",
12426
12739
  headers: { "content-type": "application/json" },
12427
12740
  body: upstreamPayload
12428
12741
  }).catch((err) => {
12429
- console.error(`${TAG24} fetch-failed op=spawn message=${err instanceof Error ? err.message : String(err)} ms=${Date.now() - forwardStart}`);
12742
+ console.error(`${TAG25} fetch-failed op=spawn message=${err instanceof Error ? err.message : String(err)} ms=${Date.now() - forwardStart}`);
12430
12743
  return null;
12431
12744
  });
12432
12745
  if (!upstream) return {
12433
12746
  response: new Response(JSON.stringify({ error: "manager-unreachable" }), { status: 503, headers: { "content-type": "application/json" } }),
12434
12747
  claudeSessionId: null
12435
12748
  };
12436
- console.log(`${TAG24} forward-spawn-done status=${upstream.status} ms=${Date.now() - forwardStart}`);
12749
+ console.log(`${TAG25} forward-spawn-done status=${upstream.status} ms=${Date.now() - forwardStart}`);
12437
12750
  if (args.initialMessage) {
12438
12751
  const inputBytes = Buffer.byteLength(args.initialMessage, "utf8");
12439
- console.log(`${TAG24} initial-message-inlined bytes=${inputBytes}`);
12752
+ console.log(`${TAG25} initial-message-inlined bytes=${inputBytes}`);
12440
12753
  }
12441
12754
  const bodyText = await upstream.text().catch(() => "");
12442
12755
  let claudeSessionId = null;
@@ -12471,7 +12784,7 @@ app18.post("/", async (c) => {
12471
12784
  if (refusal) return refusal;
12472
12785
  const senderId = getAccountIdForSession(cacheKey) ?? "";
12473
12786
  if (!senderId) {
12474
- console.error(`${TAG24} reject reason=no-account-id cacheKey-prefix=${cacheKey.slice(0, 8)}`);
12787
+ console.error(`${TAG25} reject reason=no-account-id cacheKey-prefix=${cacheKey.slice(0, 8)}`);
12475
12788
  return c.json({ error: "admin-account-not-resolved" }, 500);
12476
12789
  }
12477
12790
  const userId = getUserIdForSession(cacheKey) ?? void 0;
@@ -12480,7 +12793,7 @@ app18.post("/", async (c) => {
12480
12793
  const permissionMode = typeof body.permissionMode === "string" ? body.permissionMode : void 0;
12481
12794
  const specialist = typeof body.specialist === "string" && /^[A-Za-z0-9_-]{1,64}$/.test(body.specialist) ? body.specialist : void 0;
12482
12795
  const model = typeof body.model === "string" && /^[A-Za-z0-9._-]{1,64}$/.test(body.model) ? body.model : void 0;
12483
- console.log(`${TAG24} spawn-request-in surface=cookie accountId=${senderId.slice(0, 8)} userId=${userId ? userId.slice(0, 8) : "absent"} channel=${channel} permissionMode=${permissionMode ?? "default"} specialist=${specialist ?? "none"} model=${model ?? "default"} initialMessage=${initialMessage ? "yes" : "no"}`);
12796
+ console.log(`${TAG25} spawn-request-in surface=cookie accountId=${senderId.slice(0, 8)} userId=${userId ? userId.slice(0, 8) : "absent"} channel=${channel} permissionMode=${permissionMode ?? "default"} specialist=${specialist ?? "none"} model=${model ?? "default"} initialMessage=${initialMessage ? "yes" : "no"}`);
12484
12797
  const conversationNodeId = cacheKey ? getSessionIdForSession(cacheKey) : void 0;
12485
12798
  const { response, claudeSessionId } = await performSpawnWithInitialMessage({
12486
12799
  senderId,
@@ -12499,33 +12812,33 @@ app18.post("/", async (c) => {
12499
12812
  claudeSessionId,
12500
12813
  senderId
12501
12814
  );
12502
- console.log(`${TAG24} route-done surface=cookie status=${response.status} route-ms=${Date.now() - routeStart}`);
12815
+ console.log(`${TAG25} route-done surface=cookie status=${response.status} route-ms=${Date.now() - routeStart}`);
12503
12816
  return response;
12504
12817
  });
12505
12818
  var claude_sessions_default = app18;
12506
12819
 
12507
12820
  // server/routes/admin/log-ingest.ts
12508
- var TAG25 = "[log-ingest]";
12821
+ var TAG26 = "[log-ingest]";
12509
12822
  var TAG_PATTERN = /^[A-Za-z0-9_:-]{1,32}$/;
12510
12823
  var LEVELS = /* @__PURE__ */ new Set(["debug", "info", "warn", "error"]);
12511
12824
  var MAX_LINE_BYTES = 4096;
12512
12825
  var app19 = new Hono();
12513
- function isLoopbackAddr(addr) {
12826
+ function isLoopbackAddr2(addr) {
12514
12827
  return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
12515
12828
  }
12516
12829
  app19.post("/", async (c) => {
12517
12830
  const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
12518
- if (!isLoopbackAddr(remoteAddr)) {
12519
- console.error(`${TAG25} reject reason=non-loopback remoteAddr=${remoteAddr}`);
12831
+ if (!isLoopbackAddr2(remoteAddr)) {
12832
+ console.error(`${TAG26} reject reason=non-loopback remoteAddr=${remoteAddr}`);
12520
12833
  return c.json({ error: "log-ingest-loopback-only" }, 403);
12521
12834
  }
12522
12835
  const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
12523
12836
  const xffRaw = Array.isArray(xffHeader) ? xffHeader.join(",") : typeof xffHeader === "string" ? xffHeader : "";
12524
12837
  if (xffRaw.length > 0) {
12525
12838
  const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
12526
- const offender = tokens.find((t) => !isLoopbackAddr(t));
12839
+ const offender = tokens.find((t) => !isLoopbackAddr2(t));
12527
12840
  if (offender !== void 0) {
12528
- console.error(`${TAG25} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
12841
+ console.error(`${TAG26} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
12529
12842
  return c.json({ error: "log-ingest-loopback-only" }, 403);
12530
12843
  }
12531
12844
  }
@@ -12567,18 +12880,18 @@ var ALLOWED_EVENTS = /* @__PURE__ */ new Set([
12567
12880
  ]);
12568
12881
  var app20 = new Hono();
12569
12882
  app20.post("/", async (c) => {
12570
- const TAG38 = "[admin:events]";
12883
+ const TAG39 = "[admin:events]";
12571
12884
  let body;
12572
12885
  try {
12573
12886
  body = await c.req.json();
12574
12887
  } catch (err) {
12575
12888
  const detail = err instanceof Error ? err.message : String(err);
12576
- console.error(`${TAG38} reject reason=body-not-json detail=${detail}`);
12889
+ console.error(`${TAG39} reject reason=body-not-json detail=${detail}`);
12577
12890
  return c.json({ ok: false, detail: "Request body was not valid JSON" }, 400);
12578
12891
  }
12579
12892
  const event = typeof body.event === "string" ? body.event : "";
12580
12893
  if (!ALLOWED_EVENTS.has(event)) {
12581
- console.error(`${TAG38} reject reason=event-not-allowed event=${JSON.stringify(event)}`);
12894
+ console.error(`${TAG39} reject reason=event-not-allowed event=${JSON.stringify(event)}`);
12582
12895
  return c.json({ ok: false, detail: `Event "${event}" is not allowed` }, 400);
12583
12896
  }
12584
12897
  const rawFields = body.fields && typeof body.fields === "object" ? body.fields : {};
@@ -12604,7 +12917,7 @@ import { createReadStream as createReadStream2, createWriteStream as createWrite
12604
12917
  import { readdir as readdir3, readFile as readFile4, stat as stat4, mkdir as mkdir3, unlink as unlink2, rename } from "fs/promises";
12605
12918
  import { realpathSync as realpathSync5 } from "fs";
12606
12919
  import { randomUUID as randomUUID9 } from "crypto";
12607
- import { basename as basename9, dirname as dirname8, join as join27, relative as relative4, resolve as resolve22, sep as sep6 } from "path";
12920
+ import { basename as basename10, dirname as dirname9, join as join27, relative as relative4, resolve as resolve22, sep as sep6 } from "path";
12608
12921
  import { Readable as Readable2 } from "stream";
12609
12922
 
12610
12923
  // ../lib/graph-trash/src/index.ts
@@ -12922,7 +13235,7 @@ async function cascadeDeleteDocument(params) {
12922
13235
 
12923
13236
  // app/lib/file-index.ts
12924
13237
  import * as fsp from "fs/promises";
12925
- import { resolve as resolve21, relative as relative3, join as join26, basename as basename8, extname as extname2, sep as sep5 } from "path";
13238
+ import { resolve as resolve21, relative as relative3, join as join26, basename as basename9, extname as extname2, sep as sep5 } from "path";
12926
13239
  import { tmpdir as tmpdir2 } from "os";
12927
13240
  import { execFile as execFile2 } from "child_process";
12928
13241
  import { promisify as promisify2 } from "util";
@@ -13060,8 +13373,8 @@ async function extractFileContent(absolute) {
13060
13373
  }
13061
13374
  }
13062
13375
  async function readDisplayName(absolute) {
13063
- const dir = absolute.slice(0, absolute.length - basename8(absolute).length);
13064
- const base = basename8(absolute);
13376
+ const dir = absolute.slice(0, absolute.length - basename9(absolute).length);
13377
+ const base = basename9(absolute);
13065
13378
  const dot = base.lastIndexOf(".");
13066
13379
  const stem = dot === -1 ? base : base.slice(0, dot);
13067
13380
  if (base === `${stem}.meta.json`) return null;
@@ -13199,7 +13512,7 @@ async function cascadeTrashLinkedKnowledgeDocs(session, accountId, relativePaths
13199
13512
  return { kds, sections, chunks };
13200
13513
  }
13201
13514
  async function buildArtifact(accountId, wf, embed2) {
13202
- const name = basename8(wf.absolute);
13515
+ const name = basename9(wf.absolute);
13203
13516
  const { content, route } = await extractFileContent(wf.absolute);
13204
13517
  const displayName = await readDisplayName(wf.absolute);
13205
13518
  const embedInput = `${name}
@@ -13729,7 +14042,7 @@ app21.get("/download", requireAdminSession, async (c) => {
13729
14042
  if (!info.isFile()) {
13730
14043
  return c.json({ error: "Path is not a file" }, 400);
13731
14044
  }
13732
- const filename = basename9(absolute);
14045
+ const filename = basename10(absolute);
13733
14046
  const mimeType = detectMimeType(absolute);
13734
14047
  const nodeStream = createReadStream2(absolute);
13735
14048
  const webStream = Readable2.toWeb(nodeStream);
@@ -13834,7 +14147,7 @@ app21.get("/download-zip", requireAdminSession, async (c) => {
13834
14147
  const info = await stat4(absolute);
13835
14148
  if (info.isDirectory()) {
13836
14149
  dirsWalked++;
13837
- const parentAbs = dirname8(absolute);
14150
+ const parentAbs = dirname9(absolute);
13838
14151
  for await (const fileAbs of walkRegularFiles(absolute)) {
13839
14152
  const within = relative4(absolute, fileAbs).split(sep6).join("/");
13840
14153
  const fileRel = relPath === "" || relPath === "." ? within : `${relPath}/${within}`;
@@ -13845,7 +14158,7 @@ app21.get("/download-zip", requireAdminSession, async (c) => {
13845
14158
  if (over) return over;
13846
14159
  }
13847
14160
  } else if (info.isFile()) {
13848
- const over = await addFile(absolute, info.size, basename9(absolute));
14161
+ const over = await addFile(absolute, info.size, basename10(absolute));
13849
14162
  if (over) return over;
13850
14163
  } else {
13851
14164
  return c.json({ error: "Path is not a file or directory" }, 400);
@@ -13874,12 +14187,12 @@ function dataUploadCeiling() {
13874
14187
  }
13875
14188
  async function resolveUploadTarget(c, accountId, uid) {
13876
14189
  const rawName = c.req.query("filename") ?? "";
13877
- const safeName = basename9(rawName).replace(/[\0/\\]/g, "_");
14190
+ const safeName = basename10(rawName).replace(/[\0/\\]/g, "_");
13878
14191
  if (!safeName) return { ok: false, status: 400, error: "filename query param required" };
13879
14192
  const rawRelpath = c.req.query("relpath") ?? "";
13880
14193
  const relpath = rawRelpath !== "" ? rawRelpath : null;
13881
14194
  const token = c.req.query("token") ?? "";
13882
- const finalName = relpath ? basename9(relpath).replace(/[\0/\\]/g, "_") : `${Date.now()}-${safeName}`;
14195
+ const finalName = relpath ? basename10(relpath).replace(/[\0/\\]/g, "_") : `${Date.now()}-${safeName}`;
13883
14196
  const mimeType = (c.req.header("content-type") ?? "").split(";")[0].trim();
13884
14197
  if (!SUPPORTED_MIME_TYPES.has(mimeType)) {
13885
14198
  console.error(`[data-upload] op=reject-mime uid=${uid} mime="${mimeType}" token=${token} rel="${relpath ?? ""}"`);
@@ -13888,7 +14201,7 @@ async function resolveUploadTarget(c, accountId, uid) {
13888
14201
  const rawDir = c.req.query("path") ?? "";
13889
14202
  const base = resolveOwnAccountWrite(rawDir, accountId, "POST /api/admin/files/upload");
13890
14203
  if (!base.ok) return { ok: false, status: base.status, error: base.error };
13891
- const relDir = relpath ? dirname8(relpath) : ".";
14204
+ const relDir = relpath ? dirname9(relpath) : ".";
13892
14205
  const destRel = relDir === "." ? base.relative : join27(base.relative, relDir);
13893
14206
  if (crossesForeignAccountPartition(destRel, accountId)) {
13894
14207
  console.error(`[data] account-scope-blocked endpoint="POST /api/admin/files/upload" path="${destRel}" account=${accountId.slice(0, 8)}\u2026`);
@@ -14249,7 +14562,7 @@ app21.delete("/", requireAdminSession, async (c) => {
14249
14562
  console.error(`[data] account-scope-blocked endpoint="DELETE /api/admin/files" path="${relPath}" account=${accountId.slice(0, 8)}\u2026`);
14250
14563
  return c.json({ error: "Not found" }, 404);
14251
14564
  }
14252
- const base = basename9(absolute);
14565
+ const base = basename10(absolute);
14253
14566
  const protection = isProtectedFromDeletion(relPath);
14254
14567
  if (protection.protected) {
14255
14568
  console.error(`[data] file-delete blocked path="${relPath}" reason="protected" rule="${protection.rule}"`);
@@ -14265,7 +14578,7 @@ app21.delete("/", requireAdminSession, async (c) => {
14265
14578
  }
14266
14579
  const dot = base.lastIndexOf(".");
14267
14580
  const stem = dot === -1 ? base : base.slice(0, dot);
14268
- const sidecarPath = UUID_RE3.test(stem) && base !== `${stem}.meta.json` ? join27(dirname8(absolute), `${stem}.meta.json`) : null;
14581
+ const sidecarPath = UUID_RE3.test(stem) && base !== `${stem}.meta.json` ? join27(dirname9(absolute), `${stem}.meta.json`) : null;
14269
14582
  await unlink2(absolute);
14270
14583
  if (sidecarPath) {
14271
14584
  try {
@@ -14361,8 +14674,8 @@ app21.post("/rename", requireAdminSession, async (c) => {
14361
14674
  console.error(`[data] file-rename blocked path="${src.relative}" reason="protected"`);
14362
14675
  return c.json({ error: "Protected entry \u2014 refusing to rename" }, 403);
14363
14676
  }
14364
- const destAbs = resolve22(dirname8(src.absolute), newName);
14365
- const newRel = `${dirname8(src.relative)}/${newName}`.replace(/^\.\//, "");
14677
+ const destAbs = resolve22(dirname9(src.absolute), newName);
14678
+ const newRel = `${dirname9(src.relative)}/${newName}`.replace(/^\.\//, "");
14366
14679
  if (isProtectedFromRename(newRel)) {
14367
14680
  console.error(`[data] file-rename blocked path="${newRel}" reason="protected-target"`);
14368
14681
  return c.json({ error: "That name is reserved" }, 403);
@@ -16493,7 +16806,7 @@ var graph_default_view_default = app27;
16493
16806
 
16494
16807
  // server/routes/admin/sidebar-artefacts.ts
16495
16808
  import { readdir as readdir4, stat as stat5 } from "fs/promises";
16496
- import { resolve as resolve23, relative as relative5, isAbsolute as isAbsolute2, sep as sep7, basename as basename10 } from "path";
16809
+ import { resolve as resolve23, relative as relative5, isAbsolute as isAbsolute2, sep as sep7, basename as basename11 } from "path";
16497
16810
  import { existsSync as existsSync25 } from "fs";
16498
16811
  var LIMIT = 50;
16499
16812
  var ADMIN_AGENT_FILES = ["IDENTITY.md", "SOUL.md", "KNOWLEDGE.md"];
@@ -16546,7 +16859,7 @@ async function fetchAccountFileArtefacts(accountId) {
16546
16859
  const modifiedAt = r.get("modifiedAt") ?? "";
16547
16860
  return {
16548
16861
  id: `account-file:${relativePath}`,
16549
- name: displayName ?? basename10(relativePath),
16862
+ name: displayName ?? basename11(relativePath),
16550
16863
  kind: "account-file",
16551
16864
  updatedAt: modifiedAt,
16552
16865
  mimeType,
@@ -17393,7 +17706,7 @@ function managerLogFollowUrl(sessionId, opts) {
17393
17706
  }
17394
17707
 
17395
17708
  // server/routes/admin/linkedin-ingest.ts
17396
- var TAG26 = "[linkedin-ingest-route]";
17709
+ var TAG27 = "[linkedin-ingest-route]";
17397
17710
  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}$/;
17398
17711
  var ISO = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?(?:Z|[+-]\d{2}:\d{2})$/;
17399
17712
  var LINKEDIN_URL = /^https:\/\/www\.linkedin\.com\//;
@@ -17497,29 +17810,29 @@ app37.post("/", requireAdminSession, async (c) => {
17497
17810
  try {
17498
17811
  body = await c.req.json();
17499
17812
  } catch {
17500
- console.error(TAG26 + " rejected status=400 reason=schema:body-not-json");
17813
+ console.error(TAG27 + " rejected status=400 reason=schema:body-not-json");
17501
17814
  return c.json({ ok: false, error: "schema", reason: "body-not-json" }, 400);
17502
17815
  }
17503
17816
  const v = validate(body);
17504
17817
  if (!v.ok) {
17505
- console.error(TAG26 + " rejected status=" + v.status + " reason=" + v.reason + " missing=" + v.missing.join(","));
17818
+ console.error(TAG27 + " rejected status=" + v.status + " reason=" + v.reason + " missing=" + v.missing.join(","));
17506
17819
  return c.json({ ok: false, error: v.error, reason: v.reason, missing: v.missing }, v.status);
17507
17820
  }
17508
17821
  const envelope = v.envelope;
17509
17822
  const cacheKey = c.var.cacheKey ?? "";
17510
17823
  const senderId = getAccountIdForSession(cacheKey) ?? "";
17511
17824
  if (!senderId) {
17512
- console.error(TAG26 + " rejected status=500 reason=admin-account-not-resolved");
17825
+ console.error(TAG27 + " rejected status=500 reason=admin-account-not-resolved");
17513
17826
  return c.json({ ok: false, error: "admin-account-not-resolved" }, 500);
17514
17827
  }
17515
17828
  const payloadBytes = JSON.stringify(envelope).length;
17516
17829
  console.log(
17517
- TAG26 + " received kind=" + envelope.kind + " account=" + senderId.slice(0, 8) + " pageUrl=" + envelope.pageUrl + " dispatchId=" + envelope.dispatchId + " payloadBytes=" + payloadBytes
17830
+ TAG27 + " received kind=" + envelope.kind + " account=" + senderId.slice(0, 8) + " pageUrl=" + envelope.pageUrl + " dispatchId=" + envelope.dispatchId + " payloadBytes=" + payloadBytes
17518
17831
  );
17519
17832
  const initialMessage = buildInitialMessage(envelope);
17520
17833
  const spawnStart = Date.now();
17521
17834
  const sessionId = randomUUID12();
17522
- console.log(TAG26 + " route target=rc-spawn dispatchId=" + envelope.dispatchId + " sessionId=" + sessionId.slice(0, 8));
17835
+ console.log(TAG27 + " route target=rc-spawn dispatchId=" + envelope.dispatchId + " sessionId=" + sessionId.slice(0, 8));
17523
17836
  const spawned = await managerRcSpawn({
17524
17837
  sessionId,
17525
17838
  initialMessage,
@@ -17528,12 +17841,12 @@ app37.post("/", requireAdminSession, async (c) => {
17528
17841
  });
17529
17842
  if ("error" in spawned) {
17530
17843
  console.error(
17531
- TAG26 + " dispatch-failed dispatchId=" + envelope.dispatchId + " status=" + spawned.status + " ms=" + (Date.now() - spawnStart) + " message=" + spawned.error
17844
+ TAG27 + " dispatch-failed dispatchId=" + envelope.dispatchId + " status=" + spawned.status + " ms=" + (Date.now() - spawnStart) + " message=" + spawned.error
17532
17845
  );
17533
17846
  return c.json({ ok: false, error: "dispatch-failed", upstreamStatus: spawned.status, detail: spawned.error }, 502);
17534
17847
  }
17535
17848
  console.log(
17536
- TAG26 + " dispatched dispatchId=" + envelope.dispatchId + " taskId=" + spawned.sessionId + " ms=" + (Date.now() - spawnStart)
17849
+ TAG27 + " dispatched dispatchId=" + envelope.dispatchId + " taskId=" + spawned.sessionId + " ms=" + (Date.now() - spawnStart)
17537
17850
  );
17538
17851
  return c.json({ ok: true, dispatchId: envelope.dispatchId, taskId: spawned.sessionId }, 202);
17539
17852
  });
@@ -17541,7 +17854,7 @@ var linkedin_ingest_default = app37;
17541
17854
 
17542
17855
  // server/routes/admin/post-turn-context.ts
17543
17856
  import neo4j3 from "neo4j-driver";
17544
- var TAG27 = "[post-turn-context]";
17857
+ var TAG28 = "[post-turn-context]";
17545
17858
  var STRIPPED_PROPERTIES2 = /* @__PURE__ */ new Set([
17546
17859
  "embedding",
17547
17860
  "passwordHash",
@@ -17549,7 +17862,7 @@ var STRIPPED_PROPERTIES2 = /* @__PURE__ */ new Set([
17549
17862
  "otpCode",
17550
17863
  "cacheKey"
17551
17864
  ]);
17552
- function isLoopbackAddr2(addr) {
17865
+ function isLoopbackAddr3(addr) {
17553
17866
  return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
17554
17867
  }
17555
17868
  function convertValue(value) {
@@ -17582,17 +17895,17 @@ function pruneProperties(raw) {
17582
17895
  var app38 = new Hono();
17583
17896
  app38.get("/", async (c) => {
17584
17897
  const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
17585
- if (!isLoopbackAddr2(remoteAddr)) {
17586
- console.error(`${TAG27} reject reason=non-loopback remoteAddr=${remoteAddr}`);
17898
+ if (!isLoopbackAddr3(remoteAddr)) {
17899
+ console.error(`${TAG28} reject reason=non-loopback remoteAddr=${remoteAddr}`);
17587
17900
  return c.json({ error: "post-turn-context-loopback-only" }, 403);
17588
17901
  }
17589
17902
  const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
17590
17903
  const xffRaw = Array.isArray(xffHeader) ? xffHeader.join(",") : typeof xffHeader === "string" ? xffHeader : "";
17591
17904
  if (xffRaw.length > 0) {
17592
17905
  const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
17593
- const offender = tokens.find((t) => !isLoopbackAddr2(t));
17906
+ const offender = tokens.find((t) => !isLoopbackAddr3(t));
17594
17907
  if (offender !== void 0) {
17595
- console.error(`${TAG27} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
17908
+ console.error(`${TAG28} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
17596
17909
  return c.json({ error: "post-turn-context-loopback-only" }, 403);
17597
17910
  }
17598
17911
  }
@@ -17624,7 +17937,7 @@ app38.get("/", async (c) => {
17624
17937
  writes.sort((a, b) => a.sortKey.localeCompare(b.sortKey));
17625
17938
  const total = Date.now() - started;
17626
17939
  console.log(
17627
- `${TAG27} sessionId=${sessionId} accountId=${accountId} writes=${writes.length} ms=${total}`
17940
+ `${TAG28} sessionId=${sessionId} accountId=${accountId} writes=${writes.length} ms=${total}`
17628
17941
  );
17629
17942
  return c.json({
17630
17943
  writes: writes.map(({ elementId, labels, properties }) => ({ elementId, labels, properties }))
@@ -17633,7 +17946,7 @@ app38.get("/", async (c) => {
17633
17946
  const elapsed = Date.now() - started;
17634
17947
  const message = err instanceof Error ? err.message : String(err);
17635
17948
  console.error(
17636
- `${TAG27} neo4j-unreachable sessionId=${sessionId} ms=${elapsed} err="${message}"`
17949
+ `${TAG28} neo4j-unreachable sessionId=${sessionId} ms=${elapsed} err="${message}"`
17637
17950
  );
17638
17951
  return c.json({ error: `post-turn-context unavailable: ${message}` }, 503);
17639
17952
  } finally {
@@ -17746,24 +18059,24 @@ function formatPreviousContext(writes) {
17746
18059
  }
17747
18060
 
17748
18061
  // server/routes/admin/public-session-context.ts
17749
- var TAG28 = "[public-session-context]";
17750
- function isLoopbackAddr3(addr) {
18062
+ var TAG29 = "[public-session-context]";
18063
+ function isLoopbackAddr4(addr) {
17751
18064
  return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
17752
18065
  }
17753
18066
  var app39 = new Hono();
17754
18067
  app39.get("/", async (c) => {
17755
18068
  const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
17756
- if (!isLoopbackAddr3(remoteAddr)) {
17757
- console.error(`${TAG28} reject reason=non-loopback remoteAddr=${remoteAddr}`);
18069
+ if (!isLoopbackAddr4(remoteAddr)) {
18070
+ console.error(`${TAG29} reject reason=non-loopback remoteAddr=${remoteAddr}`);
17758
18071
  return c.json({ error: "public-session-context-loopback-only" }, 403);
17759
18072
  }
17760
18073
  const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
17761
18074
  const xffRaw = Array.isArray(xffHeader) ? xffHeader.join(",") : typeof xffHeader === "string" ? xffHeader : "";
17762
18075
  if (xffRaw.length > 0) {
17763
18076
  const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
17764
- const offender = tokens.find((t) => !isLoopbackAddr3(t));
18077
+ const offender = tokens.find((t) => !isLoopbackAddr4(t));
17765
18078
  if (offender !== void 0) {
17766
- console.error(`${TAG28} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
18079
+ console.error(`${TAG29} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
17767
18080
  return c.json({ error: "public-session-context-loopback-only" }, 403);
17768
18081
  }
17769
18082
  }
@@ -17777,14 +18090,14 @@ app39.get("/", async (c) => {
17777
18090
  const writes = await fetchSliceWrites(session, sliceToken, accountId);
17778
18091
  const total = Date.now() - started;
17779
18092
  console.log(
17780
- `${TAG28} sliceToken=${sliceToken.slice(0, 8)} writes=${writes.length} ms=${total}`
18093
+ `${TAG29} sliceToken=${sliceToken.slice(0, 8)} writes=${writes.length} ms=${total}`
17781
18094
  );
17782
18095
  return c.json({ writes });
17783
18096
  } catch (err) {
17784
18097
  const elapsed = Date.now() - started;
17785
18098
  const message = err instanceof Error ? err.message : String(err);
17786
18099
  console.error(
17787
- `${TAG28} neo4j-unreachable sliceToken=${sliceToken.slice(0, 8)} ms=${elapsed} err="${message}"`
18100
+ `${TAG29} neo4j-unreachable sliceToken=${sliceToken.slice(0, 8)} ms=${elapsed} err="${message}"`
17788
18101
  );
17789
18102
  return c.json({ error: `public-session-context unavailable: ${message}` }, 503);
17790
18103
  } finally {
@@ -17803,24 +18116,24 @@ function getWebchatGateway() {
17803
18116
  }
17804
18117
 
17805
18118
  // server/routes/admin/public-session-exit.ts
17806
- var TAG29 = "[public-session-exit-route]";
17807
- function isLoopbackAddr4(addr) {
18119
+ var TAG30 = "[public-session-exit-route]";
18120
+ function isLoopbackAddr5(addr) {
17808
18121
  return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
17809
18122
  }
17810
18123
  var app40 = new Hono();
17811
18124
  app40.post("/", async (c) => {
17812
18125
  const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
17813
- if (!isLoopbackAddr4(remoteAddr)) {
17814
- console.error(`${TAG29} reject reason=non-loopback remoteAddr=${remoteAddr}`);
18126
+ if (!isLoopbackAddr5(remoteAddr)) {
18127
+ console.error(`${TAG30} reject reason=non-loopback remoteAddr=${remoteAddr}`);
17815
18128
  return c.json({ error: "public-session-exit-loopback-only" }, 403);
17816
18129
  }
17817
18130
  const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
17818
18131
  const xffRaw = Array.isArray(xffHeader) ? xffHeader.join(",") : typeof xffHeader === "string" ? xffHeader : "";
17819
18132
  if (xffRaw.length > 0) {
17820
18133
  const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
17821
- const offender = tokens.find((t) => !isLoopbackAddr4(t));
18134
+ const offender = tokens.find((t) => !isLoopbackAddr5(t));
17822
18135
  if (offender !== void 0) {
17823
- console.error(`${TAG29} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
18136
+ console.error(`${TAG30} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
17824
18137
  return c.json({ error: "public-session-exit-loopback-only" }, 403);
17825
18138
  }
17826
18139
  }
@@ -17834,7 +18147,7 @@ app40.post("/", async (c) => {
17834
18147
  if (!sessionId) return c.json({ error: "sessionId required" }, 400);
17835
18148
  const gateway = getWebchatGateway();
17836
18149
  if (!gateway) {
17837
- console.error(`${TAG29} reject reason=gateway-unset sessionId=${sessionId.slice(0, 8)}`);
18150
+ console.error(`${TAG30} reject reason=gateway-unset sessionId=${sessionId.slice(0, 8)}`);
17838
18151
  return c.json({ error: "webchat-gateway-unset" }, 503);
17839
18152
  }
17840
18153
  gateway.handlePublicSessionExit(sessionId);
@@ -17843,24 +18156,24 @@ app40.post("/", async (c) => {
17843
18156
  var public_session_exit_default = app40;
17844
18157
 
17845
18158
  // server/routes/admin/access-session-evict.ts
17846
- var TAG30 = "[access-session-evict]";
17847
- function isLoopbackAddr5(addr) {
18159
+ var TAG31 = "[access-session-evict]";
18160
+ function isLoopbackAddr6(addr) {
17848
18161
  return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
17849
18162
  }
17850
18163
  var app41 = new Hono();
17851
18164
  app41.post("/", async (c) => {
17852
18165
  const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
17853
- if (!isLoopbackAddr5(remoteAddr)) {
17854
- console.error(`${TAG30} reject reason=non-loopback remoteAddr=${remoteAddr}`);
18166
+ if (!isLoopbackAddr6(remoteAddr)) {
18167
+ console.error(`${TAG31} reject reason=non-loopback remoteAddr=${remoteAddr}`);
17855
18168
  return c.json({ error: "access-session-evict-loopback-only" }, 403);
17856
18169
  }
17857
18170
  const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
17858
18171
  const xffRaw = Array.isArray(xffHeader) ? xffHeader.join(",") : typeof xffHeader === "string" ? xffHeader : "";
17859
18172
  if (xffRaw.length > 0) {
17860
18173
  const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
17861
- const offender = tokens.find((t) => !isLoopbackAddr5(t));
18174
+ const offender = tokens.find((t) => !isLoopbackAddr6(t));
17862
18175
  if (offender !== void 0) {
17863
- console.error(`${TAG30} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
18176
+ console.error(`${TAG31} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
17864
18177
  return c.json({ error: "access-session-evict-loopback-only" }, 403);
17865
18178
  }
17866
18179
  }
@@ -17873,31 +18186,31 @@ app41.post("/", async (c) => {
17873
18186
  const grantId = typeof body.grantId === "string" ? body.grantId.trim() : "";
17874
18187
  if (!grantId) return c.json({ error: "grantId required" }, 400);
17875
18188
  const dropped = evictAccessSessionsByGrant(grantId);
17876
- console.log(`${TAG30} grantId=${grantId} dropped=${dropped}`);
18189
+ console.log(`${TAG31} grantId=${grantId} dropped=${dropped}`);
17877
18190
  return c.json({ ok: true, dropped });
17878
18191
  });
17879
18192
  var access_session_evict_default = app41;
17880
18193
 
17881
18194
  // server/routes/admin/enrol-person.ts
17882
- var TAG31 = "[enrol]";
17883
- function isLoopbackAddr6(addr) {
18195
+ var TAG32 = "[enrol]";
18196
+ function isLoopbackAddr7(addr) {
17884
18197
  return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
17885
18198
  }
17886
18199
  var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
17887
18200
  var app42 = new Hono();
17888
18201
  app42.post("/", async (c) => {
17889
18202
  const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
17890
- if (!isLoopbackAddr6(remoteAddr)) {
17891
- console.error(`${TAG31} reject reason=non-loopback remoteAddr=${remoteAddr}`);
18203
+ if (!isLoopbackAddr7(remoteAddr)) {
18204
+ console.error(`${TAG32} reject reason=non-loopback remoteAddr=${remoteAddr}`);
17892
18205
  return c.json({ error: "enrol-person-loopback-only" }, 403);
17893
18206
  }
17894
18207
  const xffHeader = c.env?.incoming?.headers?.["x-forwarded-for"];
17895
18208
  const xffRaw = Array.isArray(xffHeader) ? xffHeader.join(",") : typeof xffHeader === "string" ? xffHeader : "";
17896
18209
  if (xffRaw.length > 0) {
17897
18210
  const tokens = xffRaw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
17898
- const offender = tokens.find((t) => !isLoopbackAddr6(t));
18211
+ const offender = tokens.find((t) => !isLoopbackAddr7(t));
17899
18212
  if (offender !== void 0) {
17900
- console.error(`${TAG31} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
18213
+ console.error(`${TAG32} reject reason=xff-non-loopback xff=${JSON.stringify(xffRaw)}`);
17901
18214
  return c.json({ error: "enrol-person-loopback-only" }, 403);
17902
18215
  }
17903
18216
  }
@@ -17922,7 +18235,7 @@ app42.post("/", async (c) => {
17922
18235
  }
17923
18236
  const accountId = process.env.ACCOUNT_ID ?? "";
17924
18237
  if (!accountId) {
17925
- console.error(`${TAG31} op=person result=no-account-id`);
18238
+ console.error(`${TAG32} op=person result=no-account-id`);
17926
18239
  return c.json({ error: "no-account-id" }, 500);
17927
18240
  }
17928
18241
  let personId;
@@ -17930,7 +18243,7 @@ app42.post("/", async (c) => {
17930
18243
  personId = await enrolPerson({ accountId, phone, email, agentSlug });
17931
18244
  } catch (err) {
17932
18245
  console.error(
17933
- `${TAG31} op=person result=write-failed agentSlug=${agentSlug} contact=${maskContact(email)} err="${err instanceof Error ? err.message : String(err)}"`
18246
+ `${TAG32} op=person result=write-failed agentSlug=${agentSlug} contact=${maskContact(email)} err="${err instanceof Error ? err.message : String(err)}"`
17934
18247
  );
17935
18248
  return c.json({ error: "enrol-failed" }, 500);
17936
18249
  }
@@ -17940,11 +18253,11 @@ app42.post("/", async (c) => {
17940
18253
  if (g) grant = { grantId: g.grantId, status: g.status, contactValue: g.contactValue };
17941
18254
  } catch (err) {
17942
18255
  console.error(
17943
- `${TAG31} op=person result=grant-lookup-failed contact=${maskContact(email)} err="${err instanceof Error ? err.message : String(err)}"`
18256
+ `${TAG32} op=person result=grant-lookup-failed contact=${maskContact(email)} err="${err instanceof Error ? err.message : String(err)}"`
17944
18257
  );
17945
18258
  }
17946
18259
  console.log(
17947
- `${TAG31} op=person personId=${personId} account=${accountId} agentSlug=${agentSlug} contact=${maskContact(email)}`
18260
+ `${TAG32} op=person personId=${personId} account=${accountId} agentSlug=${agentSlug} contact=${maskContact(email)}`
17948
18261
  );
17949
18262
  return c.json({ personId, grant }, 200);
17950
18263
  });
@@ -18513,7 +18826,7 @@ app45.route("/calendar", calendar_default);
18513
18826
  var admin_default = app45;
18514
18827
 
18515
18828
  // server/routes/access/verify-token.ts
18516
- var TAG32 = "[access-verify]";
18829
+ var TAG33 = "[access-verify]";
18517
18830
  var MINT_TAG = "[access-session-mint]";
18518
18831
  var COOKIE_NAME = "__access_session";
18519
18832
  var app46 = new Hono();
@@ -18532,39 +18845,39 @@ app46.post("/", async (c) => {
18532
18845
  }
18533
18846
  const rateMsg = checkAccessRateLimit(ip, agentSlug);
18534
18847
  if (rateMsg) {
18535
- console.error(`${TAG32} grantId=- agentSlug=${agentSlug} result=rate-limited ip=${ip}`);
18848
+ console.error(`${TAG33} grantId=- agentSlug=${agentSlug} result=rate-limited ip=${ip}`);
18536
18849
  return c.json({ error: rateMsg }, 429);
18537
18850
  }
18538
18851
  const grant = await findGrantByMagicToken(token);
18539
18852
  if (!grant) {
18540
18853
  recordAccessFailedAttempt(ip, agentSlug);
18541
- console.error(`${TAG32} grantId=- agentSlug=${agentSlug} result=notfound ip=${ip}`);
18854
+ console.error(`${TAG33} grantId=- agentSlug=${agentSlug} result=notfound ip=${ip}`);
18542
18855
  return c.json({ error: "invalid-or-expired-link" }, 401);
18543
18856
  }
18544
18857
  if (grant.agentSlug !== agentSlug) {
18545
18858
  recordAccessFailedAttempt(ip, agentSlug);
18546
18859
  console.error(
18547
- `${TAG32} grantId=${grant.grantId} agentSlug=${agentSlug} result=agent-mismatch grantAgent=${grant.agentSlug} ip=${ip}`
18860
+ `${TAG33} grantId=${grant.grantId} agentSlug=${agentSlug} result=agent-mismatch grantAgent=${grant.agentSlug} ip=${ip}`
18548
18861
  );
18549
18862
  return c.json({ error: "invalid-or-expired-link" }, 401);
18550
18863
  }
18551
18864
  if (grant.status === "expired" || grant.status === "revoked") {
18552
18865
  recordAccessFailedAttempt(ip, agentSlug);
18553
18866
  console.error(
18554
- `${TAG32} grantId=${grant.grantId} agentSlug=${agentSlug} result=expired status=${grant.status} ip=${ip}`
18867
+ `${TAG33} grantId=${grant.grantId} agentSlug=${agentSlug} result=expired status=${grant.status} ip=${ip}`
18555
18868
  );
18556
18869
  return c.json({ error: "access-no-longer-valid" }, 401);
18557
18870
  }
18558
18871
  if (grant.expiresAt !== null && grant.expiresAt < Date.now()) {
18559
18872
  recordAccessFailedAttempt(ip, agentSlug);
18560
18873
  console.error(
18561
- `${TAG32} grantId=${grant.grantId} agentSlug=${agentSlug} result=expired reason=expiresAt-past ip=${ip}`
18874
+ `${TAG33} grantId=${grant.grantId} agentSlug=${agentSlug} result=expired reason=expiresAt-past ip=${ip}`
18562
18875
  );
18563
18876
  return c.json({ error: "access-no-longer-valid" }, 401);
18564
18877
  }
18565
18878
  if (!grant.sliceToken) {
18566
18879
  console.error(
18567
- `${TAG32} grantId=${grant.grantId} agentSlug=${agentSlug} result=no-slice-token reason=schema-violation`
18880
+ `${TAG33} grantId=${grant.grantId} agentSlug=${agentSlug} result=no-slice-token reason=schema-violation`
18568
18881
  );
18569
18882
  return c.json({ error: "grant-misconfigured" }, 500);
18570
18883
  }
@@ -18580,12 +18893,12 @@ app46.post("/", async (c) => {
18580
18893
  await consumeMagicTokenAndActivate(grant.grantId);
18581
18894
  } catch (err) {
18582
18895
  console.error(
18583
- `${TAG32} grantId=${grant.grantId} agentSlug=${agentSlug} result=consume-failed err="${err instanceof Error ? err.message : String(err)}"`
18896
+ `${TAG33} grantId=${grant.grantId} agentSlug=${agentSlug} result=consume-failed err="${err instanceof Error ? err.message : String(err)}"`
18584
18897
  );
18585
18898
  return c.json({ error: "verification-failed" }, 500);
18586
18899
  }
18587
18900
  clearAccessRateLimit(ip, agentSlug);
18588
- console.log(`${TAG32} grantId=${grant.grantId} agentSlug=${agentSlug} result=ok ip=${ip}`);
18901
+ console.log(`${TAG33} grantId=${grant.grantId} agentSlug=${agentSlug} result=ok ip=${ip}`);
18589
18902
  console.log(
18590
18903
  `${MINT_TAG} grantId=${grant.grantId} sliceToken=${grant.sliceToken.slice(0, 8)} agentSlug=${agentSlug} personId=${grant.personId ?? "none"}`
18591
18904
  );
@@ -18661,7 +18974,7 @@ async function sendMagicLinkEmail(payload) {
18661
18974
  }
18662
18975
 
18663
18976
  // server/routes/access/request-magic-link.ts
18664
- var TAG33 = "[access-request-link]";
18977
+ var TAG34 = "[access-request-link]";
18665
18978
  var app47 = new Hono();
18666
18979
  var VISITOR_MESSAGE = "If that email is on the invite list, a fresh link is on the way.";
18667
18980
  app47.post("/", async (c) => {
@@ -18678,18 +18991,18 @@ app47.post("/", async (c) => {
18678
18991
  }
18679
18992
  const rateMsg = checkRequestLinkRateLimit(contactValue);
18680
18993
  if (rateMsg) {
18681
- console.error(`${TAG33} contactValue=${maskContact(contactValue)} result=rate-limited`);
18994
+ console.error(`${TAG34} contactValue=${maskContact(contactValue)} result=rate-limited`);
18682
18995
  return c.json({ error: rateMsg }, 429);
18683
18996
  }
18684
18997
  recordRequestLinkAttempt(contactValue);
18685
18998
  const accountId = process.env.ACCOUNT_ID ?? "";
18686
18999
  if (!accountId) {
18687
- console.error(`${TAG33} contactValue=${maskContact(contactValue)} result=no-account-id`);
19000
+ console.error(`${TAG34} contactValue=${maskContact(contactValue)} result=no-account-id`);
18688
19001
  return c.json({ message: VISITOR_MESSAGE }, 200);
18689
19002
  }
18690
19003
  const grant = await findActiveGrantByContact(contactValue, agentSlug, accountId);
18691
19004
  if (!grant) {
18692
- console.log(`${TAG33} contactValue=${maskContact(contactValue)} result=notfound`);
19005
+ console.log(`${TAG34} contactValue=${maskContact(contactValue)} result=notfound`);
18693
19006
  return c.json({ message: VISITOR_MESSAGE }, 200);
18694
19007
  }
18695
19008
  let token;
@@ -18697,7 +19010,7 @@ app47.post("/", async (c) => {
18697
19010
  token = await generateNewMagicToken(grant.grantId);
18698
19011
  } catch (err) {
18699
19012
  console.error(
18700
- `${TAG33} contactValue=${maskContact(contactValue)} result=mint-failed err="${err instanceof Error ? err.message : String(err)}"`
19013
+ `${TAG34} contactValue=${maskContact(contactValue)} result=mint-failed err="${err instanceof Error ? err.message : String(err)}"`
18701
19014
  );
18702
19015
  return c.json({ message: VISITOR_MESSAGE }, 200);
18703
19016
  }
@@ -18729,12 +19042,12 @@ app47.post("/", async (c) => {
18729
19042
  });
18730
19043
  if (!sendResult.ok) {
18731
19044
  console.error(
18732
- `${TAG33} contactValue=${maskContact(contactValue)} result=send-failed err="${sendResult.error}"`
19045
+ `${TAG34} contactValue=${maskContact(contactValue)} result=send-failed err="${sendResult.error}"`
18733
19046
  );
18734
19047
  return c.json({ message: VISITOR_MESSAGE }, 200);
18735
19048
  }
18736
19049
  console.log(
18737
- `${TAG33} contactValue=${maskContact(contactValue)} result=ok messageId=${sendResult.messageId}`
19050
+ `${TAG34} contactValue=${maskContact(contactValue)} result=ok messageId=${sendResult.messageId}`
18738
19051
  );
18739
19052
  return c.json({ message: VISITOR_MESSAGE }, 200);
18740
19053
  });
@@ -18888,7 +19201,7 @@ var sites_default = app49;
18888
19201
  // app/lib/visitor-token.ts
18889
19202
  import { createHmac, randomBytes, timingSafeEqual } from "crypto";
18890
19203
  import { mkdirSync as mkdirSync7, readFileSync as readFileSync32, writeFileSync as writeFileSync11 } from "fs";
18891
- import { dirname as dirname9 } from "path";
19204
+ import { dirname as dirname10 } from "path";
18892
19205
  var TOKEN_PREFIX = "v1.";
18893
19206
  var SECRET_BYTES = 32;
18894
19207
  var DEFAULT_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
@@ -18905,7 +19218,7 @@ function getSecret() {
18905
19218
  }
18906
19219
  const fresh = randomBytes(SECRET_BYTES).toString("hex");
18907
19220
  try {
18908
- mkdirSync7(dirname9(VISITOR_TOKEN_SECRET_FILE), { recursive: true, mode: 448 });
19221
+ mkdirSync7(dirname10(VISITOR_TOKEN_SECRET_FILE), { recursive: true, mode: 448 });
18909
19222
  writeFileSync11(VISITOR_TOKEN_SECRET_FILE, fresh, { mode: 384, flag: "wx" });
18910
19223
  console.log(`[visitor-token] secret minted path=${VISITOR_TOKEN_SECRET_FILE}`);
18911
19224
  } catch {
@@ -20013,7 +20326,7 @@ async function startFileWatcher(opts = {}) {
20013
20326
 
20014
20327
  // app/lib/migrate-uploads.ts
20015
20328
  import { mkdir as mkdir5, readdir as readdir5, rename as rename2, rm as rm3 } from "fs/promises";
20016
- import { dirname as dirname10, relative as relative6, resolve as resolve29 } from "path";
20329
+ import { dirname as dirname11, relative as relative6, resolve as resolve29 } from "path";
20017
20330
  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;
20018
20331
  async function walkFiles(dir) {
20019
20332
  const out = [];
@@ -20045,7 +20358,7 @@ async function relocateTree(srcDir, destDir, dataRoot, accountId, session) {
20045
20358
  for (const oldAbs of files) {
20046
20359
  const suffix = relative6(srcDir, oldAbs);
20047
20360
  const newAbs = resolve29(destDir, suffix);
20048
- await mkdir5(dirname10(newAbs), { recursive: true });
20361
+ await mkdir5(dirname11(newAbs), { recursive: true });
20049
20362
  await rename2(oldAbs, newAbs);
20050
20363
  moved++;
20051
20364
  const oldRel = relative6(dataRoot, oldAbs);
@@ -20752,6 +21065,7 @@ var WaGateway = class {
20752
21065
  const willDeliverNow = this.hub.isReady(input.senderId);
20753
21066
  const openable = input.media.filter((m) => m.type !== "audio");
20754
21067
  const mediaField = openable.length > 0 ? `media=${openable.map((m) => m.type).join(",")} mediaPaths=${openable.map((m) => m.path).join(",")}` : input.media.some((m) => m.type === "audio") ? "media=audio mediaPath=transcribed" : "media=none";
21068
+ const source = input.source ?? "user";
20755
21069
  this.replies.set(input.senderId, input.reply);
20756
21070
  this.docContexts.set(input.senderId, { accountId: input.accountId });
20757
21071
  this.hub.deliver(
@@ -20759,12 +21073,13 @@ var WaGateway = class {
20759
21073
  senderId: input.senderId,
20760
21074
  text: input.text,
20761
21075
  waMessageId: `wa-${++this.seq}`,
20762
- media: input.media
21076
+ media: input.media,
21077
+ source
20763
21078
  },
20764
21079
  Date.now()
20765
21080
  );
20766
21081
  console.error(
20767
- `[whatsapp-native] op=inbound senderId=${input.senderId} accountId=${input.accountId} bytes=${bytes} ${mediaField} delivery=${willDeliverNow ? "immediate" : "queued"}`
21082
+ `[whatsapp-native] op=inbound senderId=${input.senderId} accountId=${input.accountId} bytes=${bytes} ${mediaField} source=${source} delivery=${willDeliverNow ? "immediate" : "queued"}`
20768
21083
  );
20769
21084
  if (!hadSubscriber && !this.spawning.has(input.senderId)) {
20770
21085
  this.spawning.add(input.senderId);
@@ -20772,6 +21087,7 @@ var WaGateway = class {
20772
21087
  try {
20773
21088
  await this.deps.ensureChannelSession({
20774
21089
  accountId: input.accountId,
21090
+ effectiveAccountId: input.effectiveAccountId,
20775
21091
  senderId: input.senderId,
20776
21092
  role: input.role ?? "admin",
20777
21093
  personId: input.personId ?? null,
@@ -21701,7 +22017,7 @@ function makeFileDelivery(opts) {
21701
22017
  }
21702
22018
 
21703
22019
  // app/lib/whatsapp/inbound/file-delivery-bridge.ts
21704
- var TAG34 = "[whatsapp-adaptor]";
22020
+ var TAG35 = "[whatsapp-adaptor]";
21705
22021
  var SEND_USER_FILE2 = "SendUserFile";
21706
22022
  var WHATSAPP_SEND_DOCUMENT = "whatsapp-send-document";
21707
22023
  function platformRoot() {
@@ -21714,7 +22030,7 @@ function makeWhatsAppSendFile(entry) {
21714
22030
  maxyAccountId = resolvePlatformAccountId();
21715
22031
  } catch (err) {
21716
22032
  console.error(
21717
- `${TAG34} file-delivery reject reason=account-unresolved sender=${entry.senderId} message=${err instanceof Error ? err.message : String(err)}`
22033
+ `${TAG35} file-delivery reject reason=account-unresolved sender=${entry.senderId} message=${err instanceof Error ? err.message : String(err)}`
21718
22034
  );
21719
22035
  return { ok: false, error: "account-unresolved" };
21720
22036
  }
@@ -21728,7 +22044,7 @@ function makeWhatsAppSendFile(entry) {
21728
22044
  });
21729
22045
  if (result.ok) return { ok: true };
21730
22046
  console.error(
21731
- `${TAG34} file-delivery reject reason=send-failed sender=${entry.senderId} status=${result.status} message=${result.error}`
22047
+ `${TAG35} file-delivery reject reason=send-failed sender=${entry.senderId} status=${result.status} message=${result.error}`
21732
22048
  );
21733
22049
  return { ok: false, error: result.error };
21734
22050
  };
@@ -21736,7 +22052,7 @@ function makeWhatsAppSendFile(entry) {
21736
22052
  function makeWhatsAppFileDelivery(entry) {
21737
22053
  const shared = makeFileDelivery({
21738
22054
  entry,
21739
- tag: TAG34,
22055
+ tag: TAG35,
21740
22056
  channel: "whatsapp",
21741
22057
  sendFile: makeWhatsAppSendFile(entry)
21742
22058
  });
@@ -21771,7 +22087,7 @@ function makeWhatsAppFileDelivery(entry) {
21771
22087
  if (!delivered) {
21772
22088
  const fileField = call.filePath !== void 0 ? ` file=${call.filePath}` : "";
21773
22089
  console.error(
21774
- `${TAG34} file-delivery-unreconciled sender=${entry.senderId} sessionId=${sid} tool=${WHATSAPP_SEND_DOCUMENT}${fileField}`
22090
+ `${TAG35} file-delivery-unreconciled sender=${entry.senderId} sessionId=${sid} tool=${WHATSAPP_SEND_DOCUMENT}${fileField}`
21775
22091
  );
21776
22092
  }
21777
22093
  }
@@ -22080,13 +22396,14 @@ var TelegramGateway = class {
22080
22396
  const bytes = Buffer.byteLength(input.text, "utf8");
22081
22397
  const hadSubscriber = this.hub.hasSubscriber(input.senderId);
22082
22398
  const willDeliverNow = this.hub.isReady(input.senderId);
22399
+ const source = input.source ?? "user";
22083
22400
  this.replies.set(input.senderId, input.reply);
22084
22401
  this.hub.deliver(
22085
- { key: input.senderId, text: input.text, messageId: `tg-${++this.seq}` },
22402
+ { key: input.senderId, text: input.text, messageId: `tg-${++this.seq}`, source },
22086
22403
  Date.now()
22087
22404
  );
22088
22405
  console.error(
22089
- `[telegram-native] op=inbound key=${input.senderId} accountId=${input.accountId} botType=${input.role} bytes=${bytes} delivery=${willDeliverNow ? "immediate" : "queued"}`
22406
+ `[telegram-native] op=inbound key=${input.senderId} accountId=${input.accountId} botType=${input.role} bytes=${bytes} source=${source} delivery=${willDeliverNow ? "immediate" : "queued"}`
22090
22407
  );
22091
22408
  if (!hadSubscriber && !this.spawning.has(input.senderId)) {
22092
22409
  this.spawning.add(input.senderId);
@@ -22135,8 +22452,8 @@ function buildTelegramSpawnRequest(input) {
22135
22452
  // app/lib/telegram/outbound/send-document.ts
22136
22453
  import { realpathSync as realpathSync7 } from "fs";
22137
22454
  import { readFile as readFile7, stat as stat7 } from "fs/promises";
22138
- import { resolve as resolve30, basename as basename11 } from "path";
22139
- var TAG35 = "[telegram:outbound]";
22455
+ import { resolve as resolve30, basename as basename12 } from "path";
22456
+ var TAG36 = "[telegram:outbound]";
22140
22457
  var TELEGRAM_DOCUMENT_MAX_BYTES = 50 * 1024 * 1024;
22141
22458
  async function sendTelegramDocument(input) {
22142
22459
  const { botToken, chatId, filePath, caption, maxyAccountId, platformRoot: platformRoot3 } = input;
@@ -22152,16 +22469,16 @@ async function sendTelegramDocument(input) {
22152
22469
  resolvedPath = realpathSync7(filePath);
22153
22470
  const accountResolved = realpathSync7(accountDir);
22154
22471
  if (!resolvedPath.startsWith(accountResolved + "/")) {
22155
- console.error(`${TAG35} document REJECTED reason=outside_account_directory`);
22472
+ console.error(`${TAG36} document REJECTED reason=outside_account_directory`);
22156
22473
  return { ok: false, status: 403, error: "Access denied: file is outside the account directory" };
22157
22474
  }
22158
22475
  } catch (err) {
22159
22476
  const code = err.code;
22160
22477
  if (code === "ENOENT") {
22161
- console.error(`${TAG35} document ENOENT path=${filePath}`);
22478
+ console.error(`${TAG36} document ENOENT path=${filePath}`);
22162
22479
  return { ok: false, status: 404, error: `File not found: ${filePath}` };
22163
22480
  }
22164
- console.error(`${TAG35} document path error: ${String(err)}`);
22481
+ console.error(`${TAG36} document path error: ${String(err)}`);
22165
22482
  return { ok: false, status: 500, error: String(err) };
22166
22483
  }
22167
22484
  const fileStat = await stat7(resolvedPath);
@@ -22172,7 +22489,7 @@ async function sendTelegramDocument(input) {
22172
22489
  error: `File exceeds 50 MB limit (${(fileStat.size / 1024 / 1024).toFixed(1)} MB)`
22173
22490
  };
22174
22491
  }
22175
- const filename = basename11(resolvedPath);
22492
+ const filename = basename12(resolvedPath);
22176
22493
  const buffer = Buffer.from(await readFile7(resolvedPath));
22177
22494
  const form = new FormData();
22178
22495
  form.append("chat_id", String(chatId));
@@ -22194,13 +22511,13 @@ async function sendTelegramDocument(input) {
22194
22511
  error = e instanceof Error ? e.message : String(e);
22195
22512
  }
22196
22513
  console.error(
22197
- `${TAG35} sent document to=${chatId} file=${filename} bytes=${fileStat.size} ok=${ok}` + (messageId !== void 0 ? ` id=${messageId}` : "")
22514
+ `${TAG36} sent document to=${chatId} file=${filename} bytes=${fileStat.size} ok=${ok}` + (messageId !== void 0 ? ` id=${messageId}` : "")
22198
22515
  );
22199
22516
  return ok ? { ok: true, messageId } : { ok: false, status: 500, error };
22200
22517
  }
22201
22518
 
22202
22519
  // app/lib/telegram/outbound/file-delivery.ts
22203
- var TAG36 = "[telegram:outbound]";
22520
+ var TAG37 = "[telegram:outbound]";
22204
22521
  function platformRoot2() {
22205
22522
  return process.env.MAXY_PLATFORM_ROOT || "";
22206
22523
  }
@@ -22212,11 +22529,11 @@ function makeTelegramSendFile(entry) {
22212
22529
  botToken = (entry.role === "admin" ? tg?.adminBotToken : tg?.publicBotToken) ?? null;
22213
22530
  }
22214
22531
  if (!botToken) {
22215
- console.error(`${TAG36} file-delivery reject reason=no-bot-token sender=${entry.senderId} role=${entry.role}`);
22532
+ console.error(`${TAG37} file-delivery reject reason=no-bot-token sender=${entry.senderId} role=${entry.role}`);
22216
22533
  return { ok: false, error: "no-bot-token" };
22217
22534
  }
22218
22535
  if (entry.replyTarget == null) {
22219
- console.error(`${TAG36} file-delivery reject reason=no-reply-target sender=${entry.senderId} role=${entry.role}`);
22536
+ console.error(`${TAG37} file-delivery reject reason=no-reply-target sender=${entry.senderId} role=${entry.role}`);
22220
22537
  return { ok: false, error: "no-reply-target" };
22221
22538
  }
22222
22539
  let maxyAccountId;
@@ -22224,7 +22541,7 @@ function makeTelegramSendFile(entry) {
22224
22541
  maxyAccountId = resolvePlatformAccountId();
22225
22542
  } catch (err) {
22226
22543
  console.error(
22227
- `${TAG36} file-delivery reject reason=account-unresolved sender=${entry.senderId} message=${err instanceof Error ? err.message : String(err)}`
22544
+ `${TAG37} file-delivery reject reason=account-unresolved sender=${entry.senderId} message=${err instanceof Error ? err.message : String(err)}`
22228
22545
  );
22229
22546
  return { ok: false, error: "account-unresolved" };
22230
22547
  }
@@ -22242,7 +22559,7 @@ function makeTelegramSendFile(entry) {
22242
22559
  function makeTelegramFileDelivery(entry) {
22243
22560
  return makeFileDelivery({
22244
22561
  entry,
22245
- tag: TAG36,
22562
+ tag: TAG37,
22246
22563
  channel: "telegram",
22247
22564
  sendFile: makeTelegramSendFile(entry)
22248
22565
  });
@@ -22292,7 +22609,7 @@ function telegramServerPath() {
22292
22609
 
22293
22610
  // app/lib/channel-pty-bridge/public-session-end-review.ts
22294
22611
  import { randomUUID as randomUUID13 } from "crypto";
22295
- var TAG37 = "[public-session-review]";
22612
+ var TAG38 = "[public-session-review]";
22296
22613
  var CONSUMED_CAP = 500;
22297
22614
  var consumed = /* @__PURE__ */ new Set();
22298
22615
  function consumeOnce(sessionId) {
@@ -22319,7 +22636,7 @@ async function fetchJsonl(sessionId) {
22319
22636
  return await res.text();
22320
22637
  } catch (err) {
22321
22638
  console.error(
22322
- `${TAG37} jsonl-fetch-failed sessionId=${sessionId} err="${err instanceof Error ? err.message : String(err)}"`
22639
+ `${TAG38} jsonl-fetch-failed sessionId=${sessionId} err="${err instanceof Error ? err.message : String(err)}"`
22323
22640
  );
22324
22641
  return null;
22325
22642
  }
@@ -22343,7 +22660,7 @@ async function fetchPriorWrites(sliceToken, accountId) {
22343
22660
  const res = await fetch(url);
22344
22661
  if (!res.ok) {
22345
22662
  console.error(
22346
- `${TAG37} prior-writes-fetch-failed sliceToken=${sliceToken.slice(0, 8)} status=${res.status}`
22663
+ `${TAG38} prior-writes-fetch-failed sliceToken=${sliceToken.slice(0, 8)} status=${res.status}`
22347
22664
  );
22348
22665
  return [];
22349
22666
  }
@@ -22351,7 +22668,7 @@ async function fetchPriorWrites(sliceToken, accountId) {
22351
22668
  return Array.isArray(body.writes) ? body.writes : [];
22352
22669
  } catch (err) {
22353
22670
  console.error(
22354
- `${TAG37} prior-writes-fetch-threw sliceToken=${sliceToken.slice(0, 8)} err="${err instanceof Error ? err.message : String(err)}"`
22671
+ `${TAG38} prior-writes-fetch-threw sliceToken=${sliceToken.slice(0, 8)} err="${err instanceof Error ? err.message : String(err)}"`
22355
22672
  );
22356
22673
  return [];
22357
22674
  }
@@ -22380,7 +22697,7 @@ function composeInitialMessage(input) {
22380
22697
  }
22381
22698
  async function dispatchReviewer(input, initialMessage) {
22382
22699
  const sessionId = randomUUID13();
22383
- console.log(`${TAG37} route target=rc-spawn sessionId=${sessionId.slice(0, 8)} sourceSession=${input.sessionId.slice(0, 8)}`);
22700
+ console.log(`${TAG38} route target=rc-spawn sessionId=${sessionId.slice(0, 8)} sourceSession=${input.sessionId.slice(0, 8)}`);
22384
22701
  const spawned = await managerRcSpawn({
22385
22702
  sessionId,
22386
22703
  initialMessage,
@@ -22400,21 +22717,21 @@ async function firePublicSessionEndReview(input) {
22400
22717
  const dispatchedAt = Date.now();
22401
22718
  if (consumed.has(input.sessionId)) {
22402
22719
  console.log(
22403
- `${TAG37} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-already-reviewed`
22720
+ `${TAG38} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-already-reviewed`
22404
22721
  );
22405
22722
  return;
22406
22723
  }
22407
22724
  const jsonl = await fetchJsonl(input.sessionId);
22408
22725
  if (!jsonl) {
22409
22726
  console.log(
22410
- `${TAG37} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-jsonl-missing`
22727
+ `${TAG38} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-jsonl-missing`
22411
22728
  );
22412
22729
  return;
22413
22730
  }
22414
22731
  const operatorTurns = countOperatorTurns(jsonl);
22415
22732
  if (operatorTurns === 0) {
22416
22733
  console.log(
22417
- `${TAG37} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-no-turns`
22734
+ `${TAG38} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-no-turns`
22418
22735
  );
22419
22736
  return;
22420
22737
  }
@@ -22428,23 +22745,34 @@ async function firePublicSessionEndReview(input) {
22428
22745
  });
22429
22746
  if (!consumeOnce(input.sessionId)) {
22430
22747
  console.log(
22431
- `${TAG37} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-already-reviewed`
22748
+ `${TAG38} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-already-reviewed`
22432
22749
  );
22433
22750
  return;
22434
22751
  }
22435
22752
  const dispatched = await dispatchReviewer(input, initialMessage);
22436
22753
  if (!dispatched.ok) {
22437
22754
  console.error(
22438
- `${TAG37} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-spawn-failed reason=${dispatched.reason}`
22755
+ `${TAG38} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-spawn-failed reason=${dispatched.reason}`
22439
22756
  );
22440
22757
  return;
22441
22758
  }
22442
22759
  console.log(
22443
- `${TAG37} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=dispatched managerSessionId=${dispatched.managerSessionId} operatorTurns=${operatorTurns} priorWrites=${priorWrites.length} ms=${Date.now() - dispatchedAt}`
22760
+ `${TAG38} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=dispatched managerSessionId=${dispatched.managerSessionId} operatorTurns=${operatorTurns} priorWrites=${priorWrites.length} ms=${Date.now() - dispatchedAt}`
22444
22761
  );
22445
22762
  }
22446
22763
 
22447
22764
  // app/lib/whatsapp/inbound/channel-admin-binding-drift.ts
22765
+ function findAccountManagerDrift(accountManagers, adminPhones, validAccountIds) {
22766
+ const drift = [];
22767
+ for (const [phone, managesAccount] of Object.entries(accountManagers)) {
22768
+ if (!validAccountIds.includes(managesAccount)) {
22769
+ drift.push({ phone, managesAccount, reason: "not-in-registry" });
22770
+ } else if (adminPhones.some((a) => phonesMatch(a, phone))) {
22771
+ drift.push({ phone, managesAccount, reason: "phone-also-admin" });
22772
+ }
22773
+ }
22774
+ return drift;
22775
+ }
22448
22776
  function findChannelAdminBindingDrift(adminPhones, admins, users) {
22449
22777
  const drift = [];
22450
22778
  for (const phone of adminPhones) {
@@ -22454,6 +22782,10 @@ function findChannelAdminBindingDrift(adminPhones, admins, users) {
22454
22782
  }
22455
22783
  return drift;
22456
22784
  }
22785
+ function classifyAdminPhonesForAccount(isSocketOwner, adminPhones, admins, users) {
22786
+ if (!isSocketOwner) return { nonSocketPhones: [...adminPhones], drift: [] };
22787
+ return { nonSocketPhones: [], drift: findChannelAdminBindingDrift(adminPhones, admins, users) };
22788
+ }
22457
22789
  function warnOnChannelAdminBindingDrift() {
22458
22790
  let users;
22459
22791
  try {
@@ -22467,15 +22799,39 @@ function warnOnChannelAdminBindingDrift() {
22467
22799
  } catch {
22468
22800
  return;
22469
22801
  }
22802
+ let socketOwner;
22803
+ try {
22804
+ socketOwner = resolveHouseOrSoleAccountId(ACCOUNTS_DIR);
22805
+ } catch {
22806
+ socketOwner = null;
22807
+ }
22808
+ const validAccountIds = accounts.map((a) => a.accountId);
22470
22809
  for (const acct of accounts) {
22471
22810
  const adminPhones = readAdminPhones(acct.accountDir);
22472
- const drift = findChannelAdminBindingDrift(adminPhones, acct.config.admins ?? [], users);
22811
+ const isSocketOwner = socketOwner === null || acct.accountId === socketOwner;
22812
+ const { nonSocketPhones, drift } = classifyAdminPhonesForAccount(
22813
+ isSocketOwner,
22814
+ adminPhones,
22815
+ acct.config.admins ?? [],
22816
+ users
22817
+ );
22818
+ for (const phone of nonSocketPhones) {
22819
+ console.error(
22820
+ `[admin-identity] adminphones-on-non-socket-account accountId=${acct.accountId} phone=${phone} count=${nonSocketPhones.length}`
22821
+ );
22822
+ }
22473
22823
  for (const d of drift) {
22474
22824
  const tail = d.reason === "userid-not-admin" ? ` userId=${d.userId}` : "";
22475
22825
  console.error(
22476
22826
  `[admin-identity] binding-drift accountId=${acct.accountId} phone=${d.phone} reason=${d.reason}${tail}`
22477
22827
  );
22478
22828
  }
22829
+ const managerDrift = findAccountManagerDrift(readAccountManagers(acct.accountDir), adminPhones, validAccountIds);
22830
+ for (const d of managerDrift) {
22831
+ console.error(
22832
+ `[admin-identity] account-manager-drift accountId=${acct.accountId} phone=${d.phone} managesAccount=${d.managesAccount} reason=${d.reason}`
22833
+ );
22834
+ }
22479
22835
  }
22480
22836
  }
22481
22837
 
@@ -22839,9 +23195,13 @@ var waGateway = new WaGateway({
22839
23195
  });
22840
23196
  return result.ok ? { ok: true, messageId: result.messageId } : { ok: false, error: result.error };
22841
23197
  },
22842
- ensureChannelSession: async ({ accountId, senderId, role, personId, gatewayUrl, serverPath }) => {
22843
- const req = buildWaSpawnRequest({ accountId, senderId, role, personId, gatewayUrl, serverPath });
22844
- const userId = role === "admin" ? resolveAdminUserId({ accountId, senderPhone: senderId }) ?? void 0 : void 0;
23198
+ ensureChannelSession: async ({ accountId, effectiveAccountId, senderId, role, personId, gatewayUrl, serverPath }) => {
23199
+ if (role === "admin") {
23200
+ const managesAccount = effectiveAccountId !== accountId ? effectiveAccountId : "none";
23201
+ console.error(`[whatsapp-native] op=account-manager-route senderId=${senderId} managesAccount=${managesAccount} effectiveAccount=${effectiveAccountId} source=gate`);
23202
+ }
23203
+ const req = buildWaSpawnRequest({ accountId: effectiveAccountId, senderId, role, personId, gatewayUrl, serverPath });
23204
+ const userId = role === "admin" ? resolveAdminUserId({ accountId: effectiveAccountId, senderPhone: senderId }) ?? void 0 : void 0;
22845
23205
  if (role === "admin") {
22846
23206
  console.error(`[whatsapp-native] op=admin-identity senderId=${senderId} userId=${userId ?? "owner-fallback"}`);
22847
23207
  }
@@ -22987,6 +23347,22 @@ var chatRoutes = createChatRoutes({
22987
23347
  handleInbound: (input) => webchatGateway.handleInbound(input),
22988
23348
  awaitReply: (key, timeoutMs) => webchatGateway.awaitReply(key, timeoutMs)
22989
23349
  });
23350
+ var scheduleInjectRoutes = createScheduleInjectRoutes({
23351
+ resolveAccount: () => resolveAccount(),
23352
+ waHandleInbound: (input) => waGateway.handleInbound(input),
23353
+ tgHandleInbound: (input) => telegramGateway.handleInbound(input),
23354
+ sendWhatsAppText: async (accountId, destination, text) => {
23355
+ const sock = getSocket(accountId);
23356
+ if (!sock) throw new Error("WhatsApp disconnected \u2014 cannot deliver scheduled reply");
23357
+ await sendTextMessage(sock, toWhatsappJid(destination), text, { accountId });
23358
+ },
23359
+ sendTelegramText: async (botToken, chatId, text) => {
23360
+ const sent = await sendTelegramText(botToken, chatId, text);
23361
+ if (!sent.ok) throw new Error(sent.error ?? "telegram send failed");
23362
+ },
23363
+ effectiveAccountFor: (accountId, accountDir, destination) => managedAccountFor(readAccountManagers(accountDir), destination) ?? accountId
23364
+ });
23365
+ app55.route("/api/channel/schedule-inject", scheduleInjectRoutes);
22990
23366
  app55.use("*", clientIpMiddleware);
22991
23367
  function allowsSameOriginFraming(path2) {
22992
23368
  return path2 === "/vnc-viewer.html" || path2.startsWith("/api/admin/attachment/") || path2.startsWith("/api/public-reader/attachment/") || path2 === "/api/admin/files/download";
@@ -23963,7 +24339,7 @@ try {
23963
24339
  } catch (err) {
23964
24340
  console.error(`[graph-health] account-enumeration unavailable reason=${err instanceof Error ? err.message : String(err)}`);
23965
24341
  }
23966
- var configDirForWhatsApp = basename12(MAXY_DIR) || ".maxy";
24342
+ var configDirForWhatsApp = basename13(MAXY_DIR) || ".maxy";
23967
24343
  var bootAccount = resolveAccount();
23968
24344
  if (bootAccount) {
23969
24345
  migrateRemovedConfigKeys(bootAccount.accountDir);
@@ -23983,6 +24359,7 @@ reconcileEnabledPlugins(bootAccount?.accountDir, bootAccount?.config, bootAccoun
23983
24359
  for (const acct of listValidAccounts()) {
23984
24360
  cleanupLeakedPremiumSubs(acct.accountDir, acct.config, acct.accountId);
23985
24361
  }
24362
+ purgeNonSocketAdminPhonesAtBoot();
23986
24363
  warnOnChannelAdminBindingDrift();
23987
24364
  var bootEnabled = Array.isArray(bootAccountConfig?.enabledPlugins) ? bootAccountConfig.enabledPlugins : [];
23988
24365
  var bootDelivered = [];
@@ -24042,6 +24419,10 @@ init({
24042
24419
  console.error(`[whatsapp:route] dropped reason=no-text-no-media senderId=${msg.senderPhone} mediaCount=${msg.media.length}`);
24043
24420
  return;
24044
24421
  }
24422
+ if (!msg.effectiveAccountId) {
24423
+ console.error(`[whatsapp:route] op=dropped reason=no-effective-account senderId=${msg.senderPhone}`);
24424
+ return;
24425
+ }
24045
24426
  if (decision.role === "public") {
24046
24427
  console.error(`[whatsapp:route] op=routed agentType=public personId=${decision.personId} senderId=${msg.senderPhone}`);
24047
24428
  }
@@ -24050,6 +24431,7 @@ init({
24050
24431
  });
24051
24432
  await waGateway.handleInbound({
24052
24433
  accountId: msg.accountId,
24434
+ effectiveAccountId: msg.effectiveAccountId,
24053
24435
  senderId: msg.senderPhone,
24054
24436
  role: decision.role,
24055
24437
  personId: decision.personId,