@rubytech/create-maxy-code 0.1.387 → 0.1.389

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 (101) hide show
  1. package/dist/__tests__/joblogic-excluded.test.js +39 -0
  2. package/dist/index.js +21 -0
  3. package/package.json +1 -1
  4. package/payload/platform/config/brand.json +1 -1
  5. package/payload/platform/plugins/.claude-plugin/marketplace.json +0 -5
  6. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +21 -2
  7. package/payload/platform/plugins/docs/references/admin-session.md +4 -0
  8. package/payload/platform/plugins/docs/references/admin-ui.md +18 -1
  9. package/payload/platform/plugins/docs/references/joblogic.md +2 -0
  10. package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +7 -3
  11. package/payload/platform/plugins/whatsapp/skills/manage-whatsapp-config/SKILL.md +2 -0
  12. package/payload/platform/plugins/work/PLUGIN.md +3 -0
  13. package/payload/platform/plugins/work/mcp/dist/__tests__/session-metering.test.d.ts +2 -0
  14. package/payload/platform/plugins/work/mcp/dist/__tests__/session-metering.test.d.ts.map +1 -0
  15. package/payload/platform/plugins/work/mcp/dist/__tests__/session-metering.test.js +98 -0
  16. package/payload/platform/plugins/work/mcp/dist/__tests__/session-metering.test.js.map +1 -0
  17. package/payload/platform/plugins/work/mcp/dist/index.js +16 -0
  18. package/payload/platform/plugins/work/mcp/dist/index.js.map +1 -1
  19. package/payload/platform/plugins/work/mcp/dist/tools/session-metering.d.ts +53 -0
  20. package/payload/platform/plugins/work/mcp/dist/tools/session-metering.d.ts.map +1 -0
  21. package/payload/platform/plugins/work/mcp/dist/tools/session-metering.js +80 -0
  22. package/payload/platform/plugins/work/mcp/dist/tools/session-metering.js.map +1 -0
  23. package/payload/platform/plugins/work/mcp/package.json +2 -1
  24. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
  25. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +1 -0
  26. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  27. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  28. package/payload/platform/services/claude-session-manager/dist/http-server.js +98 -8
  29. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  30. package/payload/platform/services/claude-session-manager/dist/pricing.d.ts +45 -0
  31. package/payload/platform/services/claude-session-manager/dist/pricing.d.ts.map +1 -0
  32. package/payload/platform/services/claude-session-manager/dist/pricing.js +57 -0
  33. package/payload/platform/services/claude-session-manager/dist/pricing.js.map +1 -0
  34. package/payload/platform/services/claude-session-manager/dist/session-metering.d.ts +38 -0
  35. package/payload/platform/services/claude-session-manager/dist/session-metering.d.ts.map +1 -0
  36. package/payload/platform/services/claude-session-manager/dist/session-metering.js +292 -0
  37. package/payload/platform/services/claude-session-manager/dist/session-metering.js.map +1 -0
  38. package/payload/platform/templates/specialists/agents/project-manager.md +1 -1
  39. package/payload/server/public/assets/{AdminLoginScreens-BejIjbmU.js → AdminLoginScreens-CerrEc_m.js} +1 -1
  40. package/payload/server/public/assets/AdminShell-cRTvNRbo.js +1 -0
  41. package/payload/server/public/assets/{Checkbox-1F1tzqca.js → Checkbox-Dh2pLMFN.js} +1 -1
  42. package/payload/server/public/assets/{Transcript-DkGa4CQH.js → Transcript-B_GVJujB.js} +1 -1
  43. package/payload/server/public/assets/{admin-DqQARkjy.js → admin-BXaYelnR.js} +1 -1
  44. package/payload/server/public/assets/{browser-nDtEK6RC.js → browser-fhjGE7fH.js} +1 -1
  45. package/payload/server/public/assets/calendar-PnZudAtc.js +1 -0
  46. package/payload/server/public/assets/chat-Dy_zrjKS.js +1 -0
  47. package/payload/server/public/assets/chevron-left-IB6TmMZ_.js +1 -0
  48. package/payload/server/public/assets/data-B2IcjAj6.js +1 -0
  49. package/payload/server/public/assets/{graph-DFyicKd7.js → graph-CycO3tkW.js} +2 -2
  50. package/payload/server/public/assets/{graph-labels-D3BQdcpD.js → graph-labels-DlSsSpBV.js} +1 -1
  51. package/payload/server/public/assets/{operator-BxrjWdT9.js → operator-L4kBC-zS.js} +1 -1
  52. package/payload/server/public/assets/page-D5E-Ng4D.js +1 -0
  53. package/payload/server/public/assets/page-M8sD9LOi.js +32 -0
  54. package/payload/server/public/assets/{public-DbdqfdYq.js → public-BmnajF3K.js} +1 -1
  55. package/payload/server/public/assets/{rotate-ccw-BkX8HODe.js → rotate-ccw-W5HhvAbo.js} +1 -1
  56. package/payload/server/public/assets/useSubAccountSwitcher-D1TI1xvd.css +1 -0
  57. package/payload/server/public/assets/useSubAccountSwitcher-UqZbmzYy.js +9 -0
  58. package/payload/server/public/browser.html +4 -4
  59. package/payload/server/public/calendar.html +5 -5
  60. package/payload/server/public/chat.html +8 -8
  61. package/payload/server/public/data.html +7 -7
  62. package/payload/server/public/graph.html +8 -8
  63. package/payload/server/public/index.html +10 -10
  64. package/payload/server/public/operator.html +10 -10
  65. package/payload/server/public/public.html +8 -8
  66. package/payload/server/server.js +444 -315
  67. package/payload/platform/plugins/joblogic/.claude-plugin/plugin.json +0 -21
  68. package/payload/platform/plugins/joblogic/PLUGIN.md +0 -182
  69. package/payload/platform/plugins/joblogic/lib/mcp-spawn-tee/index.js +0 -193
  70. package/payload/platform/plugins/joblogic/lib/mcp-spawn-tee/package.json +0 -3
  71. package/payload/platform/plugins/joblogic/mcp/dist/index.d.ts +0 -2
  72. package/payload/platform/plugins/joblogic/mcp/dist/index.d.ts.map +0 -1
  73. package/payload/platform/plugins/joblogic/mcp/dist/index.js +0 -229
  74. package/payload/platform/plugins/joblogic/mcp/dist/index.js.map +0 -1
  75. package/payload/platform/plugins/joblogic/mcp/dist/lib/auth.d.ts +0 -31
  76. package/payload/platform/plugins/joblogic/mcp/dist/lib/auth.d.ts.map +0 -1
  77. package/payload/platform/plugins/joblogic/mcp/dist/lib/auth.js +0 -78
  78. package/payload/platform/plugins/joblogic/mcp/dist/lib/auth.js.map +0 -1
  79. package/payload/platform/plugins/joblogic/mcp/dist/lib/client.d.ts +0 -35
  80. package/payload/platform/plugins/joblogic/mcp/dist/lib/client.d.ts.map +0 -1
  81. package/payload/platform/plugins/joblogic/mcp/dist/lib/client.js +0 -106
  82. package/payload/platform/plugins/joblogic/mcp/dist/lib/client.js.map +0 -1
  83. package/payload/platform/plugins/joblogic/mcp/dist/lib/idempotency.d.ts +0 -8
  84. package/payload/platform/plugins/joblogic/mcp/dist/lib/idempotency.d.ts.map +0 -1
  85. package/payload/platform/plugins/joblogic/mcp/dist/lib/idempotency.js +0 -41
  86. package/payload/platform/plugins/joblogic/mcp/dist/lib/idempotency.js.map +0 -1
  87. package/payload/platform/plugins/joblogic/mcp/dist/lib/secrets.d.ts +0 -21
  88. package/payload/platform/plugins/joblogic/mcp/dist/lib/secrets.d.ts.map +0 -1
  89. package/payload/platform/plugins/joblogic/mcp/dist/lib/secrets.js +0 -47
  90. package/payload/platform/plugins/joblogic/mcp/dist/lib/secrets.js.map +0 -1
  91. package/payload/platform/plugins/joblogic/mcp/package.json +0 -10
  92. package/payload/platform/plugins/joblogic/skills/joblogic/SKILL.md +0 -32
  93. package/payload/server/public/assets/AdminShell-D2-uBSB5.js +0 -1
  94. package/payload/server/public/assets/calendar-CO4Bwmho.js +0 -1
  95. package/payload/server/public/assets/chat-DeIge_bC.js +0 -1
  96. package/payload/server/public/assets/chevron-left-DhVdq0aN.js +0 -1
  97. package/payload/server/public/assets/data-B1GIdzHk.js +0 -1
  98. package/payload/server/public/assets/page-ByDLq_o1.js +0 -1
  99. package/payload/server/public/assets/page-D-Ep4bXd.js +0 -32
  100. package/payload/server/public/assets/useSubAccountSwitcher-DMbRhLPv.js +0 -9
  101. package/payload/server/public/assets/useSubAccountSwitcher-DS0iqSkP.css +0 -1
@@ -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,7 @@ 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
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."),
1731
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."),
1732
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."),
@@ -2628,27 +2628,31 @@ function isAdminPhone(phone, adminPhones) {
2628
2628
  return adminPhones.some((entry) => phonesMatch(entry, normalized));
2629
2629
  }
2630
2630
  function checkDmAccess(params) {
2631
- const { senderPhone, selfPhone } = params;
2631
+ const { senderPhone, selfPhone, accountId } = params;
2632
2632
  const cfg = resolveAccountConfig(params.config, params.accountConfig);
2633
2633
  if (phonesMatch(senderPhone, selfPhone)) {
2634
- return { allowed: true, reason: "self-phone", agentType: "admin" };
2634
+ return { allowed: true, reason: "self-phone", agentType: "admin", effectiveAccountId: accountId };
2635
2635
  }
2636
2636
  if (isAdminPhone(senderPhone, cfg.adminPhones)) {
2637
- return { allowed: true, reason: "admin-binding", agentType: "admin" };
2637
+ return { allowed: true, reason: "admin-binding", agentType: "admin", effectiveAccountId: accountId };
2638
2638
  }
2639
- if (managedAccountFor(cfg.accountManagers, senderPhone)) {
2640
- return { allowed: true, reason: "account-manager", agentType: "admin" };
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 };
2641
2645
  }
2642
2646
  switch (cfg.dmPolicy) {
2643
2647
  case "open":
2644
- return { allowed: true, reason: "dm-policy-open", agentType: "public" };
2648
+ return { allowed: true, reason: "dm-policy-open", agentType: "public", effectiveAccountId: accountId };
2645
2649
  case "allowlist": {
2646
2650
  const inList = cfg.allowFrom.some((entry) => {
2647
2651
  if (entry === "*") return true;
2648
2652
  return phonesMatch(entry, senderPhone);
2649
2653
  });
2650
2654
  if (inList) {
2651
- return { allowed: true, reason: "allowlist-match", agentType: "public" };
2655
+ return { allowed: true, reason: "allowlist-match", agentType: "public", effectiveAccountId: accountId };
2652
2656
  }
2653
2657
  return { allowed: false, reason: "not-in-allowlist", agentType: "public" };
2654
2658
  }
@@ -4100,7 +4104,9 @@ function buildSelfChatDispatch(input) {
4100
4104
  senderPhone,
4101
4105
  isGroup: false
4102
4106
  }),
4103
- media: []
4107
+ media: [],
4108
+ // Self-chat is the owner: the session scopes to the house account (Task 1383).
4109
+ effectiveAccountId: input.accountId
4104
4110
  };
4105
4111
  }
4106
4112
  async function init(opts) {
@@ -4748,12 +4754,24 @@ async function handleInboundMessage(conn, msg) {
4748
4754
  config: whatsAppConfig,
4749
4755
  accountConfig: whatsAppConfig.accounts?.[conn.accountId],
4750
4756
  accountId: conn.accountId,
4757
+ isValidAccount: (id) => listValidAccounts().some((a) => a.accountId === id),
4751
4758
  resolvePersonByPhone
4752
4759
  });
4753
4760
  }
4754
4761
  console.error(
4755
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}` : "")
4756
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
+ }
4757
4775
  if (!accessResult.allowed) return;
4758
4776
  if (isCustomerTurnSuppressed(accessResult.agentType, isHouseManagedService())) {
4759
4777
  console.error(
@@ -4801,7 +4819,10 @@ async function handleInboundMessage(conn, msg) {
4801
4819
  composing,
4802
4820
  cacheKey,
4803
4821
  media: mediaResult?.path ? [{ path: mediaResult.path, type: extracted.mediaType, mimetype: mediaResult.mimetype }] : [],
4804
- 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
4805
4826
  };
4806
4827
  if (accessResult.agentType === "public") {
4807
4828
  const hoursResult = await isBusinessOpen(conn.accountId);
@@ -5719,8 +5740,8 @@ function webchatTurnTimeoutMs() {
5719
5740
  return Number(process.env.WEBCHAT_TURN_TIMEOUT_MS ?? String(2 * 6e4));
5720
5741
  }
5721
5742
  function createChatRoutes(deps) {
5722
- const app56 = new Hono();
5723
- app56.post("/", async (c) => {
5743
+ const app57 = new Hono();
5744
+ app57.post("/", async (c) => {
5724
5745
  console.log(`[chat-route] entered route=public method=POST`);
5725
5746
  const contentType = c.req.header("content-type") ?? "";
5726
5747
  const account = resolveAccount();
@@ -5983,7 +6004,7 @@ ${result.result.text}` : result.result.text;
5983
6004
  }
5984
6005
  });
5985
6006
  });
5986
- return app56;
6007
+ return app57;
5987
6008
  }
5988
6009
 
5989
6010
  // app/lib/channel-pty-bridge/admin-session-id.ts
@@ -6004,8 +6025,8 @@ function isLoopbackAddr(addr) {
6004
6025
  return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
6005
6026
  }
