@rubytech/create-maxy-code 0.1.550 → 0.1.551

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 (48) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/lib/agent-dispatch-rule/dist/index.d.ts +22 -8
  3. package/payload/platform/lib/agent-dispatch-rule/dist/index.d.ts.map +1 -1
  4. package/payload/platform/lib/agent-dispatch-rule/dist/index.js +47 -13
  5. package/payload/platform/lib/agent-dispatch-rule/dist/index.js.map +1 -1
  6. package/payload/platform/lib/agent-dispatch-rule/src/__tests__/rule.test.ts +68 -9
  7. package/payload/platform/lib/agent-dispatch-rule/src/index.ts +60 -16
  8. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +3 -3
  9. package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +4 -0
  10. package/payload/platform/plugins/docs/references/admin-ui.md +2 -2
  11. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/agent-dispatch.test.js +2 -2
  12. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/agent-dispatch.test.js.map +1 -1
  13. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/connector-sync-provision.test.js +2 -2
  14. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/connector-sync-provision.test.js.map +1 -1
  15. package/payload/platform/plugins/scheduling/mcp/dist/lib/connector-sync-provision.d.ts.map +1 -1
  16. package/payload/platform/plugins/scheduling/mcp/dist/lib/connector-sync-provision.js +21 -8
  17. package/payload/platform/plugins/scheduling/mcp/dist/lib/connector-sync-provision.js.map +1 -1
  18. package/payload/platform/plugins/telegram/.claude-plugin/plugin.json +1 -1
  19. package/payload/platform/plugins/telegram/PLUGIN.md +6 -4
  20. package/payload/platform/plugins/telegram/mcp/dist/index.js +62 -38
  21. package/payload/platform/plugins/telegram/mcp/dist/index.js.map +1 -1
  22. package/payload/platform/plugins/telegram/mcp/dist/lib/account-token.d.ts +18 -5
  23. package/payload/platform/plugins/telegram/mcp/dist/lib/account-token.d.ts.map +1 -1
  24. package/payload/platform/plugins/telegram/mcp/dist/lib/account-token.js +34 -6
  25. package/payload/platform/plugins/telegram/mcp/dist/lib/account-token.js.map +1 -1
  26. package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.d.ts +25 -8
  27. package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.d.ts.map +1 -1
  28. package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.js +28 -16
  29. package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.js.map +1 -1
  30. package/payload/platform/plugins/telegram/mcp/dist/lib/agent-active.d.ts +12 -0
  31. package/payload/platform/plugins/telegram/mcp/dist/lib/agent-active.d.ts.map +1 -0
  32. package/payload/platform/plugins/telegram/mcp/dist/lib/agent-active.js +26 -0
  33. package/payload/platform/plugins/telegram/mcp/dist/lib/agent-active.js.map +1 -0
  34. package/payload/platform/plugins/telegram/mcp/dist/lib/secret-path.d.ts +16 -13
  35. package/payload/platform/plugins/telegram/mcp/dist/lib/secret-path.d.ts.map +1 -1
  36. package/payload/platform/plugins/telegram/mcp/dist/lib/secret-path.js +19 -23
  37. package/payload/platform/plugins/telegram/mcp/dist/lib/secret-path.js.map +1 -1
  38. package/payload/platform/plugins/telegram/mcp/dist/lib/webhook-url.d.ts +9 -7
  39. package/payload/platform/plugins/telegram/mcp/dist/lib/webhook-url.d.ts.map +1 -1
  40. package/payload/platform/plugins/telegram/mcp/dist/lib/webhook-url.js +11 -8
  41. package/payload/platform/plugins/telegram/mcp/dist/lib/webhook-url.js.map +1 -1
  42. package/payload/platform/plugins/telegram/mcp/dist/tools/message.d.ts +4 -0
  43. package/payload/platform/plugins/telegram/mcp/dist/tools/message.d.ts.map +1 -1
  44. package/payload/platform/plugins/telegram/mcp/dist/tools/message.js +13 -3
  45. package/payload/platform/plugins/telegram/mcp/dist/tools/message.js.map +1 -1
  46. package/payload/platform/plugins/telegram/references/setup-guide.md +16 -8
  47. package/payload/platform/plugins/telegram/skills/configure/SKILL.md +30 -32
  48. package/payload/server/server.js +510 -240
@@ -1836,10 +1836,10 @@ var require_dist6 = __commonJS({
1836
1836
  });
1837
1837
 
1838
1838
  // server/index.ts
1839
- import { readFileSync as readFileSync47, existsSync as existsSync51, watchFile, readdirSync as readdirSync30, statSync as statSync27 } from "fs";
1839
+ import { readFileSync as readFileSync48, existsSync as existsSync52, watchFile, readdirSync as readdirSync30, statSync as statSync27 } from "fs";
1840
1840
  import { spawn as spawn3 } from "child_process";
1841
1841
  import { createHash as createHash7 } from "crypto";
1842
- import { resolve as resolve45, join as join55, basename as basename18 } from "path";
1842
+ import { resolve as resolve45, join as join56, basename as basename18 } from "path";
1843
1843
  import { homedir as homedir4 } from "os";
1844
1844
 
1845
1845
  // server/lib/route-cache-watchers.ts
