@rubytech/create-maxy-code 0.1.387 → 0.1.388

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-maxy-code",
3
- "version": "0.1.387",
3
+ "version": "0.1.388",
4
4
  "description": "Install Maxy — AI for Productive People",
5
5
  "bin": {
6
6
  "create-maxy-code": "./dist/index.js"
@@ -101,13 +101,15 @@ The platform enforces this at multiple levels:
101
101
  | Term | Definition | Code |
102
102
  |------|-----------|------|
103
103
  | **Self phone** | The WhatsApp account's own phone number (the phone that scanned the QR code). Self-chat only. | `phonesMatch(senderPhone, selfPhone)` in `access-control.ts` |
104
- | **Admin phones** | Phones listed in `adminPhones` in `account.json`. The user's personal phone is auto-registered on QR link; additional phones are added via `whatsapp-config action: "add-admin-phone"`. Their admin session is scoped to the **house** account. | `isAdminPhone()` in `access-control.ts` — iterates `adminPhones` |
105
- | **Account managers** | Phones in the `accountManagers` map (`account.json`, phone → sub-account UUID), added via `whatsapp-config action: "add-account-manager"`. Their admin session is scoped to the **bound sub-account**, not the house. Disjoint from `adminPhones` — a phone is a house admin or a sub-account manager, never both. | `managedAccountFor()` in `access-control.ts`; the sub-account scope is applied in `ensureChannelSession` |
104
+ | **Admin phones** | Phones listed in `adminPhones` in `account.json`. The user's personal phone is auto-registered on QR link; additional phones are added via `whatsapp-config action: "add-admin-phone"`. Their admin session is scoped to the **house** account. `adminPhones` is authoritative only on the account that owns the WhatsApp paired socket (the house): `add-admin-phone` is refused on any other account, and a list left on a non-socket (sub-)account is inert and purged at boot with `[admin-identity] op=purge-nonsocket-adminphone`. To answer "is this phone a manager?" read the house account's `adminPhones` + `accountManagers` only — never a sub-account's list. | `isAdminPhone()` in `access-control.ts` — iterates `adminPhones` |
105
+ | **Account managers** | Phones in the `accountManagers` map (`account.json`, phone → sub-account UUID), added via `whatsapp-config action: "add-account-manager"`. Their admin session is scoped to the **bound sub-account**, not the house. Disjoint from `adminPhones` — a phone is a house admin or a sub-account manager, never both. | `managedAccountFor()` in `access-control.ts` resolves the sub-account **once**; `checkDmAccess` returns it as `effectiveAccountId`, and `ensureChannelSession` consumes that value without re-resolving |
106
106
  | **Public/unknown** | Any phone that is not self, not in `adminPhones`, and not in `accountManagers`. Subject to DM policy gating. | Everything else in `checkDmAccess()` |
107
107
 
108
108
  **Critical distinction:** The *self phone* is the paired device's number (the WhatsApp account Maxy controls). The *admin phone* is the user's personal phone (the phone they message *from*). These are typically different numbers. If the user's personal phone is not in `adminPhones` or `accountManagers`, their messages route as public — not admin.
109
109
 
110
- **Account-manager scoping — the two-account split.** A designated manager's inbound spawns an admin session scoped to their sub-account (its graph, skills, and `${USER_ID}` attribution). Replies and file delivery still go out over the **house's** paired WhatsApp device — the only WhatsApp socket — so the sub-account scope never redirects the transport. `ensureChannelSession` feeds the effective (sub-account) id to the spawn while keeping the connection (house) account for `startNativeFileFollower` and the reply path. A stale binding (deleted sub-account, or a phone wrongly also on `adminPhones`) is surfaced at boot by `account-manager-drift` and never silently mis-routes.
110
+ **Account-manager scoping — the two-account split.** A designated manager's inbound spawns an admin session scoped to their sub-account (its graph, skills, and `${USER_ID}` attribution). Replies and file delivery still go out over the **house's** paired WhatsApp device — the only WhatsApp socket — so the sub-account scope never redirects the transport. The connection (house) account is kept for `startNativeFileFollower` and the reply path. A stale binding (a phone wrongly also on `adminPhones`) is surfaced at boot by `account-manager-drift` and never silently mis-routes.
111
+
112
+ **Single source of truth + fail-closed.** The effective account is resolved **exactly once**, in the gate: `checkDmAccess` returns `effectiveAccountId` (the house account for owner/`adminPhones`/public, the bound sub-account for a manager), and that value threads through the inbound payload → gateway → `ensureChannelSession`, which spawns into it **without re-reading** the `accountManagers` map. There is no second resolution and no `?? accountId` house fallback: if a manager's bound sub-account is not a valid account, the inbound is **rejected** (no session spawned, no reply), never routed to the house. This closes the escalation where a divergence between two independent map reads handed a scoped manager a house-owner admin session. Observable signals: `op=account-manager-route … effectiveAccount=… source=gate` on a routed manager inbound; `op=account-manager-reject … reason=unresolved-effective-account` on the fail-closed drop; a standing `op=escalation-tripwire` belt that can only fire if a future change reintroduces the divergence.
111
113
 
112
114
  **`dmPolicy` behaviour (per-account):**
113
115
 
@@ -31,6 +31,8 @@ Call `whatsapp-config action: list-admin-phones` and present the current list. A
31
31
 
32
32
  Each add/remove fires immediately — admin phones are managed per-action, independent of the form's batch submit.
33
33
 
34
+ Admin phones are authoritative only on the account that owns the WhatsApp paired socket (the house). Adding an admin phone always applies to that account; the write is refused on any other account, because a phone in a sub-account's admin list is never consulted. To route a person to a specific sub-account, bind them as an account manager with `add-account-manager` instead — do not read or edit a sub-account's admin list to answer "is this phone a manager?".
35
+
34
36
  **Account managers:**
35
37
 
36
38
  An account manager is a phone bound to a specific sub-account. Its WhatsApp messages spawn an admin session scoped to that sub-account's agent, not the house. Use this when a person who manages one client sub-account should reach that sub-account's agent over WhatsApp.
@@ -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);
@@ -6050,6 +6071,12 @@ function createScheduleInjectRoutes(deps) {
6050
6071
  try {
6051
6072
  await deps.waHandleInbound({
6052
6073
  accountId: houseAccountId,
6074
+ // Task 1383 — the router (ensureChannelSession) no longer re-resolves
6075
+ // the accountManagers map; it consumes this value. The scheduler
6076
+ // supplies its own resolution (effectiveAccountFor, computed above) so
6077
+ // a manager destination scopes the spawned session to the sub-account,
6078
+ // exactly as a real inbound now does via the gate.
6079
+ effectiveAccountId: effectiveAccount2,
6053
6080
  senderId: destination,
6054
6081
  role: "admin",
6055
6082
  personId: null,
@@ -6120,7 +6147,7 @@ import { randomUUID as randomUUID6 } from "crypto";
6120
6147
 
6121
6148
  // app/lib/whatsapp/config-persist.ts
6122
6149
  import { readFileSync as readFileSync10, writeFileSync as writeFileSync4, existsSync as existsSync5 } from "fs";
6123
- import { resolve as resolve8, join as join8 } from "path";
6150
+ import { resolve as resolve8, join as join8, dirname as dirname2, basename as basename2 } from "path";
6124
6151
  var TAG17 = "[whatsapp:config]";
6125
6152
  function configPath(accountDir) {
6126
6153
  return resolve8(accountDir, "account.json");
@@ -6195,6 +6222,16 @@ function addAdminPhone(accountDir, phone) {
6195
6222
  }
6196
6223
  try {
6197
6224
  const config = readConfig(accountDir);
6225
+ const targetId = typeof config.accountId === "string" && config.accountId ? config.accountId : basename2(resolve8(accountDir));
6226
+ const socketOwner = resolveHouseOrSoleAccountId(dirname2(resolve8(accountDir)));
6227
+ if (targetId !== socketOwner) {
6228
+ console.error(`${TAG17} add-admin-phone rejected accountId=${targetId} reason=not-socket-account`);
6229
+ return {
6230
+ ok: false,
6231
+ reason: "not-socket-account",
6232
+ error: `${targetId} does not own the WhatsApp paired socket, so its admin phones are never consulted. To route a phone to a sub-account, bind it as an account manager (add-account-manager) instead.`
6233
+ };
6234
+ }
6198
6235
  if (!config.whatsapp || typeof config.whatsapp !== "object") {
6199
6236
  config.whatsapp = {};
6200
6237
  }
@@ -6270,6 +6307,44 @@ function readAdminPhones(accountDir) {
6270
6307
  return [];
6271
6308
  }
6272
6309
  }
6310
+ function purgeNonSocketAdminPhones(accountDir, socketOwnerId) {
6311
+ try {
6312
+ const config = readConfig(accountDir);
6313
+ const wa = config.whatsapp;
6314
+ if (!wa || typeof wa !== "object") return { removed: [] };
6315
+ const targetId = typeof config.accountId === "string" && config.accountId ? config.accountId : basename2(resolve8(accountDir));
6316
+ if (targetId === socketOwnerId) return { removed: [] };
6317
+ const adminPhones = Array.isArray(wa.adminPhones) ? wa.adminPhones.filter((p) => typeof p === "string") : [];
6318
+ if (adminPhones.length === 0) return { removed: [] };
6319
+ delete wa.adminPhones;
6320
+ writeConfig(accountDir, config);
6321
+ for (const phone of adminPhones) {
6322
+ console.error(`[admin-identity] op=purge-nonsocket-adminphone accountId=${targetId} phone=${phone} removed=true`);
6323
+ }
6324
+ return { removed: adminPhones };
6325
+ } catch (err) {
6326
+ console.error(`[admin-identity] purge-nonsocket-adminphone failed dir=${accountDir}: ${String(err)}`);
6327
+ return { removed: [] };
6328
+ }
6329
+ }
6330
+ function purgeNonSocketAdminPhonesAtBoot() {
6331
+ let socketOwner;
6332
+ try {
6333
+ socketOwner = resolveHouseOrSoleAccountId(ACCOUNTS_DIR);
6334
+ } catch (err) {
6335
+ console.error(`[admin-identity] purge-nonsocket-adminphone skipped \u2014 cannot resolve socket owner: ${String(err)}`);
6336
+ return;
6337
+ }
6338
+ let accounts;
6339
+ try {
6340
+ accounts = listValidAccounts();
6341
+ } catch {
6342
+ return;
6343
+ }
6344
+ for (const acct of accounts) {
6345
+ purgeNonSocketAdminPhones(acct.accountDir, socketOwner);
6346
+ }
6347
+ }
6273
6348
  function setAccountManager(accountDir, phone, subAccountId) {
6274
6349
  const normalized = phone.trim();
6275
6350
  if (!E164_PATTERN.test(normalized)) {
@@ -6798,7 +6873,7 @@ function listActiveLoginAccountIds() {
6798
6873
  // app/lib/whatsapp/outbound/send-document.ts
6799
6874
  import { realpathSync as realpathSync3 } from "fs";
6800
6875
  import { readFile, stat as stat2 } from "fs/promises";
6801
- import { resolve as resolve9, basename as basename2 } from "path";
6876
+ import { resolve as resolve9, basename as basename3 } from "path";
6802
6877
  var TAG19 = "[whatsapp:outbound]";
6803
6878
  var WHATSAPP_DOCUMENT_MAX_BYTES = 100 * 1024 * 1024;
6804
6879
  var lastDocumentOutboundAt = /* @__PURE__ */ new Map();
@@ -6853,7 +6928,7 @@ async function sendWhatsAppDocument(input) {
6853
6928
  error: `File exceeds 100 MB limit (${(fileStat.size / 1024 / 1024).toFixed(1)} MB)`
6854
6929
  };
6855
6930
  }
6856
- const filename = basename2(resolvedPath);
6931
+ const filename = basename3(resolvedPath);
6857
6932
  const jid = normalizeJid2(to);
6858
6933
  const sock = getSocket(accountId);
6859
6934
  if (!sock) {
@@ -7142,7 +7217,8 @@ app2.post("/config", async (c) => {
7142
7217
  return c.json({ ok: false, error: 'Missing required field "phone" (E.164 format, e.g. +441234567890).' }, 400);
7143
7218
  }
7144
7219
  const result = addAdminPhone(account.accountDir, phone);
7145
- console.error(`${TAG21} config action=add-admin-phone phone=${phone} ok=${result.ok}`);
7220
+ const reasonTail = !result.ok && result.reason ? ` reason=${result.reason}` : "";
7221
+ console.error(`${TAG21} config action=add-admin-phone accountId=${account.accountId} phone=${phone} ok=${result.ok}${reasonTail}`);
7146
7222
  return c.json(result, result.ok ? 200 : 400);
7147
7223
  }
7148
7224
  case "remove-admin-phone": {
@@ -7501,11 +7577,11 @@ var whatsapp_default = app2;
7501
7577
 
7502
7578
  // server/routes/whatsapp-reader.ts
7503
7579
  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";
7580
+ import { basename as basename5, dirname as dirname4, isAbsolute, join as join14, relative as relative2, resolve as resolve12, sep as sep3 } from "path";
7505
7581
 
7506
7582
  // server/routes/admin/sidebar-sessions.ts
7507
7583
  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";
7584
+ import { basename as basename4, dirname as dirname3, join as join11, resolve as resolve11 } from "path";
7509
7585
 
7510
7586
  // app/lib/whatsapp-reader/select-sessions.ts
7511
7587
  function isReaderChannelSession(role, channel) {
@@ -7586,7 +7662,7 @@ function findSessionProjectDir(sessionId) {
7586
7662
  const cfg = claudeConfigDir();
7587
7663
  if (!cfg) return null;
7588
7664
  for (const { path: path2 } of enumerateJsonls(join11(cfg, "projects"))) {
7589
- if (basename3(path2) === `${sessionId}.jsonl`) return dirname2(path2);
7665
+ if (basename4(path2) === `${sessionId}.jsonl`) return dirname3(path2);
7590
7666
  }
7591
7667
  return null;
7592
7668
  }
@@ -8315,8 +8391,8 @@ app4.get("/conversations", requireAdminSession, async (c) => {
8315
8391
  const rows = [];
8316
8392
  let sessionRowsExcludedUntagged = 0;
8317
8393
  for (const { path: path2 } of enumerateJsonls(projectsRoot)) {
8318
- const sessionId = basename4(path2).replace(/\.jsonl$/, "");
8319
- const projectDir = dirname3(path2);
8394
+ const sessionId = basename5(path2).replace(/\.jsonl$/, "");
8395
+ const projectDir = dirname4(path2);
8320
8396
  const meta = readSidecarMeta(join14(projectDir, `${sessionId}.meta.json`));
8321
8397
  if (!isReaderChannelSession(meta.role, meta.channel)) continue;
8322
8398
  if (multiAccount) {
@@ -8846,7 +8922,7 @@ var whatsapp_reader_default = app4;
8846
8922
 
8847
8923
  // server/routes/public-reader.ts
8848
8924
  import { statSync as statSync6, openSync as openSync2, readSync as readSync2, closeSync as closeSync2, watch as watch2, readFileSync as readFileSync16, readdirSync as readdirSync9 } from "fs";
8849
- import { basename as basename5, dirname as dirname4, join as join15, resolve as resolve13, sep as sep4 } from "path";
8925
+ import { basename as basename6, dirname as dirname5, join as join15, resolve as resolve13, sep as sep4 } from "path";
8850
8926
 
8851
8927
  // app/lib/whatsapp-reader/delivered-kinds.ts
8852
8928
  var PUBLIC_DELIVERED_KINDS = /* @__PURE__ */ new Set([
@@ -8921,8 +8997,8 @@ function enumeratePublicRows() {
8921
8997
  if (!cfg) return [];
8922
8998
  const rows = [];
8923
8999
  for (const { path: path2 } of enumerateJsonls(join15(cfg, "projects"))) {
8924
- const sessionId = basename5(path2).replace(/\.jsonl$/, "");
8925
- const projectDir = dirname4(path2);
9000
+ const sessionId = basename6(path2).replace(/\.jsonl$/, "");
9001
+ const projectDir = dirname5(path2);
8926
9002
  const meta = readSidecarMeta(join15(projectDir, `${sessionId}.meta.json`));
8927
9003
  rows.push({
8928
9004
  sessionId,
@@ -9140,7 +9216,7 @@ app5.get("/attachment/:attachmentId", (c) => {
9140
9216
  var public_reader_default = app5;
9141
9217
 
9142
9218
  // server/routes/webchat.ts
9143
- import { basename as basename6, dirname as dirname5 } from "path";
9219
+ import { basename as basename7, dirname as dirname6 } from "path";
9144
9220
  import { join as join18 } from "path";
9145
9221
  import { existsSync as existsSync10, readdirSync as readdirSync11, readFileSync as readFileSync19, renameSync as renameSync3, statSync as statSync7, writeFileSync as writeFileSync7 } from "fs";
9146
9222
 
@@ -9538,8 +9614,8 @@ function latestAdminWebchatSessionId(requesterUserId, primaryUserId, scopeAccoun
9538
9614
  let best = null;
9539
9615
  for (const { path: path2, isSubagent, archived } of enumerateJsonls(join18(cfg, "projects"))) {
9540
9616
  if (isSubagent || archived) continue;
9541
- const id = basename6(path2).slice(0, -".jsonl".length);
9542
- const meta = readSidecarMeta(join18(dirname5(path2), `${id}.meta.json`));
9617
+ const id = basename7(path2).slice(0, -".jsonl".length);
9618
+ const meta = readSidecarMeta(join18(dirname6(path2), `${id}.meta.json`));
9543
9619
  if (meta.role !== "admin" || meta.channel !== "webchat") continue;
9544
9620
  if (multiAccount && meta.accountId !== scopeAccountId) continue;
9545
9621
  let mtimeMs;
@@ -9562,7 +9638,7 @@ function latestAdminWebchatSessionId(requesterUserId, primaryUserId, scopeAccoun
9562
9638
  function overrideKnownNonArchived(id) {
9563
9639
  const { projectDir } = locateSession(id);
9564
9640
  if (projectDir === null) return sessionSidecarExists(id);
9565
- return basename6(projectDir) !== "archive";
9641
+ return basename7(projectDir) !== "archive";
9566
9642
  }
9567
9643
  function resolveCanonical(accountId, accountDir, requesterUserId, primaryUserId, scopeAccountId, multiAccount) {
9568
9644
  const bootstrapAccountId = scopeAccountId ?? accountId;
@@ -9930,8 +10006,8 @@ ${note}` : note;
9930
10006
  let projectDir = null;
9931
10007
  if (cfg) {
9932
10008
  for (const { path: path2 } of enumerateJsonls(join18(cfg, "projects"))) {
9933
- if (basename6(path2) === `${sessionId}.jsonl`) {
9934
- projectDir = dirname5(path2);
10009
+ if (basename7(path2) === `${sessionId}.jsonl`) {
10010
+ projectDir = dirname6(path2);
9935
10011
  break;
9936
10012
  }
9937
10013
  }
@@ -10369,7 +10445,7 @@ import { createHash as createHash3, randomUUID as randomUUID7 } from "crypto";
10369
10445
 
10370
10446
  // ../lib/admins-write/src/index.ts
10371
10447
  import { existsSync as existsSync14, readFileSync as readFileSync23, writeFileSync as writeFileSync9, renameSync as renameSync5, mkdirSync as mkdirSync5, readdirSync as readdirSync13, statSync as statSync8, appendFileSync as appendFileSync2 } from "fs";
10372
- import { dirname as dirname6, join as join22 } from "path";
10448
+ import { dirname as dirname7, join as join22 } from "path";
10373
10449
  function id8(value) {
10374
10450
  return value.slice(0, 8);
10375
10451
  }
@@ -10382,7 +10458,7 @@ function appendUsersAuditLine(audit, fields) {
10382
10458
  const line = `[users-audit] action=${fields.action} actor=${actor}${sess} field=${fields.field} rowsBefore=${fields.rowsBefore} rowsAfter=${fields.rowsAfter} ts=${(/* @__PURE__ */ new Date()).toISOString()}
10383
10459
  `;
10384
10460
  try {
10385
- mkdirSync5(dirname6(audit.logFile), { recursive: true, mode: 448 });
10461
+ mkdirSync5(dirname7(audit.logFile), { recursive: true, mode: 448 });
10386
10462
  appendFileSync2(audit.logFile, line, { mode: 384 });
10387
10463
  } catch (err) {
10388
10464
  console.error(
@@ -10400,7 +10476,7 @@ function logLine(input, result) {
10400
10476
  );
10401
10477
  }
10402
10478
  function writeFileAtomic(filePath, contents, mode) {
10403
- mkdirSync5(dirname6(filePath), { recursive: true });
10479
+ mkdirSync5(dirname7(filePath), { recursive: true });
10404
10480
  const tempPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
10405
10481
  writeFileSync9(tempPath, contents, mode !== void 0 ? { mode } : void 0);
10406
10482
  renameSync5(tempPath, filePath);
@@ -11249,7 +11325,7 @@ var accounts_default = app12;
11249
11325
 
11250
11326
  // server/routes/admin/logs.ts
11251
11327
  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";
11328
+ import { resolve as resolve16, basename as basename8 } from "path";
11253
11329
 
11254
11330
  // app/lib/logs-read-resolve.ts
11255
11331
  import { existsSync as existsSync17 } from "fs";
@@ -11281,7 +11357,7 @@ app13.get("/", async (c) => {
11281
11357
  if (accountLogDir) logDirs.push(accountLogDir);
11282
11358
  logDirs.push(LOG_DIR);
11283
11359
  if (fileParam) {
11284
- const safe = basename7(fileParam);
11360
+ const safe = basename8(fileParam);
11285
11361
  const searched = [];
11286
11362
  for (const dir of logDirs) {
11287
11363
  const filePath = resolve16(dir, safe);
@@ -11351,7 +11427,7 @@ app13.get("/", async (c) => {
11351
11427
  if (hit) {
11352
11428
  console.info(`[admin/logs] resolved cacheKey=${cacheKeySlice} sessionId=${sessionIdSlice} via=${primaryId === cacheKey ? "cacheKey" : primaryId === sessionKeyFromConv ? "reverse-lookup" : "sessionId-fallback"}`);
11353
11429
  try {
11354
- const filename = basename7(hit.path);
11430
+ const filename = basename8(hit.path);
11355
11431
  const buffer = readFileSync25(hit.path);
11356
11432
  const onDiskBytes = statSync10(hit.path).size;
11357
11433
  const headers = {
@@ -12285,7 +12361,7 @@ var sessions_default = app17;
12285
12361
 
12286
12362
  // app/lib/claude-agent/spawn-context.ts
12287
12363
  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";
12364
+ import { dirname as dirname8, resolve as resolve19, join as join25 } from "path";
12289
12365
  async function resolveOwnerProfileBlock(accountId, userId) {
12290
12366
  if (!userId) return { ok: false, reason: "missing-user-id" };
12291
12367
  try {
@@ -12392,7 +12468,7 @@ function listPluginDirs() {
12392
12468
  }
12393
12469
  }
12394
12470
  }
12395
- const premiumRoot = resolve19(dirname7(PLATFORM_ROOT), "premium-plugins");
12471
+ const premiumRoot = resolve19(dirname8(PLATFORM_ROOT), "premium-plugins");
12396
12472
  if (existsSync22(premiumRoot)) {
12397
12473
  for (const bundle of readdirSync16(premiumRoot)) {
12398
12474
  const bundlePlugins = join25(premiumRoot, bundle, "plugins");
@@ -12841,7 +12917,7 @@ import { createReadStream as createReadStream2, createWriteStream as createWrite
12841
12917
  import { readdir as readdir3, readFile as readFile4, stat as stat4, mkdir as mkdir3, unlink as unlink2, rename } from "fs/promises";
12842
12918
  import { realpathSync as realpathSync5 } from "fs";
12843
12919
  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";
12920
+ import { basename as basename10, dirname as dirname9, join as join27, relative as relative4, resolve as resolve22, sep as sep6 } from "path";
12845
12921
  import { Readable as Readable2 } from "stream";
12846
12922
 
12847
12923
  // ../lib/graph-trash/src/index.ts
@@ -13159,7 +13235,7 @@ async function cascadeDeleteDocument(params) {
13159
13235
 
13160
13236
  // app/lib/file-index.ts
13161
13237
  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";
13238
+ import { resolve as resolve21, relative as relative3, join as join26, basename as basename9, extname as extname2, sep as sep5 } from "path";
13163
13239
  import { tmpdir as tmpdir2 } from "os";
13164
13240
  import { execFile as execFile2 } from "child_process";
13165
13241
  import { promisify as promisify2 } from "util";
@@ -13297,8 +13373,8 @@ async function extractFileContent(absolute) {
13297
13373
  }
13298
13374
  }
13299
13375
  async function readDisplayName(absolute) {
13300
- const dir = absolute.slice(0, absolute.length - basename8(absolute).length);
13301
- const base = basename8(absolute);
13376
+ const dir = absolute.slice(0, absolute.length - basename9(absolute).length);
13377
+ const base = basename9(absolute);
13302
13378
  const dot = base.lastIndexOf(".");
13303
13379
  const stem = dot === -1 ? base : base.slice(0, dot);
13304
13380
  if (base === `${stem}.meta.json`) return null;
@@ -13436,7 +13512,7 @@ async function cascadeTrashLinkedKnowledgeDocs(session, accountId, relativePaths
13436
13512
  return { kds, sections, chunks };
13437
13513
  }
13438
13514
  async function buildArtifact(accountId, wf, embed2) {
13439
- const name = basename8(wf.absolute);
13515
+ const name = basename9(wf.absolute);
13440
13516
  const { content, route } = await extractFileContent(wf.absolute);
13441
13517
  const displayName = await readDisplayName(wf.absolute);
13442
13518
  const embedInput = `${name}
@@ -13966,7 +14042,7 @@ app21.get("/download", requireAdminSession, async (c) => {
13966
14042
  if (!info.isFile()) {
13967
14043
  return c.json({ error: "Path is not a file" }, 400);
13968
14044
  }
13969
- const filename = basename9(absolute);
14045
+ const filename = basename10(absolute);
13970
14046
  const mimeType = detectMimeType(absolute);
13971
14047
  const nodeStream = createReadStream2(absolute);
13972
14048
  const webStream = Readable2.toWeb(nodeStream);
@@ -14071,7 +14147,7 @@ app21.get("/download-zip", requireAdminSession, async (c) => {
14071
14147
  const info = await stat4(absolute);
14072
14148
  if (info.isDirectory()) {
14073
14149
  dirsWalked++;
14074
- const parentAbs = dirname8(absolute);
14150
+ const parentAbs = dirname9(absolute);
14075
14151
  for await (const fileAbs of walkRegularFiles(absolute)) {
14076
14152
  const within = relative4(absolute, fileAbs).split(sep6).join("/");
14077
14153
  const fileRel = relPath === "" || relPath === "." ? within : `${relPath}/${within}`;
@@ -14082,7 +14158,7 @@ app21.get("/download-zip", requireAdminSession, async (c) => {
14082
14158
  if (over) return over;
14083
14159
  }
14084
14160
  } else if (info.isFile()) {
14085
- const over = await addFile(absolute, info.size, basename9(absolute));
14161
+ const over = await addFile(absolute, info.size, basename10(absolute));
14086
14162
  if (over) return over;
14087
14163
  } else {
14088
14164
  return c.json({ error: "Path is not a file or directory" }, 400);
@@ -14111,12 +14187,12 @@ function dataUploadCeiling() {
14111
14187
  }
14112
14188
  async function resolveUploadTarget(c, accountId, uid) {
14113
14189
  const rawName = c.req.query("filename") ?? "";
14114
- const safeName = basename9(rawName).replace(/[\0/\\]/g, "_");
14190
+ const safeName = basename10(rawName).replace(/[\0/\\]/g, "_");
14115
14191
  if (!safeName) return { ok: false, status: 400, error: "filename query param required" };
14116
14192
  const rawRelpath = c.req.query("relpath") ?? "";
14117
14193
  const relpath = rawRelpath !== "" ? rawRelpath : null;
14118
14194
  const token = c.req.query("token") ?? "";
14119
- const finalName = relpath ? basename9(relpath).replace(/[\0/\\]/g, "_") : `${Date.now()}-${safeName}`;
14195
+ const finalName = relpath ? basename10(relpath).replace(/[\0/\\]/g, "_") : `${Date.now()}-${safeName}`;
14120
14196
  const mimeType = (c.req.header("content-type") ?? "").split(";")[0].trim();
14121
14197
  if (!SUPPORTED_MIME_TYPES.has(mimeType)) {
14122
14198
  console.error(`[data-upload] op=reject-mime uid=${uid} mime="${mimeType}" token=${token} rel="${relpath ?? ""}"`);
@@ -14125,7 +14201,7 @@ async function resolveUploadTarget(c, accountId, uid) {
14125
14201
  const rawDir = c.req.query("path") ?? "";
14126
14202
  const base = resolveOwnAccountWrite(rawDir, accountId, "POST /api/admin/files/upload");
14127
14203
  if (!base.ok) return { ok: false, status: base.status, error: base.error };
14128
- const relDir = relpath ? dirname8(relpath) : ".";
14204
+ const relDir = relpath ? dirname9(relpath) : ".";
14129
14205
  const destRel = relDir === "." ? base.relative : join27(base.relative, relDir);
14130
14206
  if (crossesForeignAccountPartition(destRel, accountId)) {
14131
14207
  console.error(`[data] account-scope-blocked endpoint="POST /api/admin/files/upload" path="${destRel}" account=${accountId.slice(0, 8)}\u2026`);
@@ -14486,7 +14562,7 @@ app21.delete("/", requireAdminSession, async (c) => {
14486
14562
  console.error(`[data] account-scope-blocked endpoint="DELETE /api/admin/files" path="${relPath}" account=${accountId.slice(0, 8)}\u2026`);
14487
14563
  return c.json({ error: "Not found" }, 404);
14488
14564
  }
14489
- const base = basename9(absolute);
14565
+ const base = basename10(absolute);
14490
14566
  const protection = isProtectedFromDeletion(relPath);
14491
14567
  if (protection.protected) {
14492
14568
  console.error(`[data] file-delete blocked path="${relPath}" reason="protected" rule="${protection.rule}"`);
@@ -14502,7 +14578,7 @@ app21.delete("/", requireAdminSession, async (c) => {
14502
14578
  }
14503
14579
  const dot = base.lastIndexOf(".");
14504
14580
  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;
14581
+ const sidecarPath = UUID_RE3.test(stem) && base !== `${stem}.meta.json` ? join27(dirname9(absolute), `${stem}.meta.json`) : null;
14506
14582
  await unlink2(absolute);
14507
14583
  if (sidecarPath) {
14508
14584
  try {
@@ -14598,8 +14674,8 @@ app21.post("/rename", requireAdminSession, async (c) => {
14598
14674
  console.error(`[data] file-rename blocked path="${src.relative}" reason="protected"`);
14599
14675
  return c.json({ error: "Protected entry \u2014 refusing to rename" }, 403);
14600
14676
  }
14601
- const destAbs = resolve22(dirname8(src.absolute), newName);
14602
- const newRel = `${dirname8(src.relative)}/${newName}`.replace(/^\.\//, "");
14677
+ const destAbs = resolve22(dirname9(src.absolute), newName);
14678
+ const newRel = `${dirname9(src.relative)}/${newName}`.replace(/^\.\//, "");
14603
14679
  if (isProtectedFromRename(newRel)) {
14604
14680
  console.error(`[data] file-rename blocked path="${newRel}" reason="protected-target"`);
14605
14681
  return c.json({ error: "That name is reserved" }, 403);
@@ -16730,7 +16806,7 @@ var graph_default_view_default = app27;
16730
16806
 
16731
16807
  // server/routes/admin/sidebar-artefacts.ts
16732
16808
  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";
16809
+ import { resolve as resolve23, relative as relative5, isAbsolute as isAbsolute2, sep as sep7, basename as basename11 } from "path";
16734
16810
  import { existsSync as existsSync25 } from "fs";
16735
16811
  var LIMIT = 50;
16736
16812
  var ADMIN_AGENT_FILES = ["IDENTITY.md", "SOUL.md", "KNOWLEDGE.md"];
@@ -16783,7 +16859,7 @@ async function fetchAccountFileArtefacts(accountId) {
16783
16859
  const modifiedAt = r.get("modifiedAt") ?? "";
16784
16860
  return {
16785
16861
  id: `account-file:${relativePath}`,
16786
- name: displayName ?? basename10(relativePath),
16862
+ name: displayName ?? basename11(relativePath),
16787
16863
  kind: "account-file",
16788
16864
  updatedAt: modifiedAt,
16789
16865
  mimeType,
@@ -19125,7 +19201,7 @@ var sites_default = app49;
19125
19201
  // app/lib/visitor-token.ts
19126
19202
  import { createHmac, randomBytes, timingSafeEqual } from "crypto";
19127
19203
  import { mkdirSync as mkdirSync7, readFileSync as readFileSync32, writeFileSync as writeFileSync11 } from "fs";
19128
- import { dirname as dirname9 } from "path";
19204
+ import { dirname as dirname10 } from "path";
19129
19205
  var TOKEN_PREFIX = "v1.";
19130
19206
  var SECRET_BYTES = 32;
19131
19207
  var DEFAULT_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
@@ -19142,7 +19218,7 @@ function getSecret() {
19142
19218
  }
19143
19219
  const fresh = randomBytes(SECRET_BYTES).toString("hex");
19144
19220
  try {
19145
- mkdirSync7(dirname9(VISITOR_TOKEN_SECRET_FILE), { recursive: true, mode: 448 });
19221
+ mkdirSync7(dirname10(VISITOR_TOKEN_SECRET_FILE), { recursive: true, mode: 448 });
19146
19222
  writeFileSync11(VISITOR_TOKEN_SECRET_FILE, fresh, { mode: 384, flag: "wx" });
19147
19223
  console.log(`[visitor-token] secret minted path=${VISITOR_TOKEN_SECRET_FILE}`);
19148
19224
  } catch {
@@ -20250,7 +20326,7 @@ async function startFileWatcher(opts = {}) {
20250
20326
 
20251
20327
  // app/lib/migrate-uploads.ts
20252
20328
  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";
20329
+ import { dirname as dirname11, relative as relative6, resolve as resolve29 } from "path";
20254
20330
  var ACCOUNT_UUID_RE4 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
20255
20331
  async function walkFiles(dir) {
20256
20332
  const out = [];
@@ -20282,7 +20358,7 @@ async function relocateTree(srcDir, destDir, dataRoot, accountId, session) {
20282
20358
  for (const oldAbs of files) {
20283
20359
  const suffix = relative6(srcDir, oldAbs);
20284
20360
  const newAbs = resolve29(destDir, suffix);
20285
- await mkdir5(dirname10(newAbs), { recursive: true });
20361
+ await mkdir5(dirname11(newAbs), { recursive: true });
20286
20362
  await rename2(oldAbs, newAbs);
20287
20363
  moved++;
20288
20364
  const oldRel = relative6(dataRoot, oldAbs);
@@ -21011,6 +21087,7 @@ var WaGateway = class {
21011
21087
  try {
21012
21088
  await this.deps.ensureChannelSession({
21013
21089
  accountId: input.accountId,
21090
+ effectiveAccountId: input.effectiveAccountId,
21014
21091
  senderId: input.senderId,
21015
21092
  role: input.role ?? "admin",
21016
21093
  personId: input.personId ?? null,
@@ -22375,7 +22452,7 @@ function buildTelegramSpawnRequest(input) {
22375
22452
  // app/lib/telegram/outbound/send-document.ts
22376
22453
  import { realpathSync as realpathSync7 } from "fs";
22377
22454
  import { readFile as readFile7, stat as stat7 } from "fs/promises";
22378
- import { resolve as resolve30, basename as basename11 } from "path";
22455
+ import { resolve as resolve30, basename as basename12 } from "path";
22379
22456
  var TAG36 = "[telegram:outbound]";
22380
22457
  var TELEGRAM_DOCUMENT_MAX_BYTES = 50 * 1024 * 1024;
22381
22458
  async function sendTelegramDocument(input) {
@@ -22412,7 +22489,7 @@ async function sendTelegramDocument(input) {
22412
22489
  error: `File exceeds 50 MB limit (${(fileStat.size / 1024 / 1024).toFixed(1)} MB)`
22413
22490
  };
22414
22491
  }
22415
- const filename = basename11(resolvedPath);
22492
+ const filename = basename12(resolvedPath);
22416
22493
  const buffer = Buffer.from(await readFile7(resolvedPath));
22417
22494
  const form = new FormData();
22418
22495
  form.append("chat_id", String(chatId));
@@ -22705,6 +22782,10 @@ function findChannelAdminBindingDrift(adminPhones, admins, users) {
22705
22782
  }
22706
22783
  return drift;
22707
22784
  }
22785
+ function classifyAdminPhonesForAccount(isSocketOwner, adminPhones, admins, users) {
22786
+ if (!isSocketOwner) return { nonSocketPhones: [...adminPhones], drift: [] };
22787
+ return { nonSocketPhones: [], drift: findChannelAdminBindingDrift(adminPhones, admins, users) };
22788
+ }
22708
22789
  function warnOnChannelAdminBindingDrift() {
22709
22790
  let users;
22710
22791
  try {
@@ -22718,10 +22799,27 @@ function warnOnChannelAdminBindingDrift() {
22718
22799
  } catch {
22719
22800
  return;
22720
22801
  }
22802
+ let socketOwner;
22803
+ try {
22804
+ socketOwner = resolveHouseOrSoleAccountId(ACCOUNTS_DIR);
22805
+ } catch {
22806
+ socketOwner = null;
22807
+ }
22721
22808
  const validAccountIds = accounts.map((a) => a.accountId);
22722
22809
  for (const acct of accounts) {
22723
22810
  const adminPhones = readAdminPhones(acct.accountDir);
22724
- const drift = findChannelAdminBindingDrift(adminPhones, acct.config.admins ?? [], users);
22811
+ const isSocketOwner = socketOwner === null || acct.accountId === socketOwner;
22812
+ const { nonSocketPhones, drift } = classifyAdminPhonesForAccount(
22813
+ isSocketOwner,
22814
+ adminPhones,
22815
+ acct.config.admins ?? [],
22816
+ users
22817
+ );
22818
+ for (const phone of nonSocketPhones) {
22819
+ console.error(
22820
+ `[admin-identity] adminphones-on-non-socket-account accountId=${acct.accountId} phone=${phone} count=${nonSocketPhones.length}`
22821
+ );
22822
+ }
22725
22823
  for (const d of drift) {
22726
22824
  const tail = d.reason === "userid-not-admin" ? ` userId=${d.userId}` : "";
22727
22825
  console.error(
@@ -23097,12 +23195,10 @@ var waGateway = new WaGateway({
23097
23195
  });
23098
23196
  return result.ok ? { ok: true, messageId: result.messageId } : { ok: false, error: result.error };
23099
23197
  },
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;
23198
+ ensureChannelSession: async ({ accountId, effectiveAccountId, senderId, role, personId, gatewayUrl, serverPath }) => {
23104
23199
  if (role === "admin") {
23105
- console.error(`[whatsapp-native] op=account-manager-route senderId=${senderId} managesAccount=${managed ?? "none"} effectiveAccount=${effectiveAccountId}`);
23200
+ const managesAccount = effectiveAccountId !== accountId ? effectiveAccountId : "none";
23201
+ console.error(`[whatsapp-native] op=account-manager-route senderId=${senderId} managesAccount=${managesAccount} effectiveAccount=${effectiveAccountId} source=gate`);
23106
23202
  }
23107
23203
  const req = buildWaSpawnRequest({ accountId: effectiveAccountId, senderId, role, personId, gatewayUrl, serverPath });
23108
23204
  const userId = role === "admin" ? resolveAdminUserId({ accountId: effectiveAccountId, senderPhone: senderId }) ?? void 0 : void 0;
@@ -24243,7 +24339,7 @@ try {
24243
24339
  } catch (err) {
24244
24340
  console.error(`[graph-health] account-enumeration unavailable reason=${err instanceof Error ? err.message : String(err)}`);
24245
24341
  }
24246
- var configDirForWhatsApp = basename12(MAXY_DIR) || ".maxy";
24342
+ var configDirForWhatsApp = basename13(MAXY_DIR) || ".maxy";
24247
24343
  var bootAccount = resolveAccount();
24248
24344
  if (bootAccount) {
24249
24345
  migrateRemovedConfigKeys(bootAccount.accountDir);
@@ -24263,6 +24359,7 @@ reconcileEnabledPlugins(bootAccount?.accountDir, bootAccount?.config, bootAccoun
24263
24359
  for (const acct of listValidAccounts()) {
24264
24360
  cleanupLeakedPremiumSubs(acct.accountDir, acct.config, acct.accountId);
24265
24361
  }
24362
+ purgeNonSocketAdminPhonesAtBoot();
24266
24363
  warnOnChannelAdminBindingDrift();
24267
24364
  var bootEnabled = Array.isArray(bootAccountConfig?.enabledPlugins) ? bootAccountConfig.enabledPlugins : [];
24268
24365
  var bootDelivered = [];
@@ -24322,6 +24419,10 @@ init({
24322
24419
  console.error(`[whatsapp:route] dropped reason=no-text-no-media senderId=${msg.senderPhone} mediaCount=${msg.media.length}`);
24323
24420
  return;
24324
24421
  }
24422
+ if (!msg.effectiveAccountId) {
24423
+ console.error(`[whatsapp:route] op=dropped reason=no-effective-account senderId=${msg.senderPhone}`);
24424
+ return;
24425
+ }
24325
24426
  if (decision.role === "public") {
24326
24427
  console.error(`[whatsapp:route] op=routed agentType=public personId=${decision.personId} senderId=${msg.senderPhone}`);
24327
24428
  }
@@ -24330,6 +24431,7 @@ init({
24330
24431
  });
24331
24432
  await waGateway.handleInbound({
24332
24433
  accountId: msg.accountId,
24434
+ effectiveAccountId: msg.effectiveAccountId,
24333
24435
  senderId: msg.senderPhone,
24334
24436
  role: decision.role,
24335
24437
  personId: decision.personId,