6006
6027
  function createScheduleInjectRoutes(deps) {
6007
- const app56 = new Hono();
6008
- app56.post("/", async (c) => {
6028
+ const app57 = new Hono();
6029
+ app57.post("/", async (c) => {
6009
6030
  const env = c.env;
6010
6031
  const remoteAddr = env?.incoming?.socket?.remoteAddress ?? "";
6011
6032
  if (!isLoopbackAddr(remoteAddr)) {
@@ -6044,12 +6065,22 @@ function createScheduleInjectRoutes(deps) {
6044
6065
  console.error(`${TAG16} op=received eventId=${eventId} channel=${channel} destination=${destination} account=${houseAccountId}`);
6045
6066
  if (channel === "whatsapp") {
6046
6067
  const effectiveAccount2 = deps.effectiveAccountFor(houseAccountId, account.accountDir, destination);
6068
+ if (effectiveAccount2 === null) {
6069
+ console.error(`${TAG16} op=schedule-account-manager-reject eventId=${eventId} destination=${destination} reason=unresolved-effective-account`);
6070
+ return c.json({ ok: false, error: "account-manager-unresolved" }, 403);
6071
+ }
6047
6072
  console.error(`${TAG16} op=effective-account eventId=${eventId} effectiveAccount=${effectiveAccount2}`);
6048
6073
  const sessionId2 = adminSessionIdFor(effectiveAccount2, destination);
6049
6074
  const reply2 = (text) => deps.sendWhatsAppText(houseAccountId, destination, text);
6050
6075
  try {
6051
6076
  await deps.waHandleInbound({
6052
6077
  accountId: houseAccountId,
6078
+ // Task 1383 — the router (ensureChannelSession) no longer re-resolves
6079
+ // the accountManagers map; it consumes this value. The scheduler
6080
+ // supplies its own resolution (effectiveAccountFor, computed above) so
6081
+ // a manager destination scopes the spawned session to the sub-account,
6082
+ // exactly as a real inbound now does via the gate.
6083
+ effectiveAccountId: effectiveAccount2,
6053
6084
  senderId: destination,
6054
6085
  role: "admin",
6055
6086
  personId: null,
@@ -6093,7 +6124,7 @@ function createScheduleInjectRoutes(deps) {
6093
6124
  console.error(`${TAG16} op=inject-spawn eventId=${eventId} sessionId=${sessionId} result=ok status=-`);
6094
6125
  return c.json({ ok: true }, 200);
6095
6126
  });
6096
- return app56;
6127
+ return app57;
6097
6128
  }
6098
6129
 
6099
6130
  // app/lib/telegram/outbound/send-text.ts
@@ -6120,7 +6151,7 @@ import { randomUUID as randomUUID6 } from "crypto";
6120
6151
 
6121
6152
  // app/lib/whatsapp/config-persist.ts
6122
6153
  import { readFileSync as readFileSync10, writeFileSync as writeFileSync4, existsSync as existsSync5 } from "fs";
6123
- import { resolve as resolve8, join as join8 } from "path";
6154
+ import { resolve as resolve8, join as join8, dirname as dirname2, basename as basename2 } from "path";
6124
6155
  var TAG17 = "[whatsapp:config]";
6125
6156
  function configPath(accountDir) {
6126
6157
  return resolve8(accountDir, "account.json");
@@ -6195,6 +6226,16 @@ function addAdminPhone(accountDir, phone) {
6195
6226
  }
6196
6227
  try {
6197
6228
  const config = readConfig(accountDir);
6229
+ const targetId = typeof config.accountId === "string" && config.accountId ? config.accountId : basename2(resolve8(accountDir));
6230
+ const socketOwner = resolveHouseOrSoleAccountId(dirname2(resolve8(accountDir)));
6231
+ if (targetId !== socketOwner) {
6232
+ console.error(`${TAG17} add-admin-phone rejected accountId=${targetId} reason=not-socket-account`);
6233
+ return {
6234
+ ok: false,
6235
+ reason: "not-socket-account",
6236
+ 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.`
6237
+ };
6238
+ }
6198
6239
  if (!config.whatsapp || typeof config.whatsapp !== "object") {
6199
6240
  config.whatsapp = {};
6200
6241
  }
@@ -6270,6 +6311,44 @@ function readAdminPhones(accountDir) {
6270
6311
  return [];
6271
6312
  }
6272
6313
  }
6314
+ function purgeNonSocketAdminPhones(accountDir, socketOwnerId) {
6315
+ try {
6316
+ const config = readConfig(accountDir);
6317
+ const wa = config.whatsapp;
6318
+ if (!wa || typeof wa !== "object") return { removed: [] };
6319
+ const targetId = typeof config.accountId === "string" && config.accountId ? config.accountId : basename2(resolve8(accountDir));
6320
+ if (targetId === socketOwnerId) return { removed: [] };
6321
+ const adminPhones = Array.isArray(wa.adminPhones) ? wa.adminPhones.filter((p) => typeof p === "string") : [];
6322
+ if (adminPhones.length === 0) return { removed: [] };
6323
+ delete wa.adminPhones;
6324
+ writeConfig(accountDir, config);
6325
+ for (const phone of adminPhones) {
6326
+ console.error(`[admin-identity] op=purge-nonsocket-adminphone accountId=${targetId} phone=${phone} removed=true`);
6327
+ }
6328
+ return { removed: adminPhones };
6329
+ } catch (err) {
6330
+ console.error(`[admin-identity] purge-nonsocket-adminphone failed dir=${accountDir}: ${String(err)}`);
6331
+ return { removed: [] };
6332
+ }
6333
+ }
6334
+ function purgeNonSocketAdminPhonesAtBoot() {
6335
+ let socketOwner;
6336
+ try {
6337
+ socketOwner = resolveHouseOrSoleAccountId(ACCOUNTS_DIR);
6338
+ } catch (err) {
6339
+ console.error(`[admin-identity] purge-nonsocket-adminphone skipped \u2014 cannot resolve socket owner: ${String(err)}`);
6340
+ return;
6341
+ }
6342
+ let accounts;
6343
+ try {
6344
+ accounts = listValidAccounts();
6345
+ } catch {
6346
+ return;
6347
+ }
6348
+ for (const acct of accounts) {
6349
+ purgeNonSocketAdminPhones(acct.accountDir, socketOwner);
6350
+ }
6351
+ }
6273
6352
  function setAccountManager(accountDir, phone, subAccountId) {
6274
6353
  const normalized = phone.trim();
6275
6354
  if (!E164_PATTERN.test(normalized)) {
@@ -6798,7 +6877,7 @@ function listActiveLoginAccountIds() {
6798
6877
  // app/lib/whatsapp/outbound/send-document.ts
6799
6878
  import { realpathSync as realpathSync3 } from "fs";
6800
6879
  import { readFile, stat as stat2 } from "fs/promises";
6801
- import { resolve as resolve9, basename as basename2 } from "path";
6880
+ import { resolve as resolve9, basename as basename3 } from "path";
6802
6881
  var TAG19 = "[whatsapp:outbound]";
6803
6882
  var WHATSAPP_DOCUMENT_MAX_BYTES = 100 * 1024 * 1024;
6804
6883
  var lastDocumentOutboundAt = /* @__PURE__ */ new Map();
@@ -6833,7 +6912,7 @@ async function sendWhatsAppDocument(input) {
6833
6912
  const accountResolved = realpathSync3(accountDir);
6834
6913
  if (!resolvedPath.startsWith(accountResolved + "/")) {
6835
6914
  const sanitised = filePath.replace(accountDir, "<account>/");
6836
- console.error(`${TAG19} document REJECTED path=${sanitised} reason=outside_account_directory`);
6915
+ console.error(`${TAG19} document REJECTED path=${sanitised} reason=outside_account_directory maxyAccountId=${maxyAccountId}`);
6837
6916
  return { ok: false, status: 403, error: "Access denied: file is outside the account directory" };
6838
6917
  }
6839
6918
  } catch (err) {
@@ -6853,7 +6932,7 @@ async function sendWhatsAppDocument(input) {
6853
6932
  error: `File exceeds 100 MB limit (${(fileStat.size / 1024 / 1024).toFixed(1)} MB)`
6854
6933
  };
6855
6934
  }
6856
- const filename = basename2(resolvedPath);
6935
+ const filename = basename3(resolvedPath);
6857
6936
  const jid = normalizeJid2(to);
6858
6937
  const sock = getSocket(accountId);
6859
6938
  if (!sock) {
@@ -7142,7 +7221,8 @@ app2.post("/config", async (c) => {
7142
7221
  return c.json({ ok: false, error: 'Missing required field "phone" (E.164 format, e.g. +441234567890).' }, 400);
7143
7222
  }
7144
7223
  const result = addAdminPhone(account.accountDir, phone);
7145
- console.error(`${TAG21} config action=add-admin-phone phone=${phone} ok=${result.ok}`);
7224
+ const reasonTail = !result.ok && result.reason ? ` reason=${result.reason}` : "";
7225
+ console.error(`${TAG21} config action=add-admin-phone accountId=${account.accountId} phone=${phone} ok=${result.ok}${reasonTail}`);
7146
7226
  return c.json(result, result.ok ? 200 : 400);
7147
7227
  }
7148
7228
  case "remove-admin-phone": {
@@ -7501,11 +7581,11 @@ var whatsapp_default = app2;
7501
7581
 
7502
7582
  // server/routes/whatsapp-reader.ts
7503
7583
  import { readFileSync as readFileSync15, watch, statSync as statSync5, openSync, readSync, closeSync, existsSync as existsSync8, readdirSync as readdirSync8, realpathSync as realpathSync4 } from "fs";
7504
- import { basename as basename4, dirname as dirname3, isAbsolute, join as join14, relative as relative2, resolve as resolve12, sep as sep3 } from "path";
7584
+ import { basename as basename5, dirname as dirname4, isAbsolute, join as join14, relative as relative2, resolve as resolve12, sep as sep3 } from "path";
7505
7585
 
7506
7586
  // server/routes/admin/sidebar-sessions.ts
7507
7587
  import { readdirSync as readdirSync5, readFileSync as readFileSync12, statSync as statSync3 } from "fs";
7508
- import { basename as basename3, dirname as dirname2, join as join11, resolve as resolve11 } from "path";
7588
+ import { basename as basename4, dirname as dirname3, join as join11, resolve as resolve11 } from "path";
7509
7589
 
7510
7590
  // app/lib/whatsapp-reader/select-sessions.ts
7511
7591
  function isReaderChannelSession(role, channel) {
@@ -7586,7 +7666,7 @@ function findSessionProjectDir(sessionId) {
7586
7666
  const cfg = claudeConfigDir();
7587
7667
  if (!cfg) return null;
7588
7668
  for (const { path: path2 } of enumerateJsonls(join11(cfg, "projects"))) {
7589
- if (basename3(path2) === `${sessionId}.jsonl`) return dirname2(path2);
7669
+ if (basename4(path2) === `${sessionId}.jsonl`) return dirname3(path2);
7590
7670
  }
7591
7671
  return null;
7592
7672
  }
@@ -8315,8 +8395,8 @@ app4.get("/conversations", requireAdminSession, async (c) => {
8315
8395
  const rows = [];
8316
8396
  let sessionRowsExcludedUntagged = 0;
8317
8397
  for (const { path: path2 } of enumerateJsonls(projectsRoot)) {
8318
- const sessionId = basename4(path2).replace(/\.jsonl$/, "");
8319
- const projectDir = dirname3(path2);
8398
+ const sessionId = basename5(path2).replace(/\.jsonl$/, "");
8399
+ const projectDir = dirname4(path2);
8320
8400
  const meta = readSidecarMeta(join14(projectDir, `${sessionId}.meta.json`));
8321
8401
  if (!isReaderChannelSession(meta.role, meta.channel)) continue;
8322
8402
  if (multiAccount) {
@@ -8846,7 +8926,7 @@ var whatsapp_reader_default = app4;
8846
8926
 
8847
8927
  // server/routes/public-reader.ts
8848
8928
  import { statSync as statSync6, openSync as openSync2, readSync as readSync2, closeSync as closeSync2, watch as watch2, readFileSync as readFileSync16, readdirSync as readdirSync9 } from "fs";
8849
- import { basename as basename5, dirname as dirname4, join as join15, resolve as resolve13, sep as sep4 } from "path";
8929
+ import { basename as basename6, dirname as dirname5, join as join15, resolve as resolve13, sep as sep4 } from "path";
8850
8930
 
8851
8931
  // app/lib/whatsapp-reader/delivered-kinds.ts
8852
8932
  var PUBLIC_DELIVERED_KINDS = /* @__PURE__ */ new Set([
@@ -8921,8 +9001,8 @@ function enumeratePublicRows() {
8921
9001
  if (!cfg) return [];
8922
9002
  const rows = [];
8923
9003
  for (const { path: path2 } of enumerateJsonls(join15(cfg, "projects"))) {
8924
- const sessionId = basename5(path2).replace(/\.jsonl$/, "");
8925
- const projectDir = dirname4(path2);
9004
+ const sessionId = basename6(path2).replace(/\.jsonl$/, "");
9005
+ const projectDir = dirname5(path2);
8926
9006
  const meta = readSidecarMeta(join15(projectDir, `${sessionId}.meta.json`));
8927
9007
  rows.push({
8928
9008
  sessionId,
@@ -9140,7 +9220,7 @@ app5.get("/attachment/:attachmentId", (c) => {
9140
9220
  var public_reader_default = app5;
9141
9221
 
9142
9222
  // server/routes/webchat.ts
9143
- import { basename as basename6, dirname as dirname5 } from "path";
9223
+ import { basename as basename7, dirname as dirname6 } from "path";
9144
9224
  import { join as join18 } from "path";
9145
9225
  import { existsSync as existsSync10, readdirSync as readdirSync11, readFileSync as readFileSync19, renameSync as renameSync3, statSync as statSync7, writeFileSync as writeFileSync7 } from "fs";
9146
9226
 
@@ -9538,8 +9618,8 @@ function latestAdminWebchatSessionId(requesterUserId, primaryUserId, scopeAccoun
9538
9618
  let best = null;
9539
9619
  for (const { path: path2, isSubagent, archived } of enumerateJsonls(join18(cfg, "projects"))) {
9540
9620
  if (isSubagent || archived) continue;
9541
- const id = basename6(path2).slice(0, -".jsonl".length);
9542
- const meta = readSidecarMeta(join18(dirname5(path2), `${id}.meta.json`));
9621
+ const id = basename7(path2).slice(0, -".jsonl".length);
9622
+ const meta = readSidecarMeta(join18(dirname6(path2), `${id}.meta.json`));
9543
9623
  if (meta.role !== "admin" || meta.channel !== "webchat") continue;
9544
9624
  if (multiAccount && meta.accountId !== scopeAccountId) continue;
9545
9625
  let mtimeMs;
@@ -9562,7 +9642,7 @@ function latestAdminWebchatSessionId(requesterUserId, primaryUserId, scopeAccoun
9562
9642
  function overrideKnownNonArchived(id) {
9563
9643
  const { projectDir } = locateSession(id);
9564
9644
  if (projectDir === null) return sessionSidecarExists(id);
9565
- return basename6(projectDir) !== "archive";
9645
+ return basename7(projectDir) !== "archive";
9566
9646
  }
9567
9647
  function resolveCanonical(accountId, accountDir, requesterUserId, primaryUserId, scopeAccountId, multiAccount) {
9568
9648
  const bootstrapAccountId = scopeAccountId ?? accountId;
@@ -9598,8 +9678,8 @@ async function reapplyLeversViaManager() {
9598
9678
  var WEBCHAT_SEND_TURN_WINDOW_MS = Number(process.env.WEBCHAT_SEND_TURN_WINDOW_MS ?? String(15e3));
9599
9679
  var sendSeq = 0;
9600
9680
  function createWebchatRoutes(deps) {
9601
- const app56 = new Hono();
9602
- app56.post("/send", requireAdminSession, async (c) => {
9681
+ const app57 = new Hono();
9682
+ app57.post("/send", requireAdminSession, async (c) => {
9603
9683
  let accountId;
9604
9684
  try {
9605
9685
  accountId = resolvePlatformAccountId();
@@ -9799,7 +9879,7 @@ ${note}` : note;
9799
9879
  if (turnTimer.unref) turnTimer.unref();
9800
9880
  return c.json({ ok: true });
9801
9881
  });
9802
- app56.post("/interrupt", requireAdminSession, async (c) => {
9882
+ app57.post("/interrupt", requireAdminSession, async (c) => {
9803
9883
  const cacheKey = c.get("cacheKey");
9804
9884
  const requesterUserId = (cacheKey ? getUserIdForSession(cacheKey) : void 0) ?? null;
9805
9885
  const primaryUserId = resolvePrimaryAdminUserId();
@@ -9827,7 +9907,7 @@ ${note}` : note;
9827
9907
  }
9828
9908
  return c.json({ ok: true, stopped: outcome.stopped, intId: outcome.intId, deadChild: outcome.deadChild }, 200);
9829
9909
  });
9830
- app56.post("/settings", requireAdminSession, async (c) => {
9910
+ app57.post("/settings", requireAdminSession, async (c) => {
9831
9911
  const body = await c.req.json().catch(() => null);
9832
9912
  const op = body?.op;
9833
9913
  const value = body?.value;
@@ -9867,7 +9947,7 @@ ${note}` : note;
9867
9947
  console.log(`[webchat:settings] op=${op} outcome=accepted value=${value} settingsApplied=${settingsApplied}`);
9868
9948
  return c.json({ ok: true, settingsApplied });
9869
9949
  });
9870
- app56.get("/session", requireAdminSession, async (c) => {
9950
+ app57.get("/session", requireAdminSession, async (c) => {
9871
9951
  let accountId;
9872
9952
  try {
9873
9953
  accountId = resolvePlatformAccountId();
@@ -9930,8 +10010,8 @@ ${note}` : note;
9930
10010
  let projectDir = null;
9931
10011
  if (cfg) {
9932
10012
  for (const { path: path2 } of enumerateJsonls(join18(cfg, "projects"))) {
9933
- if (basename6(path2) === `${sessionId}.jsonl`) {
9934
- projectDir = dirname5(path2);
10013
+ if (basename7(path2) === `${sessionId}.jsonl`) {
10014
+ projectDir = dirname6(path2);
9935
10015
  break;
9936
10016
  }
9937
10017
  }
@@ -9945,7 +10025,7 @@ ${note}` : note;
9945
10025
  const indicators = await fetchComposerIndicators(sessionId);
9946
10026
  return c.json({ sessionId, projectDir, sizeBytes: jsonlSizeBytes(projectDir, sessionId), pendingPermissionPrompt: deps.pendingPromptFor?.(`session:${sessionId}`) ?? null, deliveryFailure: deps.deliveryFailureFor?.(`session:${sessionId}`) ?? null, ...indicators, ...readLevers(account) });
9947
10027
  });
9948
- app56.post("/permission-verdict", requireAdminSession, async (c) => {
10028
+ app57.post("/permission-verdict", requireAdminSession, async (c) => {
9949
10029
  const body = await c.req.json().catch(() => null);
9950
10030
  const sessionId = body?.sessionId;
9951
10031
  const requestId = body?.request_id;
@@ -9962,7 +10042,7 @@ ${note}` : note;
9962
10042
  const ok = deps.resolvePermissionVerdict?.(`session:${sessionId}`, requestId, behavior) ?? false;
9963
10043
  return c.json({ ok });
9964
10044
  });
9965
- return app56;
10045
+ return app57;
9966
10046
  }
9967
10047
 
9968
10048
  // server/routes/webchat-greeting.ts
@@ -10369,7 +10449,7 @@ import { createHash as createHash3, randomUUID as randomUUID7 } from "crypto";
10369
10449
 
10370
10450
  // ../lib/admins-write/src/index.ts
10371
10451
  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";
10372
- import { dirname as dirname6, join as join22 } from "path";
10452
+ import { dirname as dirname7, join as join22 } from "path";
10373
10453
  function id8(value) {
10374
10454
  return value.slice(0, 8);
10375
10455
  }
@@ -10382,7 +10462,7 @@ function appendUsersAuditLine(audit, fields) {
10382
10462
  const line = `[users-audit] action=${fields.action} actor=${actor}${sess} field=${fields.field} rowsBefore=${fields.rowsBefore} rowsAfter=${fields.rowsAfter} ts=${(/* @__PURE__ */ new Date()).toISOString()}
10383
10463
  `;
10384
10464
  try {
10385
- mkdirSync5(dirname6(audit.logFile), { recursive: true, mode: 448 });
10465
+ mkdirSync5(dirname7(audit.logFile), { recursive: true, mode: 448 });
10386
10466
  appendFileSync2(audit.logFile, line, { mode: 384 });
10387
10467
  } catch (err) {
10388
10468
  console.error(
@@ -10400,7 +10480,7 @@ function logLine(input, result) {
10400
10480
  );
10401
10481
  }
10402
10482
  function writeFileAtomic(filePath, contents, mode) {
10403
- mkdirSync5(dirname6(filePath), { recursive: true });
10483
+ mkdirSync5(dirname7(filePath), { recursive: true });
10404
10484
  const tempPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
10405
10485
  writeFileSync9(tempPath, contents, mode !== void 0 ? { mode } : void 0);
10406
10486
  renameSync5(tempPath, filePath);
@@ -11249,7 +11329,7 @@ var accounts_default = app12;
11249
11329
 
11250
11330
  // server/routes/admin/logs.ts
11251
11331
  import { existsSync as existsSync18, readdirSync as readdirSync14, readFileSync as readFileSync25, statSync as statSync10 } from "fs";
11252
- import { resolve as resolve16, basename as basename7 } from "path";
11332
+ import { resolve as resolve16, basename as basename8 } from "path";
11253
11333
 
11254
11334
  // app/lib/logs-read-resolve.ts
11255
11335
  import { existsSync as existsSync17 } from "fs";
@@ -11281,7 +11361,7 @@ app13.get("/", async (c) => {
11281
11361
  if (accountLogDir) logDirs.push(accountLogDir);
11282
11362
  logDirs.push(LOG_DIR);
11283
11363
  if (fileParam) {
11284
- const safe = basename7(fileParam);
11364
+ const safe = basename8(fileParam);
11285
11365
  const searched = [];
11286
11366
  for (const dir of logDirs) {
11287
11367
  const filePath = resolve16(dir, safe);
@@ -11351,7 +11431,7 @@ app13.get("/", async (c) => {
11351
11431
  if (hit) {
11352
11432
  console.info(`[admin/logs] resolved cacheKey=${cacheKeySlice} sessionId=${sessionIdSlice} via=${primaryId === cacheKey ? "cacheKey" : primaryId === sessionKeyFromConv ? "reverse-lookup" : "sessionId-fallback"}`);
11353
11433
  try {
11354
- const filename = basename7(hit.path);
11434
+ const filename = basename8(hit.path);
11355
11435
  const buffer = readFileSync25(hit.path);
11356
11436
  const onDiskBytes = statSync10(hit.path).size;
11357
11437
  const headers = {
@@ -12285,7 +12365,7 @@ var sessions_default = app17;
12285
12365
 
12286
12366
  // app/lib/claude-agent/spawn-context.ts
12287
12367
  import { existsSync as existsSync22, readFileSync as readFileSync27, readdirSync as readdirSync16, statSync as statSync11 } from "fs";
12288
- import { dirname as dirname7, resolve as resolve19, join as join25 } from "path";
12368
+ import { dirname as dirname8, resolve as resolve19, join as join25 } from "path";
12289
12369
  async function resolveOwnerProfileBlock(accountId, userId) {
12290
12370
  if (!userId) return { ok: false, reason: "missing-user-id" };
12291
12371
  try {
@@ -12392,7 +12472,7 @@ function listPluginDirs() {
12392
12472
  }
12393
12473
  }
12394
12474
  }
12395
- const premiumRoot = resolve19(dirname7(PLATFORM_ROOT), "premium-plugins");
12475
+ const premiumRoot = resolve19(dirname8(PLATFORM_ROOT), "premium-plugins");
12396
12476
  if (existsSync22(premiumRoot)) {
12397
12477
  for (const bundle of readdirSync16(premiumRoot)) {
12398
12478
  const bundlePlugins = join25(premiumRoot, bundle, "plugins");
@@ -12841,7 +12921,7 @@ import { createReadStream as createReadStream2, createWriteStream as createWrite
12841
12921
  import { readdir as readdir3, readFile as readFile4, stat as stat4, mkdir as mkdir3, unlink as unlink2, rename } from "fs/promises";
12842
12922
  import { realpathSync as realpathSync5 } from "fs";
12843
12923
  import { randomUUID as randomUUID9 } from "crypto";
12844
- import { basename as basename9, dirname as dirname8, join as join27, relative as relative4, resolve as resolve22, sep as sep6 } from "path";
12924
+ import { basename as basename10, dirname as dirname9, join as join27, relative as relative4, resolve as resolve22, sep as sep6 } from "path";
12845
12925
  import { Readable as Readable2 } from "stream";
12846
12926
 
12847
12927
  // ../lib/graph-trash/src/index.ts
@@ -13159,7 +13239,7 @@ async function cascadeDeleteDocument(params) {
13159
13239
 
13160
13240
  // app/lib/file-index.ts
13161
13241
  import * as fsp from "fs/promises";
13162
- import { resolve as resolve21, relative as relative3, join as join26, basename as basename8, extname as extname2, sep as sep5 } from "path";
13242
+ import { resolve as resolve21, relative as relative3, join as join26, basename as basename9, extname as extname2, sep as sep5 } from "path";
13163
13243
  import { tmpdir as tmpdir2 } from "os";
13164
13244
  import { execFile as execFile2 } from "child_process";
13165
13245
  import { promisify as promisify2 } from "util";
@@ -13297,8 +13377,8 @@ async function extractFileContent(absolute) {
13297
13377
  }
13298
13378
  }
13299
13379
  async function readDisplayName(absolute) {
13300
- const dir = absolute.slice(0, absolute.length - basename8(absolute).length);
13301
- const base = basename8(absolute);
13380
+ const dir = absolute.slice(0, absolute.length - basename9(absolute).length);
13381
+ const base = basename9(absolute);
13302
13382
  const dot = base.lastIndexOf(".");
13303
13383
  const stem = dot === -1 ? base : base.slice(0, dot);
13304
13384
  if (base === `${stem}.meta.json`) return null;
@@ -13436,7 +13516,7 @@ async function cascadeTrashLinkedKnowledgeDocs(session, accountId, relativePaths
13436
13516
  return { kds, sections, chunks };
13437
13517
  }
13438
13518
  async function buildArtifact(accountId, wf, embed2) {
13439
- const name = basename8(wf.absolute);
13519
+ const name = basename9(wf.absolute);
13440
13520
  const { content, route } = await extractFileContent(wf.absolute);
13441
13521
  const displayName = await readDisplayName(wf.absolute);
13442
13522
  const embedInput = `${name}
@@ -13966,7 +14046,7 @@ app21.get("/download", requireAdminSession, async (c) => {
13966
14046
  if (!info.isFile()) {
13967
14047
  return c.json({ error: "Path is not a file" }, 400);
13968
14048
  }
13969
- const filename = basename9(absolute);
14049
+ const filename = basename10(absolute);
13970
14050
  const mimeType = detectMimeType(absolute);
13971
14051
  const nodeStream = createReadStream2(absolute);
13972
14052
  const webStream = Readable2.toWeb(nodeStream);
@@ -14071,7 +14151,7 @@ app21.get("/download-zip", requireAdminSession, async (c) => {
14071
14151
  const info = await stat4(absolute);
14072
14152
  if (info.isDirectory()) {
14073
14153
  dirsWalked++;
14074
- const parentAbs = dirname8(absolute);
14154
+ const parentAbs = dirname9(absolute);
14075
14155
  for await (const fileAbs of walkRegularFiles(absolute)) {
14076
14156
  const within = relative4(absolute, fileAbs).split(sep6).join("/");
14077
14157
  const fileRel = relPath === "" || relPath === "." ? within : `${relPath}/${within}`;
@@ -14082,7 +14162,7 @@ app21.get("/download-zip", requireAdminSession, async (c) => {
14082
14162
  if (over) return over;
14083
14163
  }
14084
14164
  } else if (info.isFile()) {
14085
- const over = await addFile(absolute, info.size, basename9(absolute));
14165
+ const over = await addFile(absolute, info.size, basename10(absolute));
14086
14166
  if (over) return over;
14087
14167
  } else {
14088
14168
  return c.json({ error: "Path is not a file or directory" }, 400);
@@ -14111,12 +14191,12 @@ function dataUploadCeiling() {
14111
14191
  }
14112
14192
  async function resolveUploadTarget(c, accountId, uid) {
14113
14193
  const rawName = c.req.query("filename") ?? "";
14114
- const safeName = basename9(rawName).replace(/[\0/\\]/g, "_");
14194
+ const safeName = basename10(rawName).replace(/[\0/\\]/g, "_");
14115
14195
  if (!safeName) return { ok: false, status: 400, error: "filename query param required" };
14116
14196
  const rawRelpath = c.req.query("relpath") ?? "";
14117
14197
  const relpath = rawRelpath !== "" ? rawRelpath : null;
14118
14198
  const token = c.req.query("token") ?? "";
14119
- const finalName = relpath ? basename9(relpath).replace(/[\0/\\]/g, "_") : `${Date.now()}-${safeName}`;
14199
+ const finalName = relpath ? basename10(relpath).replace(/[\0/\\]/g, "_") : `${Date.now()}-${safeName}`;
14120
14200
  const mimeType = (c.req.header("content-type") ?? "").split(";")[0].trim();
14121
14201
  if (!SUPPORTED_MIME_TYPES.has(mimeType)) {
14122
14202
  console.error(`[data-upload] op=reject-mime uid=${uid} mime="${mimeType}" token=${token} rel="${relpath ?? ""}"`);
@@ -14125,7 +14205,7 @@ async function resolveUploadTarget(c, accountId, uid) {
14125
14205
  const rawDir = c.req.query("path") ?? "";
14126
14206
  const base = resolveOwnAccountWrite(rawDir, accountId, "POST /api/admin/files/upload");
14127
14207
  if (!base.ok) return { ok: false, status: base.status, error: base.error };
14128
- const relDir = relpath ? dirname8(relpath) : ".";
14208
+ const relDir = relpath ? dirname9(relpath) : ".";
14129
14209
  const destRel = relDir === "." ? base.relative : join27(base.relative, relDir);
14130
14210
  if (crossesForeignAccountPartition(destRel, accountId)) {
14131
14211
  console.error(`[data] account-scope-blocked endpoint="POST /api/admin/files/upload" path="${destRel}" account=${accountId.slice(0, 8)}\u2026`);
@@ -14486,7 +14566,7 @@ app21.delete("/", requireAdminSession, async (c) => {
14486
14566
  console.error(`[data] account-scope-blocked endpoint="DELETE /api/admin/files" path="${relPath}" account=${accountId.slice(0, 8)}\u2026`);
14487
14567
  return c.json({ error: "Not found" }, 404);
14488
14568
  }
14489
- const base = basename9(absolute);
14569
+ const base = basename10(absolute);
14490
14570
  const protection = isProtectedFromDeletion(relPath);
14491
14571
  if (protection.protected) {
14492
14572
  console.error(`[data] file-delete blocked path="${relPath}" reason="protected" rule="${protection.rule}"`);
@@ -14502,7 +14582,7 @@ app21.delete("/", requireAdminSession, async (c) => {
14502
14582
  }
14503
14583
  const dot = base.lastIndexOf(".");
14504
14584
  const stem = dot === -1 ? base : base.slice(0, dot);
14505
- const sidecarPath = UUID_RE3.test(stem) && base !== `${stem}.meta.json` ? join27(dirname8(absolute), `${stem}.meta.json`) : null;
14585
+ const sidecarPath = UUID_RE3.test(stem) && base !== `${stem}.meta.json` ? join27(dirname9(absolute), `${stem}.meta.json`) : null;
14506
14586
  await unlink2(absolute);
14507
14587
  if (sidecarPath) {
14508
14588
  try {
@@ -14598,8 +14678,8 @@ app21.post("/rename", requireAdminSession, async (c) => {
14598
14678
  console.error(`[data] file-rename blocked path="${src.relative}" reason="protected"`);
14599
14679
  return c.json({ error: "Protected entry \u2014 refusing to rename" }, 403);
14600
14680
  }
14601
- const destAbs = resolve22(dirname8(src.absolute), newName);
14602
- const newRel = `${dirname8(src.relative)}/${newName}`.replace(/^\.\//, "");
14681
+ const destAbs = resolve22(dirname9(src.absolute), newName);
14682
+ const newRel = `${dirname9(src.relative)}/${newName}`.replace(/^\.\//, "");
14603
14683
  if (isProtectedFromRename(newRel)) {
14604
14684
  console.error(`[data] file-rename blocked path="${newRel}" reason="protected-target"`);
14605
14685
  return c.json({ error: "That name is reserved" }, 403);
@@ -16730,7 +16810,7 @@ var graph_default_view_default = app27;
16730
16810
 
16731
16811
  // server/routes/admin/sidebar-artefacts.ts
16732
16812
  import { readdir as readdir4, stat as stat5 } from "fs/promises";
16733
- import { resolve as resolve23, relative as relative5, isAbsolute as isAbsolute2, sep as sep7, basename as basename10 } from "path";
16813
+ import { resolve as resolve23, relative as relative5, isAbsolute as isAbsolute2, sep as sep7, basename as basename11 } from "path";
16734
16814
  import { existsSync as existsSync25 } from "fs";
16735
16815
  var LIMIT = 50;
16736
16816
  var ADMIN_AGENT_FILES = ["IDENTITY.md", "SOUL.md", "KNOWLEDGE.md"];
@@ -16783,7 +16863,7 @@ async function fetchAccountFileArtefacts(accountId) {
16783
16863
  const modifiedAt = r.get("modifiedAt") ?? "";
16784
16864
  return {
16785
16865
  id: `account-file:${relativePath}`,
16786
- name: displayName ?? basename10(relativePath),
16866
+ name: displayName ?? basename11(relativePath),
16787
16867
  kind: "account-file",
16788
16868
  updatedAt: modifiedAt,
16789
16869
  mimeType,
@@ -17071,6 +17151,36 @@ app32.post("/", requireAdminSession, async (c) => {
17071
17151
  });
17072
17152
  var session_rename_default = app32;
17073
17153
 
17154
+ // server/routes/admin/session-usage.ts
17155
+ var app33 = new Hono();
17156
+ app33.get("/", requireAdminSession, async (c) => {
17157
+ const sessionId = c.req.query("sessionId") ?? "";
17158
+ if (!SESSION_ID_RE2.test(sessionId)) {
17159
+ return c.json({ error: "sessionId must be a v4 UUID" }, 400);
17160
+ }
17161
+ let res;
17162
+ try {
17163
+ res = await fetch(`${managerBase("session-usage:wrapper")}/${sessionId}/metering`);
17164
+ } catch (err) {
17165
+ console.error(`[session-usage] sessionId=${sessionId.slice(0, 8)} manager-unreachable err=${err instanceof Error ? err.message : String(err)}`);
17166
+ return c.json({ error: "manager-unreachable" }, 502);
17167
+ }
17168
+ if (res.status === 200) {
17169
+ const summary = await res.json().catch(() => null);
17170
+ if (!summary) {
17171
+ console.error(`[session-usage] sessionId=${sessionId.slice(0, 8)} manager-bad-json`);
17172
+ return c.json({ error: "manager-bad-json" }, 502);
17173
+ }
17174
+ return c.json(summary);
17175
+ }
17176
+ if (res.status === 404) {
17177
+ return c.json({ error: "session-not-found" }, 404);
17178
+ }
17179
+ console.error(`[session-usage] sessionId=${sessionId.slice(0, 8)} manager-status=${res.status}`);
17180
+ return c.json({ error: `manager-status-${res.status}` }, 502);
17181
+ });
17182
+ var session_usage_default = app33;
17183
+
17074
17184
  // server/routes/admin/session-rc-spawn.ts
17075
17185
  import { randomUUID as randomUUID10 } from "crypto";
17076
17186
  var ACCOUNT_UUID_RE2 = /^[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}$/;
@@ -17116,8 +17226,8 @@ function shapeWebchatResponse(sessionId, manager) {
17116
17226
  const landed = redirected ? manager.sessionId : sessionId;
17117
17227
  return { sessionId: landed, outcome, target: `/chat?session=${landed}` };
17118
17228
  }
17119
- var app33 = new Hono();
17120
- app33.post("/", requireAdminSession, async (c) => {
17229
+ var app34 = new Hono();
17230
+ app34.post("/", requireAdminSession, async (c) => {
17121
17231
  let body;
17122
17232
  try {
17123
17233
  body = await c.req.json();
@@ -17200,7 +17310,7 @@ app33.post("/", requireAdminSession, async (c) => {
17200
17310
  }
17201
17311
  return c.json(parsed ?? {});
17202
17312
  });
17203
- var session_rc_spawn_default = app33;
17313
+ var session_rc_spawn_default = app34;
17204
17314
 
17205
17315
  // server/routes/admin/session-reseat.ts
17206
17316
  import { randomUUID as randomUUID11 } from "crypto";
@@ -17223,9 +17333,9 @@ function resolveReseatPlan(body, mintId = randomUUID11, adminUserId, authenticat
17223
17333
  if (authenticatedName) payload.authenticatedName = authenticatedName;
17224
17334
  return { sessionId, payload };
17225
17335
  }
17226
- var app34 = new Hono();
17336
+ var app35 = new Hono();
17227
17337
  var reseatsInFlight = /* @__PURE__ */ new Set();
17228
- app34.post("/", requireAdminSession, async (c) => {
17338
+ app35.post("/", requireAdminSession, async (c) => {
17229
17339
  let body;
17230
17340
  try {
17231
17341
  body = await c.req.json();
@@ -17313,7 +17423,7 @@ app34.post("/", requireAdminSession, async (c) => {
17313
17423
  reseatsInFlight.delete(fromSessionId);
17314
17424
  }
17315
17425
  });
17316
- var session_reseat_default = app34;
17426
+ var session_reseat_default = app35;
17317
17427
 
17318
17428
  // server/routes/admin/system-stats.ts
17319
17429
  import { readFile as readFile5 } from "fs/promises";
@@ -17410,8 +17520,8 @@ async function sample() {
17410
17520
  if (process.platform === "linux") return sampleLinux();
17411
17521
  return sampleOsFallback();
17412
17522
  }
17413
- var app35 = new Hono();
17414
- app35.get("/", async (c) => {
17523
+ var app36 = new Hono();
17524
+ app36.get("/", async (c) => {
17415
17525
  const started = Date.now();
17416
17526
  if (!inflight) {
17417
17527
  inflight = sample().finally(() => {
@@ -17434,7 +17544,7 @@ app35.get("/", async (c) => {
17434
17544
  return c.json({ degraded: true, reason, sampledAtMs: Date.now() });
17435
17545
  }
17436
17546
  });
17437
- var system_stats_default = app35;
17547
+ var system_stats_default = app36;
17438
17548
 
17439
17549
  // server/routes/admin/health.ts
17440
17550
  import { existsSync as existsSync26, readFileSync as readFileSync29 } from "fs";
@@ -17479,8 +17589,8 @@ async function probeConversationDb() {
17479
17589
  });
17480
17590
  }
17481
17591
  }
17482
- var app36 = new Hono();
17483
- app36.get("/", async (c) => {
17592
+ var app37 = new Hono();
17593
+ app37.get("/", async (c) => {
17484
17594
  const version = readVersion();
17485
17595
  const probe = await probeConversationDb();
17486
17596
  const uptimeMs = Date.now() - new Date(PROCESS_STARTED_AT).getTime();
@@ -17502,7 +17612,7 @@ app36.get("/", async (c) => {
17502
17612
  uptimeMs
17503
17613
  });
17504
17614
  });
17505
- var health_default2 = app36;
17615
+ var health_default2 = app37;
17506
17616
 
17507
17617
  // server/routes/admin/linkedin-ingest.ts
17508
17618
  import { randomUUID as randomUUID12 } from "crypto";
@@ -17728,8 +17838,8 @@ function buildInitialMessage(env) {
17728
17838
  }
17729
17839
  return header;
17730
17840
  }
17731
- var app37 = new Hono();
17732
- app37.post("/", requireAdminSession, async (c) => {
17841
+ var app38 = new Hono();
17842
+ app38.post("/", requireAdminSession, async (c) => {
17733
17843
  let body;
17734
17844
  try {
17735
17845
  body = await c.req.json();
@@ -17774,7 +17884,7 @@ app37.post("/", requireAdminSession, async (c) => {
17774
17884
  );
17775
17885
  return c.json({ ok: true, dispatchId: envelope.dispatchId, taskId: spawned.sessionId }, 202);
17776
17886
  });
17777
- var linkedin_ingest_default = app37;
17887
+ var linkedin_ingest_default = app38;
17778
17888
 
17779
17889
  // server/routes/admin/post-turn-context.ts
17780
17890
  import neo4j3 from "neo4j-driver";
@@ -17816,8 +17926,8 @@ function pruneProperties(raw) {
17816
17926
  }
17817
17927
  return out;
17818
17928
  }
17819
- var app38 = new Hono();
17820
- app38.get("/", async (c) => {
17929
+ var app39 = new Hono();
17930
+ app39.get("/", async (c) => {
17821
17931
  const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
17822
17932
  if (!isLoopbackAddr3(remoteAddr)) {
17823
17933
  console.error(`${TAG28} reject reason=non-loopback remoteAddr=${remoteAddr}`);
@@ -17877,7 +17987,7 @@ app38.get("/", async (c) => {
17877
17987
  await session.close();
17878
17988
  }
17879
17989
  });
17880
- var post_turn_context_default = app38;
17990
+ var post_turn_context_default = app39;
17881
17991
 
17882
17992
  // app/lib/slice-writes.ts
17883
17993
  import neo4j4 from "neo4j-driver";
@@ -17987,8 +18097,8 @@ var TAG29 = "[public-session-context]";
17987
18097
  function isLoopbackAddr4(addr) {
17988
18098
  return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
17989
18099
  }
17990
- var app39 = new Hono();
17991
- app39.get("/", async (c) => {
18100
+ var app40 = new Hono();
18101
+ app40.get("/", async (c) => {
17992
18102
  const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
17993
18103
  if (!isLoopbackAddr4(remoteAddr)) {
17994
18104
  console.error(`${TAG29} reject reason=non-loopback remoteAddr=${remoteAddr}`);
@@ -18028,7 +18138,7 @@ app39.get("/", async (c) => {
18028
18138
  await session.close();
18029
18139
  }
18030
18140
  });
18031
- var public_session_context_default = app39;
18141
+ var public_session_context_default = app40;
18032
18142
 
18033
18143
  // app/lib/webchat/gateway/instance.ts
18034
18144
  var instance2 = null;
@@ -18044,8 +18154,8 @@ var TAG30 = "[public-session-exit-route]";
18044
18154
  function isLoopbackAddr5(addr) {
18045
18155
  return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
18046
18156
  }
18047
- var app40 = new Hono();
18048
- app40.post("/", async (c) => {
18157
+ var app41 = new Hono();
18158
+ app41.post("/", async (c) => {
18049
18159
  const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
18050
18160
  if (!isLoopbackAddr5(remoteAddr)) {
18051
18161
  console.error(`${TAG30} reject reason=non-loopback remoteAddr=${remoteAddr}`);
@@ -18077,15 +18187,15 @@ app40.post("/", async (c) => {
18077
18187
  gateway.handlePublicSessionExit(sessionId);
18078
18188
  return c.json({ ok: true });
18079
18189
  });
18080
- var public_session_exit_default = app40;
18190
+ var public_session_exit_default = app41;
18081
18191
 
18082
18192
  // server/routes/admin/access-session-evict.ts
18083
18193
  var TAG31 = "[access-session-evict]";
18084
18194
  function isLoopbackAddr6(addr) {
18085
18195
  return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
18086
18196
  }
18087
- var app41 = new Hono();
18088
- app41.post("/", async (c) => {
18197
+ var app42 = new Hono();
18198
+ app42.post("/", async (c) => {
18089
18199
  const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
18090
18200
  if (!isLoopbackAddr6(remoteAddr)) {
18091
18201
  console.error(`${TAG31} reject reason=non-loopback remoteAddr=${remoteAddr}`);
@@ -18113,7 +18223,7 @@ app41.post("/", async (c) => {
18113
18223
  console.log(`${TAG31} grantId=${grantId} dropped=${dropped}`);
18114
18224
  return c.json({ ok: true, dropped });
18115
18225
  });
18116
- var access_session_evict_default = app41;
18226
+ var access_session_evict_default = app42;
18117
18227
 
18118
18228
  // server/routes/admin/enrol-person.ts
18119
18229
  var TAG32 = "[enrol]";
@@ -18121,8 +18231,8 @@ function isLoopbackAddr7(addr) {
18121
18231
  return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
18122
18232
  }
18123
18233
  var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
18124
- var app42 = new Hono();
18125
- app42.post("/", async (c) => {
18234
+ var app43 = new Hono();
18235
+ app43.post("/", async (c) => {
18126
18236
  const remoteAddr = c.env?.incoming?.socket?.remoteAddress ?? "";
18127
18237
  if (!isLoopbackAddr7(remoteAddr)) {
18128
18238
  console.error(`${TAG32} reject reason=non-loopback remoteAddr=${remoteAddr}`);
@@ -18185,11 +18295,11 @@ app42.post("/", async (c) => {
18185
18295
  );
18186
18296
  return c.json({ personId, grant }, 200);
18187
18297
  });
18188
- var enrol_person_default = app42;
18298
+ var enrol_person_default = app43;
18189
18299
 
18190
18300
  // server/routes/admin/browser.ts
18191
- var app43 = new Hono();
18192
- app43.post("/launch", requireAdminSession, async (c) => {
18301
+ var app44 = new Hono();
18302
+ app44.post("/launch", requireAdminSession, async (c) => {
18193
18303
  try {
18194
18304
  const transport = resolveBrowserTransport(c.req.raw, c.env?.incoming?.socket?.remoteAddress);
18195
18305
  console.error(`[admin/browser/launch] op=request transport=${transport}`);
@@ -18217,7 +18327,7 @@ app43.post("/launch", requireAdminSession, async (c) => {
18217
18327
  );
18218
18328
  }
18219
18329
  });
18220
- var browser_default = app43;
18330
+ var browser_default = app44;
18221
18331
 
18222
18332
  // server/routes/admin/calendar.ts
18223
18333
  import { existsSync as existsSync27 } from "fs";
@@ -18363,7 +18473,7 @@ function readAvailabilityConfig(accountDir) {
18363
18473
  }
18364
18474
 
18365
18475
  // server/routes/admin/calendar.ts
18366
- var app44 = new Hono();
18476
+ var app45 = new Hono();
18367
18477
  function normalizeAttendees(raw) {
18368
18478
  if (!Array.isArray(raw)) return [];
18369
18479
  return raw.map((a) => {
@@ -18394,7 +18504,7 @@ function mapMeeting(r) {
18394
18504
  privateNote: r.get("privateNote")
18395
18505
  };
18396
18506
  }
18397
- app44.get("/meetings", requireAdminSession, async (c) => {
18507
+ app45.get("/meetings", requireAdminSession, async (c) => {
18398
18508
  const cacheKey = c.var.cacheKey;
18399
18509
  const accountId = getAccountIdForSession(cacheKey);
18400
18510
  if (!accountId) {
@@ -18457,7 +18567,7 @@ function icsFilename(title) {
18457
18567
  const slug = (title ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60);
18458
18568
  return `${slug || "meeting"}.ics`;
18459
18569
  }
18460
- app44.get("/meetings/:id/ics", requireAdminSession, async (c) => {
18570
+ app45.get("/meetings/:id/ics", requireAdminSession, async (c) => {
18461
18571
  const cacheKey = c.var.cacheKey;
18462
18572
  const accountId = getAccountIdForSession(cacheKey);
18463
18573
  if (!accountId) {
@@ -18515,7 +18625,7 @@ app44.get("/meetings/:id/ics", requireAdminSession, async (c) => {
18515
18625
  await session.close();
18516
18626
  }
18517
18627
  });
18518
- app44.patch("/meetings/:id", requireAdminSession, async (c) => {
18628
+ app45.patch("/meetings/:id", requireAdminSession, async (c) => {
18519
18629
  const cacheKey = c.var.cacheKey;
18520
18630
  const accountId = getAccountIdForSession(cacheKey);
18521
18631
  if (!accountId) {
@@ -18559,7 +18669,7 @@ app44.patch("/meetings/:id", requireAdminSession, async (c) => {
18559
18669
  await session.close();
18560
18670
  }
18561
18671
  });
18562
- app44.post("/meetings/:id/attendees", requireAdminSession, async (c) => {
18672
+ app45.post("/meetings/:id/attendees", requireAdminSession, async (c) => {
18563
18673
  const cacheKey = c.var.cacheKey;
18564
18674
  const accountId = getAccountIdForSession(cacheKey);
18565
18675
  if (!accountId) {
@@ -18609,7 +18719,7 @@ app44.post("/meetings/:id/attendees", requireAdminSession, async (c) => {
18609
18719
  await session.close();
18610
18720
  }
18611
18721
  });
18612
- app44.delete("/meetings/:id/attendees", requireAdminSession, async (c) => {
18722
+ app45.delete("/meetings/:id/attendees", requireAdminSession, async (c) => {
18613
18723
  const cacheKey = c.var.cacheKey;
18614
18724
  const accountId = getAccountIdForSession(cacheKey);
18615
18725
  if (!accountId) {
@@ -18655,7 +18765,7 @@ app44.delete("/meetings/:id/attendees", requireAdminSession, async (c) => {
18655
18765
  await session.close();
18656
18766
  }
18657
18767
  });
18658
- app44.delete("/meetings/:id", requireAdminSession, async (c) => {
18768
+ app45.delete("/meetings/:id", requireAdminSession, async (c) => {
18659
18769
  const cacheKey = c.var.cacheKey;
18660
18770
  const accountId = getAccountIdForSession(cacheKey);
18661
18771
  if (!accountId) {
@@ -18685,7 +18795,7 @@ app44.delete("/meetings/:id", requireAdminSession, async (c) => {
18685
18795
  await session.close();
18686
18796
  }
18687
18797
  });
18688
- app44.get("/booking-link", requireAdminSession, (c) => {
18798
+ app45.get("/booking-link", requireAdminSession, (c) => {
18689
18799
  try {
18690
18800
  const account = resolveAccount();
18691
18801
  if (!account) {
@@ -18708,53 +18818,54 @@ app44.get("/booking-link", requireAdminSession, (c) => {
18708
18818
  return c.json({ bookingDomain: null });
18709
18819
  }
18710
18820
  });
18711
- var calendar_default = app44;
18821
+ var calendar_default = app45;
18712
18822
 
18713
18823
  // server/routes/admin/index.ts
18714
- var app45 = new Hono();
18715
- app45.route("/session", session_default);
18716
- app45.route("/accounts", accounts_default);
18717
- app45.route("/logs", logs_default);
18718
- app45.route("/claude-info", claude_info_default);
18719
- app45.route("/attachment", attachment_default);
18720
- app45.route("/agents", agents_default);
18721
- app45.route("/sessions", sessions_default);
18722
- app45.route("/claude-sessions", claude_sessions_default);
18723
- app45.route("/log-ingest", log_ingest_default);
18724
- app45.route("/events", events_default);
18725
- app45.route("/files", files_default);
18726
- app45.route("/graph-search", graph_search_default);
18727
- app45.route("/graph-subgraph", graph_subgraph_default);
18728
- app45.route("/graph-delete", graph_delete_default);
18729
- app45.route("/graph-restore", graph_restore_default);
18730
- app45.route("/graph-labels-in-graph", graph_labels_in_graph_default);
18731
- app45.route("/graph-default-view", graph_default_view_default);
18732
- app45.route("/sidebar-artefacts", sidebar_artefacts_default);
18733
- app45.route("/sidebar-sessions", sidebar_sessions_default);
18734
- app45.route("/session-delete", session_delete_default);
18735
- app45.route("/session-stop", session_stop_default);
18736
- app45.route("/session-archive", session_archive_default);
18737
- app45.route("/session-rename", session_rename_default);
18738
- app45.route("/browser", browser_default);
18739
- app45.route("/session-rc-spawn", session_rc_spawn_default);
18740
- app45.route("/session-reseat", session_reseat_default);
18741
- app45.route("/system-stats", system_stats_default);
18742
- app45.route("/health-brand", health_default2);
18743
- app45.route("/linkedin-ingest", linkedin_ingest_default);
18744
- app45.route("/post-turn-context", post_turn_context_default);
18745
- app45.route("/public-session-context", public_session_context_default);
18746
- app45.route("/public-session-exit", public_session_exit_default);
18747
- app45.route("/access-session-evict", access_session_evict_default);
18748
- app45.route("/enrol-person", enrol_person_default);
18749
- app45.route("/calendar", calendar_default);
18750
- var admin_default = app45;
18824
+ var app46 = new Hono();
18825
+ app46.route("/session", session_default);
18826
+ app46.route("/accounts", accounts_default);
18827
+ app46.route("/logs", logs_default);
18828
+ app46.route("/claude-info", claude_info_default);
18829
+ app46.route("/attachment", attachment_default);
18830
+ app46.route("/agents", agents_default);
18831
+ app46.route("/sessions", sessions_default);
18832
+ app46.route("/claude-sessions", claude_sessions_default);
18833
+ app46.route("/log-ingest", log_ingest_default);
18834
+ app46.route("/events", events_default);
18835
+ app46.route("/files", files_default);
18836
+ app46.route("/graph-search", graph_search_default);
18837
+ app46.route("/graph-subgraph", graph_subgraph_default);
18838
+ app46.route("/graph-delete", graph_delete_default);
18839
+ app46.route("/graph-restore", graph_restore_default);
18840
+ app46.route("/graph-labels-in-graph", graph_labels_in_graph_default);
18841
+ app46.route("/graph-default-view", graph_default_view_default);
18842
+ app46.route("/sidebar-artefacts", sidebar_artefacts_default);
18843
+ app46.route("/sidebar-sessions", sidebar_sessions_default);
18844
+ app46.route("/session-delete", session_delete_default);
18845
+ app46.route("/session-stop", session_stop_default);
18846
+ app46.route("/session-archive", session_archive_default);
18847
+ app46.route("/session-rename", session_rename_default);
18848
+ app46.route("/session-usage", session_usage_default);
18849
+ app46.route("/browser", browser_default);
18850
+ app46.route("/session-rc-spawn", session_rc_spawn_default);
18851
+ app46.route("/session-reseat", session_reseat_default);
18852
+ app46.route("/system-stats", system_stats_default);
18853
+ app46.route("/health-brand", health_default2);
18854
+ app46.route("/linkedin-ingest", linkedin_ingest_default);
18855
+ app46.route("/post-turn-context", post_turn_context_default);
18856
+ app46.route("/public-session-context", public_session_context_default);
18857
+ app46.route("/public-session-exit", public_session_exit_default);
18858
+ app46.route("/access-session-evict", access_session_evict_default);
18859
+ app46.route("/enrol-person", enrol_person_default);
18860
+ app46.route("/calendar", calendar_default);
18861
+ var admin_default = app46;
18751
18862
 
18752
18863
  // server/routes/access/verify-token.ts
18753
18864
  var TAG33 = "[access-verify]";
18754
18865
  var MINT_TAG = "[access-session-mint]";
18755
18866
  var COOKIE_NAME = "__access_session";
18756
- var app46 = new Hono();
18757
- app46.post("/", async (c) => {
18867
+ var app47 = new Hono();
18868
+ app47.post("/", async (c) => {
18758
18869
  const ip = c.var.clientIp || "unknown";
18759
18870
  let body;
18760
18871
  try {
@@ -18836,7 +18947,7 @@ app46.post("/", async (c) => {
18836
18947
  displayName: grant.displayName
18837
18948
  });
18838
18949
  });
18839
- var verify_token_default = app46;
18950
+ var verify_token_default = app47;
18840
18951
 
18841
18952
  // app/lib/access-email.ts
18842
18953
  import { spawn as spawn2 } from "child_process";
@@ -18899,9 +19010,9 @@ async function sendMagicLinkEmail(payload) {
18899
19010
 
18900
19011
  // server/routes/access/request-magic-link.ts
18901
19012
  var TAG34 = "[access-request-link]";
18902
- var app47 = new Hono();
19013
+ var app48 = new Hono();
18903
19014
  var VISITOR_MESSAGE = "If that email is on the invite list, a fresh link is on the way.";
18904
- app47.post("/", async (c) => {
19015
+ app48.post("/", async (c) => {
18905
19016
  let body;
18906
19017
  try {
18907
19018
  body = await c.req.json();
@@ -18975,13 +19086,13 @@ app47.post("/", async (c) => {
18975
19086
  );
18976
19087
  return c.json({ message: VISITOR_MESSAGE }, 200);
18977
19088
  });
18978
- var request_magic_link_default = app47;
19089
+ var request_magic_link_default = app48;
18979
19090
 
18980
19091
  // server/routes/access/index.ts
18981
- var app48 = new Hono();
18982
- app48.route("/verify-token", verify_token_default);
18983
- app48.route("/request-magic-link", request_magic_link_default);
18984
- var access_default = app48;
19092
+ var app49 = new Hono();
19093
+ app49.route("/verify-token", verify_token_default);
19094
+ app49.route("/request-magic-link", request_magic_link_default);
19095
+ var access_default = app49;
18985
19096
 
18986
19097
  // server/routes/sites.ts
18987
19098
  import { existsSync as existsSync28, readFileSync as readFileSync31, realpathSync as realpathSync6, statSync as statSync12 } from "fs";
@@ -19016,8 +19127,8 @@ function getExt(p) {
19016
19127
  if (idx < p.lastIndexOf("/")) return "";
19017
19128
  return p.slice(idx).toLowerCase();
19018
19129
  }
19019
- var app49 = new Hono();
19020
- app49.get("/:rel{.*}", (c) => {
19130
+ var app50 = new Hono();
19131
+ app50.get("/:rel{.*}", (c) => {
19021
19132
  const reqPath = c.req.path;
19022
19133
  const rawRel = c.req.param("rel") ?? "";
19023
19134
  const trimmed = rawRel.replace(/^\/+/, "").replace(/\/+$/, "");
@@ -19120,12 +19231,12 @@ app49.get("/:rel{.*}", (c) => {
19120
19231
  "X-Content-Type-Options": "nosniff"
19121
19232
  });
19122
19233
  });
19123
- var sites_default = app49;
19234
+ var sites_default = app50;
19124
19235
 
19125
19236
  // app/lib/visitor-token.ts
19126
19237
  import { createHmac, randomBytes, timingSafeEqual } from "crypto";
19127
19238
  import { mkdirSync as mkdirSync7, readFileSync as readFileSync32, writeFileSync as writeFileSync11 } from "fs";
19128
- import { dirname as dirname9 } from "path";
19239
+ import { dirname as dirname10 } from "path";
19129
19240
  var TOKEN_PREFIX = "v1.";
19130
19241
  var SECRET_BYTES = 32;
19131
19242
  var DEFAULT_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
@@ -19142,7 +19253,7 @@ function getSecret() {
19142
19253
  }
19143
19254
  const fresh = randomBytes(SECRET_BYTES).toString("hex");
19144
19255
  try {
19145
- mkdirSync7(dirname9(VISITOR_TOKEN_SECRET_FILE), { recursive: true, mode: 448 });
19256
+ mkdirSync7(dirname10(VISITOR_TOKEN_SECRET_FILE), { recursive: true, mode: 448 });
19146
19257
  writeFileSync11(VISITOR_TOKEN_SECRET_FILE, fresh, { mode: 384, flag: "wx" });
19147
19258
  console.log(`[visitor-token] secret minted path=${VISITOR_TOKEN_SECRET_FILE}`);
19148
19259
  } catch {
@@ -19220,7 +19331,7 @@ function readBrandConfig() {
19220
19331
  }
19221
19332
 
19222
19333
  // server/routes/visitor-consent.ts
19223
- var app50 = new Hono();
19334
+ var app51 = new Hono();
19224
19335
  var CONSENT_COOKIE_NAME = "mxy_consent";
19225
19336
  var CONSENT_COOKIE_MAX_AGE_SECONDS = 60 * 60 * 24 * 365;
19226
19337
  var DEFAULT_CONSENT_COPY = {
@@ -19265,17 +19376,17 @@ function siteSlugFromReferer(referer) {
19265
19376
  return "";
19266
19377
  }
19267
19378
  }
19268
- app50.options("/consent", (c) => {
19379
+ app51.options("/consent", (c) => {
19269
19380
  const origin = getOrigin(c);
19270
19381
  setCorsHeaders(c, origin);
19271
19382
  return c.body(null, 204);
19272
19383
  });
19273
- app50.options("/brand-config", (c) => {
19384
+ app51.options("/brand-config", (c) => {
19274
19385
  const origin = getOrigin(c);
19275
19386
  setCorsHeaders(c, origin);
19276
19387
  return c.body(null, 204);
19277
19388
  });
19278
- app50.post("/consent", async (c) => {
19389
+ app51.post("/consent", async (c) => {
19279
19390
  const origin = getOrigin(c);
19280
19391
  setCorsHeaders(c, origin);
19281
19392
  let raw;
@@ -19315,7 +19426,7 @@ app50.post("/consent", async (c) => {
19315
19426
  console.log(`[consent] ${parsed.decision} site=${site} brand=${brandName} tokenBound=${tokenBound}`);
19316
19427
  return c.body(null, 204);
19317
19428
  });
19318
- app50.get("/brand-config", (c) => {
19429
+ app51.get("/brand-config", (c) => {
19319
19430
  const origin = getOrigin(c);
19320
19431
  setCorsHeaders(c, origin);
19321
19432
  const brand = readBrandConfig();
@@ -19325,7 +19436,7 @@ app50.get("/brand-config", (c) => {
19325
19436
  c.header("Cache-Control", "public, max-age=300");
19326
19437
  return c.json({ consent: { copy, palette } });
19327
19438
  });
19328
- var visitor_consent_default = app50;
19439
+ var visitor_consent_default = app51;
19329
19440
 
19330
19441
  // server/routes/listings.ts
19331
19442
  function getCookie(headerValue, name) {
@@ -19352,8 +19463,8 @@ function appendConsentParams(pageUrl, token) {
19352
19463
  }
19353
19464
  var SAFE_SLUG_RE = /^[a-z0-9](?:[a-z0-9-]{0,118}[a-z0-9])?$/;
19354
19465
  var CHAT_SESSION_KEY_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{7,127}$/;
19355
- var app51 = new Hono();
19356
- app51.get("/:slug/click", async (c) => {
19466
+ var app52 = new Hono();
19467
+ app52.get("/:slug/click", async (c) => {
19357
19468
  const slug = c.req.param("slug") ?? "";
19358
19469
  const rawSession = c.req.query("session") ?? "";
19359
19470
  const sessionKey = CHAT_SESSION_KEY_RE.test(rawSession) ? rawSession : "invalid";
@@ -19417,10 +19528,10 @@ app51.get("/:slug/click", async (c) => {
19417
19528
  console.log(`[property-card-click] sessionKey=${sessionKey} listingSlug=${slug} consent=${consentCookie ?? "absent"} ts=${(/* @__PURE__ */ new Date()).toISOString()}`);
19418
19529
  return c.redirect(redirectUrl, 302);
19419
19530
  });
19420
- var listings_default = app51;
19531
+ var listings_default = app52;
19421
19532
 
19422
19533
  // server/routes/visitor-event.ts
19423
- var app52 = new Hono();
19534
+ var app53 = new Hono();
19424
19535
  var BOT_UA_RE = /\b(bot|crawl|spider|slurp|headlesschrome|phantomjs|googlebot|bingbot|yandex|baiduspider|ahrefsbot|semrushbot|mj12bot|dotbot|petalbot)\b/i;
19425
19536
  var buckets = /* @__PURE__ */ new Map();
19426
19537
  var RATE_LIMIT = 60;
@@ -19487,12 +19598,12 @@ function originAllowed(origin, allowlist) {
19487
19598
  return false;
19488
19599
  }
19489
19600
  }
19490
- app52.options("/event", (c) => {
19601
+ app53.options("/event", (c) => {
19491
19602
  const origin = getOrigin2(c);
19492
19603
  setCorsHeaders2(c, origin);
19493
19604
  return c.body(null, 204);
19494
19605
  });
19495
- app52.post("/event", async (c) => {
19606
+ app53.post("/event", async (c) => {
19496
19607
  const origin = getOrigin2(c);
19497
19608
  setCorsHeaders2(c, origin);
19498
19609
  const ua = c.req.header("user-agent") ?? "";
@@ -19697,7 +19808,7 @@ async function writeEvent(opts) {
19697
19808
  );
19698
19809
  }
19699
19810
  }
19700
- var visitor_event_default = app52;
19811
+ var visitor_event_default = app53;
19701
19812
 
19702
19813
  // server/routes/session.ts
19703
19814
  import { resolve as resolve27 } from "path";
@@ -19743,8 +19854,8 @@ function withVisitorCookie(response, visitorId) {
19743
19854
  headers
19744
19855
  });
19745
19856
  }
19746
- var app53 = new Hono();
19747
- app53.post("/", async (c) => {
19857
+ var app54 = new Hono();
19858
+ app54.post("/", async (c) => {
19748
19859
  let body;
19749
19860
  try {
19750
19861
  body = await c.req.json();
@@ -19891,7 +20002,7 @@ app53.post("/", async (c) => {
19891
20002
  newVisitorId
19892
20003
  );
19893
20004
  });
19894
- var session_default2 = app53;
20005
+ var session_default2 = app54;
19895
20006
 
19896
20007
  // server/lib/calendar-slots.ts
19897
20008
  var WEEKDAYS = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"];
@@ -19974,17 +20085,17 @@ function computeOpenSlots(config, busy, fromISO, toISO) {
19974
20085
  }
19975
20086
 
19976
20087
  // server/routes/calendar-public.ts
19977
- var app54 = new Hono();
20088
+ var app55 = new Hono();
19978
20089
  function setCors(c) {
19979
20090
  c.header("Access-Control-Allow-Origin", "*");
19980
20091
  c.header("Access-Control-Allow-Methods", "GET, OPTIONS");
19981
20092
  c.header("Access-Control-Allow-Headers", "content-type");
19982
20093
  }
19983
- app54.options("/free-busy", (c) => {
20094
+ app55.options("/free-busy", (c) => {
19984
20095
  setCors(c);
19985
20096
  return c.body(null, 204);
19986
20097
  });
19987
- app54.get("/free-busy", async (c) => {
20098
+ app55.get("/free-busy", async (c) => {
19988
20099
  setCors(c);
19989
20100
  const from = c.req.query("from");
19990
20101
  const to = c.req.query("to");
@@ -20026,7 +20137,7 @@ app54.get("/free-busy", async (c) => {
20026
20137
  await session.close();
20027
20138
  }
20028
20139
  });
20029
- var calendar_public_default = app54;
20140
+ var calendar_public_default = app55;
20030
20141
 
20031
20142
  // app/lib/graph-health.ts
20032
20143
  var import_dist4 = __toESM(require_dist3(), 1);
@@ -20250,7 +20361,7 @@ async function startFileWatcher(opts = {}) {
20250
20361
 
20251
20362
  // app/lib/migrate-uploads.ts
20252
20363
  import { mkdir as mkdir5, readdir as readdir5, rename as rename2, rm as rm3 } from "fs/promises";
20253
- import { dirname as dirname10, relative as relative6, resolve as resolve29 } from "path";
20364
+ import { dirname as dirname11, relative as relative6, resolve as resolve29 } from "path";
20254
20365
  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;
20255
20366
  async function walkFiles(dir) {
20256
20367
  const out = [];
@@ -20282,7 +20393,7 @@ async function relocateTree(srcDir, destDir, dataRoot, accountId, session) {
20282
20393
  for (const oldAbs of files) {
20283
20394
  const suffix = relative6(srcDir, oldAbs);
20284
20395
  const newAbs = resolve29(destDir, suffix);
20285
- await mkdir5(dirname10(newAbs), { recursive: true });
20396
+ await mkdir5(dirname11(newAbs), { recursive: true });
20286
20397
  await rename2(oldAbs, newAbs);
20287
20398
  moved++;
20288
20399
  const oldRel = relative6(dataRoot, oldAbs);
@@ -20792,8 +20903,8 @@ var streamSSE = (c, cb, onError) => {
20792
20903
 
20793
20904
  // app/lib/whatsapp/gateway/routes.ts
20794
20905
  function createWaChannelRoutes(deps) {
20795
- const app56 = new Hono();
20796
- app56.get("/wa-channel/inbound", (c) => {
20906
+ const app57 = new Hono();
20907
+ app57.get("/wa-channel/inbound", (c) => {
20797
20908
  const senderId = c.req.query("senderId");
20798
20909
  if (!senderId) return c.json({ error: "senderId required" }, 400);
20799
20910
  return streamSSE(c, async (stream2) => {
@@ -20811,7 +20922,7 @@ function createWaChannelRoutes(deps) {
20811
20922
  }
20812
20923
  });
20813
20924
  });
20814
- app56.post("/wa-channel/reply", async (c) => {
20925
+ app57.post("/wa-channel/reply", async (c) => {
20815
20926
  const body = await c.req.json().catch(() => null);
20816
20927
  const senderId = body?.senderId;
20817
20928
  const text = body?.text;
@@ -20832,7 +20943,7 @@ function createWaChannelRoutes(deps) {
20832
20943
  console.error(`[whatsapp-native] op=reply-dispatch senderId=${senderId} bytes=${bytes}`);
20833
20944
  return c.json({ ok: true });
20834
20945
  });
20835
- app56.post("/wa-channel/reply-document", async (c) => {
20946
+ app57.post("/wa-channel/reply-document", async (c) => {
20836
20947
  const body = await c.req.json().catch(() => null);
20837
20948
  const senderId = body?.senderId;
20838
20949
  const files = body?.files;
@@ -20871,7 +20982,7 @@ function createWaChannelRoutes(deps) {
20871
20982
  }
20872
20983
  return c.json({ ok: true, results });
20873
20984
  });
20874
- app56.post("/wa-channel/ready", async (c) => {
20985
+ app57.post("/wa-channel/ready", async (c) => {
20875
20986
  const body = await c.req.json().catch(() => null);
20876
20987
  const senderId = body?.senderId;
20877
20988
  if (typeof senderId !== "string") {
@@ -20880,7 +20991,7 @@ function createWaChannelRoutes(deps) {
20880
20991
  deps.onReady?.(senderId);
20881
20992
  return c.json({ ok: true });
20882
20993
  });
20883
- app56.post("/wa-channel/received", async (c) => {
20994
+ app57.post("/wa-channel/received", async (c) => {
20884
20995
  const body = await c.req.json().catch(() => null);
20885
20996
  const senderId = body?.senderId;
20886
20997
  const waMessageId = body?.waMessageId;
@@ -20890,7 +21001,7 @@ function createWaChannelRoutes(deps) {
20890
21001
  deps.onReceived?.(senderId, waMessageId);
20891
21002
  return c.json({ ok: true });
20892
21003
  });
20893
- app56.post("/wa-channel/turn-end", async (c) => {
21004
+ app57.post("/wa-channel/turn-end", async (c) => {
20894
21005
  const body = await c.req.json().catch(() => null);
20895
21006
  const senderId = body?.senderId;
20896
21007
  const sessionId = body?.sessionId;
@@ -20921,7 +21032,7 @@ function createWaChannelRoutes(deps) {
20921
21032
  console.error(`[whatsapp-native] op=turn-end sessionId=${sid} replied=no delivered=fallback bytes=${bytes}`);
20922
21033
  return c.json({ ok: true, delivered: "fallback" });
20923
21034
  });
20924
- return app56;
21035
+ return app57;
20925
21036
  }
20926
21037
 
20927
21038
  // app/lib/whatsapp/gateway/wa-gateway.ts
@@ -20934,9 +21045,11 @@ var WaGateway = class {
20934
21045
  }
20935
21046
  hub = new InboundHub();
20936
21047
  replies = /* @__PURE__ */ new Map();
20937
- // Per-sender document-delivery context (the Baileys accountId), set on every
20938
- // inbound. Its presence is the reply-only guard: a sender with no inbound has
20939
- // no context, so a file reply to a cold recipient is refused.
21048
+ // Per-sender document-delivery context, set on every inbound. Its presence is
21049
+ // the reply-only guard: a sender with no inbound has no context, so a file
21050
+ // reply to a cold recipient is refused. `accountId` is the Baileys socket
21051
+ // owner (reply transport); `maxyAccountId` (Task 1390) is the sender's
21052
+ // effective session account — the file-path validation scope.
20940
21053
  docContexts = /* @__PURE__ */ new Map();
20941
21054
  spawning = /* @__PURE__ */ new Set();
20942
21055
  seq = 0;
@@ -20991,7 +21104,7 @@ var WaGateway = class {
20991
21104
  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";
20992
21105
  const source = input.source ?? "user";
20993
21106
  this.replies.set(input.senderId, input.reply);
20994
- this.docContexts.set(input.senderId, { accountId: input.accountId });
21107
+ this.docContexts.set(input.senderId, { accountId: input.accountId, maxyAccountId: input.effectiveAccountId });
20995
21108
  this.hub.deliver(
20996
21109
  {
20997
21110
  senderId: input.senderId,
@@ -21011,6 +21124,7 @@ var WaGateway = class {
21011
21124
  try {
21012
21125
  await this.deps.ensureChannelSession({
21013
21126
  accountId: input.accountId,
21127
+ effectiveAccountId: input.effectiveAccountId,
21014
21128
  senderId: input.senderId,
21015
21129
  role: input.role ?? "admin",
21016
21130
  personId: input.personId ?? null,
@@ -21033,7 +21147,7 @@ var WaGateway = class {
21033
21147
  async sendDocument(senderId, filePath, caption) {
21034
21148
  const ctx = this.docContexts.get(senderId);
21035
21149
  if (!ctx) throw new WaReplyError(`no conversation for sender ${senderId}`, false);
21036
- return this.deps.sendDocument({ senderId, accountId: ctx.accountId, filePath, caption });
21150
+ return this.deps.sendDocument({ senderId, accountId: ctx.accountId, maxyAccountId: ctx.maxyAccountId, filePath, caption });
21037
21151
  }
21038
21152
  };
21039
21153
 
@@ -21222,8 +21336,8 @@ var InboundHub2 = class {
21222
21336
 
21223
21337
  // app/lib/webchat/gateway/routes.ts
21224
21338
  function createWebchatChannelRoutes(deps) {
21225
- const app56 = new Hono();
21226
- app56.get("/webchat-channel/inbound", (c) => {
21339
+ const app57 = new Hono();
21340
+ app57.get("/webchat-channel/inbound", (c) => {
21227
21341
  const key = c.req.query("key");
21228
21342
  if (!key) return c.json({ error: "key required" }, 400);
21229
21343
  return streamSSE(c, async (stream2) => {
@@ -21241,7 +21355,7 @@ function createWebchatChannelRoutes(deps) {
21241
21355
  }
21242
21356
  });
21243
21357
  });
21244
- app56.post("/webchat-channel/reply", async (c) => {
21358
+ app57.post("/webchat-channel/reply", async (c) => {
21245
21359
  const body = await c.req.json().catch(() => null);
21246
21360
  const key = body?.key;
21247
21361
  const text = body?.text;
@@ -21251,7 +21365,7 @@ function createWebchatChannelRoutes(deps) {
21251
21365
  deps.onReply?.(key, text);
21252
21366
  return c.json({ ok: true });
21253
21367
  });
21254
- app56.post("/webchat-channel/connector-auth", async (c) => {
21368
+ app57.post("/webchat-channel/connector-auth", async (c) => {
21255
21369
  const body = await c.req.json().catch(() => null);
21256
21370
  const key = body?.key;
21257
21371
  const sessionId = body?.sessionId;
@@ -21264,7 +21378,7 @@ function createWebchatChannelRoutes(deps) {
21264
21378
  const result = await deps.onConnectorAuth({ key, sessionId, name, completed });
21265
21379
  return c.json(result);
21266
21380
  });
21267
- app56.post("/webchat-channel/ready", async (c) => {
21381
+ app57.post("/webchat-channel/ready", async (c) => {
21268
21382
  const body = await c.req.json().catch(() => null);
21269
21383
  const key = body?.key;
21270
21384
  if (typeof key !== "string") {
@@ -21273,7 +21387,7 @@ function createWebchatChannelRoutes(deps) {
21273
21387
  deps.onReady?.(key);
21274
21388
  return c.json({ ok: true });
21275
21389
  });
21276
- app56.post("/webchat-channel/permission-request", async (c) => {
21390
+ app57.post("/webchat-channel/permission-request", async (c) => {
21277
21391
  const body = await c.req.json().catch(() => null);
21278
21392
  const key = body?.key;
21279
21393
  const requestId = body?.request_id;
@@ -21287,7 +21401,7 @@ function createWebchatChannelRoutes(deps) {
21287
21401
  const verdict = await deps.awaitPermissionVerdict({ key, requestId, toolName, description, inputPreview });
21288
21402
  return c.json(verdict);
21289
21403
  });
21290
- app56.post("/webchat-channel/received", async (c) => {
21404
+ app57.post("/webchat-channel/received", async (c) => {
21291
21405
  const body = await c.req.json().catch(() => null);
21292
21406
  const key = body?.key;
21293
21407
  const messageId = body?.messageId;
@@ -21297,7 +21411,7 @@ function createWebchatChannelRoutes(deps) {
21297
21411
  deps.onReceived?.(key, messageId);
21298
21412
  return c.json({ ok: true });
21299
21413
  });
21300
- app56.post("/webchat-channel/turn-end", async (c) => {
21414
+ app57.post("/webchat-channel/turn-end", async (c) => {
21301
21415
  const body = await c.req.json().catch(() => null);
21302
21416
  const key = body?.key;
21303
21417
  const sessionId = body?.sessionId;
@@ -21317,7 +21431,7 @@ function createWebchatChannelRoutes(deps) {
21317
21431
  console.error(`[webchat-native] op=turn-undelivered channel=webchat key=${k} sessionId=${sid} bytes=${finalBytes}`);
21318
21432
  return c.json({ ok: true, delivered: "undelivered" });
21319
21433
  });
21320
- return app56;
21434
+ return app57;
21321
21435
  }
21322
21436
 
21323
21437
  // app/lib/webchat/gateway/webchat-gateway.ts
@@ -21946,17 +22060,8 @@ var WHATSAPP_SEND_DOCUMENT = "whatsapp-send-document";
21946
22060
  function platformRoot() {
21947
22061
  return process.env.MAXY_PLATFORM_ROOT || "";
21948
22062
  }
21949
- function makeWhatsAppSendFile(entry) {
22063
+ function makeWhatsAppSendFile(entry, maxyAccountId) {
21950
22064
  return async (filePath, caption) => {
21951
- let maxyAccountId;
21952
- try {
21953
- maxyAccountId = resolvePlatformAccountId();
21954
- } catch (err) {
21955
- console.error(
21956
- `${TAG35} file-delivery reject reason=account-unresolved sender=${entry.senderId} message=${err instanceof Error ? err.message : String(err)}`
21957
- );
21958
- return { ok: false, error: "account-unresolved" };
21959
- }
21960
22065
  const result = await sendWhatsAppDocument({
21961
22066
  to: entry.senderId,
21962
22067
  filePath,
@@ -21972,12 +22077,12 @@ function makeWhatsAppSendFile(entry) {
21972
22077
  return { ok: false, error: result.error };
21973
22078
  };
21974
22079
  }
21975
- function makeWhatsAppFileDelivery(entry) {
22080
+ function makeWhatsAppFileDelivery(entry, maxyAccountId) {
21976
22081
  const shared = makeFileDelivery({
21977
22082
  entry,
21978
22083
  tag: TAG35,
21979
22084
  channel: "whatsapp",
21980
- sendFile: makeWhatsAppSendFile(entry)
22085
+ sendFile: makeWhatsAppSendFile(entry, maxyAccountId)
21981
22086
  });
21982
22087
  let turnStartedAt = null;
21983
22088
  let routeCalls = [];
@@ -22061,7 +22166,7 @@ function startNativeFileFollower(input) {
22061
22166
  return startFollower({
22062
22167
  entry,
22063
22168
  tag: "[whatsapp-adaptor]",
22064
- fileDelivery: makeWhatsAppFileDelivery(entry),
22169
+ fileDelivery: makeWhatsAppFileDelivery(entry, input.maxyAccountId),
22065
22170
  // A resumed session's JSONL already holds prior SendUserFile tool_uses;
22066
22171
  // suppress replay so historical files are not re-sent on attach.
22067
22172
  suppressResumeReplay: true,
@@ -22184,8 +22289,8 @@ var InboundHub3 = class {
22184
22289
 
22185
22290
  // app/lib/telegram/gateway/routes.ts
22186
22291
  function createTelegramChannelRoutes(deps) {
22187
- const app56 = new Hono();
22188
- app56.get("/tg-channel/inbound", (c) => {
22292
+ const app57 = new Hono();
22293
+ app57.get("/tg-channel/inbound", (c) => {
22189
22294
  const key = c.req.query("key");
22190
22295
  if (!key) return c.json({ error: "key required" }, 400);
22191
22296
  return streamSSE(c, async (stream2) => {
@@ -22203,7 +22308,7 @@ function createTelegramChannelRoutes(deps) {
22203
22308
  }
22204
22309
  });
22205
22310
  });
22206
- app56.post("/tg-channel/reply", async (c) => {
22311
+ app57.post("/tg-channel/reply", async (c) => {
22207
22312
  const body = await c.req.json().catch(() => null);
22208
22313
  const key = body?.key;
22209
22314
  const text = body?.text;
@@ -22219,7 +22324,7 @@ function createTelegramChannelRoutes(deps) {
22219
22324
  console.error(`[telegram-native] op=reply key=${key} bytes=${Buffer.byteLength(text, "utf8")}`);
22220
22325
  return c.json({ ok: true });
22221
22326
  });
22222
- app56.post("/tg-channel/ready", async (c) => {
22327
+ app57.post("/tg-channel/ready", async (c) => {
22223
22328
  const body = await c.req.json().catch(() => null);
22224
22329
  const key = body?.key;
22225
22330
  if (typeof key !== "string") {
@@ -22228,7 +22333,7 @@ function createTelegramChannelRoutes(deps) {
22228
22333
  deps.onReady?.(key);
22229
22334
  return c.json({ ok: true });
22230
22335
  });
22231
- app56.post("/tg-channel/received", async (c) => {
22336
+ app57.post("/tg-channel/received", async (c) => {
22232
22337
  const body = await c.req.json().catch(() => null);
22233
22338
  const key = body?.key;
22234
22339
  const messageId = body?.messageId;
@@ -22238,7 +22343,7 @@ function createTelegramChannelRoutes(deps) {
22238
22343
  deps.onReceived?.(key, messageId);
22239
22344
  return c.json({ ok: true });
22240
22345
  });
22241
- app56.post("/tg-channel/turn-end", async (c) => {
22346
+ app57.post("/tg-channel/turn-end", async (c) => {
22242
22347
  const body = await c.req.json().catch(() => null);
22243
22348
  const key = body?.key;
22244
22349
  const sessionId = body?.sessionId;
@@ -22263,7 +22368,7 @@ function createTelegramChannelRoutes(deps) {
22263
22368
  console.error(`[telegram-native] op=turn-fallback key=${key} sessionId=${sid} bytes=${Buffer.byteLength(finalText, "utf8")}`);
22264
22369
  return c.json({ ok: true, delivered: "fallback" });
22265
22370
  });
22266
- return app56;
22371
+ return app57;
22267
22372
  }
22268
22373
 
22269
22374
  // app/lib/telegram/gateway/telegram-gateway.ts
@@ -22375,7 +22480,7 @@ function buildTelegramSpawnRequest(input) {
22375
22480
  // app/lib/telegram/outbound/send-document.ts
22376
22481
  import { realpathSync as realpathSync7 } from "fs";
22377
22482
  import { readFile as readFile7, stat as stat7 } from "fs/promises";
22378
- import { resolve as resolve30, basename as basename11 } from "path";
22483
+ import { resolve as resolve30, basename as basename12 } from "path";
22379
22484
  var TAG36 = "[telegram:outbound]";
22380
22485
  var TELEGRAM_DOCUMENT_MAX_BYTES = 50 * 1024 * 1024;
22381
22486
  async function sendTelegramDocument(input) {
@@ -22392,7 +22497,7 @@ async function sendTelegramDocument(input) {
22392
22497
  resolvedPath = realpathSync7(filePath);
22393
22498
  const accountResolved = realpathSync7(accountDir);
22394
22499
  if (!resolvedPath.startsWith(accountResolved + "/")) {
22395
- console.error(`${TAG36} document REJECTED reason=outside_account_directory`);
22500
+ console.error(`${TAG36} document REJECTED reason=outside_account_directory maxyAccountId=${maxyAccountId}`);
22396
22501
  return { ok: false, status: 403, error: "Access denied: file is outside the account directory" };
22397
22502
  }
22398
22503
  } catch (err) {
@@ -22412,7 +22517,7 @@ async function sendTelegramDocument(input) {
22412
22517
  error: `File exceeds 50 MB limit (${(fileStat.size / 1024 / 1024).toFixed(1)} MB)`
22413
22518
  };
22414
22519
  }
22415
- const filename = basename11(resolvedPath);
22520
+ const filename = basename12(resolvedPath);
22416
22521
  const buffer = Buffer.from(await readFile7(resolvedPath));
22417
22522
  const form = new FormData();
22418
22523
  form.append("chat_id", String(chatId));
@@ -22459,21 +22564,12 @@ function makeTelegramSendFile(entry) {
22459
22564
  console.error(`${TAG37} file-delivery reject reason=no-reply-target sender=${entry.senderId} role=${entry.role}`);
22460
22565
  return { ok: false, error: "no-reply-target" };
22461
22566
  }
22462
- let maxyAccountId;
22463
- try {
22464
- maxyAccountId = resolvePlatformAccountId();
22465
- } catch (err) {
22466
- console.error(
22467
- `${TAG37} file-delivery reject reason=account-unresolved sender=${entry.senderId} message=${err instanceof Error ? err.message : String(err)}`
22468
- );
22469
- return { ok: false, error: "account-unresolved" };
22470
- }
22471
22567
  const result = await sendTelegramDocument({
22472
22568
  botToken,
22473
22569
  chatId: Number(entry.replyTarget),
22474
22570
  filePath,
22475
22571
  caption,
22476
- maxyAccountId,
22572
+ maxyAccountId: entry.accountId,
22477
22573
  platformRoot: platformRoot2()
22478
22574
  });
22479
22575
  return result.ok ? { ok: true } : { ok: false, error: result.error };
@@ -22705,6 +22801,10 @@ function findChannelAdminBindingDrift(adminPhones, admins, users) {
22705
22801
  }
22706
22802
  return drift;
22707
22803
  }
22804
+ function classifyAdminPhonesForAccount(isSocketOwner, adminPhones, admins, users) {
22805
+ if (!isSocketOwner) return { nonSocketPhones: [...adminPhones], drift: [] };
22806
+ return { nonSocketPhones: [], drift: findChannelAdminBindingDrift(adminPhones, admins, users) };
22807
+ }
22708
22808
  function warnOnChannelAdminBindingDrift() {
22709
22809
  let users;
22710
22810
  try {
@@ -22718,10 +22818,27 @@ function warnOnChannelAdminBindingDrift() {
22718
22818
  } catch {
22719
22819
  return;
22720
22820
  }
22821
+ let socketOwner;
22822
+ try {
22823
+ socketOwner = resolveHouseOrSoleAccountId(ACCOUNTS_DIR);
22824
+ } catch {
22825
+ socketOwner = null;
22826
+ }
22721
22827
  const validAccountIds = accounts.map((a) => a.accountId);
22722
22828
  for (const acct of accounts) {
22723
22829
  const adminPhones = readAdminPhones(acct.accountDir);
22724
- const drift = findChannelAdminBindingDrift(adminPhones, acct.config.admins ?? [], users);
22830
+ const isSocketOwner = socketOwner === null || acct.accountId === socketOwner;
22831
+ const { nonSocketPhones, drift } = classifyAdminPhonesForAccount(
22832
+ isSocketOwner,
22833
+ adminPhones,
22834
+ acct.config.admins ?? [],
22835
+ users
22836
+ );
22837
+ for (const phone of nonSocketPhones) {
22838
+ console.error(
22839
+ `[admin-identity] adminphones-on-non-socket-account accountId=${acct.accountId} phone=${phone} count=${nonSocketPhones.length}`
22840
+ );
22841
+ }
22725
22842
  for (const d of drift) {
22726
22843
  const tail = d.reason === "userid-not-admin" ? ` userId=${d.userId}` : "";
22727
22844
  console.error(
@@ -23073,20 +23190,19 @@ watchFile(ALIAS_DOMAINS_PATH, { interval: 2e3 }, () => {
23073
23190
  function isPublicHost(host) {
23074
23191
  return host.startsWith("public.") || aliasDomains.has(host);
23075
23192
  }
23076
- var app55 = new Hono();
23193
+ var app56 = new Hono();
23077
23194
  var nativeFileFollowers = /* @__PURE__ */ new Map();
23078
23195
  var waGateway = new WaGateway({
23079
23196
  gatewayUrl: `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`,
23080
23197
  serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve33(process.env.MAXY_PLATFORM_ROOT ?? join34(__dirname, ".."), "services/whatsapp-channel/dist/server.js"),
23081
- // Task 751 — file delivery on the native channel: resolve the platform
23082
- // account + path validation here and funnel through the shared send core.
23083
- sendDocument: async ({ senderId, accountId, filePath, caption }) => {
23084
- let maxyAccountId;
23085
- try {
23086
- maxyAccountId = resolvePlatformAccountId();
23087
- } catch (err) {
23088
- return { ok: false, error: `account-unresolved: ${err instanceof Error ? err.message : String(err)}` };
23089
- }
23198
+ // Task 751 / 1390 — file delivery on the native channel. `maxyAccountId` (the
23199
+ // path-validation scope) is the sender's effective SESSION account, resolved
23200
+ // once at the inbound gate and threaded here via the gateway's per-sender doc
23201
+ // context. For an account-manager that is the bound sub-account (where the
23202
+ // session's files live); using the house account (resolvePlatformAccountId)
23203
+ // rejected an in-account file as outside_account_directory. `accountId` stays
23204
+ // the house Baileys socket (the reply transport, two-account invariant).
23205
+ sendDocument: async ({ senderId, accountId, maxyAccountId, filePath, caption }) => {
23090
23206
  const result = await sendWhatsAppDocument({
23091
23207
  to: senderId,
23092
23208
  filePath,
@@ -23097,12 +23213,10 @@ var waGateway = new WaGateway({
23097
23213
  });
23098
23214
  return result.ok ? { ok: true, messageId: result.messageId } : { ok: false, error: result.error };
23099
23215
  },
23100
- ensureChannelSession: async ({ accountId, senderId, role, personId, gatewayUrl, serverPath }) => {
23101
- const houseDir = role === "admin" ? listValidAccounts().find((a) => a.accountId === accountId)?.accountDir ?? null : null;
23102
- const managed = houseDir ? managedAccountFor(readAccountManagers(houseDir), senderId) : null;
23103
- const effectiveAccountId = managed ?? accountId;
23216
+ ensureChannelSession: async ({ accountId, effectiveAccountId, senderId, role, personId, gatewayUrl, serverPath }) => {
23104
23217
  if (role === "admin") {
23105
- console.error(`[whatsapp-native] op=account-manager-route senderId=${senderId} managesAccount=${managed ?? "none"} effectiveAccount=${effectiveAccountId}`);
23218
+ const managesAccount = effectiveAccountId !== accountId ? effectiveAccountId : "none";
23219
+ console.error(`[whatsapp-native] op=account-manager-route senderId=${senderId} managesAccount=${managesAccount} effectiveAccount=${effectiveAccountId} source=gate`);
23106
23220
  }
23107
23221
  const req = buildWaSpawnRequest({ accountId: effectiveAccountId, senderId, role, personId, gatewayUrl, serverPath });
23108
23222
  const userId = role === "admin" ? resolveAdminUserId({ accountId: effectiveAccountId, senderPhone: senderId }) ?? void 0 : void 0;
@@ -23122,6 +23236,10 @@ var waGateway = new WaGateway({
23122
23236
  sessionId: req.sessionId,
23123
23237
  senderId,
23124
23238
  accountId,
23239
+ // Task 1390 — the file-path validation scope is the sender's effective
23240
+ // session account (the sub-account for an account-manager), not the house
23241
+ // socket account. accountId stays the getSocket key.
23242
+ maxyAccountId: effectiveAccountId,
23125
23243
  onClose: () => {
23126
23244
  if (nativeFileFollowers.get(senderId) === ac) nativeFileFollowers.delete(senderId);
23127
23245
  }
@@ -23129,7 +23247,7 @@ var waGateway = new WaGateway({
23129
23247
  nativeFileFollowers.set(senderId, ac);
23130
23248
  }
23131
23249
  });
23132
- app55.route("/", waGateway.routes());
23250
+ app56.route("/", waGateway.routes());
23133
23251
  waGateway.startSweeper();
23134
23252
  var webchatGateway = new WebchatGateway({
23135
23253
  gatewayUrl: webchatGatewayUrl(),
@@ -23212,7 +23330,7 @@ var webchatGateway = new WebchatGateway({
23212
23330
  firePublicSessionEndReview: (input) => firePublicSessionEndReview(input)
23213
23331
  });
23214
23332
  setWebchatGateway(webchatGateway);
23215
- app55.route("/", webchatGateway.routes());
23333
+ app56.route("/", webchatGateway.routes());
23216
23334
  webchatGateway.startSweeper();
23217
23335
  webchatGateway.startPublicReaper();
23218
23336
  var telegramFileFollowers = /* @__PURE__ */ new Map();
@@ -23245,7 +23363,7 @@ var telegramGateway = new TelegramGateway({
23245
23363
  }
23246
23364
  });
23247
23365
  setTelegramGateway(telegramGateway);
23248
- app55.route("/", telegramGateway.routes());
23366
+ app56.route("/", telegramGateway.routes());
23249
23367
  telegramGateway.startSweeper();
23250
23368
  var chatRoutes = createChatRoutes({
23251
23369
  handleInbound: (input) => webchatGateway.handleInbound(input),
@@ -23264,21 +23382,26 @@ var scheduleInjectRoutes = createScheduleInjectRoutes({
23264
23382
  const sent = await sendTelegramText(botToken, chatId, text);
23265
23383
  if (!sent.ok) throw new Error(sent.error ?? "telegram send failed");
23266
23384
  },
23267
- effectiveAccountFor: (accountId, accountDir, destination) => managedAccountFor(readAccountManagers(accountDir), destination) ?? accountId
23385
+ effectiveAccountFor: (accountId, accountDir, destination) => {
23386
+ const managedSub = managedAccountFor(readAccountManagers(accountDir), destination);
23387
+ if (managedSub === null) return accountId;
23388
+ if (!listValidAccounts().some((a) => a.accountId === managedSub)) return null;
23389
+ return managedSub;
23390
+ }
23268
23391
  });
23269
- app55.route("/api/channel/schedule-inject", scheduleInjectRoutes);
23270
- app55.use("*", clientIpMiddleware);
23392
+ app56.route("/api/channel/schedule-inject", scheduleInjectRoutes);
23393
+ app56.use("*", clientIpMiddleware);
23271
23394
  function allowsSameOriginFraming(path2) {
23272
23395
  return path2 === "/vnc-viewer.html" || path2.startsWith("/api/admin/attachment/") || path2.startsWith("/api/public-reader/attachment/") || path2 === "/api/admin/files/download";
23273
23396
  }
23274
- app55.use("*", async (c, next) => {
23397
+ app56.use("*", async (c, next) => {
23275
23398
  await next();
23276
23399
  c.header("X-Content-Type-Options", "nosniff");
23277
23400
  c.header("Referrer-Policy", "strict-origin-when-cross-origin");
23278
23401
  c.header("X-Frame-Options", allowsSameOriginFraming(c.req.path) ? "SAMEORIGIN" : "DENY");
23279
23402
  });
23280
23403
  var HTTP_LOG_PATHS = /* @__PURE__ */ new Set(["/vnc-viewer.html", "/vnc-popout.html"]);
23281
- app55.use("*", async (c, next) => {
23404
+ app56.use("*", async (c, next) => {
23282
23405
  if (!HTTP_LOG_PATHS.has(c.req.path)) {
23283
23406
  await next();
23284
23407
  return;
@@ -23296,7 +23419,7 @@ app55.use("*", async (c, next) => {
23296
23419
  });
23297
23420
  }
23298
23421
  });
23299
- app55.use("*", async (c, next) => {
23422
+ app56.use("*", async (c, next) => {
23300
23423
  const host = (c.req.header("host") ?? "").split(":")[0];
23301
23424
  if (isOperatorHost(host, getOperatorDomains()) || !isPublicHost(host)) {
23302
23425
  await next();
@@ -23334,7 +23457,7 @@ function resolveRemoteAuthOpts(c) {
23334
23457
  return { ...brandLoginOpts, origin };
23335
23458
  }
23336
23459
  var MAX_LOGIN_BODY = 8 * 1024;
23337
- app55.post("/__remote-auth/login", async (c) => {
23460
+ app56.post("/__remote-auth/login", async (c) => {
23338
23461
  const client = clientFrom(c);
23339
23462
  const clientIp = client.ip || "unknown";
23340
23463
  if (!requestIsTlsTerminated(c)) {
@@ -23379,7 +23502,7 @@ app55.post("/__remote-auth/login", async (c) => {
23379
23502
  }
23380
23503
  });
23381
23504
  });
23382
- app55.get("/__remote-auth/logout", (c) => {
23505
+ app56.get("/__remote-auth/logout", (c) => {
23383
23506
  const client = clientFrom(c);
23384
23507
  const clientIp = client.ip || "unknown";
23385
23508
  console.error(`[remote-auth] logout ip=${clientIp}`);
@@ -23392,7 +23515,7 @@ app55.get("/__remote-auth/logout", (c) => {
23392
23515
  }
23393
23516
  });
23394
23517
  });
23395
- app55.post("/__remote-auth/change-password", async (c) => {
23518
+ app56.post("/__remote-auth/change-password", async (c) => {
23396
23519
  const client = clientFrom(c);
23397
23520
  const clientIp = client.ip || "unknown";
23398
23521
  const rateLimited = checkRateLimit(client);
@@ -23451,13 +23574,13 @@ app55.post("/__remote-auth/change-password", async (c) => {
23451
23574
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(c), mode: "change", changeError: "Failed to save password", redirect }), 200);
23452
23575
  }
23453
23576
  });
23454
- app55.get("/__remote-auth/setup", (c) => {
23577
+ app56.get("/__remote-auth/setup", (c) => {
23455
23578
  if (isRemoteAuthConfigured()) {
23456
23579
  return c.redirect("/");
23457
23580
  }
23458
23581
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(c), mode: "setup" }), 200);
23459
23582
  });
23460
- app55.post("/__remote-auth/set-initial-password", async (c) => {
23583
+ app56.post("/__remote-auth/set-initial-password", async (c) => {
23461
23584
  if (isRemoteAuthConfigured()) {
23462
23585
  return c.redirect("/");
23463
23586
  }
@@ -23495,10 +23618,10 @@ app55.post("/__remote-auth/set-initial-password", async (c) => {
23495
23618
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(c), mode: "setup", setupError: "Failed to save password. Please try again." }), 200);
23496
23619
  }
23497
23620
  });
23498
- app55.get("/api/remote-auth/status", (c) => {
23621
+ app56.get("/api/remote-auth/status", (c) => {
23499
23622
  return c.json({ configured: isRemoteAuthConfigured() });
23500
23623
  });
23501
- app55.post("/api/remote-auth/set-password", async (c) => {
23624
+ app56.post("/api/remote-auth/set-password", async (c) => {
23502
23625
  let body;
23503
23626
  try {
23504
23627
  body = await c.req.json();
@@ -23537,10 +23660,10 @@ app55.post("/api/remote-auth/set-password", async (c) => {
23537
23660
  return c.json({ error: "Failed to save password" }, 500);
23538
23661
  }
23539
23662
  });
23540
- app55.route("/api/_client-error", client_error_default);
23663
+ app56.route("/api/_client-error", client_error_default);
23541
23664
  console.log("[client-error-route] mounted");
23542
23665
  var PWA_PUBLIC_PATHS = /* @__PURE__ */ new Set(["/sw.js", ...PWA_SURFACES.map((s) => s.manifestPath)]);
23543
- app55.use("*", async (c, next) => {
23666
+ app56.use("*", async (c, next) => {
23544
23667
  const host = (c.req.header("host") ?? "").split(":")[0];
23545
23668
  const path2 = c.req.path;
23546
23669
  if (path2 === "/favicon.ico" || path2.startsWith("/assets/") || path2.startsWith("/brand/") || // Public free/busy is read by the booking page (a separate Cloudflare Pages
@@ -23584,26 +23707,26 @@ app55.use("*", async (c, next) => {
23584
23707
  }
23585
23708
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(c), redirect: path2 }), 200);
23586
23709
  });
23587
- app55.route("/api/health", health_default);
23588
- app55.route("/api/chat", chatRoutes);
23589
- app55.route("/api/whatsapp", whatsapp_default);
23590
- app55.route("/api/whatsapp-reader", whatsapp_reader_default);
23591
- app55.route("/api/public-reader", public_reader_default);
23592
- app55.route("/api/webchat", createWebchatRoutes({
23710
+ app56.route("/api/health", health_default);
23711
+ app56.route("/api/chat", chatRoutes);
23712
+ app56.route("/api/whatsapp", whatsapp_default);
23713
+ app56.route("/api/whatsapp-reader", whatsapp_reader_default);
23714
+ app56.route("/api/public-reader", public_reader_default);
23715
+ app56.route("/api/webchat", createWebchatRoutes({
23593
23716
  handleInbound: (input) => webchatGateway.handleInbound(input),
23594
23717
  // Task 940 — the permission relay's pointer read + verdict write.
23595
23718
  pendingPromptFor: (key) => webchatGateway.pendingPromptFor(key),
23596
23719
  deliveryFailureFor: (key) => webchatGateway.deliveryFailureFor(key),
23597
23720
  resolvePermissionVerdict: (key, requestId, behavior) => webchatGateway.resolvePermissionVerdict(key, requestId, behavior)
23598
23721
  }));
23599
- app55.route("/api/webchat/greeting", webchat_greeting_default);
23600
- app55.route("/api/telegram", telegram_default);
23601
- app55.route("/api/quickbooks", quickbooks_default);
23602
- app55.route("/api/onboarding", onboarding_default);
23603
- app55.route("/api/admin", admin_default);
23604
- app55.route("/api/access", access_default);
23605
- app55.route("/api/session", session_default2);
23606
- app55.route("/api/calendar", calendar_public_default);
23722
+ app56.route("/api/webchat/greeting", webchat_greeting_default);
23723
+ app56.route("/api/telegram", telegram_default);
23724
+ app56.route("/api/quickbooks", quickbooks_default);
23725
+ app56.route("/api/onboarding", onboarding_default);
23726
+ app56.route("/api/admin", admin_default);
23727
+ app56.route("/api/access", access_default);
23728
+ app56.route("/api/session", session_default2);
23729
+ app56.route("/api/calendar", calendar_public_default);
23607
23730
  var SAFE_SLUG_RE2 = /^[a-z][a-z0-9-]{2,49}$/;
23608
23731
  var SAFE_FILENAME_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;
23609
23732
  var IMAGE_MIME = {
@@ -23615,7 +23738,7 @@ var IMAGE_MIME = {
23615
23738
  ".svg": "image/svg+xml",
23616
23739
  ".ico": "image/x-icon"
23617
23740
  };
23618
- app55.get("/agent-assets/:slug/:filename", (c) => {
23741
+ app56.get("/agent-assets/:slug/:filename", (c) => {
23619
23742
  const slug = c.req.param("slug");
23620
23743
  const filename = c.req.param("filename");
23621
23744
  if (!SAFE_SLUG_RE2.test(slug)) {
@@ -23650,7 +23773,7 @@ app55.get("/agent-assets/:slug/:filename", (c) => {
23650
23773
  "Cache-Control": "public, max-age=3600"
23651
23774
  });
23652
23775
  });
23653
- app55.get("/generated/:filename", (c) => {
23776
+ app56.get("/generated/:filename", (c) => {
23654
23777
  const filename = c.req.param("filename");
23655
23778
  if (!SAFE_FILENAME_RE.test(filename) || filename.includes("..")) {
23656
23779
  console.error(`[generated] serve file=${filename} status=403`);
@@ -23680,10 +23803,10 @@ app55.get("/generated/:filename", (c) => {
23680
23803
  "Cache-Control": "public, max-age=86400"
23681
23804
  });
23682
23805
  });
23683
- app55.route("/sites", sites_default);
23684
- app55.route("/listings", listings_default);
23685
- app55.route("/v", visitor_event_default);
23686
- app55.route("/v", visitor_consent_default);
23806
+ app56.route("/sites", sites_default);
23807
+ app56.route("/listings", listings_default);
23808
+ app56.route("/v", visitor_event_default);
23809
+ app56.route("/v", visitor_consent_default);
23687
23810
  var htmlCache = /* @__PURE__ */ new Map();
23688
23811
  var brandLogoPath = "/brand/maxy-monochrome.png";
23689
23812
  var brandIconPath = "/brand/maxy-monochrome.png";
@@ -23838,7 +23961,7 @@ function brandedPublicHtml(agentSlug) {
23838
23961
  function agentUnavailableHtml() {
23839
23962
  return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>${escapeHtml(BRAND.productName)}</title></head><body style="font-family:system-ui,sans-serif;max-width:32rem;margin:4rem auto;padding:0 1.5rem;color:#222"><h1 style="font-size:1.25rem">Agent unavailable</h1><p>This agent isn't available right now. If you reached this page from a saved link, the agent may have been turned off.</p></body></html>`;
23840
23963
  }
23841
- app55.get("/", (c) => {
23964
+ app56.get("/", (c) => {
23842
23965
  const host = (c.req.header("host") ?? "").split(":")[0];
23843
23966
  const klass = classifyHost(host, getOperatorDomains(), isPublicHost);
23844
23967
  if (klass === "operator") {
@@ -23860,12 +23983,12 @@ app55.get("/", (c) => {
23860
23983
  console.log(`[host-class] host=${host} class=admin served=index.html`);
23861
23984
  return c.html(cachedHtml("index.html"));
23862
23985
  });
23863
- app55.get("/public", (c) => {
23986
+ app56.get("/public", (c) => {
23864
23987
  const host = (c.req.header("host") ?? "").split(":")[0];
23865
23988
  if (isPublicHost(host)) return c.text("Not found", 404);
23866
23989
  return c.html(cachedHtml("public.html"));
23867
23990
  });
23868
- app55.get("/public-chat", (c) => {
23991
+ app56.get("/public-chat", (c) => {
23869
23992
  const host = (c.req.header("host") ?? "").split(":")[0];
23870
23993
  if (isPublicHost(host)) return c.text("Not found", 404);
23871
23994
  return c.html(cachedHtml("public.html"));
@@ -23884,9 +24007,9 @@ async function logViewerFetch(c, next) {
23884
24007
  duration_ms: Date.now() - start
23885
24008
  });
23886
24009
  }
23887
- app55.use("/vnc-viewer.html", logViewerFetch);
23888
- app55.use("/vnc-popout.html", logViewerFetch);
23889
- app55.get("/vnc-popout.html", (c) => {
24010
+ app56.use("/vnc-viewer.html", logViewerFetch);
24011
+ app56.use("/vnc-popout.html", logViewerFetch);
24012
+ app56.get("/vnc-popout.html", (c) => {
23890
24013
  let html = htmlCache.get("vnc-popout.html");
23891
24014
  if (!html) {
23892
24015
  html = readFileSync35(resolve33(process.cwd(), "public", "vnc-popout.html"), "utf-8");
@@ -23899,7 +24022,7 @@ app55.get("/vnc-popout.html", (c) => {
23899
24022
  }
23900
24023
  return c.html(html);
23901
24024
  });
23902
- app55.post("/api/vnc/client-event", async (c) => {
24025
+ app56.post("/api/vnc/client-event", async (c) => {
23903
24026
  let body;
23904
24027
  try {
23905
24028
  body = await c.req.json();
@@ -23920,11 +24043,11 @@ app55.post("/api/vnc/client-event", async (c) => {
23920
24043
  });
23921
24044
  return c.json({ ok: true });
23922
24045
  });
23923
- app55.get("/g/:slug", (c) => {
24046
+ app56.get("/g/:slug", (c) => {
23924
24047
  return c.html(brandedPublicHtml(resolveDefaultSlug() ?? void 0));
23925
24048
  });
23926
24049
  for (const pwa of PWA_SURFACES) {
23927
- app55.get(pwa.manifestPath, (c) => {
24050
+ app56.get(pwa.manifestPath, (c) => {
23928
24051
  const manifest = buildManifest(pwa, {
23929
24052
  productName: BRAND.productName,
23930
24053
  appIcon192: brandAppIcon192Path,
@@ -23940,7 +24063,7 @@ for (const pwa of PWA_SURFACES) {
23940
24063
  return c.body(JSON.stringify(manifest));
23941
24064
  });
23942
24065
  }
23943
- app55.get("/sw.js", (c) => {
24066
+ app56.get("/sw.js", (c) => {
23944
24067
  if (SW_SOURCE == null) {
23945
24068
  console.error("[pwa] op=sw status=500 reason=sw-source-missing");
23946
24069
  return c.text("Service worker unavailable", 500);
@@ -23949,12 +24072,12 @@ app55.get("/sw.js", (c) => {
23949
24072
  console.log(`[pwa] op=sw status=200 ct=${SW_CONTENT_TYPE}`);
23950
24073
  return c.body(SW_SOURCE);
23951
24074
  });
23952
- app55.get("/graph", (c) => {
24075
+ app56.get("/graph", (c) => {
23953
24076
  const host = (c.req.header("host") ?? "").split(":")[0];
23954
24077
  if (isPublicHost(host) || isOperatorHost(host, getOperatorDomains())) return c.text("Not found", 404);
23955
24078
  return c.html(cachedHtml("graph.html"));
23956
24079
  });
23957
- app55.get("/chat", (c) => {
24080
+ app56.get("/chat", (c) => {
23958
24081
  const host = (c.req.header("host") ?? "").split(":")[0];
23959
24082
  if (isOperatorHost(host, getOperatorDomains())) {
23960
24083
  console.log(`[host-class] host=${host} class=operator served=operator.html`);
@@ -23963,22 +24086,22 @@ app55.get("/chat", (c) => {
23963
24086
  if (isPublicHost(host)) return c.text("Not found", 404);
23964
24087
  return c.html(cachedHtml("chat.html"));
23965
24088
  });
23966
- app55.get("/data", (c) => {
24089
+ app56.get("/data", (c) => {
23967
24090
  const host = (c.req.header("host") ?? "").split(":")[0];
23968
24091
  if (isPublicHost(host)) return c.text("Not found", 404);
23969
24092
  return c.html(cachedHtml("data.html"));
23970
24093
  });
23971
- app55.get("/calendar", (c) => {
24094
+ app56.get("/calendar", (c) => {
23972
24095
  const host = (c.req.header("host") ?? "").split(":")[0];
23973
24096
  if (isPublicHost(host)) return c.text("Not found", 404);
23974
24097
  return c.html(cachedHtml("calendar.html"));
23975
24098
  });
23976
- app55.get("/browser", (c) => {
24099
+ app56.get("/browser", (c) => {
23977
24100
  const host = (c.req.header("host") ?? "").split(":")[0];
23978
24101
  if (isPublicHost(host) || isOperatorHost(host, getOperatorDomains())) return c.text("Not found", 404);
23979
24102
  return c.html(cachedHtml("browser.html"));
23980
24103
  });
23981
- app55.get("/:slug", async (c, next) => {
24104
+ app56.get("/:slug", async (c, next) => {
23982
24105
  const slug = c.req.param("slug");
23983
24106
  if (AGENT_SLUG_PATTERN.test(`/${slug}`)) {
23984
24107
  const account = resolveAccount();
@@ -23993,13 +24116,13 @@ app55.get("/:slug", async (c, next) => {
23993
24116
  await next();
23994
24117
  });
23995
24118
  if (brandFaviconPath !== "/favicon.ico") {
23996
- app55.get("/favicon.ico", (c) => {
24119
+ app56.get("/favicon.ico", (c) => {
23997
24120
  c.header("Cache-Control", "public, max-age=300");
23998
24121
  return c.redirect(brandFaviconPath, 302);
23999
24122
  });
24000
24123
  }
24001
- app55.use("/*", serveStatic({ root: "./public" }));
24002
- app55.all("*", (c) => {
24124
+ app56.use("/*", serveStatic({ root: "./public" }));
24125
+ app56.all("*", (c) => {
24003
24126
  const host = (c.req.header("host") ?? "").split(":")[0];
24004
24127
  const path2 = c.req.path;
24005
24128
  if (isPublicHost(host)) {
@@ -24013,7 +24136,7 @@ app55.all("*", (c) => {
24013
24136
  });
24014
24137
  var port = requirePortEnv("MAXY_UI_INTERNAL_PORT", { tag: "ui-server" });
24015
24138
  var hostname = process.env.HOSTNAME ?? "127.0.0.1";
24016
- var httpServer = serve({ fetch: app55.fetch, port, hostname });
24139
+ var httpServer = serve({ fetch: app56.fetch, port, hostname });
24017
24140
  console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
24018
24141
  {
24019
24142
  const reconcilePlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join34(__dirname, "..");
@@ -24113,7 +24236,7 @@ for (const m of SUBAPP_MANIFEST) {
24113
24236
  }
24114
24237
  try {
24115
24238
  const registered = [];
24116
- for (const r of app55.routes ?? []) {
24239
+ for (const r of app56.routes ?? []) {
24117
24240
  if (typeof r.path !== "string" || r.path.includes(":") || r.path.includes("*")) continue;
24118
24241
  if (AGENT_SLUG_PATTERN.test(r.path)) {
24119
24242
  registered.push({ method: (r.method ?? "ALL").toUpperCase(), path: r.path });
@@ -24243,7 +24366,7 @@ try {
24243
24366
  } catch (err) {
24244
24367
  console.error(`[graph-health] account-enumeration unavailable reason=${err instanceof Error ? err.message : String(err)}`);
24245
24368
  }
24246
- var configDirForWhatsApp = basename12(MAXY_DIR) || ".maxy";
24369
+ var configDirForWhatsApp = basename13(MAXY_DIR) || ".maxy";
24247
24370
  var bootAccount = resolveAccount();
24248
24371
  if (bootAccount) {
24249
24372
  migrateRemovedConfigKeys(bootAccount.accountDir);
@@ -24263,6 +24386,7 @@ reconcileEnabledPlugins(bootAccount?.accountDir, bootAccount?.config, bootAccoun
24263
24386
  for (const acct of listValidAccounts()) {
24264
24387
  cleanupLeakedPremiumSubs(acct.accountDir, acct.config, acct.accountId);
24265
24388
  }
24389
+ purgeNonSocketAdminPhonesAtBoot();
24266
24390
  warnOnChannelAdminBindingDrift();
24267
24391
  var bootEnabled = Array.isArray(bootAccountConfig?.enabledPlugins) ? bootAccountConfig.enabledPlugins : [];
24268
24392
  var bootDelivered = [];
@@ -24322,6 +24446,10 @@ init({
24322
24446
  console.error(`[whatsapp:route] dropped reason=no-text-no-media senderId=${msg.senderPhone} mediaCount=${msg.media.length}`);
24323
24447
  return;
24324
24448
  }
24449
+ if (!msg.effectiveAccountId) {
24450
+ console.error(`[whatsapp:route] op=dropped reason=no-effective-account senderId=${msg.senderPhone}`);
24451
+ return;
24452
+ }
24325
24453
  if (decision.role === "public") {
24326
24454
  console.error(`[whatsapp:route] op=routed agentType=public personId=${decision.personId} senderId=${msg.senderPhone}`);
24327
24455
  }
@@ -24330,6 +24458,7 @@ init({
24330
24458
  });
24331
24459
  await waGateway.handleInbound({
24332
24460
  accountId: msg.accountId,
24461
+ effectiveAccountId: msg.effectiveAccountId,
24333
24462
  senderId: msg.senderPhone,
24334
24463
  role: decision.role,
24335
24464
  personId: decision.personId,