@rubytech/create-maxy-code 0.1.567 → 0.1.569

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 (120) hide show
  1. package/dist/__tests__/brand-agent-root.test.js +91 -0
  2. package/dist/brand-agent-root.js +58 -0
  3. package/dist/index.js +25 -28
  4. package/package.json +1 -1
  5. package/payload/platform/plugins/admin/PLUGIN.md +4 -2
  6. package/payload/platform/plugins/admin/hooks/__tests__/quote-path-write-gate.test.sh +152 -0
  7. package/payload/platform/plugins/admin/hooks/__tests__/quote-pdf-bash-gate.test.sh +95 -0
  8. package/payload/platform/plugins/admin/hooks/__tests__/quote-render-gate.test.sh +141 -45
  9. package/payload/platform/plugins/admin/hooks/__tests__/quote-render-pdf-conformance.test.sh +114 -0
  10. package/payload/platform/plugins/admin/hooks/quote-path-write-gate.sh +137 -0
  11. package/payload/platform/plugins/admin/hooks/quote-pdf-bash-gate.sh +70 -0
  12. package/payload/platform/plugins/admin/hooks/quote-render-gate.sh +105 -37
  13. package/payload/platform/plugins/admin/hooks/quote-render-pdf-conformance.sh +178 -0
  14. package/payload/platform/plugins/admin/mcp/dist/index.js +7 -1
  15. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  16. package/payload/platform/plugins/admin/mcp/dist/lib/roster-census.d.ts +10 -1
  17. package/payload/platform/plugins/admin/mcp/dist/lib/roster-census.d.ts.map +1 -1
  18. package/payload/platform/plugins/admin/mcp/dist/lib/roster-census.js +52 -1
  19. package/payload/platform/plugins/admin/mcp/dist/lib/roster-census.js.map +1 -1
  20. package/payload/platform/plugins/admin/mcp/dist/tools/capabilities-here.d.ts +9 -0
  21. package/payload/platform/plugins/admin/mcp/dist/tools/capabilities-here.d.ts.map +1 -1
  22. package/payload/platform/plugins/admin/mcp/dist/tools/capabilities-here.js +1 -1
  23. package/payload/platform/plugins/admin/mcp/dist/tools/capabilities-here.js.map +1 -1
  24. package/payload/platform/plugins/admin/skills/capabilities-here/SKILL.md +1 -1
  25. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +44 -1
  26. package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +15 -0
  27. package/payload/platform/plugins/docs/references/channel-wake-and-prompt.md +43 -0
  28. package/payload/platform/scripts/__tests__/mailbox-inject-registered.test.sh +5 -5
  29. package/payload/platform/scripts/__tests__/preference-hooks-registered.test.sh +3 -1
  30. package/payload/platform/scripts/__tests__/public-surface-registered.test.sh +5 -5
  31. package/payload/platform/scripts/__tests__/quote-ownership-contract.test.sh +86 -0
  32. package/payload/platform/scripts/lib/__tests__/account-settings-reconcile.test.sh +73 -0
  33. package/payload/platform/scripts/lib/account-settings-reconcile.sh +88 -0
  34. package/payload/platform/scripts/lib/provision-account-dir.sh +11 -146
  35. package/payload/platform/scripts/setup-account.sh +13 -0
  36. package/payload/platform/services/claude-session-manager/dist/account-agent-registry.d.ts +39 -0
  37. package/payload/platform/services/claude-session-manager/dist/account-agent-registry.d.ts.map +1 -0
  38. package/payload/platform/services/claude-session-manager/dist/account-agent-registry.js +223 -0
  39. package/payload/platform/services/claude-session-manager/dist/account-agent-registry.js.map +1 -0
  40. package/payload/platform/services/claude-session-manager/dist/fs-watcher.d.ts +5 -0
  41. package/payload/platform/services/claude-session-manager/dist/fs-watcher.d.ts.map +1 -1
  42. package/payload/platform/services/claude-session-manager/dist/fs-watcher.js +5 -5
  43. package/payload/platform/services/claude-session-manager/dist/fs-watcher.js.map +1 -1
  44. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts +11 -1
  45. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  46. package/payload/platform/services/claude-session-manager/dist/http-server.js +58 -12
  47. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  48. package/payload/platform/services/claude-session-manager/dist/index.js +112 -13
  49. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  50. package/payload/platform/services/claude-session-manager/dist/pty-census.d.ts +6 -1
  51. package/payload/platform/services/claude-session-manager/dist/pty-census.d.ts.map +1 -1
  52. package/payload/platform/services/claude-session-manager/dist/pty-census.js +6 -3
  53. package/payload/platform/services/claude-session-manager/dist/pty-census.js.map +1 -1
  54. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +21 -8
  55. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
  56. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +38 -15
  57. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
  58. package/payload/platform/services/claude-session-manager/dist/quote-delivery-reconcile.d.ts +42 -0
  59. package/payload/platform/services/claude-session-manager/dist/quote-delivery-reconcile.d.ts.map +1 -0
  60. package/payload/platform/services/claude-session-manager/dist/quote-delivery-reconcile.js +215 -0
  61. package/payload/platform/services/claude-session-manager/dist/quote-delivery-reconcile.js.map +1 -0
  62. package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts +2 -0
  63. package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts.map +1 -1
  64. package/payload/platform/services/claude-session-manager/dist/rc-daemon.js +4 -1
  65. package/payload/platform/services/claude-session-manager/dist/rc-daemon.js.map +1 -1
  66. package/payload/platform/services/claude-session-manager/dist/reaper.d.ts +3 -0
  67. package/payload/platform/services/claude-session-manager/dist/reaper.d.ts.map +1 -1
  68. package/payload/platform/services/claude-session-manager/dist/reaper.js +2 -1
  69. package/payload/platform/services/claude-session-manager/dist/reaper.js.map +1 -1
  70. package/payload/platform/services/claude-session-manager/dist/session-cap-audit.d.ts +2 -0
  71. package/payload/platform/services/claude-session-manager/dist/session-cap-audit.d.ts.map +1 -1
  72. package/payload/platform/services/claude-session-manager/dist/session-cap-audit.js +5 -2
  73. package/payload/platform/services/claude-session-manager/dist/session-cap-audit.js.map +1 -1
  74. package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.d.ts +2 -0
  75. package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.d.ts.map +1 -1
  76. package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.js +5 -3
  77. package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.js.map +1 -1
  78. package/payload/platform/services/claude-session-manager/dist/specialist-preference-audit.d.ts +108 -0
  79. package/payload/platform/services/claude-session-manager/dist/specialist-preference-audit.d.ts.map +1 -0
  80. package/payload/platform/services/claude-session-manager/dist/specialist-preference-audit.js +299 -0
  81. package/payload/platform/services/claude-session-manager/dist/specialist-preference-audit.js.map +1 -0
  82. package/payload/platform/services/claude-session-manager/dist/systemd-scope.d.ts +26 -16
  83. package/payload/platform/services/claude-session-manager/dist/systemd-scope.d.ts.map +1 -1
  84. package/payload/platform/services/claude-session-manager/dist/systemd-scope.js +84 -31
  85. package/payload/platform/services/claude-session-manager/dist/systemd-scope.js.map +1 -1
  86. package/payload/platform/services/claude-session-manager/dist/wa-channel-mcp.d.ts +7 -0
  87. package/payload/platform/services/claude-session-manager/dist/wa-channel-mcp.d.ts.map +1 -1
  88. package/payload/platform/services/claude-session-manager/dist/wa-channel-mcp.js +6 -0
  89. package/payload/platform/services/claude-session-manager/dist/wa-channel-mcp.js.map +1 -1
  90. package/payload/platform/services/telegram-channel/dist/notification.d.ts +10 -2
  91. package/payload/platform/services/telegram-channel/dist/notification.d.ts.map +1 -1
  92. package/payload/platform/services/telegram-channel/dist/notification.js +15 -3
  93. package/payload/platform/services/telegram-channel/dist/notification.js.map +1 -1
  94. package/payload/platform/services/telegram-channel/dist/server.d.ts.map +1 -1
  95. package/payload/platform/services/telegram-channel/dist/server.js +35 -3
  96. package/payload/platform/services/telegram-channel/dist/server.js.map +1 -1
  97. package/payload/platform/services/telegram-channel/dist/turn-follow.d.ts +10 -0
  98. package/payload/platform/services/telegram-channel/dist/turn-follow.d.ts.map +1 -1
  99. package/payload/platform/services/telegram-channel/dist/turn-follow.js +38 -1
  100. package/payload/platform/services/telegram-channel/dist/turn-follow.js.map +1 -1
  101. package/payload/platform/services/webchat-channel/dist/server.d.ts.map +1 -1
  102. package/payload/platform/services/webchat-channel/dist/server.js +31 -2
  103. package/payload/platform/services/webchat-channel/dist/server.js.map +1 -1
  104. package/payload/platform/services/webchat-channel/dist/turn-follow.d.ts +10 -0
  105. package/payload/platform/services/webchat-channel/dist/turn-follow.d.ts.map +1 -1
  106. package/payload/platform/services/webchat-channel/dist/turn-follow.js +38 -1
  107. package/payload/platform/services/webchat-channel/dist/turn-follow.js.map +1 -1
  108. package/payload/platform/services/whatsapp-channel/dist/instructions.d.ts +9 -0
  109. package/payload/platform/services/whatsapp-channel/dist/instructions.d.ts.map +1 -0
  110. package/payload/platform/services/whatsapp-channel/dist/instructions.js +22 -0
  111. package/payload/platform/services/whatsapp-channel/dist/instructions.js.map +1 -0
  112. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +3 -1
  113. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
  114. package/payload/platform/services/whatsapp-channel/dist/notification.js +10 -2
  115. package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
  116. package/payload/platform/services/whatsapp-channel/dist/server.d.ts.map +1 -1
  117. package/payload/platform/services/whatsapp-channel/dist/server.js +26 -2
  118. package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
  119. package/payload/platform/templates/account-settings.json +153 -0
  120. package/payload/server/server.js +254 -96