@@ -3847,11 +3847,22 @@ ${result.result.text}` : result.result.text;
3847
3847
  import { statSync as statSync5 } from "fs";
3848
3848
  import { join as join10 } from "path";
3849
3849
 
3850
+ // app/lib/telegram/channel-key.ts
3851
+ function telegramChannelKey(botId, senderId) {
3852
+ return `${botId}:${senderId}`;
3853
+ }
3854
+ function parseTelegramChannelKey(key) {
3855
+ const i = key.indexOf(":");
3856
+ if (i <= 0 || i === key.length - 1) return null;
3857
+ return { botId: key.slice(0, i), senderId: key.slice(i + 1) };
3858
+ }
3859
+
3850
3860
  // app/lib/channel-pty-bridge/admin-session-id.ts
3851
3861
  import { createHash } from "crypto";
3852
- function adminSessionIdFor(accountId, senderId, personId) {
3862
+ function adminSessionIdFor(accountId, senderId, personId, botId) {
3853
3863
  const subject = personId && personId.length > 0 ? `person:${personId}` : senderId;
3854
- const b = createHash("sha256").update(`${accountId}:admin:${subject}`).digest().subarray(0, 16);
3864
+ const scope = botId && botId.length > 0 ? `admin:${botId}` : "admin";
3865
+ const b = createHash("sha256").update(`${accountId}:${scope}:${subject}`).digest().subarray(0, 16);
3855
3866
  const v = Buffer.from(b);
3856
3867
  v[6] = v[6] & 15 | 64;
3857
3868
  v[8] = v[8] & 63 | 128;
@@ -4598,7 +4609,8 @@ async function computeSessionList(scopeAccountId) {
4598
4609
  continue;
4599
4610
  }
4600
4611
  const metaPath = join9(projectDir, `${sessionId}.meta.json`);
4601
- const { bridgeIds, role, channel: sidecarChannel, senderId, personId, adminUserId, accountId: rowAccountId } = await readSidecarMetaAsync(metaPath);
4612
+ const { bridgeIds, role, channel: sidecarChannel, senderId: rawSenderId, personId, adminUserId, accountId: rowAccountId } = await readSidecarMetaAsync(metaPath);
4613
+ const senderId = sidecarChannel === "telegram" ? parseTelegramChannelKey(rawSenderId ?? "")?.senderId ?? rawSenderId : rawSenderId;
4602
4614
  if (multiAccount) {
4603
4615
  if (rowAccountId === null) untaggedCount += 1;
4604
4616
  if (rowAccountId !== scopeAccountId) continue;
@@ -4872,19 +4884,15 @@ function createScheduleInjectRoutes(deps) {
4872
4884
  console.error(`${TAG5} op=telegram-destination-ambiguous eventId=${eventId} destination=${destination} accounts=${resolution.count} dispatched=no`);
4873
4885
  return c.json({ ok: false, error: "telegram-destination-ambiguous" }, 403);
4874
4886
  }
4875
- const effectiveAccount = resolution.accountId;
4876
- console.error(`${TAG5} op=effective-account eventId=${eventId} effectiveAccount=${effectiveAccount}`);
4887
+ const { accountId: effectiveAccount, botId, botToken } = resolution;
4888
+ console.error(`${TAG5} op=effective-account eventId=${eventId} effectiveAccount=${effectiveAccount} botId=${botId}`);
4877
4889
  if (eventAccountId === null || eventAccountId !== effectiveAccount) {
4878
4890
  console.error(`${TAG5} op=event-account-scope-mismatch eventId=${eventId} eventAccount=${eventAccountId ?? "-"} effectiveAccount=${effectiveAccount} destination=${destination} dispatched=no`);
4879
4891
  return c.json({ ok: false, error: "event-account-scope-mismatch" }, 403);
4880
4892
  }
4881
- const botToken = resolution.botToken;
4882
- if (!botToken) {
4883
- console.error(`${TAG5} reject reason=no-admin-bot-token eventId=${eventId} accountId=${effectiveAccount}`);
4884
- return c.json({ ok: false, error: "no-admin-bot-token" }, 400);
4885
- }
4886
4893
  const chatId = Number(destination);
4887
- const sessionId = deps.resolveSessionOverride?.(effectiveAccount, "telegram", destination) ?? adminSessionIdFor(effectiveAccount, destination);
4894
+ const channelKey = telegramChannelKey(botId, destination);
4895
+ const sessionId = deps.resolveSessionOverride?.(effectiveAccount, "telegram", channelKey) ?? adminSessionIdFor(effectiveAccount, destination, void 0, botId);
4888
4896
  const reply = (text) => deps.sendTelegramText(botToken, chatId, text);
4889
4897
  try {
4890
4898
  await deps.tgHandleInbound({
@@ -4893,6 +4901,14 @@ function createScheduleInjectRoutes(deps) {
4893
4901
  // session scope are one account and Telegram needs no
4894
4902
  // effectiveAccountId companion field the way WhatsApp does above.
4895
4903
  accountId: effectiveAccount,
4904
+ // A scheduled dispatch is always role:'admin', and the admin branch of
4905
+ // the spawn never reads the account dir (only the public branch
4906
+ // re-checks its paired agent against it), so there is nothing to
4907
+ // resolve here.
4908
+ accountDir: "",
4909
+ botId,
4910
+ botToken,
4911
+ agentSlug: "admin",
4896
4912
  senderId: destination,
4897
4913
  chatId: destination,
4898
4914
  role: "admin",
@@ -12907,6 +12923,44 @@ import { homedir as homedir2 } from "os";
12907
12923
  import { resolve as resolve17, join as join29 } from "path";
12908
12924
  import { existsSync as existsSync21, readFileSync as readFileSync23 } from "fs";
12909
12925
 
12926
+ // app/lib/telegram/bot-registry.ts
12927
+ function botIdFromToken(token) {
12928
+ const i = token.indexOf(":");
12929
+ if (i <= 0) return null;
12930
+ const id = token.slice(0, i);
12931
+ return /^\d+$/.test(id) ? id : null;
12932
+ }
12933
+ function listBotEntries(config) {
12934
+ const raw = config?.bots;
12935
+ if (!Array.isArray(raw)) return [];
12936
+ const out = [];
12937
+ for (const e of raw) {
12938
+ if (!e || typeof e !== "object") continue;
12939
+ const { id, token, role } = e;
12940
+ if (typeof id !== "string" || id.length === 0) continue;
12941
+ if (typeof token !== "string" || token.length === 0) continue;
12942
+ if (role === "admin") {
12943
+ out.push(e);
12944
+ } else if (role === "public") {
12945
+ const agent = e.agent;
12946
+ if (typeof agent !== "string" || agent.length === 0) continue;
12947
+ out.push(e);
12948
+ }
12949
+ }
12950
+ return out;
12951
+ }
12952
+ function resolveBotEntry(accounts, botId) {
12953
+ const hits = [];
12954
+ for (const a of accounts) {
12955
+ for (const entry of listBotEntries(a.config.telegram)) {
12956
+ if (entry.id === botId) hits.push({ accountId: a.accountId, accountDir: a.accountDir, entry });
12957
+ }
12958
+ }
12959
+ if (hits.length === 0) return { kind: "none" };
12960
+ if (hits.length > 1) return { kind: "duplicate", count: hits.length };
12961
+ return { kind: "one", ...hits[0] };
12962
+ }
12963
+
12910
12964
  // app/lib/telegram/gateway/instance.ts
12911
12965
  var instance = null;
12912
12966
  function setTelegramGateway(gw) {
@@ -12918,20 +12972,20 @@ function getTelegramGateway() {
12918
12972
 
12919
12973
  // app/lib/telegram/access-control.ts
12920
12974
  function checkTelegramAccess(params) {
12921
- const { senderId, botType, config } = params;
12922
- if (botType === "admin") {
12923
- const adminUsers = config.adminUsers ?? [];
12975
+ const { senderId, entry } = params;
12976
+ if (entry.role === "admin") {
12977
+ const adminUsers = entry.adminUsers ?? [];
12924
12978
  if (adminUsers.includes(senderId)) {
12925
12979
  return { allowed: true, reason: "admin-binding", agentType: "admin" };
12926
12980
  }
12927
12981
  return { allowed: false, reason: "not-admin-user", agentType: "admin" };
12928
12982
  }
12929
- const policy = config.dmPolicy ?? "disabled";
12983
+ const policy = entry.dmPolicy ?? "disabled";
12930
12984
  switch (policy) {
12931
12985
  case "open":
12932
12986
  return { allowed: true, reason: "dm-policy-open", agentType: "public" };
12933
12987
  case "allowlist": {
12934
- const allowFrom = config.allowFrom ?? [];
12988
+ const allowFrom = entry.allowFrom ?? [];
12935
12989
  if (allowFrom.includes(senderId)) {
12936
12990
  return { allowed: true, reason: "allowlist-match", agentType: "public" };
12937
12991
  }
@@ -12953,11 +13007,11 @@ function routeTelegramUpdate(input) {
12953
13007
  if (typeof m.text !== "string" || m.text.length === 0) {
12954
13008
  return { kind: "ignore", reason: "no-text" };
12955
13009
  }
12956
- const access = checkTelegramAccess({ senderId: m.from.id, botType: input.botType, config: input.config });
13010
+ const access = checkTelegramAccess({ senderId: m.from.id, entry: input.entry });
12957
13011
  if (!access.allowed) {
12958
13012
  return { kind: "denied", reason: access.reason, agentType: access.agentType };
12959
13013
  }
12960
- return { kind: "dispatch", senderId: String(m.from.id), chatId: m.chat.id, text: m.text, agentType: access.agentType };
13014
+ return { kind: "dispatch", senderId: String(m.from.id), chatId: m.chat.id, text: m.text, agentType: access.agentType, reason: access.reason };
12961
13015
  }
12962
13016
 
12963
13017
  // server/routes/telegram.ts
@@ -12973,81 +13027,85 @@ function configDirName() {
12973
13027
  }
12974
13028
  return ".maxy";
12975
13029
  }
12976
- function secretPath(botType, accountId) {
12977
- const stem = botType === "admin" ? ".telegram-admin-webhook-secret" : ".telegram-webhook-secret";
12978
- return resolve17(homedir2(), configDirName(), `${stem}.${accountId}`);
13030
+ function telegramSecretsDir() {
13031
+ return resolve17(homedir2(), configDirName(), "telegram-secrets");
13032
+ }
13033
+ function secretPath(botId) {
13034
+ return resolve17(telegramSecretsDir(), botId);
12979
13035
  }
12980
13036
  var app10 = new Hono();
12981
13037
  app10.post("/", async (c) => {
12982
- const botParam = c.req.query("bot");
12983
- const botType = botParam === "admin" ? "admin" : botParam === "public" ? "public" : null;
12984
- if (!botType) {
13038
+ const botId = c.req.query("bot");
13039
+ if (!botId) {
12985
13040
  console.error(`${TAG19} op=reject reason=missing-bot-param`);
12986
13041
  return c.json({ ok: false }, 400);
12987
13042
  }
12988
- const accountParam = c.req.query("account");
12989
- if (!accountParam) {
12990
- console.error(`${TAG19} op=reject reason=missing-account-param botType=${botType}`);
13043
+ console.error(`${TAG19} op=received botId=${botId} path=/api/telegram/`);
13044
+ const resolved = resolveBotEntry(listValidAccounts(), botId);
13045
+ if (resolved.kind === "none") {
13046
+ console.error(`${TAG19} op=reject reason=unknown-bot botId=${botId}`);
12991
13047
  return c.json({ ok: false }, 401);
12992
13048
  }
12993
- const account = listValidAccounts().find((a) => a.accountId === accountParam) ?? null;
12994
- if (!account) {
12995
- console.error(`${TAG19} op=reject reason=bad-account botType=${botType}`);
13049
+ if (resolved.kind === "duplicate") {
13050
+ console.error(`${TAG19} op=reject reason=duplicate-bot-id botId=${botId} accounts=${resolved.count}`);
12996
13051
  return c.json({ ok: false }, 401);
12997
13052
  }
12998
- const sp = secretPath(botType, account.accountId);
13053
+ const { accountId, accountDir, entry } = resolved;
13054
+ const agentSlug = entry.role === "admin" ? "admin" : entry.agent;
13055
+ console.error(`${TAG19} op=entry botId=${botId} accountId=${accountId} role=${entry.role} agent=${agentSlug}`);
13056
+ const sp = secretPath(botId);
12999
13057
  if (!existsSync21(sp)) {
13000
- console.error(`${TAG19} op=reject reason=no-secret-file botType=${botType} accountId=${account.accountId}`);
13058
+ console.error(`${TAG19} op=secret botId=${botId} result=missing-file`);
13001
13059
  return c.json({ ok: false }, 401);
13002
13060
  }
13003
13061
  const expected = readFileSync23(sp, "utf-8").trim();
13004
13062
  const got = c.req.header("x-telegram-bot-api-secret-token") ?? "";
13005
13063
  if (got !== expected) {
13006
- console.error(`${TAG19} op=reject reason=bad-secret botType=${botType} accountId=${account.accountId}`);
13064
+ console.error(`${TAG19} op=secret botId=${botId} result=mismatch`);
13007
13065
  return c.json({ ok: false }, 401);
13008
13066
  }
13067
+ console.error(`${TAG19} op=secret botId=${botId} result=ok`);
13009
13068
  let update;
13010
13069
  try {
13011
13070
  update = await c.req.json();
13012
13071
  } catch {
13013
13072
  return c.json({ ok: true }, 200);
13014
13073
  }
13015
- const decision = routeTelegramUpdate({ update, botType, config: account.config.telegram ?? {} });
13074
+ const decision = routeTelegramUpdate({ update, entry });
13016
13075
  if (decision.kind === "ignore") {
13017
- console.error(`${TAG19} op=ignore reason=${decision.reason}`);
13076
+ console.error(`${TAG19} op=ignore botId=${botId} reason=${decision.reason}`);
13018
13077
  return c.json({ ok: true }, 200);
13019
13078
  }
13020
13079
  if (decision.kind === "denied") {
13021
- console.error(`${TAG19} op=denied reason=${decision.reason} agentType=${decision.agentType}`);
13080
+ console.error(`${TAG19} op=access botId=${botId} senderId=- allowed=false reason=${decision.reason}`);
13022
13081
  return c.json({ ok: true }, 200);
13023
13082
  }
13024
- const role = decision.agentType === "admin" ? "admin" : "public";
13025
- const agentSlug = role === "admin" ? "admin" : resolveDefaultAgentSlug(account.accountDir);
13026
- if (!agentSlug) {
13027
- console.error(`${TAG19} op=reject reason=no-default-agent`);
13083
+ const { senderId, chatId, text } = decision;
13084
+ console.error(`${TAG19} op=access botId=${botId} senderId=${senderId} allowed=true reason=${decision.reason}`);
13085
+ const active = entry.role === "admin" ? true : isActiveAgentSlug(accountDir, entry.agent);
13086
+ console.error(`${TAG19} op=agent botId=${botId} slug=${agentSlug} active=${active}`);
13087
+ if (!active) {
13088
+ console.error(`${TAG19} op=reject reason=agent-inactive botId=${botId} agent=${agentSlug}`);
13028
13089
  return c.json({ ok: true }, 200);
13029
13090
  }
13030
- console.error(`${TAG19} op=update accountId=${account.accountId} from=${decision.senderId}`);
13031
- const botToken = botType === "admin" ? account.config.telegram?.adminBotToken : account.config.telegram?.publicBotToken;
13032
- const { senderId, chatId, text } = decision;
13033
13091
  const gateway = getTelegramGateway();
13034
13092
  if (!gateway) {
13035
- console.error(`${TAG19} op=reject reason=gateway-not-ready from=${senderId}`);
13093
+ console.error(`${TAG19} op=reply-dropped botId=${botId} reason=gateway-not-ready`);
13036
13094
  return c.json({ ok: true }, 200);
13037
13095
  }
13038
13096
  const reply = async (replyText) => {
13039
- if (!botToken) {
13040
- const reason = account.config.telegram ? "no-bot-token" : "no-telegram-config";
13041
- console.error(`${TAG19} op=reply-dropped reason=${reason} botType=${botType} from=${senderId}`);
13042
- return;
13043
- }
13044
- const sent = await sendTelegramText(botToken, chatId, replyText);
13045
- console.error(`${TAG19} op=reply-sent from=${senderId} ok=${sent.ok}${sent.ok ? "" : ` error=${sent.error}`}`);
13097
+ const sent = await sendTelegramText(entry.token, chatId, replyText);
13098
+ if (sent.ok) console.error(`${TAG19} op=reply-sent botId=${botId} ok=true`);
13099
+ else console.error(`${TAG19} op=reply-dropped botId=${botId} reason=${sent.error}`);
13046
13100
  };
13047
13101
  void gateway.handleInbound({
13048
- accountId: account.accountId,
13102
+ accountId,
13103
+ accountDir,
13104
+ botId,
13105
+ botToken: entry.token,
13106
+ agentSlug,
13049
13107
  senderId,
13050
- role,
13108
+ role: entry.role,
13051
13109
  personId: null,
13052
13110
  chatId: String(chatId),
13053
13111
  text,
@@ -16927,18 +16985,18 @@ var ALLOWED_EVENTS = /* @__PURE__ */ new Set([
16927
16985
  ]);
16928
16986
  var app24 = new Hono();
16929
16987
  app24.post("/", async (c) => {
16930
- const TAG53 = "[admin:events]";
16988
+ const TAG54 = "[admin:events]";
16931
16989
  let body;
16932
16990
  try {
16933
16991
  body = await c.req.json();
16934
16992
  } catch (err) {
16935
16993
  const detail = err instanceof Error ? err.message : String(err);
16936
- console.error(`${TAG53} reject reason=body-not-json detail=${detail}`);
16994
+ console.error(`${TAG54} reject reason=body-not-json detail=${detail}`);
16937
16995
  return c.json({ ok: false, detail: "Request body was not valid JSON" }, 400);
16938
16996
  }
16939
16997
  const event = typeof body.event === "string" ? body.event : "";
16940
16998
  if (!ALLOWED_EVENTS.has(event)) {
16941
- console.error(`${TAG53} reject reason=event-not-allowed event=${JSON.stringify(event)}`);
16999
+ console.error(`${TAG54} reject reason=event-not-allowed event=${JSON.stringify(event)}`);
16942
17000
  return c.json({ ok: false, detail: `Event "${event}" is not allowed` }, 400);
16943
17001
  }
16944
17002
  const rawFields = body.fields && typeof body.fields === "object" ? body.fields : {};
@@ -22405,6 +22463,7 @@ async function managerSpawn(opts) {
22405
22463
  name: opts.name,
22406
22464
  webchatChannel: opts.webchatChannel,
22407
22465
  waChannel: opts.waChannel,
22466
+ telegramChannel: opts.telegramChannel,
22408
22467
  sessionId: opts.sessionId,
22409
22468
  previousContext: opts.previousContext,
22410
22469
  initialMessage: opts.initialMessage,
@@ -23935,8 +23994,24 @@ function readChannelAdmins(accountDir, channel) {
23935
23994
  return { adminPhones, accountManagers };
23936
23995
  }
23937
23996
  const tg = config.telegram ?? {};
23938
- const adminUsers = Array.isArray(tg.adminUsers) ? tg.adminUsers.filter((u) => typeof u === "number") : [];
23939
- return { adminUsers };
23997
+ const byBot = [];
23998
+ const seen = /* @__PURE__ */ new Set();
23999
+ const adminUsers = [];
24000
+ if (Array.isArray(tg.bots)) {
24001
+ for (const raw of tg.bots) {
24002
+ if (!raw || typeof raw !== "object") continue;
24003
+ const b = raw;
24004
+ if (b.role !== "admin" || typeof b.id !== "string" || b.id.length === 0) continue;
24005
+ const users = Array.isArray(b.adminUsers) ? b.adminUsers.filter((u) => typeof u === "number") : [];
24006
+ byBot.push({ botId: b.id, adminUsers: users });
24007
+ for (const u of users) {
24008
+ if (seen.has(u)) continue;
24009
+ seen.add(u);
24010
+ adminUsers.push(u);
24011
+ }
24012
+ }
24013
+ }
24014
+ return { adminUsers, byBot };
23940
24015
  }
23941
24016
  function registeredAccountForDestination(houseDir, houseAccountId, destination) {
23942
24017
  const { adminPhones, accountManagers } = readChannelAdmins(houseDir, "whatsapp");
@@ -23995,8 +24070,13 @@ function listDispatchDestinations(input) {
23995
24070
  admins.push({ channel: "whatsapp", destination: phone, label: "House admin" });
23996
24071
  }
23997
24072
  }
23998
- const { adminUsers } = readChannelAdmins(resolve31(accountsDir, accountId), "telegram");
23999
- const telegram = [...adminUsers].sort((a, b) => a - b).map((id) => ({ channel: "telegram", destination: String(id), label: "Telegram admin" }));
24073
+ const { byBot } = readChannelAdmins(resolve31(accountsDir, accountId), "telegram");
24074
+ const telegram = byBot.flatMap((b) => b.adminUsers.map((id) => ({ botId: b.botId, id }))).sort((a, b) => a.id - b.id || a.botId.localeCompare(b.botId)).map(({ botId, id }) => ({
24075
+ channel: "telegram",
24076
+ destination: String(id),
24077
+ label: "Telegram admin",
24078
+ botId
24079
+ }));
24000
24080
  const byDestination = (a, b) => a.destination.localeCompare(b.destination);
24001
24081
  return [...managers.sort(byDestination), ...admins.sort(byDestination), ...telegram];
24002
24082
  }
@@ -31103,19 +31183,22 @@ var TelegramGateway = class {
31103
31183
  * its own `sessionIdFor`.
31104
31184
  */
31105
31185
  sessionIdFor(input) {
31106
- return (input.role === "admin" ? this.deps.resolveSessionOverride?.(input.accountId, input.senderId) : null) || adminSessionIdFor(
31186
+ const key = telegramChannelKey(input.botId, input.senderId);
31187
+ return (input.role === "admin" ? this.deps.resolveSessionOverride?.(input.accountId, key) : null) || adminSessionIdFor(
31107
31188
  input.accountId,
31108
31189
  input.senderId,
31109
- input.role === "public" && input.personId ? input.personId : void 0
31190
+ input.role === "public" && input.personId ? input.personId : void 0,
31191
+ input.botId
31110
31192
  );
31111
31193
  }
31112
31194
  /** Handle one inbound telegram message. */
31113
31195
  async handleInbound(input) {
31114
31196
  const bytes = Buffer.byteLength(input.text, "utf8");
31115
- const hadSubscriber = this.hub.hasSubscriber(input.senderId);
31116
- const willDeliverNow = this.hub.isReady(input.senderId);
31197
+ const key = telegramChannelKey(input.botId, input.senderId);
31198
+ const hadSubscriber = this.hub.hasSubscriber(key);
31199
+ const willDeliverNow = this.hub.isReady(key);
31117
31200
  const source = input.source ?? "user";
31118
- this.replies.set(input.senderId, input.reply);
31201
+ this.replies.set(key, input.reply);
31119
31202
  let standingRules;
31120
31203
  if (input.role === "admin" && this.deps.fetchStandingRules) {
31121
31204
  const res = await this.deps.fetchStandingRules(input.accountId);
@@ -31127,19 +31210,24 @@ var TelegramGateway = class {
31127
31210
  );
31128
31211
  }
31129
31212
  this.hub.deliver(
31130
- { key: input.senderId, text: input.text, messageId: `tg-${++this.seq}`, source, standingRules, scheduleProvenance: input.scheduleProvenance },
31213
+ { key, text: input.text, messageId: `tg-${++this.seq}`, source, standingRules, scheduleProvenance: input.scheduleProvenance },
31131
31214
  Date.now()
31132
31215
  );
31133
31216
  const resolvedSessionId = this.sessionIdFor(input);
31134
31217
  console.error(
31135
- `[telegram-native] op=inbound key=${input.senderId} accountId=${input.accountId} botType=${input.role} bytes=${bytes} source=${source} delivery=${willDeliverNow ? "immediate" : "queued"} sessionId=${resolvedSessionId}`
31218
+ `[telegram-native] op=inbound key=${key} botId=${input.botId} accountId=${input.accountId} role=${input.role} agent=${input.agentSlug} bytes=${bytes} source=${source} delivery=${willDeliverNow ? "immediate" : "queued"} sessionId=${resolvedSessionId}`
31136
31219
  );
31137
- if (!hadSubscriber && !this.spawning.has(input.senderId)) {
31138
- this.spawning.add(input.senderId);
31139
- console.error(`[telegram-native] op=spawn-trigger key=${input.senderId} role=${input.role}`);
31220
+ if (!hadSubscriber && !this.spawning.has(key)) {
31221
+ this.spawning.add(key);
31222
+ console.error(`[telegram-native] op=spawn-trigger key=${key} botId=${input.botId} role=${input.role}`);
31140
31223
  try {
31141
31224
  await this.deps.ensureChannelSession({
31142
31225
  accountId: input.accountId,
31226
+ accountDir: input.accountDir,
31227
+ botId: input.botId,
31228
+ botToken: input.botToken,
31229
+ agentSlug: input.agentSlug,
31230
+ channelKey: key,
31143
31231
  senderId: input.senderId,
31144
31232
  role: input.role,
31145
31233
  personId: input.personId ?? null,
@@ -31148,7 +31236,7 @@ var TelegramGateway = class {
31148
31236
  serverPath: this.deps.serverPath
31149
31237
  });
31150
31238
  } finally {
31151
- this.spawning.delete(input.senderId);
31239
+ this.spawning.delete(key);
31152
31240
  }
31153
31241
  }
31154
31242
  }
@@ -31162,12 +31250,17 @@ var TelegramGateway = class {
31162
31250
  // app/lib/telegram/gateway/spawn-request.ts
31163
31251
  function buildTelegramSpawnRequest(input) {
31164
31252
  const personId = input.role === "public" && input.personId ? input.personId : void 0;
31253
+ const channelKey = telegramChannelKey(input.botId, input.senderId);
31165
31254
  return {
31166
31255
  // Task 1746 / 1789 — the deterministic per-sender id is the DEFAULT; an
31167
31256
  // operator re-seat sets a stable per-sender forward pointer that wins here.
31168
31257
  // Mirror of the WhatsApp builder; see its comment for the full distinction
31169
31258
  // between this pointer and Task 1526's removed per-firing seat.
31170
- sessionId: input.overrideSessionId || adminSessionIdFor(input.accountId, input.senderId, personId),
31259
+ // Task 2368 the bot id is a hash component of its own, not part of the
31260
+ // subject: with a resolved personId the subject is `person:<personId>` and
31261
+ // the sender never enters the hash at all.
31262
+ sessionId: input.overrideSessionId || adminSessionIdFor(input.accountId, input.senderId, personId, input.botId),
31263
+ channelKey,
31171
31264
  role: input.role,
31172
31265
  channel: "telegram",
31173
31266
  personId,
@@ -31177,8 +31270,78 @@ function buildTelegramSpawnRequest(input) {
31177
31270
  // session resolve under that account, not the manager's boot-time house
31178
31271
  // default. Mirror of the WhatsApp builder.
31179
31272
  targetAccountId: input.accountId,
31180
- telegramChannel: { senderId: input.senderId, gatewayUrl: input.gatewayUrl, serverPath: input.serverPath },
31181
- logContext: `channel=telegram-native role=${input.role} senderId=${input.senderId}`
31273
+ telegramChannel: { senderId: channelKey, gatewayUrl: input.gatewayUrl, serverPath: input.serverPath },
31274
+ logContext: `channel=telegram-native role=${input.role} botId=${input.botId} senderId=${input.senderId}`
31275
+ };
31276
+ }
31277
+
31278
+ // server/telegram-ensure-session.ts
31279
+ var TAG50 = "[telegram-inbound]";
31280
+ function buildTelegramEnsureSession(deps) {
31281
+ return async (input) => {
31282
+ const req = buildTelegramSpawnRequest({
31283
+ accountId: input.accountId,
31284
+ senderId: input.senderId,
31285
+ role: input.role,
31286
+ personId: input.personId,
31287
+ gatewayUrl: input.gatewayUrl,
31288
+ serverPath: input.serverPath,
31289
+ botId: input.botId,
31290
+ // Task 1789 — admin rows only; see the WhatsApp twin.
31291
+ overrideSessionId: input.role === "admin" ? deps.resolveSessionOverride(input.accountId, input.channelKey) : null
31292
+ });
31293
+ if (input.role === "public") {
31294
+ const result2 = await deps.managerSpawn({
31295
+ senderId: req.channelKey,
31296
+ role: "public",
31297
+ channel: "telegram",
31298
+ accountId: input.accountId,
31299
+ targetAccountId: input.accountId,
31300
+ // The slug came from THIS account's own bot entry, so the spawn reads
31301
+ // that account's agents directory, runs in its directory and files its
31302
+ // session under it. Mirror of the WhatsApp public-agent path.
31303
+ agentAccountId: input.accountId,
31304
+ agentSlug: input.agentSlug,
31305
+ sessionId: req.sessionId,
31306
+ personId: input.personId ?? void 0,
31307
+ telegramChannel: req.telegramChannel
31308
+ });
31309
+ if ("error" in result2) {
31310
+ console.error(
31311
+ `[telegram-native] op=spawn-failed botId=${input.botId} key=${req.channelKey} role=public agent=${input.agentSlug} error=${result2.error} status=${result2.status}`
31312
+ );
31313
+ return;
31314
+ }
31315
+ console.error(
31316
+ `${TAG50} op=spawn botId=${input.botId} sessionId=${result2.sessionId} targetAccountId=${input.accountId} role=public`
31317
+ );
31318
+ return;
31319
+ }
31320
+ const result = await deps.managerRcSpawn(req);
31321
+ if ("error" in result) {
31322
+ console.error(
31323
+ `[telegram-native] op=spawn-failed botId=${input.botId} key=${req.channelKey} role=admin error=${result.error} status=${result.status}`
31324
+ );
31325
+ return;
31326
+ }
31327
+ console.error(
31328
+ `${TAG50} op=spawn botId=${input.botId} sessionId=${req.sessionId} targetAccountId=${input.accountId} role=admin`
31329
+ );
31330
+ deps.followers.get(req.channelKey)?.abort();
31331
+ let ac;
31332
+ ac = deps.startFileFollower({
31333
+ sessionId: req.sessionId,
31334
+ senderId: req.channelKey,
31335
+ accountId: input.accountId,
31336
+ chatId: input.chatId,
31337
+ botToken: input.botToken,
31338
+ // Identity-guarded eviction: only delete if this controller is still the
31339
+ // current one, so a stale self-close never removes a live follower.
31340
+ onClose: () => {
31341
+ if (deps.followers.get(req.channelKey) === ac) deps.followers.delete(req.channelKey);
31342
+ }
31343
+ });
31344
+ deps.followers.set(req.channelKey, ac);
31182
31345
  };
31183
31346
  }
31184
31347
 
@@ -31186,7 +31349,7 @@ function buildTelegramSpawnRequest(input) {
31186
31349
  import { realpathSync as realpathSync10 } from "fs";
31187
31350
  import { readFile as readFile14, stat as stat10 } from "fs/promises";
31188
31351
  import { resolve as resolve42, basename as basename16 } from "path";
31189
- var TAG50 = "[telegram:outbound]";
31352
+ var TAG51 = "[telegram:outbound]";
31190
31353
  var TELEGRAM_DOCUMENT_MAX_BYTES = 50 * 1024 * 1024;
31191
31354
  async function sendTelegramDocument(input) {
31192
31355
  const { botToken, chatId, filePath, caption, maxyAccountId, platformRoot: platformRoot5 } = input;
@@ -31203,10 +31366,10 @@ async function sendTelegramDocument(input) {
31203
31366
  } catch (err) {
31204
31367
  const code = err.code;
31205
31368
  if (code === "ENOENT") {
31206
- console.error(`${TAG50} document ENOENT scope=account-dir accountDir=${accountDir} maxyAccountId=${maxyAccountId}`);
31369
+ console.error(`${TAG51} document ENOENT scope=account-dir accountDir=${accountDir} maxyAccountId=${maxyAccountId}`);
31207
31370
  return { ok: false, status: 500, error: `Account directory not resolvable for ${maxyAccountId}` };
31208
31371
  }
31209
- console.error(`${TAG50} document path error scope=account-dir maxyAccountId=${maxyAccountId}: ${String(err)}`);
31372
+ console.error(`${TAG51} document path error scope=account-dir maxyAccountId=${maxyAccountId}: ${String(err)}`);
31210
31373
  return { ok: false, status: 500, error: String(err) };
31211
31374
  }
31212
31375
  let resolvedPath;
@@ -31215,14 +31378,14 @@ async function sendTelegramDocument(input) {
31215
31378
  } catch (err) {
31216
31379
  const code = err.code;
31217
31380
  if (code === "ENOENT") {
31218
- console.error(`${TAG50} document ENOENT scope=file path=${filePath} maxyAccountId=${maxyAccountId}`);
31381
+ console.error(`${TAG51} document ENOENT scope=file path=${filePath} maxyAccountId=${maxyAccountId}`);
31219
31382
  return { ok: false, status: 404, error: `File not found: ${filePath}` };
31220
31383
  }
31221
- console.error(`${TAG50} document path error scope=file maxyAccountId=${maxyAccountId}: ${String(err)}`);
31384
+ console.error(`${TAG51} document path error scope=file maxyAccountId=${maxyAccountId}: ${String(err)}`);
31222
31385
  return { ok: false, status: 500, error: String(err) };
31223
31386
  }
31224
31387
  if (!resolvedPath.startsWith(accountResolved + "/")) {
31225
- console.error(`${TAG50} document REJECTED reason=outside_account_directory maxyAccountId=${maxyAccountId}`);
31388
+ console.error(`${TAG51} document REJECTED reason=outside_account_directory maxyAccountId=${maxyAccountId}`);
31226
31389
  return { ok: false, status: 403, error: "Access denied: file is outside the account directory" };
31227
31390
  }
31228
31391
  const fileStat = await stat10(resolvedPath);
@@ -31255,29 +31418,24 @@ async function sendTelegramDocument(input) {
31255
31418
  error = e instanceof Error ? e.message : String(e);
31256
31419
  }
31257
31420
  console.error(
31258
- `${TAG50} sent document to=${chatId} file=${filename} bytes=${fileStat.size} ok=${ok2}` + (messageId !== void 0 ? ` id=${messageId}` : "")
31421
+ `${TAG51} sent document to=${chatId} file=${filename} bytes=${fileStat.size} ok=${ok2}` + (messageId !== void 0 ? ` id=${messageId}` : "")
31259
31422
  );
31260
31423
  return ok2 ? { ok: true, messageId } : { ok: false, status: 500, error };
31261
31424
  }
31262
31425
 
31263
31426
  // app/lib/telegram/outbound/file-delivery.ts
31264
- var TAG51 = "[telegram:outbound]";
31427
+ var TAG52 = "[telegram:outbound]";
31265
31428
  function platformRoot4() {
31266
31429
  return process.env.MAXY_PLATFORM_ROOT || "";
31267
31430
  }
31268
- function makeTelegramSendFile(entry) {
31269
- let botToken;
31431
+ function makeTelegramSendFile(entry, botToken) {
31270
31432
  return async (filePath, caption) => {
31271
- if (botToken === void 0) {
31272
- const tg = resolveAccount()?.config.telegram;
31273
- botToken = (entry.role === "admin" ? tg?.adminBotToken : tg?.publicBotToken) ?? null;
31274
- }
31275
31433
  if (!botToken) {
31276
- console.error(`${TAG51} file-delivery reject reason=no-bot-token sender=${entry.senderId} role=${entry.role}`);
31434
+ console.error(`${TAG52} file-delivery reject reason=no-bot-token sender=${entry.senderId} role=${entry.role}`);
31277
31435
  return { ok: false, error: "no-bot-token" };
31278
31436
  }
31279
31437
  if (entry.replyTarget == null) {
31280
- console.error(`${TAG51} file-delivery reject reason=no-reply-target sender=${entry.senderId} role=${entry.role}`);
31438
+ console.error(`${TAG52} file-delivery reject reason=no-reply-target sender=${entry.senderId} role=${entry.role}`);
31281
31439
  return { ok: false, error: "no-reply-target" };
31282
31440
  }
31283
31441
  const result = await sendTelegramDocument({
@@ -31291,12 +31449,12 @@ function makeTelegramSendFile(entry) {
31291
31449
  return result.ok ? { ok: true } : { ok: false, error: result.error };
31292
31450
  };
31293
31451
  }
31294
- function makeTelegramFileDelivery(entry) {
31452
+ function makeTelegramFileDelivery(entry, botToken) {
31295
31453
  return makeFileDelivery({
31296
31454
  entry,
31297
- tag: TAG51,
31455
+ tag: TAG52,
31298
31456
  channel: "telegram",
31299
- sendFile: makeTelegramSendFile(entry)
31457
+ sendFile: makeTelegramSendFile(entry, botToken)
31300
31458
  });
31301
31459
  }
31302
31460
 
@@ -31324,7 +31482,7 @@ function startTelegramNativeFileFollower(input) {
31324
31482
  return startFollower({
31325
31483
  entry,
31326
31484
  tag: "[telegram-adaptor]",
31327
- fileDelivery: makeTelegramFileDelivery(entry),
31485
+ fileDelivery: makeTelegramFileDelivery(entry, input.botToken),
31328
31486
  // A resumed session's JSONL already holds prior SendUserFile tool_uses;
31329
31487
  // suppress replay so historical files are not re-sent on attach.
31330
31488
  suppressResumeReplay: true,
@@ -31335,7 +31493,7 @@ function startTelegramNativeFileFollower(input) {
31335
31493
 
31336
31494
  // app/lib/channel-pty-bridge/public-session-end-review.ts
31337
31495
  import { randomUUID as randomUUID14 } from "crypto";
31338
- var TAG52 = "[public-session-review]";
31496
+ var TAG53 = "[public-session-review]";
31339
31497
  var CONSUMED_CAP = 500;
31340
31498
  var consumed = /* @__PURE__ */ new Set();
31341
31499
  function consumeOnce(sessionId) {
@@ -31362,7 +31520,7 @@ async function fetchJsonl(sessionId) {
31362
31520
  return await res.text();
31363
31521
  } catch (err) {
31364
31522
  console.error(
31365
- `${TAG52} jsonl-fetch-failed sessionId=${sessionId} err="${err instanceof Error ? err.message : String(err)}"`
31523
+ `${TAG53} jsonl-fetch-failed sessionId=${sessionId} err="${err instanceof Error ? err.message : String(err)}"`
31366
31524
  );
31367
31525
  return null;
31368
31526
  }
@@ -31386,7 +31544,7 @@ async function fetchPriorWrites(sliceToken, accountId) {
31386
31544
  const res = await fetch(url);
31387
31545
  if (!res.ok) {
31388
31546
  console.error(
31389
- `${TAG52} prior-writes-fetch-failed sliceToken=${sliceToken.slice(0, 8)} status=${res.status}`
31547
+ `${TAG53} prior-writes-fetch-failed sliceToken=${sliceToken.slice(0, 8)} status=${res.status}`
31390
31548
  );
31391
31549
  return [];
31392
31550
  }
@@ -31394,7 +31552,7 @@ async function fetchPriorWrites(sliceToken, accountId) {
31394
31552
  return Array.isArray(body.writes) ? body.writes : [];
31395
31553
  } catch (err) {
31396
31554
  console.error(
31397
- `${TAG52} prior-writes-fetch-threw sliceToken=${sliceToken.slice(0, 8)} err="${err instanceof Error ? err.message : String(err)}"`
31555
+ `${TAG53} prior-writes-fetch-threw sliceToken=${sliceToken.slice(0, 8)} err="${err instanceof Error ? err.message : String(err)}"`
31398
31556
  );
31399
31557
  return [];
31400
31558
  }
@@ -31423,7 +31581,7 @@ function composeInitialMessage(input) {
31423
31581
  }
31424
31582
  async function dispatchReviewer(input, initialMessage) {
31425
31583
  const sessionId = randomUUID14();
31426
- console.log(`${TAG52} route target=rc-spawn sessionId=${sessionId.slice(0, 8)} sourceSession=${input.sessionId.slice(0, 8)}`);
31584
+ console.log(`${TAG53} route target=rc-spawn sessionId=${sessionId.slice(0, 8)} sourceSession=${input.sessionId.slice(0, 8)}`);
31427
31585
  const spawned = await managerRcSpawn({
31428
31586
  sessionId,
31429
31587
  initialMessage,
@@ -31447,21 +31605,21 @@ async function firePublicSessionEndReview(input) {
31447
31605
  const dispatchedAt = Date.now();
31448
31606
  if (consumed.has(input.sessionId)) {
31449
31607
  console.log(
31450
- `${TAG52} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-already-reviewed`
31608
+ `${TAG53} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-already-reviewed`
31451
31609
  );
31452
31610
  return;
31453
31611
  }
31454
31612
  const jsonl = await fetchJsonl(input.sessionId);
31455
31613
  if (!jsonl) {
31456
31614
  console.log(
31457
- `${TAG52} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-jsonl-missing`
31615
+ `${TAG53} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-jsonl-missing`
31458
31616
  );
31459
31617
  return;
31460
31618
  }
31461
31619
  const operatorTurns = countOperatorTurns(jsonl);
31462
31620
  if (operatorTurns === 0) {
31463
31621
  console.log(
31464
- `${TAG52} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-no-turns`
31622
+ `${TAG53} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-no-turns`
31465
31623
  );
31466
31624
  return;
31467
31625
  }
@@ -31475,19 +31633,19 @@ async function firePublicSessionEndReview(input) {
31475
31633
  });
31476
31634
  if (!consumeOnce(input.sessionId)) {
31477
31635
  console.log(
31478
- `${TAG52} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-already-reviewed`
31636
+ `${TAG53} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-already-reviewed`
31479
31637
  );
31480
31638
  return;
31481
31639
  }
31482
31640
  const dispatched = await dispatchReviewer(input, initialMessage);
31483
31641
  if (!dispatched.ok) {
31484
31642
  console.error(
31485
- `${TAG52} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-spawn-failed reason=${dispatched.reason}`
31643
+ `${TAG53} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-spawn-failed reason=${dispatched.reason}`
31486
31644
  );
31487
31645
  return;
31488
31646
  }
31489
31647
  console.log(
31490
- `${TAG52} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=dispatched managerSessionId=${dispatched.managerSessionId} operatorTurns=${operatorTurns} priorWrites=${priorWrites.length} ms=${Date.now() - dispatchedAt}`
31648
+ `${TAG53} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=dispatched managerSessionId=${dispatched.managerSessionId} operatorTurns=${operatorTurns} priorWrites=${priorWrites.length} ms=${Date.now() - dispatchedAt}`
31491
31649
  );
31492
31650
  }
31493
31651
 
@@ -31625,28 +31783,59 @@ import { statSync as statSync25 } from "fs";
31625
31783
  import { basename as basename17, dirname as dirname17, join as join54 } from "path";
31626
31784
  function buildTelegramAuditLines(input) {
31627
31785
  const lines = [];
31628
- for (const account of input.accounts) {
31629
- const tg = account.telegram;
31630
- if (!tg) continue;
31631
- const botTypes = [];
31632
- if (tg.adminBotToken) botTypes.push("admin");
31633
- if (tg.publicBotToken) botTypes.push("public");
31634
- if (botTypes.length === 0) continue;
31635
- const last = input.lastInboundAt(account.accountId);
31636
- const age = last === null ? "never" : String(input.now - last);
31637
- for (const botType of botTypes) {
31638
- const secretFile = input.secretFileExists(botType, account.accountId) ? "present" : "absent";
31786
+ const entries2 = input.accounts.flatMap(
31787
+ (a) => listBotEntries(a.telegram).map((entry) => ({ accountId: a.accountId, accountDir: a.accountDir, entry }))
31788
+ );
31789
+ if (entries2.length === 0) return lines;
31790
+ const byId = /* @__PURE__ */ new Map();
31791
+ for (const { entry } of entries2) byId.set(entry.id, (byId.get(entry.id) ?? 0) + 1);
31792
+ for (const [botId, count] of byId) {
31793
+ if (count > 1) lines.push(`[telegram-audit] op=duplicate-bot-id botId=${botId} accounts=${count}`);
31794
+ }
31795
+ for (const { accountId, accountDir, entry } of entries2) {
31796
+ if (entry.role === "public" && !input.agentActive(accountDir, entry.agent)) {
31639
31797
  lines.push(
31640
- `[telegram-audit] op=configured accountId=${account.accountId} botType=${botType} secretFile=${secretFile} lastInboundAgeMs=${age}`
31798
+ `[telegram-audit] op=entry-agent-inactive accountId=${accountId} botId=${entry.id} agent=${entry.agent}`
31641
31799
  );
31642
31800
  }
31801
+ if (!input.secretFileExists(entry.id)) {
31802
+ lines.push(`[telegram-audit] op=secret-orphan botId=${entry.id} side=entry`);
31803
+ }
31804
+ }
31805
+ const known = new Set(entries2.map((e) => e.entry.id));
31806
+ for (const botId of input.secretFilesOnDisk) {
31807
+ if (!known.has(botId)) lines.push(`[telegram-audit] op=secret-orphan botId=${botId} side=file`);
31808
+ }
31809
+ for (const { accountId, entry } of entries2) {
31810
+ const last = input.lastInboundByBot(entry.id);
31811
+ const age = last === null ? "never" : String(input.now - last);
31812
+ const agent = entry.role === "admin" ? "admin" : entry.agent;
31813
+ const secretFile = input.secretFileExists(entry.id) ? "present" : "absent";
31814
+ lines.push(
31815
+ `[telegram-audit] op=configured botId=${entry.id} accountId=${accountId} agent=${agent} secretFile=${secretFile} lastInboundAgeMs=${age}`
31816
+ );
31643
31817
  }
31644
31818
  return lines;
31645
31819
  }
31646
- function newestTelegramSessionByAccount() {
31820
+ function newestTelegramSessionByBot() {
31647
31821
  const out = /* @__PURE__ */ new Map();
31822
+ for (const { botId, mtime } of walkTelegramSidecars()) {
31823
+ if (botId === null) continue;
31824
+ const prev = out.get(botId);
31825
+ if (prev === void 0 || mtime > prev) out.set(botId, mtime);
31826
+ }
31827
+ return out;
31828
+ }
31829
+ function countTelegramSessionsForAccount(accountId) {
31830
+ let n = 0;
31831
+ for (const { accountId: id } of walkTelegramSidecars()) {
31832
+ if (id === accountId) n += 1;
31833
+ }
31834
+ return n;
31835
+ }
31836
+ function* walkTelegramSidecars() {
31648
31837
  const cfg = claudeConfigDir();
31649
- if (!cfg) return out;
31838
+ if (!cfg) return;
31650
31839
  const projectsRoot = join54(cfg, "projects");
31651
31840
  for (const { path } of enumerateJsonls(projectsRoot)) {
31652
31841
  const sessionId = basename17(path).replace(/\.jsonl$/, "");
@@ -31658,11 +31847,80 @@ function newestTelegramSessionByAccount() {
31658
31847
  } catch {
31659
31848
  continue;
31660
31849
  }
31661
- const prev = out.get(meta.accountId);
31662
- if (prev === void 0 || mtime > prev) out.set(meta.accountId, mtime);
31850
+ yield {
31851
+ accountId: meta.accountId,
31852
+ botId: parseTelegramChannelKey(meta.senderId ?? "")?.botId ?? null,
31853
+ mtime
31854
+ };
31855
+ }
31856
+ }
31857
+
31858
+ // server/telegram-migrate.ts
31859
+ import { readFileSync as readFileSync45, writeFileSync as writeFileSync17, existsSync as existsSync49, renameSync as renameSync11, mkdirSync as mkdirSync10 } from "fs";
31860
+ import { dirname as dirname18, join as join55 } from "path";
31861
+ function migrateTelegramTwoSlot(input) {
31862
+ const out = [];
31863
+ for (const account of input.accounts) {
31864
+ const configPath3 = join55(account.accountDir, "account.json");
31865
+ let config;
31866
+ try {
31867
+ config = JSON.parse(readFileSync45(configPath3, "utf-8"));
31868
+ } catch {
31869
+ console.error(`[telegram-audit] op=migrate-unreadable accountId=${account.accountId}`);
31870
+ continue;
31871
+ }
31872
+ const tg = config.telegram;
31873
+ if (!tg) continue;
31874
+ const adminToken = typeof tg.adminBotToken === "string" ? tg.adminBotToken : null;
31875
+ const publicToken = typeof tg.publicBotToken === "string" ? tg.publicBotToken : null;
31876
+ if (!adminToken && !publicToken) continue;
31877
+ const outcome = { accountId: account.accountId, converted: 0, orphanSessions: 0 };
31878
+ const bots = Array.isArray(tg.bots) ? [...tg.bots] : [];
31879
+ if (adminToken) {
31880
+ const id = botIdFromToken(adminToken);
31881
+ if (!id) {
31882
+ outcome.skippedAdmin = "malformed-token";
31883
+ } else {
31884
+ const adminUsers = Array.isArray(tg.adminUsers) ? tg.adminUsers.filter((u) => typeof u === "number") : void 0;
31885
+ bots.push({ id, token: adminToken, role: "admin", ...adminUsers ? { adminUsers } : {} });
31886
+ moveSecret(input.legacySecretPathFor("admin", account.accountId), input.secretPathFor(id));
31887
+ outcome.converted += 1;
31888
+ }
31889
+ }
31890
+ if (publicToken) {
31891
+ const id = botIdFromToken(publicToken);
31892
+ const agent = typeof config.defaultAgent === "string" && config.defaultAgent.length > 0 ? config.defaultAgent : null;
31893
+ if (!id) {
31894
+ outcome.skippedPublic = "malformed-token";
31895
+ } else if (!agent) {
31896
+ outcome.skippedPublic = "no-default-agent";
31897
+ } else {
31898
+ const dmPolicy = typeof tg.dmPolicy === "string" ? tg.dmPolicy : void 0;
31899
+ const allowFrom = Array.isArray(tg.allowFrom) ? tg.allowFrom.filter((u) => typeof u === "number") : void 0;
31900
+ bots.push({
31901
+ id,
31902
+ token: publicToken,
31903
+ role: "public",
31904
+ agent,
31905
+ ...dmPolicy ? { dmPolicy } : {},
31906
+ ...allowFrom ? { allowFrom } : {}
31907
+ });
31908
+ moveSecret(input.legacySecretPathFor("public", account.accountId), input.secretPathFor(id));
31909
+ outcome.converted += 1;
31910
+ }
31911
+ }
31912
+ config.telegram = { bots };
31913
+ writeFileSync17(configPath3, JSON.stringify(config, null, 2) + "\n", "utf-8");
31914
+ outcome.orphanSessions = input.countOrphanSessions(account.accountId);
31915
+ out.push(outcome);
31663
31916
  }
31664
31917
  return out;
31665
31918
  }
31919
+ function moveSecret(from, to) {
31920
+ if (!existsSync49(from)) return;
31921
+ mkdirSync10(dirname18(to), { recursive: true, mode: 448 });
31922
+ renameSync11(from, to);
31923
+ }
31666
31924
 
31667
31925
  // app/lib/whatsapp/recall-store-backfill.ts
31668
31926
  function backfillMisfiledRecords(params) {
@@ -31700,14 +31958,14 @@ function backfillMisfiledRecords(params) {
31700
31958
  }
31701
31959
 
31702
31960
  // server/lib/booking-site-accounts.ts
31703
- import { existsSync as existsSync49, readFileSync as readFileSync45 } from "fs";
31961
+ import { existsSync as existsSync50, readFileSync as readFileSync46 } from "fs";
31704
31962
  import { resolve as resolve43 } from "path";
31705
31963
  function listBookingSiteAccounts(accounts) {
31706
31964
  return accounts.filter((a) => {
31707
31965
  const availPath = resolve43(a.accountDir, "calendar-availability.json");
31708
- if (!existsSync49(availPath)) return false;
31966
+ if (!existsSync50(availPath)) return false;
31709
31967
  try {
31710
- const cfg = JSON.parse(readFileSync45(availPath, "utf-8"));
31968
+ const cfg = JSON.parse(readFileSync46(availPath, "utf-8"));
31711
31969
  return typeof cfg.bookingDbName === "string" && cfg.bookingDbName.length > 0;
31712
31970
  } catch {
31713
31971
  return false;
@@ -31745,7 +32003,7 @@ function broadcastAdminShutdown(reason) {
31745
32003
 
31746
32004
  // ../lib/entitlement/src/index.ts
31747
32005
  import { createPublicKey, createHash as createHash6, verify as cryptoVerify } from "crypto";
31748
- import { existsSync as existsSync50, readFileSync as readFileSync46, statSync as statSync26 } from "fs";
32006
+ import { existsSync as existsSync51, readFileSync as readFileSync47, statSync as statSync26 } from "fs";
31749
32007
  import { resolve as resolve44 } from "path";
31750
32008
 
31751
32009
  // ../lib/entitlement/src/canonicalize.ts
@@ -31794,7 +32052,7 @@ function resolveEntitlement(brand, account) {
31794
32052
  return logResolved(implicitTrust(account), null);
31795
32053
  }
31796
32054
  const entitlementPath = resolve44(brand.configDir, "entitlement.json");
31797
- if (!existsSync50(entitlementPath)) {
32055
+ if (!existsSync51(entitlementPath)) {
31798
32056
  return logResolved(anonymousFallback("missing"), { reason: "missing" });
31799
32057
  }
31800
32058
  const stat11 = statSync26(entitlementPath);
@@ -31809,7 +32067,7 @@ function resolveEntitlement(brand, account) {
31809
32067
  function verifyAndResolve(brand, entitlementPath, account) {
31810
32068
  let pubkeyPem;
31811
32069
  try {
31812
- pubkeyPem = readFileSync46(pubkeyPath(brand), "utf-8");
32070
+ pubkeyPem = readFileSync47(pubkeyPath(brand), "utf-8");
31813
32071
  } catch (err) {
31814
32072
  return logResolved(anonymousFallback("pubkey-missing"), {
31815
32073
  reason: "pubkey-missing"
@@ -31823,7 +32081,7 @@ function verifyAndResolve(brand, entitlementPath, account) {
31823
32081
  }
31824
32082
  let envelope;
31825
32083
  try {
31826
- envelope = JSON.parse(readFileSync46(entitlementPath, "utf-8"));
32084
+ envelope = JSON.parse(readFileSync47(entitlementPath, "utf-8"));
31827
32085
  } catch {
31828
32086
  return logResolved(anonymousFallback("malformed"), { reason: "malformed" });
31829
32087
  }
@@ -31985,14 +32243,14 @@ function clientFrom(c) {
31985
32243
  }
31986
32244
  installMediaDownloadGuard();
31987
32245
  var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT || "";
31988
- var BRAND_JSON_PATH = PLATFORM_ROOT6 ? join55(PLATFORM_ROOT6, "config", "brand.json") : "";
32246
+ var BRAND_JSON_PATH = PLATFORM_ROOT6 ? join56(PLATFORM_ROOT6, "config", "brand.json") : "";
31989
32247
  var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", marketingUrl: "https://getmaxy.com" };
31990
- if (BRAND_JSON_PATH && !existsSync51(BRAND_JSON_PATH)) {
32248
+ if (BRAND_JSON_PATH && !existsSync52(BRAND_JSON_PATH)) {
31991
32249
  console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);
31992
32250
  }
31993
- if (BRAND_JSON_PATH && existsSync51(BRAND_JSON_PATH)) {
32251
+ if (BRAND_JSON_PATH && existsSync52(BRAND_JSON_PATH)) {
31994
32252
  try {
31995
- const parsed = JSON.parse(readFileSync47(BRAND_JSON_PATH, "utf-8"));
32253
+ const parsed = JSON.parse(readFileSync48(BRAND_JSON_PATH, "utf-8"));
31996
32254
  BRAND = { ...BRAND, ...parsed };
31997
32255
  } catch (err) {
31998
32256
  console.error(`[brand] Failed to parse brand.json: ${err.message}`);
@@ -32024,11 +32282,11 @@ var brandLoginOpts = {
32024
32282
  appleTouchIconPath: brandAppIcon512Path,
32025
32283
  themeColor: BRAND.defaultColors?.primary
32026
32284
  };
32027
- var ALIAS_DOMAINS_PATH = join55(homedir4(), BRAND.configDir, "alias-domains.json");
32285
+ var ALIAS_DOMAINS_PATH = join56(homedir4(), BRAND.configDir, "alias-domains.json");
32028
32286
  function loadAliasDomains() {
32029
32287
  try {
32030
- if (!existsSync51(ALIAS_DOMAINS_PATH)) return null;
32031
- const parsed = JSON.parse(readFileSync47(ALIAS_DOMAINS_PATH, "utf-8"));
32288
+ if (!existsSync52(ALIAS_DOMAINS_PATH)) return null;
32289
+ const parsed = JSON.parse(readFileSync48(ALIAS_DOMAINS_PATH, "utf-8"));
32032
32290
  if (!Array.isArray(parsed)) {
32033
32291
  console.error("[alias-domains] malformed alias-domains.json \u2014 expected array");
32034
32292
  return null;
@@ -32119,7 +32377,7 @@ var waGateway = new WaGateway({
32119
32377
  sendToParty: (accountId, phone, text) => sendWhatsAppTextForRelay(accountId, phone, text),
32120
32378
  resolveSessionOverride: (accountId, senderId) => resolveChannelOverride(accountId, "whatsapp", senderId),
32121
32379
  gatewayUrl: `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`,
32122
- serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve45(process.env.MAXY_PLATFORM_ROOT ?? join55(__dirname, ".."), "services/whatsapp-channel/dist/server.js"),
32380
+ serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve45(process.env.MAXY_PLATFORM_ROOT ?? join56(__dirname, ".."), "services/whatsapp-channel/dist/server.js"),
32123
32381
  // Task 751 / 1390 — file delivery on the native channel. `maxyAccountId` (the
32124
32382
  // path-validation scope) is the sender's effective SESSION account, resolved
32125
32383
  // once at the inbound gate and threaded here via the gateway's per-sender doc
@@ -32134,7 +32392,7 @@ var waGateway = new WaGateway({
32134
32392
  caption,
32135
32393
  accountId,
32136
32394
  maxyAccountId,
32137
- platformRoot: resolve45(process.env.MAXY_PLATFORM_ROOT ?? join55(__dirname, ".."))
32395
+ platformRoot: resolve45(process.env.MAXY_PLATFORM_ROOT ?? join56(__dirname, ".."))
32138
32396
  });
32139
32397
  return result.ok ? { ok: true, messageId: result.messageId } : { ok: false, error: result.error };
32140
32398
  },
@@ -32301,7 +32559,7 @@ function runDuplicateSenderAudit() {
32301
32559
  try {
32302
32560
  const configDir = process.env.CLAUDE_CONFIG_DIR;
32303
32561
  if (!configDir) return;
32304
- const projectsRoot = join55(configDir, "projects");
32562
+ const projectsRoot = join56(configDir, "projects");
32305
32563
  const records = [];
32306
32564
  for (const { path: jsonlPath, isSubagent, archived } of enumerateJsonls(projectsRoot)) {
32307
32565
  if (isSubagent || archived) continue;
@@ -32442,42 +32700,22 @@ var telegramGateway = new TelegramGateway({
32442
32700
  // Task 2353 — no turn-instruction resolver on this door. See the dep comment in
32443
32701
  // telegram-gateway.ts: the account file governs the relay turn, which only the
32444
32702
  // WhatsApp door raises.
32445
- resolveSessionOverride: (accountId, senderId) => resolveChannelOverride(accountId, "telegram", senderId),
32703
+ // Task 2368 the pointer keys on the `<botId>:<senderId>` channel key, which
32704
+ // is what the classification sidecar records (the manager reads
32705
+ // telegramChannel.senderId) and therefore what session-reseat.ts writes.
32706
+ resolveSessionOverride: (accountId, channelKey) => resolveChannelOverride(accountId, "telegram", channelKey),
32446
32707
  gatewayUrl: telegramGatewayUrl(),
32447
32708
  serverPath: telegramServerPath(),
32448
- ensureChannelSession: async ({ accountId, senderId, role, personId, chatId, gatewayUrl, serverPath }) => {
32449
- const req = buildTelegramSpawnRequest({
32450
- accountId,
32451
- senderId,
32452
- role,
32453
- personId,
32454
- gatewayUrl,
32455
- serverPath,
32456
- // Task 1789 — admin rows only; see the WhatsApp twin.
32457
- overrideSessionId: role === "admin" ? resolveChannelOverride(accountId, "telegram", senderId) : null
32458
- });
32459
- const result = await managerRcSpawn(req);
32460
- if ("error" in result) {
32461
- console.error(`[telegram-native] spawn-failed senderId=${senderId} role=${role} error=${result.error} status=${result.status}`);
32462
- return;
32463
- }
32464
- if (role !== "admin") return;
32465
- const prior = telegramFileFollowers.get(senderId);
32466
- if (prior) prior.abort();
32467
- let ac;
32468
- ac = startTelegramNativeFileFollower({
32469
- sessionId: req.sessionId,
32470
- senderId,
32471
- accountId,
32472
- chatId,
32473
- // Identity-guarded eviction: only delete if this controller is still the
32474
- // current one, so a stale self-close never removes a live follower.
32475
- onClose: () => {
32476
- if (telegramFileFollowers.get(senderId) === ac) telegramFileFollowers.delete(senderId);
32477
- }
32478
- });
32479
- telegramFileFollowers.set(senderId, ac);
32480
- }
32709
+ // Task 2368 the role branch lives in its own module so it has a test that
32710
+ // needs no live manager. An admin bot keeps /rc-spawn; a public bot goes to
32711
+ // /public-spawn, the only door that accepts an agent slug.
32712
+ ensureChannelSession: buildTelegramEnsureSession({
32713
+ managerRcSpawn,
32714
+ managerSpawn,
32715
+ resolveSessionOverride: (accountId, channelKey) => resolveChannelOverride(accountId, "telegram", channelKey),
32716
+ startFileFollower: startTelegramNativeFileFollower,
32717
+ followers: telegramFileFollowers
32718
+ })
32481
32719
  });
32482
32720
  setTelegramGateway(telegramGateway);
32483
32721
  app76.route("/", telegramGateway.routes());
@@ -32519,24 +32757,29 @@ var scheduleInjectRoutes = createScheduleInjectRoutes({
32519
32757
  // effective-account.ts for why `resolveAccount()` was the wrong side to move,
32520
32758
  // and why the real dependencies are bound there rather than assembled here.
32521
32759
  effectiveAccountFor: resolveScheduledEffectiveAccountForInstall,
32522
- // Task 2365the account whose own telegram.adminUsers claims this chat id.
32523
- // Reads the same list, through the same readChannelAdmins call, that
32760
+ // Task 2368which BOT claims this chat id, across every account. Reads the
32761
+ // same per-bot split, through the same readChannelAdmins call, that
32524
32762
  // validateAgentDestination uses (agent-dispatch-rule), so the routine picker,
32525
- // the save and this dispatch cannot disagree. Two claimants is reported to
32526
- // the route and refused there, never picked between.
32763
+ // the save and this dispatch cannot disagree. Two claimants is reported to the
32764
+ // route and refused there, never picked between — and two bots in ONE account
32765
+ // is as ambiguous as two accounts, because the dispatch has no basis to choose
32766
+ // which bot the person hears from.
32527
32767
  telegramAccountForDestination: (destination) => {
32528
32768
  const asNumber = Number(destination);
32529
32769
  if (!Number.isInteger(asNumber)) return { kind: "none" };
32530
- const claimants = listValidAccounts().filter(
32531
- (a) => readChannelAdmins(a.accountDir, "telegram").adminUsers.includes(asNumber)
32532
- );
32533
- if (claimants.length === 0) return { kind: "none" };
32534
- if (claimants.length > 1) return { kind: "ambiguous", count: claimants.length };
32535
- return {
32536
- kind: "one",
32537
- accountId: claimants[0].accountId,
32538
- botToken: claimants[0].config.telegram?.adminBotToken ?? null
32539
- };
32770
+ const hits = [];
32771
+ for (const a of listValidAccounts()) {
32772
+ const entries2 = listBotEntries(a.config.telegram);
32773
+ for (const bot of readChannelAdmins(a.accountDir, "telegram").byBot) {
32774
+ if (!bot.adminUsers.includes(asNumber)) continue;
32775
+ const entry = entries2.find((e) => e.id === bot.botId);
32776
+ if (!entry) continue;
32777
+ hits.push({ accountId: a.accountId, botId: bot.botId, botToken: entry.token });
32778
+ }
32779
+ }
32780
+ if (hits.length === 0) return { kind: "none" };
32781
+ if (hits.length > 1) return { kind: "ambiguous", count: hits.length };
32782
+ return { kind: "one", ...hits[0] };
32540
32783
  }
32541
32784
  });
32542
32785
  app76.route("/api/channel/schedule-inject", scheduleInjectRoutes);
@@ -32909,14 +33152,14 @@ app76.get("/agent-assets/:slug/:filename", (c) => {
32909
33152
  console.error(`[agent-assets] path-traversal-rejected slug=${slug} file=${filename}`);
32910
33153
  return c.text("Forbidden", 403);
32911
33154
  }
32912
- if (!existsSync51(filePath)) {
33155
+ if (!existsSync52(filePath)) {
32913
33156
  console.error(`[agent-assets] serve slug=${slug} file=${filename} status=404`);
32914
33157
  return c.text("Not found", 404);
32915
33158
  }
32916
33159
  const ext = "." + filename.split(".").pop()?.toLowerCase();
32917
33160
  const contentType = IMAGE_MIME[ext] || "application/octet-stream";
32918
33161
  console.log(`[agent-assets] serve slug=${slug} file=${filename} status=200`);
32919
- const body = readFileSync47(filePath);
33162
+ const body = readFileSync48(filePath);
32920
33163
  return c.body(body, 200, {
32921
33164
  "Content-Type": contentType,
32922
33165
  "Cache-Control": "public, max-age=3600"
@@ -32939,14 +33182,14 @@ app76.get("/generated/:filename", (c) => {
32939
33182
  console.error(`[generated] serve file=${filename} status=403`);
32940
33183
  return c.text("Forbidden", 403);
32941
33184
  }
32942
- if (!existsSync51(filePath)) {
33185
+ if (!existsSync52(filePath)) {
32943
33186
  console.error(`[generated] serve file=${filename} status=404`);
32944
33187
  return c.text("Not found", 404);
32945
33188
  }
32946
33189
  const ext = "." + filename.split(".").pop()?.toLowerCase();
32947
33190
  const contentType = IMAGE_MIME[ext] || "application/octet-stream";
32948
33191
  console.log(`[generated] serve file=${filename} status=200`);
32949
- const body = readFileSync47(filePath);
33192
+ const body = readFileSync48(filePath);
32950
33193
  return c.body(body, 200, {
32951
33194
  "Content-Type": contentType,
32952
33195
  "Cache-Control": "public, max-age=86400"
@@ -32959,9 +33202,9 @@ app76.route("/v", visitor_consent_default);
32959
33202
  var htmlCache = /* @__PURE__ */ new Map();
32960
33203
  var brandLogoPath = "/brand/maxy-monochrome.png";
32961
33204
  var brandIconPath = "/brand/maxy-monochrome.png";
32962
- if (BRAND_JSON_PATH && existsSync51(BRAND_JSON_PATH)) {
33205
+ if (BRAND_JSON_PATH && existsSync52(BRAND_JSON_PATH)) {
32963
33206
  try {
32964
- const fullBrand = JSON.parse(readFileSync47(BRAND_JSON_PATH, "utf-8"));
33207
+ const fullBrand = JSON.parse(readFileSync48(BRAND_JSON_PATH, "utf-8"));
32965
33208
  if (fullBrand.assets?.logo) brandLogoPath = `/brand/${fullBrand.assets.logo}`;
32966
33209
  brandIconPath = fullBrand.assets?.icon ? `/brand/${fullBrand.assets.icon}` : brandLogoPath;
32967
33210
  } catch {
@@ -32988,11 +33231,11 @@ var brandScript = `<script>window.__BRAND__=${JSON.stringify({
32988
33231
  var brandThemeColor = BRAND.defaultColors?.primary ?? "#000000";
32989
33232
  var brandBackgroundColor = BRAND.defaultColors?.background ?? "#ffffff";
32990
33233
  var brandAppIconsExist = [brandAppIcon192Path, brandAppIcon512Path, brandMaskableIconPath].every(
32991
- (p) => existsSync51(resolve45(process.cwd(), "public", p.replace(/^\//, "")))
33234
+ (p) => existsSync52(resolve45(process.cwd(), "public", p.replace(/^\//, "")))
32992
33235
  );
32993
33236
  var SW_SOURCE = (() => {
32994
33237
  try {
32995
- return readFileSync47(resolve45(process.cwd(), "public", "sw.js"), "utf-8");
33238
+ return readFileSync48(resolve45(process.cwd(), "public", "sw.js"), "utf-8");
32996
33239
  } catch {
32997
33240
  return null;
32998
33241
  }
@@ -33000,9 +33243,9 @@ var SW_SOURCE = (() => {
33000
33243
  function readInstalledVersion() {
33001
33244
  try {
33002
33245
  if (!PLATFORM_ROOT6) return "unknown";
33003
- const versionFile = join55(PLATFORM_ROOT6, "config", `.${BRAND.hostname}-version`);
33004
- if (!existsSync51(versionFile)) return "unknown";
33005
- const content = readFileSync47(versionFile, "utf-8").trim();
33246
+ const versionFile = join56(PLATFORM_ROOT6, "config", `.${BRAND.hostname}-version`);
33247
+ if (!existsSync52(versionFile)) return "unknown";
33248
+ const content = readFileSync48(versionFile, "utf-8").trim();
33006
33249
  return content || "unknown";
33007
33250
  } catch {
33008
33251
  return "unknown";
@@ -33013,7 +33256,7 @@ var versionScript = `<script>window.__MAXY_VERSION__=${JSON.stringify(INSTALLED_
33013
33256
  function cachedHtml(file) {
33014
33257
  let html = htmlCache.get(file);
33015
33258
  if (!html) {
33016
- html = readFileSync47(resolve45(process.cwd(), "public", file), "utf-8");
33259
+ html = readFileSync48(resolve45(process.cwd(), "public", file), "utf-8");
33017
33260
  const productNameEsc = escapeHtml(BRAND.productName);
33018
33261
  html = html.replace(/<title>([^<]*)<\/title>/, (_match, inner) => `<title>${escapeHtml(inner).replace(/Maxy/g, productNameEsc)}</title>`);
33019
33262
  html = html.replace('href="/favicon.ico"', `href="${escapeHtml(brandFaviconPath)}"`);
@@ -33030,14 +33273,14 @@ ${clientErrorReporterScript}
33030
33273
  return html;
33031
33274
  }
33032
33275
  function loadBrandingCache(agentSlug) {
33033
- const configDir = join55(homedir4(), BRAND.configDir);
33276
+ const configDir = join56(homedir4(), BRAND.configDir);
33034
33277
  try {
33035
33278
  const verdict = resolvePublicAddressFromDisk(agentSlug);
33036
33279
  const accountId = verdict.kind === "served" ? verdict.owner.accountId : getDefaultAccountId();
33037
33280
  if (!accountId) return null;
33038
- const cachePath = join55(configDir, "branding-cache", accountId, `${agentSlug}.json`);
33039
- if (!existsSync51(cachePath)) return null;
33040
- return JSON.parse(readFileSync47(cachePath, "utf-8"));
33281
+ const cachePath = join56(configDir, "branding-cache", accountId, `${agentSlug}.json`);
33282
+ if (!existsSync52(cachePath)) return null;
33283
+ return JSON.parse(readFileSync48(cachePath, "utf-8"));
33041
33284
  } catch {
33042
33285
  return null;
33043
33286
  }
@@ -33132,7 +33375,7 @@ app76.use("/vnc-popout.html", logViewerFetch);
33132
33375
  app76.get("/vnc-popout.html", (c) => {
33133
33376
  let html = htmlCache.get("vnc-popout.html");
33134
33377
  if (!html) {
33135
- html = readFileSync47(resolve45(process.cwd(), "public", "vnc-popout.html"), "utf-8");
33378
+ html = readFileSync48(resolve45(process.cwd(), "public", "vnc-popout.html"), "utf-8");
33136
33379
  const name = escapeHtml(BRAND.productName);
33137
33380
  html = html.replace("<title>Browser \u2014 Maxy</title>", `<title>${name}</title>`);
33138
33381
  html = html.replace("</head>", ` ${brandScript}
@@ -33285,10 +33528,10 @@ var hostname = process.env.HOSTNAME ?? "127.0.0.1";
33285
33528
  var httpServer = serve({ fetch: app76.fetch, port, hostname });
33286
33529
  console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
33287
33530
  {
33288
- const reconcilePlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join55(__dirname, "..");
33531
+ const reconcilePlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join56(__dirname, "..");
33289
33532
  const reconcileScript = resolve45(reconcilePlatformRoot, "plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js");
33290
33533
  const runReconcile = (ctx) => {
33291
- if (!existsSync51(reconcileScript)) return;
33534
+ if (!existsSync52(reconcileScript)) return;
33292
33535
  try {
33293
33536
  const child = spawn3(process.execPath, [reconcileScript], {
33294
33537
  env: { ...process.env, PLATFORM_ROOT: reconcilePlatformRoot },
@@ -33308,7 +33551,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
33308
33551
  detached: true,
33309
33552
  run: runReconcile
33310
33553
  });
33311
- const reconcileStatePath = join55(MAXY_DIR, "booking-reconcile-state.json");
33554
+ const reconcileStatePath = join56(MAXY_DIR, "booking-reconcile-state.json");
33312
33555
  registerLoop({
33313
33556
  name: "calendar-reconcile-liveness",
33314
33557
  intervalMs: 6e5,
@@ -33326,7 +33569,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
33326
33569
  }
33327
33570
  {
33328
33571
  const DREAM_CYCLE_SWEEP_INTERVAL_MS = 36e5;
33329
- const dreamPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join55(__dirname, "..");
33572
+ const dreamPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join56(__dirname, "..");
33330
33573
  const dreamSweepScript = resolve45(dreamPlatformRoot, "plugins/memory/mcp/dist/scripts/dream-cycle-sweep.js");
33331
33574
  registerLoop({
33332
33575
  name: "dream-cycle-sweep",
@@ -33334,7 +33577,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
33334
33577
  firstRunDelayMs: 3e4,
33335
33578
  detached: true,
33336
33579
  run: (ctx) => {
33337
- if (!existsSync51(dreamSweepScript)) return;
33580
+ if (!existsSync52(dreamSweepScript)) return;
33338
33581
  try {
33339
33582
  const child = spawn3(process.execPath, [dreamSweepScript], {
33340
33583
  env: { ...process.env, PLATFORM_ROOT: dreamPlatformRoot },
@@ -33350,11 +33593,11 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
33350
33593
  });
33351
33594
  }
33352
33595
  {
33353
- const outlookPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join55(__dirname, "..");
33596
+ const outlookPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join56(__dirname, "..");
33354
33597
  const outlookScript = resolve45(outlookPlatformRoot, "plugins/outlook/mcp/dist/scripts/complete-registration.js");
33355
33598
  const OUTLOOK_COMPLETE_INTERVAL_MS = 3e4;
33356
33599
  const runOutlookComplete = (ctx) => {
33357
- if (!existsSync51(outlookScript)) return;
33600
+ if (!existsSync52(outlookScript)) return;
33358
33601
  try {
33359
33602
  const child = spawn3(process.execPath, [outlookScript], {
33360
33603
  env: { ...process.env, PLATFORM_ROOT: outlookPlatformRoot },
@@ -33376,19 +33619,19 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
33376
33619
  });
33377
33620
  }
33378
33621
  {
33379
- const auditRoot = process.env.MAXY_PLATFORM_ROOT ?? join55(__dirname, "..");
33622
+ const auditRoot = process.env.MAXY_PLATFORM_ROOT ?? join56(__dirname, "..");
33380
33623
  const strandedAccountsDir = resolve45(auditRoot, "..", "data/accounts");
33381
33624
  const STRANDED_AUDIT_INTERVAL_MS = 3e5;
33382
33625
  const STRANDED_AGE_MS = 16 * 6e4;
33383
33626
  const STRANDED_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
33384
33627
  const runStrandedAudit = () => {
33385
33628
  try {
33386
- if (!existsSync51(strandedAccountsDir)) return;
33629
+ if (!existsSync52(strandedAccountsDir)) return;
33387
33630
  const now = Date.now();
33388
33631
  for (const name of readdirSync30(strandedAccountsDir)) {
33389
33632
  if (!STRANDED_UUID_RE.test(name)) continue;
33390
33633
  const pendingPath2 = resolve45(strandedAccountsDir, name, "secrets/outlook/pending-devicecode.enc");
33391
- if (!existsSync51(pendingPath2)) continue;
33634
+ if (!existsSync52(pendingPath2)) continue;
33392
33635
  const ageMs2 = now - statSync27(pendingPath2).mtimeMs;
33393
33636
  if (ageMs2 > STRANDED_AGE_MS) {
33394
33637
  console.error(`[outlook-mcp] devicecode-stranded account=${name} ageSec=${Math.floor(ageMs2 / 1e3)}`);
@@ -33406,7 +33649,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
33406
33649
  });
33407
33650
  }
33408
33651
  {
33409
- const googleRoot = process.env.MAXY_PLATFORM_ROOT ?? join55(__dirname, "..");
33652
+ const googleRoot = process.env.MAXY_PLATFORM_ROOT ?? join56(__dirname, "..");
33410
33653
  const googleAccountsDir = resolve45(googleRoot, "..", "data/accounts");
33411
33654
  const GOOGLE_PENDING_AUDIT_INTERVAL_MS = 3e5;
33412
33655
  const runGooglePendingAuditSafe = () => {
@@ -33425,7 +33668,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
33425
33668
  const googleAuditScript = resolve45(googleRoot, "plugins/google/mcp/dist/scripts/account-audit.js");
33426
33669
  const GOOGLE_ACCOUNT_AUDIT_INTERVAL_MS = 36e5;
33427
33670
  const runGoogleAccountAudit = (ctx) => {
33428
- if (!existsSync51(googleAuditScript)) return;
33671
+ if (!existsSync52(googleAuditScript)) return;
33429
33672
  try {
33430
33673
  const child = spawn3(process.execPath, [googleAuditScript], {
33431
33674
  env: { ...process.env, PLATFORM_ROOT: googleRoot },
@@ -33450,7 +33693,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
33450
33693
  });
33451
33694
  }
33452
33695
  {
33453
- const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join55(__dirname, "..");
33696
+ const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join56(__dirname, "..");
33454
33697
  const STORAGE_AUDIT_INTERVAL_MS = 3e5;
33455
33698
  const runStorageAuditSafe = () => runStorageAudit(auditPlatformRoot).catch((err) => {
33456
33699
  console.error(`[storage-audit] error="${err.message}"`);
@@ -33482,15 +33725,42 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
33482
33725
  firstRunDelayMs: 5e4,
33483
33726
  run: runDataPortalAuditSafe
33484
33727
  });
33728
+ for (const o of migrateTelegramTwoSlot({
33729
+ accounts: listValidAccounts().map((a) => ({ accountId: a.accountId, accountDir: a.accountDir })),
33730
+ secretPathFor: secretPath,
33731
+ legacySecretPathFor: (botType, accountId) => resolve45(
33732
+ homedir4(),
33733
+ configDirName(),
33734
+ `${botType === "admin" ? ".telegram-admin-webhook-secret" : ".telegram-webhook-secret"}.${accountId}`
33735
+ ),
33736
+ // Counted here and nowhere else: the session-key re-key strands these and no
33737
+ // later event reveals it, because the new key never resolves to the old JSONL.
33738
+ countOrphanSessions: countTelegramSessionsForAccount
33739
+ })) {
33740
+ console.error(
33741
+ `[telegram-audit] op=migrate-session-orphans accountId=${o.accountId} count=${o.orphanSessions} converted=${o.converted}${o.skippedAdmin ? ` skippedAdmin=${o.skippedAdmin}` : ""}${o.skippedPublic ? ` skippedPublic=${o.skippedPublic}` : ""}`
33742
+ );
33743
+ }
33485
33744
  const runTelegramAudit = async () => {
33486
- const accounts = listValidAccounts().map((a) => ({ accountId: a.accountId, telegram: a.config.telegram }));
33487
- const configured = accounts.some((a) => a.telegram?.adminBotToken || a.telegram?.publicBotToken);
33745
+ const accounts = listValidAccounts().map((a) => ({
33746
+ accountId: a.accountId,
33747
+ accountDir: a.accountDir,
33748
+ telegram: a.config.telegram
33749
+ }));
33750
+ const configured = accounts.some((a) => listBotEntries(a.telegram).length > 0);
33488
33751
  if (!configured) return;
33489
- const lastByAccount = newestTelegramSessionByAccount();
33752
+ const lastByBot = newestTelegramSessionByBot();
33753
+ let secretFilesOnDisk = [];
33754
+ try {
33755
+ secretFilesOnDisk = readdirSync30(telegramSecretsDir());
33756
+ } catch {
33757
+ }
33490
33758
  const lines = buildTelegramAuditLines({
33491
33759
  accounts,
33492
- secretFileExists: (botType, accountId) => existsSync51(secretPath(botType, accountId)),
33493
- lastInboundAt: (accountId) => lastByAccount.get(accountId) ?? null,
33760
+ secretFileExists: (botId) => existsSync52(secretPath(botId)),
33761
+ agentActive: isActiveAgentSlug,
33762
+ secretFilesOnDisk,
33763
+ lastInboundByBot: (botId) => lastByBot.get(botId) ?? null,
33494
33764
  now: Date.now()
33495
33765
  });
33496
33766
  for (const line of lines) console.error(line);
@@ -33566,7 +33836,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
33566
33836
  });
33567
33837
  }
33568
33838
  {
33569
- const publishPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join55(__dirname, "..");
33839
+ const publishPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join56(__dirname, "..");
33570
33840
  const publishScript = resolve45(publishPlatformRoot, "plugins/scheduling/mcp/dist/scripts/publish-availability.js");
33571
33841
  const PUBLISH_INTERVAL_MS = 3e5;
33572
33842
  const bookingAccounts = () => {
@@ -33578,7 +33848,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
33578
33848
  }
33579
33849
  };
33580
33850
  const spawnPublish = (account, ctx) => {
33581
- if (!existsSync51(publishScript)) return;
33851
+ if (!existsSync52(publishScript)) return;
33582
33852
  try {
33583
33853
  const child = spawn3(process.execPath, [publishScript], {
33584
33854
  env: {
@@ -33600,8 +33870,8 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
33600
33870
  const availPath = resolve45(account.accountDir, "calendar-availability.json");
33601
33871
  let hasBookingSite = false;
33602
33872
  try {
33603
- if (existsSync51(availPath)) {
33604
- const cfg = JSON.parse(readFileSync47(availPath, "utf-8"));
33873
+ if (existsSync52(availPath)) {
33874
+ const cfg = JSON.parse(readFileSync48(availPath, "utf-8"));
33605
33875
  hasBookingSite = typeof cfg.bookingDbName === "string" && cfg.bookingDbName.length > 0;
33606
33876
  }
33607
33877
  } catch {
@@ -33609,9 +33879,9 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
33609
33879
  if (!hasBookingSite) return;
33610
33880
  const statePath = resolve45(account.accountDir, "state", "booking-availability", "last-publish.json");
33611
33881
  let lastSuccessAt = null;
33612
- if (existsSync51(statePath)) {
33882
+ if (existsSync52(statePath)) {
33613
33883
  try {
33614
- const rec = JSON.parse(readFileSync47(statePath, "utf-8"));
33884
+ const rec = JSON.parse(readFileSync48(statePath, "utf-8"));
33615
33885
  lastSuccessAt = rec.lastSuccessAt ?? null;
33616
33886
  } catch {
33617
33887
  console.error(`[calendar-availability] op=audit accountId=${account.accountId} snapshotAgeMs=na reason=bad-state-file`);
@@ -33643,12 +33913,12 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
33643
33913
  startTimeEntryCensus(() => getSession());
33644
33914
  startLedgerCensus(() => getSession());
33645
33915
  {
33646
- const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join55(__dirname, "..");
33916
+ const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join56(__dirname, "..");
33647
33917
  const auditScript = resolve45(auditPlatformRoot, "plugins/connector/mcp/dist/scripts/audit-connectors.js");
33648
33918
  const auditLogDir = process.env.LOG_DIR ?? LOG_DIR;
33649
33919
  const CONNECTOR_AUDIT_INTERVAL_MS = 3e5;
33650
33920
  const runConnectorAudit = (ctx) => {
33651
- if (!existsSync51(auditScript)) return;
33921
+ if (!existsSync52(auditScript)) return;
33652
33922
  try {
33653
33923
  const child = spawn3(process.execPath, [auditScript], {
33654
33924
  env: { ...process.env, PLATFORM_ROOT: auditPlatformRoot, LOG_DIR: auditLogDir },
@@ -33768,11 +34038,11 @@ try {
33768
34038
  var ADMINUSER_RECONCILE_INTERVAL_MS = 60 * 60 * 1e3;
33769
34039
  async function runAdminUserReconcileTick() {
33770
34040
  try {
33771
- if (!existsSync51(USERS_FILE)) {
34041
+ if (!existsSync52(USERS_FILE)) {
33772
34042
  console.error("[adminuser-self-heal] skip reason=no-users-file");
33773
34043
  return;
33774
34044
  }
33775
- const usersRaw = readFileSync47(USERS_FILE, "utf-8").trim();
34045
+ const usersRaw = readFileSync48(USERS_FILE, "utf-8").trim();
33776
34046
  if (!usersRaw) {
33777
34047
  console.error("[adminuser-self-heal] skip reason=empty-users-file");
33778
34048
  return;
@@ -33854,8 +34124,8 @@ var runIngestAuditTick = () => {
33854
34124
  },
33855
34125
  agentExists: (accountId, slug) => {
33856
34126
  if (!house) return false;
33857
- const dir = isHouseAccountKey(house.accountId, accountId) ? house.accountDir : join55(ACCOUNTS_DIR, accountId);
33858
- return existsSync51(join55(dir, "agents", slug, "config.json"));
34127
+ const dir = isHouseAccountKey(house.accountId, accountId) ? house.accountDir : join56(ACCOUNTS_DIR, accountId);
34128
+ return existsSync52(join56(dir, "agents", slug, "config.json"));
33859
34129
  }
33860
34130
  });
33861
34131
  } catch (err) {
@@ -33903,8 +34173,8 @@ registerLoop({
33903
34173
  runPublicAddressCollisionAudit({
33904
34174
  houseAccountId: () => resolveAccount()?.accountId ?? null,
33905
34175
  activeSlugs: () => listValidAccounts().map((a) => {
33906
- const agentsRoot = join55(a.accountDir, "agents");
33907
- if (!existsSync51(agentsRoot)) return { accountId: a.accountId, slugs: [] };
34176
+ const agentsRoot = join56(a.accountDir, "agents");
34177
+ if (!existsSync52(agentsRoot)) return { accountId: a.accountId, slugs: [] };
33908
34178
  const slugs = readdirSync30(agentsRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).filter((slug) => isActiveAgentSlug(a.accountDir, slug));
33909
34179
  return { accountId: a.accountId, slugs };
33910
34180
  })
@@ -33918,8 +34188,8 @@ registerLoop({
33918
34188
  });
33919
34189
  var USERS_RECONCILE_INTERVAL_MS = 60 * 60 * 1e3;
33920
34190
  function countUsersRows() {
33921
- if (!existsSync51(USERS_FILE)) return 0;
33922
- const raw = readFileSync47(USERS_FILE, "utf-8").trim();
34191
+ if (!existsSync52(USERS_FILE)) return 0;
34192
+ const raw = readFileSync48(USERS_FILE, "utf-8").trim();
33923
34193
  if (!raw) return 0;
33924
34194
  const users = JSON.parse(raw);
33925
34195
  return users.filter((u) => typeof u.userId === "string").length;
@@ -34055,7 +34325,7 @@ if (bootAccountConfig?.whatsapp) {
34055
34325
  }
34056
34326
  init({
34057
34327
  configDir: configDirForWhatsApp,
34058
- platformRoot: resolve45(process.env.MAXY_PLATFORM_ROOT ?? join55(__dirname, "..")),
34328
+ platformRoot: resolve45(process.env.MAXY_PLATFORM_ROOT ?? join56(__dirname, "..")),
34059
34329
  accountConfig: bootAccountConfig,
34060
34330
  // Task 2074 — enrich with the sub-account name and fan out to every open
34061
34331
  // admin SSE stream. Fire-and-forget: the manager's persist path must not