@rubytech/create-maxy-code 0.1.478 → 0.1.479

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 (100) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/docs/superpowers/plans/2026-07-20-task-1789-reseat-channel-row-fork.md +1244 -0
  3. package/payload/platform/docs/superpowers/plans/2026-07-20-task-1818-loop-gate-app-routes.md +462 -0
  4. package/payload/platform/docs/superpowers/plans/2026-07-20-task-1819-top-level-label-allowlist.md +321 -0
  5. package/payload/platform/docs/superpowers/plans/2026-07-20-task-1831-data-portal-class-derived-allowlist.md +2177 -0
  6. package/payload/platform/docs/superpowers/specs/2026-07-20-task-1789-reseat-channel-row-fork-design.md +201 -0
  7. package/payload/platform/docs/superpowers/specs/2026-07-20-task-1818-loop-gate-app-routes-design.md +217 -0
  8. package/payload/platform/docs/superpowers/specs/2026-07-20-task-1819-top-level-label-allowlist-design.md +80 -0
  9. package/payload/platform/docs/superpowers/specs/2026-07-20-task-1831-data-portal-class-derived-allowlist-design.md +163 -0
  10. package/payload/platform/lib/graph-style/dist/index.d.ts.map +1 -1
  11. package/payload/platform/lib/graph-style/dist/index.js +22 -0
  12. package/payload/platform/lib/graph-style/dist/index.js.map +1 -1
  13. package/payload/platform/lib/graph-style/src/__tests__/parity.test.ts +6 -0
  14. package/payload/platform/lib/graph-style/src/index.ts +20 -0
  15. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +13 -1
  16. package/payload/platform/plugins/cloudflare/PLUGIN.md +26 -1
  17. package/payload/platform/plugins/cloudflare/bin/d1-http.mjs +80 -0
  18. package/payload/platform/plugins/cloudflare/bin/portal-enrol.mjs +17 -2
  19. package/payload/platform/plugins/cloudflare/bin/portal-index-push.mjs +357 -0
  20. package/payload/platform/plugins/cloudflare/bin/schema-exposed-dirs.mjs +103 -0
  21. package/payload/platform/plugins/cloudflare/mcp/__tests__/authorize.test.ts +2 -2
  22. package/payload/platform/plugins/cloudflare/mcp/__tests__/d1-http.test.ts +96 -0
  23. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-directory-listing.test.ts +120 -0
  24. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-enrol.test.ts +59 -4
  25. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-index-push.test.ts +176 -0
  26. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-index-state-contract.test.ts +75 -0
  27. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-indexed-download.test.ts +131 -0
  28. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-sign.test.ts +68 -0
  29. package/payload/platform/plugins/cloudflare/mcp/__tests__/schema-exposed-dirs.test.ts +88 -0
  30. package/payload/platform/plugins/cloudflare/mcp/__tests__/template-config.test.ts +39 -0
  31. package/payload/platform/plugins/cloudflare/skills/data-portal/SKILL.md +68 -1
  32. package/payload/platform/plugins/cloudflare/skills/data-portal/schema.sql +30 -1
  33. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/portal-sign.mjs +87 -0
  34. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/session.ts +8 -4
  35. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/types.ts +10 -0
  36. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/download.ts +107 -1
  37. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/files.ts +82 -2
  38. package/payload/platform/plugins/cloudflare/skills/data-portal/template/index.html +9 -0
  39. package/payload/platform/plugins/cloudflare/skills/data-portal/template/portal.css +56 -0
  40. package/payload/platform/plugins/cloudflare/skills/data-portal/template/portal.js +98 -2
  41. package/payload/platform/plugins/cloudflare/skills/data-portal/template/wrangler.toml +12 -0
  42. package/payload/platform/plugins/docs/references/cloudflare.md +12 -0
  43. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/portal-index-audit.test.d.ts +2 -0
  44. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/portal-index-audit.test.d.ts.map +1 -0
  45. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/portal-index-audit.test.js +66 -0
  46. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/portal-index-audit.test.js.map +1 -0
  47. package/payload/platform/plugins/scheduling/mcp/dist/lib/portal-index-audit.d.ts +41 -0
  48. package/payload/platform/plugins/scheduling/mcp/dist/lib/portal-index-audit.d.ts.map +1 -0
  49. package/payload/platform/plugins/scheduling/mcp/dist/lib/portal-index-audit.js +75 -0
  50. package/payload/platform/plugins/scheduling/mcp/dist/lib/portal-index-audit.js.map +1 -0
  51. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +122 -0
  52. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
  53. package/payload/server/public/activity.html +8 -6
  54. package/payload/server/public/assets/{AdminLoginScreens-DR5eLF-a.js → AdminLoginScreens-DkxQZnvB.js} +1 -1
  55. package/payload/server/public/assets/AdminShell-Cv_nIFGe.js +2 -0
  56. package/payload/server/public/assets/Checkbox-CsTWK8mm.js +1 -0
  57. package/payload/server/public/assets/activity-BbnTiVBT.js +1 -0
  58. package/payload/server/public/assets/{admin-D2KLZnQW.js → admin-BZY3i_Ff.js} +1 -1
  59. package/payload/server/public/assets/{browser-CDWqEiCD.js → browser-BbIiSFKr.js} +1 -1
  60. package/payload/server/public/assets/{calendar-BG7fOpbB.js → calendar-BvxeTHvQ.js} +1 -1
  61. package/payload/server/public/assets/chat-DUKxufRk.js +1 -0
  62. package/payload/server/public/assets/chevron-left-BtfJD6LL.js +1 -0
  63. package/payload/server/public/assets/data-hl1wa-Ax.js +1 -0
  64. package/payload/server/public/assets/{graph-DRloNic4.js → graph-CDSGRoeb.js} +1 -1
  65. package/payload/server/public/assets/graph-labels-C5qLwnWA.js +1 -0
  66. package/payload/server/public/assets/{maximize-2-CzDaBugs.js → maximize-2-CAzH8dED.js} +1 -1
  67. package/payload/server/public/assets/{operator-BZeeyait.js → operator-BK7r8DAB.js} +1 -1
  68. package/payload/server/public/assets/{page-ef-6rRPL.js → page-B5YngOrK.js} +1 -1
  69. package/payload/server/public/assets/{page-B0obcg3b.js → page-BsCS0Dmc.js} +1 -1
  70. package/payload/server/public/assets/{public-9D3MMOkC.js → public-C1Pz-IBs.js} +1 -1
  71. package/payload/server/public/assets/{rotate-ccw-DXEKUhXP.js → rotate-ccw-UDln4Tou.js} +1 -1
  72. package/payload/server/public/assets/{tasks-BB0L7e5a.js → tasks-B-cLsn3p.js} +1 -1
  73. package/payload/server/public/assets/{time-entry-format-CsYAjplS.js → time-entry-format-DPOBIE18.js} +1 -1
  74. package/payload/server/public/assets/{triangle-alert-0lSBIUg-.js → triangle-alert-Dz-CZKo5.js} +1 -1
  75. package/payload/server/public/assets/{useCopyFeedback-jTBQ2h0V.js → useCopyFeedback-wtUHGohk.js} +1 -1
  76. package/payload/server/public/assets/{useSelectionMode-BooArySZ.js → useSelectionMode-sph7AQT5.js} +1 -1
  77. package/payload/server/public/assets/useSubAccountSwitcher-BRIluIKf.css +1 -0
  78. package/payload/server/public/assets/{useVoiceRecorder-PUT8q_sm.js → useVoiceRecorder-DN4jYQ0A.js} +1 -1
  79. package/payload/server/public/brand-defaults.css +1 -0
  80. package/payload/server/public/browser.html +5 -5
  81. package/payload/server/public/calendar.html +6 -6
  82. package/payload/server/public/chat.html +12 -12
  83. package/payload/server/public/data.html +11 -11
  84. package/payload/server/public/graph.html +11 -11
  85. package/payload/server/public/index.html +14 -14
  86. package/payload/server/public/operator.html +14 -14
  87. package/payload/server/public/privacy.html +1 -0
  88. package/payload/server/public/public.html +12 -12
  89. package/payload/server/public/tasks.html +6 -5
  90. package/payload/server/public/vnc-popout.html +1 -0
  91. package/payload/server/server.js +741 -366
  92. package/payload/server/public/assets/AdminShell-D9zaUxPd.js +0 -2
  93. package/payload/server/public/assets/Checkbox-DcbbAIe5.js +0 -1
  94. package/payload/server/public/assets/activity-C9P5NjEZ.js +0 -1
  95. package/payload/server/public/assets/chat-RyH-WYQC.js +0 -1
  96. package/payload/server/public/assets/chevron-left-DmXCr6nB.js +0 -1
  97. package/payload/server/public/assets/data-BaY_LkLR.js +0 -1
  98. package/payload/server/public/assets/graph-labels-DD-GqS4v.js +0 -1
  99. package/payload/server/public/assets/useSubAccountSwitcher-D9knHOKq.css +0 -1
  100. /package/payload/server/public/assets/{useSubAccountSwitcher-BZnzc5C3.js → useSubAccountSwitcher-v8LyBzZN.js} +0 -0