@@ -4886,9 +4886,9 @@ var idCache = /* @__PURE__ */ new Map();
4886
4886
  function persistedMsgKeyIds() {
4887
4887
  const ids = /* @__PURE__ */ new Set();
4888
4888
  for (const accountId of listStoredAccountIds()) {
4889
- for (const [remoteJid, records] of readAllConversationsCached(accountId)) {
4889
+ for (const [remoteJid, records2] of readAllConversationsCached(accountId)) {
4890
4890
  void remoteJid;
4891
- for (const id of records) ids.add(id);
4891
+ for (const id of records2) ids.add(id);
4892
4892
  }
4893
4893
  }
4894
4894
  return ids;
@@ -6562,9 +6562,9 @@ function readConversation2(accountId, channelKey) {
6562
6562
  }
6563
6563
  }
6564
6564
  var summaryCache = /* @__PURE__ */ new Map();
6565
- function summariseRecords(records) {
6566
- if (records.length === 0) return null;
6567
- const sorted = records.slice().sort(byDateSentAsc);
6565
+ function summariseRecords(records2) {
6566
+ if (records2.length === 0) return null;
6567
+ const sorted = records2.slice().sort(byDateSentAsc);
6568
6568
  return { first: sorted[0], last: sorted[sorted.length - 1] };
6569
6569
  }
6570
6570
  function readConversationSummaries2(accountId) {
@@ -6962,15 +6962,15 @@ var CENSUS_TAG = "[wa-avatar-census]";
6962
6962
  var AVATAR_STORE_MAX_BYTES = 16 * 1024 * 1024;
6963
6963
  function liveJidsForAccount(accountId) {
6964
6964
  const live = /* @__PURE__ */ new Set();
6965
- for (const [remoteJid, records] of readAllConversations(accountId)) {
6966
- for (const jid of avatarJidsForConversation(remoteJid, records)) live.add(jid);
6965
+ for (const [remoteJid, records2] of readAllConversations(accountId)) {
6966
+ for (const jid of avatarJidsForConversation(remoteJid, records2)) live.add(jid);
6967
6967
  }
6968
6968
  return live;
6969
6969
  }
6970
- function avatarJidsForConversation(remoteJid, records) {
6970
+ function avatarJidsForConversation(remoteJid, records2) {
6971
6971
  const jids = /* @__PURE__ */ new Set([remoteJid]);
6972
6972
  if (!isGroupJid(remoteJid)) return jids;
6973
- for (const r of records) {
6973
+ for (const r of records2) {
6974
6974
  if (r.fromMe) continue;
6975
6975
  if (r.senderJid && isUserJid(r.senderJid)) jids.add(r.senderJid);
6976
6976
  }
@@ -7107,10 +7107,10 @@ function auditGroupOutbound() {
7107
7107
  let unstamped = 0;
7108
7108
  const offenders = [];
7109
7109
  for (const accountId of listStoredAccountIds()) {
7110
- for (const [remoteJid, records] of readAllConversations(accountId)) {
7110
+ for (const [remoteJid, records2] of readAllConversations(accountId)) {
7111
7111
  if (!isGroupJid(remoteJid)) continue;
7112
7112
  convos++;
7113
- for (const r of records) {
7113
+ for (const r of records2) {
7114
7114
  if (!r.fromMe) continue;
7115
7115
  outbound++;
7116
7116
  if (r.origin === "operator-manual") operator++;
@@ -12532,8 +12532,8 @@ function resolveReplyAppendAccount(args) {
12532
12532
  if (args.isGroup) return args.sendAccountId;
12533
12533
  return args.resolve(args.sendAccountId, args.counterpartyPhone) || args.sendAccountId;
12534
12534
  }
12535
- function warmConversationAvatars(accountId, remoteJid, records) {
12536
- const jids = avatarJidsForConversation(remoteJid, records);
12535
+ function warmConversationAvatars(accountId, remoteJid, records2) {
12536
+ const jids = avatarJidsForConversation(remoteJid, records2);
12537
12537
  console.log(`[wa-avatar] op=request account=${accountId.slice(0, 8)} convo=${remoteJid.slice(0, 12)} jids=${jids.size} reason=reader-open`);
12538
12538
  for (const jid of jids) {
12539
12539
  void fetchAvatar(accountId, jid).catch((e) => {
@@ -12605,7 +12605,7 @@ app5.get("/store-backfill", requireAdminSession, (c) => {
12605
12605
  return c.json({ turns: [], from: 0, reachedStart: true });
12606
12606
  }
12607
12607
  const { from, aligned } = backfillRangeStart(file, to, TAIL_WINDOW_BYTES);
12608
- const records = [];
12608
+ const records2 = [];
12609
12609
  const reactionRecords = [];
12610
12610
  let reactionScanBytes = 0;
12611
12611
  try {
@@ -12619,7 +12619,7 @@ app5.get("/store-backfill", requireAdminSession, (c) => {
12619
12619
  reactionRecords.push(parsed);
12620
12620
  continue;
12621
12621
  }
12622
- records.push(parsed);
12622
+ records2.push(parsed);
12623
12623
  } catch {
12624
12624
  }
12625
12625
  }
@@ -12638,11 +12638,11 @@ app5.get("/store-backfill", requireAdminSession, (c) => {
12638
12638
  console.error(`[wa-reader] op=store-backfill remoteJid=${remoteJid} from=${from} to=${to} outcome=read-failed`);
12639
12639
  return c.json({ error: "read-failed" }, 500);
12640
12640
  }
12641
- records.sort((a, b) => a.dateSent.localeCompare(b.dateSent));
12641
+ records2.sort((a, b) => a.dateSent.localeCompare(b.dateSent));
12642
12642
  const byMsgKeyId = /* @__PURE__ */ new Map();
12643
- for (const m of records) byMsgKeyId.set(m.msgKeyId, m);
12643
+ for (const m of records2) byMsgKeyId.set(m.msgKeyId, m);
12644
12644
  const wanted = /* @__PURE__ */ new Set();
12645
- for (const m of records) {
12645
+ for (const m of records2) {
12646
12646
  if (m.quotedId && !byMsgKeyId.has(m.quotedId)) wanted.add(m.quotedId);
12647
12647
  }
12648
12648
  const q2 = resolveQuoteTargets(file, from, wanted);
@@ -12658,7 +12658,7 @@ app5.get("/store-backfill", requireAdminSession, (c) => {
12658
12658
  const uploadsBase = resolve16(DATA_ROOT, "accounts", accountId, "uploads");
12659
12659
  const toTurn2 = makeToTurn({ byMsgKeyId, uploadsBase });
12660
12660
  const fold = foldReactions(reactionRecords);
12661
- const turns = records.map((m) => toTurn2(m, fold.get(m.msgKeyId)));
12661
+ const turns = records2.map((m) => toTurn2(m, fold.get(m.msgKeyId)));
12662
12662
  const reachedStart = from === 0;
12663
12663
  noteBackfill(remoteJid.slice(0, 12));
12664
12664
  console.log(
@@ -12735,7 +12735,7 @@ data: ${JSON.stringify({ from: alignedFrom, fileEnd: fileEnd0 })}
12735
12735
 
12736
12736
  `)
12737
12737
  );
12738
- const records = [];
12738
+ const records2 = [];
12739
12739
  try {
12740
12740
  const { buf, end } = readFrom(file, alignedFrom);
12741
12741
  offset = end;
@@ -12747,18 +12747,18 @@ data: ${JSON.stringify({ from: alignedFrom, fileEnd: fileEnd0 })}
12747
12747
  reactionRecords.push(parsed);
12748
12748
  continue;
12749
12749
  }
12750
- records.push(parsed);
12750
+ records2.push(parsed);
12751
12751
  } catch {
12752
12752
  }
12753
12753
  }
12754
12754
  } catch {
12755
12755
  offset = fileEnd0;
12756
12756
  }
12757
- records.sort((a, b) => a.dateSent.localeCompare(b.dateSent));
12758
- const scope = records.length ? records[records.length - 1].scope : "none";
12759
- for (const m of records) byMsgKeyId.set(m.msgKeyId, m);
12757
+ records2.sort((a, b) => a.dateSent.localeCompare(b.dateSent));
12758
+ const scope = records2.length ? records2[records2.length - 1].scope : "none";
12759
+ for (const m of records2) byMsgKeyId.set(m.msgKeyId, m);
12760
12760
  const wantedQuotes = /* @__PURE__ */ new Set();
12761
- for (const m of records) {
12761
+ for (const m of records2) {
12762
12762
  if (m.quotedId && !byMsgKeyId.has(m.quotedId)) wantedQuotes.add(m.quotedId);
12763
12763
  }
12764
12764
  const q2 = resolveQuoteTargets(file, alignedFrom, wantedQuotes);
@@ -12772,14 +12772,14 @@ data: ${JSON.stringify({ from: alignedFrom, fileEnd: fileEnd0 })}
12772
12772
  });
12773
12773
  }
12774
12774
  const backlogFold = foldReactions(reactionRecords);
12775
- for (const m of records) send(toTurn2(m, backlogFold.get(m.msgKeyId)), offset);
12775
+ for (const m of records2) send(toTurn2(m, backlogFold.get(m.msgKeyId)), offset);
12776
12776
  console.log(
12777
- `[wa-reader] op=open remoteJid=${remoteJid} scope=${scope} messages=${records.length} source=store`
12777
+ `[wa-reader] op=open remoteJid=${remoteJid} scope=${scope} messages=${records2.length} source=store`
12778
12778
  );
12779
12779
  console.log(
12780
- `[wa-reader] op=store-window remoteJid=${remoteJid} fileBytes=${fileEnd0} budget=${TAIL_WINDOW_BYTES} rawFrom=${rawFrom} alignedFrom=${alignedFrom} aligned=${aligned} source=${alignedFrom > 0 ? "windowed" : "full"} messages=${records.length} quoteWanted=${wantedQuotes.size} quoteResolved=${q2.found.size} quoteMissing=${wantedQuotes.size - q2.found.size} quoteScanBytes=${q2.scannedBytes} quoteCapped=${q2.capped}`
12780
+ `[wa-reader] op=store-window remoteJid=${remoteJid} fileBytes=${fileEnd0} budget=${TAIL_WINDOW_BYTES} rawFrom=${rawFrom} alignedFrom=${alignedFrom} aligned=${aligned} source=${alignedFrom > 0 ? "windowed" : "full"} messages=${records2.length} quoteWanted=${wantedQuotes.size} quoteResolved=${q2.found.size} quoteMissing=${wantedQuotes.size - q2.found.size} quoteScanBytes=${q2.scannedBytes} quoteCapped=${q2.capped}`
12781
12781
  );
12782
- warmConversationAvatars(accountId, remoteJid, records);
12782
+ warmConversationAvatars(accountId, remoteJid, records2);
12783
12783
  } else {
12784
12784
  reactionRecords.push(...readReactions(accountId, remoteJid));
12785
12785
  }
@@ -13233,16 +13233,16 @@ app6.get("/store-backfill", requireAdminSession, (c) => {
13233
13233
  const to = Number(c.req.query("before") ?? "0");
13234
13234
  if (!Number.isFinite(to) || to <= 0) return c.json({ turns: [], from: 0, done: true });
13235
13235
  const { from } = backfillRangeStart(file, to, TAIL_WINDOW_BYTES);
13236
- let records = [];
13236
+ let records2 = [];
13237
13237
  try {
13238
13238
  const { buf } = readFrom(file, from);
13239
- records = parseRecords(buf.subarray(0, to - from)).sort((a, b) => a.dateSent.localeCompare(b.dateSent));
13239
+ records2 = parseRecords(buf.subarray(0, to - from)).sort((a, b) => a.dateSent.localeCompare(b.dateSent));
13240
13240
  } catch {
13241
13241
  }
13242
13242
  console.log(
13243
- `${TAG22} op=backfill channelKey=${v.channelKey} from=${from} to=${to} turns=${records.length} done=${from === 0}`
13243
+ `${TAG22} op=backfill channelKey=${v.channelKey} from=${from} to=${to} turns=${records2.length} done=${from === 0}`
13244
13244
  );
13245
- return c.json({ turns: records.map(toTurn), from, done: from === 0 });
13245
+ return c.json({ turns: records2.map(toTurn), from, done: from === 0 });
13246
13246
  });
13247
13247
  app6.get("/store-stream", requireAdminSession, (c) => {
13248
13248
  const v = validate(c.req.query("accountId"), c.req.query("channelKey"));
@@ -13283,18 +13283,18 @@ data: ${JSON.stringify({ from: alignedFrom, fileEnd: fileEnd0 })}
13283
13283
 
13284
13284
  `)
13285
13285
  );
13286
- let records = [];
13286
+ let records2 = [];
13287
13287
  try {
13288
13288
  const { buf, end } = readFrom(file, alignedFrom);
13289
13289
  offset = end;
13290
- records = parseRecords(buf);
13290
+ records2 = parseRecords(buf);
13291
13291
  } catch {
13292
13292
  offset = fileEnd0;
13293
13293
  }
13294
- records.sort((a, b) => a.dateSent.localeCompare(b.dateSent));
13295
- for (const r of records) send(toTurn(r), offset);
13294
+ records2.sort((a, b) => a.dateSent.localeCompare(b.dateSent));
13295
+ for (const r of records2) send(toTurn(r), offset);
13296
13296
  console.log(
13297
- `${TAG22} op=open channelKey=${channelKey} messages=${records.length} fileBytes=${fileEnd0} alignedFrom=${alignedFrom} aligned=${aligned} source=${alignedFrom > 0 ? "windowed" : "full"}`
13297
+ `${TAG22} op=open channelKey=${channelKey} messages=${records2.length} fileBytes=${fileEnd0} alignedFrom=${alignedFrom} aligned=${aligned} source=${alignedFrom > 0 ? "windowed" : "full"}`
13298
13298
  );
13299
13299
  }
13300
13300
  const drain = () => {
@@ -13473,10 +13473,10 @@ function collectTelegramStoreCensus() {
13473
13473
  const summaries = readConversationSummaries2(accountId);
13474
13474
  for (const channelKey of summaries.keys()) {
13475
13475
  out.conversations += 1;
13476
- const records = readConversation2(accountId, channelKey);
13477
- if (records.length > 0) out.served += 1;
13478
- out.records += records.length;
13479
- for (const r of records) {
13476
+ const records2 = readConversation2(accountId, channelKey);
13477
+ if (records2.length > 0) out.served += 1;
13478
+ out.records += records2.length;
13479
+ for (const r of records2) {
13480
13480
  if (r.origin === "inbound") out.inbound += 1;
13481
13481
  else if (r.origin === "agent") out.agent += 1;
13482
13482
  else if (r.origin === "operator-manual") out.operatorManual += 1;
@@ -31580,6 +31580,40 @@ async function fanOut(p) {
31580
31580
  return { reached, notReached, line };
31581
31581
  }
31582
31582
 
31583
+ // app/lib/channel-delivery/delivery-census.ts
31584
+ var WINDOW_MS2 = 60 * 60 * 1e3;
31585
+ var records = [];
31586
+ function noteTurnEnd(door, role, outcome, now) {
31587
+ records.push({ door, role, outcome, atMs: now });
31588
+ }
31589
+ function readDeliveryCensus(door, now) {
31590
+ records = records.filter((r) => now - r.atMs <= WINDOW_MS2);
31591
+ const mine = records.filter((r) => r.door === door);
31592
+ const undelivered = mine.filter((r) => r.outcome === "undelivered");
31593
+ return {
31594
+ turnEnds: mine.length,
31595
+ record: mine.filter((r) => r.outcome === "record").length,
31596
+ none: mine.filter((r) => r.outcome === "none").length,
31597
+ undelivered: undelivered.length,
31598
+ // The failure field. An admin answer that reaches nobody is Task 2459's
31599
+ // subject; a PUBLIC one is this task's, and the two must be separable on the
31600
+ // line or neither is diagnosable from it.
31601
+ undeliveredPublic: undelivered.filter((r) => r.role === "public").length,
31602
+ undeliveredAdmin: undelivered.filter((r) => r.role === "admin").length,
31603
+ roleAbsent: mine.filter((r) => r.outcome === "role-absent").length
31604
+ };
31605
+ }
31606
+ function buildDeliveryCensusLine(door, c) {
31607
+ return `[${door}-delivery-census] op=census windowMin=60 turnEnds=${c.turnEnds} record=${c.record} none=${c.none} undelivered=${c.undelivered} undeliveredPublic=${c.undeliveredPublic} undeliveredAdmin=${c.undeliveredAdmin} roleAbsent=${c.roleAbsent}`;
31608
+ }
31609
+ function runDeliveryCensus(now) {
31610
+ for (const door of ["telegram", "whatsapp"]) {
31611
+ const c = readDeliveryCensus(door, now);
31612
+ if (c.turnEnds === 0) continue;
31613
+ console.error(buildDeliveryCensusLine(door, c));
31614
+ }
31615
+ }
31616
+
31583
31617
  // app/lib/whatsapp/gateway/routes.ts
31584
31618
  var PARTY_PHONE_PATTERN = /^[+0-9][0-9 ()\-.]{4,31}$/;
31585
31619
  function createWaChannelRoutes(deps) {
@@ -31679,35 +31713,61 @@ function createWaChannelRoutes(deps) {
31679
31713
  console.error(
31680
31714
  `[whatsapp-native] op=turn-context sessionId=${sid} cacheReadTokens=${num2(body?.cacheReadTokens)} inputTokens=${num2(body?.inputTokens)} outputTokens=${num2(body?.outputTokens)} thinkingBlocks=${num2(body?.thinkingBlocks)} thinkingChars=${num2(body?.thinkingChars)}`
31681
31715
  );
31716
+ const role = body?.role;
31717
+ if (role !== "admin" && role !== "public") {
31718
+ const evicted = deps.hub.hasSubscriber(accountId, senderId);
31719
+ if (evicted) deps.hub.detach(accountId, senderId);
31720
+ console.error(
31721
+ `[whatsapp-native] op=turn-role-absent senderId=${senderId} accountId=${accountId} sessionId=${sid} segments=${segmentText(finalText).length} evicted=${evicted ? "yes" : "no"}`
31722
+ );
31723
+ noteTurnEnd("whatsapp", null, "role-absent", Date.now());
31724
+ return c.json({ error: "role must be 'admin' or 'public'" }, 400);
31725
+ }
31682
31726
  const segments = segmentText(finalText);
31683
- const decision = `sessionId=${sid} replied=${replied ? "yes" : "no"} interactive=${interactive ? "yes" : "no"} source=${source ?? "unset"} finalBytes=${finalBytes} queueDepth=${queueDepth} pendingTurns=${pendingTurns} consumed=${consumed2} segments=${segments.length}`;
31727
+ const decision = `sessionId=${sid} role=${role} replied=${replied ? "yes" : "no"} interactive=${interactive ? "yes" : "no"} source=${source ?? "unset"} finalBytes=${finalBytes} queueDepth=${queueDepth} pendingTurns=${pendingTurns} consumed=${consumed2} segments=${segments.length}`;
31684
31728
  if (segments.length === 0) {
31685
31729
  console.error(
31686
31730
  `[whatsapp-native] op=turn-answer-absent sessionId=${sid} interactive=${interactive ? "yes" : "no"} finalBytes=${finalBytes} queueDepth=${queueDepth}`
31687
31731
  );
31688
31732
  console.error(`[whatsapp-native] op=turn-end ${decision} delivered=none`);
31733
+ noteTurnEnd("whatsapp", role, "none", Date.now());
31689
31734
  return c.json({ ok: true, delivered: "none", segments: 0 });
31690
31735
  }
31691
- const targets = deps.fanTargetsFor?.(accountId, senderId) ?? [
31692
- {
31693
- channel: "whatsapp",
31694
- live: true,
31695
- send: async (text) => {
31696
- await deps.sendOutbound(accountId, senderId, text);
31736
+ if (role === "public") {
31737
+ let sent = 0;
31738
+ try {
31739
+ for (const segment of segments) {
31740
+ await deps.sendOutbound(accountId, senderId, segment);
31697
31741
  deps.onReplyDispatch?.(senderId, Date.now());
31742
+ sent += 1;
31698
31743
  }
31744
+ } catch {
31699
31745
  }
31700
- ];
31746
+ if (sent < segments.length) {
31747
+ console.error(
31748
+ `[whatsapp-native] op=turn-strand-anomaly senderId=${senderId} sessionId=${sid} role=public interactive=${interactive ? "yes" : "no"} segments=${segments.length} delivered=undelivered notReached=whatsapp`
31749
+ );
31750
+ console.error(`[whatsapp-native] op=turn-end ${decision} delivered=undelivered reached=none`);
31751
+ noteTurnEnd("whatsapp", role, "undelivered", Date.now());
31752
+ return c.json({ ok: true, delivered: "undelivered", segments: segments.length, reached: [], notReached: ["whatsapp"] });
31753
+ }
31754
+ console.error(`[whatsapp-native] op=turn-end ${decision} delivered=record bytes=${Buffer.byteLength(finalText, "utf8")} reached=whatsapp`);
31755
+ noteTurnEnd("whatsapp", role, "record", Date.now());
31756
+ return c.json({ ok: true, delivered: "record", segments: segments.length, reached: ["whatsapp"], notReached: [] });
31757
+ }
31758
+ const targets = deps.fanTargetsFor(accountId, senderId);
31701
31759
  const { reached, notReached, line } = await fanOut({ targets, segments });
31702
31760
  console.error(line);
31703
31761
  if (reached.length === 0) {
31704
31762
  console.error(
31705
- `[whatsapp-native] op=turn-strand-anomaly senderId=${senderId} sessionId=${sid} interactive=${interactive ? "yes" : "no"} segments=${segments.length} delivered=undelivered notReached=${notReached.join(",") || "none"}`
31763
+ `[whatsapp-native] op=turn-strand-anomaly senderId=${senderId} sessionId=${sid} role=${role} interactive=${interactive ? "yes" : "no"} segments=${segments.length} delivered=undelivered notReached=${notReached.join(",") || "none"}`
31706
31764
  );
31707
31765
  console.error(`[whatsapp-native] op=turn-end ${decision} delivered=undelivered reached=none`);
31766
+ noteTurnEnd("whatsapp", role, "undelivered", Date.now());
31708
31767
  return c.json({ ok: true, delivered: "undelivered", segments: segments.length, reached, notReached });
31709
31768
  }
31710
31769
  console.error(`[whatsapp-native] op=turn-end ${decision} delivered=record bytes=${Buffer.byteLength(finalText, "utf8")} reached=${reached.join(",")}`);
31770
+ noteTurnEnd("whatsapp", role, "record", Date.now());
31711
31771
  return c.json({ ok: true, delivered: "record", segments: segments.length, reached, notReached });
31712
31772
  });
31713
31773
  app78.post("/wa-channel/send-party", async (c) => {
@@ -31882,7 +31942,7 @@ var WaGateway = class {
31882
31942
  return createWaChannelRoutes({
31883
31943
  hub: this.hub,
31884
31944
  sendOutbound: (accountId, senderId, text) => this.sendOutbound(accountId, senderId, text),
31885
- ...this.deps.fanTargetsFor ? { fanTargetsFor: (accountId, senderId) => this.deps.fanTargetsFor(accountId, senderId) } : {},
31945
+ fanTargetsFor: (accountId, senderId) => this.deps.fanTargetsFor(accountId, senderId),
31886
31946
  onReady: (accountId, senderId) => {
31887
31947
  const count = this.hub.markReady(accountId, senderId, Date.now());
31888
31948
  if (count > 0) {
@@ -32173,9 +32233,9 @@ var WaGateway = class {
32173
32233
 
32174
32234
  // app/lib/whatsapp/gateway/duplicate-sender-audit.ts
32175
32235
  init_normalize();
32176
- function findDuplicateSenderGroups(records) {
32236
+ function findDuplicateSenderGroups(records2) {
32177
32237
  const buckets2 = /* @__PURE__ */ new Map();
32178
- for (const r of records) {
32238
+ for (const r of records2) {
32179
32239
  if (r.channel !== "whatsapp" || !r.senderId) continue;
32180
32240
  const canonical = normalizeTarget(r.senderId);
32181
32241
  if (!canonical) continue;
@@ -32196,9 +32256,9 @@ function logDuplicateSenderGroups(groups) {
32196
32256
  );
32197
32257
  }
32198
32258
  }
32199
- function findCrossAccountSenderGroups(records) {
32259
+ function findCrossAccountSenderGroups(records2) {
32200
32260
  const byCanonical = /* @__PURE__ */ new Map();
32201
- for (const r of records) {
32261
+ for (const r of records2) {
32202
32262
  if (r.channel !== "whatsapp" || !r.senderId || !r.accountId) continue;
32203
32263
  const canonical = normalizeTarget(r.senderId);
32204
32264
  if (!canonical) continue;
@@ -33848,28 +33908,60 @@ function createTelegramChannelRoutes(deps) {
33848
33908
  console.error(
33849
33909
  `[telegram-native] op=turn-context sessionId=${sid} cacheReadTokens=${num2(body?.cacheReadTokens)} inputTokens=${num2(body?.inputTokens)} outputTokens=${num2(body?.outputTokens)} thinkingBlocks=${num2(body?.thinkingBlocks)} thinkingChars=${num2(body?.thinkingChars)}`
33850
33910
  );
33911
+ const role = body?.role;
33912
+ if (role !== "admin" && role !== "public") {
33913
+ const evicted = deps.hub.hasSubscriber(key2);
33914
+ if (evicted) deps.hub.detach(key2);
33915
+ console.error(
33916
+ `[telegram-native] op=turn-role-absent key=${key2} sessionId=${sid} segments=${segmentText(finalText).length} evicted=${evicted ? "yes" : "no"}`
33917
+ );
33918
+ noteTurnEnd("telegram", null, "role-absent", Date.now());
33919
+ return c.json({ error: "role must be 'admin' or 'public'" }, 400);
33920
+ }
33851
33921
  const segments = segmentText(finalText);
33852
- const decision = `key=${key2} sessionId=${sid} replied=${replied ? "yes" : "no"} interactive=${interactive ? "yes" : "no"} segments=${segments.length}`;
33922
+ const decision = `key=${key2} sessionId=${sid} role=${role} replied=${replied ? "yes" : "no"} interactive=${interactive ? "yes" : "no"} segments=${segments.length}`;
33853
33923
  if (segments.length === 0) {
33854
33924
  console.error(
33855
33925
  `[telegram-native] op=turn-answer-absent key=${key2} sessionId=${sid} interactive=${interactive ? "yes" : "no"}`
33856
33926
  );
33857
33927
  console.error(`[telegram-native] op=turn-end ${decision} delivered=none`);
33928
+ noteTurnEnd("telegram", role, "none", Date.now());
33858
33929
  return c.json({ ok: true, delivered: "none", segments: 0 });
33859
33930
  }
33860
- const targets = deps.fanTargetsFor?.(key2) ?? [
33861
- { channel: "telegram", live: true, send: (text) => deps.sendOutbound(key2, text) }
33862
- ];
33931
+ if (role === "public") {
33932
+ let sent = 0;
33933
+ try {
33934
+ for (const segment of segments) {
33935
+ await deps.sendOutbound(key2, segment);
33936
+ sent += 1;
33937
+ }
33938
+ } catch {
33939
+ }
33940
+ if (sent < segments.length) {
33941
+ console.error(
33942
+ `[telegram-native] op=turn-strand-anomaly key=${key2} sessionId=${sid} role=public interactive=${interactive ? "yes" : "no"} segments=${segments.length} delivered=undelivered notReached=telegram`
33943
+ );
33944
+ console.error(`[telegram-native] op=turn-end ${decision} delivered=undelivered reached=none`);
33945
+ noteTurnEnd("telegram", role, "undelivered", Date.now());
33946
+ return c.json({ ok: true, delivered: "undelivered", segments: segments.length, reached: [], notReached: ["telegram"] });
33947
+ }
33948
+ console.error(`[telegram-native] op=turn-end ${decision} delivered=record bytes=${Buffer.byteLength(finalText, "utf8")} reached=telegram`);
33949
+ noteTurnEnd("telegram", role, "record", Date.now());
33950
+ return c.json({ ok: true, delivered: "record", segments: segments.length, reached: ["telegram"], notReached: [] });
33951
+ }
33952
+ const targets = deps.fanTargetsFor(key2);
33863
33953
  const { reached, notReached, line } = await fanOut({ targets, segments });
33864
33954
  console.error(line);
33865
33955
  if (reached.length === 0) {
33866
33956
  console.error(
33867
- `[telegram-native] op=turn-strand-anomaly key=${key2} sessionId=${sid} interactive=${interactive ? "yes" : "no"} segments=${segments.length} delivered=undelivered notReached=${notReached.join(",") || "none"}`
33957
+ `[telegram-native] op=turn-strand-anomaly key=${key2} sessionId=${sid} role=${role} interactive=${interactive ? "yes" : "no"} segments=${segments.length} delivered=undelivered notReached=${notReached.join(",") || "none"}`
33868
33958
  );
33869
33959
  console.error(`[telegram-native] op=turn-end ${decision} delivered=undelivered reached=none`);
33960
+ noteTurnEnd("telegram", role, "undelivered", Date.now());
33870
33961
  return c.json({ ok: true, delivered: "undelivered", segments: segments.length, reached, notReached });
33871
33962
  }
33872
33963
  console.error(`[telegram-native] op=turn-end ${decision} delivered=record bytes=${Buffer.byteLength(finalText, "utf8")} reached=${reached.join(",")}`);
33964
+ noteTurnEnd("telegram", role, "record", Date.now());
33873
33965
  return c.json({ ok: true, delivered: "record", segments: segments.length, reached, notReached });
33874
33966
  });
33875
33967
  return app78;
@@ -33994,7 +34086,7 @@ var TelegramGateway = class {
33994
34086
  return createTelegramChannelRoutes({
33995
34087
  hub: this.hub,
33996
34088
  sendOutbound: (key2, text) => this.sendOutbound(key2, text),
33997
- ...this.deps.fanTargetsFor ? { fanTargetsFor: (key2) => this.deps.fanTargetsFor(key2) } : {},
34089
+ fanTargetsFor: (key2) => this.deps.fanTargetsFor(key2),
33998
34090
  onReady: (key2) => {
33999
34091
  if (this.spawning.has(key2)) {
34000
34092
  if (!this.readyWhileSpawning.has(key2)) {
@@ -34397,6 +34489,48 @@ function startTelegramNativeFileFollower(input) {
34397
34489
 
34398
34490
  // server/index.ts
34399
34491
  init_admin_session_id();
34492
+
34493
+ // app/lib/channel-delivery/admin-fan-targets.ts
34494
+ function noTarget(origin, accountId, reason) {
34495
+ console.error(`[channel-fanout] op=no-target origin=${origin} accountId=${accountId} reason=${reason}`);
34496
+ }
34497
+ function adminFanTargets(p) {
34498
+ const send = p.deps.originLiveTarget(p.originKey);
34499
+ const origin = send ? { channel: p.origin, live: true, send } : { channel: p.origin, live: false };
34500
+ if (!send) noTarget(p.origin, p.accountId, "origin-not-live");
34501
+ if (!p.adminUserId) {
34502
+ noTarget(p.origin, p.accountId, "no-admin-user");
34503
+ return [origin];
34504
+ }
34505
+ let users;
34506
+ try {
34507
+ users = p.deps.readUsers();
34508
+ } catch {
34509
+ noTarget(p.origin, p.accountId, "users-read-failed");
34510
+ return [origin];
34511
+ }
34512
+ const entry = (users ?? []).find((u) => u.userId === p.adminUserId);
34513
+ if (!entry) {
34514
+ noTarget(p.origin, p.accountId, "no-entry");
34515
+ return [origin];
34516
+ }
34517
+ if (!entry.phone && !entry.telegram) {
34518
+ noTarget(p.origin, p.accountId, "no-bindings");
34519
+ return [origin];
34520
+ }
34521
+ const bound = [];
34522
+ if (entry.phone) {
34523
+ const wa = p.deps.whatsappLiveTarget(entry.phone);
34524
+ bound.push(wa ? { channel: "whatsapp", live: true, send: wa } : { channel: "whatsapp", live: false });
34525
+ }
34526
+ if (entry.telegram) {
34527
+ const tg = p.deps.telegramLiveTarget(entry.telegram);
34528
+ bound.push(tg ? { channel: "telegram", live: true, send: tg } : { channel: "telegram", live: false });
34529
+ }
34530
+ return [origin, ...bound.filter((t) => t.channel !== p.origin)];
34531
+ }
34532
+
34533
+ // server/index.ts
34400
34534
  init_pin_validator();
34401
34535
  init_webchat();
34402
34536
 
@@ -35335,7 +35469,15 @@ var waGateway = new WaGateway({
35335
35469
  sendToParty: (accountId, phone, text) => sendWhatsAppTextForRelay(accountId, phone, text),
35336
35470
  resolveSessionOverride: (accountId, senderId) => resolveChannelOverride(accountId, "whatsapp", senderId),
35337
35471
  // Task 2078 — one answer reaches every live door for this admin person.
35338
- fanTargetsFor: (accountId, senderId) => adminFanTargets("whatsapp", accountId, resolveAdminUserId({ accountId, senderPhone: senderId })),
35472
+ // Task 2459 the origin door is resolved from `senderId`, so it is present
35473
+ // even when the sender resolves to no admin at all.
35474
+ fanTargetsFor: (accountId, senderId) => adminFanTargets({
35475
+ origin: "whatsapp",
35476
+ originKey: senderId,
35477
+ accountId,
35478
+ adminUserId: resolveAdminUserId({ accountId, senderPhone: senderId }),
35479
+ deps: adminFanDeps("whatsapp", accountId)
35480
+ }),
35339
35481
  gatewayUrl: `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`,
35340
35482
  serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve49(process.env.MAXY_PLATFORM_ROOT ?? join59(__dirname, ".."), "services/whatsapp-channel/dist/server.js"),
35341
35483
  // Task 751 / 1390 — file delivery on the native channel. `maxyAccountId` (the
@@ -35520,16 +35662,16 @@ function runDuplicateSenderAudit() {
35520
35662
  const configDir = process.env.CLAUDE_CONFIG_DIR;
35521
35663
  if (!configDir) return;
35522
35664
  const projectsRoot = join59(configDir, "projects");
35523
- const records = [];
35665
+ const records2 = [];
35524
35666
  for (const { path: jsonlPath, isSubagent, archived } of enumerateJsonls(projectsRoot)) {
35525
35667
  if (isSubagent || archived) continue;
35526
35668
  const sessionId = jsonlPath.slice(jsonlPath.lastIndexOf("/") + 1).replace(/\.jsonl$/, "");
35527
35669
  const metaPath = jsonlPath.replace(/\.jsonl$/, ".meta.json");
35528
35670
  const meta = readSidecarMeta(metaPath);
35529
- records.push({ sessionId, channel: meta.channel, senderId: meta.senderId, accountId: meta.accountId });
35671
+ records2.push({ sessionId, channel: meta.channel, senderId: meta.senderId, accountId: meta.accountId });
35530
35672
  }
35531
- logDuplicateSenderGroups(findDuplicateSenderGroups(records));
35532
- logCrossAccountSenderGroups(findCrossAccountSenderGroups(records));
35673
+ logDuplicateSenderGroups(findDuplicateSenderGroups(records2));
35674
+ logCrossAccountSenderGroups(findCrossAccountSenderGroups(records2));
35533
35675
  const outbound = waGateway.takeOutboundAccountCensus();
35534
35676
  console.error(
35535
35677
  `[whatsapp-native] op=outbound-account-mismatch sends=${outbound.sends} mismatched=${outbound.mismatched}`
@@ -35687,8 +35829,18 @@ var telegramGateway = new TelegramGateway({
35687
35829
  const account = listValidAccounts().find(
35688
35830
  (a) => listBotEntries(a.config.telegram).some((e) => e.id === key2.slice(0, key2.indexOf(":")))
35689
35831
  );
35690
- if (!account) return [];
35691
- return adminFanTargets("telegram", account.accountId, resolveTelegramAdminUserId({ accountId: account.accountId, chatId }));
35832
+ if (!account) {
35833
+ console.error(`[channel-fanout] op=no-target origin=telegram key=${key2} accountId=none reason=no-account`);
35834
+ const send = telegramOriginTarget(key2);
35835
+ return [send ? { channel: "telegram", live: true, send } : { channel: "telegram", live: false }];
35836
+ }
35837
+ return adminFanTargets({
35838
+ origin: "telegram",
35839
+ originKey: key2,
35840
+ accountId: account.accountId,
35841
+ adminUserId: resolveTelegramAdminUserId({ accountId: account.accountId, chatId }),
35842
+ deps: adminFanDeps("telegram", account.accountId)
35843
+ });
35692
35844
  },
35693
35845
  gatewayUrl: telegramGatewayUrl(),
35694
35846
  serverPath: telegramServerPath(),
@@ -36792,6 +36944,14 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
36792
36944
  runTelegramStoreCensus();
36793
36945
  }
36794
36946
  });
36947
+ registerLoop({
36948
+ name: "channel-delivery-census",
36949
+ intervalMs: 6e4,
36950
+ firstRunDelayMs: 95e3,
36951
+ run: async () => {
36952
+ runDeliveryCensus(Date.now());
36953
+ }
36954
+ });
36795
36955
  registerLoop({
36796
36956
  name: "portal-uploads-pull",
36797
36957
  intervalMs: PORTAL_UPLOADS_PULL_INTERVAL_MS,
@@ -37002,12 +37162,12 @@ startAuthHealthHeartbeat();
37002
37162
  const loopHist = monitorEventLoopDelay({ resolution: 50 });
37003
37163
  loopHist.enable();
37004
37164
  const LOUD_THRESHOLD_MS = 1e3;
37005
- const WINDOW_MS2 = 3e4;
37165
+ const WINDOW_MS3 = 3e4;
37006
37166
  registerLoop({
37007
37167
  name: "event-loop-lag",
37008
- intervalMs: WINDOW_MS2,
37168
+ intervalMs: WINDOW_MS3,
37009
37169
  run: () => {
37010
- const w = readAndResetWindow(loopHist, WINDOW_MS2);
37170
+ const w = readAndResetWindow(loopHist, WINDOW_MS3);
37011
37171
  const line = formatLagWindow(w);
37012
37172
  if (w.maxMs >= LOUD_THRESHOLD_MS) console.error(line);
37013
37173
  else console.log(line);
@@ -37304,30 +37464,28 @@ try {
37304
37464
  console.error(`[graph-health] account-enumeration unavailable reason=${err instanceof Error ? err.message : String(err)}`);
37305
37465
  }
37306
37466
  var adminAdoptionSeen = /* @__PURE__ */ new Set();
37307
- function adminFanTargets(origin, accountId, adminUserId) {
37308
- if (!adminUserId) return [];
37309
- let entry;
37310
- try {
37311
- entry = (readUsersFile(USERS_FILE) ?? []).find((u) => u.userId === adminUserId);
37312
- } catch {
37313
- return [];
37314
- }
37315
- if (!entry) return [];
37316
- const targets = [];
37317
- if (entry.phone) {
37318
- const send = waGateway.liveReplyTarget(accountId, entry.phone);
37319
- targets.push(
37320
- send ? { channel: "whatsapp", live: true, send: async (t) => {
37321
- await send(t);
37322
- waGateway.noteReplyDispatch(entry.phone, Date.now());
37323
- } } : { channel: "whatsapp", live: false }
37324
- );
37325
- }
37326
- if (entry.telegram) {
37327
- const send = telegramBotIdsFor(accountId).map((botId) => telegramGateway.liveReplyTarget(`${botId}:${entry.telegram}`)).find((t) => t !== null);
37328
- targets.push(send ? { channel: "telegram", live: true, send } : { channel: "telegram", live: false });
37329
- }
37330
- return targets.filter((t) => t.channel !== origin || t.live);
37467
+ function adminFanDeps(origin, accountId) {
37468
+ return {
37469
+ readUsers: () => readUsersFile(USERS_FILE),
37470
+ originLiveTarget: (originKey) => origin === "whatsapp" ? whatsappSender(accountId, originKey) : telegramOriginTarget(originKey),
37471
+ whatsappLiveTarget: (phone) => whatsappSender(accountId, phone),
37472
+ // The telegram reply map is keyed on the `<botId>:<chatId>` composite, and
37473
+ // the bot is not derivable from the person. Every bot this account runs is
37474
+ // tried, and the first live one wins: a person reachable on two of this
37475
+ // account's bots hears once, on whichever is attached.
37476
+ telegramLiveTarget: (chatId) => telegramBotIdsFor(accountId).map((botId) => telegramGateway.liveReplyTarget(`${botId}:${chatId}`)).find((t) => t !== null) ?? null
37477
+ };
37478
+ }
37479
+ function whatsappSender(accountId, key2) {
37480
+ const send = waGateway.liveReplyTarget(accountId, key2);
37481
+ if (!send) return null;
37482
+ return async (text) => {
37483
+ await send(text);
37484
+ waGateway.noteReplyDispatch(key2, Date.now());
37485
+ };
37486
+ }
37487
+ function telegramOriginTarget(key2) {
37488
+ return telegramGateway.liveReplyTarget(key2);
37331
37489
  }
37332
37490
  function telegramBotIdsFor(accountId) {
37333
37491
  try {