@@ -652,7 +652,17 @@ var require_dist3 = __commonJS({
652
652
  Risk: "#C15B57",
653
653
  Source: "#2F8FA6",
654
654
  Finding: "#4FA389",
655
- Hypothesis: "#9A8C3E"
655
+ Hypothesis: "#9A8C3E",
656
+ // Ledger cash movement (schema-base.md, added by 7b225f814). One movement
657
+ // of money attached to no invoice — operator-entry with no parent to drill
658
+ // down from, so it is a top-level chip. Burnished olive-gold sits in the
659
+ // money register beside Invoice's brown without colliding with it.
660
+ //
661
+ // Deliberately appended at the END rather than beside the ledger family:
662
+ // SHAPE_BY_LABEL assigns shapes by index position in ALL_GRAPH_LABELS
663
+ // (this object's key order), so a mid-table insert would silently change
664
+ // the rendered shape of every label after it.
665
+ CashEntry: "#7D5A2B"
656
666
  };
657
667
  exports.FALLBACK_LABEL_COLOUR = "#94A3B8";
658
668
  exports.ALL_GRAPH_LABELS = Object.freeze(Object.keys(exports.GRAPH_LABEL_COLOURS));
@@ -749,7 +759,8 @@ var require_dist3 = __commonJS({
749
759
  Risk: '<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"></path><path d="M12 9v4"></path><path d="M12 17h.01"></path>',
750
760
  Source: '<path d="M12 7v14"></path><path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"></path>',
751
761
  Finding: '<path d="M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5"></path><path d="M9 18h6"></path><path d="M10 22h4"></path>',
752
- Hypothesis: '<path d="M14 2v6a2 2 0 0 0 .245.96l5.51 10.08A2 2 0 0 1 18 22H6a2 2 0 0 1-1.755-2.96l5.51-10.08A2 2 0 0 0 10 8V2"></path><path d="M6.453 15h11.094"></path><path d="M8.5 2h7"></path>'
762
+ Hypothesis: '<path d="M14 2v6a2 2 0 0 0 .245.96l5.51 10.08A2 2 0 0 1 18 22H6a2 2 0 0 1-1.755-2.96l5.51-10.08A2 2 0 0 0 10 8V2"></path><path d="M6.453 15h11.094"></path><path d="M8.5 2h7"></path>',
763
+ CashEntry: '<rect width="20" height="12" x="2" y="6" rx="2"></rect><circle cx="12" cy="12" r="2"></circle><path d="M6 12h.01"></path><path d="M18 12h.01"></path>'
753
764
  };
754
765
  function iconForLabel2(label) {
755
766
  return Object.prototype.hasOwnProperty.call(LABEL_ICONS, label) ? LABEL_ICONS[label] : null;
@@ -845,6 +856,11 @@ var require_dist3 = __commonJS({
845
856
  if (clientName.length > 0) {
846
857
  return clientName.length > 24 ? clientName.slice(0, 24) + "\u2026" : clientName;
847
858
  }
859
+ } else if (primaryLabel2 === "CashEntry") {
860
+ const desc = typeof props.description === "string" ? props.description : "";
861
+ if (desc.length > 0) {
862
+ return desc.length > 24 ? desc.slice(0, 24) + "\u2026" : desc;
863
+ }
848
864
  } else if (primaryLabel2 === "Message") {
849
865
  const role = typeof props.role === "string" ? props.role : "";
850
866
  const roleShort = role === "user" ? "user" : role === "assistant" ? "asst" : role === "system" ? "sys" : role === "tool" ? "tool" : null;
@@ -918,6 +934,10 @@ var require_dist3 = __commonJS({
918
934
  const clientName = typeof props.clientName === "string" ? props.clientName : "";
919
935
  if (clientName.length > 0)
920
936
  return clientName;
937
+ } else if (primaryLabel2 === "CashEntry") {
938
+ const desc = typeof props.description === "string" ? props.description : "";
939
+ if (desc.length > 0)
940
+ return desc;
921
941
  }
922
942
  for (const k of ["name", "title", "summary", "givenName", "subject", "text"]) {
923
943
  const v = props[k];
@@ -1291,8 +1311,8 @@ var require_dist4 = __commonJS({
1291
1311
  let edgesCreated = 0;
1292
1312
  for (const rel of relationships) {
1293
1313
  const type = rel.type.replace(/`/g, "");
1294
- const q = rel.direction === "outgoing" ? `MATCH (a), (b) WHERE elementId(a) = $from AND elementId(b) = $to CREATE (a)-[:\`${type}\`]->(b)` : `MATCH (a), (b) WHERE elementId(a) = $from AND elementId(b) = $to CREATE (b)-[:\`${type}\`]->(a)`;
1295
- const r = await tx.run(q, { from: nodeId, to: rel.targetNodeId });
1314
+ const q2 = rel.direction === "outgoing" ? `MATCH (a), (b) WHERE elementId(a) = $from AND elementId(b) = $to CREATE (a)-[:\`${type}\`]->(b)` : `MATCH (a), (b) WHERE elementId(a) = $from AND elementId(b) = $to CREATE (b)-[:\`${type}\`]->(a)`;
1315
+ const r = await tx.run(q2, { from: nodeId, to: rel.targetNodeId });
1296
1316
  const created = r.summary.counters.updates().relationshipsCreated;
1297
1317
  if (created === 0) {
1298
1318
  process.stderr.write(`[graph-write] reject reason=unresolved-target-on-create labels=${labelCsv} agent=${agentLabel} relType=${rel.type} targetId=${rel.targetNodeId}
@@ -1540,10 +1560,10 @@ var serveStatic = (options = { root: "" }) => {
1540
1560
  };
1541
1561
 
1542
1562
  // server/index.ts
1543
- import { readFileSync as readFileSync41, existsSync as existsSync38, watchFile, readdirSync as readdirSync26, statSync as statSync17 } from "fs";
1563
+ import { readFileSync as readFileSync42, existsSync as existsSync38, watchFile, readdirSync as readdirSync26, statSync as statSync17 } from "fs";
1544
1564
  import { spawn as spawn3 } from "child_process";
1545
1565
  import { createHash as createHash7 } from "crypto";
1546
- import { resolve as resolve36, join as join42, basename as basename12 } from "path";
1566
+ import { resolve as resolve37, join as join44, basename as basename12 } from "path";
1547
1567
  import { homedir as homedir4 } from "os";
1548
1568
  import { monitorEventLoopDelay } from "perf_hooks";
1549
1569
 
@@ -1958,14 +1978,14 @@ function readAllConversationsCached(accountId) {
1958
1978
  for (const file of dir) {
1959
1979
  if (!file.endsWith(".jsonl")) continue;
1960
1980
  const path3 = join4(STORE_ROOT(), accountId, file);
1961
- let stat8;
1981
+ let stat9;
1962
1982
  try {
1963
- stat8 = statSync3(path3);
1983
+ stat9 = statSync3(path3);
1964
1984
  } catch {
1965
1985
  continue;
1966
1986
  }
1967
1987
  const hit = idCache.get(path3);
1968
- if (hit && hit.size === stat8.size && hit.mtimeMs === stat8.mtimeMs) {
1988
+ if (hit && hit.size === stat9.size && hit.mtimeMs === stat9.mtimeMs) {
1969
1989
  out.set(file, hit.ids);
1970
1990
  continue;
1971
1991
  }
@@ -1982,7 +2002,7 @@ function readAllConversationsCached(accountId) {
1982
2002
  } catch {
1983
2003
  continue;
1984
2004
  }
1985
- idCache.set(path3, { size: stat8.size, mtimeMs: stat8.mtimeMs, ids: parsed });
2005
+ idCache.set(path3, { size: stat9.size, mtimeMs: stat9.mtimeMs, ids: parsed });
1986
2006
  out.set(file, parsed);
1987
2007
  }
1988
2008
  return out;
@@ -2816,7 +2836,7 @@ var credsSaveQueue = Promise.resolve();
2816
2836
  async function drainCredsSaveQueue(timeoutMs = 5e3) {
2817
2837
  console.error(`${TAG6} draining credential save queue\u2026`);
2818
2838
  const timer = new Promise(
2819
- (resolve37) => setTimeout(() => resolve37("timeout"), timeoutMs)
2839
+ (resolve38) => setTimeout(() => resolve38("timeout"), timeoutMs)
2820
2840
  );
2821
2841
  const result = await Promise.race([
2822
2842
  credsSaveQueue.then(() => "drained"),
@@ -2993,11 +3013,11 @@ async function createWaSocket(opts) {
2993
3013
  return sock;
2994
3014
  }
2995
3015
  async function waitForConnection(sock) {
2996
- return new Promise((resolve37, reject) => {
3016
+ return new Promise((resolve38, reject) => {
2997
3017
  const handler = (update) => {
2998
3018
  if (update.connection === "open") {
2999
3019
  sock.ev.off("connection.update", handler);
3000
- resolve37();
3020
+ resolve38();
3001
3021
  }
3002
3022
  if (update.connection === "close") {
3003
3023
  sock.ev.off("connection.update", handler);
@@ -3105,7 +3125,7 @@ function isTimeoutError(err) {
3105
3125
  return Boolean(err?.__maxyTimeout);
3106
3126
  }
3107
3127
  function withTimeout(label, promise, timeoutMs) {
3108
- return new Promise((resolve37, reject) => {
3128
+ return new Promise((resolve38, reject) => {
3109
3129
  const timer = setTimeout(() => {
3110
3130
  const err = new Error(`${label} timed out after ${timeoutMs}ms`);
3111
3131
  err.__maxyTimeout = true;
@@ -3115,7 +3135,7 @@ function withTimeout(label, promise, timeoutMs) {
3115
3135
  promise.then(
3116
3136
  (value) => {
3117
3137
  clearTimeout(timer);
3118
- resolve37(value);
3138
+ resolve38(value);
3119
3139
  },
3120
3140
  (err) => {
3121
3141
  clearTimeout(timer);
@@ -4029,15 +4049,15 @@ function getChannelActivity(accountId) {
4029
4049
 
4030
4050
  // app/lib/whatsapp/outbound/send.ts
4031
4051
  var TAG10 = "[whatsapp:outbound]";
4032
- function buildQuoted(q) {
4052
+ function buildQuoted(q2) {
4033
4053
  return {
4034
4054
  key: {
4035
- remoteJid: q.remoteJid,
4036
- id: q.id,
4037
- fromMe: q.fromMe,
4038
- ...q.participant ? { participant: q.participant } : {}
4055
+ remoteJid: q2.remoteJid,
4056
+ id: q2.id,
4057
+ fromMe: q2.fromMe,
4058
+ ...q2.participant ? { participant: q2.participant } : {}
4039
4059
  },
4040
- message: { conversation: q.body ?? "" }
4060
+ message: { conversation: q2.body ?? "" }
4041
4061
  };
4042
4062
  }
4043
4063
  async function sendTextMessage(sock, to, text, opts) {
@@ -4155,8 +4175,8 @@ async function persistWhatsAppMessage(input) {
4155
4175
  const { givenName, familyName } = splitName(input.pushName);
4156
4176
  const prev = sessionWriteLocks.get(input.cacheKey);
4157
4177
  let release;
4158
- const mine = new Promise((resolve37) => {
4159
- release = resolve37;
4178
+ const mine = new Promise((resolve38) => {
4179
+ release = resolve38;
4160
4180
  });
4161
4181
  const chained = (prev ?? Promise.resolve()).then(() => mine);
4162
4182
  sessionWriteLocks.set(input.cacheKey, chained);
@@ -5860,11 +5880,11 @@ async function connectWithReconnect(conn) {
5860
5880
  console.error(
5861
5881
  `${TAG17} reconnecting account=${conn.accountId} in ${delay}ms (attempt ${decision.nextAttempts}/${maxAttempts})`
5862
5882
  );
5863
- await new Promise((resolve37) => {
5864
- const timer = setTimeout(resolve37, delay);
5883
+ await new Promise((resolve38) => {
5884
+ const timer = setTimeout(resolve38, delay);
5865
5885
  conn.abortController.signal.addEventListener("abort", () => {
5866
5886
  clearTimeout(timer);
5867
- resolve37();
5887
+ resolve38();
5868
5888
  }, { once: true });
5869
5889
  });
5870
5890
  }
@@ -5872,16 +5892,16 @@ async function connectWithReconnect(conn) {
5872
5892
  }
5873
5893
  }
5874
5894
  function waitForDisconnectEvent(conn) {
5875
- return new Promise((resolve37) => {
5895
+ return new Promise((resolve38) => {
5876
5896
  if (!conn.sock) {
5877
- resolve37();
5897
+ resolve38();
5878
5898
  return;
5879
5899
  }
5880
5900
  const sock = conn.sock;
5881
5901
  const handler = (update) => {
5882
5902
  if (update.connection === "close") {
5883
5903
  sock.ev.off("connection.update", handler);
5884
- resolve37();
5904
+ resolve38();
5885
5905
  }
5886
5906
  };
5887
5907
  sock.ev.on("connection.update", handler);
@@ -6291,8 +6311,8 @@ async function handleInboundMessage(conn, msg) {
6291
6311
  const conversationKey = isGroup ? remoteJid : senderPhone;
6292
6312
  const debounceKey = `${conn.accountId}:${conversationKey}:${senderPhone}`;
6293
6313
  let resolvePending;
6294
- const sttPending = new Promise((resolve37) => {
6295
- resolvePending = resolve37;
6314
+ const sttPending = new Promise((resolve38) => {
6315
+ resolvePending = resolve38;
6296
6316
  });
6297
6317
  if (conn.debouncer) conn.debouncer.registerPending(debounceKey, sttPending);
6298
6318
  try {
@@ -6720,20 +6740,20 @@ function buildX11Env(chromiumWrapperPath, transport = "vnc") {
6720
6740
 
6721
6741
  // server/routes/health.ts
6722
6742
  function checkPort(port2, timeoutMs = 500) {
6723
- return new Promise((resolve37) => {
6743
+ return new Promise((resolve38) => {
6724
6744
  const socket = createConnection2(port2, "127.0.0.1");
6725
6745
  socket.setTimeout(timeoutMs);
6726
6746
  socket.once("connect", () => {
6727
6747
  socket.destroy();
6728
- resolve37(true);
6748
+ resolve38(true);
6729
6749
  });
6730
6750
  socket.once("error", () => {
6731
6751
  socket.destroy();
6732
- resolve37(false);
6752
+ resolve38(false);
6733
6753
  });
6734
6754
  socket.once("timeout", () => {
6735
6755
  socket.destroy();
6736
- resolve37(false);
6756
+ resolve38(false);
6737
6757
  });
6738
6758
  });
6739
6759
  }
@@ -6988,8 +7008,8 @@ function webchatTurnTimeoutMs() {
6988
7008
  return Number(process.env.WEBCHAT_TURN_TIMEOUT_MS ?? String(2 * 6e4));
6989
7009
  }
6990
7010
  function createChatRoutes(deps) {
6991
- const app64 = new Hono();
6992
- app64.post("/", async (c) => {
7011
+ const app65 = new Hono();
7012
+ app65.post("/", async (c) => {
6993
7013
  console.log(`[chat-route] entered route=public method=POST`);
6994
7014
  const contentType = c.req.header("content-type") ?? "";
6995
7015
  const account = resolveAccount();
@@ -7253,7 +7273,7 @@ ${result.result.text}` : result.result.text;
7253
7273
  }
7254
7274
  });
7255
7275
  });
7256
- return app64;
7276
+ return app65;
7257
7277
  }
7258
7278
 
7259
7279
  // app/lib/channel-pty-bridge/admin-session-id.ts
@@ -7287,8 +7307,8 @@ function isLoopbackAddr(addr) {
7287
7307
  return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
7288
7308
  }
7289
7309
  function createScheduleInjectRoutes(deps) {
7290
- const app64 = new Hono();
7291
- app64.post("/", async (c) => {
7310
+ const app65 = new Hono();
7311
+ app65.post("/", async (c) => {
7292
7312
  const env = c.env;
7293
7313
  const remoteAddr = env?.incoming?.socket?.remoteAddress ?? "";
7294
7314
  if (!isLoopbackAddr(remoteAddr)) {
@@ -7389,7 +7409,7 @@ function createScheduleInjectRoutes(deps) {
7389
7409
  console.error(`${TAG20} op=inject-spawn eventId=${eventId} sessionId=${sessionId} result=ok status=-`);
7390
7410
  return c.json({ ok: true }, 200);
7391
7411
  });
7392
- return app64;
7412
+ return app65;
7393
7413
  }
7394
7414
 
7395
7415
  // server/index.ts
@@ -8022,8 +8042,8 @@ async function startLogin(opts) {
8022
8042
  await clearAuth(authDir);
8023
8043
  let resolveCode = null;
8024
8044
  let rejectCode = null;
8025
- const codePromise = new Promise((resolve37, reject) => {
8026
- resolveCode = resolve37;
8045
+ const codePromise = new Promise((resolve38, reject) => {
8046
+ resolveCode = resolve38;
8027
8047
  rejectCode = reject;
8028
8048
  });
8029
8049
  const codeTimer = setTimeout(
@@ -11337,12 +11357,12 @@ app5.get("/store-stream", requireAdminSession, (c) => {
11337
11357
  ...reactions && reactions.length > 0 ? { reactions } : {}
11338
11358
  };
11339
11359
  if (r.quotedId) {
11340
- const q = byMsgKeyId.get(r.quotedId);
11360
+ const q2 = byMsgKeyId.get(r.quotedId);
11341
11361
  turn = {
11342
11362
  ...turn,
11343
- quoted: q ? { body: q.body, sender: q.senderName ?? (q.fromMe ? null : q.senderTelephone || null), fromMe: q.fromMe } : { body: "", sender: r.quotedSender, fromMe: false }
11363
+ quoted: q2 ? { body: q2.body, sender: q2.senderName ?? (q2.fromMe ? null : q2.senderTelephone || null), fromMe: q2.fromMe } : { body: "", sender: r.quotedSender, fromMe: false }
11344
11364
  };
11345
- console.log(`[wa-reader] op=store-quote msgId=${r.msgKeyId} quoted=${!!q}`);
11365
+ console.log(`[wa-reader] op=store-quote msgId=${r.msgKeyId} quoted=${!!q2}`);
11346
11366
  }
11347
11367
  if (r.attachmentId) {
11348
11368
  const meta = readAttachmentMeta(join19(uploadsBase, r.attachmentId));
@@ -12252,10 +12272,7 @@ function locateSidecar(sessionId) {
12252
12272
  }
12253
12273
  for (const slug of slugs) {
12254
12274
  const metaPath = join23(projectsRoot, slug, `${sessionId}.meta.json`);
12255
- if (existsSync15(metaPath)) {
12256
- const meta = readSidecarMeta(metaPath);
12257
- return { channel: meta.channel, adminUserId: meta.adminUserId, accountId: meta.accountId };
12258
- }
12275
+ if (existsSync15(metaPath)) return readSidecarMeta(metaPath);
12259
12276
  }
12260
12277
  return null;
12261
12278
  }
@@ -12268,6 +12285,13 @@ function readTargetOwner(sessionId) {
12268
12285
  }
12269
12286
  return { found: false, owner: null };
12270
12287
  }
12288
+ function readSourceRowMeta(sessionId) {
12289
+ const sidecar = locateSidecar(sessionId);
12290
+ if (sidecar !== null) return sidecar;
12291
+ const { projectDir } = locateSession(sessionId);
12292
+ if (projectDir !== null) return readSidecarMeta(join23(projectDir, `${sessionId}.meta.json`));
12293
+ return null;
12294
+ }
12271
12295
  function ownedByRequester(owner, requesterUserId, primaryUserId) {
12272
12296
  if (requesterUserId === null) return false;
12273
12297
  return owner === requesterUserId || owner === null && requesterUserId === primaryUserId;
@@ -12476,8 +12500,8 @@ async function reapplyLeversViaManager() {
12476
12500
  var WEBCHAT_SEND_TURN_WINDOW_MS = Number(process.env.WEBCHAT_SEND_TURN_WINDOW_MS ?? String(15e3));
12477
12501
  var sendSeq = 0;
12478
12502
  function createWebchatRoutes(deps) {
12479
- const app64 = new Hono();
12480
- app64.post("/send", requireAdminSession, async (c) => {
12503
+ const app65 = new Hono();
12504
+ app65.post("/send", requireAdminSession, async (c) => {
12481
12505
  let accountId;
12482
12506
  try {
12483
12507
  accountId = resolvePlatformAccountId();
@@ -12689,7 +12713,7 @@ ${note}` : note;
12689
12713
  if (turnTimer.unref) turnTimer.unref();
12690
12714
  return c.json({ ok: true });
12691
12715
  });
12692
- app64.post("/interrupt", requireAdminSession, async (c) => {
12716
+ app65.post("/interrupt", requireAdminSession, async (c) => {
12693
12717
  const cacheKey = c.get("cacheKey");
12694
12718
  const requesterUserId = (cacheKey ? getUserIdForSession(cacheKey) : void 0) ?? null;
12695
12719
  const primaryUserId = resolvePrimaryAdminUserId();
@@ -12717,7 +12741,7 @@ ${note}` : note;
12717
12741
  }
12718
12742
  return c.json({ ok: true, stopped: outcome.stopped, intId: outcome.intId, deadChild: outcome.deadChild }, 200);
12719
12743
  });
12720
- app64.post("/settings", requireAdminSession, async (c) => {
12744
+ app65.post("/settings", requireAdminSession, async (c) => {
12721
12745
  const body = await c.req.json().catch(() => null);
12722
12746
  const op = body?.op;
12723
12747
  const value = body?.value;
@@ -12757,7 +12781,7 @@ ${note}` : note;
12757
12781
  console.log(`[webchat:settings] op=${op} outcome=accepted value=${value} settingsApplied=${settingsApplied}`);
12758
12782
  return c.json({ ok: true, settingsApplied });
12759
12783
  });
12760
- app64.get("/session", requireAdminSession, async (c) => {
12784
+ app65.get("/session", requireAdminSession, async (c) => {
12761
12785
  let accountId;
12762
12786
  try {
12763
12787
  accountId = resolvePlatformAccountId();
@@ -12835,7 +12859,7 @@ ${note}` : note;
12835
12859
  const indicators = await fetchComposerIndicators(sessionId);
12836
12860
  return c.json({ sessionId, projectDir, source, sizeBytes: jsonlSizeBytes(projectDir, sessionId), pendingPermissionPrompt: deps.pendingPromptFor?.(`session:${sessionId}`) ?? null, deliveryFailure: deps.deliveryFailureFor?.(`session:${sessionId}`) ?? null, ...indicators, ...readLevers(account) });
12837
12861
  });
12838
- app64.post("/permission-verdict", requireAdminSession, async (c) => {
12862
+ app65.post("/permission-verdict", requireAdminSession, async (c) => {
12839
12863
  const body = await c.req.json().catch(() => null);
12840
12864
  const sessionId = body?.sessionId;
12841
12865
  const requestId = body?.request_id;
@@ -12852,7 +12876,7 @@ ${note}` : note;
12852
12876
  const ok = deps.resolvePermissionVerdict?.(`session:${sessionId}`, requestId, behavior) ?? false;
12853
12877
  return c.json({ ok });
12854
12878
  });
12855
- return app64;
12879
+ return app65;
12856
12880
  }
12857
12881
 
12858
12882
  // server/routes/webchat-greeting.ts
@@ -14208,15 +14232,15 @@ function operatorRoleFor(config, userId) {
14208
14232
  async function buildAccountOptionList(accounts, userId) {
14209
14233
  const houseAccount = accounts.find((a) => a.config.role === "house");
14210
14234
  console.log(`[admin-accounts] op=list count=${accounts.length} house=${houseAccount?.accountId ?? "none"}`);
14211
- const resolve37 = async (a) => {
14235
+ const resolve38 = async (a) => {
14212
14236
  const businessName = await fetchAccountName(a.accountId) || void 0;
14213
14237
  return { accountId: a.accountId, businessName, role: operatorRoleFor(a.config, userId), isHouse: a.config.role === "house" };
14214
14238
  };
14215
14239
  const houseOptions = await Promise.all(
14216
- accounts.filter((a) => a.config.role === "house").map(resolve37)
14240
+ accounts.filter((a) => a.config.role === "house").map(resolve38)
14217
14241
  );
14218
14242
  const nonHouseOptions = await Promise.all(
14219
- accounts.filter((a) => a.config.role !== "house").map(resolve37)
14243
+ accounts.filter((a) => a.config.role !== "house").map(resolve38)
14220
14244
  );
14221
14245
  const sortKey = (o) => (o.businessName || o.accountId).toLowerCase();
14222
14246
  nonHouseOptions.sort((x, y) => {
@@ -15223,9 +15247,9 @@ app18.post("/:id/resume", async (c) => {
15223
15247
  if (healQueue.length > 0) {
15224
15248
  void (async () => {
15225
15249
  for (let i = 0; i < healQueue.length; i++) {
15226
- const q = healQueue[i];
15250
+ const q2 = healQueue[i];
15227
15251
  try {
15228
- const healComponents = await Promise.all(q.components.map(async (c2) => {
15252
+ const healComponents = await Promise.all(q2.components.map(async (c2) => {
15229
15253
  const base = {
15230
15254
  componentId: c2.componentId,
15231
15255
  name: c2.name,
@@ -15263,25 +15287,25 @@ app18.post("/:id/resume", async (c) => {
15263
15287
  }));
15264
15288
  const messageId = await persistMessage(
15265
15289
  sessionId,
15266
- q.role,
15267
- q.content,
15290
+ q2.role,
15291
+ q2.content,
15268
15292
  accountId,
15269
15293
  void 0,
15270
- q.createdAt,
15294
+ q2.createdAt,
15271
15295
  void 0,
15272
15296
  healComponents,
15273
15297
  void 0
15274
15298
  );
15275
15299
  jsonlHealOk += 1;
15276
15300
  try {
15277
- streamLogPath.write(`[${(/* @__PURE__ */ new Date()).toISOString()}] [admin-persist-heal] convId=${sessionId.slice(0, 8)} turnIndex=${i} role=${q.role} outcome=ok messageId=${(messageId ?? "").slice(0, 8)}
15301
+ streamLogPath.write(`[${(/* @__PURE__ */ new Date()).toISOString()}] [admin-persist-heal] convId=${sessionId.slice(0, 8)} turnIndex=${i} role=${q2.role} outcome=ok messageId=${(messageId ?? "").slice(0, 8)}
15278
15302
  `);
15279
15303
  } catch {
15280
15304
  }
15281
15305
  } catch (err) {
15282
15306
  jsonlHealFail += 1;
15283
15307
  try {
15284
- streamLogPath.write(`[${(/* @__PURE__ */ new Date()).toISOString()}] [admin-persist-heal] convId=${sessionId.slice(0, 8)} turnIndex=${i} role=${q.role} outcome=fail reason=${JSON.stringify((err instanceof Error ? err.message : String(err)).slice(0, 200))}
15308
+ streamLogPath.write(`[${(/* @__PURE__ */ new Date()).toISOString()}] [admin-persist-heal] convId=${sessionId.slice(0, 8)} turnIndex=${i} role=${q2.role} outcome=fail reason=${JSON.stringify((err instanceof Error ? err.message : String(err)).slice(0, 200))}
15285
15309
  `);
15286
15310
  } catch {
15287
15311
  }
@@ -15606,10 +15630,10 @@ function computeSpecialistDomains(accountId) {
15606
15630
  const resolveDesc = (qualified) => {
15607
15631
  if (!qualified.startsWith("mcp__")) return void 0;
15608
15632
  const rest = qualified.slice(5);
15609
- const sep9 = rest.indexOf("__");
15610
- if (sep9 < 0) return void 0;
15611
- const plugin = rest.slice(0, sep9);
15612
- const tool = rest.slice(sep9 + 2);
15633
+ const sep10 = rest.indexOf("__");
15634
+ if (sep10 < 0) return void 0;
15635
+ const plugin = rest.slice(0, sep10);
15636
+ const tool = rest.slice(sep10 + 2);
15613
15637
  let map = descByPlugin.get(plugin);
15614
15638
  if (!map) {
15615
15639
  const dir = pluginDirs.get(plugin);
@@ -15912,18 +15936,18 @@ var ALLOWED_EVENTS = /* @__PURE__ */ new Set([
15912
15936
  ]);
15913
15937
  var app21 = new Hono();
15914
15938
  app21.post("/", async (c) => {
15915
- const TAG53 = "[admin:events]";
15939
+ const TAG54 = "[admin:events]";
15916
15940
  let body;
15917
15941
  try {
15918
15942
  body = await c.req.json();
15919
15943
  } catch (err) {
15920
15944
  const detail = err instanceof Error ? err.message : String(err);
15921
- console.error(`${TAG53} reject reason=body-not-json detail=${detail}`);
15945
+ console.error(`${TAG54} reject reason=body-not-json detail=${detail}`);
15922
15946
  return c.json({ ok: false, detail: "Request body was not valid JSON" }, 400);
15923
15947
  }
15924
15948
  const event = typeof body.event === "string" ? body.event : "";
15925
15949
  if (!ALLOWED_EVENTS.has(event)) {
15926
- console.error(`${TAG53} reject reason=event-not-allowed event=${JSON.stringify(event)}`);
15950
+ console.error(`${TAG54} reject reason=event-not-allowed event=${JSON.stringify(event)}`);
15927
15951
  return c.json({ ok: false, detail: `Event "${event}" is not allowed` }, 400);
15928
15952
  }
15929
15953
  const rawFields = body.fields && typeof body.fields === "object" ? body.fields : {};
@@ -18564,7 +18588,7 @@ var CONVERSATION_PARENT_LABELS = /* @__PURE__ */ new Set(["AdminConversation", "
18564
18588
  var app23 = new Hono();
18565
18589
  app23.get("/", requireAdminSession, async (c) => {
18566
18590
  const cacheKey = c.var.cacheKey;
18567
- const q = (c.req.query("q") ?? "").trim();
18591
+ const q2 = (c.req.query("q") ?? "").trim();
18568
18592
  const rawLimit = c.req.query("limit");
18569
18593
  const rawLabels = c.req.query("labels");
18570
18594
  const rawThreshold = c.req.query("threshold");
@@ -18573,13 +18597,13 @@ app23.get("/", requireAdminSession, async (c) => {
18573
18597
  console.error(`[graph-search] auth-rejected endpoint="/api/admin/graph-search" reason="no account for session"`);
18574
18598
  return c.json({ error: "Account not found for session" }, 401);
18575
18599
  }
18576
- if (!q) return c.json({ error: "q (search query) required" }, 400);
18600
+ if (!q2) return c.json({ error: "q (search query) required" }, 400);
18577
18601
  const labels = rawLabels ? rawLabels.split(",").map((s) => s.trim()).filter((s) => s.length > 0) : [];
18578
18602
  const wildcard = labels.includes("*");
18579
18603
  const noChips = labels.length === 0;
18580
18604
  const allLabels = wildcard || noChips;
18581
18605
  if (noChips && !wildcard) {
18582
- console.error(`[graph-search] op=labels-default applied=all query="${q}"`);
18606
+ console.error(`[graph-search] op=labels-default applied=all query="${q2}"`);
18583
18607
  }
18584
18608
  const parsedLimit = rawLimit ? parseInt(rawLimit, 10) : DEFAULT_LIMIT;
18585
18609
  const limit = Number.isFinite(parsedLimit) && parsedLimit > 0 ? Math.min(parsedLimit, MAX_LIMIT) : DEFAULT_LIMIT;
@@ -18599,7 +18623,7 @@ app23.get("/", requireAdminSession, async (c) => {
18599
18623
  const session = getSession();
18600
18624
  try {
18601
18625
  const res = await hybrid(session, embed, {
18602
- query: q,
18626
+ query: q2,
18603
18627
  accountId,
18604
18628
  labels: forwardedLabels,
18605
18629
  limit,
@@ -18609,7 +18633,7 @@ app23.get("/", requireAdminSession, async (c) => {
18609
18633
  const total = Date.now() - started;
18610
18634
  if (res.embedError) {
18611
18635
  console.error(`[graph-search] embed-unavailable err="${res.embedError}" \u2014 bm25-only`);
18612
- console.error(`[graph-search] embed-degraded query="${q}" results=${res.results.length}`);
18636
+ console.error(`[graph-search] embed-degraded query="${q2}" results=${res.results.length}`);
18613
18637
  }
18614
18638
  let labelMatchCount = 0;
18615
18639
  let bodyFulltextCount = 0;
@@ -18660,16 +18684,16 @@ app23.get("/", requireAdminSession, async (c) => {
18660
18684
  }
18661
18685
  resolvedResults = Array.from(seen.values()).sort((a, b) => b.score - a.score);
18662
18686
  console.error(
18663
- `[graph-search] label-match query="${q}" hits=${labelMatchCount}`
18687
+ `[graph-search] label-match query="${q2}" hits=${labelMatchCount}`
18664
18688
  );
18665
18689
  console.error(
18666
- `[graph-search] body-fulltext query="${q}" hits=${bodyFulltextCount} ms=${total}`
18690
+ `[graph-search] body-fulltext query="${q2}" hits=${bodyFulltextCount} ms=${total}`
18667
18691
  );
18668
18692
  }
18669
18693
  const labelsToken = wildcard ? "*" : noChips ? "all" : labels.join(",");
18670
18694
  const expandedFlag = !wildcard && wantsBodyFulltext ? 1 : 0;
18671
18695
  console.error(
18672
- `[graph-search] query="${q}" labels=${labelsToken} expanded=${expandedFlag} limit=${limit} mode=${res.mode} raw-results=${res.results.length} resolved-results=${resolvedResults.length} expand-ms=${res.expandMs} total-ms=${total}`
18696
+ `[graph-search] query="${q2}" labels=${labelsToken} expanded=${expandedFlag} limit=${limit} mode=${res.mode} raw-results=${res.results.length} resolved-results=${resolvedResults.length} expand-ms=${res.expandMs} total-ms=${total}`
18673
18697
  );
18674
18698
  console.error(
18675
18699
  `[graph-search] threshold=${res.threshold ?? "off"} raw-merged=${res.rawMerged} suppressed=${res.suppressed} rendered=${res.results.length} bm25-bypass=${res.bm25Bypass}`
@@ -18692,7 +18716,7 @@ app23.get("/", requireAdminSession, async (c) => {
18692
18716
  } catch (err) {
18693
18717
  const elapsed = Date.now() - started;
18694
18718
  const message = err instanceof Error ? err.message : String(err);
18695
- console.error(`[graph-search] neo4j-unreachable query="${q}" ms=${elapsed} err="${message}"`);
18719
+ console.error(`[graph-search] neo4j-unreachable query="${q2}" ms=${elapsed} err="${message}"`);
18696
18720
  return c.json({ error: `Graph search unavailable: ${message}` }, 503);
18697
18721
  } finally {
18698
18722
  await session.close();
@@ -18759,7 +18783,11 @@ var FILTER_TOP_LEVEL_LABELS = Object.freeze(
18759
18783
  "Risk",
18760
18784
  "Source",
18761
18785
  "Finding",
18762
- "Hypothesis"
18786
+ "Hypothesis",
18787
+ // Base-schema ledger (schema-base.md, Task 1819). A cash movement
18788
+ // attached to no invoice — its only edges are outbound, so nothing
18789
+ // drills down to it and a chip is the sole way to reach it.
18790
+ "CashEntry"
18763
18791
  ])
18764
18792
  );
18765
18793
  var AGENT_ACTION_LABELS = Object.freeze(
@@ -18810,6 +18838,17 @@ var STATIC_TOP_LEVEL_LABELS = Object.freeze(
18810
18838
  "Review",
18811
18839
  "ImageObject",
18812
18840
  "Invoice",
18841
+ // Supplier invoice. Declared in schema-base.md's `## Node Types` table
18842
+ // (promoted there from schema-construction.md's top-level table by
18843
+ // 7b225f814), which this module never parses — so it is static, not
18844
+ // derived. Top-level because it is operator-entry with a natural key
18845
+ // (accountId, supplier, confirmationNumber), unlike its InvoiceLine /
18846
+ // InvoicePayment / Credit children.
18847
+ "InboundInvoice",
18848
+ // Ledger cash movement, declared in schema-base.md. Operator-entry with
18849
+ // a natural key (accountId, entryId) and no parent label, so it is
18850
+ // reachable only as a chip.
18851
+ "CashEntry",
18813
18852
  "Task",
18814
18853
  "Project",
18815
18854
  "Event",
@@ -18860,8 +18899,8 @@ function parseTopLevelLabels(content) {
18860
18899
  const line = lines[i];
18861
18900
  if (line.startsWith("## ")) return [];
18862
18901
  if (!line.includes("|")) continue;
18863
- const sep9 = lines[i + 1];
18864
- if (!/^\s*\|?[\s:|-]+\|?\s*$/.test(sep9) || !sep9.includes("-")) continue;
18902
+ const sep10 = lines[i + 1];
18903
+ if (!/^\s*\|?[\s:|-]+\|?\s*$/.test(sep10) || !sep10.includes("-")) continue;
18865
18904
  const headers = splitPipeRow(line).map((h) => stripBackticks(h).toLowerCase());
18866
18905
  const labelCol = headers.findIndex((h) => h === "neo4j label");
18867
18906
  if (labelCol === -1) return [];
@@ -19112,7 +19151,7 @@ async function handleNeighbourhood(c, accountId) {
19112
19151
  }
19113
19152
  const includeAgentActions = c.req.query("includeAgentActions") === "1";
19114
19153
  const agentActionLabels = includeAgentActions ? [] : [...AGENT_ACTION_LABELS];
19115
- const q = (c.req.query("q") ?? "").trim();
19154
+ const q2 = (c.req.query("q") ?? "").trim();
19116
19155
  const rawLimit = c.req.query("limit");
19117
19156
  const parsedLimit = rawLimit ? parseInt(rawLimit, 10) : NEIGHBOURHOOD_SEARCH_DEFAULT_LIMIT;
19118
19157
  const searchLimit = Number.isFinite(parsedLimit) && parsedLimit > 0 ? Math.min(parsedLimit, NEIGHBOURHOOD_SEARCH_MAX_LIMIT) : NEIGHBOURHOOD_SEARCH_DEFAULT_LIMIT;
@@ -19120,9 +19159,9 @@ async function handleNeighbourhood(c, accountId) {
19120
19159
  const session = getSession();
19121
19160
  try {
19122
19161
  let allowedIds = null;
19123
- if (q) {
19162
+ if (q2) {
19124
19163
  const searchRes = await hybrid(session, embed, {
19125
- query: q,
19164
+ query: q2,
19126
19165
  accountId,
19127
19166
  labels: [],
19128
19167
  limit: searchLimit,
@@ -19205,7 +19244,7 @@ async function handleNeighbourhood(c, accountId) {
19205
19244
  }
19206
19245
  if (allowedIds !== null) {
19207
19246
  console.error(
19208
- `[graph-page] neighbourhood-search-intersect q="${q}" allowed=${allowedIds.length} root=${elementId} rendered=${nodes.length}`
19247
+ `[graph-page] neighbourhood-search-intersect q="${q2}" allowed=${allowedIds.length} root=${elementId} rendered=${nodes.length}`
19209
19248
  );
19210
19249
  }
19211
19250
  return c.json({ nodes, edges });
@@ -20546,18 +20585,68 @@ var session_rc_spawn_default = app35;
20546
20585
 
20547
20586
  // server/routes/admin/session-reseat.ts
20548
20587
  import { randomUUID as randomUUID12 } from "crypto";
20549
- function resolveReseatPlan(body, mintId = randomUUID12, adminUserId, authenticatedName) {
20588
+
20589
+ // server/channel-session-override.ts
20590
+ import { readFileSync as readFileSync34, writeFileSync as writeFileSync12, renameSync as renameSync9 } from "fs";
20591
+ import { join as join35 } from "path";
20592
+ var FILE2 = "channel-session-override.json";
20593
+ var UUID2 = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
20594
+ function channelOverridePath(accountDir) {
20595
+ return join35(accountDir, FILE2);
20596
+ }
20597
+ function senderKey(channel, senderId) {
20598
+ return `${channel}:${senderId}`;
20599
+ }
20600
+ function readRaw2(accountDir) {
20601
+ try {
20602
+ return JSON.parse(readFileSync34(channelOverridePath(accountDir), "utf8"));
20603
+ } catch {
20604
+ return null;
20605
+ }
20606
+ }
20607
+ function readChannelOverrideId(accountDir, channel, senderId) {
20608
+ const raw = readRaw2(accountDir);
20609
+ if (!raw || !raw.bySender || typeof raw.bySender !== "object") return null;
20610
+ const v = raw.bySender[senderKey(channel, senderId)];
20611
+ return typeof v === "string" && UUID2.test(v) ? v : null;
20612
+ }
20613
+ function writeChannelOverrideId(accountDir, channel, senderId, sessionId) {
20614
+ const raw = readRaw2(accountDir);
20615
+ const bySender = {};
20616
+ if (raw?.bySender && typeof raw.bySender === "object") {
20617
+ for (const [k, v] of Object.entries(raw.bySender)) {
20618
+ if (typeof v === "string") bySender[k] = v;
20619
+ }
20620
+ }
20621
+ bySender[senderKey(channel, senderId)] = sessionId;
20622
+ const path3 = channelOverridePath(accountDir);
20623
+ const tmp = `${path3}.${process.pid}.tmp`;
20624
+ writeFileSync12(tmp, JSON.stringify({ bySender }, null, 2), "utf8");
20625
+ renameSync9(tmp, path3);
20626
+ }
20627
+
20628
+ // server/routes/admin/session-reseat.ts
20629
+ function isAdminChannelSource(source) {
20630
+ return !!source && source.role === "admin" && (source.channel === "whatsapp" || source.channel === "telegram") && typeof source.senderId === "string" && source.senderId.length > 0;
20631
+ }
20632
+ function resolveReseatPlan(body, mintId = randomUUID12, adminUserId, authenticatedName, source) {
20550
20633
  const sessionId = mintId();
20551
20634
  const key = `session:${sessionId}`;
20552
20635
  const payload = {
20553
20636
  sessionId,
20554
20637
  forkFromSessionId: body.fromSessionId,
20555
20638
  model: body.model,
20556
- role: "admin",
20557
- channel: "webchat",
20558
- senderId: key,
20559
- webchatChannel: webchatChannelDescriptor(key)
20639
+ role: "admin"
20560
20640
  };
20641
+ if (isAdminChannelSource(source)) {
20642
+ payload.channel = source.channel;
20643
+ payload.senderId = source.senderId;
20644
+ if (source.accountId) payload.targetAccountId = source.accountId;
20645
+ } else {
20646
+ payload.channel = "webchat";
20647
+ payload.senderId = key;
20648
+ payload.webchatChannel = webchatChannelDescriptor(key);
20649
+ }
20561
20650
  if (body.name) payload.name = body.name;
20562
20651
  if (body.permissionMode) payload.permissionMode = body.permissionMode;
20563
20652
  if (body.effort) payload.effort = body.effort;
@@ -20598,14 +20687,18 @@ app36.post("/", requireAdminSession, async (c) => {
20598
20687
  }
20599
20688
  const accountId = cacheKey ? getAccountIdForSession(cacheKey) : void 0;
20600
20689
  const authenticatedName = accountId ? await resolveAuthenticatedName(accountId, adminUserId) : void 0;
20690
+ const sourceMeta = readSourceRowMeta(fromSessionId);
20691
+ const source = sourceMeta ? { channel: sourceMeta.channel, senderId: sourceMeta.senderId, role: sourceMeta.role, accountId: sourceMeta.accountId } : null;
20601
20692
  const plan = resolveReseatPlan(
20602
20693
  { fromSessionId, model, ...permissionMode ? { permissionMode } : {}, ...effort ? { effort } : {} },
20603
20694
  randomUUID12,
20604
20695
  adminUserId,
20605
- authenticatedName
20696
+ authenticatedName,
20697
+ source
20606
20698
  );
20699
+ const isChannelFork = isAdminChannelSource(source);
20607
20700
  const reseatReqId = randomUUID12();
20608
- console.log(`[chat-reseat] op=request reseatReqId=${reseatReqId} from=${fromSessionId} to-model=${model} to-mode=${permissionMode ?? "none"} to-effort=${effort ?? "none"} new=${plan.sessionId}`);
20701
+ console.log(`[chat-reseat] op=request reseatReqId=${reseatReqId} from=${fromSessionId} from-channel=${source?.channel ?? "none"} from-sender=${source?.senderId ?? "none"} to-model=${model} to-mode=${permissionMode ?? "none"} to-effort=${effort ?? "none"} new=${plan.sessionId}`);
20609
20702
  let res;
20610
20703
  try {
20611
20704
  res = await fetch(`${managerBase("session-reseat:wrapper")}/rc-spawn`, {
@@ -20645,6 +20738,23 @@ app36.post("/", requireAdminSession, async (c) => {
20645
20738
  } catch (err) {
20646
20739
  console.error(`[session-reseat] canonical-forward-failed err=${err instanceof Error ? err.message : String(err)}`);
20647
20740
  }
20741
+ if (isChannelFork) {
20742
+ const channel = plan.payload.channel;
20743
+ const senderId = plan.payload.senderId;
20744
+ try {
20745
+ const provenance = source?.accountId ? "source-account" : "boot-account-fallback";
20746
+ const dir = source?.accountId ? listValidAccounts().find((a) => a.accountId === source.accountId)?.accountDir ?? null : resolveAccount()?.accountDir ?? null;
20747
+ if (dir) {
20748
+ writeChannelOverrideId(dir, channel, senderId, plan.sessionId);
20749
+ console.log(`[chat-reseat] op=channel-forward reseatReqId=${reseatReqId} senderId=${senderId} from=${fromSessionId} to=${plan.sessionId} keyedOn=${provenance}`);
20750
+ } else {
20751
+ console.error(`[session-reseat] channel-forward-failed reason=no-account-dir accountId=${source?.accountId ?? "none"} senderId=${senderId}`);
20752
+ }
20753
+ } catch (err) {
20754
+ console.error(`[session-reseat] channel-forward-failed err=${err instanceof Error ? err.message : String(err)} senderId=${senderId}`);
20755
+ }
20756
+ console.log(`[chat-reseat] op=channel-fork-unbound reseatReqId=${reseatReqId} senderId=${senderId} session=${plan.sessionId} note=binds-on-next-inbound`);
20757
+ }
20648
20758
  const target = `/chat?session=${plan.sessionId}`;
20649
20759
  console.log(`[chat-reseat] op=acquired reseatReqId=${reseatReqId} pid=${parsed?.spawnedPid ?? "none"} new=${plan.sessionId}`);
20650
20760
  console.log(`[chat-reseat] op=navigate reseatReqId=${reseatReqId} target=${target}`);
@@ -20779,14 +20889,14 @@ app37.get("/", async (c) => {
20779
20889
  var system_stats_default = app37;
20780
20890
 
20781
20891
  // server/routes/admin/health.ts
20782
- import { existsSync as existsSync31, readFileSync as readFileSync34 } from "fs";
20783
- import { resolve as resolve26, join as join35 } from "path";
20892
+ import { existsSync as existsSync31, readFileSync as readFileSync35 } from "fs";
20893
+ import { resolve as resolve26, join as join36 } from "path";
20784
20894
  var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT ?? resolve26(process.cwd(), "..");
20785
20895
  var brandHostname = "maxy";
20786
- var brandJsonPath = join35(PLATFORM_ROOT6, "config", "brand.json");
20896
+ var brandJsonPath = join36(PLATFORM_ROOT6, "config", "brand.json");
20787
20897
  if (existsSync31(brandJsonPath)) {
20788
20898
  try {
20789
- const brand = JSON.parse(readFileSync34(brandJsonPath, "utf-8"));
20899
+ const brand = JSON.parse(readFileSync35(brandJsonPath, "utf-8"));
20790
20900
  if (brand.hostname) brandHostname = brand.hostname;
20791
20901
  } catch {
20792
20902
  }
@@ -20796,7 +20906,7 @@ var PROCESS_STARTED_AT = (/* @__PURE__ */ new Date()).toISOString();
20796
20906
  var PROBE_TIMEOUT_MS = 1e3;
20797
20907
  function readVersion() {
20798
20908
  if (!existsSync31(VERSION_FILE)) return "unknown";
20799
- return readFileSync34(VERSION_FILE, "utf-8").trim() || "unknown";
20909
+ return readFileSync35(VERSION_FILE, "utf-8").trim() || "unknown";
20800
20910
  }
20801
20911
  async function probeConversationDb() {
20802
20912
  let session;
@@ -20976,7 +21086,7 @@ function managerLogFollowUrl(sessionId, opts) {
20976
21086
 
20977
21087
  // server/routes/admin/linkedin-ingest.ts
20978
21088
  var TAG31 = "[linkedin-ingest-route]";
20979
- var UUID2 = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
21089
+ var UUID3 = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
20980
21090
  var ISO = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?(?:Z|[+-]\d{2}:\d{2})$/;
20981
21091
  var LINKEDIN_URL = /^https:\/\/www\.linkedin\.com\//;
20982
21092
  function rejectSchema(reason, missing = []) {
@@ -20990,7 +21100,7 @@ function validate(body) {
20990
21100
  if (b.kind !== "profile" && b.kind !== "thread") missing.push("kind");
20991
21101
  if (typeof b.capturedAt !== "string" || !ISO.test(b.capturedAt)) missing.push("capturedAt");
20992
21102
  if (typeof b.pageUrl !== "string" || !LINKEDIN_URL.test(b.pageUrl)) missing.push("pageUrl");
20993
- if (typeof b.dispatchId !== "string" || !UUID2.test(b.dispatchId)) missing.push("dispatchId");
21103
+ if (typeof b.dispatchId !== "string" || !UUID3.test(b.dispatchId)) missing.push("dispatchId");
20994
21104
  if (missing.length) return rejectSchema("envelope", missing);
20995
21105
  if (b.kind === "profile") {
20996
21106
  const p = b.profile;
@@ -21573,7 +21683,7 @@ var browser_default = app45;
21573
21683
 
21574
21684
  // server/routes/admin/calendar.ts
21575
21685
  import { existsSync as existsSync32 } from "fs";
21576
- import { join as join37 } from "path";
21686
+ import { join as join38 } from "path";
21577
21687
 
21578
21688
  // server/lib/calendar-ics.ts
21579
21689
  var CRLF = "\r\n";
@@ -21672,8 +21782,8 @@ function countVEvents(ics) {
21672
21782
  }
21673
21783
 
21674
21784
  // server/lib/calendar-availability.ts
21675
- import { readFileSync as readFileSync35 } from "fs";
21676
- import { join as join36 } from "path";
21785
+ import { readFileSync as readFileSync36 } from "fs";
21786
+ import { join as join37 } from "path";
21677
21787
  var AVAILABILITY_FILENAME = "calendar-availability.json";
21678
21788
  var REQUIRED_KEYS = [
21679
21789
  "timezone",
@@ -21682,10 +21792,10 @@ var REQUIRED_KEYS = [
21682
21792
  "weekly"
21683
21793
  ];
21684
21794
  function readAvailabilityConfig(accountDir) {
21685
- const path3 = join36(accountDir, AVAILABILITY_FILENAME);
21795
+ const path3 = join37(accountDir, AVAILABILITY_FILENAME);
21686
21796
  let raw;
21687
21797
  try {
21688
- raw = readFileSync35(path3, "utf-8");
21798
+ raw = readFileSync36(path3, "utf-8");
21689
21799
  } catch {
21690
21800
  throw new Error(`availability not configured: ${path3} not found`);
21691
21801
  }
@@ -22044,7 +22154,7 @@ app46.get("/booking-link", requireAdminSession, (c) => {
22044
22154
  console.error('[calendar] op=booking-link-fail err="no account configured"');
22045
22155
  return c.json({ bookingDomain: null });
22046
22156
  }
22047
- if (!existsSync32(join37(account.accountDir, AVAILABILITY_FILENAME))) {
22157
+ if (!existsSync32(join38(account.accountDir, AVAILABILITY_FILENAME))) {
22048
22158
  console.log("[calendar] op=booking-link bookingDomain=none source=availability-config");
22049
22159
  return c.json({ bookingDomain: null });
22050
22160
  }
@@ -22847,7 +22957,7 @@ app56.route("/request-magic-link", request_magic_link_default);
22847
22957
  var access_default = app56;
22848
22958
 
22849
22959
  // server/routes/sites.ts
22850
- import { existsSync as existsSync33, readFileSync as readFileSync36, realpathSync as realpathSync7, statSync as statSync14 } from "fs";
22960
+ import { existsSync as existsSync33, readFileSync as readFileSync37, realpathSync as realpathSync7, statSync as statSync14 } from "fs";
22851
22961
  import { resolve as resolve28 } from "path";
22852
22962
  var SAFE_SEG_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;
22853
22963
  var MIME = {
@@ -22911,13 +23021,13 @@ app57.get("/:rel{.*}", (c) => {
22911
23021
  console.error(`[sites] path-traversal-rejected path=${reqPath} reason=escape status=403`);
22912
23022
  return c.text("Forbidden", 403);
22913
23023
  }
22914
- let stat8;
23024
+ let stat9;
22915
23025
  try {
22916
- stat8 = existsSync33(filePath) ? statSync14(filePath) : null;
23026
+ stat9 = existsSync33(filePath) ? statSync14(filePath) : null;
22917
23027
  } catch {
22918
- stat8 = null;
23028
+ stat9 = null;
22919
23029
  }
22920
- if (stat8?.isDirectory() && !reqPath.endsWith("/")) {
23030
+ if (stat9?.isDirectory() && !reqPath.endsWith("/")) {
22921
23031
  const search = new URL(c.req.url).search;
22922
23032
  const target = `${reqPath}/${search}`;
22923
23033
  console.log(
@@ -22925,7 +23035,7 @@ app57.get("/:rel{.*}", (c) => {
22925
23035
  );
22926
23036
  return c.redirect(target, 301);
22927
23037
  }
22928
- if (stat8?.isDirectory()) {
23038
+ if (stat9?.isDirectory()) {
22929
23039
  filePath = resolve28(filePath, "index.html");
22930
23040
  }
22931
23041
  if (!filePath.startsWith(rootDir + "/")) {
@@ -22951,7 +23061,7 @@ app57.get("/:rel{.*}", (c) => {
22951
23061
  }
22952
23062
  let body;
22953
23063
  try {
22954
- body = readFileSync36(realPath);
23064
+ body = readFileSync37(realPath);
22955
23065
  } catch (err) {
22956
23066
  const code = err?.code;
22957
23067
  if (code === "EISDIR") {
@@ -22987,7 +23097,7 @@ var sites_default = app57;
22987
23097
 
22988
23098
  // app/lib/visitor-token.ts
22989
23099
  import { createHmac, randomBytes, timingSafeEqual } from "crypto";
22990
- import { mkdirSync as mkdirSync8, readFileSync as readFileSync37, writeFileSync as writeFileSync12 } from "fs";
23100
+ import { mkdirSync as mkdirSync8, readFileSync as readFileSync38, writeFileSync as writeFileSync13 } from "fs";
22991
23101
  import { dirname as dirname12 } from "path";
22992
23102
  var TOKEN_PREFIX = "v1.";
22993
23103
  var SECRET_BYTES = 32;
@@ -22996,7 +23106,7 @@ var cachedSecret = null;
22996
23106
  function getSecret() {
22997
23107
  if (cachedSecret) return cachedSecret;
22998
23108
  try {
22999
- const hex2 = readFileSync37(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
23109
+ const hex2 = readFileSync38(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
23000
23110
  if (hex2.length === SECRET_BYTES * 2) {
23001
23111
  cachedSecret = Buffer.from(hex2, "hex");
23002
23112
  return cachedSecret;
@@ -23006,11 +23116,11 @@ function getSecret() {
23006
23116
  const fresh = randomBytes(SECRET_BYTES).toString("hex");
23007
23117
  try {
23008
23118
  mkdirSync8(dirname12(VISITOR_TOKEN_SECRET_FILE), { recursive: true, mode: 448 });
23009
- writeFileSync12(VISITOR_TOKEN_SECRET_FILE, fresh, { mode: 384, flag: "wx" });
23119
+ writeFileSync13(VISITOR_TOKEN_SECRET_FILE, fresh, { mode: 384, flag: "wx" });
23010
23120
  console.log(`[visitor-token] secret minted path=${VISITOR_TOKEN_SECRET_FILE}`);
23011
23121
  } catch {
23012
23122
  }
23013
- const hex = readFileSync37(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
23123
+ const hex = readFileSync38(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
23014
23124
  cachedSecret = Buffer.from(hex, "hex");
23015
23125
  return cachedSecret;
23016
23126
  }
@@ -23063,8 +23173,8 @@ var VISITOR_COOKIE_NAME = "mxy_v";
23063
23173
  var VISITOR_COOKIE_MAX_AGE_SECONDS = Math.floor(DEFAULT_TTL_MS / 1e3);
23064
23174
 
23065
23175
  // app/lib/brand-config.ts
23066
- import { existsSync as existsSync34, readFileSync as readFileSync38 } from "fs";
23067
- import { join as join38 } from "path";
23176
+ import { existsSync as existsSync34, readFileSync as readFileSync39 } from "fs";
23177
+ import { join as join39 } from "path";
23068
23178
  var cached2 = null;
23069
23179
  var cachedAttempted = false;
23070
23180
  function readBrandConfig() {
@@ -23072,10 +23182,10 @@ function readBrandConfig() {
23072
23182
  cachedAttempted = true;
23073
23183
  const platformRoot5 = process.env.MAXY_PLATFORM_ROOT;
23074
23184
  if (!platformRoot5) return null;
23075
- const brandPath = join38(platformRoot5, "config", "brand.json");
23185
+ const brandPath = join39(platformRoot5, "config", "brand.json");
23076
23186
  if (!existsSync34(brandPath)) return null;
23077
23187
  try {
23078
- cached2 = JSON.parse(readFileSync38(brandPath, "utf-8"));
23188
+ cached2 = JSON.parse(readFileSync39(brandPath, "utf-8"));
23079
23189
  return cached2;
23080
23190
  } catch {
23081
23191
  return null;
@@ -23209,8 +23319,8 @@ function appendConsentParams(pageUrl, token) {
23209
23319
  const hashIdx = pageUrl.indexOf("#");
23210
23320
  const hash = hashIdx >= 0 ? pageUrl.slice(hashIdx) : "";
23211
23321
  const base = hashIdx >= 0 ? pageUrl.slice(0, hashIdx) : pageUrl;
23212
- const sep9 = base.indexOf("?") >= 0 ? "&" : "?";
23213
- return base + sep9 + `consent=needed&v=${encodeURIComponent(token)}` + hash;
23322
+ const sep10 = base.indexOf("?") >= 0 ? "&" : "?";
23323
+ return base + sep10 + `consent=needed&v=${encodeURIComponent(token)}` + hash;
23214
23324
  }
23215
23325
  }
23216
23326
  var SAFE_SLUG_RE = /^[a-z0-9](?:[a-z0-9-]{0,118}[a-z0-9])?$/;
@@ -23573,13 +23683,13 @@ var visitor_event_default = app60;
23573
23683
 
23574
23684
  // server/routes/session.ts
23575
23685
  import { resolve as resolve29 } from "path";
23576
- import { existsSync as existsSync35, writeFileSync as writeFileSync13, mkdirSync as mkdirSync9 } from "fs";
23686
+ import { existsSync as existsSync35, writeFileSync as writeFileSync14, mkdirSync as mkdirSync9 } from "fs";
23577
23687
  var UUID_RE5 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
23578
23688
  function writeBrandingCache(accountId, agentSlug, branding) {
23579
23689
  try {
23580
23690
  const cacheDir = resolve29(MAXY_DIR, "branding-cache", accountId);
23581
23691
  mkdirSync9(cacheDir, { recursive: true });
23582
- writeFileSync13(resolve29(cacheDir, `${agentSlug}.json`), JSON.stringify(branding), "utf-8");
23692
+ writeFileSync14(resolve29(cacheDir, `${agentSlug}.json`), JSON.stringify(branding), "utf-8");
23583
23693
  } catch (err) {
23584
23694
  console.error(`[branding] cache write failed: ${err instanceof Error ? err.message : String(err)}`);
23585
23695
  }
@@ -23904,8 +24014,205 @@ app62.get("/free-busy", async (c) => {
23904
24014
  });
23905
24015
  var calendar_public_default = app62;
23906
24016
 
24017
+ // server/routes/portal-fetch.ts
24018
+ import { createReadStream as createReadStream3 } from "fs";
24019
+ import { stat as stat6, readFile as readFile6, realpath } from "fs/promises";
24020
+ import { join as join40, resolve as resolve30, sep as sep8 } from "path";
24021
+ import { Readable as Readable3 } from "stream";
24022
+
24023
+ // ../plugins/cloudflare/bin/schema-exposed-dirs.mjs
24024
+ var ONT_START = "<!-- ontology-buckets:start -->";
24025
+ var ONT_END = "<!-- ontology-buckets:end -->";
24026
+ var OWNED_START = "<!-- plugin-owned-dirs:start -->";
24027
+ var OWNED_END = "<!-- plugin-owned-dirs:end -->";
24028
+ var ALWAYS_EXPOSED = "output";
24029
+ var DOMAIN_LINE = /^- `([^`/]+)\/` - one folder per /;
24030
+ var OWNED_LINE = /^- `([^`/]+)\/` — /;
24031
+ function region(text, start, end) {
24032
+ const i = text.indexOf(start);
24033
+ if (i === -1) return null;
24034
+ const j = text.indexOf(end, i);
24035
+ return text.slice(i, j === -1 ? text.length : j);
24036
+ }
24037
+ function allowedBlockOrNull(text) {
24038
+ const lines = text.split("\n");
24039
+ const start = lines.findIndex((l) => l.trim() === "```allowed-top-level");
24040
+ if (start === -1) return null;
24041
+ for (let j = start + 1; j < lines.length; j++) {
24042
+ if (lines[j].trim() === "```") {
24043
+ return lines.slice(start + 1, j).map((l) => l.trim()).filter(Boolean);
24044
+ }
24045
+ }
24046
+ return null;
24047
+ }
24048
+ function resolveExposedDirs(schemaText) {
24049
+ const empty = {
24050
+ schemaPresent: false,
24051
+ allowed: (
24052
+ /** @type {string[]} */
24053
+ []
24054
+ ),
24055
+ domainBuckets: (
24056
+ /** @type {string[]} */
24057
+ []
24058
+ ),
24059
+ pluginOwned: (
24060
+ /** @type {string[]} */
24061
+ []
24062
+ ),
24063
+ collisions: (
24064
+ /** @type {string[]} */
24065
+ []
24066
+ ),
24067
+ exposed: (
24068
+ /** @type {string[]} */
24069
+ []
24070
+ )
24071
+ };
24072
+ if (typeof schemaText !== "string" || schemaText.length === 0) return empty;
24073
+ const allowed = allowedBlockOrNull(schemaText);
24074
+ if (allowed === null) return { ...empty, schemaPresent: true };
24075
+ const ont = region(schemaText, ONT_START, ONT_END);
24076
+ const owned = region(schemaText, OWNED_START, OWNED_END);
24077
+ const dirsIn = (text, re) => {
24078
+ if (!text) return [];
24079
+ const out = [];
24080
+ for (const line of text.split("\n")) {
24081
+ const m = re.exec(line.trim());
24082
+ if (m && !out.includes(m[1])) out.push(m[1]);
24083
+ }
24084
+ return out;
24085
+ };
24086
+ const domainBuckets = dirsIn(ont, DOMAIN_LINE);
24087
+ const pluginOwned = dirsIn(owned, OWNED_LINE);
24088
+ const collisions = domainBuckets.filter((d) => pluginOwned.includes(d));
24089
+ const candidates = [ALWAYS_EXPOSED, ...domainBuckets.filter((d) => !collisions.includes(d))];
24090
+ const exposed = [...new Set(candidates)].filter((d) => allowed.includes(d)).sort();
24091
+ return { schemaPresent: true, allowed, domainBuckets, pluginOwned, collisions, exposed };
24092
+ }
24093
+
24094
+ // server/routes/portal-fetch.ts
24095
+ var app63 = new Hono();
24096
+ var TAG45 = "[portal-fetch]";
24097
+ async function verifySignature(secret, accountId, relPath, expiresAtMs, signature, nowMs) {
24098
+ if (!secret || !signature) return false;
24099
+ if (!Number.isFinite(expiresAtMs) || expiresAtMs <= nowMs) return false;
24100
+ const key = await crypto.subtle.importKey(
24101
+ "raw",
24102
+ new TextEncoder().encode(secret),
24103
+ { name: "HMAC", hash: "SHA-256" },
24104
+ false,
24105
+ ["sign"]
24106
+ );
24107
+ const mac = await crypto.subtle.sign(
24108
+ "HMAC",
24109
+ key,
24110
+ new TextEncoder().encode(`${accountId}
24111
+ ${relPath}
24112
+ ${expiresAtMs}`)
24113
+ );
24114
+ const expected = [...new Uint8Array(mac)].map((b) => b.toString(16).padStart(2, "0")).join("");
24115
+ if (expected.length !== signature.length) return false;
24116
+ let diff = 0;
24117
+ for (let i = 0; i < expected.length; i++) diff |= expected.charCodeAt(i) ^ signature.charCodeAt(i);
24118
+ return diff === 0;
24119
+ }
24120
+ async function readAccountSecret(accountId) {
24121
+ const path3 = resolve30(DATA_ROOT, "accounts", accountId, "secrets", "data-portal.env");
24122
+ let text;
24123
+ try {
24124
+ text = await readFile6(path3, "utf8");
24125
+ } catch {
24126
+ console.error(`${TAG45} op=no-secret account="${accountId}" path="${path3}"`);
24127
+ return "";
24128
+ }
24129
+ for (const line of text.split("\n")) {
24130
+ const m = line.match(/^PORTAL_FETCH_SECRET=(.*)$/);
24131
+ if (m) return m[1].trim();
24132
+ }
24133
+ console.error(`${TAG45} op=no-secret account="${accountId}" reason=key-absent path="${path3}"`);
24134
+ return "";
24135
+ }
24136
+ function isValidAccountId2(value) {
24137
+ return /^[A-Za-z0-9-]+$/.test(value);
24138
+ }
24139
+ async function resolveFetchTarget(accountDir, relPath) {
24140
+ let schemaText = null;
24141
+ try {
24142
+ schemaText = await readFile6(join40(accountDir, "SCHEMA.md"), "utf8");
24143
+ } catch {
24144
+ schemaText = null;
24145
+ }
24146
+ const { exposed } = resolveExposedDirs(schemaText);
24147
+ if (exposed.length === 0) return { ok: false, reason: "not-exposed" };
24148
+ const lexical = resolve30(accountDir, relPath);
24149
+ let root;
24150
+ let absolute;
24151
+ try {
24152
+ root = await realpath(accountDir);
24153
+ absolute = await realpath(lexical);
24154
+ } catch {
24155
+ return { ok: false, reason: "enoent" };
24156
+ }
24157
+ if (absolute === root || !absolute.startsWith(root + sep8)) return { ok: false, reason: "not-exposed" };
24158
+ const top = absolute.slice(root.length + 1).split(sep8)[0];
24159
+ if (!exposed.includes(top)) return { ok: false, reason: "not-exposed" };
24160
+ try {
24161
+ const st = await stat6(absolute);
24162
+ if (!st.isFile()) return { ok: false, reason: "enoent" };
24163
+ return { ok: true, absolute, sizeBytes: st.size };
24164
+ } catch {
24165
+ return { ok: false, reason: "enoent" };
24166
+ }
24167
+ }
24168
+ function q(value) {
24169
+ return `"${value.replace(/[\\"]/g, "\\$&").replace(/[\r\n\t]/g, " ")}"`;
24170
+ }
24171
+ async function handle(c, headOnly) {
24172
+ const accountId = c.req.query("accountId") ?? "";
24173
+ const relPath = c.req.query("path") ?? "";
24174
+ const exp = Number(c.req.query("exp") ?? "0");
24175
+ const sig = c.req.query("sig") ?? "";
24176
+ console.log(`${TAG45} op=request account=${q(accountId)} path=${q(relPath)} head=${headOnly}`);
24177
+ if (!isValidAccountId2(accountId)) {
24178
+ console.error(`${TAG45} op=denied account=${q(accountId)} reason=account-id`);
24179
+ return c.json({ ok: false, error: "denied" }, 401);
24180
+ }
24181
+ const secret = await readAccountSecret(accountId);
24182
+ if (!await verifySignature(secret, accountId, relPath, exp, sig, Date.now())) {
24183
+ console.error(`${TAG45} op=denied account=${q(accountId)} reason=secret`);
24184
+ return c.json({ ok: false, error: "denied" }, 401);
24185
+ }
24186
+ const target = await resolveFetchTarget(resolve30(DATA_ROOT, "accounts", accountId), relPath);
24187
+ if (!target.ok) {
24188
+ const op = target.reason === "enoent" ? "miss" : "denied";
24189
+ console.error(`${TAG45} op=${op} account=${accountId} reason=${target.reason}`);
24190
+ return c.json({ ok: false, error: "not found" }, 404);
24191
+ }
24192
+ if (headOnly) {
24193
+ console.log(`${TAG45} op=served account=${accountId} bytes=${target.sizeBytes} head=true`);
24194
+ return c.body(null, 200);
24195
+ }
24196
+ console.log(`${TAG45} op=served account=${accountId} bytes=${target.sizeBytes}`);
24197
+ return new Response(Readable3.toWeb(createReadStream3(target.absolute)), {
24198
+ status: 200,
24199
+ headers: {
24200
+ "content-type": detectMimeType(target.absolute),
24201
+ // Percent-encoded per RFC 6266, so a name carrying a quote cannot break
24202
+ // out of the form and CR/LF cannot reach the Headers constructor.
24203
+ "content-disposition": `attachment; filename*=UTF-8''${encodeURIComponent(relPath.split("/").pop() ?? "file")}`,
24204
+ "x-content-type-options": "nosniff",
24205
+ // Private client documents: never let a browser or proxy cache one.
24206
+ "cache-control": "private, no-store"
24207
+ }
24208
+ });
24209
+ }
24210
+ app63.get("/fetch", (c) => handle(c, false));
24211
+ app63.on("HEAD", "/fetch", (c) => handle(c, true));
24212
+ var portal_fetch_default = app63;
24213
+
23907
24214
  // app/lib/timeentry-census.ts
23908
- var TAG45 = "[timeentry-census]";
24215
+ var TAG46 = "[timeentry-census]";
23909
24216
  function reconcileTimeEntries(rows, now, horizonMs) {
23910
24217
  const openEntries = rows.openEntries.length;
23911
24218
  let oldestOpenAgeMs = 0;
@@ -23945,12 +24252,12 @@ async function runTimeEntryCensusOnce(session, now, horizonMs) {
23945
24252
  sumAdjustments: toNum(r.get("sumAdjustments"))
23946
24253
  }));
23947
24254
  const finding = reconcileTimeEntries({ openEntries, taskSums }, now, horizonMs);
23948
- const line = `${TAG45} openEntries=${finding.openEntries} oldestOpenAgeMin=${finding.oldestOpenAgeMin} maxOpenPerAccount=${finding.maxOpenPerAccount} driftTasks=${finding.driftTasks}`;
24255
+ const line = `${TAG46} openEntries=${finding.openEntries} oldestOpenAgeMin=${finding.oldestOpenAgeMin} maxOpenPerAccount=${finding.maxOpenPerAccount} driftTasks=${finding.driftTasks}`;
23949
24256
  if (finding.alarm) console.error(`${line} alarm=true`);
23950
24257
  else console.log(line);
23951
24258
  return finding;
23952
24259
  } catch (err) {
23953
- console.error(`${TAG45} error="${err instanceof Error ? err.message : String(err)}"`);
24260
+ console.error(`${TAG46} error="${err instanceof Error ? err.message : String(err)}"`);
23954
24261
  return null;
23955
24262
  }
23956
24263
  }
@@ -23964,13 +24271,13 @@ function startTimeEntryCensus(openSession, opts = {}) {
23964
24271
  try {
23965
24272
  session = openSession();
23966
24273
  } catch (err) {
23967
- console.error(`${TAG45} open-session-failed error="${err instanceof Error ? err.message : String(err)}"`);
24274
+ console.error(`${TAG46} open-session-failed error="${err instanceof Error ? err.message : String(err)}"`);
23968
24275
  return;
23969
24276
  }
23970
24277
  try {
23971
24278
  await runTimeEntryCensusOnce(session, Date.now(), horizonMs);
23972
24279
  } catch (err) {
23973
- console.error(`${TAG45} tick-failed error="${err instanceof Error ? err.message : String(err)}"`);
24280
+ console.error(`${TAG46} tick-failed error="${err instanceof Error ? err.message : String(err)}"`);
23974
24281
  } finally {
23975
24282
  try {
23976
24283
  await session.close();
@@ -23987,8 +24294,8 @@ function startTimeEntryCensus(openSession, opts = {}) {
23987
24294
  }
23988
24295
 
23989
24296
  // app/lib/ledger-census.ts
23990
- import { readdirSync as readdirSync25, readFileSync as readFileSync39, statSync as statSync15 } from "fs";
23991
- import { join as join39 } from "path";
24297
+ import { readdirSync as readdirSync25, readFileSync as readFileSync40, statSync as statSync15 } from "fs";
24298
+ import { join as join41 } from "path";
23992
24299
 
23993
24300
  // ../lib/ledger-core/dist/reconcile.js
23994
24301
  var LEDGER_INDEX_NAMES = [
@@ -24031,7 +24338,7 @@ function formatCensusLine(f) {
24031
24338
  }
24032
24339
 
24033
24340
  // app/lib/ledger-census.ts
24034
- var TAG46 = "[ledger-census]";
24341
+ var TAG47 = "[ledger-census]";
24035
24342
  var DAY_MS = 864e5;
24036
24343
  function countWriteRejects24h(logDir, now) {
24037
24344
  let entries;
@@ -24042,10 +24349,10 @@ function countWriteRejects24h(logDir, now) {
24042
24349
  }
24043
24350
  let total = 0;
24044
24351
  for (const name of entries) {
24045
- const path3 = join39(logDir, name);
24352
+ const path3 = join41(logDir, name);
24046
24353
  try {
24047
24354
  if (now - statSync15(path3).mtimeMs > DAY_MS) continue;
24048
- for (const line of readFileSync39(path3, "utf8").split("\n")) {
24355
+ for (const line of readFileSync40(path3, "utf8").split("\n")) {
24049
24356
  if (line.includes("[graph-write] reject")) total += 1;
24050
24357
  }
24051
24358
  } catch {
@@ -24107,7 +24414,7 @@ async function runLedgerCensusOnce(session, now) {
24107
24414
  else console.log(line);
24108
24415
  return finding;
24109
24416
  } catch (err) {
24110
- console.error(`${TAG46} error="${err instanceof Error ? err.message : String(err)}"`);
24417
+ console.error(`${TAG47} error="${err instanceof Error ? err.message : String(err)}"`);
24111
24418
  return null;
24112
24419
  }
24113
24420
  }
@@ -24120,7 +24427,7 @@ function startLedgerCensus(openSession, opts = {}) {
24120
24427
  session = openSession();
24121
24428
  } catch (err) {
24122
24429
  console.error(
24123
- `${TAG46} open-session-failed error="${err instanceof Error ? err.message : String(err)}"`
24430
+ `${TAG47} open-session-failed error="${err instanceof Error ? err.message : String(err)}"`
24124
24431
  );
24125
24432
  return;
24126
24433
  }
@@ -24128,7 +24435,7 @@ function startLedgerCensus(openSession, opts = {}) {
24128
24435
  await runLedgerCensusOnce(session, Date.now());
24129
24436
  } catch (err) {
24130
24437
  console.error(
24131
- `${TAG46} tick-failed error="${err instanceof Error ? err.message : String(err)}"`
24438
+ `${TAG47} tick-failed error="${err instanceof Error ? err.message : String(err)}"`
24132
24439
  );
24133
24440
  } finally {
24134
24441
  try {
@@ -24146,7 +24453,7 @@ function startLedgerCensus(openSession, opts = {}) {
24146
24453
  }
24147
24454
 
24148
24455
  // app/lib/conversation-audit.ts
24149
- var TAG47 = "[conversation-audit]";
24456
+ var TAG48 = "[conversation-audit]";
24150
24457
  async function runConversationAudit() {
24151
24458
  const session = getSession();
24152
24459
  try {
@@ -24172,12 +24479,12 @@ async function runConversationAudit() {
24172
24479
  unpairable: Number(rec?.get("unpairable") ?? 0)
24173
24480
  };
24174
24481
  console.error(
24175
- `${TAG47} channel=whatsapp total=${result.total} unreconciled=${result.unreconciled} unpairable=${result.unpairable}`
24482
+ `${TAG48} channel=whatsapp total=${result.total} unreconciled=${result.unreconciled} unpairable=${result.unpairable}`
24176
24483
  );
24177
24484
  return result;
24178
24485
  } catch (err) {
24179
24486
  const reason = err instanceof Error ? `${err.name}:${err.message.slice(0, 200)}` : String(err).slice(0, 200);
24180
- console.error(`${TAG47} op=failed reason=${reason}`);
24487
+ console.error(`${TAG48} op=failed reason=${reason}`);
24181
24488
  return null;
24182
24489
  } finally {
24183
24490
  await session.close();
@@ -24303,13 +24610,13 @@ function startGraphHealthTimer() {
24303
24610
 
24304
24611
  // app/lib/file-watcher.ts
24305
24612
  import * as fsp2 from "fs/promises";
24306
- import { resolve as resolve30, sep as sep8 } from "path";
24613
+ import { resolve as resolve31, sep as sep9 } from "path";
24307
24614
  var ACCOUNT_UUID_RE4 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
24308
24615
  var DEFAULT_COALESCE_MS = 500;
24309
24616
  var ROOTS = ["accounts"];
24310
24617
  function _routeEvent(rootName, filename) {
24311
24618
  if (!filename) return null;
24312
- const segs = filename.split(sep8).filter(Boolean);
24619
+ const segs = filename.split(sep9).filter(Boolean);
24313
24620
  if (segs.length < 2) return null;
24314
24621
  const accountId = segs[0];
24315
24622
  if (!ACCOUNT_UUID_RE4.test(accountId)) return null;
@@ -24322,7 +24629,7 @@ async function startFileWatcher(opts = {}) {
24322
24629
  const dropFn = opts.drop ?? dropFileIndex;
24323
24630
  for (const r of ROOTS) {
24324
24631
  try {
24325
- await fsp2.mkdir(resolve30(dataRoot, r), { recursive: true });
24632
+ await fsp2.mkdir(resolve31(dataRoot, r), { recursive: true });
24326
24633
  } catch (err) {
24327
24634
  console.error(
24328
24635
  `[file-watcher] start-failed root="${r}" err="${err.message}" \u2014 index will be maintained by the 5-min reconcile backstop only`
@@ -24343,7 +24650,7 @@ async function startFileWatcher(opts = {}) {
24343
24650
  timers.set(relativePath, t);
24344
24651
  }
24345
24652
  async function runHook(relativePath, accountId) {
24346
- const absolute = resolve30(dataRoot, relativePath);
24653
+ const absolute = resolve31(dataRoot, relativePath);
24347
24654
  let exists = false;
24348
24655
  try {
24349
24656
  const st = await fsp2.stat(absolute);
@@ -24367,7 +24674,7 @@ async function startFileWatcher(opts = {}) {
24367
24674
  }
24368
24675
  }
24369
24676
  async function watchRoot(rootName) {
24370
- const absRoot = resolve30(dataRoot, rootName);
24677
+ const absRoot = resolve31(dataRoot, rootName);
24371
24678
  try {
24372
24679
  const iter = fsp2.watch(absRoot, { recursive: true, signal: controller.signal });
24373
24680
  for await (const event of iter) {
@@ -24406,7 +24713,7 @@ async function startFileWatcher(opts = {}) {
24406
24713
 
24407
24714
  // app/lib/migrate-uploads.ts
24408
24715
  import { mkdir as mkdir5, readdir as readdir5, rename as rename2, rm as rm3 } from "fs/promises";
24409
- import { dirname as dirname13, relative as relative6, resolve as resolve31 } from "path";
24716
+ import { dirname as dirname13, relative as relative6, resolve as resolve32 } from "path";
24410
24717
  var ACCOUNT_UUID_RE5 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
24411
24718
  async function walkFiles(dir) {
24412
24719
  const out = [];
@@ -24417,7 +24724,7 @@ async function walkFiles(dir) {
24417
24724
  return out;
24418
24725
  }
24419
24726
  for (const e of entries) {
24420
- const abs = resolve31(dir, e.name);
24727
+ const abs = resolve32(dir, e.name);
24421
24728
  if (e.isDirectory()) {
24422
24729
  out.push(...await walkFiles(abs));
24423
24730
  } else if (e.isFile()) {
@@ -24437,7 +24744,7 @@ async function relocateTree(srcDir, destDir, dataRoot, accountId, session) {
24437
24744
  let moved = 0;
24438
24745
  for (const oldAbs of files) {
24439
24746
  const suffix = relative6(srcDir, oldAbs);
24440
- const newAbs = resolve31(destDir, suffix);
24747
+ const newAbs = resolve32(destDir, suffix);
24441
24748
  await mkdir5(dirname13(newAbs), { recursive: true });
24442
24749
  await rename2(oldAbs, newAbs);
24443
24750
  moved++;
@@ -24464,7 +24771,7 @@ async function relocateTree(srcDir, destDir, dataRoot, accountId, session) {
24464
24771
  }
24465
24772
  async function migrateUploads(opts = {}) {
24466
24773
  const dataRoot = opts.dataRoot ?? DATA_ROOT;
24467
- const oldRoot = resolve31(dataRoot, "uploads");
24774
+ const oldRoot = resolve32(dataRoot, "uploads");
24468
24775
  let topEntries;
24469
24776
  try {
24470
24777
  topEntries = await readdir5(oldRoot, { withFileTypes: true });
@@ -24485,8 +24792,8 @@ async function migrateUploads(opts = {}) {
24485
24792
  const name = entry.name;
24486
24793
  if (ACCOUNT_UUID_RE5.test(name)) {
24487
24794
  moved += await relocateTree(
24488
- resolve31(oldRoot, name),
24489
- resolve31(dataRoot, "accounts", name, "uploads"),
24795
+ resolve32(oldRoot, name),
24796
+ resolve32(dataRoot, "accounts", name, "uploads"),
24490
24797
  dataRoot,
24491
24798
  name,
24492
24799
  session
@@ -24498,8 +24805,8 @@ async function migrateUploads(opts = {}) {
24498
24805
  continue;
24499
24806
  }
24500
24807
  moved += await relocateTree(
24501
- resolve31(oldRoot, "public"),
24502
- resolve31(dataRoot, "accounts", installAccountId, "uploads", "public"),
24808
+ resolve32(oldRoot, "public"),
24809
+ resolve32(dataRoot, "accounts", installAccountId, "uploads", "public"),
24503
24810
  dataRoot,
24504
24811
  null,
24505
24812
  // public uploads carry no graph nodes
@@ -24528,9 +24835,9 @@ async function migrateUploads(opts = {}) {
24528
24835
  }
24529
24836
 
24530
24837
  // app/lib/migrate-admin-webchat-sidecars.ts
24531
- import { readdir as readdir6, readFile as readFile6, rename as rename3, writeFile as writeFile4, unlink as unlink3 } from "fs/promises";
24838
+ import { readdir as readdir6, readFile as readFile7, rename as rename3, writeFile as writeFile4, unlink as unlink3 } from "fs/promises";
24532
24839
  import { existsSync as existsSync36 } from "fs";
24533
- import { join as join40 } from "path";
24840
+ import { join as join42 } from "path";
24534
24841
  var ADMIN_ROLE2 = "admin";
24535
24842
  var WEBCHAT_CHANNEL2 = "webchat";
24536
24843
  var SESSION_META_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.meta\.json$/i;
@@ -24538,10 +24845,10 @@ var defaultResolveName = async (_accountId, userId) => {
24538
24845
  const res = await loadAdminUserName(userId);
24539
24846
  return res.source === "neo4j" && res.joined.length > 0 ? res.joined : null;
24540
24847
  };
24541
- async function readRaw2(path3) {
24848
+ async function readRaw3(path3) {
24542
24849
  let raw;
24543
24850
  try {
24544
- raw = await readFile6(path3, "utf8");
24851
+ raw = await readFile7(path3, "utf8");
24545
24852
  } catch {
24546
24853
  return null;
24547
24854
  }
@@ -24575,7 +24882,7 @@ async function collectLiveSidecars(projectsRoot) {
24575
24882
  }
24576
24883
  for (const slug of slugs) {
24577
24884
  if (!slug.isDirectory()) continue;
24578
- const slugDir = join40(projectsRoot, slug.name);
24885
+ const slugDir = join42(projectsRoot, slug.name);
24579
24886
  let entries;
24580
24887
  try {
24581
24888
  entries = await readdir6(slugDir, { withFileTypes: true });
@@ -24584,9 +24891,9 @@ async function collectLiveSidecars(projectsRoot) {
24584
24891
  }
24585
24892
  for (const entry of entries) {
24586
24893
  if (entry.isFile() && SESSION_META_RE.test(entry.name)) {
24587
- out.push(join40(slugDir, entry.name));
24894
+ out.push(join42(slugDir, entry.name));
24588
24895
  } else if (entry.isDirectory() && entry.name === "subagents") {
24589
- const subDir = join40(slugDir, entry.name);
24896
+ const subDir = join42(slugDir, entry.name);
24590
24897
  let subs;
24591
24898
  try {
24592
24899
  subs = await readdir6(subDir, { withFileTypes: true });
@@ -24594,7 +24901,7 @@ async function collectLiveSidecars(projectsRoot) {
24594
24901
  continue;
24595
24902
  }
24596
24903
  for (const s of subs) {
24597
- if (s.isFile() && SESSION_META_RE.test(s.name)) out.push(join40(subDir, s.name));
24904
+ if (s.isFile() && SESSION_META_RE.test(s.name)) out.push(join42(subDir, s.name));
24598
24905
  }
24599
24906
  }
24600
24907
  }
@@ -24606,7 +24913,7 @@ function shortId(path3) {
24606
24913
  return base.slice(0, 8);
24607
24914
  }
24608
24915
  async function migrateAdminWebchatSidecars(opts = {}) {
24609
- const projectsRoot = opts.projectsRoot ?? (process.env.CLAUDE_CONFIG_DIR ? join40(process.env.CLAUDE_CONFIG_DIR, "projects") : null) ?? "";
24916
+ const projectsRoot = opts.projectsRoot ?? (process.env.CLAUDE_CONFIG_DIR ? join42(process.env.CLAUDE_CONFIG_DIR, "projects") : null) ?? "";
24610
24917
  const usersFile = opts.usersFile ?? USERS_FILE;
24611
24918
  const accountId = opts.accountId ?? resolveAccount()?.accountId ?? null;
24612
24919
  const resolveName = opts.resolveName ?? defaultResolveName;
@@ -24646,7 +24953,7 @@ async function migrateAdminWebchatSidecars(opts = {}) {
24646
24953
  };
24647
24954
  const sidecars = await collectLiveSidecars(projectsRoot);
24648
24955
  for (const path3 of sidecars) {
24649
- const raw = await readRaw2(path3);
24956
+ const raw = await readRaw3(path3);
24650
24957
  if (!raw) continue;
24651
24958
  if (raw.role !== ADMIN_ROLE2 || raw.channel !== WEBCHAT_CHANNEL2) continue;
24652
24959
  result.scanned++;
@@ -24664,7 +24971,7 @@ async function migrateAdminWebchatSidecars(opts = {}) {
24664
24971
  await writeRaw(path3, raw);
24665
24972
  result.backfilled++;
24666
24973
  await resolveAdminName();
24667
- const back = await readRaw2(path3);
24974
+ const back = await readRaw3(path3);
24668
24975
  const wrote = back && back.adminUserId === soleAdminUserId;
24669
24976
  if (wrote && resolvedNameForAdmin) {
24670
24977
  result.resolvedName++;
@@ -24951,8 +25258,8 @@ var streamSSE = (c, cb, onError) => {
24951
25258
 
24952
25259
  // app/lib/whatsapp/gateway/routes.ts
24953
25260
  function createWaChannelRoutes(deps) {
24954
- const app64 = new Hono();
24955
- app64.get("/wa-channel/inbound", (c) => {
25261
+ const app65 = new Hono();
25262
+ app65.get("/wa-channel/inbound", (c) => {
24956
25263
  const senderId = c.req.query("senderId");
24957
25264
  if (!senderId) return c.json({ error: "senderId required" }, 400);
24958
25265
  return streamSSE(c, async (stream2) => {
@@ -24960,9 +25267,11 @@ function createWaChannelRoutes(deps) {
24960
25267
  void stream2.writeSSE({ data: JSON.stringify(payload) });
24961
25268
  });
24962
25269
  console.error(`[whatsapp-native] op=channel-attached senderId=${senderId}`);
25270
+ deps.onAttached?.(senderId);
24963
25271
  stream2.onAbort(() => {
24964
25272
  deps.hub.detach(senderId);
24965
25273
  console.error(`[whatsapp-native] op=channel-detached senderId=${senderId}`);
25274
+ deps.onDetached?.(senderId);
24966
25275
  });
24967
25276
  while (!stream2.aborted) {
24968
25277
  await stream2.sleep(15e3);
@@ -24970,7 +25279,7 @@ function createWaChannelRoutes(deps) {
24970
25279
  }
24971
25280
  });
24972
25281
  });
24973
- app64.post("/wa-channel/reply", async (c) => {
25282
+ app65.post("/wa-channel/reply", async (c) => {
24974
25283
  const body = await c.req.json().catch(() => null);
24975
25284
  const senderId = body?.senderId;
24976
25285
  const text = body?.text;
@@ -24991,7 +25300,7 @@ function createWaChannelRoutes(deps) {
24991
25300
  console.error(`[whatsapp-native] op=reply-dispatch senderId=${senderId} bytes=${bytes}`);
24992
25301
  return c.json({ ok: true });
24993
25302
  });
24994
- app64.post("/wa-channel/reply-document", async (c) => {
25303
+ app65.post("/wa-channel/reply-document", async (c) => {
24995
25304
  const body = await c.req.json().catch(() => null);
24996
25305
  const senderId = body?.senderId;
24997
25306
  const files = body?.files;
@@ -25030,7 +25339,7 @@ function createWaChannelRoutes(deps) {
25030
25339
  }
25031
25340
  return c.json({ ok: true, results });
25032
25341
  });
25033
- app64.post("/wa-channel/ready", async (c) => {
25342
+ app65.post("/wa-channel/ready", async (c) => {
25034
25343
  const body = await c.req.json().catch(() => null);
25035
25344
  const senderId = body?.senderId;
25036
25345
  if (typeof senderId !== "string") {
@@ -25039,7 +25348,7 @@ function createWaChannelRoutes(deps) {
25039
25348
  deps.onReady?.(senderId);
25040
25349
  return c.json({ ok: true });
25041
25350
  });
25042
- app64.post("/wa-channel/received", async (c) => {
25351
+ app65.post("/wa-channel/received", async (c) => {
25043
25352
  const body = await c.req.json().catch(() => null);
25044
25353
  const senderId = body?.senderId;
25045
25354
  const waMessageId = body?.waMessageId;
@@ -25049,7 +25358,7 @@ function createWaChannelRoutes(deps) {
25049
25358
  deps.onReceived?.(senderId, waMessageId);
25050
25359
  return c.json({ ok: true });
25051
25360
  });
25052
- app64.post("/wa-channel/turn-end", async (c) => {
25361
+ app65.post("/wa-channel/turn-end", async (c) => {
25053
25362
  const body = await c.req.json().catch(() => null);
25054
25363
  const senderId = body?.senderId;
25055
25364
  const sessionId = body?.sessionId;
@@ -25082,7 +25391,7 @@ function createWaChannelRoutes(deps) {
25082
25391
  console.error(`[whatsapp-native] op=turn-end sessionId=${sid} replied=no delivered=none`);
25083
25392
  return c.json({ ok: true, delivered: "none" });
25084
25393
  });
25085
- return app64;
25394
+ return app65;
25086
25395
  }
25087
25396
 
25088
25397
  // app/lib/whatsapp/gateway/wa-gateway.ts
@@ -25104,9 +25413,30 @@ var WaGateway = class {
25104
25413
  spawning = /* @__PURE__ */ new Set();
25105
25414
  seq = 0;
25106
25415
  /** Hono sub-app exposing the loopback channel routes; mount on the UI app. */
25416
+ /** Task 1789 — senderId -> the ms at which its channel server last detached,
25417
+ * cleared on attach. The standing audit reads this; nothing else does. */
25418
+ detachedAt = /* @__PURE__ */ new Map();
25419
+ /** Seat lost. `atMs` is injected so the audit's accounting is testable. */
25420
+ recordDetached(senderId, atMs) {
25421
+ this.detachedAt.set(senderId, atMs);
25422
+ }
25423
+ /** Seat regained — the sender is no longer unseated. */
25424
+ recordAttached(senderId) {
25425
+ this.detachedAt.delete(senderId);
25426
+ }
25427
+ /** Senders with no channel server for longer than `olderThanMs`. */
25428
+ unseatedSenders(nowMs, olderThanMs) {
25429
+ const out = [];
25430
+ for (const [senderId, sinceMs] of this.detachedAt) {
25431
+ if (nowMs - sinceMs > olderThanMs) out.push({ senderId, sinceMs });
25432
+ }
25433
+ return out.sort((a, b) => a.senderId.localeCompare(b.senderId));
25434
+ }
25107
25435
  routes() {
25108
25436
  return createWaChannelRoutes({
25109
25437
  hub: this.hub,
25438
+ onAttached: (senderId) => this.recordAttached(senderId),
25439
+ onDetached: (senderId) => this.recordDetached(senderId, Date.now()),
25110
25440
  sendOutbound: (senderId, text) => this.sendOutbound(senderId, text),
25111
25441
  sendDocument: (senderId, filePath, caption) => this.sendDocument(senderId, filePath, caption),
25112
25442
  onReady: (senderId) => {
@@ -25217,7 +25547,7 @@ var WaGateway = class {
25217
25547
  },
25218
25548
  Date.now()
25219
25549
  );
25220
- const resolvedSessionId = adminSessionIdFor(
25550
+ const resolvedSessionId = (input.role === "admin" ? this.deps.resolveSessionOverride?.(input.effectiveAccountId, senderId) : null) || adminSessionIdFor(
25221
25551
  input.effectiveAccountId,
25222
25552
  senderId,
25223
25553
  input.role === "public" && input.personId ? input.personId : void 0
@@ -25285,17 +25615,28 @@ function logDuplicateSenderGroups(groups) {
25285
25615
  );
25286
25616
  }
25287
25617
  }
25618
+ function logUnseatedSenders(entries, nowMs) {
25619
+ for (const e of entries) {
25620
+ const secs = Math.round((nowMs - e.sinceMs) / 1e3);
25621
+ console.error(`[whatsapp-native] op=sender-unseated senderId=${e.senderId} since=${new Date(e.sinceMs).toISOString()} forSeconds=${secs}`);
25622
+ }
25623
+ }
25288
25624
 
25289
25625
  // app/lib/whatsapp/gateway/spawn-request.ts
25290
25626
  function buildWaSpawnRequest(input) {
25291
25627
  const personId = input.role === "public" && input.personId ? input.personId : void 0;
25292
25628
  return {
25293
- // Task 1746 — the deterministic per-sender id is the ONLY id a sender's
25294
- // session ever has. Task 1526's per-firing seat override used to replace it
25295
- // on a scheduled cold start, which spawned a second session for the sender;
25296
- // that session then attached under the sender's own hub key and swallowed
25297
- // their real messages until it detached, splitting the conversation in two.
25298
- sessionId: adminSessionIdFor(input.accountId, input.senderId, personId),
25629
+ // Task 1746 — the deterministic per-sender id is the DEFAULT id a sender's
25630
+ // session has. Task 1526's per-firing seat override used to replace it on a
25631
+ // scheduled cold start, which spawned a second session per firing; that
25632
+ // session then attached under the sender's own hub key and swallowed their
25633
+ // real messages until it detached, splitting the conversation in two.
25634
+ //
25635
+ // Task 1789's override is not that. It is a stable per-sender forward
25636
+ // pointer written once by an operator re-seat and resolving identically for
25637
+ // every subsequent inbound, so it can never yield a second id for a sender.
25638
+ // Do not remove it without reading server/channel-session-override.ts.
25639
+ sessionId: input.overrideSessionId || adminSessionIdFor(input.accountId, input.senderId, personId),
25299
25640
  role: input.role,
25300
25641
  channel: "whatsapp",
25301
25642
  personId,
@@ -25479,8 +25820,8 @@ var import_dist7 = __toESM(require_dist(), 1);
25479
25820
 
25480
25821
  // app/lib/webchat/gateway/routes.ts
25481
25822
  function createWebchatChannelRoutes(deps) {
25482
- const app64 = new Hono();
25483
- app64.get("/webchat-channel/inbound", (c) => {
25823
+ const app65 = new Hono();
25824
+ app65.get("/webchat-channel/inbound", (c) => {
25484
25825
  const key = c.req.query("key");
25485
25826
  if (!key) return c.json({ error: "key required" }, 400);
25486
25827
  return streamSSE(c, async (stream2) => {
@@ -25498,7 +25839,7 @@ function createWebchatChannelRoutes(deps) {
25498
25839
  }
25499
25840
  });
25500
25841
  });
25501
- app64.post("/webchat-channel/reply", async (c) => {
25842
+ app65.post("/webchat-channel/reply", async (c) => {
25502
25843
  const body = await c.req.json().catch(() => null);
25503
25844
  const key = body?.key;
25504
25845
  const text = body?.text;
@@ -25508,7 +25849,7 @@ function createWebchatChannelRoutes(deps) {
25508
25849
  deps.onReply?.(key, text);
25509
25850
  return c.json({ ok: true });
25510
25851
  });
25511
- app64.post("/webchat-channel/connector-auth", async (c) => {
25852
+ app65.post("/webchat-channel/connector-auth", async (c) => {
25512
25853
  const body = await c.req.json().catch(() => null);
25513
25854
  const key = body?.key;
25514
25855
  const sessionId = body?.sessionId;
@@ -25521,7 +25862,7 @@ function createWebchatChannelRoutes(deps) {
25521
25862
  const result = await deps.onConnectorAuth({ key, sessionId, name, completed });
25522
25863
  return c.json(result);
25523
25864
  });
25524
- app64.post("/webchat-channel/ready", async (c) => {
25865
+ app65.post("/webchat-channel/ready", async (c) => {
25525
25866
  const body = await c.req.json().catch(() => null);
25526
25867
  const key = body?.key;
25527
25868
  if (typeof key !== "string") {
@@ -25530,7 +25871,7 @@ function createWebchatChannelRoutes(deps) {
25530
25871
  deps.onReady?.(key);
25531
25872
  return c.json({ ok: true });
25532
25873
  });
25533
- app64.post("/webchat-channel/permission-request", async (c) => {
25874
+ app65.post("/webchat-channel/permission-request", async (c) => {
25534
25875
  const body = await c.req.json().catch(() => null);
25535
25876
  const key = body?.key;
25536
25877
  const requestId = body?.request_id;
@@ -25544,7 +25885,7 @@ function createWebchatChannelRoutes(deps) {
25544
25885
  const verdict = await deps.awaitPermissionVerdict({ key, requestId, toolName, description, inputPreview });
25545
25886
  return c.json(verdict);
25546
25887
  });
25547
- app64.post("/webchat-channel/received", async (c) => {
25888
+ app65.post("/webchat-channel/received", async (c) => {
25548
25889
  const body = await c.req.json().catch(() => null);
25549
25890
  const key = body?.key;
25550
25891
  const messageId = body?.messageId;
@@ -25554,7 +25895,7 @@ function createWebchatChannelRoutes(deps) {
25554
25895
  deps.onReceived?.(key, messageId);
25555
25896
  return c.json({ ok: true });
25556
25897
  });
25557
- app64.post("/webchat-channel/turn-end", async (c) => {
25898
+ app65.post("/webchat-channel/turn-end", async (c) => {
25558
25899
  const body = await c.req.json().catch(() => null);
25559
25900
  const key = body?.key;
25560
25901
  const sessionId = body?.sessionId;
@@ -25577,7 +25918,7 @@ function createWebchatChannelRoutes(deps) {
25577
25918
  console.error(`[webchat-native] op=turn-end channel=webchat key=${k} sessionId=${sid} replied=no delivered=none`);
25578
25919
  return c.json({ ok: true, delivered: "none" });
25579
25920
  });
25580
- return app64;
25921
+ return app65;
25581
25922
  }
25582
25923
 
25583
25924
  // app/lib/webchat/gateway/webchat-gateway.ts
@@ -25635,16 +25976,16 @@ var WebchatGateway = class _WebchatGateway {
25635
25976
  * The public /api/chat route awaits this to return the reply on the same SSE
25636
25977
  * response, preserving the pre-756 POST→single-blob browser contract. */
25637
25978
  awaitReply(key, timeoutMs) {
25638
- return new Promise((resolve37) => {
25979
+ return new Promise((resolve38) => {
25639
25980
  const timer = setTimeout(() => {
25640
25981
  this.replyAwaiters.delete(key);
25641
- resolve37({ timeout: true });
25982
+ resolve38({ timeout: true });
25642
25983
  }, timeoutMs);
25643
25984
  if (timer.unref) timer.unref();
25644
25985
  this.replyAwaiters.set(key, (r) => {
25645
25986
  clearTimeout(timer);
25646
25987
  this.replyAwaiters.delete(key);
25647
- resolve37(r);
25988
+ resolve38(r);
25648
25989
  });
25649
25990
  });
25650
25991
  }
@@ -25655,11 +25996,11 @@ var WebchatGateway = class _WebchatGateway {
25655
25996
  * terminal dialog stays answerable). */
25656
25997
  awaitPermissionVerdict(p, timeoutMs) {
25657
25998
  const k = _WebchatGateway.promptKey(p.key, p.requestId);
25658
- return new Promise((resolve37) => {
25999
+ return new Promise((resolve38) => {
25659
26000
  this.pendingPrompts.get(k)?.resolve({ timeout: true });
25660
26001
  const timer = setTimeout(() => {
25661
26002
  this.pendingPrompts.delete(k);
25662
- resolve37({ timeout: true });
26003
+ resolve38({ timeout: true });
25663
26004
  }, timeoutMs);
25664
26005
  if (timer.unref) timer.unref();
25665
26006
  this.pendingPrompts.set(k, {
@@ -25668,7 +26009,7 @@ var WebchatGateway = class _WebchatGateway {
25668
26009
  resolve: (r) => {
25669
26010
  clearTimeout(timer);
25670
26011
  this.pendingPrompts.delete(k);
25671
- resolve37(r);
26012
+ resolve38(r);
25672
26013
  }
25673
26014
  });
25674
26015
  console.error(`[webchat:perm] op=open key=${keyDisplay(p.key)} id=${p.requestId} tool=${p.toolName}`);
@@ -26233,7 +26574,7 @@ async function fanOut(subscribers, text, onError, tag) {
26233
26574
 
26234
26575
  // app/lib/webchat/file-delivery.ts
26235
26576
  import { realpathSync as realpathSync8 } from "fs";
26236
- import { resolve as resolve32 } from "path";
26577
+ import { resolve as resolve33 } from "path";
26237
26578
 
26238
26579
  // app/lib/channel-pty-bridge/file-delivery.ts
26239
26580
  var SEND_USER_FILE = "SendUserFile";
@@ -26324,29 +26665,29 @@ function makeFileDelivery(opts) {
26324
26665
  }
26325
26666
 
26326
26667
  // app/lib/webchat/file-delivery.ts
26327
- var TAG48 = "[webchat-adaptor]";
26668
+ var TAG49 = "[webchat-adaptor]";
26328
26669
  function platformRoot2() {
26329
26670
  return process.env.MAXY_PLATFORM_ROOT || "";
26330
26671
  }
26331
26672
  function makeWebchatSendFile(entry) {
26332
26673
  return async (filePath) => {
26333
26674
  if (!entry.accountId) {
26334
- console.error(`${TAG48} file-delivery reject reason=no-account sender=${entry.senderId}`);
26675
+ console.error(`${TAG49} file-delivery reject reason=no-account sender=${entry.senderId}`);
26335
26676
  return { ok: false, error: "no-account" };
26336
26677
  }
26337
- const accountDir = resolve32(platformRoot2(), "..", "data/accounts", entry.accountId);
26678
+ const accountDir = resolve33(platformRoot2(), "..", "data/accounts", entry.accountId);
26338
26679
  try {
26339
26680
  const resolved = realpathSync8(filePath);
26340
26681
  const accountResolved = realpathSync8(accountDir);
26341
26682
  if (!resolved.startsWith(accountResolved + "/")) {
26342
- console.error(`${TAG48} file-delivery reject reason=outside_account_directory sender=${entry.senderId}`);
26683
+ console.error(`${TAG49} file-delivery reject reason=outside_account_directory sender=${entry.senderId}`);
26343
26684
  return { ok: false, error: "outside-account" };
26344
26685
  }
26345
26686
  return { ok: true };
26346
26687
  } catch (err) {
26347
26688
  const code = err.code;
26348
26689
  console.error(
26349
- `${TAG48} file-delivery reject reason=${code === "ENOENT" ? "not-found" : "path-error"} sender=${entry.senderId}`
26690
+ `${TAG49} file-delivery reject reason=${code === "ENOENT" ? "not-found" : "path-error"} sender=${entry.senderId}`
26350
26691
  );
26351
26692
  return { ok: false, error: code === "ENOENT" ? "not-found" : "path-error" };
26352
26693
  }
@@ -26355,7 +26696,7 @@ function makeWebchatSendFile(entry) {
26355
26696
  function makeWebchatFileDelivery(entry) {
26356
26697
  return makeFileDelivery({
26357
26698
  entry,
26358
- tag: TAG48,
26699
+ tag: TAG49,
26359
26700
  channel: "webchat",
26360
26701
  sendFile: makeWebchatSendFile(entry),
26361
26702
  deferUntilVerdict: true
@@ -26417,7 +26758,7 @@ function buildPublicWebchatSpawnRequest(input) {
26417
26758
  }
26418
26759
 
26419
26760
  // app/lib/whatsapp/inbound/file-delivery-bridge.ts
26420
- var TAG49 = "[whatsapp-adaptor]";
26761
+ var TAG50 = "[whatsapp-adaptor]";
26421
26762
  var SEND_USER_FILE2 = "SendUserFile";
26422
26763
  var WHATSAPP_SEND_DOCUMENT = "whatsapp-send-document";
26423
26764
  function platformRoot3() {
@@ -26435,7 +26776,7 @@ function makeWhatsAppSendFile(entry, maxyAccountId) {
26435
26776
  });
26436
26777
  if (result.ok) return { ok: true };
26437
26778
  console.error(
26438
- `${TAG49} file-delivery reject reason=send-failed sender=${entry.senderId} status=${result.status} message=${result.error}`
26779
+ `${TAG50} file-delivery reject reason=send-failed sender=${entry.senderId} status=${result.status} message=${result.error}`
26439
26780
  );
26440
26781
  return { ok: false, error: result.error };
26441
26782
  };
@@ -26443,7 +26784,7 @@ function makeWhatsAppSendFile(entry, maxyAccountId) {
26443
26784
  function makeWhatsAppFileDelivery(entry, maxyAccountId) {
26444
26785
  const shared = makeFileDelivery({
26445
26786
  entry,
26446
- tag: TAG49,
26787
+ tag: TAG50,
26447
26788
  channel: "whatsapp",
26448
26789
  sendFile: makeWhatsAppSendFile(entry, maxyAccountId)
26449
26790
  });
@@ -26478,7 +26819,7 @@ function makeWhatsAppFileDelivery(entry, maxyAccountId) {
26478
26819
  if (!delivered) {
26479
26820
  const fileField = call.filePath !== void 0 ? ` file=${call.filePath}` : "";
26480
26821
  console.error(
26481
- `${TAG49} file-delivery-unreconciled sender=${entry.senderId} sessionId=${sid} tool=${WHATSAPP_SEND_DOCUMENT}${fileField}`
26822
+ `${TAG50} file-delivery-unreconciled sender=${entry.senderId} sessionId=${sid} tool=${WHATSAPP_SEND_DOCUMENT}${fileField}`
26482
26823
  );
26483
26824
  }
26484
26825
  }
@@ -26664,8 +27005,8 @@ var import_dist8 = __toESM(require_dist(), 1);
26664
27005
 
26665
27006
  // app/lib/telegram/gateway/routes.ts
26666
27007
  function createTelegramChannelRoutes(deps) {
26667
- const app64 = new Hono();
26668
- app64.get("/tg-channel/inbound", (c) => {
27008
+ const app65 = new Hono();
27009
+ app65.get("/tg-channel/inbound", (c) => {
26669
27010
  const key = c.req.query("key");
26670
27011
  if (!key) return c.json({ error: "key required" }, 400);
26671
27012
  return streamSSE(c, async (stream2) => {
@@ -26683,7 +27024,7 @@ function createTelegramChannelRoutes(deps) {
26683
27024
  }
26684
27025
  });
26685
27026
  });
26686
- app64.post("/tg-channel/reply", async (c) => {
27027
+ app65.post("/tg-channel/reply", async (c) => {
26687
27028
  const body = await c.req.json().catch(() => null);
26688
27029
  const key = body?.key;
26689
27030
  const text = body?.text;
@@ -26699,7 +27040,7 @@ function createTelegramChannelRoutes(deps) {
26699
27040
  console.error(`[telegram-native] op=reply key=${key} bytes=${Buffer.byteLength(text, "utf8")}`);
26700
27041
  return c.json({ ok: true });
26701
27042
  });
26702
- app64.post("/tg-channel/ready", async (c) => {
27043
+ app65.post("/tg-channel/ready", async (c) => {
26703
27044
  const body = await c.req.json().catch(() => null);
26704
27045
  const key = body?.key;
26705
27046
  if (typeof key !== "string") {
@@ -26708,7 +27049,7 @@ function createTelegramChannelRoutes(deps) {
26708
27049
  deps.onReady?.(key);
26709
27050
  return c.json({ ok: true });
26710
27051
  });
26711
- app64.post("/tg-channel/received", async (c) => {
27052
+ app65.post("/tg-channel/received", async (c) => {
26712
27053
  const body = await c.req.json().catch(() => null);
26713
27054
  const key = body?.key;
26714
27055
  const messageId = body?.messageId;
@@ -26718,7 +27059,7 @@ function createTelegramChannelRoutes(deps) {
26718
27059
  deps.onReceived?.(key, messageId);
26719
27060
  return c.json({ ok: true });
26720
27061
  });
26721
- app64.post("/tg-channel/turn-end", async (c) => {
27062
+ app65.post("/tg-channel/turn-end", async (c) => {
26722
27063
  const body = await c.req.json().catch(() => null);
26723
27064
  const key = body?.key;
26724
27065
  const sessionId = body?.sessionId;
@@ -26751,7 +27092,7 @@ function createTelegramChannelRoutes(deps) {
26751
27092
  console.error(`[telegram-native] op=turn-end key=${key} sessionId=${sid} replied=no delivered=none`);
26752
27093
  return c.json({ ok: true, delivered: "none" });
26753
27094
  });
26754
- return app64;
27095
+ return app65;
26755
27096
  }
26756
27097
 
26757
27098
  // app/lib/telegram/gateway/telegram-gateway.ts
@@ -26842,7 +27183,7 @@ var TelegramGateway = class {
26842
27183
  { key: input.senderId, text: input.text, messageId: `tg-${++this.seq}`, source, standingRules, scheduleProvenance: input.scheduleProvenance },
26843
27184
  Date.now()
26844
27185
  );
26845
- const resolvedSessionId = adminSessionIdFor(
27186
+ const resolvedSessionId = (input.role === "admin" ? this.deps.resolveSessionOverride?.(input.accountId, input.senderId) : null) || adminSessionIdFor(
26846
27187
  input.accountId,
26847
27188
  input.senderId,
26848
27189
  input.role === "public" && input.personId ? input.personId : void 0
@@ -26879,10 +27220,11 @@ var TelegramGateway = class {
26879
27220
  function buildTelegramSpawnRequest(input) {
26880
27221
  const personId = input.role === "public" && input.personId ? input.personId : void 0;
26881
27222
  return {
26882
- // Task 1746 — the deterministic per-sender id is the ONLY id a sender's
26883
- // session ever has. Mirror of the WhatsApp builder; see its comment for why
26884
- // Task 1526's per-firing override was removed.
26885
- sessionId: adminSessionIdFor(input.accountId, input.senderId, personId),
27223
+ // Task 1746 / 1789 — the deterministic per-sender id is the DEFAULT; an
27224
+ // operator re-seat sets a stable per-sender forward pointer that wins here.
27225
+ // Mirror of the WhatsApp builder; see its comment for the full distinction
27226
+ // between this pointer and Task 1526's removed per-firing seat.
27227
+ sessionId: input.overrideSessionId || adminSessionIdFor(input.accountId, input.senderId, personId),
26886
27228
  role: input.role,
26887
27229
  channel: "telegram",
26888
27230
  personId,
@@ -26899,9 +27241,9 @@ function buildTelegramSpawnRequest(input) {
26899
27241
 
26900
27242
  // app/lib/telegram/outbound/send-document.ts
26901
27243
  import { realpathSync as realpathSync9 } from "fs";
26902
- import { readFile as readFile7, stat as stat7 } from "fs/promises";
26903
- import { resolve as resolve33, basename as basename11 } from "path";
26904
- var TAG50 = "[telegram:outbound]";
27244
+ import { readFile as readFile8, stat as stat8 } from "fs/promises";
27245
+ import { resolve as resolve34, basename as basename11 } from "path";
27246
+ var TAG51 = "[telegram:outbound]";
26905
27247
  var TELEGRAM_DOCUMENT_MAX_BYTES = 50 * 1024 * 1024;
26906
27248
  async function sendTelegramDocument(input) {
26907
27249
  const { botToken, chatId, filePath, caption, maxyAccountId, platformRoot: platformRoot5 } = input;
@@ -26911,25 +27253,25 @@ async function sendTelegramDocument(input) {
26911
27253
  if (!maxyAccountId || !platformRoot5) {
26912
27254
  return { ok: false, status: 400, error: "Cannot validate file path: missing account or platform context" };
26913
27255
  }
26914
- const accountDir = resolve33(platformRoot5, "..", "data/accounts", maxyAccountId);
27256
+ const accountDir = resolve34(platformRoot5, "..", "data/accounts", maxyAccountId);
26915
27257
  let resolvedPath;
26916
27258
  try {
26917
27259
  resolvedPath = realpathSync9(filePath);
26918
27260
  const accountResolved = realpathSync9(accountDir);
26919
27261
  if (!resolvedPath.startsWith(accountResolved + "/")) {
26920
- console.error(`${TAG50} document REJECTED reason=outside_account_directory maxyAccountId=${maxyAccountId}`);
27262
+ console.error(`${TAG51} document REJECTED reason=outside_account_directory maxyAccountId=${maxyAccountId}`);
26921
27263
  return { ok: false, status: 403, error: "Access denied: file is outside the account directory" };
26922
27264
  }
26923
27265
  } catch (err) {
26924
27266
  const code = err.code;
26925
27267
  if (code === "ENOENT") {
26926
- console.error(`${TAG50} document ENOENT path=${filePath}`);
27268
+ console.error(`${TAG51} document ENOENT path=${filePath}`);
26927
27269
  return { ok: false, status: 404, error: `File not found: ${filePath}` };
26928
27270
  }
26929
- console.error(`${TAG50} document path error: ${String(err)}`);
27271
+ console.error(`${TAG51} document path error: ${String(err)}`);
26930
27272
  return { ok: false, status: 500, error: String(err) };
26931
27273
  }
26932
- const fileStat = await stat7(resolvedPath);
27274
+ const fileStat = await stat8(resolvedPath);
26933
27275
  if (fileStat.size > TELEGRAM_DOCUMENT_MAX_BYTES) {
26934
27276
  return {
26935
27277
  ok: false,
@@ -26938,7 +27280,7 @@ async function sendTelegramDocument(input) {
26938
27280
  };
26939
27281
  }
26940
27282
  const filename = basename11(resolvedPath);
26941
- const buffer = Buffer.from(await readFile7(resolvedPath));
27283
+ const buffer = Buffer.from(await readFile8(resolvedPath));
26942
27284
  const form = new FormData();
26943
27285
  form.append("chat_id", String(chatId));
26944
27286
  if (caption) form.append("caption", caption);
@@ -26959,13 +27301,13 @@ async function sendTelegramDocument(input) {
26959
27301
  error = e instanceof Error ? e.message : String(e);
26960
27302
  }
26961
27303
  console.error(
26962
- `${TAG50} sent document to=${chatId} file=${filename} bytes=${fileStat.size} ok=${ok}` + (messageId !== void 0 ? ` id=${messageId}` : "")
27304
+ `${TAG51} sent document to=${chatId} file=${filename} bytes=${fileStat.size} ok=${ok}` + (messageId !== void 0 ? ` id=${messageId}` : "")
26963
27305
  );
26964
27306
  return ok ? { ok: true, messageId } : { ok: false, status: 500, error };
26965
27307
  }
26966
27308
 
26967
27309
  // app/lib/telegram/outbound/file-delivery.ts
26968
- var TAG51 = "[telegram:outbound]";
27310
+ var TAG52 = "[telegram:outbound]";
26969
27311
  function platformRoot4() {
26970
27312
  return process.env.MAXY_PLATFORM_ROOT || "";
26971
27313
  }
@@ -26977,11 +27319,11 @@ function makeTelegramSendFile(entry) {
26977
27319
  botToken = (entry.role === "admin" ? tg?.adminBotToken : tg?.publicBotToken) ?? null;
26978
27320
  }
26979
27321
  if (!botToken) {
26980
- console.error(`${TAG51} file-delivery reject reason=no-bot-token sender=${entry.senderId} role=${entry.role}`);
27322
+ console.error(`${TAG52} file-delivery reject reason=no-bot-token sender=${entry.senderId} role=${entry.role}`);
26981
27323
  return { ok: false, error: "no-bot-token" };
26982
27324
  }
26983
27325
  if (entry.replyTarget == null) {
26984
- console.error(`${TAG51} file-delivery reject reason=no-reply-target sender=${entry.senderId} role=${entry.role}`);
27326
+ console.error(`${TAG52} file-delivery reject reason=no-reply-target sender=${entry.senderId} role=${entry.role}`);
26985
27327
  return { ok: false, error: "no-reply-target" };
26986
27328
  }
26987
27329
  const result = await sendTelegramDocument({
@@ -26998,7 +27340,7 @@ function makeTelegramSendFile(entry) {
26998
27340
  function makeTelegramFileDelivery(entry) {
26999
27341
  return makeFileDelivery({
27000
27342
  entry,
27001
- tag: TAG51,
27343
+ tag: TAG52,
27002
27344
  channel: "telegram",
27003
27345
  sendFile: makeTelegramSendFile(entry)
27004
27346
  });
@@ -27038,17 +27380,17 @@ function startTelegramNativeFileFollower(input) {
27038
27380
  }
27039
27381
 
27040
27382
  // server/telegram-descriptor.ts
27041
- import { resolve as resolve34, join as join41 } from "path";
27383
+ import { resolve as resolve35, join as join43 } from "path";
27042
27384
  function telegramGatewayUrl() {
27043
27385
  return `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`;
27044
27386
  }
27045
27387
  function telegramServerPath() {
27046
- return process.env.MAXY_TELEGRAM_CHANNEL_SERVER_PATH ?? resolve34(process.env.MAXY_PLATFORM_ROOT ?? join41(__dirname, ".."), "services/telegram-channel/dist/server.js");
27388
+ return process.env.MAXY_TELEGRAM_CHANNEL_SERVER_PATH ?? resolve35(process.env.MAXY_PLATFORM_ROOT ?? join43(__dirname, ".."), "services/telegram-channel/dist/server.js");
27047
27389
  }
27048
27390
 
27049
27391
  // app/lib/channel-pty-bridge/public-session-end-review.ts
27050
27392
  import { randomUUID as randomUUID16 } from "crypto";
27051
- var TAG52 = "[public-session-review]";
27393
+ var TAG53 = "[public-session-review]";
27052
27394
  var CONSUMED_CAP = 500;
27053
27395
  var consumed = /* @__PURE__ */ new Set();
27054
27396
  function consumeOnce(sessionId) {
@@ -27075,7 +27417,7 @@ async function fetchJsonl(sessionId) {
27075
27417
  return await res.text();
27076
27418
  } catch (err) {
27077
27419
  console.error(
27078
- `${TAG52} jsonl-fetch-failed sessionId=${sessionId} err="${err instanceof Error ? err.message : String(err)}"`
27420
+ `${TAG53} jsonl-fetch-failed sessionId=${sessionId} err="${err instanceof Error ? err.message : String(err)}"`
27079
27421
  );
27080
27422
  return null;
27081
27423
  }
@@ -27099,7 +27441,7 @@ async function fetchPriorWrites(sliceToken, accountId) {
27099
27441
  const res = await fetch(url);
27100
27442
  if (!res.ok) {
27101
27443
  console.error(
27102
- `${TAG52} prior-writes-fetch-failed sliceToken=${sliceToken.slice(0, 8)} status=${res.status}`
27444
+ `${TAG53} prior-writes-fetch-failed sliceToken=${sliceToken.slice(0, 8)} status=${res.status}`
27103
27445
  );
27104
27446
  return [];
27105
27447
  }
@@ -27107,7 +27449,7 @@ async function fetchPriorWrites(sliceToken, accountId) {
27107
27449
  return Array.isArray(body.writes) ? body.writes : [];
27108
27450
  } catch (err) {
27109
27451
  console.error(
27110
- `${TAG52} prior-writes-fetch-threw sliceToken=${sliceToken.slice(0, 8)} err="${err instanceof Error ? err.message : String(err)}"`
27452
+ `${TAG53} prior-writes-fetch-threw sliceToken=${sliceToken.slice(0, 8)} err="${err instanceof Error ? err.message : String(err)}"`
27111
27453
  );
27112
27454
  return [];
27113
27455
  }
@@ -27136,7 +27478,7 @@ function composeInitialMessage(input) {
27136
27478
  }
27137
27479
  async function dispatchReviewer(input, initialMessage) {
27138
27480
  const sessionId = randomUUID16();
27139
- console.log(`${TAG52} route target=rc-spawn sessionId=${sessionId.slice(0, 8)} sourceSession=${input.sessionId.slice(0, 8)}`);
27481
+ console.log(`${TAG53} route target=rc-spawn sessionId=${sessionId.slice(0, 8)} sourceSession=${input.sessionId.slice(0, 8)}`);
27140
27482
  const spawned = await managerRcSpawn({
27141
27483
  sessionId,
27142
27484
  initialMessage,
@@ -27156,21 +27498,21 @@ async function firePublicSessionEndReview(input) {
27156
27498
  const dispatchedAt = Date.now();
27157
27499
  if (consumed.has(input.sessionId)) {
27158
27500
  console.log(
27159
- `${TAG52} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-already-reviewed`
27501
+ `${TAG53} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-already-reviewed`
27160
27502
  );
27161
27503
  return;
27162
27504
  }
27163
27505
  const jsonl = await fetchJsonl(input.sessionId);
27164
27506
  if (!jsonl) {
27165
27507
  console.log(
27166
- `${TAG52} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-jsonl-missing`
27508
+ `${TAG53} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-jsonl-missing`
27167
27509
  );
27168
27510
  return;
27169
27511
  }
27170
27512
  const operatorTurns = countOperatorTurns(jsonl);
27171
27513
  if (operatorTurns === 0) {
27172
27514
  console.log(
27173
- `${TAG52} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-no-turns`
27515
+ `${TAG53} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-no-turns`
27174
27516
  );
27175
27517
  return;
27176
27518
  }
@@ -27184,19 +27526,19 @@ async function firePublicSessionEndReview(input) {
27184
27526
  });
27185
27527
  if (!consumeOnce(input.sessionId)) {
27186
27528
  console.log(
27187
- `${TAG52} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-already-reviewed`
27529
+ `${TAG53} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-already-reviewed`
27188
27530
  );
27189
27531
  return;
27190
27532
  }
27191
27533
  const dispatched = await dispatchReviewer(input, initialMessage);
27192
27534
  if (!dispatched.ok) {
27193
27535
  console.error(
27194
- `${TAG52} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-spawn-failed reason=${dispatched.reason}`
27536
+ `${TAG53} dispatchFor=${input.dispatchFor} sessionId=${input.sessionId} sliceToken=${sliceShort} personId=${personField} result=skipped-spawn-failed reason=${dispatched.reason}`
27195
27537
  );
27196
27538
  return;
27197
27539
  }
27198
27540
  console.log(
27199
- `${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}`
27541
+ `${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}`
27200
27542
  );
27201
27543
  }
27202
27544
 
@@ -27330,8 +27672,8 @@ function broadcastAdminShutdown(reason) {
27330
27672
 
27331
27673
  // ../lib/entitlement/src/index.ts
27332
27674
  import { createPublicKey, createHash as createHash6, verify as cryptoVerify } from "crypto";
27333
- import { existsSync as existsSync37, readFileSync as readFileSync40, statSync as statSync16 } from "fs";
27334
- import { resolve as resolve35 } from "path";
27675
+ import { existsSync as existsSync37, readFileSync as readFileSync41, statSync as statSync16 } from "fs";
27676
+ import { resolve as resolve36 } from "path";
27335
27677
 
27336
27678
  // ../lib/entitlement/src/canonicalize.ts
27337
27679
  function canonicalize(value) {
@@ -27366,7 +27708,7 @@ var PUBKEY_SHA256 = "8eee6bcb33545fd13b16d3199a5735ca5db5062834c7b49dfe4f23801d9
27366
27708
  var GRACE_DAYS = 7;
27367
27709
  var GRACE_MS = GRACE_DAYS * 24 * 60 * 60 * 1e3;
27368
27710
  function pubkeyPath(brand) {
27369
- return resolve35(brand.platformRoot, "lib", "entitlement", "rubytech-pubkey.pem");
27711
+ return resolve36(brand.platformRoot, "lib", "entitlement", "rubytech-pubkey.pem");
27370
27712
  }
27371
27713
  var memo = null;
27372
27714
  function memoKey(mtimeMs, account) {
@@ -27378,12 +27720,12 @@ function resolveEntitlement(brand, account) {
27378
27720
  if (brand.commercialMode !== true) {
27379
27721
  return logResolved(implicitTrust(account), null);
27380
27722
  }
27381
- const entitlementPath = resolve35(brand.configDir, "entitlement.json");
27723
+ const entitlementPath = resolve36(brand.configDir, "entitlement.json");
27382
27724
  if (!existsSync37(entitlementPath)) {
27383
27725
  return logResolved(anonymousFallback("missing"), { reason: "missing" });
27384
27726
  }
27385
- const stat8 = statSync16(entitlementPath);
27386
- const key = memoKey(stat8.mtimeMs, account);
27727
+ const stat9 = statSync16(entitlementPath);
27728
+ const key = memoKey(stat9.mtimeMs, account);
27387
27729
  if (memo && memo.key === key) {
27388
27730
  return memo.result;
27389
27731
  }
@@ -27394,7 +27736,7 @@ function resolveEntitlement(brand, account) {
27394
27736
  function verifyAndResolve(brand, entitlementPath, account) {
27395
27737
  let pubkeyPem;
27396
27738
  try {
27397
- pubkeyPem = readFileSync40(pubkeyPath(brand), "utf-8");
27739
+ pubkeyPem = readFileSync41(pubkeyPath(brand), "utf-8");
27398
27740
  } catch (err) {
27399
27741
  return logResolved(anonymousFallback("pubkey-missing"), {
27400
27742
  reason: "pubkey-missing"
@@ -27408,7 +27750,7 @@ function verifyAndResolve(brand, entitlementPath, account) {
27408
27750
  }
27409
27751
  let envelope;
27410
27752
  try {
27411
- envelope = JSON.parse(readFileSync40(entitlementPath, "utf-8"));
27753
+ envelope = JSON.parse(readFileSync41(entitlementPath, "utf-8"));
27412
27754
  } catch {
27413
27755
  return logResolved(anonymousFallback("malformed"), { reason: "malformed" });
27414
27756
  }
@@ -27570,14 +27912,14 @@ function clientFrom(c) {
27570
27912
  }
27571
27913
  installMediaDownloadGuard();
27572
27914
  var PLATFORM_ROOT8 = process.env.MAXY_PLATFORM_ROOT || "";
27573
- var BRAND_JSON_PATH = PLATFORM_ROOT8 ? join42(PLATFORM_ROOT8, "config", "brand.json") : "";
27915
+ var BRAND_JSON_PATH = PLATFORM_ROOT8 ? join44(PLATFORM_ROOT8, "config", "brand.json") : "";
27574
27916
  var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", marketingUrl: "https://getmaxy.com" };
27575
27917
  if (BRAND_JSON_PATH && !existsSync38(BRAND_JSON_PATH)) {
27576
27918
  console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);
27577
27919
  }
27578
27920
  if (BRAND_JSON_PATH && existsSync38(BRAND_JSON_PATH)) {
27579
27921
  try {
27580
- const parsed = JSON.parse(readFileSync41(BRAND_JSON_PATH, "utf-8"));
27922
+ const parsed = JSON.parse(readFileSync42(BRAND_JSON_PATH, "utf-8"));
27581
27923
  BRAND = { ...BRAND, ...parsed };
27582
27924
  } catch (err) {
27583
27925
  console.error(`[brand] Failed to parse brand.json: ${err.message}`);
@@ -27609,11 +27951,11 @@ var brandLoginOpts = {
27609
27951
  appleTouchIconPath: brandAppIcon512Path,
27610
27952
  themeColor: BRAND.defaultColors?.primary
27611
27953
  };
27612
- var ALIAS_DOMAINS_PATH = join42(homedir4(), BRAND.configDir, "alias-domains.json");
27954
+ var ALIAS_DOMAINS_PATH = join44(homedir4(), BRAND.configDir, "alias-domains.json");
27613
27955
  function loadAliasDomains() {
27614
27956
  try {
27615
27957
  if (!existsSync38(ALIAS_DOMAINS_PATH)) return null;
27616
- const parsed = JSON.parse(readFileSync41(ALIAS_DOMAINS_PATH, "utf-8"));
27958
+ const parsed = JSON.parse(readFileSync42(ALIAS_DOMAINS_PATH, "utf-8"));
27617
27959
  if (!Array.isArray(parsed)) {
27618
27960
  console.error("[alias-domains] malformed alias-domains.json \u2014 expected array");
27619
27961
  return null;
@@ -27637,7 +27979,15 @@ watchFile(ALIAS_DOMAINS_PATH, { interval: 2e3 }, () => {
27637
27979
  function isPublicHost(host) {
27638
27980
  return host.startsWith("public.") || aliasDomains.has(host);
27639
27981
  }
27640
- var app63 = new Hono();
27982
+ var app64 = new Hono();
27983
+ function resolveChannelOverride(accountId, channel, senderId) {
27984
+ try {
27985
+ const dir = listValidAccounts().find((a) => a.accountId === accountId)?.accountDir;
27986
+ return dir ? readChannelOverrideId(dir, channel, senderId) : null;
27987
+ } catch {
27988
+ return null;
27989
+ }
27990
+ }
27641
27991
  var nativeFileFollowers = /* @__PURE__ */ new Map();
27642
27992
  var webchatFileFollowers = /* @__PURE__ */ new Map();
27643
27993
  async function fetchAccountStandingRules(accountId) {
@@ -27676,8 +28026,9 @@ async function recordPassiveDispatch(input) {
27676
28026
  }
27677
28027
  var waGateway = new WaGateway({
27678
28028
  fetchStandingRules: fetchAccountStandingRules,
28029
+ resolveSessionOverride: (accountId, senderId) => resolveChannelOverride(accountId, "whatsapp", senderId),
27679
28030
  gatewayUrl: `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`,
27680
- serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve36(process.env.MAXY_PLATFORM_ROOT ?? join42(__dirname, ".."), "services/whatsapp-channel/dist/server.js"),
28031
+ serverPath: process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve37(process.env.MAXY_PLATFORM_ROOT ?? join44(__dirname, ".."), "services/whatsapp-channel/dist/server.js"),
27681
28032
  // Task 751 / 1390 — file delivery on the native channel. `maxyAccountId` (the
27682
28033
  // path-validation scope) is the sender's effective SESSION account, resolved
27683
28034
  // once at the inbound gate and threaded here via the gateway's per-sender doc
@@ -27692,7 +28043,7 @@ var waGateway = new WaGateway({
27692
28043
  caption,
27693
28044
  accountId,
27694
28045
  maxyAccountId,
27695
- platformRoot: resolve36(process.env.MAXY_PLATFORM_ROOT ?? join42(__dirname, ".."))
28046
+ platformRoot: resolve37(process.env.MAXY_PLATFORM_ROOT ?? join44(__dirname, ".."))
27696
28047
  });
27697
28048
  return result.ok ? { ok: true, messageId: result.messageId } : { ok: false, error: result.error };
27698
28049
  },
@@ -27744,7 +28095,17 @@ var waGateway = new WaGateway({
27744
28095
  const managesAccount = effectiveAccountId !== accountId ? effectiveAccountId : "none";
27745
28096
  console.error(`[whatsapp-native] op=account-manager-route senderId=${senderId} managesAccount=${managesAccount} effectiveAccount=${effectiveAccountId} source=gate`);
27746
28097
  }
27747
- const req = buildWaSpawnRequest({ accountId: effectiveAccountId, senderId, role, personId, gatewayUrl, serverPath });
28098
+ const req = buildWaSpawnRequest({
28099
+ accountId: effectiveAccountId,
28100
+ senderId,
28101
+ role,
28102
+ personId,
28103
+ gatewayUrl,
28104
+ serverPath,
28105
+ // Task 1789 — admin rows only: a public session's id keys on personId as
28106
+ // well as senderId, so the per-sender pointer is not applicable to it.
28107
+ overrideSessionId: role === "admin" ? resolveChannelOverride(effectiveAccountId, "whatsapp", senderId) : null
28108
+ });
27748
28109
  await ensureSessionConversation({
27749
28110
  accountId: effectiveAccountId,
27750
28111
  sessionId: req.sessionId,
@@ -27790,13 +28151,13 @@ var waGateway = new WaGateway({
27790
28151
  nativeFileFollowers.set(senderId, ac);
27791
28152
  }
27792
28153
  });
27793
- app63.route("/", waGateway.routes());
28154
+ app64.route("/", waGateway.routes());
27794
28155
  waGateway.startSweeper();
27795
28156
  function runDuplicateSenderAudit() {
27796
28157
  try {
27797
28158
  const configDir2 = process.env.CLAUDE_CONFIG_DIR;
27798
28159
  if (!configDir2) return;
27799
- const projectsRoot = join42(configDir2, "projects");
28160
+ const projectsRoot = join44(configDir2, "projects");
27800
28161
  const records = [];
27801
28162
  for (const { path: jsonlPath, isSubagent, archived } of enumerateJsonls(projectsRoot)) {
27802
28163
  if (isSubagent || archived) continue;
@@ -27806,6 +28167,8 @@ function runDuplicateSenderAudit() {
27806
28167
  records.push({ sessionId, channel: meta.channel, senderId: meta.senderId, accountId: meta.accountId });
27807
28168
  }
27808
28169
  logDuplicateSenderGroups(findDuplicateSenderGroups(records));
28170
+ const now = Date.now();
28171
+ logUnseatedSenders(waGateway.unseatedSenders(now, 15 * 6e4), now);
27809
28172
  } catch (err) {
27810
28173
  console.error(`[whatsapp-native] op=duplicate-sender-audit-error error=${err instanceof Error ? err.message : String(err)}`);
27811
28174
  }
@@ -27912,16 +28275,26 @@ var webchatGateway = new WebchatGateway({
27912
28275
  firePublicSessionEndReview: (input) => firePublicSessionEndReview(input)
27913
28276
  });
27914
28277
  setWebchatGateway(webchatGateway);
27915
- app63.route("/", webchatGateway.routes());
28278
+ app64.route("/", webchatGateway.routes());
27916
28279
  webchatGateway.startSweeper();
27917
28280
  webchatGateway.startPublicReaper();
27918
28281
  var telegramFileFollowers = /* @__PURE__ */ new Map();
27919
28282
  var telegramGateway = new TelegramGateway({
27920
28283
  fetchStandingRules: fetchAccountStandingRules,
28284
+ resolveSessionOverride: (accountId, senderId) => resolveChannelOverride(accountId, "telegram", senderId),
27921
28285
  gatewayUrl: telegramGatewayUrl(),
27922
28286
  serverPath: telegramServerPath(),
27923
28287
  ensureChannelSession: async ({ accountId, senderId, role, personId, chatId, gatewayUrl, serverPath }) => {
27924
- const req = buildTelegramSpawnRequest({ accountId, senderId, role, personId, gatewayUrl, serverPath });
28288
+ const req = buildTelegramSpawnRequest({
28289
+ accountId,
28290
+ senderId,
28291
+ role,
28292
+ personId,
28293
+ gatewayUrl,
28294
+ serverPath,
28295
+ // Task 1789 — admin rows only; see the WhatsApp twin.
28296
+ overrideSessionId: role === "admin" ? resolveChannelOverride(accountId, "telegram", senderId) : null
28297
+ });
27925
28298
  const result = await managerRcSpawn(req);
27926
28299
  if ("error" in result) {
27927
28300
  console.error(`[telegram-native] spawn-failed senderId=${senderId} role=${role} error=${result.error} status=${result.status}`);
@@ -27946,7 +28319,7 @@ var telegramGateway = new TelegramGateway({
27946
28319
  }
27947
28320
  });
27948
28321
  setTelegramGateway(telegramGateway);
27949
- app63.route("/", telegramGateway.routes());
28322
+ app64.route("/", telegramGateway.routes());
27950
28323
  telegramGateway.startSweeper();
27951
28324
  var chatRoutes = createChatRoutes({
27952
28325
  handleInbound: (input) => webchatGateway.handleInbound(input),
@@ -27980,19 +28353,19 @@ var scheduleInjectRoutes = createScheduleInjectRoutes({
27980
28353
  return managed.account;
27981
28354
  }
27982
28355
  });
27983
- app63.route("/api/channel/schedule-inject", scheduleInjectRoutes);
27984
- app63.use("*", clientIpMiddleware);
28356
+ app64.route("/api/channel/schedule-inject", scheduleInjectRoutes);
28357
+ app64.use("*", clientIpMiddleware);
27985
28358
  function allowsSameOriginFraming(path3) {
27986
28359
  return path3 === "/vnc-viewer.html" || path3.startsWith("/api/admin/attachment/") || path3.startsWith("/api/public-reader/attachment/") || path3 === "/api/admin/files/download";
27987
28360
  }
27988
- app63.use("*", async (c, next) => {
28361
+ app64.use("*", async (c, next) => {
27989
28362
  await next();
27990
28363
  c.header("X-Content-Type-Options", "nosniff");
27991
28364
  c.header("Referrer-Policy", "strict-origin-when-cross-origin");
27992
28365
  c.header("X-Frame-Options", allowsSameOriginFraming(c.req.path) ? "SAMEORIGIN" : "DENY");
27993
28366
  });
27994
28367
  var HTTP_LOG_PATHS = /* @__PURE__ */ new Set(["/vnc-viewer.html", "/vnc-popout.html"]);
27995
- app63.use("*", async (c, next) => {
28368
+ app64.use("*", async (c, next) => {
27996
28369
  if (!HTTP_LOG_PATHS.has(c.req.path)) {
27997
28370
  await next();
27998
28371
  return;
@@ -28010,7 +28383,7 @@ app63.use("*", async (c, next) => {
28010
28383
  });
28011
28384
  }
28012
28385
  });
28013
- app63.use("*", async (c, next) => {
28386
+ app64.use("*", async (c, next) => {
28014
28387
  const host = (c.req.header("host") ?? "").split(":")[0];
28015
28388
  if (isOperatorHost(host, getOperatorDomains()) || !isPublicHost(host)) {
28016
28389
  await next();
@@ -28048,7 +28421,7 @@ function resolveRemoteAuthOpts(c) {
28048
28421
  return { ...brandLoginOpts, origin };
28049
28422
  }
28050
28423
  var MAX_LOGIN_BODY = 8 * 1024;
28051
- app63.post("/__remote-auth/login", async (c) => {
28424
+ app64.post("/__remote-auth/login", async (c) => {
28052
28425
  const client = clientFrom(c);
28053
28426
  const clientIp = client.ip || "unknown";
28054
28427
  if (!requestIsTlsTerminated(c)) {
@@ -28093,7 +28466,7 @@ app63.post("/__remote-auth/login", async (c) => {
28093
28466
  }
28094
28467
  });
28095
28468
  });
28096
- app63.get("/__remote-auth/logout", (c) => {
28469
+ app64.get("/__remote-auth/logout", (c) => {
28097
28470
  const client = clientFrom(c);
28098
28471
  const clientIp = client.ip || "unknown";
28099
28472
  console.error(`[remote-auth] logout ip=${clientIp}`);
@@ -28106,7 +28479,7 @@ app63.get("/__remote-auth/logout", (c) => {
28106
28479
  }
28107
28480
  });
28108
28481
  });
28109
- app63.post("/__remote-auth/change-password", async (c) => {
28482
+ app64.post("/__remote-auth/change-password", async (c) => {
28110
28483
  const client = clientFrom(c);
28111
28484
  const clientIp = client.ip || "unknown";
28112
28485
  const rateLimited = checkRateLimit(client);
@@ -28165,13 +28538,13 @@ app63.post("/__remote-auth/change-password", async (c) => {
28165
28538
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(c), mode: "change", changeError: "Failed to save password", redirect }), 200);
28166
28539
  }
28167
28540
  });
28168
- app63.get("/__remote-auth/setup", (c) => {
28541
+ app64.get("/__remote-auth/setup", (c) => {
28169
28542
  if (isRemoteAuthConfigured()) {
28170
28543
  return c.redirect("/");
28171
28544
  }
28172
28545
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(c), mode: "setup" }), 200);
28173
28546
  });
28174
- app63.post("/__remote-auth/set-initial-password", async (c) => {
28547
+ app64.post("/__remote-auth/set-initial-password", async (c) => {
28175
28548
  if (isRemoteAuthConfigured()) {
28176
28549
  return c.redirect("/");
28177
28550
  }
@@ -28209,10 +28582,10 @@ app63.post("/__remote-auth/set-initial-password", async (c) => {
28209
28582
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(c), mode: "setup", setupError: "Failed to save password. Please try again." }), 200);
28210
28583
  }
28211
28584
  });
28212
- app63.get("/api/remote-auth/status", (c) => {
28585
+ app64.get("/api/remote-auth/status", (c) => {
28213
28586
  return c.json({ configured: isRemoteAuthConfigured() });
28214
28587
  });
28215
- app63.post("/api/remote-auth/set-password", async (c) => {
28588
+ app64.post("/api/remote-auth/set-password", async (c) => {
28216
28589
  let body;
28217
28590
  try {
28218
28591
  body = await c.req.json();
@@ -28251,10 +28624,10 @@ app63.post("/api/remote-auth/set-password", async (c) => {
28251
28624
  return c.json({ error: "Failed to save password" }, 500);
28252
28625
  }
28253
28626
  });
28254
- app63.route("/api/_client-error", client_error_default);
28627
+ app64.route("/api/_client-error", client_error_default);
28255
28628
  console.log("[client-error-route] mounted");
28256
28629
  var PWA_PUBLIC_PATHS = /* @__PURE__ */ new Set(["/sw.js", ...PWA_SURFACES.map((s) => s.manifestPath)]);
28257
- app63.use("*", async (c, next) => {
28630
+ app64.use("*", async (c, next) => {
28258
28631
  const host = (c.req.header("host") ?? "").split(":")[0];
28259
28632
  const path3 = c.req.path;
28260
28633
  if (path3 === "/favicon.ico" || path3.startsWith("/assets/") || path3.startsWith("/brand/") || // Public free/busy is read by the booking page (a separate Cloudflare Pages
@@ -28298,27 +28671,28 @@ app63.use("*", async (c, next) => {
28298
28671
  }
28299
28672
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(c), redirect: path3 }), 200);
28300
28673
  });
28301
- app63.route("/api/health", health_default);
28302
- app63.route("/api/chat", chatRoutes);
28303
- app63.route("/api/whatsapp", whatsapp_default);
28304
- app63.route("/api/storage", storage_broker_default);
28305
- app63.route("/api/whatsapp-reader", whatsapp_reader_default);
28306
- app63.route("/api/public-reader", public_reader_default);
28307
- app63.route("/api/webchat", createWebchatRoutes({
28674
+ app64.route("/api/health", health_default);
28675
+ app64.route("/api/chat", chatRoutes);
28676
+ app64.route("/api/whatsapp", whatsapp_default);
28677
+ app64.route("/api/storage", storage_broker_default);
28678
+ app64.route("/api/whatsapp-reader", whatsapp_reader_default);
28679
+ app64.route("/api/public-reader", public_reader_default);
28680
+ app64.route("/api/webchat", createWebchatRoutes({
28308
28681
  handleInbound: (input) => webchatGateway.handleInbound(input),
28309
28682
  // Task 940 — the permission relay's pointer read + verdict write.
28310
28683
  pendingPromptFor: (key) => webchatGateway.pendingPromptFor(key),
28311
28684
  deliveryFailureFor: (key) => webchatGateway.deliveryFailureFor(key),
28312
28685
  resolvePermissionVerdict: (key, requestId, behavior) => webchatGateway.resolvePermissionVerdict(key, requestId, behavior)
28313
28686
  }));
28314
- app63.route("/api/webchat/greeting", webchat_greeting_default);
28315
- app63.route("/api/telegram", telegram_default);
28316
- app63.route("/api/quickbooks", quickbooks_default);
28317
- app63.route("/api/onboarding", onboarding_default);
28318
- app63.route("/api/admin", admin_default);
28319
- app63.route("/api/access", access_default);
28320
- app63.route("/api/session", session_default2);
28321
- app63.route("/api/calendar", calendar_public_default);
28687
+ app64.route("/api/webchat/greeting", webchat_greeting_default);
28688
+ app64.route("/api/telegram", telegram_default);
28689
+ app64.route("/api/quickbooks", quickbooks_default);
28690
+ app64.route("/api/onboarding", onboarding_default);
28691
+ app64.route("/api/admin", admin_default);
28692
+ app64.route("/api/access", access_default);
28693
+ app64.route("/api/session", session_default2);
28694
+ app64.route("/api/calendar", calendar_public_default);
28695
+ app64.route("/api/portal", portal_fetch_default);
28322
28696
  var SAFE_SLUG_RE2 = /^[a-z][a-z0-9-]{2,49}$/;
28323
28697
  var SAFE_FILENAME_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;
28324
28698
  var IMAGE_MIME = {
@@ -28330,7 +28704,7 @@ var IMAGE_MIME = {
28330
28704
  ".svg": "image/svg+xml",
28331
28705
  ".ico": "image/x-icon"
28332
28706
  };
28333
- app63.get("/agent-assets/:slug/:filename", (c) => {
28707
+ app64.get("/agent-assets/:slug/:filename", (c) => {
28334
28708
  const slug = c.req.param("slug");
28335
28709
  const filename = c.req.param("filename");
28336
28710
  if (!SAFE_SLUG_RE2.test(slug)) {
@@ -28346,8 +28720,8 @@ app63.get("/agent-assets/:slug/:filename", (c) => {
28346
28720
  console.error(`[agent-assets] no-account slug=${slug} file=${filename}`);
28347
28721
  return c.text("Not found", 404);
28348
28722
  }
28349
- const filePath = resolve36(account.accountDir, "agents", slug, "assets", filename);
28350
- const expectedDir = resolve36(account.accountDir, "agents", slug, "assets");
28723
+ const filePath = resolve37(account.accountDir, "agents", slug, "assets", filename);
28724
+ const expectedDir = resolve37(account.accountDir, "agents", slug, "assets");
28351
28725
  if (!filePath.startsWith(expectedDir + "/")) {
28352
28726
  console.error(`[agent-assets] path-traversal-rejected slug=${slug} file=${filename}`);
28353
28727
  return c.text("Forbidden", 403);
@@ -28359,13 +28733,13 @@ app63.get("/agent-assets/:slug/:filename", (c) => {
28359
28733
  const ext = "." + filename.split(".").pop()?.toLowerCase();
28360
28734
  const contentType = IMAGE_MIME[ext] || "application/octet-stream";
28361
28735
  console.log(`[agent-assets] serve slug=${slug} file=${filename} status=200`);
28362
- const body = readFileSync41(filePath);
28736
+ const body = readFileSync42(filePath);
28363
28737
  return c.body(body, 200, {
28364
28738
  "Content-Type": contentType,
28365
28739
  "Cache-Control": "public, max-age=3600"
28366
28740
  });
28367
28741
  });
28368
- app63.get("/generated/:filename", (c) => {
28742
+ app64.get("/generated/:filename", (c) => {
28369
28743
  const filename = c.req.param("filename");
28370
28744
  if (!SAFE_FILENAME_RE.test(filename) || filename.includes("..")) {
28371
28745
  console.error(`[generated] serve file=${filename} status=403`);
@@ -28376,8 +28750,8 @@ app63.get("/generated/:filename", (c) => {
28376
28750
  console.error(`[generated] serve file=${filename} status=404`);
28377
28751
  return c.text("Not found", 404);
28378
28752
  }
28379
- const filePath = resolve36(account.accountDir, "generated", filename);
28380
- const expectedDir = resolve36(account.accountDir, "generated");
28753
+ const filePath = resolve37(account.accountDir, "generated", filename);
28754
+ const expectedDir = resolve37(account.accountDir, "generated");
28381
28755
  if (!filePath.startsWith(expectedDir + "/")) {
28382
28756
  console.error(`[generated] serve file=${filename} status=403`);
28383
28757
  return c.text("Forbidden", 403);
@@ -28389,22 +28763,22 @@ app63.get("/generated/:filename", (c) => {
28389
28763
  const ext = "." + filename.split(".").pop()?.toLowerCase();
28390
28764
  const contentType = IMAGE_MIME[ext] || "application/octet-stream";
28391
28765
  console.log(`[generated] serve file=${filename} status=200`);
28392
- const body = readFileSync41(filePath);
28766
+ const body = readFileSync42(filePath);
28393
28767
  return c.body(body, 200, {
28394
28768
  "Content-Type": contentType,
28395
28769
  "Cache-Control": "public, max-age=86400"
28396
28770
  });
28397
28771
  });
28398
- app63.route("/sites", sites_default);
28399
- app63.route("/listings", listings_default);
28400
- app63.route("/v", visitor_event_default);
28401
- app63.route("/v", visitor_consent_default);
28772
+ app64.route("/sites", sites_default);
28773
+ app64.route("/listings", listings_default);
28774
+ app64.route("/v", visitor_event_default);
28775
+ app64.route("/v", visitor_consent_default);
28402
28776
  var htmlCache = /* @__PURE__ */ new Map();
28403
28777
  var brandLogoPath = "/brand/maxy-monochrome.png";
28404
28778
  var brandIconPath = "/brand/maxy-monochrome.png";
28405
28779
  if (BRAND_JSON_PATH && existsSync38(BRAND_JSON_PATH)) {
28406
28780
  try {
28407
- const fullBrand = JSON.parse(readFileSync41(BRAND_JSON_PATH, "utf-8"));
28781
+ const fullBrand = JSON.parse(readFileSync42(BRAND_JSON_PATH, "utf-8"));
28408
28782
  if (fullBrand.assets?.logo) brandLogoPath = `/brand/${fullBrand.assets.logo}`;
28409
28783
  brandIconPath = fullBrand.assets?.icon ? `/brand/${fullBrand.assets.icon}` : brandLogoPath;
28410
28784
  } catch {
@@ -28431,11 +28805,11 @@ var brandScript = `<script>window.__BRAND__=${JSON.stringify({
28431
28805
  var brandThemeColor = BRAND.defaultColors?.primary ?? "#000000";
28432
28806
  var brandBackgroundColor = BRAND.defaultColors?.background ?? "#ffffff";
28433
28807
  var brandAppIconsExist = [brandAppIcon192Path, brandAppIcon512Path, brandMaskableIconPath].every(
28434
- (p) => existsSync38(resolve36(process.cwd(), "public", p.replace(/^\//, "")))
28808
+ (p) => existsSync38(resolve37(process.cwd(), "public", p.replace(/^\//, "")))
28435
28809
  );
28436
28810
  var SW_SOURCE = (() => {
28437
28811
  try {
28438
- return readFileSync41(resolve36(process.cwd(), "public", "sw.js"), "utf-8");
28812
+ return readFileSync42(resolve37(process.cwd(), "public", "sw.js"), "utf-8");
28439
28813
  } catch {
28440
28814
  return null;
28441
28815
  }
@@ -28443,9 +28817,9 @@ var SW_SOURCE = (() => {
28443
28817
  function readInstalledVersion() {
28444
28818
  try {
28445
28819
  if (!PLATFORM_ROOT8) return "unknown";
28446
- const versionFile = join42(PLATFORM_ROOT8, "config", `.${BRAND.hostname}-version`);
28820
+ const versionFile = join44(PLATFORM_ROOT8, "config", `.${BRAND.hostname}-version`);
28447
28821
  if (!existsSync38(versionFile)) return "unknown";
28448
- const content = readFileSync41(versionFile, "utf-8").trim();
28822
+ const content = readFileSync42(versionFile, "utf-8").trim();
28449
28823
  return content || "unknown";
28450
28824
  } catch {
28451
28825
  return "unknown";
@@ -28486,7 +28860,7 @@ var clientErrorReporterScript = `<script>
28486
28860
  function cachedHtml(file) {
28487
28861
  let html = htmlCache.get(file);
28488
28862
  if (!html) {
28489
- html = readFileSync41(resolve36(process.cwd(), "public", file), "utf-8");
28863
+ html = readFileSync42(resolve37(process.cwd(), "public", file), "utf-8");
28490
28864
  const productNameEsc = escapeHtml(BRAND.productName);
28491
28865
  html = html.replace(/<title>([^<]*)<\/title>/, (_match, inner) => `<title>${escapeHtml(inner).replace(/Maxy/g, productNameEsc)}</title>`);
28492
28866
  html = html.replace('href="/favicon.ico"', `href="${escapeHtml(brandFaviconPath)}"`);
@@ -28503,13 +28877,13 @@ ${clientErrorReporterScript}
28503
28877
  return html;
28504
28878
  }
28505
28879
  function loadBrandingCache(agentSlug) {
28506
- const configDir2 = join42(homedir4(), BRAND.configDir);
28880
+ const configDir2 = join44(homedir4(), BRAND.configDir);
28507
28881
  try {
28508
28882
  const accountId = getDefaultAccountId();
28509
28883
  if (!accountId) return null;
28510
- const cachePath = join42(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
28884
+ const cachePath = join44(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
28511
28885
  if (!existsSync38(cachePath)) return null;
28512
- return JSON.parse(readFileSync41(cachePath, "utf-8"));
28886
+ return JSON.parse(readFileSync42(cachePath, "utf-8"));
28513
28887
  } catch {
28514
28888
  return null;
28515
28889
  }
@@ -28553,7 +28927,7 @@ function brandedPublicHtml(agentSlug) {
28553
28927
  function agentUnavailableHtml() {
28554
28928
  return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>${escapeHtml(BRAND.productName)}</title></head><body style="font-family:system-ui,sans-serif;max-width:32rem;margin:4rem auto;padding:0 1.5rem;color:#222"><h1 style="font-size:1.25rem">Agent unavailable</h1><p>This agent isn't available right now. If you reached this page from a saved link, the agent may have been turned off.</p></body></html>`;
28555
28929
  }
28556
- app63.get("/", (c) => {
28930
+ app64.get("/", (c) => {
28557
28931
  const host = (c.req.header("host") ?? "").split(":")[0];
28558
28932
  const klass = classifyHost(host, getOperatorDomains(), isPublicHost);
28559
28933
  if (klass === "operator") {
@@ -28575,12 +28949,12 @@ app63.get("/", (c) => {
28575
28949
  console.log(`[host-class] host=${host} class=admin served=index.html`);
28576
28950
  return c.html(cachedHtml("index.html"));
28577
28951
  });
28578
- app63.get("/public", (c) => {
28952
+ app64.get("/public", (c) => {
28579
28953
  const host = (c.req.header("host") ?? "").split(":")[0];
28580
28954
  if (isPublicHost(host)) return c.text("Not found", 404);
28581
28955
  return c.html(cachedHtml("public.html"));
28582
28956
  });
28583
- app63.get("/public-chat", (c) => {
28957
+ app64.get("/public-chat", (c) => {
28584
28958
  const host = (c.req.header("host") ?? "").split(":")[0];
28585
28959
  if (isPublicHost(host)) return c.text("Not found", 404);
28586
28960
  return c.html(cachedHtml("public.html"));
@@ -28599,12 +28973,12 @@ async function logViewerFetch(c, next) {
28599
28973
  duration_ms: Date.now() - start
28600
28974
  });
28601
28975
  }
28602
- app63.use("/vnc-viewer.html", logViewerFetch);
28603
- app63.use("/vnc-popout.html", logViewerFetch);
28604
- app63.get("/vnc-popout.html", (c) => {
28976
+ app64.use("/vnc-viewer.html", logViewerFetch);
28977
+ app64.use("/vnc-popout.html", logViewerFetch);
28978
+ app64.get("/vnc-popout.html", (c) => {
28605
28979
  let html = htmlCache.get("vnc-popout.html");
28606
28980
  if (!html) {
28607
- html = readFileSync41(resolve36(process.cwd(), "public", "vnc-popout.html"), "utf-8");
28981
+ html = readFileSync42(resolve37(process.cwd(), "public", "vnc-popout.html"), "utf-8");
28608
28982
  const name = escapeHtml(BRAND.productName);
28609
28983
  html = html.replace("<title>Browser \u2014 Maxy</title>", `<title>${name}</title>`);
28610
28984
  html = html.replace("</head>", ` ${brandScript}
@@ -28614,7 +28988,7 @@ app63.get("/vnc-popout.html", (c) => {
28614
28988
  }
28615
28989
  return c.html(html);
28616
28990
  });
28617
- app63.post("/api/vnc/client-event", async (c) => {
28991
+ app64.post("/api/vnc/client-event", async (c) => {
28618
28992
  let body;
28619
28993
  try {
28620
28994
  body = await c.req.json();
@@ -28635,11 +29009,11 @@ app63.post("/api/vnc/client-event", async (c) => {
28635
29009
  });
28636
29010
  return c.json({ ok: true });
28637
29011
  });
28638
- app63.get("/g/:slug", (c) => {
29012
+ app64.get("/g/:slug", (c) => {
28639
29013
  return c.html(brandedPublicHtml(resolveDefaultSlug() ?? void 0));
28640
29014
  });
28641
29015
  for (const pwa of PWA_SURFACES) {
28642
- app63.get(pwa.manifestPath, (c) => {
29016
+ app64.get(pwa.manifestPath, (c) => {
28643
29017
  const manifest = buildManifest(pwa, {
28644
29018
  productName: BRAND.productName,
28645
29019
  appIcon192: brandAppIcon192Path,
@@ -28655,7 +29029,7 @@ for (const pwa of PWA_SURFACES) {
28655
29029
  return c.body(JSON.stringify(manifest));
28656
29030
  });
28657
29031
  }
28658
- app63.get("/sw.js", (c) => {
29032
+ app64.get("/sw.js", (c) => {
28659
29033
  if (SW_SOURCE == null) {
28660
29034
  console.error("[pwa] op=sw status=500 reason=sw-source-missing");
28661
29035
  return c.text("Service worker unavailable", 500);
@@ -28664,12 +29038,12 @@ app63.get("/sw.js", (c) => {
28664
29038
  console.log(`[pwa] op=sw status=200 ct=${SW_CONTENT_TYPE}`);
28665
29039
  return c.body(SW_SOURCE);
28666
29040
  });
28667
- app63.get("/graph", (c) => {
29041
+ app64.get("/graph", (c) => {
28668
29042
  const host = (c.req.header("host") ?? "").split(":")[0];
28669
29043
  if (isPublicHost(host) || isOperatorHost(host, getOperatorDomains())) return c.text("Not found", 404);
28670
29044
  return c.html(cachedHtml("graph.html"));
28671
29045
  });
28672
- app63.get("/chat", (c) => {
29046
+ app64.get("/chat", (c) => {
28673
29047
  const host = (c.req.header("host") ?? "").split(":")[0];
28674
29048
  if (isOperatorHost(host, getOperatorDomains())) {
28675
29049
  console.log(`[host-class] host=${host} class=operator served=operator.html`);
@@ -28678,32 +29052,32 @@ app63.get("/chat", (c) => {
28678
29052
  if (isPublicHost(host)) return c.text("Not found", 404);
28679
29053
  return c.html(cachedHtml("chat.html"));
28680
29054
  });
28681
- app63.get("/data", (c) => {
29055
+ app64.get("/data", (c) => {
28682
29056
  const host = (c.req.header("host") ?? "").split(":")[0];
28683
29057
  if (isPublicHost(host)) return c.text("Not found", 404);
28684
29058
  return c.html(cachedHtml("data.html"));
28685
29059
  });
28686
- app63.get("/tasks", (c) => {
29060
+ app64.get("/tasks", (c) => {
28687
29061
  const host = (c.req.header("host") ?? "").split(":")[0];
28688
29062
  if (isPublicHost(host)) return c.text("Not found", 404);
28689
29063
  return c.html(cachedHtml("tasks.html"));
28690
29064
  });
28691
- app63.get("/activity", (c) => {
29065
+ app64.get("/activity", (c) => {
28692
29066
  const host = (c.req.header("host") ?? "").split(":")[0];
28693
29067
  if (isPublicHost(host)) return c.text("Not found", 404);
28694
29068
  return c.html(cachedHtml("activity.html"));
28695
29069
  });
28696
- app63.get("/calendar", (c) => {
29070
+ app64.get("/calendar", (c) => {
28697
29071
  const host = (c.req.header("host") ?? "").split(":")[0];
28698
29072
  if (isPublicHost(host)) return c.text("Not found", 404);
28699
29073
  return c.html(cachedHtml("calendar.html"));
28700
29074
  });
28701
- app63.get("/browser", (c) => {
29075
+ app64.get("/browser", (c) => {
28702
29076
  const host = (c.req.header("host") ?? "").split(":")[0];
28703
29077
  if (isPublicHost(host) || isOperatorHost(host, getOperatorDomains())) return c.text("Not found", 404);
28704
29078
  return c.html(cachedHtml("browser.html"));
28705
29079
  });
28706
- app63.get("/:slug", async (c, next) => {
29080
+ app64.get("/:slug", async (c, next) => {
28707
29081
  const slug = c.req.param("slug");
28708
29082
  if (AGENT_SLUG_PATTERN.test(`/${slug}`)) {
28709
29083
  const account = resolveAccount();
@@ -28718,13 +29092,13 @@ app63.get("/:slug", async (c, next) => {
28718
29092
  await next();
28719
29093
  });
28720
29094
  if (brandFaviconPath !== "/favicon.ico") {
28721
- app63.get("/favicon.ico", (c) => {
29095
+ app64.get("/favicon.ico", (c) => {
28722
29096
  c.header("Cache-Control", "public, max-age=300");
28723
29097
  return c.redirect(brandFaviconPath, 302);
28724
29098
  });
28725
29099
  }
28726
- app63.use("/*", serveStatic({ root: "./public" }));
28727
- app63.all("*", (c) => {
29100
+ app64.use("/*", serveStatic({ root: "./public" }));
29101
+ app64.all("*", (c) => {
28728
29102
  const host = (c.req.header("host") ?? "").split(":")[0];
28729
29103
  const path3 = c.req.path;
28730
29104
  if (isPublicHost(host)) {
@@ -28738,11 +29112,11 @@ app63.all("*", (c) => {
28738
29112
  });
28739
29113
  var port = requirePortEnv("MAXY_UI_INTERNAL_PORT", { tag: "ui-server" });
28740
29114
  var hostname = process.env.HOSTNAME ?? "127.0.0.1";
28741
- var httpServer = serve({ fetch: app63.fetch, port, hostname });
29115
+ var httpServer = serve({ fetch: app64.fetch, port, hostname });
28742
29116
  console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28743
29117
  {
28744
- const reconcilePlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join42(__dirname, "..");
28745
- const reconcileScript = resolve36(reconcilePlatformRoot, "plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js");
29118
+ const reconcilePlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join44(__dirname, "..");
29119
+ const reconcileScript = resolve37(reconcilePlatformRoot, "plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js");
28746
29120
  const RECONCILE_INTERVAL_MS = 12e4;
28747
29121
  const runReconcile = (ctx) => {
28748
29122
  if (!existsSync38(reconcileScript)) return;
@@ -28767,8 +29141,8 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28767
29141
  });
28768
29142
  }
28769
29143
  {
28770
- const outlookPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join42(__dirname, "..");
28771
- const outlookScript = resolve36(outlookPlatformRoot, "plugins/outlook/mcp/dist/scripts/complete-registration.js");
29144
+ const outlookPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join44(__dirname, "..");
29145
+ const outlookScript = resolve37(outlookPlatformRoot, "plugins/outlook/mcp/dist/scripts/complete-registration.js");
28772
29146
  const OUTLOOK_COMPLETE_INTERVAL_MS = 3e4;
28773
29147
  const runOutlookComplete = (ctx) => {
28774
29148
  if (!existsSync38(outlookScript)) return;
@@ -28793,8 +29167,8 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28793
29167
  });
28794
29168
  }
28795
29169
  {
28796
- const auditRoot = process.env.MAXY_PLATFORM_ROOT ?? join42(__dirname, "..");
28797
- const strandedAccountsDir = resolve36(auditRoot, "..", "data/accounts");
29170
+ const auditRoot = process.env.MAXY_PLATFORM_ROOT ?? join44(__dirname, "..");
29171
+ const strandedAccountsDir = resolve37(auditRoot, "..", "data/accounts");
28798
29172
  const STRANDED_AUDIT_INTERVAL_MS = 3e5;
28799
29173
  const STRANDED_AGE_MS = 16 * 6e4;
28800
29174
  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;
@@ -28804,7 +29178,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28804
29178
  const now = Date.now();
28805
29179
  for (const name of readdirSync26(strandedAccountsDir)) {
28806
29180
  if (!STRANDED_UUID_RE.test(name)) continue;
28807
- const pendingPath2 = resolve36(strandedAccountsDir, name, "secrets/outlook/pending-devicecode.enc");
29181
+ const pendingPath2 = resolve37(strandedAccountsDir, name, "secrets/outlook/pending-devicecode.enc");
28808
29182
  if (!existsSync38(pendingPath2)) continue;
28809
29183
  const ageMs = now - statSync17(pendingPath2).mtimeMs;
28810
29184
  if (ageMs > STRANDED_AGE_MS) {
@@ -28823,8 +29197,8 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28823
29197
  });
28824
29198
  }
28825
29199
  {
28826
- const googleRoot = process.env.MAXY_PLATFORM_ROOT ?? join42(__dirname, "..");
28827
- const googleAccountsDir = resolve36(googleRoot, "..", "data/accounts");
29200
+ const googleRoot = process.env.MAXY_PLATFORM_ROOT ?? join44(__dirname, "..");
29201
+ const googleAccountsDir = resolve37(googleRoot, "..", "data/accounts");
28828
29202
  const GOOGLE_PENDING_AUDIT_INTERVAL_MS = 3e5;
28829
29203
  const runGooglePendingAuditSafe = () => {
28830
29204
  try {
@@ -28839,7 +29213,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28839
29213
  firstRunDelayMs: 27e3,
28840
29214
  run: runGooglePendingAuditSafe
28841
29215
  });
28842
- const googleAuditScript = resolve36(googleRoot, "plugins/google/mcp/dist/scripts/account-audit.js");
29216
+ const googleAuditScript = resolve37(googleRoot, "plugins/google/mcp/dist/scripts/account-audit.js");
28843
29217
  const GOOGLE_ACCOUNT_AUDIT_INTERVAL_MS = 36e5;
28844
29218
  const runGoogleAccountAudit = (ctx) => {
28845
29219
  if (!existsSync38(googleAuditScript)) return;
@@ -28867,7 +29241,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28867
29241
  });
28868
29242
  }
28869
29243
  {
28870
- const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join42(__dirname, "..");
29244
+ const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join44(__dirname, "..");
28871
29245
  const STORAGE_AUDIT_INTERVAL_MS = 3e5;
28872
29246
  const runStorageAuditSafe = () => runStorageAudit(auditPlatformRoot).catch((err) => {
28873
29247
  console.error(`[storage-audit] error="${err.message}"`);
@@ -28901,8 +29275,8 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28901
29275
  });
28902
29276
  }
28903
29277
  {
28904
- const publishPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join42(__dirname, "..");
28905
- const publishScript = resolve36(publishPlatformRoot, "plugins/scheduling/mcp/dist/scripts/publish-availability.js");
29278
+ const publishPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join44(__dirname, "..");
29279
+ const publishScript = resolve37(publishPlatformRoot, "plugins/scheduling/mcp/dist/scripts/publish-availability.js");
28906
29280
  const PUBLISH_INTERVAL_MS = 3e5;
28907
29281
  const currentAccount = () => {
28908
29282
  try {
@@ -28932,21 +29306,21 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28932
29306
  }
28933
29307
  };
28934
29308
  const auditSnapshotAge = (account) => {
28935
- const availPath = resolve36(account.accountDir, "calendar-availability.json");
29309
+ const availPath = resolve37(account.accountDir, "calendar-availability.json");
28936
29310
  let hasBookingSite = false;
28937
29311
  try {
28938
29312
  if (existsSync38(availPath)) {
28939
- const cfg = JSON.parse(readFileSync41(availPath, "utf-8"));
29313
+ const cfg = JSON.parse(readFileSync42(availPath, "utf-8"));
28940
29314
  hasBookingSite = typeof cfg.bookingDbName === "string" && cfg.bookingDbName.length > 0;
28941
29315
  }
28942
29316
  } catch {
28943
29317
  }
28944
29318
  if (!hasBookingSite) return;
28945
- const statePath = resolve36(account.accountDir, "state", "booking-availability", "last-publish.json");
29319
+ const statePath = resolve37(account.accountDir, "state", "booking-availability", "last-publish.json");
28946
29320
  let lastSuccessAt = null;
28947
29321
  if (existsSync38(statePath)) {
28948
29322
  try {
28949
- const rec = JSON.parse(readFileSync41(statePath, "utf-8"));
29323
+ const rec = JSON.parse(readFileSync42(statePath, "utf-8"));
28950
29324
  lastSuccessAt = rec.lastSuccessAt ?? null;
28951
29325
  } catch {
28952
29326
  console.error(`[calendar-availability] op=audit accountId=${account.accountId} snapshotAgeMs=na reason=bad-state-file`);
@@ -28974,8 +29348,8 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28974
29348
  startTimeEntryCensus(() => getSession());
28975
29349
  startLedgerCensus(() => getSession());
28976
29350
  {
28977
- const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join42(__dirname, "..");
28978
- const auditScript = resolve36(auditPlatformRoot, "plugins/connector/mcp/dist/scripts/audit-connectors.js");
29351
+ const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join44(__dirname, "..");
29352
+ const auditScript = resolve37(auditPlatformRoot, "plugins/connector/mcp/dist/scripts/audit-connectors.js");
28979
29353
  const auditLogDir = process.env.LOG_DIR ?? LOG_DIR;
28980
29354
  const CONNECTOR_AUDIT_INTERVAL_MS = 3e5;
28981
29355
  const runConnectorAudit = (ctx) => {
@@ -29047,6 +29421,7 @@ var SUBAPP_MANIFEST = [
29047
29421
  { prefix: "/api/access", file: "server/routes/access/index.ts", subapp: access_default },
29048
29422
  { prefix: "/api/session", file: "server/routes/session.ts", subapp: session_default2 },
29049
29423
  { prefix: "/api/calendar", file: "server/routes/calendar-public.ts", subapp: calendar_public_default },
29424
+ { prefix: "/api/portal", file: "server/routes/portal-fetch.ts", subapp: portal_fetch_default },
29050
29425
  { prefix: "/api/_client-error", file: "server/routes/client-error.ts", subapp: client_error_default },
29051
29426
  { prefix: "/listings", file: "server/routes/listings.ts", subapp: listings_default },
29052
29427
  { prefix: "/v", file: "server/routes/visitor-event.ts", subapp: visitor_event_default },
@@ -29058,7 +29433,7 @@ for (const m of SUBAPP_MANIFEST) {
29058
29433
  }
29059
29434
  try {
29060
29435
  const registered = [];
29061
- for (const r of app63.routes ?? []) {
29436
+ for (const r of app64.routes ?? []) {
29062
29437
  if (typeof r.path !== "string" || r.path.includes(":") || r.path.includes("*")) continue;
29063
29438
  if (AGENT_SLUG_PATTERN.test(r.path)) {
29064
29439
  registered.push({ method: (r.method ?? "ALL").toUpperCase(), path: r.path });
@@ -29102,7 +29477,7 @@ async function runAdminUserReconcileTick() {
29102
29477
  console.error("[adminuser-self-heal] skip reason=no-users-file");
29103
29478
  return;
29104
29479
  }
29105
- const usersRaw = readFileSync41(USERS_FILE, "utf-8").trim();
29480
+ const usersRaw = readFileSync42(USERS_FILE, "utf-8").trim();
29106
29481
  if (!usersRaw) {
29107
29482
  console.error("[adminuser-self-heal] skip reason=empty-users-file");
29108
29483
  return;
@@ -29150,7 +29525,7 @@ registerLoop({
29150
29525
  var USERS_RECONCILE_INTERVAL_MS = 60 * 60 * 1e3;
29151
29526
  function countUsersRows() {
29152
29527
  if (!existsSync38(USERS_FILE)) return 0;
29153
- const raw = readFileSync41(USERS_FILE, "utf-8").trim();
29528
+ const raw = readFileSync42(USERS_FILE, "utf-8").trim();
29154
29529
  if (!raw) return 0;
29155
29530
  const users = JSON.parse(raw);
29156
29531
  return users.filter((u) => typeof u.userId === "string").length;
@@ -29283,7 +29658,7 @@ if (bootAccountConfig?.whatsapp) {
29283
29658
  }
29284
29659
  init({
29285
29660
  configDir: configDirForWhatsApp,
29286
- platformRoot: resolve36(process.env.MAXY_PLATFORM_ROOT ?? join42(__dirname, "..")),
29661
+ platformRoot: resolve37(process.env.MAXY_PLATFORM_ROOT ?? join44(__dirname, "..")),
29287
29662
  accountConfig: bootAccountConfig,
29288
29663
  onMessage: async (msg) => {
29289
29664
  if (msg.isOwnerMirror) {